PROBLEM SOLVER
Making it
by Bob Toxen
Compiling a simple C program under UNIX is a fairly straight-forward matter.
It can be as easy as entering a command like:
% cc -O -o simp simp.c
Some programs need some libraries and must be compiled like so:
% cc -O -o foo foo.c -lcurses -ltermcap -lgl -lm
Other programs may have separate source files and need to know what version of
UNIX they will be used on (for conditional compilation). Such a program may
need to have the binary stripped out of the symbol table. To compile the
program, commands like the following would be necessary:
% cc -O -DSYS5 -c egads1.c egads2.c
% cc -s -o egads egads1.o egads2.o -ltermcap -lPW -lm
Now suppose you have to worry about compiling these programs, as well as
300 others, which include large libraries, kernel applications, formatting
documents, and the like. Typing all those cc commands would
clearly be a large task. Remembering how to compile each program would be
nearly impossible considering all the compile and load flags, libraries, and
other matters. A shell script can be used to make this task easier but this
doesn't solve the problem of determining which modules have to be recompiled
if you change source modules or include files.
Fortunately, a program named make has been developed to
handle all these tasks simply and elegantly. To use make,
one edits a file called makefile or Makefile in the current
directory. The makefile delineates the sources that binaries depend on and
tells how to compile the sources into a specific binary.
ON THE MAKE
Two different types of lines can be found in makefiles. The first (which
describes who depends on whom) is called a dependency line. It
consists of the name of the file to be made followed by a colon (:),
optionally followed by spaces (or tabs), and followed by a blank-separated
list of files the makefile depends on. In our first example, the dependency
line would be:
simp: simp.c
The second type of line (which describes how to do the actual compilation) is
called an action line and consists of an initial tab followed by a
legal shell command. There may be several action lines following each
dependency line. On standard System III & V, the $SHELL
environment variable determines which shell one gets, with the default being
sh. On other systems, sh is always used.
Some advanced makefiles use if, for, and
other shell statements. In our example, the format would be:
cc -O -o simp simp.c
The make command provides certain macros to make things
easier. A $@ variable can be used as a placeholder for the
thing to be made and a $? can hold the place of the source
files that need re-compilation. Thus our action line could read:
cc -O -o $@ $?
and the makefile would look like:
simp: simp.c
cc -O -o $@ $?
To use it, simply enter:
% make
The same makefile can be used to describe how to make many different things.
Figure 1 contains a makefile capable of compiling three different programs.
Note that egads depends on both egads1.o and
egads2.o and that these in turn depend on egads1.c and
egads2.c, respectively. The make program is quite
smart and will figure out that when egads depends on
egads1.o and egads1.o depends on egads1.c,
it must first make egads1.o from egads1.c and them make
egads from egads1.o.
If egads1.o already exists, make will determine
when egads1.c was last changed (edited) and compare this time to the time
that egads1.o was last changed (compiled). If egads1.c
is newer, make will assume you just edited it and that it
needs to be re-compiled.
On the other hand, if egads1.o is newer, make will
assume that egads1.c was compiled into egads1.o
more recently than it was edited and that it therefore does not need
to be re-compiled.
Thus if you have edited
egads1.c
since the last invocation of make but have not edited
egads2.c
, make will include the following:
cc -O -DSYS5 -c egads1.c
cc -s -o egads egads1.o egads2.o -ltermcap -lPW -lm
but will not do:
cc -O -DSYS5 -c egads2.c
To make simp and egads (but not foo), issue the
command:
% make simp egads
To make all three, issue the command:
% make simp foo egads
all: simp foo egads
simp: simp.c
cc -O -o $@ $?
foo: foo.c
cc -O -o foo foo.c -lcurses -ltermcap -lgl -lm
egads1.o:egads1.c
cc -O -DSYS5 -c egads1.c
egads2.o:egads2.c
cc -O -DSYS5 -c egads2.c
egads: egads1.o egads2.o
cc -s -o egads egads1.o egads2.o \
-lcurses -ltermcap -lPW -lm
Figure 1 -- A sample makefile
As you can see, we are again faced with having to remember what we have to
type in order to compile everything in a directory. All the same, it is still
less to type than before. This can be cut even further by having a name such
as all (which is commonly used for this purpose) depend on everything
you usually want to make in that directory. The example in Figure 1 shows how
this can be done. Thus, if we issue the command:
% make all
or even:
% make
all three programs will be re-compiled since all is the first
target (or thing to be made) in the file. It does not matter that
there is not really a program called all or that there is not an
action line associated with it. We could, in fact, have an action line such
as:
@echo All Done
The make command would otherwise display each command line on
the terminal just before executing it. The @ sign inhibits
this.
MAKING THE MOST OF MAKE
We can use other pseudo targets similar to all to do other
operations. One that's commonly used is called install, as in
install the compiled binary where it will be used with the correct
permissions. Assuming our sample programs are to be usable by everyone, we
might want to install them in the /usr/bin directory with the owner
and group defined as bin and the mode set to 755. To do this, we
could add the lines displayed in Figure 2 to our makefile. Note that
install depends on all so that if we do:
% make install
the all target will first be "made", ensuring that the compiled
programs are up to date.
install:all
cp simp foo egads /usr/bin
cd /usr/bin ; chmod 755 simp foo egads
cd /usr/bin ; chgrp bin simp foo egads ; \
chown bin simp foo egads
Figure 2 -- Making programs available systemwide.
Two other commonly included targets are clean and clobber
used as follows:
% make clean
This is commonly used to remove temporary files such as *.o. The
command:
% make clobber
is used to remove temporary files and binaries. Thus it removes everything
except the source files (and the makefile). These entries for our sample
makefile would look like:
clean:
rm -f *.o
clobber:clean
rm -f simp foo egads
There are many other uses for makefiles. One common use is for document
preparation. Suppose we are working on a document that is made up of several
chapters. The text of each chapter is in a separate file whose name is
docx.mm, where x is the chapter number. Let's say we also
use tables and equations in the document. The makefile might look like the
one shown in Figure 3. One can use the command:
% make print
to print the entire document.
SHELL = /bin/sh
print:
tbl doc1.mm doc2.mm doc3.mm | neqn | nroff -mm
spell:
for x in doc1.mm doc2.mm doc3.mm ; \
do spell $$x | pr -h $$x | lpr ; done
doc1 doc2 doc3:
tbl $@.mm | neqn | nroff -mm
Figure 3 -- A sample makefile for document production.
One can also enter:
% make spell
to run a spelling check on each text file separately. The
pr -h $$x action specified within spell will print
the name of the file at the top of each page of spelling errors. The file
will then be sent to the printer via lpr. Since
$ is used to reference make macros,
$$ is used to send a $ to the shell. We use
a shell for statement to iterate through each document file.
The backslash indicates a continuation line. That is, the line it occurs on
and the following line will be joined together before being given to the
shell.
The $@, as discussed previously, gets expanded to the thing
we want to make. Since we have listed several items to the left of the
colon, the action line will be used to make any of them. Thus we could issue
the command:
% make doc2
to format and print just doc2.mm. Lastly, the line:
SHELL = /bin/sh
is used on standard System III & V to have make use
sh to process the action lines even when csh
is being used for an interactive shell. This is important for complex
makefiles where shell syntaxes differ. Also, sh's usually
faster speed is more important than csh's greater power when
making files.
SUMMARY
This should not be taken as anything more than an introduction to
make and makefiles. There are many more things that can be
done and many advanced features that can be utilized to make life much easier.
These features must be learned before intermediate or advanced makefiles are
attempted.
Bob Toxen is a member of the technical staff at Silicon Graphics, Inc.
He has gained a reputation as a leading expert on uucp
communications, file system repair, and UNIX utilities.
He has also done ports of System III and System V to systems based on
the Zilog 8000 and Motorola 68010.
Copyright © 1985, 2007, 2014, 2020 Robert M. Toxen. All rights reserved.
Back
|