Now you know most of what is in a
Makefile
, what do you do next? There are
two choices: use one of the uncommonly-available makefile
generators or write your own makefile (I leave out the third
choice of ignoring PMake and doing
everything by hand as being beyond the bounds of common
sense).
When faced with the writing of a makefile, it is usually
best to start from first principles: just what are you trying to
do? What do you want the makefile finally to produce? To begin
with a somewhat traditional example, let's say you need to write
a makefile to create a program, expr
, that
takes standard infix expressions and converts them to prefix
form (for no readily apparent reason). You have got three
source files, in C, that make up the program:
main.c
, parse.c
, and
output.c
. Harking back to my pithy advice
about dependency lines, you write the first line of the
file:
expr : main.o parse.o output.o
because you remember expr
is made from
.o
files, not .c
files. Similarly for the .o
files you
produce the lines:
main.o : main.c parse.o : parse.c output.o : output.c main.o parse.o output.o : defs.h
Great. You have now got the dependencies specified. What you need now is commands. These commands, remember, must produce the target on the dependency line, usually by using the sources you have listed. You remember about local variables? Good, so it should come to you as no surprise when you write:
expr : main.o parse.o output.o cc -o $(.TARGET) $(.ALLSRC)
Why use the variables? If your program grows to produce
postfix expressions too (which, of course, requires a name
change or two), it is one fewer place you have to change the
file. You cannot do this for the object files, however, because
they depend on their corresponding source files and
defs.h
, thus if you said:
cc -c $(.ALLSRC)
you will get (for main.o
):
cc -c main.c defs.h
which is wrong. So you round out the makefile with these lines:
main.o : main.c cc -c main.c parse.o : parse.c cc -c parse.c output.o : output.c cc -c output.c
The makefile is now complete and will, in fact, create the program you want it to without unnecessary compilations or excessive typing on your part. There are two things wrong with it, however (aside from it being altogether too long, something I will address in Chapter 3, Short-cuts and Other Nice Things):
The string main.o parse.o output.o
is
repeated twice, necessitating two changes when you add
postfix (you were planning on that, were not you?). This is
in direct violation of de Boor's First Rule of writing
makefiles:
Anything that needs to be written more than once should be placed in a variable. I cannot emphasize this enough as being very important to the maintenance of a makefile and its program.
There is no way to alter the way compilations are performed short of editing the makefile and making the change in all places. This is evil and violates de Boor's Second Rule, which follows directly from the first:
Any flags or programs used inside a makefile should be placed in a variable so they may be changed, temporarily or permanently, with the greatest ease.
The makefile should more properly read:
OBJS = main.o parse.o output.o expr : $(OBJS) $(CC) $(CFLAGS) -o $(.TARGET) $(.ALLSRC) main.o : main.c $(CC) $(CFLAGS) -c main.c parse.o : parse.c $(CC) $(CFLAGS) -c parse.c output.o : output.c $(CC) $(CFLAGS) -c output.c $(OBJS) : defs.h
Alternatively, if you like the idea of dynamic sources mentioned in Section 2.3.1, “Local Variables”, you could write it like this:
OBJS = main.o parse.o output.o expr : $(OBJS) $(CC) $(CFLAGS) -o $(.TARGET) $(.ALLSRC) $(OBJS) : $(.PREFIX).c defs.h $(CC) $(CFLAGS) -c $(.PREFIX).c
These two rules and examples lead to de Boor's First Corollary: Variables are your friends.
Once you have written the makefile comes the
sometimes-difficult task of making sure the darn thing works.
Your most helpful tool to make sure the makefile is at least
syntactically correct is the -n
flag, which
allows you to see if PMake will choke
on the makefile. The second thing the -n
flag
lets you do is see what PMake would
do without it actually doing it, thus you can make sure the
right commands would be executed were you to give
PMake its head.
When you find your makefile is not behaving as you hoped,
the first question that comes to mind (after “What time is
it, anyway?”) is “Why not?” In answering
this, two flags will serve you well: -d m
and
“-p 2”.
The first causes PMake to tell you as
it examines each target in the makefile and indicate why it is
deciding whatever it is deciding. You can then use the
information printed for other targets to see where you went
wrong. The “-p 2” flag makes
PMake print out its internal state
when it is done, allowing you to see that you forgot to make
that one chapter depend on that file of macros you just got a
new version of. The output from “-p 2” is intended
to resemble closely a real makefile, but with additional
information provided and with variables expanded in those
commands PMake actually printed or
executed.
Something to be especially careful about is circular dependencies. For example:
a : b b : c d d : a
In this case,
because of how PMake works,
c
is the only thing
PMake will examine, because
d
and a
will
effectively fall off the edge of the universe, making it
impossible to examine b
(or them, for
that matter). PMake will tell you
(if run in its normal mode) all the targets involved in any
cycle it looked at (i.e. if you have two cycles in the
graph (naughty, naughty), but only try to make a target in one
of them, PMake will only tell you
about that one. You will have to try to make the other to find
the second cycle). When run as Make,
it will only print the first target in the cycle.
All FreeBSD documents are available for download at https://download.freebsd.org/ftp/doc/
Questions that are not answered by the
documentation may be
sent to <freebsd-questions@FreeBSD.org>.
Send questions about this document to <freebsd-doc@FreeBSD.org>.