Handout 26

Writing A Makefile


The Make Utility provides a resource which enables a programmer to write once how a series of files is combined into an executable program. Once properly set up, Make will verify which files are not up to date and will compile/link as necessary to update the executable. This is VERY VALUABLE when you are debugging (using the same sequence of commands over and over again).

Make is invoked by typing "make program_name" at the prompt. The utility looks for a file named "makefile" or "Makefile" assumed, for our purposes, to be in the current directory.

Let's use an example to illustrate how to write the make file. Assume we have a program we want to call "myprog". The following files are used: mymain.cxx, func.cxx and func.h (#included in mymain.cxx). In
addition, we need the standard math library (math.h).

We might compile and link this program using the following steps:

cxx -c mymain.cxx
cxx -c func.cxx
cxx -o myprog mymain.o func.o -lm

Remember that the "-c" option means "compile but do not link" and leaves a *.o file in the current directory. The "-o" option says to save the executable as "myprog" instead of "a.out".

We invoke Make by entering "make myprog". In this case, "myprog" is the "target" of Make. The various files are "prerequisites" or "dependents".

Each line in "Makefile" or "makefile" consists of a "dependency line" and a "command line". The dependency line consists of a "target" followed by a colon (a separator) and then the dependencies for that target. The command line represents the command you would type to make sure the target is up to date.

The "Makefile" or "makefile" might look like this:

#
# make for myprog
#
myprog: mymain.o func.o
cxx -o myprog mymain.o func.o -lm

mymain.o: mymain.cxx
cxx -c mymain.cxx

mymain.o: func.h #show dependency on header file also

func.o: func.cxx
cxx -c mymain.cxx
#
# end of makefile
#

The symbol "#" acts just like "//" in C++. From "#" to the end of the line is ignored, i.e., treated as comments.

The first set of lines says that there is a target "myprog" which depends on "mymain.o" and "func.o". If "myprog" does not exist, then the command line "cxx -o ...." will be executed. If "myprog" does exist, then the dates/times of compilation of mymain.o and func.o will be checked to ensure they are earlier than "myprog". If one or more date is more recent, then one or both of mymain.o and func.o must be updated.

Make now uses first mymain.o and then func.o as a target. the next 3 lines show that mymain.o is a target for two sets of dependencies. This is perfectly legal, but only one of the dependencies may have a command line. Make figures out what to do. Here we show that mymain.o depends not only on mymain.cxx but also on func.h. If either mymain.cxx or func.h has a date later than mymain.o, the command "cxx -c mymain.cxx" will be executed and then the line for "myprog" will be executed.

The same sequence is followed for "func.o". Note that, for standard libraries, such as math.h, we do not need to include a dependency. We assume that they are up to date. In more complicated make files, we
would not make that assumption.

That's all there is to the logic of an elementary makefile. There are, however, some VERY STRICT syntax rules which are not obvious above.
  1. Make recognizes a "command line" by a leading tab. There can be no other character or space prior to the tab. DO NOT USE tabs in the makefile except at start of command lines.
  2. You can continue a long line by placing a backslash "\" at the end. There can be no other character between the "\" and the carriage return!!
  3. Make ignores blank lines.
  4. Except for the two rules about tabs and "\" above, you may use whitespace freely in the makefile.


Make is VERY POWERFUL. See the source document for the full features.

NOTE: Make can be used to avoid retyping a single long command,too. For example, if you are debugging and get tired of typing

cxx -o myprog main.cxx func.cxx -lm

you could write a makefile as follows:

# lazy file
#
myprog:
cxx -o myprog main.cxx func.cxx -lm
mynext:
cxx -o mynext next.cxx
#
# end lazy file

There are two independent targets here. Since there are no dependencies listed for the targets, Make assume every time that it must execute the appropriate command link (depending on whether you enter "make myprog" or "make mynext").

Simple Example of `make'

Suppose we have a text editor consisting of eight C source files and three header files. We need a makefile to tell `make' how to compile and link the editor. Assume that all the C files include `defs.h', but only those defining editing commands include `commands.h' and only low level files that change the editor buffer include `buffer.h'.

To recompile the editor, each changed C source file must be recompiled. If a header file has changed, to be safe each C source file that includes the header file must be recompiled. Each compilation produces an
object file corresponding to the source file. Finally, if any source file has been recompiled, all the object files, whether newly made or saved from previous compilations, must be linked together to produce the new
executable editor.

Here is a straightforward makefile that describes these criteria and says how to compile and link when the time comes:

edit : main.o kbd.o commands.o display.o \
insert.o search.o files.o utils.o
cc -o edit main.o kbd.o commands.o display.o \
insert.o search.o files.o utils.o

main.o : main.c defs.h
cc -c main.c
kbd.o : kbd.c defs.h command.h
cc -c kbd.c
commands.o : command.c defs.h command.h
cc -c commands.c
display.o : display.c defs.h buffer.h
cc -c display.c
insert.o : insert.c defs.h buffer.h
cc -c insert.c
search.o : search.c defs.h buffer.h
cc -c search.c
files.o : files.c defs.h buffer.h command.h
cc -c files.c
utils.o : utils.c defs.h
cc -c utils.c

We split each long line into two lines using a backslash-newline; this is like using one long line, but is easier to read.

Each file that is generated by a program---that is to say, each file except for source files---is the "target" of a "rule" (Note: Rules). (In this example, these are the object files such as `main.o', `kbd.o', etc., and the executable file `edit'.) The target appears at the beginning of a line, followed by a colon.

