GNU Make is both a dependency-description language and a text-expansion language. A Makefile first defines a graph of files, prerequisites, and recipes; its variables, functions, conditionals, and directives determine which parts of that graph exist. Make then decides which targets are stale and executes the recipes required to update them.
Keeping these two roles separate while reading a Makefile prevents several common errors. Variable and function expansion can happen while Make reads a rule, while it searches for a rule, or while it executes a recipe. In contrast, a recipe’s shell commands run only after Make has constructed enough of the graph to choose an update plan.
Starting from a Predictable Environment #
An inherited environment can change a build before the Makefile has read a line: every environment variable, except SHELL on POSIX systems, becomes a Make variable. For diagnosis, start with an empty environment:
env -i makeProjects that define all intended rules can also disable GNU Make’s built-in variables and implicit rules:
# Do not use Make's built-in rules and variables.
MAKEFLAGS += -rR
# GNU Make 3.x does not apply this setting early enough to affect
# the current top-level invocation.
# Make character ordering independent of the caller's locale.
unexport LC_ALL
LC_COLLATE = C
LC_NUMERIC = C
export LC_COLLATE LC_NUMERIC-r disables built-in implicit rules, and -R disables built-in variables. The settings reduce implicit-rule search and prevent an unintended built-in recipe from participating in a build. They are appropriate only when the project supplies the necessary explicit or pattern rules. GNU Make 3.x has a historical timing limitation: appending -rR to MAKEFLAGS while reading the top-level Makefile does not fully disable the built-ins for that same read, so projects supporting that version may restart through a sub-make.
Reading a Makefile and Constructing the Graph #
GNU Make has two broad phases:
- During the read phase, it reads the primary Makefile and included makefiles; expands directives and variables where their flavor requires expansion; records variables, explicit rules, pattern rules, and implicit rules; and constructs the dependency graph.
- During the update phase, it starts from the requested goals, determines which reachable targets are out of date, and runs the recipes necessary to update them.
The distinction does not mean that all expansion occurs in the first phase. Recursive variables and recipe text can expand later. It does mean that conditionals and include directives select graph structure while Make is reading, rather than after a target’s recipe starts.
Makefile Constructs #
A Makefile contains the following kinds of constructs:
- Rules describe targets, prerequisites, and optional recipes.
- Variable definitions associate a name with text that Make can substitute later.
- Directives include other files, select conditional text, or define multi-line variables.
- Comments begin with
#outside recipe context and continue to the next unescaped newline.
Comments are contextual. A line in a recipe is sent to the recipe shell, so a # there is interpreted by that shell. Within a define directive, text is retained in the variable value, including apparent comments; after expansion, that text may become Make syntax or recipe text depending on where it is evaluated.
Including Other Makefiles #
include tells Make to read more Makefile syntax at the directive’s position. It is not a textual preprocessor substitution: Make tracks included files because it may later need to remake them.
# File names are processed in this order; wildcard expansion is allowed.
include Makefile.other foo *.mk $(bar)
# Missing generated configuration is acceptable during an initial build.
-include include/config/auto.confFor a relative included name, GNU Make first searches relative to its current working directory, not relative to the including file’s directory. If that lookup fails, GNU Make searches .INCLUDE_DIRS, which includes directories passed through -I and implementation-configured include directories. Therefore, a Makefile stored outside the working directory should not assume that an include path is relative to its own location.
After reading every requested Makefile, Make attempts to update makefiles that are missing or out of date, including included fragments. If an included file is missing but a rule can generate it, Make runs that rule, rereads the Makefiles, and then constructs the final graph. A non-optional included file becomes an error only when Make cannot read or remake it. Remaking makefiles before final graph construction supports generated configuration and dependency fragments:
include include/config/auto.conf
include include/config/auto.conf.cmd
%/config/auto.conf %/config/auto.conf.cmd %/generated/autoconf.h: .config
$(MAKE) -f $(srctree)/Makefile syncconfig
The example is a Linux-kernel-style fragment. When .config is newer than either included configuration file, Make can remake the files before using their current contents. Exact target sets and helper variables vary between kernel versions.
Rules, Targets, and Goals #
An explicit rule describes when and how to update one or more targets:
target: prerequisites
recipe
app: main.o parse.o
$(CXX) $(LDFLAGS) -o $@ $^ $(LDLIBS)
main.o: main.cc parse.h
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $<
Targets and prerequisites conventionally name files. The rule states that app cannot be current until main.o and parse.o are current; its recipe links those objects. In recipes, $@ expands to the target, $< to the first prerequisite, and $^ to the prerequisite list with duplicates removed.
Goals are targets requested on the command line, such as make app. Without an explicit goal, GNU Make uses .DEFAULT_GOAL when it is set; otherwise it uses the first suitable target defined in the Makefiles, excluding targets whose names begin with .. A default goal should therefore be deliberate:
.DEFAULT_GOAL := all
.PHONY: all
all: appPattern rules can also implement related command-line goal families:
config: outputmakefile scripts_basic FORCE
$(MAKE) $(build)=scripts/kconfig $@
%config: outputmakefile scripts_basic FORCE
$(MAKE) $(build)=scripts/kconfig $@
The first rule handles the literal config goal; the second handles names such as oldconfig or menuconfig when their target names end in config. The target name passed as $@ lets the helper select the requested operation without duplicating the recipe.
A target may occur in several explicit rules. Make merges their prerequisites, which permits conditional and incremental declarations. At most one ordinary recipe should be supplied for a target; multiple recipes produce a warning and one recipe replaces the other. Double-colon rules have different independent-rule semantics and should be used only when that distinction is intended.
__all:
ifeq ($(KBUILD_EXTMOD),)
__all: all
else
__all: modules
endifNormal, Order-Only, and Phony Targets #
For a normal file target, a normal prerequisite establishes both ordering and freshness. Make updates the prerequisite first; then it updates the target when the target is missing or a normal prerequisite is newer. An order-only prerequisite establishes only ordering:
build/main.o: src/main.cc | build
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $<
build:
mkdir -p $@
The directory must exist before compilation, but changes to a directory’s timestamp should not force every object to rebuild. The syntax generalizes as follows:
targets: normal-prerequisites | order-only-prerequisites
recipe
Names that do not denote generated files must be declared as prerequisites of .PHONY:
.PHONY: clean test
clean:
rm -f app *.o *.d
A phony target is always considered out of date, so its recipe runs whenever that target is needed. A real target with a phony normal prerequisite consequently rebuilds on every invocation. Do not use a phony prerequisite as a convenience “function call” for a real output unless the output must always be rebuilt:
.PHONY: main func_foo
main: func_foo
@echo "main follows func_foo"
func_foo:
@echo "phony prerequisites provide ordering, not arguments"
The idiom can organize requested actions, but it does not provide parameter passing and it changes freshness semantics. Some large Makefiles accumulate phony names first and declare them once:
PHONY := __all
PHONY += $(MAKECMDGOALS) __sub-make scripts_basic
PHONY += outputmakefile all include/config/auto.conf prepare0
.PHONY: $(PHONY)Pattern and Implicit Rules #
An explicit rule names a concrete target. A pattern rule uses % as a nonempty stem and can describe a family of targets:
build/%.o: src/%.cc
@mkdir -p $(@D)
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -MMD -MP -c -o $@ $<
When Make considers build/parser.o, % has the stem parser, so the prerequisite is src/parser.cc. A target pattern that contains a slash matches directory components as part of the stem:
%/config/auto.conf:
@:
The pattern can therefore match foo/config/auto.conf and bar/zoo/config/auto.conf. A rule may have multiple target patterns when they are generated together:
$(obj)/%.asn1.c $(obj)/%.asn1.h: $(src)/%.asn1 $(objtree)/scripts/asn1_compiler
$(call cmd,asn1_compiler)
GNU Make also has built-in implicit rules. In a directory containing main.c, make main.o can compile the source even without a Makefile because GNU Make knows a built-in C compilation rule. Built-in rules use built-in variables such as CC, CFLAGS, CPPFLAGS, CXX, CXXFLAGS, LDFLAGS, and LDLIBS.
Pattern rules are preferable for project behavior because they make the complete dependency relationship visible. Disable the built-ins with -rR only after adding equivalent project rules. CC and related variables remain useful conventions in explicit recipes; disabling the built-ins does not prohibit defining and using them.
The established toolchain variables separate each compilation stage:
CCselects the C compiler, andCFLAGSsupplies its C-specific options.CXXselects the C++ compiler, andCXXFLAGSsupplies its C++-specific options.CPPselects a standalone C preprocessor, whileCPPFLAGSsupplies preprocessor options for compilation commands.LDFLAGSsupplies linker-search and linker-control options when a compiler driver performs the link.LDLIBSsupplies libraries such as-lssl, because those options normally belong after object files on a link command.
The names are conventions rather than a type system. A project recipe must place them in a command where its selected tool accepts them. GNU Make’s built-in C and C++ rules recognize them, but an explicit project rule remains responsible for its own command layout.
Wildcards and File Search #
The characters *, ?, and [...] have different expansion rules by context:
- In target and prerequisite lists, GNU Make performs wildcard expansion.
- In a recipe, Make passes the characters to the shell, whose globbing rules apply.
- In variable values and other Make expressions, use
$(wildcard pattern)to request filesystem expansion.
do-stuff: *.c # Make expands this.
rm *.o # The recipe shell expands this.
objects = *.o # Stores literal text; it has not expanded yet.
output: $(objects) # Make expands the resulting wildcard as prerequisites.
objects := $(wildcard *.o)
When no names match, a wildcard in a target or prerequisite list can remain literal, whereas $(wildcard ...) returns an empty string. The different unmatched behavior matters for generated lists: use wildcard when an absent match should contribute no prerequisite. Escape a literal * or ? with a backslash.
VPATH and vpath add search directories for file names:
VPATH = src:../headers
vpath %.h ../headers
vpath %.generated generatedVPATH supplies an unselective, colon- or whitespace-separated search list. A vpath pattern directories directive supplies directories for matching names; vpath pattern clears that pattern’s search path, and bare vpath clears all paths established by vpath directives. Make searches the working directory before these paths. Keep the paths narrow because a target or prerequisite resolved in an unexpected source directory makes the graph difficult to inspect.
An empty recipe can deliberately block an implicit recipe while retaining a dependency declaration:
target: ;Variables, Functions, and Expansion #
Make variables store text rather than typed values. The assignment operator determines whether Make stores the right-hand side for later expansion or expands it immediately.
Recursive and Simple Variables #
= defines a recursively expanded variable. Make stores its right-hand side verbatim and recursively expands it each time the variable is referenced:
foo = $(bar)
bar = $(ugh)
ugh = hello
all:
@echo $(foo)
The recipe prints hello, although foo was defined before bar and ugh, because Make evaluates the references when it expands $(foo). Recursive expansion is useful when a value should follow later definitions; it also repeats functions such as $(shell ...) and $(wildcard ...) at every reference.
:= defines a simply expanded variable. Make expands the right-hand side immediately and stores the result:
x := foo
y := $(x) bar
x := later
all:
@echo $(y)
The recipe prints foo bar, because y captured the earlier value of x. GNU Make accepts :=; POSIX Make specifies ::=. Use simple variables for computed lists that should not change and to avoid repeated expansion. ?= assigns only when a variable has not already been defined, while += appends using the existing variable’s expansion flavor.
Reference names with $(name) or ${name}. Write $$ when a literal dollar must reach the recipe shell:
show-home:
@echo $$HOME
The obsolete-looking $x form names only the one-character variable x; $foo is therefore $f followed by oo. Parenthesized references avoid that ambiguity.
Variable names cannot contain :, #, =, or a space. Portable Makefiles should use letters, digits, and underscores. Lowercase names conventionally denote internal variables, while uppercase names denote user-configurable parameters and implicit-rule conventions. Avoid names beginning with . followed by an uppercase letter because GNU Make reserves that namespace for possible future special variables.
Origins, Precedence, and Export #
Variables enter Make from the Makefile, the command line, the environment, built-in defaults, and automatic-variable context. An ordinary command-line assignment overrides an ordinary Makefile assignment:
make CXXFLAGS='-O0 -g' appCXXFLAGS := -O2
override CXXFLAGS := -O0 -gThe override directive deliberately takes precedence over a command-line definition. Environment variables normally lose to an ordinary Makefile definition; make -e gives environment definitions higher precedence, but makes builds less reproducible and is not recommended. $(origin variable) reports whether a value came from the environment, command line, file, default, override, or automatic context.
Every environment variable presented to Make becomes a Make variable, except that GNU Make does not import the environment’s SHELL value as its recipe-shell setting on POSIX systems. The Makefile variable SHELL selects the executable used for recipes, and .SHELLFLAGS supplies its arguments; GNU Make defaults to -c on POSIX systems.
export adds a Make variable to the environment of recipe processes and, therefore, to a sub-make started by such a recipe:
export BUILD_MODE := release
show-environment:
env
Export only variables required by the child build. Export is an environment boundary, not a lexical scope: a child Make can replace the value according to its own Makefile and precedence rules.
GNU Make also initializes special variables that expose the invocation and parser state:
MAKEis the invoked Make program name, so recipes should use it for recursive invocations.MAKEFLAGScarries command options and command-line variable definitions to recursive Make processes.GNUMAKEFLAGSaccepts GNU-Make-specific flags and is folded into the effective flags.MAKECMDGOALSlists goals supplied on the command line; it is useful for deciding whether to include an expensive optional fragment.CURDIRis Make’s current working directory, independent of how the primary Makefile was found.MAKE_VERSION,MAKE_HOST, andMAKELEVELidentify the GNU Make implementation, host build information, and recursive depth. They are diagnostics or compatibility inputs, not a replacement for an explicit project interface.VPATH,SUFFIXES, andLIBPATTERNSinfluence file search and rule behavior.MAKEFILESnames Makefiles that GNU Make reads before the ordinary Makefiles and therefore should be controlled by the build environment.
Special variables are still variable values: a Makefile can reassign several of them, but doing so can invalidate the behavior their names imply. In particular, redefining MAKE can direct every sub-make to an unrelated executable.
Text Functions and Automatic Variables #
Function invocation uses the same syntax as variable expansion:
$(function arguments)
${function arguments}Many text functions treat their inputs as whitespace-separated word lists:
sources := $(wildcard src/*.cc)
objects := $(patsubst src/%.cc,build/%.o,$(sources))
headers := $(filter %.h,$(files))
without_tests := $(filter-out %_test.cc,$(sources))
subst replaces literal text; patsubst replaces words matching a % pattern; strip normalizes whitespace; findstring tests for a literal substring; filter and filter-out select words; and sort orders and deduplicates words. word, wordlist, words, firstword, and lastword select or count words. Path-oriented functions include dir, notdir, suffix, basename, addsuffix, addprefix, join, realpath, and abspath. wildcard performs filesystem globbing and returns no words when there are no matches.
Conditional functions choose text during expansion:
mode := $(if $(DEBUG),debug,release)
first := $(or $(PRIMARY),$(FALLBACK),default)
enabled := $(and $(FEATURE_A),$(FEATURE_B))
if, or, and and are expression functions, not Makefile conditional directives. ifeq, ifneq, ifdef, ifndef, else, and endif decide which Makefile text is read; use them when the graph itself must be conditionally defined.
Automatic variables receive their values only in a rule’s recipe and closely related expansion contexts. The common variables are $@ for the target, $< for the first prerequisite, $^ for all normal prerequisites with duplicates removed, and $? for prerequisites newer than the target. They should not be used to construct a global list during Makefile parsing because they have no single target value there.
Recipe Execution and Errors #
Make expands recipe text when it has chosen to run a rule, then invokes the recipe shell. Every logical recipe line runs in a separate shell unless the .ONESHELL special target is in effect:
bad:
cd build
pwd
good:
cd build && pwd
The bad recipe runs pwd in a fresh shell, so the previous line’s directory change is lost. Multi-line shell control structures must form one logical recipe line through backslash-newline continuation:
subprojects:
for directory in $(SUBDIRS); do \
$(MAKE) -C $$directory all; \
done
Make stops when a recipe command exits nonzero, unless the line begins with -, an error is explicitly handled in the shell, or an applicable error-handling option changes that behavior:
clean:
-rm obsolete-file
The - prefix asks Make to ignore the exit status for that recipe line. Prefer an idempotent command such as rm -f when the operation itself has well-defined absent-input behavior.
Recursive Make #
A sub-make is simply a Make process started from a recipe. The shell executes the recipe, changes directory if requested, and starts another program:
do-submake:
cd submodule && $(MAKE)
Use $(MAKE), not a literal make. GNU Make initializes MAKE to the program name used to invoke the current Make process, which keeps the recursive hierarchy on the same Make executable. GNU Make also treats a recipe line containing $(MAKE) or ${MAKE} specially: -n, -t, and -q do not suppress that recursive invocation. A leading + has the same force-execution effect.
The child may use a different Makefile without changing the shell’s working directory:
do-submake:
$(MAKE) -f target_dir/Makefile
Command-line variable assignments and options propagate through GNU Make’s recursive-build mechanisms, including MAKEFLAGS; explicitly exported Make variables cross through the recipe environment:
export BUILD_MODE := release
subproject:
$(MAKE) -C lib
An orchestration target can invoke requested goals one by one:
$(MAKECMDGOALS): __build_one_by_one
@:
.PHONY: __build_one_by_one
__build_one_by_one:
@set -e; \
for goal in $(MAKECMDGOALS); do \
$(MAKE) -f $(srctree)/Makefile $$goal; \
done
The loop gives each goal a separate Make invocation, which can be useful when an outer Makefile must establish an execution boundary. It also removes visibility between the child graphs; the pattern is not a substitute for expressing target-to-target prerequisites in one graph.
Recursive Make is appropriate for independent project boundaries. It obscures dependencies when targets in separate directories require each other, because each process sees only its own graph. Prefer one graph for cross-directory prerequisites; otherwise Make cannot schedule or invalidate the relationship accurately.
Dependency Files and Performance #
Compilers can emit header dependencies while producing object files:
SOURCES := $(wildcard src/*.cc)
OBJECTS := $(patsubst src/%.cc,build/%.o,$(SOURCES))
build/%.o: src/%.cc
@mkdir -p $(@D)
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -MMD -MP -c -o $@ $<
-include $(OBJECTS:.o=.d)-MMD records dependencies on user headers, and -MP emits phony rules that make a removed header less likely to fail as a missing target. -include permits the .d files to be absent before the first compilation. Generated headers still require explicit targets and prerequisites: generating a header invisibly inside an unrelated recipe leaves Make without the dependency edge needed for incremental correctness.
Large Makefiles can spend significant time reading fragments, searching implicit rules, and calling stat for prerequisites. Avoid adding expensive rules to the graph when no requested goal can reach them:
ifdef may-sync-config
# Make must inspect this large prerequisite set.
heavy_rule: foo bar ...
include include/config/auto.conf.cmd
endifThe conditional must be decided while Make reads the file, before it records heavy_rule. Merely placing a conditional inside a recipe does not avoid graph construction or prerequisite checks.
Some generated systems precompute an exact dependency closure. The following implementation-specific Linux-kernel-style pattern records only the configuration symbols that affect an object:
deps_kernel/sysctl.o := \
$(wildcard include/config/SYSCTL) \
$(wildcard include/config/X86) \
$(wildcard include/config/RT_MUTEXES)
kernel/sysctl.o: $(deps_kernel/sysctl.o)
$(deps_kernel/sysctl.o):The final empty rules prevent unwanted implicit-rule searches for the generated configuration paths. A generated closure trades a larger Makefile for fewer discovery steps and more precise invalidation. Maintain that closure from the same configuration information that controls compilation; a manually incomplete list causes stale outputs, while an unconditionally oversized list turns every invocation into unnecessary filesystem work.
For diagnosis, use make -n to print recipes without executing them, make --debug=v to explain update decisions, and make -pn to inspect Make’s expanded database. Read the result in the same order Make does: first the graph that directives and expansion produced, then the freshness decisions that caused recipes to run.
Comments
Read or comment on GitHub (sign-in required to comment)