Sometime during your life with Linux you will probably have to deal with make, even if you don’t plan to do any programming. It’s possible you’ll want to patch and rebuild the kernel, and that involves running make. If you’re lucky, you won’t have to muck with the makefiles — but we’ve tried to direct this book toward unlucky people as well. So in this section, we’ll explain enough of the subtle syntax of make so that you’re not intimidated by a makefile.
For some of our examples, we’ll draw on the current makefile for the Linux kernel. It exploits a lot of extensions in the powerful GNU version of make, so we’ll describe some of those as well as the standard make features. A good introduction to make is provided in Managing Projects with make by Andrew Oram and Steve Talbott (O’Reilly). GNU extensions are well documented by the GNU make manual.
Most users see make as a way to build object files and libraries from sources and to build executables from object files. More conceptually, make is a general-purpose program that builds targets from dependencies. The target can be a program executable, a PostScript document, or whatever. The prerequisites can be C code, a TeX text file, and so on.
While you can write simple shell scripts to execute gcc commands that build an executable program, make is special in that it knows which targets need to be rebuilt and which don’t. An object file needs to be recompiled only if its corresponding source has changed.
For example, say you have a program that consists of three C source files. If you were to build the executable using the command:
papaya$ gcc -o foo foo.c bar.c baz.c
each time you changed any of the source files, all three would be recompiled and relinked into the executable. If you changed only one source file, this is a real waste of time (especially if the program in question is much larger than a handful of sources). What you really want to do is recompile only the one source file that changed into an object file and relink all the object files in the program to form the executable. make can automate this process for you.
The basic goal of make is to let you build a file in small steps. If a lot of source files make up the final executable, you can change one and rebuild the executable without having to recompile everything. In order to give you this flexibility, make records what files you need to do your build.
Here’s a trivial makefile. Call it
makefile
or Makefile
and
keep it in the same directory as the source files:
edimh: main.o edit.o gcc -o edimh main.o edit.o main.o: main.c gcc -c main.c edit.o: edit.c gcc -c edit.c
This file builds a program named edimh
from two
source files named main.c
and
edit.c
. You aren’t restricted
to C programming in a makefile; the commands could be anything.
Three
entries appear in the file. Each contains a dependency line that shows how a file is built. Thus the first line
says that edimh
(the name before the colon) is
built from the two object files main.o
and
edit.o
(the names after the colon). This line
tells make that it should execute the following
gcc line whenever one of those object files
changes. The lines containing commands have to begin with tabs (not
spaces).
The command:
papaya$ make edimh
executes the gcc line if there
isn’t currently any file named
edimh
. However, the gcc
line also executes if edimh
exists, but one of
the object files is newer. Here, edimh
is called
a target. The files after the colon are called
either dependencies or
prerequisites.
The next two entries perform the same service for the object files.
main.o
is built if it doesn’t
exist or if the associated source file main.c
is
newer. edit.o
is built from
edit.c
.
How does make know if a file is new? It looks at the timestamp, which the filesystem associates with every file. You can see timestamps by issuing the ls -l command. Since the timestamp is accurate to one second, it reliably tells make whether you’ve edited a source file since the latest compilation or have compiled an object file since the executable was last built.
Let’s try out the makefile and see what it does:
papaya$ make edimh
gcc -c main.c
gcc -c edit.c
gcc -o edimh main.o edit.o
If we edit main.c
and reissue the command, it
rebuilds only the necessary files, saving us some time:
papaya$ make edimh
gcc -c main.c
gcc -o edimh main.o edit.o
It doesn’t matter what order the three entries are
within the makefile. make figures out which
files depend on which and executes all the commands in the right
order. Putting the entry for edimh
first is
convenient because that becomes the file built by default. In other
words, typing make
is the same as typing
make edimh
.
Here’s a more extensive makefile. See if you can figure out what it does:
install: all mv edimh /usr/local mv readimh /usr/local all: edimh readimh readimh: read.o main.o gcc -o readimh main.o read.o edimh: main.o edit.o gcc -o edimh main.o edit.o main.o: main.c gcc -c main.c edit.o: edit.c gcc -c edit.c read.o: read.c gcc -c read.c
First
we see the target install
. This is never going to
generate a file; it’s called a phony target because it exists just so that you can execute the
commands listed under it. But before install
runs,
all
has to run because install
depends on all
. (Remember, the order of the
entries in the file doesn’t matter.)
So make turns to the all
target. There are no commands under it (this is perfectly legal), but
it depends on edimh
and
readimh
. These are real files; each is an
executable program. So make keeps tracing back
through the list of dependencies until it arrives at the
.c
files, which don’t depend on
anything else. Then it painstakingly rebuilds each target.
Here is a sample run (you may need root privilege to install the
files in the /usr/local
directory):
papaya$ make install
gcc -c main.c
gcc -c edit.c
gcc -o edimh main.o edit.o
gcc -c read.c
gcc -o readimh main.o read.o
mv edimh /usr/local
mv readimh /usr/local
This run of make does a complete build and
install. First it builds the files needed to create
edimh
. Then it builds the additional object file
it needs to create readmh
. With those two
executables created, the all
target is satisfied.
Now make can go on to build the
install
target, which means moving the two
executables to their final home.
Many makefiles, including the ones that build Linux, contain a variety of phony targets to do routine activities. For instance, the makefile for the Linux kernel includes commands to remove temporary files:
clean: archclean rm -f kernel/ksyms.lst rm -f core `find . -name '*.[oas]' -print` . . .
and to create a list of object files and the header files they depend on (this is a complicated but important task; if a header file changes, you want to make sure the files that refer to it are recompiled):
depend dep: touch tools/version.h for i in init/*.c;do echo -n "init/";$(CPP) -M $$i;done > .tmpdep . . .
Some of these shell commands get pretty complicated; we’ll look at makefile commands later in this chapter, in Section 13.2.5.
The hardest thing about maintaining makefiles, at least if you’re new to them, is getting the syntax right. OK, let’s be straight about it, make syntax is really stupid. If you use spaces where you’re supposed to use tabs or vice versa, your makefile blows up. And the error messages are really confusing.
Always put a tab — not spaces — at the beginning of a command. And don’t use a tab before any other line.
You can place a hash sign (#) anywhere on a line to start a comment. Everything after the hash sign is ignored.
If you put a backslash at the end of a line, it continues on the next line. That works for long commands and other types of makefile lines, too.
Now let’s look at some of the powerful features of make, which form a kind of programming language of their own.
When people use a filename or other string more than once in a makefile, they tend to assign it to a macro. That’s simply a string that make expands to another string. For instance, you could change the beginning of our trivial makefile to read:
OBJECTS = main.o edit.o edimh: $(OBJECTS) gcc -o edimh $(OBJECTS)
When make runs, it simply plugs in
main.o edit.o
wherever you specify
$(OBJECTS)
. If you have to add another object file
to the project, just specify it on the first line of the file. The
dependency line and command will then be updated correspondingly.
Don’t forget the parentheses when you refer to
$(OBJECTS)
. Macros may resemble shell variables
like $HOME
and $PATH
, but
they’re not the same.
One macro can be defined in terms of another macro, so you could say something like:
ROOT = /usr/local HEADERS = $(ROOT)/include SOURCES = $(ROOT)/src
In this case, HEADERS
evaluates to the directory
/usr/local/include
and
SOURCES
to /usr/local/src
. If
you are installing this package on your system and
don’t want it to be in
/usr/local
, just choose another name and change
the line that defines ROOT
.
By the way, you don’t have to use uppercase names for macros, but that’s a universal convention.
An extension in GNU
make
allows you to add to the definition of a macro. This uses a
:=
string in place of an equals sign:
DRIVERS = drivers/block/block.a ifdef CONFIG_SCSI DRIVERS := $(DRIVERS) drivers/scsi/scsi.a endif
The first line is a normal macro definition, setting the
DRIVERS
macro to the filename
drivers/block/block.a
. The next definition adds
the filename drivers/scsi/scsi.a
. But it takes
effect only if the macro CONFIG_SCSI
is defined.
The full definition in that case becomes:
drivers/block/block.a drivers/scsi/scsi.a
So how do you define CONFIG_SCSI
? You could put it
in the makefile, assigning any string you want:
CONFIG_SCSI = yes
But you’ll probably find it easier to define it on the make command line. Here’s how to do it:
papaya$make CONFIG_SCSI=yes
target_name
One subtlety of using macros is that you can leave them undefined. If
no one defines them, a null string is substituted (that is, you end
up with nothing where the macro is supposed to be). But this also
gives you the option of defining the macro as an environment
variable. For instance, if you don’t define
CONFIG_SCSI
in the makefile, you could put this in
your .bashrc
file, for use with the
bash shell:
export CONFIG_SCSI=yes
Or put this in .cshrc
if you use
csh or tcsh:
setenv CONFIG_SCSI yes
All your builds will then have CONFIG_SCSI
defined.
For
something as routine as building an object file from a source file,
you don’t want to specify every single dependency in
your makefile. And you don’t have to. Unix compilers
enforce a simple standard (compile a file ending in the suffix
.c
to create a file ending in the suffix
.o
), and make provides a
feature called suffix rules to cover all such files.
Here’s a simple suffix rule to compile a C source file, which you could put in your makefile:
.c.o: gcc -c $(CFLAGS) $<
The .c.o:
line means “use a
.c
dependency to build a .o
file.” CFLAGS
is a macro into
which you can plug any compiler options you want:
-g for debugging, for instance, or
-O for optimization. The string
$<
is a cryptic way of saying
“the dependency.” So the name of
your .c
file is plugged in when
make executes this command.
Here’s a sample run using this suffix rule. The
command line passes both the -g
option and
the -O option:
papaya$ make CFLAGS="-O -g" edit.o
gcc -c -O -g edit.c
You actually don’t have to specify this suffix rule
in your makefile because something very similar is already built into
make. It even uses CFLAGS
, so
you can determine the options used for compiling just by setting that
variable. The makefile used to build the Linux kernel currently
contains the following definition, a whole slew of
gcc options:
CFLAGS = -Wall -Wstrict-prototypes -O2 -fomit-frame-pointer -pipe
While we’re discussing compiler flags, one set is
seen so often that it’s worth a special mention.
This is the -D option, which is used to define
symbols in the source code. Since all kinds of commonly used symbols
appear in #ifdefs
, you may need to pass lots of
such options to your makefile, such as -DDEBUG
or -DBSD. If you do this on the
make command line, be sure to put quotation
marks or apostrophes around the whole set. This is because you want
the shell to pass the set to your makefile as one argument:
papaya$ make CFLAGS="-DDEBUG -DBSD" ...
GNU make offers something called pattern rules, which are even better than suffix rules. A pattern rule uses a percent sign to mean “any string.” So C source files would be compiled using a rule, as in the following:
%.o: %.c gcc -c -o $@ $(CFLAGS) $<
Here the output file %.o
comes first, and the
dependency %.c
comes after a colon. In short, a
pattern rule is just like a regular dependency line, but it contains
percent signs instead of exact filenames.
We see the $<
string to refer to the
dependency, but we also see $@
, which refers to
the output file. So the name of the .o
file is
plugged in there. Both of these are built-in macros;
make defines them every time it executes an
entry.
Another common built-in macro is $*
, which refers
to the name of the dependency stripped of the suffix. So if the
dependency is edit.c
, the string
$*.s
would evaluate to edit.s
(an assembly-language source file).
Here’s something useful you can do with a pattern
rule that you can’t do with a suffix rule: you add
the string _dbg
to the name of the output file so
that later you can tell that you compiled it with debugging
information:
%_dbg.o: %.c gcc -c -g -o $@ $(CFLAGS) $< DEBUG_OBJECTS = main_dbg.o edit_dbg.o edimh_dbg: $(DEBUG_OBJECTS) gcc -o $@ $(DEBUG_OBJECTS)
Now you can build all your objects in two different ways: one with debugging information and one without. They’ll have different filenames, so you can keep them in one directory:
papaya$ make edimh_dbg
gcc -c -g -o main_dbg.o main.c
gcc -c -g -o edit_dbg.o edit.c
gcc -o edimh_dbg main_dbg.o edit_dbg.o
Any shell commands can be executed in a makefile. But things can get kind of complicated because make executes each command in a separate shell. So this would not work:
target: cd obj HOST_DIR=/home/e mv *.o $HOST_DIR
Neither the cd command nor the definition of the
variable HOST_DIR
has any effect on subsequent
commands. You have to string everything together into one command.
The shell uses a semicolon as a separator between commands, so you
can combine them all on one line, as in:
target: cd obj ; HOST_DIR=/home/e ; mv *.o $$HOST_DIR
One more change: to define and use a shell variable within the command, you have to double the dollar sign. This lets make know that you mean it to be a shell variable, not a macro.
You may find the file easier to read if you break the semicolon-separated commands onto multiple lines, using backslashes so that make considers them to be on one line:
target: cd obj ; HOST_DIR=/home/e ; mv *.o $$HOST_DIR
Sometimes makefiles contain their own make commands; this is called recursive make. It looks like this:
linuxsubdirs: dummy set -e; for i in $(SUBDIRS); do $(MAKE) -C $$i; done
The macro $(MAKE)
invokes
make. There are a few reasons for nesting makes.
One reason, which applies to this example, is to perform builds in
multiple directories (each of these other directories has to contain
its own makefile). Another reason is to define macros on the command
line, so you can do builds with a variety of macro definitions.
GNU make offers another powerful interface to the shell as an extension. You can issue a shell command and assign its output to a macro. A couple of examples can be found in the Linux kernel makefile, but we’ll just show a simple example here:
HOST_NAME = $(shell uname -n)
This assigns the name of your network node — the output of the
uname -n command — to the macro
HOST_NAME
.
make offers a couple of conventions you may occasionally want to use. One is to put an at sign before a command, which keeps make from echoing the command when it’s executed:
@if [ -x /bin/dnsdomainname ]; then echo #define LINUX_COMPILE_DOMAIN "`dnsdomainname`"; else echo #define LINUX_COMPILE_DOMAIN "`domainname`"; fi >> tools/version.h
Another convention is to put a hyphen before
a command, which tells make to keep going even
if the command fails. This may be useful if you want to continue
after an mv
or cp
command
fails:
- mv edimh /usr/local - mv readimh /usr/local
Large projects tend to break parts of their makefiles into separate files. This makes it easy for different makefiles in different directories to share things, particularly macro definitions. The line:
include filename
reads in the contents of filename
. You can
see this in the Linux kernel makefile, for instance:
include .depend
If you look in the file .depend
,
you’ll find a bunch of makefile entries: these lines
declare that object files depend on particular header files. (By the
way, .depend
might not exist yet; it has to be
created by another entry in the makefile.)
Sometimes include
lines refer to macros instead of
filenames, as in:
include ${INC_FILE}
In this case, INC_FILE must be defined either as an environment variable or as a macro. Doing things this way gives you more control over which file is used.
The error messages from make can be quite cryptic, so we’d like to give you some help in interpreting them. The following explanations cover the most common messages.
This usually means that there is no makefile in the directory you are
trying to compile. By default, make tries to
find the file GNUmakefile
first; then, if this
has failed, Makefile
, and finally
makefile
. If none of these exists, you will get
this error message. If for some reason you want to use a makefile
with a different name (or in another directory), you can specify the
makefile to use with the -f command-line option.
This means that make cannot find a dependency it
needs (in this case blah.c
) in order to build a
target (in this case blah.o
). As mentioned,
make first looks for a dependency among the
targets in the makefile, and if there is no suitable target, for a
file with the name of the dependency. If this does not exist either,
you will get this error message. This typically means that your
sources are incomplete or that there is a typo in the makefile.
The current versions of make are friendly enough to ask you whether you have made a very common mistake: not prepending a command with a TAB. If you use older versions of make, missing separator is all you get. In this case, check whether you really have a TAB in front of all commands, and not before anything else.
Writing makefiles for a larger project usually is a boring and time-consuming task, especially if the programs are expected to be compiled on multiple platforms. From the GNU project come two tools called Autoconf and Automake that have a steep learning curve but, once mastered, greatly simplify the task of creating portable makefiles. In addition, libtool helps a lot to create shared libraries in a portable manner. You can probably find these tools on your distribution CD, or you can download them from ftp://ftp.gnu.org/gnu/.
From a user’s point of view, using
Autoconf involves running a program
configure
, which should have been shipped in the
source package you are trying to build. This program analyzes your
system and configures the makefiles of the package to be suitable for
your system and setup. A good thing to try before running the
configure
script for real is to issue the
command:
owl$ ./configure --help
This shows all command-line switches that the
configure
program understands. Many packages
allow different setups — e.g., different modules to be compiled
in — and you can select these with configure
options.
From a programmer’s point of view, you
don’t write makefiles, but rather files called
makefile.in
. These can contain place holders
that will be replaced with actual values when the user runs the
configure
program, generating the makefiles that
make then runs. In addition, you need to write a
file called configure.in
that describes your
project and what to check for on the target system. The
Autoconf tool then generates the
configure
program from this
configure.in
file. Writing the
configure.in
file is unfortunately way too
involved to be described here, but the Autoconf
package contains documentation to get you started.
Writing the makefile.in
files is still a
cumbersome and lengthy task, but even this can be mostly automated by
using the Automake package. Using this package,
you do not write the makefile.in
files, but
rather the makefile.am
files, which have a much
simpler syntax and are much less verbose. By running the
automake tool, these
makefile.am
files are converted to the
makefile.in
, which you include when you
distribute your source code and which are later converted into the
makefiles
themselves when the package is
configured for the user’s system. How to write
makefile.am
files is beyond the scope of this
book as well. Again, please check the documentation of the package to
get started.
These days, most open-source packages use the libtool/automake/autoconf combo for generating the makefiles, but this does not mean that this rather complicated and involved method is the only one available. Other makefile-generating tools exist as well, such as the imake tool used to configure the X Window System. Another tool that is not as powerful as the Autoconf suite (even though it still lets you do most things you would want to do when it comes to makefile generation) but extremely easy to use (it can even generate its own description files for you from scratch) is the qmake tool that ships together with the C++ GUI library Qt (downloadable from http://www.trolltech.com).