Chapter 1. How to Write a Simple Makefile

The mechanics of programming usually follow a fairly simple routine of editing source files, compiling the source into an executable form, and debugging the result. Although transforming the source into an executable is considered routine, if done incorrectly a programmer can waste immense amounts of time tracking down the problem. Most developers have experienced the frustration of modifying a function and running the new code only to find that their change did not fix the bug. Later they discover that they were never executing their modified function because of some procedural error such as failing to recompile the source, relink the executable, or rebuild a jar. Moreover, as the program’s complexity grows these mundane tasks can become increasingly error-prone as different versions of the program are developed, perhaps for other platforms or other versions of support libraries, etc.

The make program is intended to automate the mundane aspects of transforming source code into an executable. The advantages of make over scripts is that you can specify the relationships between the elements of your program to make, and it knows through these relationships and timestamps exactly what steps need to be redone to produce the desired program each time. Using this information, make can also optimize the build process avoiding unnecessary steps.

GNU make (and other variants of make) do precisely this. make defines a language for describing the relationships between source code, intermediate files, and executables. It also provides features to manage alternate configurations, implement reusable libraries of specifications, and parameterize processes with user-defined macros. In short, make can be considered the center of the development process by providing a roadmap of an application’s components and how they fit together.

The specification that make uses is generally saved in a file named makefile. Here is a makefile to build the traditional “Hello, World” program:

hello: hello.c
        gcc hello.c -o hello

To build the program execute make by typing:

$ make

at the command prompt of your favorite shell. This will cause the make program to read the makefile and build the first target it finds there:

$ make
gcc hello.c -o hello

If a target is included as a command-line argument, that target is updated. If no command-line targets are given, then the first target in the file is used, called the default goal.

Typically the default goal in most makefiles is to build a program. This usually involves many steps. Often the source code for the program is incomplete and the source must be generated using utilities such as flex or bison. Next the source is compiled into binary object files (.o files for C/C++, .class files for Java, etc.). Then, for C/C++, the object files are bound together by a linker (usually invoked through the compiler, gcc) to form an executable program.

Modifying any of the source files and reinvoking make will cause some, but usually not all, of these commands to be repeated so the source code changes are properly incorporated into the executable. The specification file, or makefile, describes the relationship between the source, intermediate, and executable program files so that make can perform the minimum amount of work necessary to update the executable.

So the principle value of make comes from its ability to perform the complex series of commands necessary to build an application and to optimize these operations when possible to reduce the time taken by the edit-compile-debug cycle. Furthermore, make is flexible enough to be used anywhere one kind of file depends on another from traditional programming in C/C++ to Java, TEX, database management, and more.

Targets and Prerequisites

Essentially a makefile contains a set of rules used to build an application. The first rule seen by make is used as the default rule. A rule consists of three parts: the target, its prerequisites, and the command(s) to perform:

target: prereq1 prereq2
        commands

The target is the file or thing that must be made. The prerequisites or dependents are those files that must exist before the target can be successfully created. And the commands are those shell commands that will create the target from the prerequisites.

Here is a rule for compiling a C file, foo.c, into an object file, foo.o:

foo.o: foo.c foo.h
        gcc -c foo.c

The target file foo.o appears before the colon. The prerequisites foo.c and foo.h appear after the colon. The command script usually appears on the following lines and is preceded by a tab character.

When make is asked to evaluate a rule, it begins by finding the files indicated by the prerequisites and target. If any of the prerequisites has an associated rule, make attempts to update those first. Next, the target file is considered. If any prerequisite is newer than the target, the target is remade by executing the commands. Each command line is passed to the shell and is executed in its own subshell. If any of the commands generates an error, the building of the target is terminated and make exits. One file is considered newer than another if it has been modified more recently.

Here is a program to count the number of occurrences of the words “fee,” “fie,” “foe,” and “fum” in its input. It uses a flex scanner driven by a simple main:

#include <stdio.h>

extern int fee_count, fie_count, foe_count, fum_count;
extern int yylex( void );

int main( int argc, char ** argv )
{
    yylex( );
    printf( "%d %d %d %d\n", fee_count, fie_count, foe_count, fum_count );
    exit( 0 );
}

The scanner is very simple:

        int fee_count = 0;
        int fie_count = 0;
        int foe_count = 0;
        int fum_count = 0;
%%
fee     fee_count++;
fie     fie_count++;
foe     foe_count++;
fum     fum_count++;

The makefile for this program is also quite simple:

count_words: count_words.o lexer.o -lfl
        gcc count_words.o lexer.o -lfl -o count_words

count_words.o: count_words.c
        gcc -c count_words.c

lexer.o: lexer.c
        gcc -c lexer.c

lexer.c: lexer.l
        flex -t lexer.l > lexer.c

When this makefile is executed for the first time, we see:

$ make
gcc -c count_words.c
flex -t lexer.l > lexer.c
gcc -c lexer.c
gcc count_words.o lexer.o -lfl -o count_words

We now have an executable program. Of course, real programs typically consist of more modules than this. Also, as you will see later, this makefile does not use most of the features of make so it’s more verbose than necessary. Nevertheless, this is a functional and useful makefile. For instance, during the writing of this example, I executed the makefile several dozen times while experimenting with the program.

As you look at the makefile and sample execution, you may notice that the order in which commands are executed by make are nearly the opposite to the order they occur in the makefile. This top-down style is common in makefiles. Usually the most general form of target is specified first in the makefile and the details are left for later. The make program supports this style in many ways. Chief among these is make’s two-phase execution model and recursive variables. We will discuss these in great detail in later chapters.

Dependency Checking

