首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

读懂和编写Makefile需掌握的关键基础知识(常用符号、先解析再执行、make命令的第一个动作、规则的概念、目标及伪目标的相关概念、文件更新机制、几个重要的自动化变量等知识)

  • 25-03-05 05:21
  • 2992
  • 7589
blog.csdn.net

目录

  • 01-符号`=`、符号`:=`、符号`+=`
    • 区分符号`=`和符号`:=`
      • 1. **`:=`(简单赋值,立即展开)**
      • 2. **`=`(递归赋值,延迟展开)**
      • 3. **两者的区别总结**
      • 4. **对比示例**
      • 5. **性能方面的考虑**
      • 总结建议
    • 符号`+=`
  • 02-需要知道的几个基本常识
    • 02-1-Makefile文件并不是顺序执行,而是先解析再执行
    • 02-2-make命令执行时到底是执行哪个Makefile文件
    • 02-3-make命令如果没有指定目标,那么执行哪一个目标?
  • 04-规则的概念和介绍
  • 05-目标(target)的概念和介绍
      • 1. 目标的基本结构
      • 2. 目标的类型
        • (1) 文件目标
        • (2) 伪目标
        • 伪目标的必要性
        • 常见的伪目标
        • 伪目标的常见定义和声明形式
      • 3. 目标的依赖关系
      • 4. 如果make命令没有指定目标,那么执行哪一个目标?
      • 6. 同名目标是怎么处理?到底执行哪一个?
      • 7. 目标的总结
  • 06-几个比较重要的约定俗成的变量
    • 第01个:obj-y
    • 第02个:obj-m
    • 第03个: obj-$(CONFIG_TOUCHSCREEN_GT9XX)
  • 07-几个重要的自动化变量:`$@` 、 `$<` 、 `$^`
      • **1. `$@` - 当前目标的名称**
        • **示例**
      • **2. `$<` - 第一个依赖项的名称**
        • **示例**
      • **3. `$^` - 所有依赖项的列表**
        • **示例**
      • **4. 对比总结**
      • **5. 综合示例**
        • **解释**:
      • **6. 自动变量常见用途**
      • **7. 注意事项**
  • 08-include语句
  • 98-实际示例
  • 99-一些不是很重要的知识
    • ZZ-01-注释符

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/ 目录下的代码将被包含进最终的编译目标中。

具体工作流程:

  1. 如果在 .config 配置文件中,CONFIG_TOUCHSCREEN_GT9XX 被设置为 y(表示编译为内核的一部分)或者 m(表示编译为模块),那么 obj-$(CONFIG_TOUCHSCREEN_GT9XX) 就会展开成 obj-y 或 obj-m。
  2. += gt9xx/ 会将 gt9xx/ 目录中的源文件添加到编译列表中。
  3. 这意味着 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
解释:
  1. program 规则:

    • 目标是 program。
    • $@ 是 program。
    • $^ 是 main.o utils.o。
    • 命令:gcc -o program main.o utils.o。
  2. 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。
  3. clean 规则:

    • 没有依赖项。
    • 删除生成的目标文件和中间文件。

6. 自动变量常见用途

场景使用的自动变量示例命令
生成目标文件$@gcc -o $@ $^
使用第一个依赖$<gcc -c $< -o $@
使用所有依赖$^ar rcs $@ $^

7. 注意事项

  1. 依赖项列表去重:$^ 会去除重复的依赖项。
  2. 隐式规则中常用 $< 和 $@:例如 .o 文件生成规则,通常是基于 .c 文件的。
  3. 手动管理依赖项时需注意顺序:依赖项的顺序可能会影响构建的结果。

通过灵活运用这些自动变量,可以显著减少重复代码,让 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相同。

昊虹嵌入式技术交流群
QQ群名片
注:本文转载自blog.csdn.net的昊虹AI笔记的文章"https://blog.csdn.net/wenhao_ir/article/details/144556756"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2492) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

101
推荐
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2025 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top