1. Makefile规则
# 在行前使用"#"表示注释
target1, target2 ... : depend1, depend2, ...
#需要顶格,可以没有依赖,没有目标称为伪目标,clean常用伪目标
comand1 #每个命令前必须有一个Tab缩进
comand2 #每个命令前必须有一个Tab缩进
- command:前面需要一个tab缩进
- target:执行command后可以生成一个和目标同名的文件(也可以不生成文件,因为command可能只是一个动作,这样的目标为伪目标)
【注】target可以有多个,因为comand也可以有多个
例子:
#多个目标,多个命令
app1,app2:a.c b.c c.c
gcc -o app1 a.c b.c
gcc -o app2 b.c c.c
1.1 Makefile 控制打印(调试使用)
Makefile会默认打印出编译过程中执行的命令本身,如果不想打印命令,可以通过在那行命令前加上‘@’
,但命令任然会执行。如下:
no_output:
@echo 'not display'
echo 'will display'
执行结果如下:
$make no_output
not display
echo 'will display'
will display
2. Makefile工作原理(红字很重要)
2.1 规则的执行
先从一个例子开始:
# makefile
# 规则之间的嵌套
# 规则1
app:a.o b.o c.o
gcc a.o b.o c.o -o app
# 规则2
a.o:a.c
gcc -c a.c
# 规则3
b.o:b.c
gcc -c b.c
# 规则4
c.o:c.c
gcc -c c.c
总结一下结论:
- 执行make后,会去查找Makefile文件,从中找到第一条规则,且规则中命令不能有通配符,第一条规则中的命令执行的前提是依赖全都存在。
- 如果第一条规则中的依赖不存在,他就会去找哪条命令能生成需要的依赖,直到所有的依赖都有了,如果找不到直接生成依赖所需的规则,就会去找有通配符的规则进行匹配。
- 如果有依赖,会从第一条规则开始检查规则是否满足“目标比依赖新”的要求,如果不满足递归的寻找依赖的生成规则并如此递归下去。
- 如果要执行Makefile中非第一条规则,那在make编译时,就要把要那条规则的目标写到 make 后面(比如make <>)。
2.2 文件的时间戳(标记文件的更新时间)
还是看这个例子:
# makefile
# 规则之间的嵌套
# 规则1
app:a.o b.o c.o
gcc app.o a.o b.o c.o -o app
# 规则2
a.o:a.c
gcc -c a.c
# 规则3
b.o:b.c
gcc -c b.c
# 规则4
c.o:c.c
gcc -c c.c
如果先执行:
make
后修改了文件中的b.c,然后再执行
make
那么,这时候就会先执行规则3,然后再执行规则1。
问题:第二次执行make的时候,明明第一条规则的目标实现比依赖要新,按理说不应该去执行规则2了呀,自己写个小程序实验目标是实现以下效果,Makefile与上述例子相同。
在执行完一遍make后。我更改b.c,再次make,后执行结果如下:
可以验证,Make在检查第一条规则时,看当前的依赖是否有依赖,如果有,就会递归的检查生成第一条规则依赖的规则是否满足“目标比依赖新”的要求。
2.3 自动推导
Makefile会会使用默认的编译规则编译.c文件,所以在写依赖时只需要写*.o,Makefile就会为这个 .o 寻找合适的源文件。
2.4 伪目标
clean
常用于伪目标,即不会生成任何文件,没有依赖,只是执行命令。
为了方式目录中有clean名称的文件,造成冲突,需要使用.PHONY
声明clean不是真正的文件,举例如下:
# 添加规则, 删除生成文件 *.o 可执行程序
# 声明clean为伪文件
.PHONY:clean
clean:
# shell命令前的 - 表示强制这个指令执行, 如果执行失败也不会终止
-rm $(obj) $(target)
echo "Clear obj and target done!"
3. 变量
Makefile中有三种,分别为自定义变量,预定义变量,自动变量。最后看变量的赋值方法。
3.1 自定义变量
使用方法:
- 定义方法:变量名=变量值
- 使用方法:**
$(变量名)
** ,小括号,必须要带 - 注意,偶尔看到有人使用
${变量名}
的方法对makefile中的变量取值,虽然也行,但小括号是标准写法。
3.2 预定义变量
Makefile中有些变量是预定义的,一般都是大写的,用户可以直接使用,比如:
CFLAGS
:编译的时候使用的参数,-Wall -g -c
AR
:生成静态库库文件的程序名称
CC
:C 语言编译器的名称
PWD
:Makefile所在绝对路径
详情请参考爱编程的大丙博客。
下面是其中的一个例子:
obj=add.o div.o main.o mult.o sub.o
target=calc
CFLAGS=-O3 # 代码优化
$(target):$(obj)
$(CC) $(obj) -o $(target) $(CFLAGS)
上面说预定义代码不用定义,但上面的例子不也是定义了一遍CFLAGS嘛,因为Makefile中代码的定义和赋值是在一起的。
3.3 自动变量
自动变量很重要,用于表示规则中的目标和依赖文件,只能在某条规则的 命令中使用。
variable | implication |
---|---|
$* | 表示不包含文件扩展名的目标文件的名称 |
$@ | 表示可包含文件扩展名的目标文件的名称(如.o .c .out等) |
$< | 表示依赖项中,第一个依赖文件的名称 |
$? | 表示依赖项中,比目标文件时间戳晚了的依赖文件,空格分隔 |
$^ | 表示依赖项中,不重复的依赖文件,空格分隔 |
$+ | 表示依赖项中,可能有重复按先后顺序排列的的依赖文件,空格分隔 |
下面是一个小例子:
calc:add.o div.o main.o mult.o sub.o
gcc $^ -o $@
3.4 变量的赋值
赋值符‘=’ :使用赋值符“=”设置的变量,它的值在运行时才能确定
赋值符‘:=’ :这种赋值行为立即生效,被赋值的变量为即时变量。
赋值符‘?=’ :如果之前这个变量没有被设置过才会生效。
赋值符‘+=’ :Makefile中如果变量是字符串,我们想要追加一些字符串进去时使用,很常用。例如:
src = ./aa.c ./bb.c
src += ./sub.c
最后src的值为./aa.c ./bb.c ./sub.c
3.4 变量的取值
使用括号将变量括起来加美元符号(与shell脚本中不同。)
FOO = $(DIR)
4. 模式匹配
如果文件中有多个文件,写成大概本文“2.2 文件时间戳”小节那样,似乎没什么不妥,但是如果文件多达10个以上,会导致文件非常臃肿,我们可以将这一系列类似的规则使用百分号%
写成一个模板,如下,只是可读性下降了:
# 模式匹配 -> 通过一个公式, 代表若干个满足条件的规则
# 依赖有一个, 后缀为.c, 生成的目标是一个 .o 的文件, % 是一个通配符, 匹配的是文件名
%.o:%.c
gcc $< -c -o $@
问题:这条规则会去哪里寻找.c源文件?
问题问的就有毛病,这条规则是其他规则需要的时候被调用的。如果一条规则在执行过程发现自己的依赖文件中需要某.o
文件,但是又找不到,就会用这个匹配规则来生成对应的.o
文件
5. 函数
Makefile中的函数的主要目的是帮助开发者快速获取想要得到的信息,格式如下:
返回值接收变量=$(函数名 参数1,参数2,…)
且Makefile中的函数都有返回值。主要讲解我用到过的几个函数,从函数定义,函数参数,函数返回值,用法举例四方面介绍。
5.1 wildcard
函数定义:return=$(wildcard PATTERN)
, 该函数只有一个参数,但该参数可以分为由空格分隔的多个部分。
用于从给定目录找到与PATTERN样式匹配的文件名,并返回这些文件的路径。注意,给定目录的子目录不在扫面范围。
函数参数:
PATTERN可以有多个部分,可以指定路径,要匹配的文件用*表示类似*.c *,o
。
如果不指定路径则默认为当前路径。
返回值:
返回匹配到的文件的路径(含文件名),以空格分隔。
用法举例:
|->a.c
|->b.c
|->test/
|->aa.c
|->bb.c
比如从./*.c和./test/*.c中找到所有的符合*.c要求的空格分隔的文件路径。
rte=$(wildcard *.c ./*.c ./test/*.c)
返回值 rte=a.c b.c ./a.c ./b.c ./test/aa.c ./test/bb.c
可见*.c与./*.c是重复了有点
5.2 patsubst
函数定义:return=$(patsubst <pattern>, <replacement>, <text>)
, 该函数有 3 个参数,由逗号分隔。
用于将text中给定的文件名列表中找到与pattern符合的文件名,并将其都替换成replacement指定的文件名样式。
函数参数:
pattern : 模式字符串,text中要被替换的名称的样式,通配符的形式表示 [通配符是 %],比如%.c表示.c后缀的都要被替换掉。
replacement : 模式字符串,想要被替换成什么,通配符的形式表示。比如%.o表示原来的后缀被替换成.o(我猜测可以有多个模式,由空格分隔,但没啥用处)
text : 等待被替换的文件名称列表的原始数据
返回值:
按照规则替换后的字符串。
用法举例:
若src=aa.c bb.c test/aa.c test/bb.c
boj=$(patsubst *.c, *.o, $(src))
返回值 obj=a.o b.o test/aa.o test/bb.o
只是字符串的变换,没有真正执行编译操作。
5.3 filter
函数定义:return=$(figure <pattern>, <text>)
, 该函数有 2 个参数,由逗号分隔。
用于从text中给定的文件名列表中过滤出与pattern符合的文件名,并返回。
函数参数:
pattern : 模式字符串,可以通配符表示(多个之间空格分隔)通配符的形式表示 [通配符是 %],要留下的名称样式。
text : 等待被过滤的文件名称列表的原始数据
返回值:
按照规则过滤后的字符串。
用法举例:
若obj=aa.o bb.o ff.o gg.o ee.o
若mains=ff.o gg.o
boj=$(figure $(mains) ee.o, $(obj))
返回值 obj=ff.o gg.o ee.o
只是字符串的变换,没有真正执行编译操作。
5.4 filter-out
函数定义:return=$(figure-out <pattern>, <text>)
, 该函数有 2 个参数,由逗号分隔。
用于将text中给定的文件名列表中去除与pattern符合的文件名,并返回。
函数参数:
pattern : 模式字符串,text中要被去除的文件名,也可用通配符表示通配符的形式表示 [通配符是 %],一般直接指定要去除的文件。
text : 等待被去除的文件名称列表的原始数据
返回值:
按照规则去除后的字符串。
用法举例:
若obj=aa.o bb.o ff.o gg.o
若mains=ff.o gg.o
boj=$(figure-out $(mains), $(obj))
返回值 obj=aa.o bb.o
只是字符串的变换,没有真正执行编译操作。
6. 简单习题练习
文件结构如下:
├── Makefile//(你需要写出来的)
├── include
│ └── head.h //存放计算函数的声明
├── main.c //调用head.h,调用计算函数
└── src
├── add.c //分别实现一个计算函数
├── div.c
├── mult.c
└── sub.c
Makefile
实现结果如下
# Copyright xxxx@xxx.com
# 需要在环境变量声明编译器路径
CC=$(CROSS_COMPILE)gcc
CFLAGS=-Wall -O2
# 增加宏定义
CFLAGS+=-DENABLE_FLAG
# 最终的目标名 app
target=test.out
# 搜索头文件
include=./include
# 搜索当前项目目录下的源文件
src=$(wildcard ./*.c ./src/*.c)
# 后缀替换
obj=$(patsubst %.c, %.o, $(src))
# 第一条规则
# 依赖中都是 xx.o yy.o zz.o
# gcc命令执行的是链接操作
$(target):$(obj)
$(CC) $(CFLAGS) $^ -o $(target)
%.o:%.c
# gcc $< -c -I $(include) -o $@
$(CC) $(CFLAGS) -I $(include) -c $< -o $@
#声明clean并非文件
.PHONY:clean
clean:
-rm $(obj) $(target)
@echo "Clear obj and target done"
习题反思:
- 在写
$(CC) $(CFLAGS) -I $(include) -c $< -o $@
这条命令时,我忽略了-o $@
导致生成的.o
文件都在当前目录了,其他规则反而找不到了。其实是gcc编译命令使用不扎实。 - 在写
patsubst
函数时,对使用%还是*混淆了,导致错误。
参考链接:爱编程的大丙,跟着学习一遍,大佬牛逼。
参考链接:Makefile中的匹配符%-忘尘,通配符不是make要找的第一条命令呦。