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的第一个参数,也就是函数名fun1func2中同时输出了第一个参数和第二个参数- 注意,自定义函数是一个 过程调用,没有任何返回值,这是与
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++中的宏 , 只是简单的进行替换如果要让通配符在变量中展开, 可以使用 内置函数
wildcard1
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.okbd.ocommand.odisplay.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》



