(GNU) Make
Page Contents
References / useful Links
- GNU Make Manual.
- Recursive Make Considered Harmful, Peter Miller, 1998.
- Auto-Dependency Generation, Paul D. Smith, 2017.
- Makefile, Anonymous rm command executed at last, StackOverflow
- Writing a large build system with GNU make, by David Rothlisberger.
- Implementing non-recursive make, Emile van Bergen.
Makefile Basics
A Makefile describes how your program is built by specifying everything that it depends on and building the dependencies first. A Makefile rule looks like this:
target_1 ... target_n: prerequisite_1 ... prerequisite_m <tab> command_1 # NOTE that each command <tab> ... # is executed in its own <tab> command_j # subshell.
Here:
- Target is anything the must be made.
- Prerequisite are things that must be built before the target can be built.
- Commands are shell commands used to build the target. NOTE, each command executed in own subshell!
- Rule is what's shown above - it defines how to build a target.
To perform a build, make will construct a directed acyclic graph (DAG) from the rules. It will then do a post order traversal (visit children first), building the leaf nodes first and going back up the graph.
A very simple example is shown below:
The makefile for the above diagram would look something like this:
target: prequisite_1 prequisite_1 prequisite_1 <tab> commands_A prequisite_1: prequisite_4 <tab> commands_B prequisite_2: prequisite_5 <tab> commands_C prequisite_3: prequisite_5 prequisite_7 <tab> commands_D prequisite_4: <tab> commands_E prequisite_5: prequisite_6 <tab> commands_F prequisite_6: <tab> commands_G prequisite_7: <tab> commands_H
The post-order traversal is shown by the orange-background numbers.
- Make looks at
target
and visits the first child,prerequisit_1
. It can see thatprerequisit_1
depends onprerequisit_4
. So now,prerequisit_1
becomes the "target". If make determines thatprerequisit_1
is newer thanprerequisit_4
, it will buildprerequisit_4
. We assume that it does and buildsprerequisit_4
usingcommands_E
. -
Once
prerequisit_4
is built, all ofprerequisit_1
's dependencies have been visited soprerequisit_1
can now be built usingcommands_B
. - Make returns back to
target
and examines the next dependency,prerequisit_2
. Assumingprerequisit_5
is older thanprerequisit_2
, andprerequisit_6
is older thanprerequisit_5
, the post-order traversal will result in make next trying to buildprerequisit_6
usingcommands_F
. - All of
prerequisit_5
's dependencies have been built so make can now buildprerequisit_5
usingcommands_F
. -
All of
prerequisit_2
's dependencies have been built so make can now buildprerequisit_2
usingcommands_C
. - Make returns back to
target
and examines the next dependencyprerequisit_3
. There are two dependencies,prerequisit_5
andprerequisit_7
. Make has already builtprerequisit_5
so it knows that it does not need to build this again, so it ignores this path... nice! Thus all that is left is to look atprerequisit_7
, assuming it is older thanprerequisit_3
. Assume it is, so make builds it. -
All of
prerequisit_3
's dependencies have been built so it can now be built. - Make returns back to
target
and because all of its dependencies have been built, it can finally be built.
By doing this kind of traversal make ensures that everything that is need to be built is built, but not more than this. I.e., if some dependencies do not need to be refreshed, they are not rebuilt, helping to produce an efficient build. It is also the case that targets that are as new as all of their dependencies are not rebuilt.
The basic make process can be described like this:
- Make finds files indicated by the targets and prerequisites.
- If a prerequisite has a rule associated with it (i.e., make is looking at the rule
target: prerequisite
and the ruleprerequisite: dependencies
exists), make will try to update the prerequisite first. - When considering a target, if any prerequisite is newer than the target, make will attempt to make the prequisite(s) first.
PHONY targets
A special type of target is a .PHONY
target, which is always out of date and
thus always rebuilt.
How Make Locates Source Files: VPATH
& vpath
When a rule references a file without a path Make looks in the current working directory.
Few projects are housed in a single directory. One solution is to use VPATH
to instruct
Make to look in other directories for files.
VPATH = dir1 dir2 dir3 ...
This works for source files, i.e. *.c, *.cpp
etc but not includes. For those modifdy CPPFLAGS
.
Only targets and dependencies are searched using VPATH
. Paths in commands are not!
Beware of same-named files in different directories. Make just uses the first one it sees, so this can cause problems!
The VPATH
special variable is course-grained. It applies to all files. To be more specific
use vpath
:
vpath pattern dir-list
For example, to search for C files only under the src
directory and headers
only under the include
directory one could write the following:
vpath %.h include vpath %.c src
Make Phases
Make does its work in two phases:
- Reads Makefile and all included makefiles. Load vars and rules into internal DB. Simple-expanded vars expanded. Create DAG.
- Analyse DAG, recursively-expanded vars expanded, determined targets to update, update include targets first and possibly restart, then execute commands for all other rules.
Makefile Automatic Variables
This is just a summary of some of the more commonly used automatic variables. You can find a complete list here. The automatic variables do not have very user-friendly names so I have a few memory pegs I try to use to recall what they all mean...
Variable | Meaning |
$@ |
The file name of the target of the rule. Remember a rule looks like target: prerequisites . I like to remember this by thinking of the @ symbol as looking like a dart board or some kind of target. If the rule has multiple target $@ refers to whatever target caused the rule to be run. |
$< |
The name of the first prerequisite. Memory peg: The chevron looks like it "points" to a target, so think prerequisite. |
$? |
The names of all the prerequisites that are newer than the target. Memory peg: It's a question... what prerequisite is newer? |
$^ |
The names of all the prerequisites (duplicated removed). Memory peg: it's pointing up, so all of the above prerequisite. |
Makefile Pattern Rules
In the example makefile in the introduction section, each target was explicitly written down. This would be fine for a small project, but if you have more than a few source files, listing each one independently will soon get tedious and error prone. Most of the time we just want to say "and object file is generated from a c/c++ source file with the same file base name". And luckily we can... to say this we would write something like the following.
%.o: %.cpp <tab> ...Commands... my-target: fileA.o fileB.o ... fileZ.o
In the above example, Make knows that to build my-target
it must build the
object files fileA.o
through fileZ.o
. So, how does it construct
these objects? It looks through all of the rules it has encountered so far and tries to
match each file?.o
with a target. It finds %.o
, where %
is a wild card. The %
matches the portion file?
of the
prerequisite (this portion is called the stem), so Make can use this rule to
construct the object file.
Lets take fileA.o
as an example. Make will search for the file. If the file is
as new or newer than the target it knows that this prerequisite does not need to be rebuilt.
However, if it does, Make searches for a rule that will tell it how to build the prerequisite.
Make finds the rule %.o: %.c; commands
: fileA.o
matches
%.o
, where the %
matches fileA
. The prerequisite
for this rule specified %.c
, which substituting in the match will become
fileA.c
. As long as fileA.c
is just a file and not generated
by some other tool, Make will find no rule to create this file, so if the file doesn't
exist, then Make reports an error, otherwise it will rebuild fileA.c
if it
is newer than the target fileA.o
.
If two or more rules match Make will prefer more specific rules over more generic ones.
Limiting Scope Of Pattern Rules
To limit the scope of a pattern rule that includes wild cards to a set of files use the following:
$(OBJECTS) : %.o : %.c
In the above the %.o
will only match object filesnames in the variable
OBJECTS
and thus the pre-requisites will only match the corresponding objects
from OBJECTS
.
Implicit Rules
Make includes a set of default rules for commonly required types of dependencies. For example, you will generally not need to tell make how to compile a C or C++ program as its default rule database already has this information.
See the default rule database by typing (you can ommit the pipe to vim if you want, but the output is many screens long):
make -p -q | vim - ^ ^ ^ Don't execute any commands Print rule database
If you search the output for C/C++ rules you will find the following rules, amougst others, which are presented here possibly out-of-order wrt to the Make db print out:
OUTPUT_OPTION = -o $@ LINK.c = $(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH) LINK.o = $(CC) $(LDFLAGS) $(TARGET_ARCH) LINK.cpp = $(LINK.cc) LINK.cc = $(CXX) $(CXXFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH) COMPILE.cpp = $(COMPILE.cc) COMPILE.cc = $(CXX) $(CXXFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c COMPILE.c = $(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c %: %.o $(LINK.o) $^ $(LOADLIBES) $(LDLIBS) -o $@ %: %.c $(LINK.c) $^ $(LOADLIBES) $(LDLIBS) -o $@ %.o: %.c $(COMPILE.c) $(OUTPUT_OPTION) $<
So, if a Makefile has a rule file1.o: file1.c
,
Make will find the rule %.o: %.c
and execute the rule $(COMPILE.c) $(OUTPUT_OPTION) $<
.
This expands to $(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c -o $@ $<
, which expands to
$(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c -o file1.o file1.c
.
So, what do the variables such as CFLAGS
and CPPFLAGS
expand to? These must be
set by the makefile creator to meet the needs of their compilation!
Variable | Meaning |
CC |
By default this is set to cc , but if a different compiler was required this could
be overriden. |
CFLAGS |
Flags/Switches that should be added to the C compiler command line only, i.e., not shared with C++ compiler. |
CPPFLAGS |
Flags that should be passed to either or both of the C/C++ compilers. This generally means that they are preprocessor flags as they are generally the only thing that would be passed to bothcompilers. |
LDFLAGS |
Flags to pass to the linker. Normall add the library search pathes here: -L . |
LOADLIBES |
Itsa deprecated, but still supported, alternative to LDLIBS . |
LDLIBS |
Specifies the libraries to link against and possibly other flags. |
CXXFLAGS |
Flags/Switches that should be passed to the C++ compiler command line only, i.e., not shared with C compiler. |
TARGET_ARCH |
Use to select a specific architecture being compiled for [Ref]. For example, default PC comilation produces code that can run on any x86 architectire. So here one might add -march=... to build for a specific architecture like i686 to take advantage of machine instructions enabled for that architecture. Note binary will not be as portable. |
Variables
Naming conventions
Constants or environment variables in upper snake case.
Variables internam to the make file, lower snake case.
Types
Variables are either simple expanded or recursively expanded.
Simple Expanded
Simple expanded variables are declared like so:
SIMPLE_EXPANDED := some-value # ^^ # Note the colon before the equals! # some-value is expanded IMMEDIATELY upon reading this line
Simple expanded variables have their RHS expression expanded immediately upon being encountered, during the 1st make phase, and have the value of the expansion assigned to the variable.
Recursively Expanded
Recursively expanded variables are declared like so:
RECUSIVE_EXPANDED = some-value # ^^ # Note ONLY the equals! # some-value is not expanded. just stored as-is in the variable without evaluating it!
A recusively expanded variable just has the RHS stored as-is in the variable without any evaluation what so ever. The value is only ever expanded when the variable is USED.
When Expansion Occurs
- LHS of variable assignments immediately expanded
- RHS of
=
and?=
expansion deferred until second phase - RHS of
:=
immediately expanded - RHS of
+=
immediately expanded for variables declared with:=
, otherwise expansion/evaluation deferred until use - Rule targets and prerequisites immediately expanded. Commands deferred.
Target Specific Variables
Can append to or create variables specifically for a given target:
target: my_variable (=|:=|?=|+=) value target: prerequisites <tab> commands # ^^ # my_variable now has a value specific to this target.
Including Other Make Files Into The Main Makefile
Can include functionality from other Makefiles into the main Makefile using the include
directive.
Importantly if any include file is updated by a file, make clears its internal database and re-reads the
entire makefile! This is especially important for dependency generation.
The flow of logic that dictates when a Makefile is re-processed due to a rule-based update to an include target is shown below:
This process is especially important for dependency generation which is covered in its own section.
Dependency Generation
Notes taken from this article.
DEPDIR := .deps DEPFLAGS = -MT $@ -MMD -MP -MF $(DEPDIR)/$*.d # ^^^^^^ ^^^^ ^^^ ^^^^^^^^^^^^^^^^^^ # ^^^^^^ ^^^^ ^^^ Specify files to write the dependencies to # ^^^^^^ ^^^^ ^^^ # ^^^^^^ ^^^^ Instructs CPP to add a phony target for each dependency # ^^^^^^ ^^^^ other than the main file, causing each to depend on nothing. # ^^^^^^ ^^^^ These dummy rules work around errors make gives if you remove # ^^^^^^ ^^^^ header files without updating the Makefile to match. # ^^^^^^ ^^^^ # ^^^^^^ Instead of outputting the result of preprocessing, output a rule # ^^^^^^ suitable for make describing the dependencies of the main source # ^^^^^^ file. Do NOT include system header files as dependencues. # ^^^^^^ AND # ^^^^^^ Generate dependency information as a side-effect of compilation, # ^^^^^^ not instead of compilation. # ^^^^^^ # Change target of generated rule to $@ rather than the default that would # otherwise be the name of the main input file with directories deleted and # suffix replaced with platform specific object suffix. COMPILE.c = $(CC) $(DEPFLAGS) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c %.o : %.c %.o : %.c $(DEPDIR)/%.d | $(DEPDIR) $(COMPILE.c) $(OUTPUT_OPTION) $< $(DEPDIR): ; @mkdir -p $@ DEPFILES := $(SRCS:%.c=$(DEPDIR)/%.d) $(DEPFILES): include $(wildcard $(DEPFILES)) # ^^ # As seen in prev section if any of the include targets (i.e., the DEPFILES) were # updated by the rules the makefile is reloaded with a cleared db.
Separate Source And Binary
CMake calls this sort of the out-of-source-builds. Need these to cope with multiple targets and multiple builds per target, for example. To accomplish need to have source and output binaries in separate locations!