Makefile学习笔记
Makefile学习笔记
version : v1.0 「2023.4.12」 最后补充
author: Y.Z.T.
摘要: none
简介: 记录Make的简单特性 , 记录学习的过程
0️⃣ 前言
-
Make
是常用的构建工具, 一般是编译一个工程时使用的工具 。 -
用来描述哪些文件需要编译、哪些需要重新编译的文件就叫做
Makefile
,Makefile
带来的好处就是可以实现工程的全自动编译 -
make
是 一个命令工具,一个解释makefile
中指令的命令工具 -
从程序源码到可执行文件 (如
.out
文件) 的过程就是编译的过程吗,程序的整个编译流程大致分成 几个阶段 : 预处理、编译 、汇编 、链接。(可以参考我之前的一篇笔记程序编译过程)
1️⃣ Makefile概况
1.1 gcc与make命令
1.1.1 gcc常用参数
gcc 命令格式如下:
1 | gcc [选项] [文件名字] |
主要选项如下:
-
-c:只编译不链接为可执行文件,编译器将输入的
.c
文件编译为.o
的目标文件。 -
-o:<输出文件名>用来指定编译结束以后的输出文件名,如果不使用这个选项的话
GCC
默认编译出来的可执行文件名字为a.out
。 -
-g:添加调试信息,如果要使用调试工具(如
GDB
)的话就必须加入此选项,此选项指示编译的时候生成调试所需的符号信息。(生成debug
模式的可执行文件) -
-O:对程序进行优化编译,如果使用此选项的话整个源代码在编译、链接的的时候都会进
-
行优化,这样产生的可执行文件执行效率就高。
-
-O2:比
-O
更幅度更大的优化,生成的可执行效率更高,但是整个编译过程会很慢。
1.1.2 make常用参数
环境: GNU Make 4.3
常用参数
- -B : 认为所有的目标都需要更新(重编译),即强制编译所有目标。
- -f :指定
makefile
文件; - -I <dir>:当包含其他
makefile
文件时,利用该选项指定搜索目录; - -d:
Debug
模式,相当于--debug=a
输出有关文件和检测时间的详细信息。 - -i :忽略命令执行返回的出错信息;
- -r:禁止
make
使用任何隐含规则 - -t : 只是把目标的修改日期变成最新的,也就是阻止生成目标的命令运行。
1.2 Makefile概念
Makefile
文件是用来告诉make
命令怎么去编译和链接程序的 , Makefile的书写有以下 三个规则:
- 如果这个工程没有编译过,那么我们的所有 C 文件都要编译并被链接。
- 如果这个工程的某几个 C 文件被修改,那么我们只编译被修改的 C 文件,并链接目标程序。
- 如果这个工程的头文件被改变了,那么我们需要编译引用了这几个头文件的 C 文件,并链接成可执行文件.
注意:
Makefile
文件的文件名最好使用"Makefile
“或”makefile
" 。如果要指定命名为其他名字的Makefile
文件的话,可以使用make
的-f
和--file
参数,如:make -f Make.Linux
。
1.3 Makefile语法
Makefile
是由件由一系列规则(rules)构成。每条规则的形式如下:
1 | <target> : <prerequisites> |
target
是 目标文件 , 如.o
文件或.out
文件prerequisites
是 前置条件 , 即要生成那个target
文件 所需要的文件或是目标command
是make
需要执行的命令。(任意的Shell
命令) , 每条命令前面一定要加tab ,不能是空格。(make
命令会为Makefile
中的每个以TAB
开始的命令创建一个Shell
进程去执行。)
注意:
-
一个目标(target)就构成一条规则。目标通常是文件名,指明Make命令所要构建的对象, 目标可以是一个文
件名,也可以是多个文件名,之间用空格分隔。
-
其中第一条规则的目标成为 默认目标 , 也即是
make
命令编译生成的最终目标文件 -
目标 是必需的,不可省略;前置条件 和 命令 都是可选的,但是两者之中必须至少存在一个。
1.4 示例代码
以GUN make 中的一个Makefile
程序为例:
- 该文件描述了一个称为文本编辑器(
edit
)的可执行文件生成方法, 该文件依靠8个(.o文件
);- 这8个
.o
文件有依赖于8个源文件和3个头文件
1 | # 第一条规则说明 , edit这个目标依赖于后面8个.o文件 |
如上所示:
-
上述代码中一共有10个规则。1~4行为第一个规则, 也是 默认目标 即最终的目标文件
-
反斜杆
/
是换行符的意思, 这样比较便于Makefile
的易读。
因此由上述程序可以总结出make
命令工作的 执行流程:
-
make
会在当前目录下找名字叫Makefile
或makefile
的文件。 -
如果找到,它会找文件中的第一个目标文件(
target
),在上面的例子中,他会找到edit
这个文件,并把这个文件作为 最终的目标文件。 -
如果
edit
文件不存在,或是edit
所依赖的后面的.o
文件的文件修改时间要比edit
这个文件新,那么,他就会执行后面所定义的命令来生成edit
这个文件。 -
如果
edit
所依赖的.o
文件也不存在,或者目标所依赖的文件比目标文件新 (也就是最后修改时间比目标文件晚) 的话, 那么
make
会在当前文件中找目标为.o
文件的依赖性,如果找到则再根据那一个规则生成.o
文件。
这就是整个
make
的依赖性,make
会一层又一层地去找文件的依赖关系,直到最终编译出 第一个目标文件。
make
只管文件的依赖性, 在找寻的过程中,如果出现错误,比如最后被依赖的文件找不到,那么
make
就会直接退出,并报错。对于定义命令的错误,make
不会理会
2️⃣ 简化Makefile
2.1 变量
前面的例子只是一个最简单的Makefile程序, 我们可以使用变量来简化程序
- 在
Makefile
中 , 变量值可以使用在“ 目标”,前置条件,命令 或是Makefile
的其它部分中。 Makefile
中的变量其实就是C/C++
中的宏 , 当Makefile
被执行时, 变量都会被 扩展 到相应的引用位置上。- 变量的命名可以包含 字符、数字,下划线 (可以是数字开头),但不应该含有
:
#
、=
或是 空字符 (空格、回车等) - 变量是大小写敏感的 , 可以使用大小写搭配的变量名 , 如:
MakeFlags
,避免和系统的变量冲突
例: 以下两段代码是等同的
1 | edit : main.o kbd.o command.o display.o \ |
1 | #变量定义 |
2.1.1变量的使用
Makefile
中变量的引用方法是 $(变量名)
,比如本例中的$(objects)
就是使用变量 objects
。括号不加也可以, 但给变量加上括号完全是为了更加安全
地使用这个变量, 最好还是给变量加上括号.
2.1.1.1 变量赋值符
Makefile
一共提供了四个赋值运算符 =
、:=
、?=
、+=
2.1.1.1.1 赋值符 “=”
赋值符 =
是在 执行的时候 进行拓展 , 允许 递归拓展 .
所以在使用=
在给变量的赋值的时候,不一定要用已经定义好的值,也可以 使用后面定义的值
如下所示:
1 | a = $(b) |
输出结果:
解释:
- 赋值符
=
是在 执行的时候 才进行拓展 的 $(a)
的值是$(b)
,$(b)
的值是$(c)
,$(c)
的值是 "Hello"- 说明使用赋值符
=
的变量是可以使用后面的变量来进行定义的 , 也就是 变量的值 是取决于它所引用的变量的 最后一次有效值
注意:
- 因为赋值
=
是允许递归拓展的 , 所以有可能让make
陷入无限的变量展开过程中去 , 会让make
发出报错
1 | a = $(b) |
- 如果在 变量 中使用 函数 的话 , 会让
make
在运行时十分缓慢
2.1.1.1.2 赋值符 “:=”
为了避免使用 =
赋值符时 , 发送变量无限展开的情况 , 可以使用 赋值符 :=
赋值符 :=
是在 定义时 扩展。
使用赋值符 :=
的话 , 前面的变量不能使用后面的变量,只能使用前面已定义好了的变量。
如下所示:
1 | x := foo |
等价于
1 | y := foo bar |
可以看到前面的变量不能使用后面的变量,只能使用前面已定义好了的变量。
赋值符 “:=” 的一种特殊用法
操作符的右边是很难描述一个空格的, 但可以使用 赋值符
:=
来定义一个变量 , 其值是一个空格
1
2 nullstring :=
space := $(nullstring) # end of the line
nullstring
是一个Empty
变量 , 其值为空- 而
space
的值是一个空格- 先用一个
Empty
变量来标明变量的值开始了,而后面采用“#”
注释符来表示变量定义的终止
2.1.1.1.3 赋值符 “?=”
赋值符 ?=
只有在该变量为空时才设置值。
即当一个变量先前被定义过 , 那么这条命令将什么都不做 , 如果先前没有定义 , 那么变量值就是 赋值符右边的值
如下所示:
1 | FOO ?= bar |
其含义是,如果 FOO 没有被定义过,那么变量 FOO 的值就是“bar”,如果 FOO 先前被定义过,那么这条语将什么也不做。
等价于:
1 | #第一句是条件判断语句 , 使用origin函数判断 FOO变量是否被定义过 |
2.1.1.1.4 赋值符 “+=”
赋值符 +=
用于将值追加到变量的尾端。
如下所示:
1 | objects = main.o foo.o bar.o utils.o |
变量
objects
的值为main.o foo.o bar.o utils.o
, 后面我们给它追加了一个another.o
因此变量
objects
变成了main.o foo.o bar.o utils.o another.o
注意:
- 如果变量之前没有定义过,那么,
+=
会自动变成=
- 如果前面有变量定义,那
+=
会继承于前次操作的赋值符。
如下所示
1 | variable := value |
等价于
1 | variable := value |
2.1.1.2变量的值指向另一个变量
1 | x = y |
如上所示:
$(x)
的值是y
,所以$($(x))
就是$(y)
$(a)
的值就是z
使用这种 方式可以使用多个变量来 组成一个变量的名字,然后再取其值:
1 | first_second = Hello |
如上所示:
这里的$a_$b
组成了first_second
,于是,$(all)
的值就是 Hello 。
2.1.1.3 变量值的替换
可以替换变量中共有的部分:
写法是: 变量名 + 冒号 + 后缀名替换规则。
如: $(var:.o=.c)
是将把变量var
中所有以.o
结尾的文件替换成.c
结尾的文件。
它实际上patsubst
函数的一种简写形式。( patsubst 函数)
如下所示:
1 | foo := a.o b.o c.o |
这两行代码的意思是 : 将变量
foo
中所有后缀名.o
替换成.c
2.1.2 自动变量
Make
命令还提供一些自动变量,它们的值 与当前规则有关。
所谓自动化变量,就是这种变量会把模式中所定义的一系列的文件自动地挨个取出,直至所有的符合模式的文件都取完了。
这种自动化变量只应出现在规则的 命令 中。
主要有以下几个:
$@ | $@ 指代当前目标,就是Make命令当前构建的那个目标。比如,make foo 的 $@ 就指代foo。 |
---|---|
$< | $< 指代第一个前置条件。比如,规则为 t: p1 p2 ,那么$< 就指代p1 。 |
$? | $? 指代 比目标更新 的所有前置条件,之间以 空格分隔 。比如,规则为 t: p1 p2 ,其中 p2 的时间戳比 t 新,$? 就指代p2 。 |
$^ | $^ 指代所有 前置条件,之间以空格分隔。比如,规则为 t: p1 p2 ,那么 $^ 就指代 p1 p2 。 |
$* | $* 指代匹配符 % 匹配的部分, 比如% 匹配 f1.txt 中的f1 ,$* 就表示 f1 。 |
$(@D) 和 $(@F) | $(@D) 和 $(@F) 分别指向 $@ 的目录名和文件名。比如,$@ 是 src/input.c ,那么$(@D) 的值为 src ,$(@F) 的值为 input.c 。 |
$(<D) 和 $(<F) | $(<D) 和 $(<F) 分别指向 $< 的 目录名和 文件名。 |
2.1.2.1 $@
$@
表示规则中的目标文件集。在模式规则中,如果有多个目标,那么,$@
就是匹配于 目标中模式定义的集合。
$@
指代当前目标,就是Make
命令当前构建的那个目标。比如,make foo
的$@
就指代foo。
如下所示:
1 | a.txt b.txt: |
等价于:
1 | a.txt: |
2.1.2.2 $<
$<
指的是依赖文件集合中的第一个文件如果依赖文件是以模式 (即
%
) 定义的,那么$<
就是符合模式的一系列的文件集合。
如下所示:
1 | a.txt: b.txt c.txt |
等价于:
1 | a.txt: b.txt c.txt |
2.1.2.3 $^
$^
指代所有 前置条件,之间以空格分隔。
1
2 test1.o:test1.c head.c
gcc -o $@ $^指的就是
test1.c head.c
2.1.2.4 自动化变量示例
1 | dest/%.txt: src/%.txt |
如上所示:
- 上面代码将 src 目录下的 txt 文件,拷贝到 dest 目录下。
- 首先判断 dest 目录是否存在,如果不存在就新建
- 然后,
$<
指代前置文件(src/%.txt)
,$@
指代目标文件(dest/%.txt)
。
2.1.3 内置变量
Make命令提供一系列内置变量, 内置变量分成两种: 一种是 命令相关的 , 如"CC" ; 另一种是与 参数有关 的, 如"CFLAGS"
2.1.3.1 命令相关的变量
CC | C 语言编译程序。默认命令是 cc 。 |
---|---|
CXX | C++ 语言编译程序。默认命令是 g++ 。 |
CPP | C 程序的预处理器(输出是标准输出设备)。默认命令是$(CC) –E |
RM | 删除文件命令。默认命令是 rm –f |
AR | 函数库打包程序。默认命令是ar |
AS | 汇编语言编译程序。默认命令是as |
YACC | Yacc 文法分析器(针对于 C 程序)。默认命令是yacc |
2.1.3.2 命令参数相关的变量
下面的这些变量都是上面的命令的参数。如果没有指明其默认值,那么其 默认值都是空
ARFLAGS | 函数库打包程序 AR 命令的参数。默认值是rv |
---|---|
ASFLAGS | 汇编语言编译器参数。(当明显地调用.s 或.S 文件时) |
CFLAGS | C 语言编译器参数 |
CXXFLAGS | C++语言编译器参数 |
CPPFLAGS | C 预处理器参数。( C 和 Fortran 编译器也会用到)。 |
LDFLAGS | 链接器参数。(如:“ld”) |
YFLAGS | Yacc 文法分析器参数 |
2.2 函数
在 Makefile 中可以使用函数来处理变量, 格式如下
1 | $(函数名 参数集合) |
2.2.1 内置函数
- Makefile 提供了内置函数供我们使用 , 类似于库函数
- make 所支持的函数也不算很多,不过已经足够我们的操作了。
- 函数调用后,函数的返回值可以当做变量来使用
以下是常用的内置函数
2.2.1.1 shell函数
- shell 函数用来执行 shell 命令 ,
- 它的参数应该就是操作系统 Shell 的命令。
- shell 函数把执行操作系统命令后的输出作为函数返回。
例如:
1 | files := $(shell echo *.c) |
2.2.1.2 wildcard 函数
通配符“%”只能用在 规则 中,只有在规则中它才会展开.
如果在 变量定义 和 函数使用 时,通配符不会自动展开,这个时候就要用到函数
wildcard
,
例子:
1 | #用来获取当前目录下所有的.c 文件,类似“%” |
2.2.1.3 subst 函数
函数 subst 用来完成字符串替换,把字串 <text>中的<from>字符串替换成<to>。
调用形式如下:
1 $(subst <from>,<to>,<text>)
示例:
1 | $(subst ee,EE,feet on the street) |
例2:
1 | comma:= , |
2.2.1.4 patsubst函数
函数 patsubst 用来完成 模式字符 串替换,使用方法如下:
1 $(patsubst <pattern>,<replacement>,<text>)
- 查找字符串
<text>
中的单词是否符合模式<pattern>
, 如果匹配就用<replacement>
来替换掉<pattern>
可以使用通配符%
,表示任意长度的字符串- 函数返回值就是替换后的字符串。
- 如果
<replacement>
中也包涵%
,那么<replacement>
中的%
将是<pattern>
中的那个%
所代表的字符串,
示例:
1 | $(patsubst %.c,%.o,a.c b.c c.c) |
注意:
1 | $(patsubst %.o,%.c,$(objects)) |
2.2.1.5 dir函数
函数 dir 用来获取目录,使用方法如下:
1 $(dir <names…>)此函数用来从文件名序列
<names>
中提取出目录部分,返回值是文件名序列<names>
的目录
例如:
1 | $(dir </src/a.c>) |
提取文件
/src/a.c
的目录部分,也就是/src
2.2.1.6 notdir 函数
函数
notdir
用于取出文件, 即取出文件中的目录部分 . 格式如下:
1 $(notdir <names...>)
- 从文件名序列
<names>
中取出非目录部分。- 非目录部分是指最后一个反斜杠(“ /”)之后的部分。
示例:
1 | $(notdir src/foo.c hacks) |
2.2.1.7 foreach 函数
foreach
函数用来完成循环 , 格式如下:
1 $(foreach <var>,<list>,<text>)
参数
<list>
中的单词逐一取出放到参数<var>
所指定的变量中,然后再执行<text>
所包含的表达式每一次
<text>
会返回一个字符串,循环过程中,<text>
的所返回的每个字符串会以空格分隔,最后当整个循环结束时,<text>
所返回的每个字符串所组成的整个字符串(以空格分隔)将会是
foreach
函数的返回值。所以,
<var>
最好是一个变量名,<list>
可以是一个表达式参数是一个临时的局部变量 , 作用域只在 foreach 函数当中
示例:
1 | names := a b c d |
$(name)
中的单词会被挨个取出,并存到变量n
中$(n).o
每次根据$(n)
计算出一个值,这些值以空格分隔,最后作为foreach
函数的返回- 最终
$(files)
的值是a.o b.o c.o d.o
2.2.1.8 origin函数
origin
函数用于返回变量的来源 , 格式如下:
1 $(origin <variable>)
<variable>
是变量的名字,不应该是引用 ,最好不要在<variable>
中使用$
字符
origin
函数的返回值:
“undefined”
如果
<variable>
从来没有定义过,origin
函数返回这个值 “undefined”
“default”
如果
<variable>
是一个默认的定义,比如“CC”这个变量
"environment"
如果
<variable>
是一个环境变量,并且当Makefile
被执行时,-e
参数没有被打开。
“file”
如果
<variable>
这个变量被定义在Makefile
中。
“command line”
如果
<variable>
这个变量是被命令行定义的
“override”
如果
<variable>
是被override
指示符重新定义的。
“automatic”
如果
<variable>
是一个命令运行中的自动化变量。
示例:
1 | #使用origin函数判断 FOO变量是否被定义过 |
2.2.1.9 call函数
call
函数通常用来调用另一个自定义的make
函数,并且会展开里面的内容,同时按照展开的位置解释为makefile语句或者shell语句。格式如下:
1 $(call <expression>,<parm1>,<parm2>,<parm3>...)
<expression>
是已经定义了的函数名称 ,<parm1>,<parm2>,<parm3>
是传递给函数的参数 , 中间以 逗号 隔开- 注意,
call
函数只能在Makefile
中调用已经定义的函数,而不能用于调用Shell
命令或者 函数。
示例:
1 | PHONY: all |
运行结果:
如上所示:
make
执行这个函数的时候 ,$(1)
,$(2)
,$(3)
等 参数变量 , 会被参数<parm1>,<parm2>,<parm3>
等 依次 取代$(0)
表示<expression>
, 即函数名本身
2.2.2 自定义函数(多行变量)
- 在
makefile
中可用通过使用define
来定义 多行变量 或 自定义函数 , 以endef
关键字结束 - 变量的值可以包含 函数、命令、文字,或是其它变量。
- 如果用
define
定义的命令变量中没有以[TAB] 键开头 ,make
就不会认为其是命令 - 自定义的函数一般用于定义 命令的集合,并且运用于 规则中
- 自定义函数 使用 内置函数
call
来调用 , 后面跟自定义函数名及参数
define格式如下
1 | define 函数名 |
示例如下:
1 | PHONY: all |
运行结果:
如上所示:
- 首先定义了一个伪目标
all
, 接着定义了两个函数func1
和func2
. func1
中通过通过$(0)
输出call
的第一个参数,也就是函数名fun1
func2
中同时输出了第一个参数和第二个参数- 注意,自定义函数是一个 过程调用,没有任何返回值,这是与
Makefile
的 内置函数 的区别之处。
3️⃣
3.1 伪目标
3.1.1 介绍
- 在上面的例子中, 使用了
PHONY
来显性的指明了一个伪目标all
- 伪目标并不是一个文件,只是一个 标签 , 所以
make
无法生成它的依赖关系和决定它是否要执行。 - 在执行 make 命令的时候通过指定这个伪目标来执行其所在规则的定义的命令. 如:
make clean
如下所示:
1 | objects = main.o kbd.o command.o display.o \ |
如上所示:
有了.PHONY : clean
的声明 , 不管是否存在 "clean" 文件 , 要运行clean
这个目标 , 只能通过 make clean
指令
3.1.2 执行多个目标
可以通过使用 伪目标 , 来实现一次生成多个目标文件的目的
如下所示:
1 | all : prog1 prog2 prog3 |
说明:
- 因为
Makefile
中的第一个目标会被作为其默认目标 , 通过声明一个all
的伪目标 , 其依赖于其它三个目标prog1 prog2 prog3
- 由于伪目标的特性是,总是被执行的,所以其依赖的那三个目标就总是不如
all
这个目标新。 - 就能达到一次生成多个目标的目的
3.2 模式规则
使用模式规则可以定义一个隐含规则 , 例如: (怎么从所有的
[.c]
文件生成相应的[.o]
文件的规则)在模式规则中, 至少在规则的目标定义中要包含
%
,否则,就是一般的规则。目标中的
%
表示对文件名的匹配,%
表示长度任意的 非空字符串例如:
%.c
就是所有的以.c
结尾的文件,类似于通配符*
a.%.c
就表示以a.
开头,以.c
结束的所有文件如果
%
定义在目标中,那么,目标 中的%
的值 决定 了 依赖目标 中的%
的值
在上面关于 伪目标 的例子中使用了这样一行规则:
1 | #-c标志表示产生目标文件, -o $@表示将输出文件命名为:左边的文件名, $<表示依赖列表中的第一个项(即%.c)。 |
这段代码的含义是指出了怎么从所有的
[.c]
文件生成相应的[.o]
文件的规则
%.o
是指将当前目录下所有以.o
为后缀的文件当成 目标。同理, 对应的.c
文件为 依赖目标
3.2.1 模式匹配
- 一般来说,一个目标 的模式有一个有前缀或是后缀的
%
,或是没有前后缀,直接就是一个"%"。 - 把
%
所匹配的内容叫做 "茎" ,例如%.c
所匹配的文件test.c
中test
就是 "茎" 。 - 因为在目标和依赖目标中同时有
%
时,依赖目标会将%
替换为 茎 , 从而得出文件名。
例如: 有这样一个模式规则
1 | %.o : %.c |
当一个模式匹配 包含有斜杠 的文件时 , 在进行模式匹配时, 目录部分 会首先被移开,然后进行匹配,成功后,再把目录加回去。
例如:
- 有一个模式
e%t
, 和文件名src/eat
匹配 ,则 茎 是src/a
- 当 依赖目标 转换为文件名的时候 , 茎 中的路径名将被加在前面 ,
%
被替换为 茎 的其余部分 - 如果依赖目标中 有一个
c%r
和 茎 匹配会得到src/car
3.3 通配符
- 在
Makefile
中可以使用的通配符有:*
、?
、[…]
。通配符的使用方法和含义和在shell
中 一样。- 通配符代替了 一系列文件 , 如
*.c
表示所以后缀为 c 的文件~
波浪号表示当前用户的HOME
目录 , 如~/test
就表示/home/test
这个目录
在Makefile
中,通配符主要用在 两个场合:
-
用在规则的 目标和依赖 中:
make
在读取Makefile
时会自动对其进行匹配处理(通配符展开)。如:1
2
3
4
5test: *.o
gcc -o $@ $^
*.o: *.c
gcc -c $^ -
用在规则的 命令 中:
这时 , 通配符的通配处理在
shell
执行命令时完成。如:1
2clean:
rm -f *.o -
除了前面两个以外 , 通配符使用在其他地方, 例如使用在 变量 当中
在变量中的通配符 不会进行展开 , 因为在
makefile
中变量,相当于C/C++
中的宏 , 只是简单的进行替换如果要让通配符在变量中展开, 可以使用 内置函数
wildcard
1
2
3
4
5
6# 变量不会展开 , objects的值就是 *.o
objects = *.o
# 使用wildcard 函数获取当前目录下所有.c文件
#$(wildcard *.c) 是用来获取当前目录下所有的.c 文件
CFILES := $(wildcard *.c)
3.3.1 “%” 与 通配符 “*”
%
和*
的主要区别在于 ,*
是应用在系统中的 ,%
是应用在Makefile
文件中的
前面提到一个 示例:
1 | objects = main.o kbd.o command.o display.o |
在这个示例中:
make
命令在构造edit
文件时 , 发现需要main.o kbd.o command.o display.o
这几个文件- 接下来 , make命令会在Makefile文件中寻找 能匹配
main.o kbd.o command.o display.o
的规则- 在分别找到
main.o
kbd.o
command.o
display.o
的规则后 , 会运行该规则下的 命令- 等到目标
edit
的依赖条件齐全之后 , 开始构造edit
以上是一般make
在工作时的 执行流程 ,
在工作流程中 , 如果存在
%
符号 ,例如:
1
2
3
4
5
6
7 objects = main.o kbd.o command.o display.o
edit : $(objects)
gcc -o edit $(objects)
%.o : %.c
gcc -c -o $@ $<
%
符号只会在带着目的 (例如寻找main.o
) , 才会把它要寻找的目标 (main.o
) 匹配到%
符号所以 , 如果只写以下的代码 , make会产生报错
1
2
3 # 因为此时% 符号并没有目标 , 所以第2~3行定义的这个 规则并不会被执行 , 从而产生报错
%.o : %.c
gcc -c -o $@ $<
而如果存在通配符
*
, make命令会然后 遍历目录的文件,看是否匹配。找出所有匹配的项目。
总结
虽然 , %
和*
两个符号的功能看似类似 , 但他们的工作方式是完全不同的
3.4 条件判断
在
Makefile
中也存在条件判断 , 通过使用条件表达式可以让make
根据运行时的不同情况选择不同的执行分支用于条件判断的 关键字 有四个:
ifeq
、ifneq
、ifdef
和ifndef
语法如下: 语法有两种
1 | <条件关键字> |
另一种
1 | <条件关键字> |
3.4.1 “ifeq” 和 “ifneq”
ifeq
用来判断是否 相等
ifneq
就是判断是否 不相等
用法如下:
1 | # ifeq的用法 |
说明:
参数是可以使用make
函数的 , 例如前面提到的一个例子
1 | ifeq ($(origin FOO), undefined) |
这个例子中就在
ifeq
中使用了origin
函数 , 用来判断FOO
变量是否被定义过
3.4.2 “ifdef"和"ifndef”
格式如下:
1
2
3 ifdef <variable-name>
ifndef <variable-name>其中
ifdef
用来判断变量<variable-name>
的值是否为空 , 非空 这表达式为真 , 否则表达式为假 .
<variable-name>
同样可以是一个函数的 返回值 ,ifdef
和ifndef
只是测试一个变量是否有值,其并不会把变量扩展到当前位置。
ifndef
与ifdef
的意思相反
示例如下:
1 | bar = |
4️⃣ 示例
例1
给出一个makefile
程序 , 用来编译当前目录下的所有文件
1 | all:$(subst .c,.o,$(wildcard *.c)) |
例2 存在这样一个目录框架
Makefile 程序
1 | # 目标文件名 |
❗️参考文章
《GUN make使用手册》
《跟我一起写Makefile》