Make -- A Tutorial
Adam de Boor
Berkeley Softworks
2150 Shattuck Ave, Penthouse
Berkeley, CA 94704
adam@bsw.uu.net
...!uunet!bsw!adam
1. Introduction
This is a historic document describing PMake, the predeces-
sor of BSD and MirOS make, NetBSD(TM) nmake and some others.
Please keep this in mind while reading the following docu-
mentation. In case of doubt please consider the manual page
for your make executable.
Make is a program for creating other programs, or anything
else you can think of for it to do. The basic idea behind
Make is that, for any given system, be it a program or a
document or whatever, there will be some files that depend
on the state of other files (on when they were last modi-
fied). Make takes these dependencies, which you must
specify, and uses them to build whatever it is you want it
to build.
OpenBSD's Make is based upon PMake, a parallel make origi-
nally developed for the distributed operating system called
Sprite. PMake departs from usual Make practices in several
ways. A large number of those quirks are not relevant in a
modern POSIX world, and hence development of OpenBSD's make
has aimed at removing unwanted differences. Useful features
of OpenBSD's Make which are not POSIX compliant will be
flagged with a little sign in the left margin, like this:
Also note that this tutorial was originally written for
PMake, and hence may not be totally accurate.
This tutorial is divided into three main sections
_________________________
Permission to use, copy, modify, and distribute this
software and its documentation for any purpose and
without fee is hereby granted, provided that the above
copyright notice appears in all copies. The University
of California, Berkeley Softworks, and Adam de Boor
make no representations about the suitability of this
software for any purpose. It is provided "as is"
without express or implied warranty.
April 27, 2013
PSD:12-2 Make -- A Tutorial
corresponding to basic, intermediate and advanced Make
usage. If you already know Make well, you will only need to
skim chapter 2. Things in chapter 3 make life much easier,
while those in chapter 4 are strictly for those who know
what they are doing. Chapter 5 has definitions for the jar-
gon I use and chapter 6 contains possible solutions to the
problems presented throughout the tutorial.
2. The Basics of Make
Make takes as input a file that tells a) which files depend
on which other files to be complete and b) what to do about
files that are ``out-of-date.'' This file is known as a
``makefile'' and is usually kept in the top-most directory
of the system to be built. While you can call the makefile
anything you want, Make will look for Makefile and makefile
(in that order) in the current directory if you don't tell
it otherwise. To specify a different makefile, use the -f
flag (e.g. ``make -f program.mk'').
A makefile has four different types of lines in it:
+ File dependency specifications
+ Creation commands
+ Variable assignments
+ Comments, include statements and conditional direc-
tives
Any line may be continued over multiple lines by ending it
with a backslash. The backslash, following newline and any
initial whitespace on the following line are compressed into
a single space before the input line is examined by Make.
2.1. Dependency Lines
As mentioned in the introduction, in any system, there are
dependencies between the files that make up the system. For
instance, in a program made up of several C source files and
one header file, the C files will need to be re-compiled
should the header file be changed. For a document of several
chapters and one macro file, the chapters will need to be
reprocessed if any of the macros changes. These are depen-
dencies and are specified by means of dependency lines in
the makefile.
On a dependency line, there are targets and sources,
separated by a one- or two-character operator. The targets
``depend'' on the sources and are usually created from them.
Any number of targets and sources may be specified on a
dependency line. All the targets in the line are made to
depend on all the sources. Targets and sources need not be
actual files, but every source must be either an actual file
or another target in the makefile. If you run out of room,
use a backslash at the end of the line to continue onto the
next one.
Any file may be a target and any file may be a source, but
the relationship between the two (or however many) is deter-
mined by the ``operator'' that separates them. Three types
of operators exist: one specifies that the datedness of a
target is determined by the state of its sources, while
April 27, 2013
Make -- A Tutorial PSD:12-3
another specifies other files (the sources) that need to be
dealt with before the target can be re-created. The third
operator is very similar to the first, with the additional
condition that the target is out-of-date if it has no
sources. These operations are represented by the colon, the
exclamation point and the double-colon, respectively, and
are mutually exclusive. Their exact semantics are as fol-
lows:
: If a colon is used, a target on the line is considered
to be ``out-of-date'' (and in need of creation) if
+ any of the sources has been modified more recently
than the target, or
+ the target doesn't exist.
Under this operation, steps will be taken to re-create
the target only if it is found to be out-of-date by
using these two rules.
! If an exclamation point is used, the target will always
be re-created, but this will not happen until all of
its sources have been examined and re-created, if
necessary.
:: If a double-colon is used, a target is out-of-date if:
+ any of the sources has been modified more recently
than the target, or
+ the target doesn't exist, or
+ the target has no sources.
If the target is out-of-date according to these rules,
it will be re-created. This operator also does some-
thing else to the targets, but I'll go into that in the
next section (``Shell Commands'').
Enough words, now for an example. Take that C program I men-
tioned earlier. Say there are three C files (a.c, b.c and
c.c) each of which includes the file defs.h. The dependen-
cies between the files could then be expressed as follows:
program : a.o b.o c.o
a.o b.o c.o : defs.h
a.o : a.c
b.o : b.c
c.o : c.c
You may be wondering at this point, where a.o, b.o and c.o
came in and why they depend on defs.h and the C files don't.
The reason is quite simple: program cannot be made by link-
ing together .c files -- it must be made from .o files.
Likewise, if you change defs.h, it isn't the .c files that
need to be re-created, it's the .o files. If you think of
dependencies in these terms -- which files (targets) need to
be created from which files (sources) -- you should have no
problems.
An important thing to notice about the above example, is
that all the .o files appear as targets on more than one
line. This is perfectly all right: the target is made to
depend on all the sources mentioned on all the dependency
lines. E.g. a.o depends on both defs.h and a.c.
April 27, 2013
PSD:12-4 Make -- A Tutorial
The order of the dependency lines in the makefile is impor-
tant: the first target on the first dependency line in the
makefile will be the one that gets made if you don't say
otherwise. That's why program comes first in the example
makefile, above.
Both targets and sources may contain the standard C-Shell
wildcard characters ({, }, *, ?, [, and ]), but the non-
curly-brace ones may only appear in the final component (the
file portion) of the target or source. The characters mean
the following things:
{} These enclose a comma-separated list of options and
cause the pattern to be expanded once for each element
of the list. Each expansion contains a different ele-
ment. For example, src/{whiffle,beep,fish}.c expands to
the three words src/whiffle.c, src/beep.c, and
src/fish.c. These braces may be nested and, unlike the
other wildcard characters, the resulting words need not
be actual files. All other wildcard characters are
expanded using the files that exist when Make is
started.
* This matches zero or more characters of any sort.
src/*.c will expand to the same three words as above as
long as src contains those three files (and no other
files that end in .c).
? Matches any single character.
[] This is known as a character class and contains either
a list of single characters, or a series of character
ranges (a-z, for example means all characters between a
and z), or both. It matches any single character con-
tained in the list. E.g. [A-Za-z] will match all
letters, while [0123456789] will match all numbers.
2.2. Shell Commands
``Isn't that nice,'' you say to yourself, ``but how are
files actually `re-created,' as he likes to spell it?'' The
re-creation is accomplished by commands you place in the
makefile. These commands are passed to the MirBSD Korn shell
(better known as ``/bin/mksh'') to be executed and are
expected to do what's necessary to update the target file
(Make doesn't actually check to see if the target was
created. It just assumes it's there).
Shell commands in a makefile look a lot like shell commands
you would type at a terminal, with one important exception:
each command in a makefile must be preceded by at least one
tab.
Each target has associated with it a set of one or more of
these shell commands. The creation script for a target
should immediately follow the dependency line for that tar-
get. While any given target may appear on more than one
dependency line, only one of these dependency lines may be
followed by a creation script, unless the `::' operator was
used on the dependency line.
If the double-colon was used, each dependency line for the
target may be followed by a set of shell commands. This set
April 27, 2013
Make -- A Tutorial PSD:12-5
of shell commands will only be executed if the target on the
associated dependency line is out-of-date with respect to
the sources on that line, according to the rules I gave ear-
lier. I'll give you a good example of this later on.
To expand on the earlier makefile, you might add commands as
follows:
program : a.o b.o c.o
cc a.o b.o c.o -o program
a.o b.o c.o : defs.h
a.o : a.c
cc -c a.c
b.o : b.c
cc -c b.c
c.o : c.c
cc -c c.c
Something you should remember when writing a makefile is,
the commands will be executed if the target on the depen-
dency line is out-of-date, not the sources. In this example,
the command ``cc -c a.c'' will be executed if a.o is out-
of-date. Because of the `:' operator, this means that should
a.c or defs.h have been modified more recently than a.o, the
command will be executed (a.o will be considered out-of-
date).
Remember how I said the only difference between a makefile
shell command and a regular shell command was the leading
tab? I lied. There is another way in which makefile commands
differ from regular ones. The first two characters after the
initial whitespace are treated specially. If they are any
combination of `@' and `-', they cause Make to do different
things.
In most cases, shell commands are printed before they're
actually executed. This is to keep you informed of what's
going on. If an `@' appears, however, this echoing is
suppressed. In the case of an echo command, say ``echo Link-
ing index,'' it would be rather silly to see
echo Linking index
Linking index
so Make allows you to place an `@' before the command
(``@echo Linking index'') to prevent the command from being
printed.
The other special character is the `-'. In case you didn't
know, shell commands finish with a certain ``exit status.''
This status is made available by the operating system to
whatever program invoked the command. Normally this status
will be 0 if everything went ok and non-zero if something
went wrong. For this reason, Make will consider an error to
have occurred if one of the shells it invokes returns a
non-zero status. When it detects an error, Make's usual
action is to abort whatever it's doing and exit with a non-
zero status itself (any other targets that were being
April 27, 2013
PSD:12-6 Make -- A Tutorial
created will continue being made, but nothing new will be
started. Make will exit after the last job finishes). This
behavior can be altered, however, by placing a `-' at the
front of a command (``-mv index index.old''), certain
command-line arguments, or doing other things, to be
detailed later. In such a case, the non-zero status is sim-
ply ignored and Make keeps chugging along.
In Make -j mode, a set of shell commands attached to a tar-
get is fed to a shell as a single script. This is experi-
mental behavior from PMake's period which hasn't been fixed
yet.
Make has a -B flag (it stands for backwards-compatible) that
forces each command to be given to a separate shell. Unfor-
tunately, it also inhibits -j.
A target's shell script is fed to the shell on its (the
shell's) input stream. This means that any commands, such as
ci that need to get input from the terminal won't work right
-- they'll get the shell's input, something they probably
won't find to their liking. A simple way around this is to
give a command like this:
ci $(SRCS) < /dev/tty
This would force the program's input to come from the termi-
nal. If you can't do this for some reason, your only other
alternative is to use Make in its fullest compatibility
mode. See Compatibility in chapter 4.
2.3. Variables
Make has the ability to save text in variables to be
recalled later at your convenience. Variables in Make are
used much like variables in the shell and, by tradition,
consist of all upper-case letters (you don't have to use all
upper-case letters. In fact there's nothing to stop you from
calling a variable @^&$%$. Just tradition). Variables are
assigned-to using lines of the form
VARIABLE = value
appended-to by
VARIABLE += value
conditionally assigned-to (if the variable isn't already
defined) by
VARIABLE ?= value
and assigned-to with expansion (i.e. the value is expanded
(see below) before being assigned to the variable--useful
for placing a value at the beginning of a variable, or other
things) by
VARIABLE := value
April 27, 2013
Make -- A Tutorial PSD:12-7
Any whitespace before value is stripped off. When appending,
a space is placed between the old value and the stuff being
appended.
The final way a variable may be assigned to is using
VARIABLE != shell-command
In this case, shell-command has all its variables expanded
(see below) and is passed off to a shell to execute. The
output of the shell is then placed in the variable. Any new-
lines (other than the final one) are replaced by spaces
before the assignment is made. This is typically used to
find the current directory via a line like:
CWD != pwd
Note: this command will be invoked each time the Makefile is
parsed, regardless of whether or not the result will actu-
ally be used for making targets. If the end result is only
needed for shell commands, it is much cheaper to use
VARIABLE = `shell-command`
The value of a variable may be retrieved by enclosing the
variable name in parentheses or curly braces and prefixing
the whole thing with a dollar sign.
For example, to set the variable CFLAGS to the string
``-I/usr/local/include -O,'' you would place a line
CFLAGS = -I/usr/local/include -O
in the makefile and use the word $(CFLAGS) wherever you
would like the string -I/usr/local/include -O to appear.
This is called variable expansion.
To keep Make from substituting for a variable it knows, pre-
cede the dollar sign with another dollar sign. (e.g. to pass
${HOME} to the shell, use $${HOME}). This causes Make, in
effect, to expand the $ macro, which expands to a single $.
There are two different times at which variable expansion
occurs: When parsing a dependency line, the expansion occurs
immediately upon reading the line. If any variable used on a
dependency line is undefined, Make will print a message and
exit. Variables in shell commands are expanded when the com-
mand is executed. Variables used inside another variable are
expanded whenever the outer variable is expanded (the expan-
sion of an inner variable has no effect on the outer vari-
able. I.e. if the outer variable is used on a dependency
line and in a shell command, and the inner variable changes
value between when the dependency line is read and the shell
command is executed, two different values will be substi-
tuted for the outer variable).
Variables come in four flavors, though they are all expanded
the same and all look about the same. They are (in order of
expanding scope):
April 27, 2013
PSD:12-8 Make -- A Tutorial
+ Local variables.
+ Command-line variables.
+ Global variables.
+ Environment variables.
The classification of variables doesn't matter much, except
that the classes are searched from the top (local) to the
bottom (environment) when looking up a variable. The first
one found wins.
2.3.1. Local Variables
Each target can have as many as seven local variables. These
are variables that are only ``visible'' within that target's
shell commands and contain such things as the target's name,
all of its sources (from all its dependency lines), those
sources that were out-of-date, etc. POSIX defines short
names for these variables, which should be used for porta-
bility. OpenBSD's Make has longer synonyms, which will be
used in the rest of this tutorial for clarity.
Four local variables are defined for all targets. They are:
.TARGET
The name of the target (POSIX: @).
.OODATE
The list of the sources for the target that were
considered out-of-date. The order in the list is
not guaranteed to be the same as the order in
which the dependencies were given. (POSIX: ?)
.ALLSRC
The list of all sources for this target in the
order in which they were given. (shorter: >, not
POSIX).
.PREFIX
The target without its suffix and without any
leading path. E.g. for the target
../../lib/compat/fsRead.c, this variable would
contain fsRead (POSIX: *) .
Three other local variables are set only for certain targets
under special circumstances. These are the ``.IMPSRC,''
``.ARCHIVE,'' and ``.MEMBER'' variables. When they are set
and how they are used is described later.
Four of these variables may be used in sources as well as in
shell commands. These are ``.TARGET'', ``.PREFIX'',
``.ARCHIVE'' and ``.MEMBER''. The variables in the sources
are expanded once for each target on the dependency line,
providing what is known as a ``dynamic source,'' allowing
you to specify several dependency lines at once. For exam-
ple,
$(OBJS) : $(.PREFIX).c
will create a dependency between each object file and its
corresponding C source file.
April 27, 2013
Make -- A Tutorial PSD:12-9
2.3.2. Command-line Variables
Command-line variables are set when Make is first invoked by
giving a variable assignment as one of the arguments. For
example,
make "CFLAGS = -I/usr/local/include -O"
would make CFLAGS be a command-line variable with the given
value. Any assignments to CFLAGS in the makefile will have
no effect, because once it is set, there is (almost) nothing
you can do to change a command-line variable (the search
order, you see). Command-line variables may be set using any
of the four assignment operators, though only = and ?=
behave in a sane way, mostly because assignments to
command-line variables are performed before the makefile is
read, thus the values set in the makefile are unavailable at
the time. += is the same as =, because the old value of the
variable is sought only in the scope in which the assignment
is taking place (you don't want to know). := and ?= will
work if the only variables used are in the environment. !=
is sort of pointless to use from the command line, since the
same effect can no doubt be accomplished using the shell's
own command substitution mechanisms (backquotes and all
that).
2.3.3. Global Variables
Global variables are those set or appended-to in the
makefile. There are two classes of global variables: those
you set and those Make sets. As I said before, the ones you
set can have any name you want them to have, except they may
not contain a colon or an exclamation point. The variables
Make sets (almost) always begin with a period and always
contain upper-case letters, only. The variables are as fol-
lows:
MAKE The name by which Make was invoked is stored in
this variable.
.MAKEFLAGS
All the relevant flags with which Make was
invoked. This does not include such things as -f.
Two other variables, ``.INCLUDES'' and ``.LIBS,'' are
covered in the section on special targets in chapter 3.
Global variables may be deleted using lines of the form:
.undef variable
The `.' must be the first character on the line. Note that
this may only be done on global variables.
2.3.4. Environment Variables
Environment variables are passed by the shell that invoked
Make and are given by Make to each shell it invokes. They
are expanded like any other variable, but they cannot be
altered in any way.
Using all these variables, you can compress the sample
April 27, 2013
PSD:12-10 Make -- A Tutorial
makefile even more:
OBJS = a.o b.o c.o
program : $(OBJS)
cc $(.ALLSRC) -o $(.TARGET)
$(OBJS) : defs.h
a.o : a.c
cc -c a.c
b.o : b.c
cc -c b.c
c.o : c.c
cc -c c.c
2.4. Comments
Comments in a makefile start with a `#' character and extend
to the end of the line. They may appear anywhere you want
them, except in a shell command (though the shell will treat
it as a comment, too). If, for some reason, you need to use
the `#' in a variable or on a dependency line, put a
backslash in front of it. Make will compress the two into a
single `#'.
2.5. Parallelism
PMake was specifically designed to re-create several targets
at once, when possible, when using the -j flag (see below),
but you do have to be careful at times.
There are several problems you are likely to encounter. One
is that some makefiles (and programs) are written in such a
way that it is impossible for two targets to be made at
once. The program xstr, for example, always modifies the
files strings and x.c. There is no way to change it. Thus
you cannot run two of them at once without something being
trashed. Similarly, if you have commands in the makefile
that always send output to the same file, you will not be
able to make more than one target at once unless you change
the file you use. You can, for instance, add a $$$$ to the
end of the file name to tack on the process ID of the shell
executing the command (each $$ expands to a single $, thus
giving you the shell variable $$).
The other problem comes from improperly-specified dependen-
cies that worked in sequential mode. While I don't want to
go into depth on how Make works (look in chapter 4 if you're
interested), I will warn you that files in two different
``levels'' of the dependency tree may be examined in a dif-
ferent order in parallel mode than in sequential mode. For
example, given the makefile
a : b c
b : d
Make may examine the targets in the order c, d, b, a. If the
makefile's author expected Make to abort before making c if
an error occurred while making b, or if b needed to exist
April 27, 2013
Make -- A Tutorial PSD:12-11
before c was made, s/he will be sorely disappointed. The
dependencies are incomplete, since in both these cases, c
would depend on b. So watch out.
Another problem you may face is that, while Make is set up
to handle the output from multiple jobs in a graceful
fashion, the same is not so for input. It has no way to
regulate input to different jobs, so if you use the redirec-
tion from /dev/tty I mentioned earlier, you must be careful
not to run two of the jobs at once.
2.6. Writing and Debugging a Makefile
Now you know most of what's in a makefile, what do you do
next? There are two choices: (1) use one of the uncommonly-
available makefile generators or (2) write your own makefile
(I leave out the third choice of ignoring Make 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 try-
ing 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've 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've 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've 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
April 27, 2013
PSD:12-12 Make -- A Tutorial
cc -c $(.ALLSRC)
you'd 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'll address in chapter 3):
1) The string ``main.o parse.o output.o'' is repeated
twice, necessitating two changes when you add postfix
(you were planning on that, weren't 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.
2) There is no way to alter the way compilations are per-
formed 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
April 27, 2013
Make -- A Tutorial PSD:12-13
mentioned in section 2.3.1, 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 Corol-
lary:
Variables are your friends.
Once you've 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 syn-
tactically correct is the -n flag, which allows you to see
if Make will choke on the makefile. The second thing the -n
flag lets you do is see what Make would do without it actu-
ally doing it, thus you can make sure the right commands
would be executed were you to give Make its head.
When you find your makefile isn't behaving as you hoped, the
first question that comes to mind (after ``What time is it,
anyway?'') is ``Why not?'' In answering this, one flag will
serve you well: ``-d m.'' This causes Make 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.
Something to be especially careful about is circular depen-
dencies. E.g.
a : b
b : c d
d : a
In this case, because of the way Make works, c is the only
thing Make 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). Make 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, Make will only tell you about that one. You'll 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.
2.7. Invoking Make
Make comes with a wide variety of flags to choose from. They
may appear in any order, interspersed with command-line
variable assignments and targets to create. Some of these
flags are as follows:
-d what
This causes Make to spew out debugging information that
may prove useful to you. If you can't figure out why
Make is doing what it's doing, you might try using this
April 27, 2013
PSD:12-14 Make -- A Tutorial
flag. The what parameter is a string of single charac-
ters that tell Make what aspects you are interested in.
Most of what I describe will make little sense to you,
unless you've dealt with Make before. Just remember
where this table is and come back to it as you read on.
The characters and the information they produce are as
follows:
a Archive searching and caching.
c Conditional evaluation.
d The searching and caching of directories.
j Various snippets of information related to the
running of the multiple shells. Not particularly
interesting.
m The making of each target: what target is being
examined; when it was last modified; whether it is
out-of-date; etc.
p Makefile parsing.
r Remote execution.
s The application of suffix-transformation rules.
(See chapter 3)
t The maintenance of the list of targets.
v Variable assignment.
Of these all, the m and s letters will be most useful
to you. If the -d is the final argument or the argument
from which it would get these key letters (see below
for a note about which argument would be used) begins
with a -, all of these debugging flags will be set,
resulting in massive amounts of output.
-f makefile
Specify a makefile to read different from the standard
makefiles (Makefile or makefile). If makefile is ``-'',
Make uses the standard input. This is useful for making
quick and dirty makefiles...
-i If you give this flag, Make will ignore non-zero status
returned by any of its shells. It's like placing a `-'
before all the commands in the makefile.
-k This is similar to -i in that it allows Make to con-
tinue when it sees an error, but unlike -i, where Make
continues blithely as if nothing went wrong, -k causes
it to recognize the error and only continue work on
those things that don't depend on the target, either
directly or indirectly (through depending on something
that depends on it), whose creation returned the error.
The `k' is for ``keep going''...
-m directory
Tells Make another place to search for included
makefiles via the <...> style. Several -m options can
be given to form a search path. If this construct is
used the default system makefile search path is com-
pletely overridden. To be explained in chapter 3, sec-
tion 3.2.
-n This flag tells Make not to execute the commands needed
to update the out-of-date targets in the makefile.
Rather, Make will simply print the commands it would
April 27, 2013
Make -- A Tutorial PSD:12-15
have executed and exit. This is particularly useful for
checking the correctness of a makefile. If Make doesn't
do what you expect it to, it's a good chance the
makefile is wrong.
-q If you give Make this flag, it will not try to re-
create anything. It will just see if anything is out-
of-date and exit non-zero if so.
-r When Make starts up, it reads a default makefile that
tells it what sort of system it's on and gives it some
idea of what to do if you don't tell it anything. I'll
tell you about it in chapter 3. If you give this flag,
Make won't read the default makefile.
-s This causes Make to not print commands before they're
executed. It is the equivalent of putting an `@' before
every command in the makefile.
-t Rather than try to re-create a target, Make will simply
``touch'' it so as to make it appear up-to-date. If the
target didn't exist before, it will when Make finishes,
but if the target did exist, it will appear to have
been updated.
-B Forces OpenBSD Make to be as POSIX-compatible as possi-
ble. This includes:
+ Executing one shell per shell command
+ Using sequential mode.
-D variable
Allows you to define a variable to have ``1'' as its
value. The variable is a global variable, not a
command-line variable. This is useful mostly for people
who are used to the C compiler arguments and those
using conditionals, which I'll get into in section 4.3
-I directory
Tells Make another place to search for included
makefiles. Yet another thing to be explained in chapter
3 (section 3.2, to be precise).
-P When creating targets in parallel, several shells are
executing at once, each wanting to write its own two
cent's-worth to the screen. This output must be cap-
tured by Make in some way in order to prevent the
screen from being filled with garbage even more indeci-
pherable than you usually see. Make has two ways of
doing this, one of which provides for much cleaner out-
put and a clear separation between the output of dif-
ferent jobs, the other of which provides a more immedi-
ate response so one can tell what is really happening.
The former is done by notifying you when the creation
of a target starts, capturing the output and transfer-
ring it to the screen all at once when the job fin-
ishes. The latter is done by catching the output of the
shell (and its children) and buffering it until an
entire line is received, then printing that line pre-
ceded by an indication of which job produced the out-
put. Since I prefer this second method, it is the one
used by default. The first method will be used if you
give the -P flag to Make.
April 27, 2013
PSD:12-16 Make -- A Tutorial
Flags without arguments may follow a single `-'. E.g.
make -f server.mk -DDEBUG -I/chip2/X/server/include -n
will cause Make to read server.mk as the input makefile,
define the variable DEBUG as a global variable and look for
included makefiles in the directory /chip2/X/server/include.
2.8. Summary
A makefile is made of four types of lines:
+ Dependency lines
+ Creation commands
+ Variable assignments
+ Comments, include statements and conditional direc-
tives
A dependency line is a list of one or more targets, an
operator (`:', `::', or `!'), and a list of zero or more
sources. Sources may contain wildcards and certain local
variables.
A creation command is a regular shell command preceded by a
tab. In addition, if the first two characters after the tab
(and other whitespace) are a combination of `@' or `-', Make
will cause the command to not be printed (if the character
is `@') or errors from it to be ignored (if `-'). A blank
line, dependency line or variable assignment terminates a
creation script. There may be only one creation script for
each target with a `:' or `!' operator.
Variables are places to store text. They may be uncondition-
ally assigned-to using the `=' operator, appended-to using
the `+=' operator, conditionally (if the variable is unde-
fined) assigned-to with the `?=' operator, and assigned-to
with variable expansion with the `:=' operator. The output
of a shell command may be assigned to a variable using the
`!=' operator. Variables may be expanded (their value
inserted) by enclosing their name in parentheses or curly
braces, preceded by a dollar sign. A dollar sign may be
escaped with another dollar sign. Variables are not expanded
if Make doesn't know about them. There are seven local vari-
ables: .TARGET, .ALLSRC, .OODATE, .PREFIX, .IMPSRC,
.ARCHIVE, and .MEMBER. Four of them (.TARGET, .PREFIX,
.ARCHIVE, and .MEMBER) may be used to specify ``dynamic
sources.'' Variables are good. Know them. Love them. Live
them.
Debugging of makefiles is best accomplished using the -n,
and -d m flags.
2.9. Exercises
TBA
3. Short-cuts and Other Nice Things
Based on what I've told you so far, you may have gotten the
impression that Make is just a way of storing away commands
and making sure you don't forget to compile something. Good.
That's just what it is. However, the ways I've described
April 27, 2013
Make -- A Tutorial PSD:12-17
have been inelegant, at best, and painful, at worst. This
chapter contains things that make the writing of makefiles
easier and the makefiles themselves shorter and easier to
modify (and, occasionally, simpler). In this chapter, I
assume you are somewhat more familiar with Unix than I did
in chapter 2, just so you're on your toes. So without
further ado...
3.1. Transformation Rules
As you know, a file's name consists of two parts: a base
name, which gives some hint as to the contents of the file,
and a suffix, which usually indicates the format of the
file. Over the years, as UNIX- has developed, naming conven-
tions, with regard to suffixes, have also developed that
have become almost as incontrovertible as Law. E.g. a file
ending in .c is assumed to contain C source code; one with a
.o suffix is assumed to be a compiled object file that may
be linked into any program; a file with a .ms suffix is usu-
ally a text file to be processed by Troff with the -ms macro
package, and so on. One of the best aspects of Make comes
from its understanding of how the suffix of a file pertains
to its contents and Make's ability to do things with a file
based solely on its suffix. This ability comes from some-
thing known as a transformation rule. A transformation rule
specifies how to change a file with one suffix into a file
with another suffix.
A transformation rule looks much like a dependency line,
except the target is made of two known suffixes stuck
together. Suffixes are made known to Make by placing them as
sources on a dependency line whose target is the special
target .SUFFIXES. E.g.
.SUFFIXES : .o .c
.c.o :
$(CC) $(CFLAGS) -c $(.IMPSRC)
The creation script attached to the target is used to
transform a file with the first suffix (in this case, .c)
into a file with the second suffix (here, .o). In addition,
the target inherits whatever attributes have been applied to
the transformation rule. The simple rule given above says
that to transform a C source file into an object file, you
compile it using cc with the -c flag. This rule is taken
straight from the system makefile. Many transformation rules
(and suffixes) are defined there, and I refer you to it for
more examples.
There are several things to note about the transformation
rule given above:
1) The .IMPSRC variable. This variable is set to the
``implied source'' (the file from which the target
_________________________
- UNIX is a registered trademark of AT&T Bell Labora-
tories in the USA and other countries.
April 27, 2013
PSD:12-18 Make -- A Tutorial
is being created; the one with the first suffix),
which, in this case, is the .c file.
2) The CFLAGS variable. Almost all of the transforma-
tion rules in the system makefile are set up using
variables that you can alter in your makefile to
tailor the rule to your needs. In this case, if
you want all your C files to be compiled with the
-g flag, to provide information for dbx, you would
set the CFLAGS variable to contain -g (``CFLAGS =
-g'') and Make would take care of the rest.
To give you a quick example, the makefile in 2.3.4 could be
changed to this:
OBJS = a.o b.o c.o
program : $(OBJS)
$(CC) -o $(.TARGET) $(.ALLSRC)
$(OBJS) : defs.h
The transformation rule I gave above takes the place of the
6 lines[1]
a.o : a.c
cc -c a.c
b.o : b.c
cc -c b.c
c.o : c.c
cc -c c.c
Now you may be wondering about the dependency between the .o
and .c files -- it's not mentioned anywhere in the new
makefile. This is because it isn't needed: one of the
effects of applying a transformation rule is the target
comes to depend on the implied source. That's why it's
called the implied source.
For a more detailed example. Say you have a makefile like
this:
a.out : a.o b.o
$(CC) $(.ALLSRC)
and a directory set up like this:
total 4
-rw-rw-r-- 1 deboor 34 Sep 7 00:43 Makefile
-rw-rw-r-- 1 deboor 119 Oct 3 19:39 a.c
-rw-rw-r-- 1 deboor 201 Sep 7 00:43 a.o
-rw-rw-r-- 1 deboor 69 Sep 7 00:43 b.c
While just typing ``make'' will do the right thing, it's
much more informative to type ``make -d s''. This will show
_________________________
[1] This is also somewhat cleaner, I think, than the
dynamic source solution presented in 2.6
April 27, 2013
Make -- A Tutorial PSD:12-19
you what Make is up to as it processes the files. In this
case, Make prints the following:
Suff_FindDeps (a.out)
using existing source a.o
applying .o -> .out to "a.o"
Suff_FindDeps (a.o)
trying a.c...got it
applying .c -> .o to "a.c"
Suff_FindDeps (b.o)
trying b.c...got it
applying .c -> .o to "b.c"
Suff_FindDeps (a.c)
trying a.y...not there
trying a.l...not there
trying a.c,v...not there
trying a.y,v...not there
trying a.l,v...not there
Suff_FindDeps (b.c)
trying b.y...not there
trying b.l...not there
trying b.c,v...not there
trying b.y,v...not there
trying b.l,v...not there
--- a.o ---
cc -c a.c
--- b.o ---
cc -c b.c
--- a.out ---
cc a.o b.o
Suff_FindDeps is the name of a function in Make that is
called to check for implied sources for a target using
transformation rules. The transformations it tries are,
naturally enough, limited to the ones that have been defined
(a transformation may be defined multiple times, by the way,
but only the most recent one will be used). You will notice,
however, that there is a definite order to the suffixes that
are tried. This order is set by the relative positions of
the suffixes on the .SUFFIXES line -- the earlier a suffix
appears, the earlier it is checked as the source of a
transformation. Once a suffix has been defined, the only way
to change its position in the pecking order is to remove all
the suffixes (by having a .SUFFIXES dependency line with no
sources) and redefine them in the order you want.
(Previously-defined transformation rules will be automati-
cally redefined as the suffixes they involve are re-
entered.)
Another way to affect the search order is to make the depen-
dency explicit. In the above example, a.out depends on a.o
and b.o. Since a transformation exists from .o to .out, Make
uses that, as indicated by the ``using existing source a.o''
message.
The search for a transformation starts from the suffix of
April 27, 2013
PSD:12-20 Make -- A Tutorial
the target and continues through all the defined transforma-
tions, in the order dictated by the suffix ranking, until an
existing file with the same base (the target name minus the
suffix and any leading directories) is found. At that point,
one or more transformation rules will have been found to
change the one existing file into the target.
For example, ignoring what's in the system makefile for now,
say you have a makefile like this:
.SUFFIXES : .out .o .c .y .l
.l.c :
lex $(.IMPSRC)
mv lex.yy.c $(.TARGET)
.y.c :
yacc $(.IMPSRC)
mv y.tab.c $(.TARGET)
.c.o :
cc -c $(.IMPSRC)
.o.out :
cc -o $(.TARGET) $(.IMPSRC)
and the single file jive.l. If you were to type ``make -rd
ms jive.out,'' you would get the following output for
jive.out:
Suff_FindDeps (jive.out)
trying jive.o...not there
trying jive.c...not there
trying jive.y...not there
trying jive.l...got it
applying .l -> .c to "jive.l"
applying .c -> .o to "jive.c"
applying .o -> .out to "jive.o"
and this is why: Make starts with the target jive.out, fig-
ures out its suffix (.out) and looks for things it can
transform to a .out file. In this case, it only finds .o, so
it looks for the file jive.o. It fails to find it, so it
looks for transformations into a .o file. Again it has only
one choice: .c. So it looks for jive.c and, as you know,
fails to find it. At this point it has two choices: it can
create the .c file from either a .y file or a .l file. Since
.y came first on the .SUFFIXES line, it checks for jive.y
first, but can't find it, so it looks for jive.l and, lo and
behold, there it is. At this point, it has defined a
transformation path as follows: .l -> .c -> .o -> .out and
applies the transformation rules accordingly. For complete-
ness, and to give you a better idea of what Make actually
did with this three-step transformation, this is what Make
printed for the rest of the process:
April 27, 2013
Make -- A Tutorial PSD:12-21
Suff_FindDeps (jive.o)
using existing source jive.c
applying .c -> .o to "jive.c"
Suff_FindDeps (jive.c)
using existing source jive.l
applying .l -> .c to "jive.l"
Suff_FindDeps (jive.l)
Examining jive.l...modified 17:16:01 Oct 4, 1987...up-to-date
Examining jive.c...non-existent...out-of-date
--- jive.c ---
lex jive.l
... meaningless lex output deleted ...
mv lex.yy.c jive.c
Examining jive.o...non-existent...out-of-date
--- jive.o ---
cc -c jive.c
Examining jive.out...non-existent...out-of-date
--- jive.out ---
cc -o jive.out jive.o
One final question remains: what does Make do with targets
that have no known suffix? Make simply pretends it actually
has an empty suffix and searches for transformations accord-
ingly. Those special transformation rules involve just one
source suffix, like this:
.o :
cc -o $(.TARGET) $(.IMPSRC)
3.2. Including Other Makefiles
Just as for programs, it is often useful to extract certain
parts of a makefile into another file and just include it in
other makefiles somehow. Many compilers allow you say some-
thing like
#include "defs.h"
to include the contents of defs.h in the source file. Make
allows you to do the same thing for makefiles, with the
added ability to use variables in the filenames. An include
directive in a makefile looks either like this:
.include <file>
or this
.include "file"
The difference between the two is where Make searches for
the file: the first way, Make will look for the file only in
the system makefile directory (or directories) The system
makefile directory search path can be overridden via the -m
April 27, 2013
PSD:12-22 Make -- A Tutorial
option. For files in double-quotes, the search is more com-
plex:
1) The directory of the makefile that's including the
file.
2) The current directory (the one in which you
invoked Make).
3) The directories given by you using -I flags, in
the order in which you gave them.
4) Directories given by .PATH dependency lines (see
chapter 4).
5) The system makefile directory.
in that order.
You are free to use Make variables in the filename--Make
will expand them before searching for the file. You must
specify the searching method with either angle brackets or
double-quotes outside of a variable expansion. I.e. the fol-
lowing
SYSTEM = <command.mk>
#include $(SYSTEM)
won't work.
3.3. Saving Commands
There may come a time when you will want to save certain
commands to be executed when everything else is done. For
instance: you're making several different libraries at one
time and you want to create the members in parallel. Problem
is, ranlib is another one of those programs that can't be
run more than once in the same directory at the same time
(each one creates a file called __.SYMDEF into which it
stuffs information for the linker to use. Two of them run-
ning at once will overwrite each other's file and the result
will be garbage for both parties). You might want a way to
save the ranlib commands til the end so they can be run one
after the other, thus keeping them from trashing each
other's file. Make allows you to do this by inserting an
ellipsis (``...'') as a command between commands to be run
at once and those to be run later.
So for the ranlib case above, you might do this:
lib1.a : $(LIB1OBJS)
rm -f $(.TARGET)
ar cr $(.TARGET) $(.ALLSRC)
...
ranlib $(.TARGET)
lib2.a : $(LIB2OBJS)
rm -f $(.TARGET)
ar cr $(.TARGET) $(.ALLSRC)
...
ranlib $(.TARGET)
April 27, 2013
Make -- A Tutorial PSD:12-23
This would save both
ranlib $(.TARGET)
commands until the end, when they would run one after the
other (using the correct value for the .TARGET variable, of
course).
Commands saved in this manner are only executed if Make
manages to re-create everything without an error.
3.4. Target Attributes
Make allows you to give attributes to targets by means of
special sources. Like everything else Make uses, these
sources begin with a period and are made up of all upper-
case letters. There are various reasons for using them, and
I will try to give examples for most of them. Others you'll
have to find uses for yourself. Think of it as ``an exercise
for the reader.'' By placing one (or more) of these as a
source on a dependency line, you are ``marking the target(s)
with that attribute.'' That's just the way I phrase it, so
you know.
Any attributes given as sources for a transformation rule
are applied to the target of the transformation rule when
the rule is applied.
.DONTCARE If a target is marked with this attribute and
Make can't figure out how to create it, it will
ignore this fact and assume the file isn't
really needed or actually exists and Make just
can't find it. This may prove wrong, but the
error will be noted later on, not when Make
tries to create the target so marked. This
attribute also prevents Make from attempting to
touch the target if it is given the -t flag.
.EXEC This attribute causes its shell script to be
executed while having no effect on targets that
depend on it. This makes the target into a sort
of subroutine. An example. Say you have some
LISP files that need to be compiled and loaded
into a LISP process. To do this, you echo LISP
commands into a file and execute a LISP with
this file as its input when everything's done.
Say also that you have to load other files from
another system before you can compile your files
and further, that you don't want to go through
the loading and dumping unless one of your files
has changed. Your makefile might look a little
bit like this (remember, this is an educational
example, and don't worry about the COMPILE rule,
all will soon become clear, grasshopper):
April 27, 2013
PSD:12-24 Make -- A Tutorial
system : init a.fasl b.fasl c.fasl
for i in $(.ALLSRC);
do
echo -n '(load "' >> input
echo -n ${i} >> input
echo '")' >> input
done
echo '(dump "$(.TARGET)")' >> input
lisp < input
a.fasl : a.l init COMPILE
b.fasl : b.l init COMPILE
c.fasl : c.l init COMPILE
COMPILE : .USE
echo '(compile "$(.ALLSRC)")' >> input
init : .EXEC
echo '(load-system)' > input
.EXEC sources, don't appear in the local vari-
ables of targets that depend on them (nor are
they touched if Make is given the -t flag). Note
that all the rules, not just that for system,
include init as a source. This is because none
of the other targets can be made until init has
been made, thus they depend on it.
.EXPORT This is used to mark those targets whose crea-
tion should be sent to another machine if at all
possible. This may be used by some exportation
schemes if the exportation is expensive. You
should ask your system administrator if it is
necessary.
.EXPORTSAME Tells the export system that the job should be
exported to a machine of the same architecture
as the current one. Certain operations (e.g.
running text through nroff) can be performed the
same on any architecture (CPU and operating sys-
tem type), while others (e.g. compiling a pro-
gram with cc) must be performed on a machine
with the same architecture. Not all export sys-
tems will support this attribute.
.IGNORE Giving a target the .IGNORE attribute causes
Make to ignore errors from any of the target's
commands, as if they all had `-' before them.
.INVISIBLE This allows you to specify one target as a
source for another without the one affecting the
other's local variables. Useful if, say, you
have a makefile that creates two programs, one
of which is used to create the other, so it must
exist before the other is created. You could say
prog1 : $(PROG1OBJS) prog2 MAKEINSTALL
prog2 : $(PROG2OBJS) .INVISIBLE MAKEINSTALL
April 27, 2013
Make -- A Tutorial PSD:12-25
where MAKEINSTALL is some complex .USE rule (see
below) that depends on the .ALLSRC variable con-
taining the right things. Without the .INVISIBLE
attribute for prog2, the MAKEINSTALL rule
couldn't be applied. This is not as useful as it
should be, and the semantics may change (or the
whole thing go away) in the not-too-distant
future.
.JOIN This is another way to avoid performing some
operations in parallel while permitting every-
thing else to be done so. Specifically it forces
the target's shell script to be executed only if
one or more of the sources was out-of-date. In
addition, the target's name, in both its .TARGET
variable and all the local variables of any tar-
get that depends on it, is replaced by the value
of its .ALLSRC variable. As an example, suppose
you have a program that has four libraries that
compile in the same directory along with, and at
the same time as, the program. You again have
the problem with ranlib that I mentioned ear-
lier, only this time it's more severe: you can't
just put the ranlib off to the end since the
program will need those libraries before it can
be re-created. You can do something like this:
program : $(OBJS) libraries
cc -o $(.TARGET) $(.ALLSRC)
libraries : lib1.a lib2.a lib3.a lib4.a .JOIN
ranlib $(.OODATE)
In this case, Make will re-create the $(OBJS) as
necessary, along with lib1.a, lib2.a, lib3.a and
lib4.a. It will then execute ranlib on any
library that was changed and set program's
.ALLSRC variable to contain what's in $(OBJS)
followed by ``lib1.a lib2.a lib3.a lib4.a.'' In
case you're wondering, it's called .JOIN because
it joins together different threads of the
``input graph'' at the target marked with the
attribute. Another aspect of the .JOIN attribute
is it keeps the target from being created if the
-t flag was given.
.MAKE The .MAKE attribute marks its target as being a
recursive invocation of Make. This forces Make
to execute the script associated with the target
(if it's out-of-date) even if you gave the -n or
-t flag. By doing this, you can start at the top
of a system and type
make -n
and have it descend the directory tree (if your
April 27, 2013
PSD:12-26 Make -- A Tutorial
makefiles are set up correctly), printing what
it would have executed if you hadn't included
the -n flag.
.NOEXPORT If possible, Make will attempt to export the
creation of all targets to another machine (this
depends on how Make was configured). Sometimes,
the creation is so simple, it is pointless to
send it to another machine. If you give the tar-
get the .NOEXPORT attribute, it will be run
locally, even if you've given Make the -L 0
flag.
.NOTMAIN Normally, if you do not specify a target to make
in any other way, Make will take the first tar-
get on the first dependency line of a makefile
as the target to create. That target is known as
the ``Main Target'' and is labeled as such if
you print the dependencies out using the -p
flag. Giving a target this attribute tells Make
that the target is definitely not the Main Tar-
get. This allows you to place targets in an
included makefile and have Make create something
else by default.
.PRECIOUS When Make is interrupted (you type control-C at
the keyboard), it will attempt to clean up after
itself by removing any half-made targets. If a
target has the .PRECIOUS attribute, however,
Make will leave it alone. An additional side
effect of the `::' operator is to mark the tar-
gets as .PRECIOUS.
.SILENT Marking a target with this attribute keeps its
commands from being printed when they're exe-
cuted, just as if they had an `@' in front of
them.
.USE By giving a target this attribute, you turn it
into Make's equivalent of a macro. When the tar-
get is used as a source for another target, the
other target acquires the commands, sources and
attributes (except .USE) of the source. If the
target already has commands, the .USE target's
commands are added to the end. If more than one
.USE-marked source is given to a target, the
rules are applied sequentially.
The typical .USE rule (as I call them) will use
the sources of the target to which it is applied
(as stored in the .ALLSRC variable for the tar-
get) as its ``arguments,'' if you will. For
example, you probably noticed that the commands
for creating lib1.a and lib2.a in the example in
section 3.3 were exactly the same. You can use
the .USE attribute to eliminate the repetition,
like so:
April 27, 2013
Make -- A Tutorial PSD:12-27
lib1.a : $(LIB1OBJS) MAKELIB
lib2.a : $(LIB2OBJS) MAKELIB
MAKELIB : .USE
rm -f $(.TARGET)
ar cr $(.TARGET) $(.ALLSRC)
...
ranlib $(.TARGET)
Several system makefiles (not to be confused
with The System Makefile) make use of these
.USE rules to make your life easier (they're in
the default, system makefile directory...take a
look). Note that the .USE rule source itself
(MAKELIB) does not appear in any of the target's
local variables. There is no limit to the number
of times I could use the MAKELIB rule. If there
were more libraries, I could continue with
``lib3.a : $(LIB3OBJS) MAKELIB'' and so on and
so forth.
3.5. Special Targets
As there were in Make, so there are certain targets that
have special meaning to Make. When you use one on a depen-
dency line, it is the only target that may appear on the
left-hand-side of the operator. As for the attributes and
variables, all the special targets begin with a period and
consist of upper-case letters only. I won't describe them
all in detail because some of them are rather complex and
I'll describe them in more detail than you'll want in
chapter 4. The targets are as follows:
.BEGIN Any commands attached to this target are executed
before anything else is done. You can use it for
any initialization that needs doing.
.DEFAULT This is sort of a .USE rule for any target (that
was used only as a source) that Make can't figure
out any other way to create. It's only ``sort of''
a .USE rule because only the shell script attached
to the .DEFAULT target is used. The .IMPSRC vari-
able of a target that inherits .DEFAULT's commands
is set to the target's own name.
.END This serves a function similar to .BEGIN, in that
commands attached to it are executed once every-
thing has been re-created (so long as no errors
occurred). It also serves the extra function of
being a place on which Make can hang commands you
put off to the end. Thus the script for this tar-
get will be executed before any of the commands
you save with the ``...''.
.EXPORT The sources for this target are passed to the
exportation system compiled into Make. Some sys-
tems will use these sources to configure them-
selves. You should ask your system administrator
April 27, 2013
PSD:12-28 Make -- A Tutorial
about this.
.IGNORE This target marks each of its sources with the
.IGNORE attribute. If you don't give it any
sources, then it is like giving the -i flag when
you invoke Make -- errors are ignored for all com-
mands.
.INCLUDES The sources for this target are taken to be suf-
fixes that indicate a file that can be included in
a program source file. The suffix must have
already been declared with .SUFFIXES (see below).
Any suffix so marked will have the directories on
its search path (see .PATH, below) placed in the
.INCLUDES variable, each preceded by a -I flag.
This variable can then be used as an argument for
the compiler in the normal fashion. The .h suffix
is already marked in this way in the system
makefile. E.g. if you have
.SUFFIXES : .bitmap
.PATH.bitmap : /usr/local/X/lib/bitmaps
.INCLUDES : .bitmap
Make will place ``-I/usr/local/X/lib/bitmaps'' in
the .INCLUDES variable and you can then say
cc $(.INCLUDES) -c xprogram.c
(Note: the .INCLUDES variable is not actually
filled in until the entire makefile has been
read.)
.INTERRUPTWhen Make is interrupted, it will execute the com-
mands in the script for this target, if it exists.
.LIBS This does for libraries what .INCLUDES does for
include files, except the flag used is -L, as
required by those linkers that allow you to tell
them where to find libraries. The variable used is
.LIBS. Be forewarned that Make may not have been
compiled to do this if the linker on your system
doesn't accept the -L flag, though the .LIBS vari-
able will always be defined once the makefile has
been read.
.MAIN If you didn't give a target (or targets) to create
when you invoked Make, it will take the sources of
this target as the targets to create.
.MAKEFLAGSThis target provides a way for you to always
specify flags for Make when the makefile is used.
The flags are just as they would be typed to the
shell (except you can't use shell variables unless
they're in the environment), though the -f and -r
flags have no effect.
.NULL This allows you to specify what suffix Make should
pretend a file has if, in fact, it has no known
suffix. Only one suffix may be so designated. The
last source on the dependency line is the suffix
April 27, 2013
Make -- A Tutorial PSD:12-29
that is used (you should, however, only give one
suffix...).
.PATH If you give sources for this target, Make will
take them as directories in which to search for
files it cannot find in the current directory. If
you give no sources, it will clear out any direc-
tories added to the search path before. Since the
effects of this all get very complex, I'll leave
it til chapter four to give you a complete expla-
nation.
.PATHsuffixThis does a similar thing to .PATH, but it does
it only for files with the given suffix. The suf-
fix must have been defined already. Look at Search
Paths (section 4.1) for more information.
.PRECIOUS Similar to .IGNORE, this gives the .PRECIOUS
attribute to each source on the dependency line,
unless there are no sources, in which case the
.PRECIOUS attribute is given to every target in
the file.
.RECURSIVEThis target applies the .MAKE attribute to all its
sources. It does nothing if you don't give it any
sources.
.SHELL Make is not constrained to only using the Bourne
shell to execute the commands you put in the
makefile. You can tell it some other shell to use
with this target. Check out A Shell is a Shell is
a Shell (section 4.4) for more information.
.SILENT When you use .SILENT as a target, it applies the
.SILENT attribute to each of its sources. If there
are no sources on the dependency line, then it is
as if you gave Make the -s flag and no commands
will be echoed.
.SUFFIXES This is used to give new file suffixes for Make to
handle. Each source is a suffix Make should recog-
nize. If you give a .SUFFIXES dependency line with
no sources, Make will forget about all the suf-
fixes it knew (this also nukes the null suffix).
For those targets that need to have suffixes
defined, this is how you do it.
In addition to these targets, a line of the form
attribute : sources
applies the attribute to all the targets listed as sources.
3.6. Modifying Variable Expansion
Variables need not always be expanded verbatim. Make defines
several modifiers that may be applied to a variable's value
before it is expanded. You apply a modifier by placing it
after the variable name with a colon between the two, like
so:
${VARIABLE:modifier}
April 27, 2013
PSD:12-30 Make -- A Tutorial
Each modifier is a single character followed by something
specific to the modifier itself. You may apply as many
modifiers as you want -- each one is applied to the result
of the previous and is separated from the previous by
another colon.
There are seven ways to modify a variable's expansion, most
of which come from the C shell variable modification charac-
ters:
Mpattern
This is used to select only those words (a word is
a series of characters that are neither spaces nor
tabs) that match the given pattern. The pattern is
a wildcard pattern like that used by the shell,
where * means 0 or more characters of any sort; ?
is any single character; [abcd] matches any single
character that is either `a', `b', `c' or `d'
(there may be any number of characters between the
brackets); [0-9] matches any single character that
is between `0' and `9' (i.e. any digit. This form
may be freely mixed with the other bracket form),
and `\' is used to escape any of the characters
`*', `?', `[' or `:', leaving them as regular
characters to match themselves in a word. For
example, the system makefile <makedepend.mk> uses
``$(CFLAGS:M-[ID]*)'' to extract all the -I and -D
flags that would be passed to the C compiler. This
allows it to properly locate include files and
generate the correct dependencies.
Npattern
This is identical to :M except it substitutes all
words that don't match the given pattern.
S/search-string/replacement-string/[g]
Causes the first occurrence of search-string in
the variable to be replaced by replacement-string,
unless the g flag is given at the end, in which
case all occurrences of the string are replaced.
The substitution is performed on each word in the
variable in turn. If search-string begins with a
^, the string must match starting at the beginning
of the word. If search-string ends with a $, the
string must match to the end of the word (these
two may be combined to force an exact match). If a
backslash precedes these two characters, however,
they lose their special meaning. Variable expan-
sion also occurs in the normal fashion inside both
the search-string and the replacement-string,
except that a backslash is used to prevent the
expansion of a $, not another dollar sign, as is
usual. Note that search-string is just a string,
not a pattern, so none of the usual regular-
expression/wildcard characters have any special
meaning save ^ and $. In the replacement string,
the & character is replaced by the search-string
unless it is preceded by a backslash. You are
April 27, 2013
Make -- A Tutorial PSD:12-31
allowed to use any character except colon or exc-
lamation point to separate the two strings. This
so-called delimiter character may be placed in
either string by preceding it with a backslash.
T Replaces each word in the variable expansion by
its last component (its ``tail''). For example,
given
OBJS = ../lib/a.o b /usr/lib/libm.a
TAILS = $(OBJS:T)
the variable TAILS would expand to ``a.o b
libm.a.''
H This is similar to :T, except that every word is
replaced by everything but the tail (the
``head''). Using the same definition of OBJS, the
string ``$(OBJS:H)'' would expand to ``../lib
/usr/lib.'' Note that the final slash on the heads
is removed and anything without a head is replaced
by the empty string.
E :E replaces each word by its suffix (``exten-
sion''). So ``$(OBJS:E)'' would give you ``.o
.a.''
R This replaces each word by everything but the suf-
fix (the ``root'' of the word). ``$(OBJS:R)''
expands to `` ../lib/a b /usr/lib/libm.''
In addition, the System V style of substitution is also sup-
ported. This looks like:
$(VARIABLE:search-string=replacement)
It must be the last modifier in the chain. The search is
anchored at the end of each word, so only suffixes or whole
words may be replaced.
3.7. More on Debugging
3.8. More Exercises
(3.1)You've got a set programs, each of which is created
from its own assembly-language source file (suffix
.asm). Each program can be assembled into two versions,
one with error-checking code assembled in and one
without. You could assemble them into files with dif-
ferent suffixes (.eobj and .obj, for instance), but
your linker only understands files that end in .obj. To
top it all off, the final executables must have the
suffix .exe. How can you still use transformation rules
to make your life easier (Hint: assume the error-
checking versions have ec tacked onto their prefix)?
(3.2)Assume, for a moment or two, you want to perform a sort
of ``indirection'' by placing the name of a variable
into another one, then you want to get the value of the
first by expanding the second somehow. Unfortunately,
Make doesn't allow constructs like
April 27, 2013
PSD:12-32 Make -- A Tutorial
$($(FOO))
What do you do? Hint: no further variable expansion is
performed after modifiers are applied, thus if you
cause a $ to occur in the expansion, that's what will
be in the result.
4. Make for Gods
This chapter is devoted to those facilities in Make that
allow you to do a great deal in a makefile with very little
work, as well as do some things you couldn't do in Make
without a great deal of work (and perhaps the use of other
programs). The problem with these features, is they must be
handled with care, or you will end up with a mess.
Once more, I assume a greater familiarity with UNIX or
Sprite than I did in the previous two chapters.
4.1. Search Paths
Make supports the dispersal of files into multiple direc-
tories by allowing you to specify places to look for sources
with .PATH targets in the makefile. The directories you give
as sources for these targets make up a ``search path.'' Only
those files used exclusively as sources are actually sought
on a search path, the assumption being that anything listed
as a target in the makefile can be created by the makefile
and thus should be in the current directory.
There are two types of search paths in Make: one is used for
all types of files (including included makefiles) and is
specified with a plain .PATH target (e.g. ``.PATH : RCS''),
while the other is specific to a certain type of file, as
indicated by the file's suffix. A specific search path is
indicated by immediately following the .PATH with the suffix
of the file. For instance
.PATH.h : /sprite/lib/include /sprite/att/lib/include
would tell Make to look in the directories
/sprite/lib/include and /sprite/att/lib/include for any
files whose suffix is .h.
The current directory is always consulted first to see if a
file exists. Only if it cannot be found there are the direc-
tories in the specific search path, followed by those in the
general search path, consulted.
A search path is also used when expanding wildcard charac-
ters. If the pattern has a recognizable suffix on it, the
path for that suffix will be used for the expansion. Other-
wise the default search path is employed.
When a file is found in some directory other than the
current one, all local variables that would have contained
the target's name (.ALLSRC, and .IMPSRC) will instead con-
tain the path to the file, as found by Make. Thus if you
have a file ../lib/mumble.c and a makefile
April 27, 2013
Make -- A Tutorial PSD:12-33
.PATH.c : ../lib
mumble : mumble.c
$(CC) -o $(.TARGET) $(.ALLSRC)
the command executed to create mumble would be ``cc -o mum-
ble ../lib/mumble.c.'' (As an aside, the command in this
case isn't strictly necessary, since it will be found using
transformation rules if it isn't given. This is because .out
is the null suffix by default and a transformation exists
from .c to .out. Just thought I'd throw that in.)
If a file exists in two directories on the same search path,
the file in the first directory on the path will be the one
Make uses. So if you have a large system spread over many
directories, it would behoove you to follow a naming conven-
tion that avoids such conflicts.
Something you should know about the way search paths are
implemented is that each directory is read, and its contents
cached, exactly once -- when it is first encountered -- so
any changes to the directories while Make is running will
not be noted when searching for implicit sources, nor will
they be found when Make attempts to discover when the file
was last modified, unless the file was created in the
current directory. While people have suggested that Make
should read the directories each time, my experience sug-
gests that the caching seldom causes problems. In addition,
not caching the directories slows things down enormously
because of Make's attempts to apply transformation rules
through non-existent files -- the number of extra file-
system searches is truly staggering, especially if many
files without suffixes are used and the null suffix isn't
changed from .out.
4.2. Archives and Libraries
UNIX and Sprite allow you to merge files into an archive
using the ar command. Further, if the files are relocatable
object files, you can run ranlib on the archive and get
yourself a library that you can link into any program you
want. The main problem with archives is they double the
space you need to store the archived files, since there's
one copy in the archive and one copy out by itself. The
problem with libraries is you usually think of them as -lm
rather than /usr/lib/libm.a and the linker thinks they're
out-of-date if you so much as look at them.
Make solves the problem with archives by allowing you to
tell it to examine the files in the archives (so you can
remove the individual files without having to regenerate
them later). To handle the problem with libraries, Make adds
an additional way of deciding if a library is out-of-date:
+ If the table of contents is older than the library, or is
missing, the library is out-of-date.
A library is any target that looks like ``-lname'' or that
ends in a suffix that was marked as a library using the
.LIBS target. .a is so marked in the system makefile.
April 27, 2013
PSD:12-34 Make -- A Tutorial
Members of an archive are specified as ``archive(member[
member...])''. Thus ``'libdix.a(window.o)'' specifies the
file window.o in the archive libdix.a. You may also use
wildcards to specify the members of the archive. Just
remember that most the wildcard characters will only find
existing files.
A file that is a member of an archive is treated specially.
If the file doesn't exist, but it is in the archive, the
modification time recorded in the archive is used for the
file when determining if the file is out-of-date. When
figuring out how to make an archived member target (not the
file itself, but the file in the archive -- the
archive(member) target), special care is taken with the
transformation rules, as follows:
+ archive(member) is made to depend on member.
+ The transformation from the member's suffix to the
archive's suffix is applied to the archive(member) target.
+ The archive(member)'s .TARGET variable is set to the name
of the member if member is actually a target, or the path
to the member file if member is only a source.
+ The .ARCHIVE variable for the archive(member) target is
set to the name of the archive.
+ The .MEMBER variable is set to the actual string inside
the parentheses. In most cases, this will be the same as
the .TARGET variable.
+ The archive(member)'s place in the local variables of the
targets that depend on it is taken by the value of its
.TARGET variable.
Thus, a program library could be created with the following
makefile:
.o.a :
...
rm -f $(.TARGET:T)
OBJS = obj1.o obj2.o obj3.o
libprog.a : libprog.a($(OBJS))
ar cru $(.TARGET) $(.OODATE)
ranlib $(.TARGET)
This will cause the three object files to be compiled (if
the corresponding source files were modified after the
object file or, if that doesn't exist, the archived object
file), the out-of-date ones archived in libprog.a, a table
of contents placed in the archive and the newly-archived
object files to be removed.
All this is used in the makelib.mk system makefile to create
a single library with ease. This makefile looks like this:
April 27, 2013
Make -- A Tutorial PSD:12-35
#
# Rules for making libraries. The object files that make up the library are
# removed once they are archived.
#
# To make several libraries in parallel, you should define the variable
# "many_libraries". This will serialize the invocations of ranlib.
#
# To use, do something like this:
#
# OBJECTS = <files in the library>
#
# fish.a: fish.a($(OBJECTS)) MAKELIB
#
#
#ifndef _MAKELIB_MK
_MAKELIB_MK =
#include <po.mk>
.po.a .o.a :
...
rm -f $(.MEMBER)
ARFLAGS ?= crl
#
# Re-archive the out-of-date members and recreate the library's table of
# contents using ranlib. If many_libraries is defined, put the ranlib off
# til the end so many libraries can be made at once.
#
MAKELIB : .USE .PRECIOUS
ar $(ARFLAGS) $(.TARGET) $(.OODATE)
#ifndef no_ranlib
# ifdef many_libraries
...
# endif many_libraries
ranlib $(.TARGET)
#endif no_ranlib
#endif _MAKELIB_MK
4.3. On the Condition...
Like the C compiler before it, Make allows you to configure
the makefile, based on the current environment, using condi-
tional statements. A conditional looks like this:
April 27, 2013
PSD:12-36 Make -- A Tutorial
#if boolean expression
lines
#elif another boolean expression
more lines
#else
still more lines
#endif
They may be nested to a maximum depth of 30 and may occur
anywhere (except in a comment, of course). The ``#'' must
the very first character on the line.
Each boolean expression is made up of terms that look like
function calls, the standard C boolean operators &&, ||, and
!, and the standard relational operators ==, !=, >, >=, <,
and <=, with == and != being overloaded to allow string com-
parisons as well. && represents logical AND; || is logical
OR and ! is logical NOT. The arithmetic and string opera-
tors take precedence over all three of these operators,
while NOT takes precedence over AND, which takes precedence
over OR. This precedence may be overridden with
parentheses, and an expression may be parenthesized to your
heart's content. Each term looks like a call on one of four
functions:
make The syntax is make(target) where target is a target
in the makefile. This is true if the given target
was specified on the command line, or as the source
for a .MAIN target (note that the sources for .MAIN
are only used if no targets were given on the com-
mand line).
defined The syntax is defined(variable) and is true if
variable is defined. Certain variables are defined
in the system makefile that identify the system on
which Make is being run.
exists The syntax is exists(file) and is true if the file
can be found on the global search path (i.e. that
defined by .PATH targets, not by .PATHsuffix tar-
gets).
empty This syntax is much like the others, except the
string inside the parentheses is of the same form
as you would put between parentheses when expanding
a variable, complete with modifiers and everything.
The function returns true if the resulting string
is empty (NOTE: an undefined variable in this con-
text will cause at the very least a warning message
about a malformed conditional, and at the worst
will cause the process to stop once it has read the
makefile. If you want to check for a variable being
defined or empty, use the expression
``!defined(var) || empty(var)'' as the definition
of || will prevent the empty() from being evaluated
and causing an error, if the variable is unde-
fined). This can be used to see if a variable con-
tains a given word, for example:
April 27, 2013
Make -- A Tutorial PSD:12-37
#if !empty(var:Mword)
The arithmetic and string operators may only be used to test
the value of a variable. The lefthand side must contain the
variable expansion, while the righthand side contains either
a string, enclosed in double-quotes, or a number. The stan-
dard C numeric conventions (except for specifying an octal
number) apply to both sides. E.g.
#if $(OS) == 4.3
#if $(MACHINE) == "sun3"
#if $(LOAD_ADDR) < 0xc000
are all valid conditionals. In addition, the numeric value
of a variable can be tested as a boolean as follows:
#if $(LOAD)
would see if LOAD contains a non-zero value and
#if !$(LOAD)
would test if LOAD contains a zero value.
In addition to the bare ``#if,'' there are other forms that
apply one of the first two functions to each term. They are
as follows:
ifdef defined
ifndef !defined
ifmake make
ifnmake !make
There are also the ``else if'' forms: elif, elifdef,
elifndef, elifmake, and elifnmake.
For instance, if you wish to create two versions of a pro-
gram, one of which is optimized (the production version) and
the other of which is for debugging (has symbols for dbx),
you have two choices: you can create two makefiles, one of
which uses the -g flag for the compilation, while the other
uses the -O flag, or you can use another target (call it
debug) to create the debug version. The construct below will
take care of this for you. I have also made it so defining
the variable DEBUG (say with make -D DEBUG) will also cause
the debug version to be made.
#if defined(DEBUG) || make(debug)
CFLAGS += -g
#else
CFLAGS += -O
#endif
April 27, 2013
PSD:12-38 Make -- A Tutorial
There are, of course, problems with this approach. The most
glaring annoyance is that if you want to go from making a
debug version to making a production version, you have to
remove all the object files, or you will get some optimized
and some debug versions in the same program. Another annoy-
ance is you have to be careful not to make two targets that
``conflict'' because of some conditionals in the makefile.
For instance
#if make(print)
FORMATTER = ditroff -Plaser_printer
#endif
#if make(draft)
FORMATTER = nroff -Pdot_matrix_printer
#endif
would wreak havoc if you tried ``make draft print'' since
you would use the same formatter for each target. As I said,
this all gets somewhat complicated.
4.4. A Shell is a Shell is a Shell
In normal operation, the Bourne Shell (better known as
``sh'') is used to execute the commands to re-create tar-
gets. Make also allows you to specify a different shell for
it to use when executing these commands. There are several
things Make must know about the shell you wish to use. These
things are specified as the sources for the .SHELL target by
keyword, as follows:
path=path
Make needs to know where the shell actually resides, so
it can execute it. If you specify this and nothing
else, Make will use the last component of the path and
look in its table of the shells it knows and use the
specification it finds, if any. Use this if you just
want to use a different version of the Bourne or C
Shell (yes, Make knows how to use the C Shell too).
name=name
This is the name by which the shell is to be known. It
is a single word and, if no other keywords are speci-
fied (other than path), it is the name by which Make
attempts to find a specification for it (as mentioned
above). You can use this if you would just rather use
the C Shell than the Bourne Shell (``.SHELL: name=csh''
will do it).
quiet=echo-off command
As mentioned before, Make actually controls whether
commands are printed by introducing commands into the
shell's input stream. This keyword, and the next two,
control what those commands are. The quiet keyword is
the command used to turn echoing off. Once it is turned
off, echoing is expected to remain off until the echo-
on command is given.
echo=echo-on command
The command Make should give to turn echoing back on
April 27, 2013
Make -- A Tutorial PSD:12-39
again.
filter=printed echo-off command
Many shells will echo the echo-off command when it is
given. This keyword tells Make in what format the shell
actually prints the echo-off command. Wherever Make
sees this string in the shell's output, it will delete
it and any following whitespace, up to and including
the next newline. See the example at the end of this
section for more details.
echoFlag=flag to turn echoing on
Unless a target has been marked .SILENT, Make wants to
start the shell running with echoing on. To do this, it
passes this flag to the shell as one of its arguments.
If either this or the next flag begins with a `-', the
flags will be passed to the shell as separate argu-
ments. Otherwise, the two will be concatenated (if they
are used at the same time, of course).
errFlag=flag to turn error checking on
Likewise, unless a target is marked .IGNORE, Make
wishes error-checking to be on from the very start. To
this end, it will pass this flag to the shell as an
argument. The same rules for an initial `-' apply as
for the echoFlag.
check=command to turn error checking on
Just as for echo-control, error-control is achieved by
inserting commands into the shell's input stream. This
is the command to make the shell check for errors. It
also serves another purpose if the shell doesn't have
error-control as commands, but I'll get into that in a
minute. Again, once error checking has been turned on,
it is expected to remain on until it is turned off
again.
ignore=command to turn error checking off
This is the command Make uses to turn error checking
off. It has another use if the shell doesn't do error-
control, but I'll tell you about that...now.
hasErrCtl=yes or no
This takes a value that is either yes or no. Now you
might think that the existence of the check and ignore
keywords would be enough to tell Make if the shell can
do error-control, but you'd be wrong. If hasErrCtl is
yes, Make uses the check and ignore commands in a
straight-forward manner. If this is no, however, their
use is rather different. In this case, the check com-
mand is used as a template, in which the string %s is
replaced by the command that's about to be executed, to
produce a command for the shell that will echo the com-
mand to be executed. The ignore command is also used as
a template, again with %s replaced by the command to be
executed, to produce a command that will execute the
command to be executed and ignore any error it returns.
When these strings are used as templates, you must pro-
vide newline(s) (``\n'') in the appropriate place(s).
The strings that follow these keywords may be enclosed in
April 27, 2013
PSD:12-40 Make -- A Tutorial
single or double quotes (the quotes will be stripped off)
and may contain the usual C backslash-characters (\n is new-
line, \r is return, \b is backspace, \' escapes a single-
quote inside single-quotes, \" escapes a double-quote inside
double-quotes). Now for an example.
This is actually the contents of the <shx.mk> system
makefile, and causes Make to use the Bourne Shell in such a
way that each command is printed as it is executed. That is,
if more than one command is given on a line, each will be
printed separately. Similarly, each time the body of a loop
is executed, the commands within that loop will be printed,
etc. The specification runs like this:
#
# This is a shell specification to have the bourne shell echo
# the commands just before executing them, rather than when it reads
# them. Useful if you want to see how variables are being expanded, etc.
#
.SHELL : path=/bin/mksh \
quiet="set -" \
echo="set -x" \
filter="+ set - " \
echoFlag=x \
errFlag=e \
hasErrCtl=yes \
check="set -e" \
ignore="set +e"
It tells Make the following:
+ The shell is located in the file /bin/mksh. It need not
tell Make that the name of the shell is mksh as Make can
figure that out for itself (it's the last component of the
path).
+ The command to stop echoing is set -.
+ The command to start echoing is set -x.
+ When the echo off command is executed, the shell will
print + set - (The `+' comes from using the -x flag
(rather than the -v flag Make usually uses)). Make will
remove all occurrences of this string from the output, so
you don't notice extra commands you didn't put there.
+ The flag the Bourne Shell will take to start echoing in
this way is the -x flag. The Bourne Shell will only take
its flag arguments concatenated as its first argument, so
neither this nor the errFlag specification begins with a
-.
+ The flag to use to turn error-checking on from the start
is -e.
+ The shell can turn error-checking on and off, and the com-
mands to do so are set +e and set -e, respectively.
This will cause Make to execute the two commands
echo "+ cmd"
sh -c 'cmd || true'
April 27, 2013
Make -- A Tutorial PSD:12-41
for each command for which errors are to be ignored. (In
case you are wondering, the thing for ignore tells the shell
to execute another shell without error checking on and
always exit 0, since the || causes the exit 0 to be executed
only if the first command exited non-zero, and if the first
command exited zero, the shell will also exit zero, since
that's the last command it executed).
4.5. Compatibility
There are three (well, 3 1/2) levels of backwards-
compatibility built into Make. Most makefiles will need
none at all. Some may need a little bit of work to operate
correctly when run in parallel. Each level encompasses the
previous levels (e.g. -B (one shell per command) implies -V)
The three levels are described in the following three sec-
tions.
4.5.1. DEFCON 3 -- Variable Expansion
As noted before, Make will not expand a variable unless it
knows of a value for it. This can cause problems for
makefiles that expect to leave variables undefined except in
special circumstances (e.g. if more flags need to be passed
to the C compiler or the output from a text processor should
be sent to a different printer). If the variables are
enclosed in curly braces (``${PRINTER}''), the shell will
let them pass. If they are enclosed in parentheses, however,
the shell will declare a syntax error and the make will come
to a grinding halt.
You have two choices: change the makefile to define the
variables (their values can be overridden on the command
line, since that's where they would have been set if you
used Make, anyway) or always give the -V flag (this can be
done with the .MAKEFLAGS target, if you want).
4.5.2. DEFCON 2 -- The Number of the Beast
Then there are the makefiles that expect certain commands,
such as changing to a different directory, to not affect
other commands in a target's creation script. You can solve
this is either by going back to executing one shell per com-
mand (which is what the -B flag forces Make to do), which
slows the process down a good bit and requires you to use
semicolons and escaped newlines for shell constructs, or by
changing the makefile to execute the offending command(s) in
a subshell (by placing the line inside parentheses), like
so:
install :: .MAKE
(cd src; $(.MAKE) install)
(cd lib; $(.MAKE) install)
(cd man; $(.MAKE) install)
This will always execute the three makes (even if the -n
flag was given) because of the combination of the ``::''
operator and the .MAKE attribute. Each command will change
April 27, 2013
PSD:12-42 Make -- A Tutorial
to the proper directory to perform the install, leaving the
main shell in the directory in which it started.
4.5.3. DEFCON 1 -- Imitation is the Not the Highest Form of
Flattery
The final category of makefile is the one where every com-
mand requires input, the dependencies are incompletely
specified, or you simply cannot create more than one target
at a time, as mentioned earlier. In addition, you may not
have the time or desire to upgrade the makefile to run
smoothly with Make. If you are the conservative sort, this
is the compatibility mode for you. It is entered by giving
Make the -B flag. This includes:
+ No parallel execution.
+ Targets are made in the exact order specified by the
makefile. The sources for each target are made in strict
left-to-right order, etc.
+ A single Bourne shell is used to execute each command,
thus the shell's $$ variable is useless, changing direc-
tories doesn't work across command lines, etc.
+ If no special characters exist in a command line, Make
will break the command into words itself and execute the
command directly, without executing a shell first. The
characters that cause Make to execute a shell are: #, =,
|, ^, (, ), {, }, ;, &, <, >, *, ?, [, ], :, $, `, and \.
You should notice that these are all the characters that
are given special meaning by the shell (except ' and ,
which Make deals with all by its lonesome).
4.6. The Way Things Work
When Make reads the makefile, it parses sources and targets
into nodes in a graph. The graph is directed only in the
sense that Make knows which way is up. Each node contains
not only links to all its parents and children (the nodes
that depend on it and those on which it depends, respec-
tively), but also a count of the number of its children that
have already been processed.
The most important thing to know about how Make uses this
graph is that the traversal is breadth-first and occurs in
two passes.
After Make has parsed the makefile, it begins with the nodes
the user has told it to make (either on the command line, or
via a .MAIN target, or by the target being the first in the
file not labeled with the .NOTMAIN attribute) placed in a
queue. It continues to take the node off the front of the
queue, mark it as something that needs to be made, pass the
node to Suff_FindDeps (mentioned earlier) to find any impli-
cit sources for the node, and place all the node's children
that have yet to be marked at the end of the queue. If any
of the children is a .USE rule, its attributes are applied
to the parent, then its commands are appended to the
parent's list of commands and its children are linked to its
parent. The parent's unmade children counter is then decre-
mented (since the .USE node has been processed). You will
April 27, 2013
Make -- A Tutorial PSD:12-43
note that this allows a .USE node to have children that are
.USE nodes and the rules will be applied in sequence. If the
node has no children, it is placed at the end of another
queue to be examined in the second pass. This process con-
tinues until the first queue is empty.
At this point, all the leaves of the graph are in the exami-
nation queue. Make removes the node at the head of the queue
and sees if it is out-of-date. If it is, it is passed to a
function that will execute the commands for the node asyn-
chronously. When the commands have completed, all the node's
parents have their unmade children counter decremented and,
if the counter is then 0, they are placed on the examination
queue. Likewise, if the node is up-to-date. Only those
parents that were marked on the downward pass are processed
in this way. Thus Make traverses the graph back up to the
nodes the user instructed it to create. When the examination
queue is empty and no shells are running to create a target,
Make is finished.
Once all targets have been processed, Make executes the com-
mands attached to the .END target, either explicitly or
through the use of an ellipsis in a shell script. If there
were no errors during the entire process but there are still
some targets unmade (Make keeps a running count of how many
targets are left to be made), there is a cycle in the graph.
Make does a depth-first traversal of the graph to find all
the targets that weren't made and prints them out one by
one.
5. Answers to Exercises
(3.1)This is something of a trick question, for which I apo-
logize. The trick comes from the UNIX definition of a
suffix, which Make doesn't necessarily share. You will
have noticed that all the suffixes used in this
tutorial (and in UNIX in general) begin with a period
(.ms, .c, etc.). Now, Make's idea of a suffix is more
like English's: it's the characters at the end of a
word. With this in mind, one possible solution to this
problem goes as follows:
.SUFFIXES : ec.exe .exe ec.obj .obj .asm
ec.objec.exe .obj.exe :
link -o $(.TARGET) $(.IMPSRC)
.asmec.obj :
asm -o $(.TARGET) -DDO_ERROR_CHECKING $(.IMPSRC)
.asm.obj :
asm -o $(.TARGET) $(.IMPSRC)
(3.2)The trick to this one lies in the ``:='' variable-
assignment operator and the ``:S'' variable-expansion
modifier. Basically what you want is to take the
pointer variable, so to speak, and transform it into an
invocation of the variable at which it points. You
might try something like
April 27, 2013
PSD:12-44 Make -- A Tutorial
$(PTR:S/^/\$(/:S/$/))
which places ``$('' at the front of the variable name
and ``)'' at the end, thus transforming ``VAR,'' for
example, into ``$(VAR),'' which is just what we want.
Unfortunately (as you know if you've tried it), since,
as it says in the hint, Make does no further substitu-
tion on the result of a modified expansion, that's all
you get. The solution is to make use of ``:='' to place
that string into yet another variable, then invoke the
other variable directly:
*PTR := $(PTR:S/^/\$(/:S/$/)/)
You can then use ``$(*PTR)'' to your heart's content.
6. Glossary of Jargon
attribute: A property given to a target that causes Make to
treat it differently.
command script: The lines immediately following a dependency
line that specify commands to execute to create each of
the targets on the dependency line. Each line in the
command script must begin with a tab.
command-line variable: A variable defined in an argument
when Make is first executed. Overrides all assignments
to the same variable name in the makefile.
conditional: A construct much like that used in C that
allows a makefile to be configured on the fly based on
the local environment, or on what is being made by that
invocation of Make.
creation script: Commands used to create a target. See
``command script.''
dependency: The relationship between a source and a target.
This comes in three flavors, as indicated by the opera-
tor between the target and the source. `:' gives a
straight time-wise dependency (if the target is older
than the source, the target is out-of-date), while `!'
provides simply an ordering and always considers the
target out-of-date. `::' is much like `:', save it
creates multiple instances of a target each of which
depends on its own list of sources.
dynamic source: This refers to a source that has a local
variable invocation in it. It allows a single depen-
dency line to specify a different source for each tar-
get on the line.
global variable: Any variable defined in a makefile. Takes
precedence over variables defined in the environment,
but not over command-line or local variables.
input graph: What Make constructs from a makefile. Consists
of nodes made of the targets in the makefile, and the
links between them (the dependencies). The links are
directed (from source to target) and there may not be
any cycles (loops) in the graph.
April 27, 2013
Make -- A Tutorial PSD:12-45
local variable: A variable defined by Make visible only in a
target's shell script. There are seven local variables,
not all of which are defined for every target: .TARGET,
.ALLSRC, .OODATE, .PREFIX, .IMPSRC, .ARCHIVE, and
.MEMBER. .TARGET, .PREFIX, .ARCHIVE, and .MEMBER may be
used on dependency lines to create ``dynamic sources.''
makefile: A file that describes how a system is built. If
you don't know what it is after reading this
tutorial....
modifier: A letter, following a colon, used to alter how a
variable is expanded. It has no effect on the variable
itself.
operator: What separates a source from a target (on a depen-
dency line) and specifies the relationship between the
two. There are three: `:', `::', and `!'.
search path: A list of directories in which a file should be
sought. Make's view of the contents of directories in a
search path does not change once the makefile has been
read. A file is sought on a search path only if it is
exclusively a source.
shell: A program to which commands are passed in order to
create targets.
source: Anything to the right of an operator on a dependency
line. Targets on the dependency line are usually
created from the sources.
special target: A target that causes Make to do special
things when it's encountered.
suffix: The tail end of a file name. Usually begins with a
period, .c or .ms, e.g.
target: A word to the left of the operator on a dependency
line. More generally, any file that Make might create.
A file may be (and often is) both a target and a source
(what it is depends on how Make is looking at it at the
time -- sort of like the wave/particle duality of
light, you know).
transformation rule: A special construct in a makefile that
specifies how to create a file of one type from a file
of another, as indicated by their suffixes.
variable expansion: The process of substituting the value of
a variable for a reference to it. Expansion may be
altered by means of modifiers.
variable: A place in which to store text that may be
retrieved later. Also used to define the local environ-
ment. Conditionals exist that test whether a variable
is defined or not.
April 27, 2013
PSD:12-46 Make -- A Tutorial
Table of Contents
1. Introduction .................................... 1
2. The Basics of Make .............................. 2
2.1. Dependency Lines ................................ 2
2.2. Shell Commands .................................. 4
2.3. Variables ....................................... 6
2.3.1.Local Variables ................................. 8
2.3.2.Command-line Variables .......................... 9
2.3.3.Global Variables ................................ 9
2.3.4.Environment Variables ........................... 9
2.4. Comments ........................................ 10
2.5. Parallelism ..................................... 10
2.6. Writing and Debugging a Makefile ................ 11
2.7. Invoking Make ................................... 13
2.8. Summary ......................................... 16
2.9. Exercises ....................................... 16
3. Short-cuts and Other Nice Things ................ 16
3.1. Transformation Rules ............................ 17
3.2. Including Other Makefiles ....................... 21
3.3. Saving Commands ................................. 22
3.4. Target Attributes ............................... 23
3.5. Special Targets ................................. 27
3.6. Modifying Variable Expansion .................... 29
3.7. More on Debugging ............................... 31
3.8. More Exercises .................................. 31
4. Make for Gods ................................... 32
4.1. Search Paths .................................... 32
4.2. Archives and Libraries .......................... 33
4.3. On the Condition... ............................. 35
4.4. A Shell is a Shell is a Shell ................... 38
4.5. Compatibility ................................... 41
4.5.1.DEFCON 3 -- Variable Expansion .................. 41
4.5.2.DEFCON 2 -- The Number of the Beast ............. 41
4.5.3.DEFCON 1 -- Imitation is the Not the Highest
Form of Flattery ...................................... 42
4.6. The Way Things Work ............................. 42
5. Answers to Exercises ............................ 43
6. Glossary of Jargon .............................. 44
April 27, 2013
Generated on 2013-04-27 00:20:00 by $MirOS: src/scripts/roff2htm,v 1.77 2013/01/01 20:49:09 tg Exp $
These manual pages and other documentation are copyrighted by their respective writers;
their source is available at our CVSweb,
AnonCVS, and other mirrors. The rest is Copyright © 2002‒2013 The MirOS Project, Germany.
This product includes material
provided by Thorsten Glaser.
This manual page’s HTML representation is supposed to be valid XHTML/1.1; if not, please send a bug report – diffs preferred.