Basic Rules
The Makefile is based on declarative dependencies as follows
target: prerequisite prerequisite2 command target2: target command2
If you run target2, due to the declared dependencies, target will run first.
It is important to note that the default target in Makefile is both a target and a local file, unless you declare it as PHONY, indicating it is just a target.
Up to Date Mechanism
Up to date is the core mechanism of Make. Let’s look at the syntax of Make rules as follows
target: prerequisite prerequisite2 command
In the operating system, every file stores the last modified timestamp. If the modification time of the target is greater than all prerequisites, the task will be skipped.
# Makefile hell: hell.o hell.c: echo "int main(){return 0;}" > hell.c
. └── Makefile 0 directories, 1 file
When we run the make command for the first time, the output is
echo "int main(){return 0;}" > hell.ccc -c -o hell.o hell.ccc hell.o -o hell
. ├── hell ├── hell.c ├── hell.o └── Makefile 0 directories, 4 files
On the second run
make: 'hell' is up to date.
Because the target corresponding to hell in the directory has a modification time greater than the prerequisite hell.o, it will not execute.
// Sort files in reverse order -rwxrwxr-x 1 jack jack 15776 Mar 10 10:44 hell -rw-rw-r-- 1 jack jack 1224 Mar 10 10:44 hell.o -rw-rw-r-- 1 jack jack 22 Mar 10 10:44 hell.c -rw-rw-r-- 1 jack jack 59 Mar 10 10:43 Makefile
Suppose we use touch hell.c now, will it force make to run again?
touch hell.c; make
Output
cc -c -o hell.o hell.ccc hell.o -o hell
Clearly, the modification time of hell.c is greater than that of hell.o, causing make to perceive the need to recompile hell.o, and ultimately regenerate the executable file hell.
Automatic Variables
-
$< The name of the first prerequisite -
$^ All prerequisites -
$@ The target name -
$? All prerequisites that are newer than the target
Makefile all: hello1.c hello2.c @echo '$$< = ' $< @echo '$$? = ' $? @echo '$$@ = ' $@ @echo '$$^ = ' $^ touch all
Current directory
. ├── Makefile ├── hello1.c └── hello2.c 1 directory, 3 files
First make output:
$$< = hello1.c $? = hello1.c hello2.c $$@ = all $$^ = hello1.c hello2.c touch all
Second execution: touch hello1.c; make output:
$$< = hello1.c $? = hello1.c $$@ = all $$^ = hello1.c hello2.c touch all
Common Commands
-
make -s Silence all command executions (not silent output, but silent commands)
one: echo "hello"
make execution output
echo "hello" hello
make -s output
hello
-
make -i Ignore errors and continue running
tree: one two echo "hello tree" one: # throw an error hello one cannot print false echo "hello one" two: echo "hello two"
make output
# throw an error hello one cannot print false make: *** [one] Error 1
make -i output
# throw an error hello one cannot print false make: [one] Error 1 (ignored) echo "hello one" hello one echo "hello two" hello two echo "hello tree" hello tree
-
make -k Ignore prerequisite target errors and continue executing other prerequisite errors
tree: one two echo "hello tree" one: # throw an error hello one cannot print false echo "hello one" two: echo "hello two"
make output
# throw an error hello one cannot print false make: *** [one] Error 1
make -k
# throw an error hello one cannot print false make: *** [one] Error 1 echo "hello two" hello two make: Target `tree' not remade because of errors.
Default Implicit Rules
Since the purpose of Makefile is to manage dependencies in C projects, it defines a set of magical rules to simplify the writing of Makefiles. Of course, these rules can cause a lot of confusion.
# Makefile all:
Execution: make (1) all defaults to prerequisite as all.o and executes the corresponding compilation command $(CC) $(LDFLAGS) $^ $(LOADLIBES) $(LDLIBS) -o $@
(2) all.o’s prerequisite is all.c all.cc all.cpp (priority order, if there is a c suffix, cc will not be selected. If there is cc, cpp will not be selected) and executes the corresponding compilation command $(CXX) -c $(CPPFLAGS) $(CXXFLAGS) $^ -o $@
(3) If all.c, all.cc, all.cpp do not exist in the current directory, it will check if there are rules for all.o or all.c, all.cc, all.cpp in the Makefile.
Case 1
Prove that the all target implicitly depends on all.o, and all.o implicitly depends on all.c, all.cc, all.cpp
Directory as follows:
. ├── Makefile └── all.c
# Makefile all:
After executing the command, the output is:
learnMake make c++ all.cpp -o all
Actually, the above Makefile is equivalent to the following
# Makefile all: all.o $(CC) $(LDFLAGS) $^ $(LOADLIBES) $(LDLIBS) -o $@ # General rule for compiling .o files from .c, .cc, or .cpp files all.o: all.c $(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@ all.o: all.cc $(CXX) $(CPPFLAGS) $(CFLAGS) -c $< -o $@ all.o: all.cpp $(CXX) $(CPPFLAGS) $(CFLAGS) -c $< -o $@
Case 2
Prove that the command is optional from %.c to %.o, and both %.c to target commands are optional, and Makefile will execute implicitly
Directory as follows:
. ├── Makefile └── all.c
# Makefile all: all.o # General rule for compiling .o files from .c, .cc, or .cpp files all.o: all.c
Case 3
If there are target.c or target.o files in the directory or if the Makefile has corresponding targets, then they will be selected by default
. ├── Makefile └── all.c
# By default, all.o will be selected even if you do not write it. # Then all.o's prerequisite is all.c, and since all.c exists in the directory, it will automatically trigger all: hell.o hell.c: echo "" > hell.c clean: -rm all hell.c hell.o
Output:
make echo "" > hell.ccc -c -o hell.o hell.ccc all.c hell.o -o all
This situation also applies when the target is *.o
# Since the rule or hell.c exists in the directory, it will automatically include hell.c and all.c to compile to hell.o all.o: all.c # Generate a default hell.c hell.c: touch "hell.c"
Output
make hell.o touch "hell.c" cc -c -o hell.o hell.c
PHONY Usage
If you want to shield a rule, you can declare the target as .PHONY
all: .PHONY: all
After declaring, all will not be treated as a file target (even if there is a local all file, it will not check the newness of all) but as a pure intent target, and it will not be associated with the default implicit rules of C compilation.
. ├── Makefile └── all.c 1 directory, 2 files
all: .PHONY: all
make output
make: Nothing to be done for `all'.
If we remove PHONY
all:
make output
cc all.c -o all
Environment Variables
When running make, our environment variables will be converted into Makefile embedded variables. To increase the didactic nature, here are some prerequisites.
# Makefile all: @# Here outputs the shell environment variable @echo $$testMyVar @# Here outputs the shell environment variable converted to Makefile variable @echo $(testMyVar)
export testMyVar=123; make
output
123123
Here is an explanation of why the shell environment variable is output using $$
while the Makefile variable uses $( )
. In Makefile, if you want to escape $
for output, you need to concatenate a $
in front, i.e., $$
. echo
is a shell command, and if you want to pass a raw $variable_name
to the shell, you need to escape the preceding $
.
To help everyone understand this knowledge point better, I provided a more complex example:
# Only set Makefile environment variable myvar1 = hello myvar1 # Set environment variable and Makefile variable export myvar2 = hello myvar2 all: @# Due to printing Makefile variable, there is output hello myvar1 @echo "$$(myvar1)" = $(myvar1) @# Due to printing environment variable, but myvar1 is set as Makefile variable, so no output @echo "$$$(myvar1)" = $$myvar1 @# myvar2 variable is an environment variable, Makefile variable has output hello myvar2 @echo "$$(myvar2)" = $(myvar2) @# myvar2 variable is an environment variable, Makefile variable has output hello myvar2 @echo "$$$(myvar2)" = $$myvar2 .PHONY: all
Output:
$(myvar1) = hello myvar1 $$(myvar1) = $(myvar2) = hello myvar2 $$(myvar2) = hello myvar2
The characteristics of environment variables are very important for sub Makefile
tasks. Environment variables can be passed to sub-make tasks.
# Makefile # This variable will be sensed in the sub-make and output export myExportvar = hello myExportvar # This variable cannot be sensed in the sub-make mynonExportvar = hello mynonExportvar new_contents = "hello:
@echo myExportvar=
@echo mynonExportvar=" all: @mkdir -p subdir @printf $(new_contents) | sed -e 's/^ //' > subdir/makefile @cd subdir && $(MAKE) clean: rm -rf subdir
The above make run will create a makefile file in subdir and allow
. ├── Makefile └── subdir └── makefile
# subdir/makefile hello: @echo myExportvar=$(myExportvar) @echo mynonExportvar=$(mynonExportvar)
Corresponding output:
myExportvar=hello myExportvar mynonExportvar=
Single and Double Quotes
-
In Makefile, single and double quotes have no effect when defining variables.
myvar = hello world myvar2 = "hello world2" myvar3 = 'hello world3' all: echo $(myvar) echo $(myvar2) echo $(myvar3)
Output:
echo hello world hello world echo "hello world2" hello world2 echo 'hello world3' hello world3
-
However, there is a difference when calling functions.
# Makefile myvar = hello world all: @# Single quotes will escape all special characters except $$, the string received by echo command is \\$(myvar) echo '$$(myvar)'=$(myvar) @# Double quotes will not escape special characters, we need to escape manually, the string received by echo command is \\$(myvar). Note we manually added an escape character to $ echo "$$(myvar)"=$(myvar)
echo '$(myvar)'=hello world $(myvar)=hello world echo "$(myvar)"=hello world $(myvar)=hello world
Variables
In Makefile, there are the following styles of variable assignment:
-
Recursive Variable Assignment :=
-
Simply Expanded Variable Assignment =
-
Conditional Variable Assignment ?=
-
Immediately Expanded Variable Assignment ::=
GNU variable style documentation
The fourth type, Immediately Expanded Variable Assignment, is used less frequently and is not supported by many make versions.
-
Recursive Variable Assignment
Recursive variable assignment refers to expanding the referenced variable during the use of this variable (only the first time).
ugh = Huh2? foo = $(bar) bar = $(ugh) ugh = Huh? all:; echo $(foo)
make output
echo Huh? Huh?
When executing all, ugh is Huh?, so the value used for expanding the foo variable is Huh?. This style contradicts our daily programming thinking and can lead to a dead recursion problem (self-referencing).
ugh = Huh2? foo = $(bar) $(foo) bar = $(ugh) ugh = Huh? all:; echo $(foo)
make output:
Makefile:2: *** Recursive variable `foo' references itself (eventually). Stop.
-
Simply Expanded Variable Assignment
This assignment is very close to our daily programming; when declaring this variable, it expands the referenced variable.
bar := hello # := will immediately expand the variable for evaluation. Now that bar is defined as hello, the result is hello world foo := $(bar) world bar := world all:; echo $(foo)
make output:
echo hello world hello world
This assignment allows self-referencing without causing a dead recursion problem.
bar := hello # := will immediately expand the variable for evaluation. Now that bar is defined as hello, the result is hello world foo := $(bar) world bar := world # Concatenating its own string foo := $(foo) $(foo) all:; echo $(foo)
Output
echo hello world hello world hello world hello world
-
Conditional Variable Assignment
This is quite simple; it only assigns a value to the variable if it has not been defined (note that it is defined, not whether it has a value).
bar := hello # Since bar is assigned hello, it cannot be reassigned bar ?= world # Since the variable has not been defined, it can be successfully assigned. Assuming there is a foo = before, it will not assign, as foo has not been assigned foo ?= world all:; echo $(bar) $(foo)
Overriding Command Line Variable Values
By default, the variable values passed to the make command are not allowed to change, only when you add override are they allowed to be overridden.
# Force override override bar := overrideBar # Without adding the keyword, it is not allowed to override foo := overrideBar foo all:; echo $(bar) $(foo)
make foo=123 bar=456 output
echo overrideBar 123 overrideBar 123
Using Wildcards %
Used for writing generalized capabilities:
hell: a.o b.o c.o d.o a.c: echo "int main(){return 0;}" > $@ # Use % wildcard to achieve generalization. %.o: %.c $(CC) -c $^ -o $@ # Use % wildcard to achieve generalization, quickly create required C files %.c: touch $@
References
why_makefile
makefiletutorial