Why learn about makefiles?
This is motivated well by [2]:
The fact of the matter is that it is altogether too easy to forget to recompile some source file. Then, when the different object code files are merged, we may end up with a program that does not work, and is hard to debug because it does not correspond to the current source code. A simple, but wasteful, way to avoid this kind of problem is to recompile every source file after a series of changes have been performed.
The make
program is a software tool that can be used to keep
track of which files need recompiling after any changes have been made and
to actually issue the sequence of commands that performs all the necessary
recompilations.
target ... : dependencies ... command ... ...
g++ -MM myFile.c
make
carries out. A rule
may have more than one command, each on its own line.
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 in the rule you have specified when the
target file needs to be updated.
make
is not called with any arguments, by default it
executes the first target
A Simple Makefile
Here is a straightforward makefile that describes the way an executable
file called edit
depends on 4 object files which, in turn,
depend on four C source and three header files.
edit : main.o kbd.o command.o display.o g++ -g -o edit main.o kbd.o command.o display.o main.o : main.c defs.h g++ -g -c main.c kbd.o : kbd.c defs.h command.h g++ -g -c kbd.c command.o : command.c defs.h command.h g++ -g -c command.c display.o : display.c defs.h buffer.h g++ -g -c display.c clean : rm edit main.o kbd.o command.o display.o
To use this makefile to create the executable file called 'edit', type: make
To use this makefile to delete the executable file and all the object files
from the directory, type: make clean
Adding Variables
Now we're going to "spice" up the above makefile example a bit. To start with, we will add a few variables. A variable is defined in the makefile using format:
variable_name = value
In our case, we would like to define a variable for all of the object files. This way, we only need to list all of the objects files once.
Further, it is stylistic to specify the compiler type and options in only one place. This way different compiler specifications can be used easily.
COMPILER = g++ CFLAGS = -g OBJECTS = main.o kbd.o command.o display.o edit : $(OBJECTS) $(COMPILER) $(CFLAGS) -o edit ($OBJECTS) main.o : main.c defs.h $(COMPILER) $(CFLAGS) -c main.c kbd.o : kbd.c defs.h command.h $(COMPILER) $(CFLAGS) -c kbd.c command.o : command.c defs.h command.h $(COMPILER) $(CFLAGS) -c command.c display.o : display.c defs.h buffer.h $(COMPILER) $(CFLAGS) -c display.c clean : rm edit $(OBJECTS)
Dealing with errors in commands
After each shell command returns, make
looks at its exit status. If the command completed successfully, the next command line is executed; after the last command line is finished, the rule is finished.
If there is an error (the exit status is nonzero), make
gives up on the
current rule, and perhaps on all rules.
Sometimes the failure of a certain command does not indicate a problem.
For example, in the above we don't want an error reported if one of the
object files does not exist for removal by make clean
.
To ignore errors in a command line, write a `-' at the beginning of the line's text (after the initial tab). The `-' is discarded before the command is executed. In our case, this will look:
... clean : -rm edit $(OBJECTS)
Now the makefile will continue executing in spite of errors reported by
rm
Adding echo statements and automatic variables
Normally make
prints each command line before it is executed.
This is referred to as echoing because it gives the appearance that you
are typing the commands yourself.
When a line starts with `@', the echoing of that line is suppressed.
The `@' is discarded before the command is executed. Typically this feature
is only used with echo
commands that indicate progress through
the makefile
Automatic variables have values computed afresh for each rule that is executed, based on the target and dependencies of the rule. For example:
Incorporating these features into our running example, we get:
COMPILER = g++ CFLAGS = -g OBJECTS = main.o kbd.o command.o display.o edit : $(OBJECTS) @echo "$@ being recompiled due to updates in $?" $(COMPILER) $(CFLAGS) -o $@ ($OBJECTS) main.o : main.c defs.h @echo "$@ being recompiled due to updates in $?" $(COMPILER) $(CFLAGS) -c $< kbd.o : kbd.c defs.h command.h @echo "$@ being recompiled due to updates in $?" $(COMPILER) $(CFLAGS) -c $< command.o : command.c defs.h command.h @echo "$@ being recompiled due to updates in $?" $(COMPILER) $(CFLAGS) -c $< display.o : display.c defs.h buffer.h @echo "$@ being recompiled due to updates in $?" $(COMPILER) $(CFLAGS) -c $< clean : -rm edit $(OBJECTS)
References