1. Why do Makefiles exist?
In software or chip development, Makefile is generally used. It is a text file that contains instructions on how to compile and link programs. Makefile is used by the make tool, which is an automation build tool that can automatically execute the compilation and linking process based on the instructions in the Makefile.
The main roles of Makefile in chip development include:
-
Automating the compilation process: Makefile can specify the source code files to be compiled, the compiler, and the compilation options. This allows chip developers to easily compile the entire project without manually executing each compilation step. -
Managing dependencies: Makefile can specify the dependencies between source files and target files. This allows the make tool to determine which files need to be recompiled, thus optimizing the compilation process and saving time. -
Linking target files: Makefile can specify the target files to be linked, the linker, and the linking options. This allows chip developers to easily link multiple target files into a single executable file or library. -
Executing tests and simulations: Makefile can include rules for executing test and simulation scripts. This allows chip developers to automate the testing and simulation process, thus improving development efficiency.
2. Starting with the simplest Makefile
1. Syntax of Makefile
targets: prerequisites
command
command
command
Here, targets are the names of the instructions we want to execute, prerequisites are the conditions that need to be met before executing the instructions, and command is the instruction we want to execute. Note that the command must be preceded by a tab (multiple tabs are also acceptable), but spaces cannot be used.
hello:
echo "Hello, World"
Executing make will yield the following result:

echo "Hello, World"
For the echo command, if we only want to see the execution result without seeing the echoed result, we can use @echo:
hello:
@echo "Hello, World"
If the command is preceded by a space, the execution will prompt:
*** missing separator. Stop.
2. Multiple targets
hello1:
echo "Hello1, World"
hello2:
echo "Hello2, World"
3. Leading and trailing spaces
In a Makefile, trailing spaces are not removed, while leading spaces are ignored. This means that if you need to create a variable that contains a single space, directly using a space in the variable assignment may lead to unexpected results due to the ignored leading space. To solve this problem, you can use the $(nullstring) special variable to ensure that the single space is correctly included in the variable value.
with_spaces = hello # with_spaces has many spaces after "hello"
after = $(with_spaces)there
nullstring =
space = $(nullstring) # Make a variable with a single space.
all:
echo "$(after)"
echo start"$(space)"end
The running result is as follows:

4. Multiline
\
symbol:some_file:
echo This line is too long, so \
it is broken up into multiple lines
5. .PHONY
some_file:
touch some_file
touch clean
.PHONY: clean
clean:
rm -f some_file
rm -f clean
6. make clean
compile:
touch compile_files
run:
touch run_files
clean:
rm -f compile_files run_files
compile: clean
touch compile_files
run:
touch run_files
clean:
rm -f compile_files run_files
3. Variables in Makefile
In Makefile, variables can only be of string type. Let’s look at some variable assignment operations and distinguish between ‘=’, ‘:=’, ‘?=’ and ‘+=’.
-
= (Normal assignment): When assigning a variable using the equal sign (=), it defines a recursively expanded variable. This means that when the variable is defined, Makefile does not immediately expand its value but treats it as a macro to be processed later, only expanding it when the variable is referenced in subsequent rules or expressions.
-
:= (Immediate assignment): When assigning with the colon equal sign (:=), it defines a simply expanded variable. This assignment method immediately expands and replaces all variable references known at the time of definition, without retaining any pending macros. Once the assignment is complete, the variable’s value is fixed and no longer affected by subsequent variable definitions.
-
?= (Conditional assignment): When assigning with the question mark equal sign (?=), it will only assign the specified value if the variable has not been assigned yet (i.e., is undefined or empty). If the variable already has a non-empty value, this assignment operation will be ignored.
-
+= (Append assignment): When you assign a value to a variable using +=, the newly specified value will be added to the end of the variable’s current value, effectively concatenating the two values. This operation is particularly useful for accumulating or adding a series of related values, such as gradually collecting compilation options, source file lists, link library paths, etc.
one = one ${later_variable}
two := two ${later_variable}
later_variable = later
all:
echo $(one)
echo $(two)
The running result is as follows:

As we can see, later_variable is defined later, so when one is applied, it gets the value later, while two is assigned at the beginning and at that time later_variable has not yet been assigned, so the later_variable referenced in two is empty.
one = hello
one ?= will not be set
two ?= will be set
all:
echo $(one)
echo $(two)
The result of executing make is as follows:


