前言
笔者多年来一直从事嵌入式底层开发工作,每个项目无一例外都是使用make工具进行自动化编译工作的,make的重要性可见一斑。然而,笔者发现自己对make的认识还是比较粗浅,无法从0到1实现一个大型工程的自动化编译任务。为了早日成为嵌入式架构师,必须push自己对make进行更加深入的了解。
部分参考:https://soc.ustc.edu.cn/CECS/lab1/makefile/
一、make是什么?Makefile是什么?
make manual的介绍如下:
make - GNU make utility to maintain groups of programs
To prepare to use make, you must write a file called the makefile that describes the relationships among files in your program, and the states the commands for updating each file. In a program, typically the executable file is updated from object files, which are in turn made by compiling source files.
可见,make 是一个构建工具,用于自动化构建过程。它通过读取 Makefile 文件来执行构建过程中的一系列命令和规则,从而实现对源代码的编译、链接、打包等操作。
Makefile 是一个文本文件,其中包含了构建项目所需的所有规则和命令。Make 工具会根据 Makefile 中定义的规则来决定执行哪些操作,以及这些操作的执行顺序。
因此,Make 和 Makefile 之间的关系是密切相关的:
-
Make 是执行构建过程的工具:Make 读取 Makefile 文件,并根据其中的规则来执行构建操作。它负责解析 Makefile,执行相应的命令,并确保构建过程按照指定的顺序进行。
-
Makefile 包含了构建项目所需的规则和命令:Makefile 是一个文本文件,其中包含了构建项目所需的所有规则和命令。它定义了源文件、目标文件、依赖关系以及执行构建操作所需的命令。
通过这种方式,Make 和 Makefile 实现了自动化构建过程的管理和执行,大大提高了项目的开发效率和可维护性。
二、关键语法
1.内置函数
在Makefile中,$()
语法是用于执行内置函数的,其中包含了shell命令。$(shell ...)
的作用是执行括号中的shell命令,并将其结果返回给Makefile。
在其他上下文中,$()
也可以用于变量替换,即将括号中的变量名替换为变量的值。这种形式的 $()
是 Bash shell 中的命令替换语法。
在Makefile中,$(shell ...)
是GNU Make的内置函数,由GNU Make程序提供和解释。
GNU Make 中有许多内置函数可用于处理变量、执行命令、文件操作等。以下是一些常用的内置函数:
-
$(shell …): 执行 shell 命令并获取其输出结果。
-
$(wildcard pattern): 匹配文件模式,返回符合条件的文件列表。
-
$(foreach var, list, text): 遍历列表中的元素,并使用每个元素执行指定的操作。
-
$(subst from, to, text): 在文本中替换字符串。
-
$(patsubst pattern, replacement, text): 用于在文本中替换模式。
-
$(addprefix prefix, names): 将前缀添加到列表中的每个元素。
-
$(addsuffix suffix, names): 将后缀添加到列表中的每个元素。
-
$(join list1, list2): 将两个列表中的元素按照位置连接起来。
-
$(dir names): 获取文件名中的目录部分。
-
$(notdir names): 获取文件名中的文件部分。
-
$(abspath names): 将相对路径转换为绝对路径。
-
$(realpath names): 将路径解析为其规范的绝对路径。
-
$(shell cmd): 执行 shell 命令并获取其输出。
-
$(eval text): 执行文本中的 Makefile 代码。
-
$(strip string): 移除字符串两端的空格。
-
$(subst search, replace, text): 替换字符串。
-
$(filter pattern…, text): 从文本中筛选出符合模式的字符串。
-
$(filter-out pattern…, text): 从文本中筛选出不符合模式的字符串。
这些内置函数能够方便地处理变量、文件操作、文本处理等常见的任务,使得 Makefile 编写更加灵活和高效。
2.赋值运算符:=, =, ?=, +=
2.1 展开的概念
要从根本上区分这四种赋值运算符,特别是:=
和=
,需要对展开
有一个基本认识。
Makefile 的展开指的是变量在被使用时被替换为其定义的值的过程。当 Make 工具执行 Makefile 时,会对其中的变量进行展开,即将变量名替换为其定义的值。
2.2 :=
和=
在 Makefile 中,:= 和 = 是用来定义变量的两种不同方式,它们的主要区别在于变量的展开方式和生命周期。
-
:= 赋值:
-
使用
:=
进行赋值时,变量的值会在定义时立即展开(即进行变量展开),而不是在使用时展开。这意味着,如果在定义时使用了其它变量,它们会被立即展开为当前值,而不会延迟到变量使用的时候。 -
变量的生命周期是在整个 Makefile 中都有效的,即在 Makefile 任何地方都可以使用该变量。
-
例如:
# 定义变量 VAR := $(OTHER_VAR) # 在此处使用 VAR 时,$(OTHER_VAR) 会被立即展开为当前值
-
-
= 赋值:
-
使用
=
进行赋值时,变量的值会在使用时展开(即惰性展开),而不是在定义时展开。这意味着,如果在定义时使用了其它变量,它们会延迟到变量使用的时候再展开。 -
变量的生命周期是在定义点之后的整个 Makefile 中都有效的,即在 Makefile 的后续部分和子 Makefile 中都可以使用该变量。
-
例如:
# 定义变量 VAR = $(OTHER_VAR) # 在此处使用 VAR 时,$(OTHER_VAR) 会在使用时才被展开
-
因此,选择使用 :=
还是 =
取决于变量展开的时机和变量的作用范围。通常情况下,推荐优先使用 :=
,因为它能够更早地发现潜在的问题,并且提高了 Makefile 的执行效率。
2.3 ?= 和 +=
在 Makefile 中,?= 和 += 是两种用于定义变量的操作符,它们分别用于扩展和赋值。
-
?= 赋值操作符:
-
如果变量未被定义,则使用
?=
赋值操作符可以定义变量,并赋予其一个默认值。如果变量已经被定义,则不会对其进行重新赋值。 -
这个操作符通常用于设置变量的默认值,以防止变量被重复定义而导致的错误。
-
示例:
# 如果 FOO 变量未被定义,则将其赋值为 "default_value" FOO ?= default_value
-
-
+= 扩展操作符:
-
使用
+=
扩展操作符可以将值追加到变量的现有值之后,而不是覆盖掉原来的值。 -
这个操作符通常用于在原有的变量值的基础上添加额外的内容,例如添加额外的编译选项或文件列表。
-
示例:
# 将 "new_value" 追加到变量 FOO 的现有值之后 FOO += new_value
-
这两种操作符都可以在 Makefile 中灵活地使用,用于定义和扩展变量,从而实现对构建过程的定制和配置。
总结
`笔者目前只对赋值操作符和内建函数做了总结,他们两个是看懂Makefile的基础,非常重要。至于其他概念,如目标和依赖,编译,链接,打包等步骤需要开发者对编译原理有一个比较好的理解才能正真理解透彻。笔者在这方面还需要补习。