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).
make is 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?
- make enables the end user to build and install your package without knowing the details of how that is done — because these details are recorded in the makefile that you supply.
- make figures out automatically which files it needs to update, based on which source files have changed. It also automatically determines the proper order for updating files, in case one non-source file depends on another non-source file. As a result, if you change a few source files and then run make, it does not need to recompile all of your program. It updates only those non-source files that depend directly or indirectly on the source files that you changed.
- make is not limited to any particular language. For each non-source file in the program, the makefile specifies the shell commands to compute it. These shell commands can run a compiler to produce an object file, the linker to produce an executable, ar to update a library, or TEX or Makeinfo to format documentation.
- make is not limited to building a package. You can also use make to control installing or deinstalling a package, generate tags tables for it, or anything else you want to do often enough to make it worth while writing down how to do it.
What's in a makefile?
In general, a makefile contains two things:
- Constant definitions, such as the name of the compiler, what flags to pass it, etc
- Rules for making files
Rules for making files
A rule consists of:
- a target name
- a list of prerequisites
- 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.
- A file with that name exists (relative to the current directory)
- All of its (direct or indirect) prerequisites are also the names of existing files
- If any of those prerequisites are also targets, those targets are all up to date
- The timestamp on the file named by the target is newer than the timestamps on all of its (direct or indirect) prerequisites
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 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 -O2 (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.
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
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.:
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:
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.