foo := start
foo += more
all:
echo $(foo)
The result is as follows:
start more
For undefined variables, they are just empty strings.
all:
# Undefined variables are just empty strings!
echo $(nowhere)
?=
allows us to specify the value of a variable when executing the make command. If we use =
to assign a value to a variable, can we still overwrite this variable from the terminal? override
keyword. We execute: make all option_one=123, and we can overwrite.# Overrides command line arguments
override option_one = did_override
# Does not override command line arguments
option_two = not_override
all:
echo $(option_one)
echo $(option_two)
4. Each line is a separate shell
One important thing to note in Makefile is that each line is a separate shell; variables defined in the previous line are not valid in the next line. Let’s compare the following two Makefile styles:
all:
@export foo=hello
@echo $${foo}
Style two:
all:
@export foo=hello; echo $${foo}
hello
.This is because in the first style, export foo=hello
and echo $${foo}
are executed in two separate shells, thus the second shell cannot access the environment variable defined in the first shell.
In the second style, defining and accessing in the same line works correctly.
all:
@export foo=hello \
echo $${foo}
5. Usage of $$ symbol
As we used echo $${foo}
, why do we need to use two ? The $$ symbol is mainly used to convert references in Makefile to shell references.
When first encountering Makefile, it can be somewhat confusing.
First, it is essential to clarify that executing the make command does not occur in a shell environment; the shell is only executed when a particular operation in the Makefile is executed.
foo = one two three
all:
for i in $(foo); do \
echo $i; \
end
After preprocessing by make, it becomes:
for i in one two three; do \
echo ; \
end
foo = one two three
all:
for i in $(foo); do \
echo $$i; \
end
This is because the $$i command is translated by make to the shell’s $i, and at this point, the value of i in the shell is one two three.
6. Wildcards
In Makefile, * and % are both wildcards. Let’s look at their usage.
print: $(wildcard *.c)
ls -la $?
It is essential to note that when using the * symbol, it must be used in conjunction with the wildcard keyword.
thing_wrong := *.o # Don't do this! '*' will not get expanded
thing_right := $(wildcard *.o)
all: one two three four
# Fails, because $(thing_wrong) is the string "*.o"
one: $(thing_wrong)
# Stays as *.o if there are no files that match this pattern :(
two: *.o
# Works as you would expect! In this case, it does nothing.
three: $(thing_right)
# Same as rule three
four: $(wildcard *.o)
7. Runtime added commands
When running Make, you can use the following commands to improve our debugging efficiency:
-n
: Displays the commands to be executed in the terminal but does not execute them, allowing us to check whether the commands to be executed are correct.
one:
false1
false2
@echo "this is one"

-i
: Displays all errors that occur during Make execution; otherwise, it will stop at the first error.

-
: In the Makefile, this also achieves the same function as -i
, but only for that line. Transform the Makefile into the following:
one:
-false1
-false2
@echo "this is one"
After executing make one, it will display the same functionality as -i
:

8. Conditional statements
foo = ok
all:
ifeq ($(foo), ok)
echo "foo equals ok"
else
echo "nope"
endif
Check if a variable is empty:
nullstring =
foo = $(nullstring) # end of line; there is a space here
all:
ifeq ($(strip $(foo)),)
echo "foo is empty after being stripped"
endif
ifeq ($(nullstring),)
echo "nullstring doesn't even have spaces"
endif
Check if a variable is defined:
bar =
foo = $(bar)
all:
ifdef foo
echo "foo is defined"
endif
ifndef bar
echo "but bar is not"
endif
9. Functions in Makefile
Makefile has many built-in functions for us to call directly:
1. subst function
bar := ${subst not,totally, "I am not superman"}
all:
@echo $(bar)

comma := ,
empty:=
space := $(empty) $(empty)
foo := a b c
bar := $(subst $(space),$(comma),$(foo))
all:
@echo $(bar)
The result is:
a,b,c
comma := ,
empty:=
space := $(empty) $(empty)
foo := a b c
bar := $(subst $(space), $(comma) , $(foo))
all:
@echo $(bar)
The result is:
, a , b , c
2. patsubst function
foo := a.o b.o l.a c.o
one := $(patsubst %.o,%.c,$(foo))
# This is a shorthand for the above
two := $(foo:%.o=%.c)
# This is the suffix-only shorthand, and is also equivalent to the above.
three := $(foo:.o=.c)
all:
@echo $(one)
@echo $(two)
@echo $(three)
The result is:

3. foreach function
foo := who are you
# For each "word" in foo, output that same word with an exclamation after
bar := $(foreach wrd,$(foo),$(wrd)!)
all:
# Output is "who! are! you!"
@echo $(bar)
4. if function
foo := $(if this-is-not-empty,then!,else!)
empty :=
bar := $(if $(empty),then!,else!)
all:
@echo $(foo)
@echo $(bar)
5. call function
sweet_new_fn = Variable Name: $(0) First: $(1) Second: $(2) Empty Variable: $(3)
all:
# Outputs "Variable Name: sweet_new_fn First: go Second: tigers Empty Variable:"
@echo $(call sweet_new_fn, go, tigers)
6. shell function
all:
@echo $(shell ls -la) # Very ugly because the newlines are gone!
Many examples in this article are sourced from: Makefile Tutorial By Example

END
Source: Cat of Fourier
Copyright belongs to the original author. If there is infringement, please contact to delete.
▍Recommended Reading
Comic | The Sad State of Chinese Software Development
A Huawei C Language Interview Question That Many People Failed!
A Hard Disk That Cannot Be Described Broke Down, But I Am Too Embarrassed to Fix It…