Makefile

1. Makefile规则

# 在行前使用"#"表示注释
target1, target2 ... :  depend1, depend2, ... 
#需要顶格,可以没有依赖,没有目标称为伪目标,clean常用伪目标
	comand1 #每个命令前必须有一个Tab缩进
	comand2 #每个命令前必须有一个Tab缩进
  1. command:前面需要一个tab缩进
  2. 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

总结一下结论:

  1. 执行make后,会去查找Makefile文件,从中找到第一条规则,且规则中命令不能有通配符,第一条规则中的命令执行的前提是依赖全都存在。
  2. 如果第一条规则中的依赖不存在,他就会去找哪条命令能生成需要的依赖,直到所有的依赖都有了,如果找不到直接生成依赖所需的规则,就会去找有通配符的规则进行匹配
  3. 如果有依赖,会从第一条规则开始检查规则是否满足“目标比依赖新”的要求,如果不满足递归的寻找依赖的生成规则并如此递归下去。
  4. 如果要执行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 自动变量

自动变量很重要,用于表示规则中的目标和依赖文件,只能在某条规则的 命令中使用。

variableimplication
$*表示不包含文件扩展名的目标文件的名称
$@表示可包含文件扩展名的目标文件的名称(如.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"

习题反思:

  1. 在写$(CC) $(CFLAGS) -I $(include) -c $< -o $@这条命令时,我忽略了-o $@导致生成的.o文件都在当前目录了,其他规则反而找不到了。其实是gcc编译命令使用不扎实。
  2. 在写patsubst函数时,对使用%还是*混淆了,导致错误。

参考链接爱编程的大丙,跟着学习一遍,大佬牛逼。
参考链接Makefile中的匹配符%-忘尘,通配符不是make要找的第一条命令呦。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值