make is a utility for building programs based on the contents of a specially formatted text file named Makefile or makefile under Linux and Unix environments. A makefile has explicit rules, implicit rules, variable definitions, directives and comments. In the primer make tutorial, we looked at some example makefiles containing explicit rules. In this tutorial we will look at the variable definitions, directives and implicit rules.
VARIABLES
There are two kinds of variables in make. The first one is called recursively expanded variables. These are defined using the =
operator. The variables are expanded in a lazy fashion, just at the time required. For example,
x = $(y) y = $(z) z = a all: @echo $(x)
The substitutions happen when the value of x is to be printed in the echo command. The value printed is a.
The second kind of variables are called Simply expanded variables and are defined using the :=
operator. For example,
x := $(y) y := $(z) z := a all: @echo $(x)
Since y is not initialized (it does not exist) at the time x is assigned, x does not have a value and the execution of command echo prints nothing.
PRE-DEFINED VARIABLES
Some of the pre-defined used by make for implicit rules are:
- CC, program for compiling C programs; default: cc
- CXX, program for compiling C++ programs; default: g++
- CPP, program for running the C preprocessor, i.e., the “$(CC) -E” command
- CFLAGS, extra flags for the C compiler
- CPPFLAGS, extra flags for the C preprocessor
- CXXFLAGS, extra flags for the C++ compiler
- LDFLAGS, extra flags for the compiler for invoking the linker
- LDLIBS, library flags or names, indicating the libraries, for the compiler to pass on to the linker
AUTOMATIC VARIABLES
There are automatic variables in make. Automatic variables are computed afresh for each execution of a rule based on target and prerequisites. The automatic variables are valid only in the commands for a rule and are not defined for the target part or the prerequisites part of a rule. Some of the important automatic variables are,
- $@, the filename of the target of the rule,
- $<, the name of the first prerequisite,
- $?, the name of all prerequisites newer than the target, separated by spaces,
- $^, the name of all prerequisites with spaces in between them,
- $|, the names of all order-only prerequisites, with spaces in between them
- $*, the stem with which an implicit rule matches
make PROCESSING
make works in two phases. First it reads the entire makefile. It captures all the variables and rules in its internal variables and structures. It, then, decides which target needs to be built and executes the command for doing so.
BUILT-IN IMPLICIT RULES
There are built-in implicit rules in make to build various targets from corresponding pre-requisites. Built-in rules use the customary command and pre-defined variables to build targets. You can change the default value of pre-defined variables to fine-tune built-in rules to your requirements.
PATTERN RULES
We can define implicit rules by writing pattern rules. In a pattern rule, the target contains a %
character. A %
matches a non-empty string in the target. For example %.o matches any .o
file. The sub-string matched by %
is called the stem. For example, %.o matches foo.o and foo is the stem. In a rule, %
refers to the same stem. So, we can write a pattern rule for compiling a .c file into a .o file as,
%.o: %.c $(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@
“$<” and “$@” are the automatic variables described earlier. “$<” refers to the first prerequisite while “$@” refers to name of the target of the rule. This is, actually, predefined built-in implicit rule. Or, we can say that the default command for compiling a .c file is,
$(CC) -c $(CFLAGS) $(CPPFLAGS) foo.c
Similarly, the default command for compiling a C++ program is,
$(CXX) -c $(CXXFLAGS) $(CPPFLAGS) foo.cpp
Also, the default command for linking object files and creating the corresponding executable file is,
$(CC) $(LDFLAGS) foo.o [...] $(LOADLIBES) $(LDLIBS) -o foo
LOADLIBES is a depreciated, but still supported, alternative to LDLIBS.
makefile Example
Here is an example makefile.
# Makefile .PHONY: clean LDLIBS := -lm OBJECTS := prog0.o prog1.o prog2.o prog3.o prog0: $(OBJECTS) prog0.o: prog0.h prog1.o: prog1.h prog2.o: prog2.h prog3.o: prog3.h clean: rm -f *.o rm -f prog?
The first line defines a PHONY target, clean. This means that make does not look for a file with stem “clean”. If make is invoked as make clean, it executes the rm commands listed under the target clean. Then, we are specifying the value “-lm” for LDLIBS predefined variable. This means that the linker would be invoked with the “-lm” option and the mathematics library would be linked. Next, the variable OBJECTS is defined and it has four objects, prog[0-3].o.
It builds a target prog0, which is an executable file. prog0 has $(OBJECTS) as prerequisites. Now, the command for building the executable file from the four object files is not mentioned. It is the implicit command,
$(CC) $(LDFLAGS) $(OBJECTS) $(LOADLIBES) $(LDLIBS) -o prog0
Next, how are the individual objects prog[0-3].o made? Since the command is not mentioned, a suitable implicit command is used. Since the corresponding source files are .c files, the implicit command for a file is,
$(CC) -c $(CFLAGS) $(CPPFLAGS) progN.c
where N = 0, 1, 2 and 3. Each source file has a header file with the same stem. If a source file and/or header file is modified, the make command is run and the files which have changed are compiled. Then, the objects are linked and a new executable file is made.