After the colon come the target's "dependencies": all the files that are used as input when the target file is updated. A target file needs to be recompiled or relinked if any of its dependencies changes. In addition,
any dependencies that are themselves automatically generated should be updated first. In this example, `edit' depends on each of the eight object files; the object file `main.o' depends on the source file `main.c' and on the header file `defs.h'.

By default, `make' starts with the first rule (not counting rules whose target names start with `.'). This is called the "default goal". Therefore, we put the rule for the executable program `edit' first. The other rules are processed because their targets appear as dependencies of the goal.

After each line containing a target and dependencies come one or more lines of shell commands that say how to update the target file. These lines start with a tab to tell `make' that they are command lines. But
`make' does not know anything about how the commands work. It is up to you to supply commands that will update the target file properly. All `make' does is execute the commands you have specified when the
target file needs to be updated.

How `make' Processes This Makefile

After reading the makefile, `make' begins its real work by processing the first rule, the one for relinking `edit'; but before it can fully process this rule, it must process the rules for the files `edit' depends on: all the object files. Each of these files is processed according to its own rule. These rules say to update the `.o' file by
compiling its source file. The recompilation must be done if the source file, or any of the header files named as dependencies, is more recent than the object file, or if the object file does not exist.

Before recompiling an object file, `make' considers updating its dependencies, the source file and header files. This makefile does not specify anything to be done for them---the `.c' and `.h' files are not the targets of any rules---so nothing needs to be done. But automatically generated C programs, such as made by Bison or Yacc, would be updated by their own rules at this time.

After recompiling whichever object files need it, `make' can now decide whether to relink `edit'. This must be done if the file `edit' does not exist, or if any of the object files are newer than it. If an object file was just recompiled, it is now newer than `edit', so `edit' will be relinked.

Thus, if we change the file `insert.c' and run `make', `make' will compile that file to update `insert.o', and then
link `edit'. If we change the file `command.h' and run `make', `make' will recompile the object files `kbd.o',
`commands.o' and `files.o' and then link file `edit'.

Variables Make Makefiles Simpler

In our example, we had to list all the object files twice in the rule for `edit' (repeated here):

edit : main.o kbd.o commands.o display.o \
insert.o search.o files.o utils.o
cc -o edit main.o kbd.o commands.o display.o \
insert.o search.o files.o utils.o

Such duplication is error-prone; if a new object file is added to the system, we might add it to one list and forget the other. We can eliminate the risk and simplify the makefile by using a "variable". Variables
allow a text string to be defined once and substituted in multiple places later.

It's standard practice for every makefile to have a variable named `objects', `OBJECTS', `objs', `OBJS', `obj' or `OBJ' which is a list of all object file names. We would define such a variable `objects' with a line like this in the makefile:

objects = main.o kbd.o commands.o display.o \
insert.o search.o files.o utils.o

Then, each place we want to put a list of the object file names, we can substitute the variable's value by writing `$(objects)'. Here is how the rule for `edit' looks as a result:

edit : $(objects)
cc -o edit $(objects)

Letting `make' Deduce the Commands

It is not necessary to spell out the commands for compiling the individual C source files, because `make' can figure them out: it has an "implicit rule" for updating a `.o' file from a correspondingly named `.c' file using a `cc -c' command. For example, it will use the command `cc -c main.c -o main.o' to compile `main.c' into `main.o'. We can therefore omit the commands from the rules for the object files. *Note Implicit::.

When a `.c' file is used automatically in this way, it is also automatically added to the list of dependencies. We can therefore omit the `.c' files from the dependencies, provided we omit the commands.

Here is the entire example, with both of these changes, and a variable `objects' as suggested above:

objects = main.o kbd.o commands.o display.o \
insert.o search.o files.o utils.o

edit : $(objects)
cc -o edit $(objects)

main.o : defs.h
kbd.o : defs.h command.h
commands.o : defs.h command.h
display.o : defs.h buffer.h
insert.o : defs.h buffer.h
search.o : defs.h buffer.h
files.o : defs.h buffer.h command.h
utils.o : defs.h

This is how we would write the makefile in actual practice.

Another Style of Makefile

Since the rules for the object files specify only dependencies, no commands, one can alternatively combine them by dependency instead of by target. Here is what it looks like:

objects = main.o kbd.o commands.o display.o \
insert.o search.o files.o utils.o

edit : $(objects)
cc -o edit $(objects)

$(objects) : defs.h
kbd.o commands.o files.o : command.h
display.o insert.o search.o files.o : buffer.h

Here `defs.h' is given as a dependency of all the object files; `commands.h' and `buffer.h' are dependencies of the specific object files listed for them.

Whether this is better is a matter of taste: it is more compact, but some people dislike it because they find it clearer to put all the information about each target in one place.

Rules for Cleaning the Directory

Compiling a program isn't the only thing you might want to write rules for. Makefiles commonly tell how to do a few other things besides compiling the program: for example, how to delete all the object files and executables so that the directory is "clean". Here is how we would write a `make' rule for cleaning our example editor:

clean:
rm edit ${objects}

This rule would be added at the end of the makefile, because we don't want it to run by default! We want the rule for `all', which recompiles the editor, to remain the default goal.

Since `clean' is not a dependency of `all', this rule won't run at all if we give the command `make' with no arguments. In order to make the rule run, we have to type `make clean'.

Make References

http://www.cs.wustl.edu/~cplus/makefiles.html

http://www.cs.colorado.edu/~main/supplements/hx.html


source: here, here, here, here and here.