pete > guides > make

GNU make is quite versatile but is most often used to compile software. The official documentation is huge; following is a very short overview of features you might find handy.

Manually typing gcc commands gets tedious, especially when the commands are the same over and over and over again. You can save those commands in a Makefile (filename is case-sensitive) and then use make to invoke them for you.

Basic make

Sample Makefile:

#    +------------------------- target
#    |
#    |            +----------+-----+--------- dependencies
#    |            |          |     |
#    V            V          V     V

arglebargle: arglebargle.o foo.o bar.o
    gcc -o arglebargle arglebargle.o foo.o bar.o

#   \__________________________________________/
#                       |
#                       +------------------- commands

With this file in the current directory (along with arglebargle.o, foo.o, and bar.o), you can ask make to build arglebargle:

$ make arglebargle
gcc -o arglebargle arglebargle.o foo.o bar.o

Helpfully, make prints the command it's executing before actually doing it.

If you run it again, however, make is going to output something different:

$ make arglebargle 
make: 'arglebargle' is up to date.

This is because make compares the modification times of the target and the dependencies and only rebuilds the former if any of the latter are newer. This is extremely helpful when projects grow to a non-trivial number of source files.

make clean

One popular target is "clean", convention dictating that it deletes built targets, resulting in a clean environment. But this target doesn't actually create the file "clean", so make needs a hint to that effect:

.PHONY: clean
    rm -f arglebargle

Test it:

$ make clean
rm -f arglebargle
$ make arglebargle
gcc -o arglebargle arglebargle.o foo.o bar.o

Recursive make

Consider this Makefile:

a: b
    /usr/bin/do_something b > a

b: c
    /usr/bin/do_something_else c > b

Making "a" requires "b", but making "b" requires "c".

More make

With no arguments, make will use the first target in the file.

Invoking make with the -n argument will cause it to print the commands it would execute, without actually doing anything. This is helpful for debugging Makefiles themselves.

Abbreviations in commands

There are some built-in abbreviations you can use to make writing rules easier. There are many more, but these are the ones I find myself using most often.

$@ is replaced by the target
$^ is replaced by the list of dependencies

So the sample Makefile above could be rewritten as:

arglebargle: arglebargle.o foo.o bar.o
    gcc -o $@ $^

Wildcards in targets and dependencies

If you've got a bunch of .c files you want to compile, providing rules for every single one of them is absurd, especially when it's the same rule except for the filenames involved. The following rule says, "to build any target that ends in .o, the dependencies are that same thing ending in .c".

%.o: %.c
    gcc -c -o $@ $^


If you find yourself duplicating parts of recipes, you might find variables helpful. They're just strings that are expanded verbatim. One popular use is for compiler flags:

CFLAGS=-g -O2 -Wall -pedantic

%.o: %.c
    gcc $(CFLAGS) -c -o $@ $^

Food for thought

Consider the following two Makefiles:

# Makefile A

arglebargle: arglebargle.c foo.c bar.c
    gcc -o $@ $^
# Makefile B

arglebargle: arglebargle.o foo.o bar.o
    gcc -o $@ $^

%.o: %.c
    gcc -c -o $@ $^

Why might one be preferable to the other? Think about what commands are run under different circumstances: when arglebargle doesn't exist, when all source files are newer than arglebargle, and when only foo.c is newer than arglebargle.

Last modified: