A short introduction to make

[article index] [] [@mattmight] [+mattmight] [rss]

make is a simple but powerful tool for managing a build process in a language-independent manner. (It won the ACM Software Systems award.)

make is a domain-specific language for encoding a dependence graph.

In this graph, a node is a file, and each node contains instructions on how to synthesize that file from its dependencies.

By invoking make file, the user instructs make to synthesize file.

make is widely available on Unix systems and easy to use with little set up, yet, in my experience, few students have ever touched it.

This article covers the basics of make for new users.

Quick start

If you're working in a project that already uses make, there should be a file called Makefile in the top-level directory.

Running:

$ make

should build the project from source.

What is make?

make is a tool designed to manage dependencies in a build process.

For instance, if you have two source files, a.in and b.in, and you need to build b.in before you build a.in, then you might have a Makefile in the same directory that looks like this:

a.out: a.in b.out
	commands to generate a.out from a.in and b.out

b.out: b.in
	commands to generate b.out from b.in

Now, suppose you want to build a.out; you run:

$ make a.out

At this point, make's thought process becomes:

  1. Run make b.out.
  2. If a.out is newer than a.in and b.out, halt.
  3. If a.out is older than a.in or b.out, run:
        commands to generate a.out from a.in and b.out
      

If you run:

$ make b.out

then make's thought process becomes:

  1. If b.out is newer than b.in, halt.
  2. If b.out is older than b.in, run:
       commands to generate b.out from b.in
     

In short, when ordered to build a file, make will ensure that all dependencies are up to date, and it will not rebuild any those which need not be rebuilt.

Makefile structure

Makefiles contain definitions and rules.

A definition has the form:

VAR=value

A rule has the form:

output files: input files
	commands to turn inputs to outputs

All commands must be tab-indented.

To reference the variable VAR, surround it with $(VAR).

A simple example for C

Here's a simple example for a C program that defines foo() in foo.c, but uses it in bar.c.

CC=gcc

bar: bar.c foo.h foo.o
	$(CC) -o bar bar.c foo.o

foo.o: foo.c foo.h
	$(CC) -c foo.c

run:
	./bar

clean:
	rm -f bar foo.o

Conventions

It's common to include pseudo-targets in makefiles to automate other parts of the development process.

make clean

The rule for clean in the previous example doesn't build a file called clean.

Rather, it deletes all of the build targets to allow a fresh build from scratch.

Including a clean rule is a common convention for make.

make test

Running make test often runs the test suite for the software.

make install

Running make install typically installs the compiled binary in a PATH-accessible location.

For software using the autoconf toolchain, the installation directory is set by running ./configure command prior to using make.

make run

For simple programs, make run acts like an alias to the compiled binary.

Generic rules

If a project contains many conversions of one file type to another, e.g. .c to .o, it gets tedious to write the commands over and over.

First, define file suffixes with a .SUFFIXES declaration; for example:

.SUFFIXES: .tex .pdf

Then, create a generic rule for converting one suffix to another:

.input.output:
	commands to convert something.input into something.output

Within the commands, you can reference the input filename with $<.

You can reference the output filename with $@.

For a complete example with LaTeX:

all: doc.pdf

.SUFFIXES: .tex .pdf

.tex.pdf:
	pdflatex $<

Running make file.pdf will now run pdflatex file.tex.

Complex generic rules

For complex generic rules, the pattern % may be used in both inputs and outputs.

For example, we might want a LaTeX build to depend on both the source file and the auto-generated bibliography file:

%.dvi: %.tex %.blg
	latex $<

Somewhat confusingly, the commands for these generic rules cannot reference % to insert the basename of the build target.

To refer to the basename of the target file, use $(basename $@).

Substituting shell commands

To substitute in the value of a shell command, use $(shell command).

For instance, suppose you wanted to compile all .java files simultaneously, you might have this rule:

Foo.class: $(shell find . -name *.java)
	javac $<

Further reading

Robert Mecklenberg provides one of the few comprehensive overviews of (GNU) make in book form:

It's also available as an O'Reilly open book.

And, after make, there is the autotool site:

Related pages