目录
01-符号=
、符号:=
、符号+=
区分符号=
和符号:=
在 Makefile
中,符号 :=
和 =
都用于定义变量,但它们的行为方式不同,主要体现在变量值的计算和展开时机上。
1. :=
(简单赋值,立即展开)
- 特点:使用
:=
定义的变量会在赋值时立即计算和展开右边的值,然后将结果赋给变量。 - 用途:当变量的值在定义时已经确定,并且不会再依赖于其他变量的值发生变化时,适合使用
:=
。
示例:
FOO := $(BAR)
BAR := hello
- 1
- 2
- 这里
FOO
的值在定义时就会被计算。当FOO := $(BAR)
被执行时,BAR
的值(hello
)被立即展开并赋值给FOO
。 - 最终结果:
FOO = hello
2. =
(递归赋值,延迟展开)
- 特点:使用
=
定义的变量不会立即计算右边的值,而是延迟到变量被引用时才计算和展开右边的内容。 - 用途:当变量的值可能依赖于其他变量的值发生变化时,适合使用
=
。
示例:
FOO = $(BAR)
BAR = hello
- 1
- 2
- 这里
FOO
的值在定义时不会被计算,而是保留$(BAR)
作为变量的值。 - 当
FOO
被引用时,BAR
的值才会被计算并展开。 - 最终结果:
FOO = hello
3. 两者的区别总结
符号 | 行为 | 展开时机 | 典型用途 |
---|---|---|---|
:= | 简单赋值,立即计算和展开 | 定义时展开 | 值已确定、不依赖其他变量的情况 |
= | 递归赋值,保留引用,延迟展开 | 使用时展开 | 依赖于其他变量或需要动态计算的情况 |
4. 对比示例
BAR = hello
FOO = $(BAR)
BAZ := $(BAR)
BAR = world
all:
@echo FOO = $(FOO)
@echo BAZ = $(BAZ)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 结果:
FOO = world BAZ = hello
- 1
- 2
解释:
FOO
使用了=
定义,它延迟展开,因此FOO
的值在BAR
被重新赋值后变成world
。BAZ
使用了:=
定义,它在赋值时立即展开BAR
的值,因此固定为hello
。
5. 性能方面的考虑
:=
性能更高:由于变量值在定义时已展开,后续使用时无需再进行计算。=
更灵活:允许变量的值依赖其他变量动态变化。
总结建议
- 使用
:=
:如果变量的值是固定的,或在定义时已确定,可以使用:=
提高效率和简洁性。 - 使用
=
:如果变量的值需要动态变化,或依赖于其他变量的值,使用=
提供更多的灵活性。
根据具体需求选择适当的赋值方式,可以编写更高效、清晰的 Makefile
!
符号+=
符号+=
为追加赋值,变量的值会被追加到现有的值后面。
比如:
FOO := hello
FOO += world
- 1
- 2
上面这条两条语句使得变量FOO的值为 hello world
02-需要知道的几个基本常识
02-1-Makefile文件并不是顺序执行,而是先解析再执行
关于这一点的最直接的例子就是伪目标的定义和声明语句:
PHONY := __build
......
......
......
.PHONY : $(PHONY)
- 1
- 2
- 3
- 4
- 5
变量PHONY用于存储Makefile中的所有伪目标名,但是直到文Makefile的最后一行才对存储在变量PHONY中的所有伪目标名进行声明,但是中间的语句会用到这些伪目标名,这就充分证明了Makefile文件并不是顺序执行,而是先解析再执行。
关于伪目标的详细解释,请看本文第03点的第2小点的第⑵点。
02-2-make命令执行时到底是执行哪个Makefile文件
如果make命令是下面这种形式:
make 目标名
- 1
那么make就会进入当前目录查找名为Makefile的文件开始执行构建。
如果make命令是下面这种形式:
make -C ./ -f $(TOPDIR)/Makefile.build
或者
make -C $@ -f $(TOPDIR)/Makefile.build
- 1
- 2
- 3
此时相当于指定了具体是执行哪个Makefile文件,-f 参数就是指定体是执行哪个Makefile文件的参数,在这里具体的Makefile文件是$(TOPDIR)/Makefile.build
02-3-make命令如果没有指定目标,那么执行哪一个目标?
这一点比较重要,请参看后面对这个问题的介绍,往后。搜索“如果make命令没有指定目标,那么执行哪一个目标”
04-规则的概念和介绍
规则是Makefile中执行具体动作的结构,是Makefile中最重要的结构。
Makefile
中的每个规则通常包含以下部分:
target: dependencies
command
- 1
- 2
-
target(目标):这是规则中的第一个部分,表示你希望
make
构建或更新的文件或动作。它可以是一个文件,也可以是一个伪目标(如all
,clean
等),伪目标不代表文件,只表示某些特定操作。关于目标,下文有详细介绍。 -
dependencies(依赖项):这些是目标所依赖的文件或其他目标。
make
会根据这些依赖项的时间戳来判断目标是否需要重新生成。如果任何依赖项的时间戳比目标的时间戳更新,make
就会执行该规则的命令来更新目标。 -
command(命令):这是一个或多个命令,通常是构建目标所需执行的操作。例如,编译源文件、复制文件或删除文件。命令通常以
TAB
(制表符)开头。
05-目标(target)的概念和介绍
在 Makefile
中,目标(target) 是最重要的结构之一。目标是 Makefile
中用于执行操作的核心单元,它表示一个文件(通常是最终生成的文件),或者一个操作的步骤。在 make
运行时,它会根据目标的依赖关系来确定哪些目标需要更新或执行。
1. 目标的基本结构
目标是Makefile
中的每个规则中的第一个部分。
是Makefile
中的每个规则通常包含以下部分:
target: dependencies
command
- 1
- 2
-
target(目标):这是规则中的第一个部分,表示你希望
make
构建或更新的文件或动作。它可以是一个文件,也可以是一个伪目标(如all
,clean
等),伪目标不代表文件,只表示某些特定操作。 -
dependencies(依赖项):这些是目标所依赖的文件或其他目标。
make
会根据这些依赖项的时间戳来判断目标是否需要重新生成。如果任何依赖项的时间戳比目标的时间戳更新,make
就会执行该规则的命令来更新目标。 -
command(命令):这是一个或多个命令,通常是构建目标所需执行的操作。例如,编译源文件、复制文件或删除文件。命令通常以
TAB
(制表符)开头。
2. 目标的类型
在 Makefile
中,目标主要有两种类型:
(1) 文件目标
文件目标是 Makefile
中最常见的目标类型。它表示一个文件,make
会检查该文件的依赖项,决定是否需要重新生成这个文件。例如:
program: main.o utils.o
gcc -o program main.o utils.o
- 1
- 2
program
是目标,表示最终生成的可执行文件。main.o
和utils.o
是该目标的依赖项,表示目标program
依赖这两个目标的更新。- 命令
gcc -o program main.o utils.o
用于生成program
。
在这个例子中,make
会检查 main.o
和 utils.o
的修改时间。如果它们比 program
新,make
就会执行命令来重新生成 program
。
(2) 伪目标
伪目标是没有实际文件对应的目标。它们用于执行特定的操作或命令,通常用来管理 Makefile
的构建过程。例如:
.PHONY: clean
clean:
rm -f *.o program
- 1
- 2
- 3
- 4
.PHONY
是一个特殊的伪目标,告诉make
该目标不对应于文件,而是一个纯粹的操作。clean
目标用于清理构建过程中生成的文件,make clean
会执行命令rm -f *.o program
删除所有.o
文件和program
可执行文件。
伪目标的必要性
如果你没有告诉make某个目标是伪目标,比如上面的clean目标,它就会认为clean目标是一个文件目标,此时如果当前目录中存在一个名叫clean的文件,那么make就会认为这个文件视为已经是“最新的”,从而跳过重新构建,也就是跳过对clean目标中command(命令)的执行。而如果你告诉了make,clean是一个伪目标,那它就知道这条目标与目录中的文件无关,从而不管怎么样,都可以执行目标中的command(命令)。
再说具体点,如果你没有声音clean是一个伪目标,你本想执行make clean
命令来清除所有 .o
文件和 program
可执行文件,结果却因为目录中有一个名叫clean的文件而导致clean目标中的命令被跳过执行,从而达到自己想要的结果。
常见的伪目标
all
:通常用于表示默认的构建目标。它可能依赖于多个其他目标,通常作为make
执行时的默认目标。clean
:用于删除构建过程中生成的中间文件(例如.o
文件)或输出文件。install
:通常用于安装程序或文件到特定位置。test
:用于执行测试。
伪目标的常见定义和声明形式
PHONY := __build
......
......
......
.PHONY : $(PHONY)
- 1
- 2
- 3
- 4
- 5
变量PHONY用于存储Makefile中的所有伪目标名,但是直到文Makefile的最后一行才对存储在变量PHONY中的所有伪目标名进行声明,但是中间的语句会用到这些伪目标名,这就充分证明了Makefile文件并不是顺序执行,而是先解析再执行。
3. 目标的依赖关系
目标可以有多个依赖项,make
会根据这些依赖项的状态来判断目标是否需要更新。如果目标的依赖项发生变化(例如被修改或重新生成),则目标会被重新构建。例如:
all: program
program: main.o utils.o
gcc -o program main.o utils.o
main.o: main.c
gcc -c main.c
utils.o: utils.c
gcc -c utils.c
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
all
是伪目标,依赖program
目标。program
依赖main.o
和utils.o
,这些是编译的中间目标。main.o
和utils.o
分别依赖于它们各自的源文件(main.c
和utils.c
)。
make
会根据文件的时间戳来决定是否需要执行命令。如果 main.c
或 utils.c
比 main.o
和 utils.o
更新,make
会重新编译源文件,生成相应的目标。
4. 如果make命令没有指定目标,那么执行哪一个目标?
如果make命令没有指定目标,那么会默认执行 Makefile
中的第一个目标。如果第一个目标在后面有同名定义,那么会后面的会覆盖前面的目标,但是第一个目标仍然是这个同名目标,只是执行的是这个同名目标后面的定义。
比如下面这些Makefile语句:
__build:
all:start_recursive_build $(TARGET)
__build:$(subdir-y) built-in.o
- 1
- 2
- 3
- 4
- 5
第一个目标是__build,所以make没有指定目标时会执行目标__build,但由于__build在后面还有定义,后面的覆盖了之前的,所以真正执行的是后面的__build目标,即:
__build:$(subdir-y) built-in.o
- 1
在这个过程中,all目标并不会执行。
6. 同名目标是怎么处理?到底执行哪一个?
在 Makefile 中,同一个目标名可以出现多次,此时make 会覆盖先前定义的目标,并只执行最后一个规则。
例如,假设你有以下 Makefile
:
target:
echo "This is the first rule"
target:
echo "This is the second rule"
- 1
- 2
- 3
- 4
- 5
make
会执行第二个 target
规则(打印 “This is the second rul”),并忽略第一个定义的 target
规则。
又例如:
__build:
# 空规则
__build: touch example
- 1
- 2
- 3
make 会最终使用最后的 __build
目标定义。
关于上面命令中的touch的介绍见博文 Makefile中遇到的touch命令是怎么回事儿?
7. 目标的总结
- 目标是
Makefile
的基本构建单元,用于表示你想要构建或更新的内容。 - 目标可以是文件或伪目标,伪目标通常用于执行某些特定的操作(如清理、安装等)。
- 目标的依赖项决定了它是否需要更新,
make
会根据依赖关系自动更新目标。 make
是基于时间戳来判断目标是否过时,如果目标的依赖项比目标本身更新,make
会重新执行目标的命令。
通过合理使用目标、依赖关系和命令,可以高效地管理和自动化项目的构建过程。
06-几个比较重要的约定俗成的变量
第01个:obj-y
关于这个变量的介绍,详见我的另一篇博文:
http://iyenn.com/rec/1709618.html【搜索“obj-y”】
第02个:obj-m
关于这个变量的介绍,详见我的另一篇博文:
http://iyenn.com/rec/1709543.html
第03个: obj-$(CONFIG_TOUCHSCREEN_GT9XX)
来源:http://iyenn.com/rec/1709269.html
这条 Makefile 语句的意思是在编译内核模块时,依据 CONFIG_TOUCHSCREEN_GT9XX
配置选项,动态地决定是否编译 gt9xx/
目录中的代码。
解释:
-
obj-$(CONFIG_TOUCHSCREEN_GT9XX)
: 这是一个条件表达式。CONFIG_TOUCHSCREEN_GT9XX
是一个内核配置项,它通常在内核配置(如.config
文件)中定义。如果这个配置项被启用(即CONFIG_TOUCHSCREEN_GT9XX
的值为y
或m
),那么obj-$(CONFIG_TOUCHSCREEN_GT9XX)
就会被展开成obj-y
或obj-m
,从而指示需要编译相应的代码。 -
+= gt9xx/
: 这意味着将gt9xx/
目录添加到需要编译的目标中。具体来说,如果CONFIG_TOUCHSCREEN_GT9XX
被启用,gt9xx/
目录下的代码将被包含进最终的编译目标中。
具体工作流程:
- 如果在
.config
配置文件中,CONFIG_TOUCHSCREEN_GT9XX
被设置为y
(表示编译为内核的一部分)或者m
(表示编译为模块),那么obj-$(CONFIG_TOUCHSCREEN_GT9XX)
就会展开成obj-y
或obj-m
。 += gt9xx/
会将gt9xx/
目录中的源文件添加到编译列表中。- 这意味着
gt9xx/
目录下的代码会被编译进内核或者被编译成内核模块,具体取决于CONFIG_TOUCHSCREEN_GT9XX
配置项的值。
小结:
如果你在内核配置中启用了 CONFIG_TOUCHSCREEN_GT9XX
,那么这条 Makefile 语句会确保 gt9xx/
目录下的代码被编译。如果没有启用这个配置选项,这部分代码就不会被编译。
07-几个重要的自动化变量:$@
、 $<
、 $^
在 Makefile
中,$@
、$<
和 $^
是自动变量,用于规则中命令部分,提供构建目标和依赖项的相关信息。这些自动变量可以让 Makefile
更加简洁、动态。以下是它们的详细介绍和用法。
1. $@
- 当前目标的名称
- 含义:
$@
表示当前规则中目标的完整名称。 - 用途:当需要在命令中引用目标文件时,可以使用
$@
。
示例
program: main.o utils.o
gcc -o $@ main.o utils.o
- 1
- 2
- 解释:
- 目标是
program
。 $@
的值是program
。- 最终命令等价于:
gcc -o program main.o utils.o
。
- 目标是
2. $<
- 第一个依赖项的名称
- 含义:
$<
表示规则中第一个依赖项的名称。 - 用途:通常用于隐式规则或显式规则中,引用第一个输入文件。
示例
%.o: %.c
gcc -c $< -o $@
- 1
- 2
- 解释:
- 这是一个模式规则,表示所有的
.o
文件都依赖于对应的.c
文件。 $<
的值是第一个依赖项(例如,file.c
)。$@
的值是目标文件(例如,file.o
)。- 最终命令等价于:
gcc -c file.c -o file.o
。
- 这是一个模式规则,表示所有的
3. $^
- 所有依赖项的列表
- 含义:
$^
表示规则中所有依赖项的完整列表,并且会去重。 - 用途:当需要在命令中引用所有依赖项时,可以使用
$^
。
示例
program: main.o utils.o
gcc -o $@ $^
- 1
- 2
- 解释:
- 目标是
program
。 $^
的值是main.o utils.o
(所有依赖项)。- 最终命令等价于:
gcc -o program main.o utils.o
。
- 目标是
4. 对比总结
自动变量 | 含义 | 典型用途 |
---|---|---|
$@ | 当前规则的目标文件名称 | 引用生成的目标文件 |
$< | 当前规则的第一个依赖项的名称 | 引用第一个输入文件,通常用于编译 |
$^ | 当前规则的所有依赖项的名称列表 | 引用所有依赖项,通常用于链接 |
5. 综合示例
以下是一个完整的 Makefile
示例,展示 $@
、$<
和 $^
的结合使用:
program: main.o utils.o
gcc -o $@ $^
main.o: main.c
gcc -c $< -o $@
utils.o: utils.c
gcc -c $< -o $@
clean:
rm -f program main.o utils.o
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
解释:
-
program
规则:- 目标是
program
。 $@
是program
。$^
是main.o utils.o
。- 命令:
gcc -o program main.o utils.o
。
- 目标是
-
main.o
和utils.o
规则:$@
是目标文件,例如main.o
或utils.o
。$<
是第一个依赖项,例如main.c
或utils.c
。- 命令:
gcc -c main.c -o main.o
gcc -c utils.c -o utils.o
。
-
clean
规则:- 没有依赖项。
- 删除生成的目标文件和中间文件。
6. 自动变量常见用途
场景 | 使用的自动变量 | 示例命令 |
---|---|---|
生成目标文件 | $@ | gcc -o $@ $^ |
使用第一个依赖 | $< | gcc -c $< -o $@ |
使用所有依赖 | $^ | ar rcs $@ $^ |
7. 注意事项
- 依赖项列表去重:
$^
会去除重复的依赖项。 - 隐式规则中常用
$<
和$@
:例如.o
文件生成规则,通常是基于.c
文件的。 - 手动管理依赖项时需注意顺序:依赖项的顺序可能会影响构建的结果。
通过灵活运用这些自动变量,可以显著减少重复代码,让 Makefile
更加简洁高效!
08-include语句
假设文件Makefile.build中有下面这句话:
include Makefile
- 1
Makefile中的include语句是把被包含的文件的内容包含当前文件中,类似于C语言中的include关键词。
但是使用时要特别注意避免陷入无限循环的死循环中。
详情见我的另一篇博文
Makefile中使用include语句时要特别注意避免陷入无限循环的死循环中
98-实际示例
详见下面这篇博文
http://iyenn.com/rec/1709676.html
99-一些不是很重要的知识
ZZ-01-注释符
注释符是#
,与Python相同。



评论记录:
回复评论: