Using Make


Original Author: David Jones.


C Compiler Options

Before we can explore makefiles, we must learn what all of the switches on the ``cc'' or ``gcc'' command line do. The following switches are most commonly used:

-c Compile, but do not link. Normally, the C compiler will compile all files whose names appear on the command line, and then link them together to form a single executable. This option suppresses the link, with the intention that the link will be done at a later time.
-g Add debugging information. This option causes the compiler to add additional information about your program to the object files. This information allows a debugger such as GDB to correlate source program lines with addresses in the program and to examine your data structures.
-o Gives the name of the output file created by the compiler. This may be name of an executable, or the name of an object file destined for the link phase.

There are two C compilers on your system: ``cc'' and ``gcc''. CC is the SunOS C compiler, which supports the ``K&R'' C language definition. GCC is the GNU C compiler. It supports both K&R C as well as the ANSI standard C specification. ANSI C is easier to debug because its type checking facilities are much better. In addition, GCC produces other warnings that may signal problems with your program. For these reasons, I recommend that you use GCC and try using the prototyping features of the ANSI C standard.

Separate Compilation

Small C programs consisting of one source file are compiled and linked in one step:

gcc foo.c -o foo

As projects get more ambitious, more files appear on the command line:

gcc foo.c bar.c gnerpf.c -o myprog

Every time the above command is invoked, all three files get compiled. As the number of source files increases (large projects can have hundreds of source files), the time required to compile each one gets to be prohibitive.

The concept of linking gets around this. Linking is the process of combining object files compiled previously to obtain a final executable. The above project could be compiled as follows:

gcc -c foo.c -o foo.o
gcc -c bar.c -o bar.o
gcc -c gnerpf.c -o gnerpf.o
gcc foo.o bar.o gnerpf.o -o myprog

Note that the -c option was used to tell the compiler not to link the intermediate files (``object files'') that it produced. In the end, the compiler invokes the linker to combine the .o files to form the final program. It should be noted that linking is much faster than compiling.

Using Makefiles

The advantage of linking is that the work that needs to be done to bring a project up to date is minimized. In the above example, suppose I made a change to bar.c. I would then have to recompile bar.o and relink the program. For large projects, remembering what files need to be compiled can be a problem. Make is the solution to this problem.

``Make'' works by examining the last-modified times of the files in your project. If an object file is older than the corresponding source file, then it needs to be recompiled. In turn, if the final executable file is older than any of its constituent object files, then it needs to be relinked.

More formally, make manages a set of targets, each with one or more dependencies and a set of actions. If a target is older than any of its dependencies, then the corresponding actions are executed. The targets, dependencies and actions must be defined by the user in a file called ``Makefile''.

A simple Makefile for our example project is as follows:

myprog: foo.o bar.o gnerpf.o
gcc foo.o bar.o gnerpf.o -o myprog
foo.o: foo.c
gcc -c foo.c -o foo.o
bar.o: bar.c
gcc -c bar.c -o bar.o
gnerpf.o: gnerpf.c
gcc -c gnerpf.c -o gnerpf.o

Note the format: each target is followed by a colon and a list of its dependencies. The action(s) required to construct the target are then given. Each action line must have a tab at the beginning. You must use a true tab character; the equivalent number of spaces will not be accepted by some versions of the make program.

With the above Makefile, make will examine the dates on our files and do the minimum amount of work required to bring the project up to date. However, we can use advanced features of make to help maintain the Makefile itself.

Suffix Rules

Note that we have three files that must be compiled, and each of them is compiled in the same way. It is bothersome to have to type in the compile command line for each file, and it is even more bothersome to track down bugs that occur when one of the compile lines has a typo in it. Suffix rules help with this situation.

A sample suffix rule for our project could be:

.c.o:
gcc -c $*.c -o $*.o

The target looks a little strange: ``.c.o''. This target tells make that ``this is how you turn any .c file into a .o file''. Also note the ``$*'' characters. When the rule is used, this two-character sequence will be replaced with the name of the target, minus its suffix. Our Makefile now looks like:

myprog: foo.o bar.o gnerpf.o
gcc foo.o bar.o gnerpf.o -o myprog
.c.o:
gcc -c $*.c -o $*.o
foo.o: foo.c
bar.o: bar.c
gnerpf.o: gnerpf.c

Note that no action is given for the .c files. Make will look for a suffix rule of the form .c.o, and will use this rule to compile the program.

Make even goes one better: we don't need to mention the dependencies of the .o files on the .c files at all! If there is a rule relating .c files to .o files, then Make will automatically check for a newer .c file and invoke the appropriate actions. You may still want to use dependencies if a .o file depends on a bunch of .h's in addition to the .c file.

Macros

There is still one source of duplication in our Makefile: the list of object files. We typed this in twice, and we hope that the two lists are identical, otherwise something will not be made correctly. With three files this is not a problem. With 100 files, you need to use macros:

OBJS = foo.o bar.o gnerpf.o
myprog: $(OBJS)
gcc $(OBJS) -o myprog
.c.o:
gcc -c $*.c -o $*.o

In the above example, ``OBJS'' is the list of object files. The string ``$(OBJS)'' is replaced by the files given in its definition. The files passed to the linker are guaranteed to be the dependencies of the final program, since they come from the same macro. Note too that we have omitted the .o to .c dependencies since Make will look for these automatically.

Default Rules

Make comes with a set of default rules. They are found in the file ``/usr/include/make/default.mk''. Make automatically consults these rules as long as you do not override them by defining a suffix rule with the same pattern as that of one of the default rules. Since the default rules are customized for each computer system, they can handle idiosyncrasies of your local system without you having to care about them.

The full version of the .c.o default rule looks like a dog barfing in ASCII; here is a simplified version:

CC = cc
.c.o:
$(CC) $(CFLAGS) -c $<

This rule assumes the Sun C compiler. By including ``CC = gcc'' in your Makefile, you can override this setting. The CFLAGS definition given in the course notes is also automatically included .

If you use the default rule, then the project Makefile looks like this:

OBJS = foo.o bar.o gnerpf.o
CC = gcc
myprog: $(OBJS)
gcc $(OBJS) -o myprog

It can't get much simpler than that.

Hints & Tips

You can include comments in a Makefile. Comments must begin with the ``#'' character:

# This is a comment
CC = gcc
...etc

A long line can be wrapped onto the next line by ending all but the last line with the ``\'' character. It helps to indent the successor lines for clarity:

OBJS = libhdr.o libinf.o xferqglue.o queue.o \
session.o memobj.o address.o file.o xferqlib.o

You may want to list the .h files that each .c file includes. That way, if you change an include file, then all files that include it will be recompiled:

queue.o: queue.c xferq.h xferqint.h xferq_pragmas.h
...etc


Last Modified: January 19, 1997