1.1 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
:
prereq
1
prereq
2
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 -ocount_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 -ocount_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
makefile
s. 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.
|