Makefile is the most commonly used compilation tool in Linux. Stuart Feldman created it at Bell Labs in 1967. Although it may be older than you and me, it is still active nowadays. This article will introduce some basic syntax of Makefile.
Table of Contents
Rules
Makefile actually consists of many rules. A rule can depend on other rules. Therefore, we can write complex rules to compile our projects. The syntax of a rule is as follows:
targets ... : prerequisites ... recipes ...
- targets: One or more file names, separated by spaces. These files are often executable or object files.
- prerequisites: One or more file names, separated by spaces. These files are often input files to generate targets, such as source files. That is, targets depend on prerequisites.
- recipes: One or more actions. These actions often generate target files.
When prerequisites are changed, recipes will be executed to generate targets. It is based on the file modification time of targets and prerequisites. If the time of prerequisites is after targets, it means that prerequisites has been changed.
For example, in the following Makefile, targets are the hello executable file, prerequisites are the hello.c source code file, and recipes use gcc to compile hello.c into hello.
hello : hello.c gcc -c hello.c -o hello
When we execute make hello
in the command line, it will execute the rule whose target is hello in the Makefile. When it finds that hello.c has been changed, that is, when the file modification time of hello.c is after hello, it will execute gcc to regenerate hello.
Variables
Makefile allows us to declare variables. The value of a variable is a string, and you can use variables in targets, prerequisites, and recipes. When using variables, we must wrap the variable with $()
or ${}
, such as $(foo)
or ${foo}
.
Makefile provides several ways to set values to variables.
Operators | Descirptions |
---|---|
= | Recursively expanded variable assignment |
:= | Simply expanded variable assignment |
?= | Conditional variable assignment |
+= | Appending more text |
Recursively Expanded Variable Assignment
Suppose variable A contains a reference to variable B. When we get the value of variable A, the reference of variable B will be expanded, even if variable B is declared after variable A. This is recursively expanded variable assignment, and the symbol used is =
.
foo = foo $(bar) bar = bar $(ugh) ugh = Huh? all: echo $(foo) # Output: # foo bar Huh?
Simply Expanded Variable Assignment
Suppose variable A contains a reference to variable B, and variable B is declared after variable A. When variable A is declared, the reference to variable B is expanded. But at this time, variable B has not been declared yet, so it is expanded to an empty string. After that, even if variable B is declared, the value of variable A will not change anymore. Therefore, the value of variable A is fixed when it is declared, which is different from recursively expanded variable assignment. The symbol used for simply expanded variable assignment is :=
.
foo := foo $(bar) bar := bar $(ugh) ugh := Huh? all: echo $(foo) # Output: # foo
Conditional Variable Assignment
Conditional variable assignment will only be executed when the variable has not been declared. The symbol it uses is ?=
.
foo = foo foo ?= $(bar) bar = bar $(ugh) ugh := Huh? all: echo $(foo) # Output: # foo
foo ?= foo $(bar) bar = bar $(ugh) ugh := Huh? all: echo $(foo) # Output: # foo bar Huh?
Appending More Text
If variable A has been declared and we want to append more strings, we can use the symbol +=
. When using +=
to append a string to variable A, will it be recursively expanded variable assignment or simply expanded variable assignment? This is not determined by +=
, but depends on whether =
or :=
is used when variable A is declared.
foo = foo foo += $(bar) bar = bar $(ugh) ugh := Huh? all: echo $(foo) # Output: # foo bar Huh?
foo := foo foo += $(bar) bar = bar $(ugh) ugh := Huh? all: echo $(foo) # Output: # foo
Pattern Rules
Pattern Rules
Pattern rules are similar to ordinary rules, except that they contain the %
character. %
is called the wildcard character, which can match any non-empty string. For example, %.c
matches any file name ending in .c
. Moreover, the substring matched by %
is called stem. For example, when we use %.c
to match hello.c, hello is a stem.
We can use % in targets and prerequisites. When % is used in both targets and prerequisites, they will match the same stem. As in the example below, it will match hello.o and hello.c.
%.o : %.c recipe
Static Pattern Rules
We have learned about ordinary rules and pattern rules. The ordinary rules are strict in that they require the full file name to be specified. Relatively speaking, pattern rules are very flexible and can use wildcard characters to match all possible file names. However, static pattern rules are somewhere in between.
The syntax of static pattern rules is as follows. The targets are the same as the ordinary rules, and the full file name must be specified. Then, use target-pattern and prerequisite-patterns to match any file name in targets.
targets ... : target-pattern : prerequisite-patterns ... recipes ...
As in the example below, it will only match foo.o, foo.c, bar.o, and bar.c. It will not match hello.o and hello.c.
objects = foo.o bar.o $(objects) : %.o : %.c recipes
Automatic Variables
When we use pattern rules, how do we refer the matched file names in recipes? At this time, we have to use the automatic variables of Makefile. Automatic variables are several variables predefined by Makefile. We can use them to obtain the matching targets or prerequisites. Here are some commonly used automatic variables. For others, please refer to Automatic Variables.
Automatic Variables | Descriptions |
---|---|
$@ | The name of whichever target caused the rule’s recipe to be run. |
$< | The name of the first prerequisite. |
$? | The names of all the prerequisites that are newer than the target, with spaces between them. If the target does not exist, all prerequisites will be included. |
$^ | The names of all the prerequisites, with spaces between them, and the duplicates are removed. |
$+ | Like $^, but but the duplicates are included. |
$* | The stem which a pattern rule matches. |
The following example shows how to use automatic variables to write recipes for pattern rules.
EXEC = hello OBJS = hello.o lib.o all : $(EXEC) $(EXEC) : $(OBJS) $(CC) -o $@ $^ %.o : %.c $(CC) -c $< -o $@ $(CFLAGS)
Recursive Use of make
Assume that there is a Makefile in the current folder, and there is also a Makefile in the subfolder. We want that after executing make
in the current folder, it will recursively execute make
in the subfolder. There are two ways to execute make
recursively. One is to switch to the subfolder and execute $(MAKE)
.
subsystem: cd subdir && $(MAKE)
The other is to use $(MAKE) -C
.
subsystem: $(MAKE) -C subdir
If we want to pass a variable to sub-make, we can use export
.
# Top-level Makefile foo = foo bar = $(foo) bar export bar all : cd sub && $(MAKE)
# Makefile under sub/ directory all : echo $(bar)
Functions
Makefile provides many built-in functions. We only list some commonly used functions. For other functions, please refer to Functions for Transforming Text.
Functions | Descriptions |
---|---|
$(wildcard pattern …) | Find all files matching the pattern . |
$(subst from,to,text) | Replace from by to in the text . |
$(patsubst pattern,replacement,text) $(var:pattern=replacement) $(var:suffix=replacement) | Find words in text matching pattern , and replace them with replacement . |
$(filter pattern …,text) | Returns words in text matching pattern . |
$(if condition,then,else) | Returns then if condition is not empty; otherwise, returns else . |
$(foreach var,list,text) | Convert each word in list to text . |
$(shell command …) | Execute command . |
wildcard
The wildcard
function searches for all file names matching pattern
in the current directory, separated by spaces.
# wildcard syntax $(wildcard pattern ...) # Expand all file names matching *.c, sorted. SRCS = $(wildcard *.c) # Expand all file names matching *.c, sorted, # followed by all file names matching *.h, sorted. SRCS = $(wildcard *.c *.h)
subst
The subst
function replaces from
string in text
with to
.
# subst syntax $(subst from,to,text) # Replace EE with ee txt1 = feet on the street txt2 = $(subst ee,EE,feet on the street) # txt2 = fEEt on the strEEt
patsubst
The patsubst
function searches text
for words matching pattern
and replaces them with replacement
.
It has two other shorthand to write it. The first way is to search for words that match pattern
on a specific variable and replace them with replacement
. This approach allows us to omit the patsubst
name. The second method is for suffix
replacement. Not only does this omit the patsubst
name, it also omits the %
character.
# patsubst syntax $(patsubst pattern,replacement,text) $(var:pattern=replacement) $(var:suffix=replacement) # Replace .c by .o for all source file names SRCS = foo.c bar.c OBJS1 = $(patsubst %.c,%.o,$(SRCS)) # OBJS1 = foo.o bar.o # Use the first shorthand OBJS2 = $(SRCS:%.c=%.o) # OBJS2 = foo.o bar.o # Use the second shorthand OBJS3 = $(SRCS:.c=.o) # OBJS3 = foo.o bar.o
filter
The filter
function returns the words in the list
that match the pattern
.
# filter syntax $(filter pattern ...,text) # Filter out header files SRCS = foo.c foo.h bar.c bar.h foo : $(SRCS) $(CC) $(filter %.c,$(SRCS)) -o foo
if
If condition
is a non-empty string, if
returns then
. Otherwise, if condition
is an empty string, it returns else
.
# if syntax $(if condition,then,else) con1 := not-empty echo $(if $(con1),then part,else part) # Output: then part con2 := echo $(if $(con2),then part,else part) # Output: else part
foreach
The foreach
will convert each word in the list
into text
. var
is the word to be converted each time.
# foreach syntax $(foreach var,list,text) # foo = foo1 foo2 bar = $(foreach w,$(foo),$(w)_bar) # bar = foo1_bar foo2_bar # Find all file names in the list of dirs. dirs := a b c d files := $(foreach dir,$(dirs),$(wildcard $(dir)/*))
shell
The shell
function allows us to execute a command, and it will replace the newline characters in the returned value with whitespace.
# shell syntax $(shell command ...) contents := $(shell cat hello.c)
Conditionals
Makefile also provides conditional-directives that allow us to conditionally choose whether to execute a part of Makefile. The syntax is as follows.
conditional-directive-1 text-if-1-is-true else conditional-directive-2 text-if-2-is-true else text-if-1-and-2-are-false endif
There are four conditional-directives provided by Makefile.
# If text arg1 and arg2 are equal ifeq (arg1, arg2) # If text arg1 and arg2 are equal ifneq (arg1, arg2) # If variable is defined ifdef variable-name # If variable is not defined ifndef variable-name
ifeq ($(CC),gcc) libs = -lgnu else libs = endif ifdef libs echo $(libs) endif foo: $(objects) $(CC) -o foo $(objects) $(libs)
Special Built-in Target Names
The Makefile predefines some targets. For more details, please refer to Special Built-in Target Name .
Targets | Descirptions |
---|---|
.PHONY | Prevent Make from confusing the phony target with a file name. .PHONY : all clean |
Conclusion
This article only introduces part of the Makefile syntax. There are many things that have not been introduced, such as Implicit Rules. Although many more advanced tools are now available, there are still many projects that use Makefile. Moreover, for small projects, Makefile is a good choice because make
is installed on many Linux platforms.