Rediscovering make: the power behind rules January 18th, 2018

I used to think makefiles were just a convenient way to list groups of shell commands; over time I’ve learned how powerful, flexible, and full-featured they are. This post brings to light over some of those features related to rules.

note: all of this is valid for GNU Makefiles, if you need to support BSD Makefiles you’ll find that some features are lacking while new ones are added. Thanks to zge for the pointer

Rules are instructions that indicate make how and when a file called the target should be built. The target can depend on other files called prerequisites.

You instruct make how to build the target in the recipe, which is no more than a set of shell commands to be executed, one at a time, in the order they appear. The syntax looks like this:

target_name : prerequisites recipe

Once you have defined a rule, you can build the target from the command line by executing:

$ make target_name

Once the target is built, make is smart enough to not run the recipe ever again unless at least one of the prerequisites has changed.

Prerequisites indicate two things:

When the target should be built: if a prerequisite is newer than the target, make assumes that the target should be built.

assumes that the target should be built. An order of execution: since prerequisites can, in turn, be built by another rule on the makefile, they also implicitly set an order on which rules are executed.

If you want to define an order, but you don’t want to rebuild the target if the prerequisite changes, you can use a special kind of prerequisite called order only, which can be placed after the normal prerequisites, separated by a pipe ( | )

For convenience, make accepts patterns for targets and prerequisites. A pattern is defined by including the % character, a wildcard that matches any number of literal characters or an empty string. Here are some examples:

% : match any file

: match any file %.md : match all files with the .md extension

: match all files with the extension prefix%.go : match all files that start with prefix that have the .go extension

There’s a set of target names that have special meaning for make called special targets.

You can find the full list of special targets in the documentation. As a rule of thumb, special targets start with a dot followed by uppercase letters.

Here are a few useful ones:

.PHONY: Indicates make that the prerequisites of this target are considered to be phony targets, which means that make will always run it’s recipe regardless of whether a file with that name exists or what its last-modification time is.

.DEFAULT: Used for any target for which no rules are found.

.IGNORE: If you specify prerequisites for .IGNORE , make will ignore errors in execution of their recipes.

Substitutions are useful when you need to modify the value of a variable with alterations that you specify.

A substitution has the form $(var:a=b) and its meaning is to take the value of the variable var , replace every a at the end of a word with b in that value, and substitute the resulting string. For example:

foo := a.o bar := $( foo:.o = .c ) # sets bar to a.c

note: special thanks to Luis Lavena for letting me know about the existence of substitutions.

Archive files are used to collect multiple data files together into a single file (same concept as a zip file), they are built with the ar Unix utility. ar can be used to create archives for any purpose, but has been largely replaced by tar for any other purposes than static libraries.

In make , you can use an individual member of an archive file as a target or prerequisite as follows:

archive(member) : prerequisite recipe

There’s a lot more to discover about make, but at least this counts as a start, I strongly encourage you to check the documentation, create a dumb makefile, and just play with it.