How did make decide what to do? Let’s go over the previous execution in more detail to find out.

First make notices that the command line contains no targets so it decides to make the default goal, count_words. It checks for prerequisites and sees three: count_words.o, lexer.o, and -lfl. make now considers how to build count_words.o and sees a rule for it. Again, it checks the prerequisites, notices that count_words.c has no rules but that the file exists, so make executes the commands to transform count_words.c into count_words.o by executing the command:

gcc -c count_words.c

This “chaining” of targets to prerequisites to targets to prerequisites is typical of how make analyzes a makefile to decide the commands to be performed.

The next prerequisite make considers is lexer.o. Again the chain of rules leads to lexer. c but this time the file does not exist. make finds the rule for generating lexer.c from lexer.l so it runs the flex program. Now that lexer.c exists it can run the gcc command.

Finally, make examines -lfl. The -l option to gcc indicates a system library that must be linked into the application. The actual library name indicated by “fl” is libfl.a. GNU make includes special support for this syntax. When a prerequisite of the form-l<NAME> is seen, make searches for a file of the form libNAME.so; if no match is found, it then searches for libNAME.a. Here make finds /usr/lib/libfl.a and proceeds with the final action, linking.

Minimizing Rebuilds

When we run our program, we discover that aside from printing fees, fies, foes, and fums, it also prints text from the input file. This is not what we want. The problem is that we have forgotten some rules in our lexical analyzer and flex is passing this unrecognized text to its output. To solve this problem we simply add an “any character” rule and a newline rule:

        int fee_count = 0;
        int fie_count = 0;
        int foe_count = 0;
        int fum_count = 0;
%%
fee     fee_count++;
fie     fie_count++;
foe     foe_count++;
fum     fum_count++;
.
\n

After editing this file we need to rebuild the application to test our fix:

$ make
flex -t lexer.l > lexer.c
gcc -c lexer.c
gcc count_words.o lexer.o -lfl -ocount_words

Notice this time the file count_words.c was not recompiled. When make analyzed the rule, it discovered that count_words.o existed and was newer than its prerequisite count_words.c so no action was necessary to bring the file up to date. While analyzing lexer.c, however, make saw that the prerequisite lexer.l was newer than its target lexer.c so make must update lexer.c. This, in turn, caused the update of lexer.o and then count_words. Now our word counting program is fixed:

$ count_words < lexer.l
3 3 3 3

Invoking make

The previous examples assume that:

  • All the project source code and the make description file are stored in a single directory.

  • The make description file is called makefile, Makefile, or GNUMakefile.

  • The makefile resides in the user’s current directory when executing the make command.

When make is invoked under these conditions, it automatically creates the first target it sees. To update a different target (or to update more than one target) include the target name on the command line:

$ make lexer.c

When make is executed, it will read the description file and identify the target to be updated. If the target or any of its prerequisite files are out of date (or missing) the shell commands in the rule’s command script will be executed one at a time. After the commands are run make assumes the target is up to date and moves on to the next target or exits.

If the target you specify is already up to date, make will say so and immediately exit, doing nothing else:

$ make lexer.c
make: `lexer.c' is up to date.

If you specify a target that is not in the makefile and for which there is no implicit rule (discussed in Chapter 2), make will respond with:

$ make non-existent-target
make: *** No rule to make target `non-existent-target'.  Stop.

make has many command-line options. One of the most useful is --just-print (or -n) which tells make to display the commands it would execute for a particular target without actually executing them. This is particularly valuable while writing makefiles. It is also possible to set almost any makefile variable on the command line to override the default value or the value set in the makefile.

Basic Makefile Syntax

Now that you have a basic understanding of make you can almost write your own makefiles. Here we’ll cover enough of the syntax and structure of a makefile for you to start using make.

Makefiles are usually structured top-down so that the most general target, often called all, is updated by default. More and more detailed targets follow with targets for program maintenance, such as a clean target to delete unwanted temporary files, coming last. As you can guess from these target names, targets do not have to be actual files, any name will do.

In the example above we saw a simplified form of a rule. The more complete (but still not quite complete) form of a rule is:

target1 target2 target3 : prerequisite1 prerequisite2
        command1
        command2
        command3

One or more targets appear to the left of the colon and zero or more prerequisites can appear to the right of the colon. If no prerequisites are listed to the right, then only the target(s) that do not exist are updated. The set of commands executed to update a target are sometimes called the command script, but most often just the commands.

Each command must begin with a tab character. This (obscure) syntax tells make that the characters that follow the tab are to be passed to a subshell for execution. If you accidentally insert a tab as the first character of a noncommand line, make will interpret the following text as a command under most circumstances. If you’re lucky and your errant tab character is recognized as a syntax error you will receive the message:

$ make
Makefile:6: *** commands commence before first target.  Stop.

We’ll discuss the complexities of the tab character in Chapter 2.

The comment character for make is the hash or pound sign, #. All text from the pound sign to the end of line is ignored. Comments can be indented and leading whitespace is ignored. The comment character # does not introduce a make comment in the text of commands. The entire line, including the # and subsequent characters, is passed to the shell for execution. How it is handled there depends on your shell.

Long lines can be continued using the standard Unix escape character backslash (\). It is common for commands to be continued in this way. It is also common for lists of prerequisites to be continued with backslash. Later we’ll cover other ways of handling long prerequisite lists.

You now have enough background to write simple makefiles. Chapter 2 will cover rules in detail, followed by make variables in Chapter 3 and commands in Chapter 5. For now you should avoid the use of variables, macros, and multiline command sequences.

Get Managing Projects with GNU Make, 3rd Edition now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.