(Use this version, it looks prettier.)

make

make is a phenomenally handy tool for organising the build process of your program. It makes it trivially easy to recompile parts of your program after changing it. It is especially handy if your program consists of many source files, or some of the code is generated automatically (e.g. by lex).

makeis used by writing a file which tells it how to do whatever it is you want it to do (such as compiling a program). This file is called a makefile, and is usually called "Makefile" (this is the name make will look for if you don't tell it otherwise).

What can make do?

(This list is taken from the home page of the GNU version of make.)

What's in a makefile?

In general, a makefile contains two things:

  1. Constant definitions, such as the name of the compiler, what flags to pass it, etc
  2. Rules for making files

Rules for making files

A rule consists of:

  1. a target name
  2. a list of prerequisites
  3. a list of actions

These are written like so:

target: prereq1 prereq2
        action1
        action2

When make follows one of these rules and executes the actions, we say that make updates the target.

When you run make, you give it the name of zero or more targets to update, and it brings up for renewal those targets and all the ones they depend on (but each target only gets updated once each time). If you don't give it any targets, it assumes you want to update the first target listed in the makefile.

The target name is usually the name of a file, though it does not have to be. Now, suppose a particular target comes up for renewal. If the following are all true, then make decides the target is already up to date and does not bother updating it.

On large projects especially, this can save a huge amount of time waiting for compilation of parts of the program that haven't changed.

A target with a name that is not the name of an existing file is called a phony target. These get updated regardless whenever you tell make to update them, e.g. on the command line or as a prerequisite. This is based partly on the assumption that the target names a file which it creates (which is quite commonly the case). So if a target has phony prerequisites, make will never think it is up to date.

Constants

Constants are not strictly necessary, but using them makes it easier to make major changes to the way your program is built.

For example, suppose you are writing a C program, and you want all of your files to be compiled with gcc using the flags -Wall -pedantic -O (turning on lots of warnings and simple optimisations). Then you can specify:

CC     = gcc
CFLAGS = -Wall -pedantic -O # this comment does not get included

Then, to compile a file, you would create a target for it, such as this:

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

Notice that to refer to the constant's value you write $(CFLAGS). That string gets replaced with whatever you put on the right hand side of the = sign (up until a # if there is one, which indicates a comment).

In this example we have specified the C compiler to use in the variable CC. This is good practice if you are trying to produce portable code, as it makes it easy to compile your program with a different compiler - all you need is to change the definition of CC, and all the actions referring to $(CC) will use the new definition automatically.

Using make

To get the best out of make, you should specify a rule for each of your source files so that they get compiled separately, with the final stage being a simple linkage operation.

An example makefile

Here is a fairly typical example. Suppose you are writing a program called fred, and the source code for this program is in three source files, called foo.c, bar.c, and baz.l. You also have header files called fred.h, bar.h and lexer.h. baz.l is a lex file, which lex uses to generate a lexical analyser, also written in C, in the file lexer.c. You also use readline and the standard maths functions. A good makefile would look something like this:

CC      = gcc
LEX     = flex
CFLAGS  = -Wall -pedantic -O
LDFLAGS = -ltermcap -lreadline -lm -lfl

.PHONY:  proper clean

fred:    foo.o bar.o lexer.o
         $(CC) $(CFLAGS) foo.o bar.o lexer.o $(LDFLAGS) -o fred

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

bar.o:   fred.h bar.h bar.c
         $(CC) $(CFLAGS) -c bar.c -o bar.o

lexer.o: fred.h lexer.h lexer.c
         $(CC) $(CFLAGS) -c lexer.c -o lexer.o

lexer.c: baz.l
         $(LEX) -olexer.c baz.l

proper:
         rm -vf *.o lexer.c

clean:   proper
         rm -vf fred

To make the whole project, given just those source files, just type

make

This tells make to update the first target listed in the makefile. make notices that none of foo.o, bar.o and lexer.o actually exist yet, so it goes and updates those targets.

In the case of lexer.o, the file lexer.c also does not exist, because first it must be generated by lex. So before updating lexer.o, make first updates the target lexer.c, which runs lex on baz.l. Now make compiles lexer.c into lexer.o, using the -c switch to tell gcc not to do any linking yet. Once all the other prerequisites are updated, make looks at fred again and runs its action, which is linking together of the three object files along with some libraries.

Now suppose that, having compiled the whole program once, you change one of the source files, say, bar.h. foo.o and bar.o both depend on this file, so when you compile the program again, make will see that those object files have older timestamps than the header file they are dependent on, and so they need to be updated. However, lexer.o does not depend on bar.h, so it is still newer than all of its prerequisites. So make decides it is up to date and does not rebuild it.

If you want to compile only part of a program - for example, you're ironing out all the syntax errors from the lexer - you give make the name of the target(s) you want it to update, viz.:

make lexer.o

Cleaning up after yourself

You won't want to distribute your program, when it is finally ready, with all of the .o and executable files, and all the other intermediate files such as lexer.c. These are created during the build process, from the other source files. It's useful to have a convenient way to get rid of these before packaging. The usual way, when using make, is to have one or more phony targets which delete these files. The target which removes all files created in the build process is usually called clean, as above. Another common target is proper, which removes only the intermediate files, and not the final executable. Obviously one can be implemented using the other as a prerequisite. As before, to tell make to update these targets, type:

make clean

or

make proper

The .PHONY target at the top tells make which targets are always phony. If any files with those names ever actually appear in the directory, make pretends they don't exist and always updates those targets when they come up for renewal.