Notes on GNU Make

build-systems
 
make
 

GNU Make is a tool to manage build systems. It binds targets to some dependencies and a set of commands.

Makefile

The program make gets its rules that instruct the generation of the targets via the makefile. The makefile is determined in the following order:

  1. If the file options is passed (make [-f|--file] filename) to make, then filename is selected as its makefile;
  2. GNUmakefile is selected next, although its generally discouraged, unless the makefile explicitly requires GNU Make (as opposed to other distributions);
  3. If non the first exist in the directory, then makefile is selected;
  4. Lastly, Makefile. This is the recommended name because appears at the beginning of a ls and it is close to other project config files.

Recipe and Execution

A makefile consists of one or more recipes, each corresponding to one target and its dependencies and commands:

# The commands must start with TAB. Cannot be 4 spaces.
[target1 target2 ...] : [dependency1 dependency2 ...]
	command1
	command2
	...

To generate the targets, simply run:

$ make [target1 target2 ...]

and targets will be generated in this order. If a previous target fails, the program stops and the commands for the following targets will be abandoned. For example we have makefile

target: dep
	touch target
target2:
	touch target2

where dep doesn’t exist and leads to target failing to build executing

$ make target target2
make: *** No rule to make target 'dep', needed by 'target'.  Stop.

The execution stops right away and target2 is not created because of the previous failure.

By default, if no target is passed to make, make executes the commands for the first target in the makefile, excluding those starting with a dot (.). In the previous example, make is equivalent of make target.

When a recipe is repeated for the same target, the former one is overridden. For example, if we append

target:
	echo override > target

to the previous example, we have

$ make
Makefile\:6\: warning: overriding recipe for target 'target'
Makefile\:2\: warning: ignoring old recipe for target 'target'
echo override > target

Above is true for recipes, but in the case of dependencies, the dependencies in the additional rule is added to the target dependencies (instead of overwriting). And if the additional rule only specifies dependencies but not recipes, it will not yield a warning message. For example, if we instead append a new rule

target: dep

then dep is added to target’s dependency, and running make target doesn’t give any warning.

Dependencies

Dependencies in make have two instructions:

  1. Build Instruction. Dependencies either need to be built first if they are targets (leading to a build dependency-graph), or need to exist as files (leading to failure if non-existing);
  2. Rebuild instruction. If dependency is newer than target, rebuild is required.

If dependencies cannot be resolved (e.g. circular dependency), then it is ignored:

# A simple example "target: target"
$ make target
make: Circular target <- target dependency dropped.

GNU describes dependencies in more details in Types of Prerequisites, where it further describes a special order-only prerequisites that specifies only instruction #1 for special use cases like directory creation.

This behavior guarantees that targets are only rebuilt when necessary. To force a rebuild when dependencies rules deem it unnecessary, use

# Option forcefully consider all dependencies out-of-date.
$ make [-B|--always-make]

Special Targets

Make has a list of special targets that are used for specific causes.

Oftentimes, these special targets impose specific rules to their dependencies. For example, .PHONY’s dependencies are considered phony targets, meaning their recipe are always run. This is useful for abstract targets that does not correspond to physical files, e.g. clean, all.

But there are also special targets that offer generic instruction (similar to a flag), e.g. DELETE_ON_ERROR (if specified, failed recipe leads to deletion of the target) and others that provide rules via their recipes, e.g. .DEFAULT (its recipe is used for targets with no rules). For a comprehensive list, see Special Built-in Target Names.

Target names not starting with . do not have special meaning. However, GNU recommends some Standard Targets like all, clean, etc.

Macros

For a more complex makefile, make supports macros to complement the target rules. For macros, important concepts includes variables, directives, functions.

Variables

Make variables are names to represent texts. Variables in make work by text substitution and they can be used in any context across the makefile by dereferencing using $(var) or ${var}. Variables come in two flavors: Simply Expanded Variable and Recursively Expanded Variables.

A simply expanded variable is defined by := or ::=, and is similar to general concept of a variable in well-known programming languages. The variable is resolved at the time of the variable definition:

var1 := a
var2 := $(var1)
var1 := b
test: ; @echo $(var2)
# prints 'b'

A recursively expanded variable is defined by define or =, and is similar to target dependencies graph. It attempts to resolve all referenced variables on the right hand side before expanding:

var1 := a
var2 = $(var1)
var1 := b
test: ; @echo $(var2)
# prints 'a'

. On the case where a circular dependency occur, an error is reported:

var1 := a
var2 = $(var1)
var1 = $(var2)
test: ; @echo $(var2)
# Recursive variable 'var1' references itself (eventually).  Stop. 

?=, += are some additional assignment operators that make supports. Apart from assignments, variables can also get their values from environment variables, pass-in from make arguments, etc.

Directives

Directives adds another layer of control for make. The most impactful ones being define (variable manipulation), include (scalability) and if controls. A full list of supported directives can be found in Make Manual Appendix.

Functions

Make has a rich selection of built-in functions for text manipulation, such as patsubst, wildcard, etc. To call a function, use syntax $(func arg1,arg2...). GNU provides a comprehensive list of built-in functions.

If the built-in function are not enough, additional options such as using call function to integrate built-in functions and operations or canned variables for more flexibility.

Symbols

Lastly, let’s overview some special symbols used by make.

References