UNIX/Linux & C Programming:
Compilation Management (Makefiles)
Coverage: [UPE] Chapter 8, §8.1 (pp. 241-242) and §8.3 (pp. 254-258) and
[USP] Appendix A, §A.3 (pp. 807-809)
Overview of make
- change file access and modification times
- if file exists, touch brings its timestamp up-to-date
- if file does not exist, touch creates an empty file
$ touch foo.c
make is the standard compilation management tool for UNIX systems;
it is a program designed to simplify the maintenance of other
programs. Its input is a list of specifications as to the files upon which
programs and other files depend. This list is usually stored in a file named
Makefile or makefile.
When files (say, one or more source files used to build a given program) are
modified, you can use make to bring the executable version of the
program up to date. make uses the dependencies specified in the
Makefile to ensure that all derived files are brought up to date
after source files change, while doing the minimum possible amount of work.
make only recompiles when it is necessary.
Naming your Makefile Makefile typically ensures
that it will be listed first in an ls listing. However,
a makefile can have any filename when you use the -f
option to make (e.g., $ make -f mymakespecs)
Comment lines start with a # character (anywhere except in a
command line), and continue until the end of the line.
A directive describes the relationship between a target
(the file to be built), its dependencies or sources (the
files used to create the target), and zero or more command
lines which define the build process for the target:
target: source1 source2 ...
Long lists can be continued over multiple lines using \ as a line
continuation character. Command lines must begin with a <TAB> character.
By placing a @ character at the beginning of a command, you prevent
it from being echoed to stdout by make as it is executed. By placing
a - character at the beginning of a command, you can instruct make to
ignore any non-zero exit status for that command.
What will make do?
To see what make will do without having it actually invoke any
commands, use the -n command-line option. Then make will
echo the commands it would have performed on stdout, but it will not actually
invoke any of the commands. Use the -d to print debugging information
in addition to normal processing. Using the -d and -n options
together is helpful for debugging a Makefile.
Create the following 3 files using touch:
button.c window.c window.h
This is our source code. These are zero length files; we are
simply using them for instructional purposes.
Assuming the following are the commands to build (make) the executable popup:
gcc -c button.c → button.o
gcc -c window.c → window.o
gcc -o popup button.o window.o → popup
Write a Makefile to build this program.
Since these are files are not really source code, we must use make -n.
popup: button.o window.o
gcc -o popup button.o window.o
gcc -c button.c
window.o: window.c window.h
gcc -c window.c
[USP] §2.13 (pp. 55-56) dependency tree
[USP] §2.13 (pp. 55-56) Makefile
a.out: loggerlib.o logapp.o logapplib.o
$(CC) loggerlib.o logapplib.o logapp.o
$(CC) $(OPTS) logapp.c
logapplib.o: loggerlib.h logapplib.c
$(CC) $(OPTS) logapplib.c
loggerlib.o: loggerlib.h loggerlib.c
$(CC) $(OPTS) loggerlib.c
@-rm *.o a.out
You can define variables in a Makefile and use them:
CC = gcc
LIST_OF_FILES = file1.c file2.c \
$(CC) $(LIST_OF_FILES) -o program1
You can refer to environment variables defined in your shell within a
Makefile just as if they were declared right in the Makefile.
Further, environment variable definitions override any global
definitions appearing in the Makefile when make is invoked
with the -e option.
$ export LIST_OF_FILES="file1.c file2.c file3.c file4.c file5.c"
$ make -e program1
Variables on the command line
You can also define/redefine variables on the make command line:
$ make LIST_OF_FILES="file1.c file2.c file3.c file4.c file5.c" program1
These definitions override whatever appears in the Makefile itself.
It is possible to define default rules for constructing
certain kinds of files from their dependencies. Such rules
will be used if no command lines are given for a particular
target. For example, to say how a C program should be
compiled into an object file:
$(CC) $(CFLAGS) $< -o $@
Here, the $< and $@ are special
variables which refer to the source file for the current line, and the
current target file, respectively. The default suffix rules for updating
an object library are:
$(CC) -c $(CFLAGS) $<
ar rv $@ $*.o
rm -f $*.o
Here $* refers to the filename part (without suffix) of
the prerequisite. An example of a library reference in a Makefile is:
prog: lib(sub1) lib(sub2) lib((module1)) prog.o
$(CC) -o $@ prog.o lib
A more general approach
CC = gcc
C_FLAGS = -c
OBJECTS = $(addsuffix .o,$(SRC)) # list of all .o files
PGM = keeplog
SRC1 = $(wildcard *.c) # list of all .c files
SRC = $(patsubst %.c,%,$(SRC1)) # list of all .c files with .c stripped off
$(OBJECTS): %.o: %.c
$(CC) $(C_FLAGS) $< -o $@
$(CC) -MM $< > $*.d
-include $(addsuffix .d,$(SRC))
@-rm *.o *.d $(PGM)
make actually reads its default rules and default macro
definitions from system files stored in /usr/share/mk. The
file sys.mk in this directory is normally read by make.
It defines rules for C, C++, FORTRAN, Pascal, assembly, lex, yacc
files. It also defines basic macros/variables for the
compilers used for such files.
By default, the FreeBSD version of make will read the file
.depend in the current directory in addition to
[Mm]akefile. It is normally invoked as:
mkdep [cc-options] file1.c file2.c ...
It automatically produces directives (with no command lines) for all listed
files, using the C preprocessor to determine the exact dependency list.
Correct use of default rules and variables can then be used to direct the
compilation of the corresponding C files.
The GNU version of mkdep is gccmkdep.
Packaging and compression
- ar (maintain file library)
$ ar t /usr/lib/libc.a | grep '^printf.o'
$ ar qv project.ar *.c
$ ar t project.ar
$ ar rvb foob.c project.ar fooa.c
$ ar xv project.ar fooa.c foob.c
- tar (tape archiver; create and manipulate tar archive files)
$ tar cvf p1.tar myshell.c helper.c other.c Makefile # creates the p1.tar archive
$ tar tf p1.tar # lists the contents of p1.tar
$ tar xvf p1.tar # extracts the p1.tar archive
$ tar cvpf p1.tar myshell.c helper.c other.c Makefile # preserves file permissions
$ tar cvzf p1.tgz myshell.c helper.c other.c Makefile # compresses the data
$ tar cvpzf p1.tgz myshell.c helper.c other.c Makefile # preserves and compresses
$ tar cvpzf p1.tgz p1 # creates archive rooted at directory p1
$ tar xvzf p1.tgz p1 # extracts the compressed archive p1.tgz
- gzip, gunzip, zcat
(compress or expand files)
$ gzip foo.tar
$ gunzip foo.tar.gz
$ zcat foo.tar.gz | tar tvf -
- compress, uncompress (compress and expand data)
$ compress foo.tar
$ uncompress foo.tar.Z
- zip, unzip
(package and extract compressed archive files)
||B.W. Kernighan and R. Pike. The UNIX Programming Environment.
Prentice Hall, Upper Saddle River, NJ, Second edition, 1984.