Makefiles

This article discusses various ways that makefiles can be used other than just to build software. A makefile can be used to group commands into a single file. Groups of commands can be given within a target, and can depend on other commands by depending on other targets.

Common setup

I use the following common setup at the top of most of my makefiles.

# Setup
override SHELL:=/bin/bash
override SHELLLOPTS:=errexit:pipefail
export SHLOPTS
.DEFAULT_GOAL=all

Non-recursive make.

I don't really like using recursive make when I can avoid it. I have a utility script that I can use with GNU make that helps to set up the variables at the top of a makefile. Then, instead of recursing into a makefile, I use the macros to include the file. Within an included file I call another macro at the very top in order to obtain the file and directory of that makefile (which may be relative to the top makefile directory).

# Push/pop macros
override STACK_POSITION:=stack

define _PUSH
override STACK_POSITION:=$$(STACK_POSITION).x
override $$(STACK_POSITION):=$$($(1))
endef
define PUSH
$(eval $(call _PUSH,$1))
endef

define _POP
override $(1):=$$($$(STACK_POSITION))
override STACK_POSITION:=$$(basename $$(STACK_POSITION))
endef
define POP
$(eval $(call _POP,$1))
endef

# This macro will automaticaly set __FILE__ in the included file.  Includes
# should be relative and not absolute. Once the macro is defined, all includes
# should use this macro, starting from the main makefile, otherwise the variable
# may not be set correct. The files listed in the macro are relative to the
# file that it is called in.
define _SUBINCLUDE1
$$(call PUSH,__FILE__)
override __FILE__:=$$(if $$(__FILE__),$$(dir $$(__FILE__)),./)$1
include $$(__FILE__)
$$(call POP,__FILE__)
endef
define SUBINCLUDE1
$(eval $(call _SUBINCLUDE1,$1))
endef

define _SUBINCLUDE
$$(call PUSH,I)
$$(foreach I,$$(wildcard $1),$$(call SUBINCLUDE1,$$I))
$$(call POP,I)
endef
define SUBINCLUDE
$(eval $(call _SUBINCLUDE,$1))
endef

This defines the following macros.

PUSH
This macro will push the value of a variable onto a stack.
POP
This macro will pop the value of the top of the stack into a variable.
SUBINCLUDE
This will include the files specified and set __FILE__.

Inside a makefile, it is possible to define local variables and macros by suffixing them. Since the values of variables may change outside, the immediate assignment (:=) should be used for variables. For any rules that use the variables, target specific variables should be assigned so the rule uses the correct values. Dependencies are handled correctly since they are evaluated when they are encounted.

override D:=$(dir $(__FILE__))

# Define a "local" variable
override TARGETS_$D:=$(addprefix $D,file1.html file2.html file3.html)

# Define a local macro
define MACRO_$D
	echo Targets under $D have been built
endef

# This target uses the D variable which may change outside the file
# The called macro also uses the D variable.  To ensure they use
# use the correct value, set a target-specific variable.
.PHONY: .all_$D
all: .all_$D
.all_$D: override D:=$D
.all_$D: $(TARGETS_$D)
	@call $(MACRO_$D)

# Include other files
$(call INCLUDE,subdir1/rules.mk subdir2/rules.mk subdir3/rules.mk)

# At this point, __FILE__ should be restored to what they were before
# however D may have changed unless it is pushed before and popped
# like this:
$(call PUSH,D)
$(call INCLUDE,subdir1/rules.mk subdir2/rules.mk subdir3/rules.mk)
$(call POP,D)

Synchronizing files to and from another computer

REMOTE=laptop.local

.PHONY: check
check:
    test -d documents -a -d projects

.PHONY: sync-to-laptop
sync-to-laptop: check
    rsync -av --delete-after ./ $(REMOTE):/home/user/Files

.PHONY: sync-from-laptop
sync-from-laptop: check
    rsync -av --delete-after $(REMOTE):/home/user/Files/ .