注:以下学习内容学习于韦东山老师arm裸机第一期教程
一.Makefile的引入及规则
1.1 在keil,mdk,avr等工具开发程序时点点鼠标就可以编译了,他的内部机制就是使用Makefile来组织管理程序,决定编译哪一个文件.
1.2 如下例:
a.c文件 #include<stdio.h> int main() { func_b(); return 0; } b.c文件 #include <stdio.h> void func_b() { printf("This is func_b!\n"); }
将两个文件上传到Linux服务器用gcc进行编译
gcc -o test a.c b.c
执行生成的test程序,成功打印出了This is func_b!
对于gcc -o test a.c b.c命令
a.具体流程: 对a.c执行预处理、编译、汇编、得到a.o
对b.c执行预处理、编译、汇编、得到b.o
最后将a.o b.o链接起来得到test应用程序
b.这条命令在每次执行时都会对a.c,b.c进行重新处理,及时没有经过修改也会进行一次处理过程(当文件很多时就会使得效率低下)
目的:只对被修改的文件进行重新编译,那么如何知道哪个文件被修改了?
方法:比较 a.o与a.c的时间,如果不同说明a.c被修改
比较 b.o与b.c的时间,如果不同说明b.c被修改
比较 a.o、b.o与test的时间,如果不同需要重新生成test
1.3 Makefile的基本语法规则
目标文件:依赖文件1,2.....
(tab键) :命令
当依赖比目标新,或者目标不存在时执行命令
编写Makefile如下
test:a.o b.o gcc -o test a.o b.o、b a.o:a.c gcc -c -o a.o a.c b.o:b.c gcc -c -o b.o b.c
编译程序时直接执行make命令即可,只会编译被修改的文件.
直接执行make,如下,两个文件都被编译
[email protected]:/work/arm9/hareware/5.Makefile/001.test_app$ make gcc -c -o a.o a.c gcc -c -o b.o b.c gcc -o test a.o b.o
修改a.c文件之后再次执行make,如下,只对a.c进行了编译
[email protected]:/work/arm9/hareware/5.Makefile/001.test_app$ make gcc -c -o a.o a.c gcc -o test a.o b.o
二.Makefile语法
2.1 通配符%.o, $@, $<, $^
在上面的Makefile中,
test:a.o b.o gcc -o test a.o b.o a.o:a.c gcc -c -o a.o a.c b.o:b.c gcc -c -o b.o b.c
如果有很多很多.c文件,难道要一个个写吗?
因此引入通配符
test:a.o b.o gcc -o test $^ %.o:%.c gcc -c -o $@ $<
$@表示目标文件,$<表示第一个依赖文件,&^表示所有的依赖
如果要添加一个c.c文件,直接在b.o后面添加一个c.o即可,如下:
test:a.o b.o c.o gcc -o test $^ %.o:%.c gcc -c -o $@ $<
2.2 假象目标
2.2.1 假设想要清除文件,修改Makefile如下
test:a.o b.o c.o gcc -o test $^ %.o:%.c gcc -c -o $@ $< clean: rm *.o test
执行make clean即可清除文件
同时可以推测出make的用法,make 后面可以直接跟上目标名称,如果不加目标名称就会默认去生成第一个目标
分析流程: 当执行make clean时,发现目录下面没有名为clean的文件,因此会执行后面的条件,但是当目录下面有了名为clean的文件,就会提示clean已经是最新的了,如下:
[email protected]:/work/arm9/hareware/5.Makefile/002.synatx/phyton$ > clean [email protected]:/work/arm9/hareware/5.Makefile/002.synatx/phyton$ make clean make: 'clean' is up to date.
因此,我们需要将目标定义为假象目标,是用.PHYON关键字来定义,如下
test:a.o b.o c.o gcc -o test $^ %.o:%.c gcc -c -o $@ $< clean: rm *.o test .PHONY: clean
这时,再次执行make clean就完全没有问题了.
2.3 变量
2.3.1 即时变量(简单变量)
A := xxx #A的值在定义即确定
2.3.2 延时变量
B = xxx #B的值使用到时才确定
如下例:
A := $(C) #A即可确定,但是C还没有定义,因此A是空 B = $(C) #B在使用时才确定,因此B=abc C = abc all: @echo A = $(A) #加上@符号,在执行的时候就不会显示命令 @echo B = $(B)
[email protected]:/work/arm9/hareware/5.Makefile/002.synatx/var$ make A = B = abc
2.2.3 如果将C的定义放到最后,如下:
A := $(C) #A即可确定,但是C还没有定义,因此A是空 B = $(C) #B在使用时才确定,因此B=abc all: @echo A = $(A) #加上@符号,在执行的时候就不会显示命令 @echo B = $(B) C = abc
执行make
[email protected]:/work/arm9/hareware/5.Makefile/002.synatx/var$ make A = B = abc
从结果可以看出没有影响,因为执行make的时候程序本身会将整个Makefile读进去来分析,然后解析变量
2.2.4
A := $(C) #A即可确定,但是C还没有定义,因此A是空 B = $(C) #B在使用时才确定,因此B=abc C = abc all: @echo A = $(A) #加上@符号,在执行的时候就不会显示命令 @echo B = $(B) C = 123
执行make
[email protected]:/work/arm9/hareware/5.Makefile/002.synatx/var$ make A = B = 123
C被后面的值覆盖了
2.2.5
A := $(C) #A即可确定,但是C还没有定义,因此A是空 B = $(C) #B在使用时才确定,因此B=abc C = abc all: @echo A = $(A) #加上@符号,在执行的时候就不会显示命令 @echo B = $(B) C ?= 123
执行make
[email protected]:/work/arm9/hareware/5.Makefile/002.synatx/var$ make A = B = abc
由于是?=,C并不会被后面的值覆盖了
2.2.6
A := $(C) #A即可确定,但是C还没有定义,因此A是空 B = $(C) #B在使用时才确定,因此B=abc C = abc all: @echo A = $(A) #加上@符号,在执行的时候就不会显示命令 @echo B = $(B) C += 123
执行make
[email protected]:/work/arm9/hareware/5.Makefile/002.synatx/var$ make A = B = abc 123
中间会有一个空格
2.2.8 变量类型小结
:= #即时变量
= #延时变量
?= #延时变量,如果该变量在前面已经定义着忽略这句
+= #附加,他是即时变量还是延时变量取决于前面的定义
三.Makefile函数
3.1 $(foreach var, list, text)
#对于list里面的每一个变量,执行text公式
如下:
A = a b c #$(foreach var, list, text) B = $(foreach f, $(A), $(f).o) all: @echo B = $(B)
执行make
[email protected]:/work/arm9/hareware/5.Makefile/002.synatx/function$ make B = a.o b.o c.o
3.2 filter函数
3.2.1 $(filter pattern...,text)
#在text中取出符合pattern格式的值
3.2.2 $(filter-out pattern...,text)
#在text中取出不符合pattern格式的值
如下:
A = a b c d/ B = $(filter %/, $(A)) C = $(filter-out %/, $(A)) all: @echo A = $(A) @echo B = $(B) @echo C = $(C)
执行make
[email protected]:/work/arm9/hareware/5.Makefile/002.synatx/function$ make A = a b c d/ B = d/ C = a b c
3.3 $(wildcard pattern)
#pattern定义了文件名的格式
#wildcard取出其中存在的格式
如下1:
A = $(wildcard *.c) all: @echo A = $(A)
执行make,在当前目录下存放了a.c,b.c,c.c三个c文件
[email protected]:/work/arm9/hareware/5.Makefile/002.synatx/function$ make A = a.c c.c b.c
如下2:
file = a.c b.c c.c d.c e.c A = $(wildcard $(file)) all: @echo A = $(A)
执行make,可以判断出变量file中真实存在的文件
[email protected]:/work/arm9/hareware/5.Makefile/002.synatx/function$ make A = a.c c.c b.c
3.4 $(patsubst pattern , replacement, text)
#从text列表中把符合格式pattern的值替换成replacement格式
如下:
file = a.c b.c c.c d.c e.c abc dep_files = $(patsubst %.c, %.d, $(file)) all: @echo A = $(dep_files)
执行make,可以判断出变量file中真实存在的文件
[email protected]:/work/arm9/hareware/5.Makefile/002.synatx/function$ make A = a.d b.d c.d d.d e.d abc
abc不符合.c的格式,因此不会进行替换.
四.Makefile实例
4.1 在Makefile规则引入的例子中,我们在修改c.c函数如下
#include<stdio.h> #include "c.h" void func_c() { printf("This is func_c = %d\n", C); }
其中C是一个宏,在c.h头文件中定义为1
对a.c b.c c.c 进行编译链接,并且执行
[email protected]:/work/arm9/hareware/5.Makefile/003.example$ make gcc -c -o a.o a.c gcc -c -o b.o b.c gcc -c -o c.o c.c gcc -o test a.o b.o c.o [email protected]:/work/arm9/hareware/5.Makefile/003.example$ ./test This is func_b! This is func_c = 1
但是当修改c.h中的宏C为2时,再次进行编译,提示已经是最新的了,再次执行C仍然等于1
[email protected]:/work/arm9/hareware/5.Makefile/003.example$ make make: 'test' is up to date.
这个工程的Makefile如下
test:a.o b.o c.o gcc -o test $^ %.o:%.c gcc -c -o $@ $< clean: rm *.o test .PHONY: clean
我们修改了c.h,而c.c依赖于c.h,因此也应该被修改,但是我们的Makefile中并没有体现出来c.h依赖于c.h
修改Makefile如下,便能够成功的修改c.c导致c.o被重新编译:
test:a.o b.o c.o gcc -o test $^ c.o:c.c c.h %.o:%.c gcc -c -o $@ $< clean: rm *.o test .PHONY: clean
4.2 但是我们如何确定这些头文件的依赖关系?手工写出不是解决办法,因此我们要根据某些规则来自动生成.
通过gcc 的-M命令可以得到依赖关系,例如执行gcc -M c.c,得出c.c的依赖头文件如下
[email protected]:/work/arm9/hareware/5.Makefile/003.example$ gcc -M c.c c.o: c.c /usr/include/stdc-predef.h /usr/include/stdio.h \ /usr/include/features.h /usr/include/x86_64-linux-gnu/sys/cdefs.h \ /usr/include/x86_64-linux-gnu/bits/wordsize.h \ /usr/include/x86_64-linux-gnu/gnu/stubs.h \ /usr/include/x86_64-linux-gnu/gnu/stubs-64.h \ /usr/lib/gcc/x86_64-linux-gnu/5/include/stddef.h \ /usr/include/x86_64-linux-gnu/bits/types.h \ /usr/include/x86_64-linux-gnu/bits/typesizes.h /usr/include/libio.h \ /usr/include/_G_config.h /usr/include/wchar.h \ /usr/lib/gcc/x86_64-linux-gnu/5/include/stdarg.h \ /usr/include/x86_64-linux-gnu/bits/stdio_lim.h \ /usr/include/x86_64-linux-gnu/bits/sys_errlist.h c.
我们需要将依赖写入文件中去,可以通过gcc -M -MF c.d c.c命令,这样就会生成一个c.d的文件
通过 gcc -c -o c.o c.c -MD -MF c.d 即可以生成c.o,也可以生成c.d依赖文件
修改Makefile如下:
test:a.o b.o c.o gcc -o test $^ c.o:c.c c.h %.o:%.c gcc -c -o $@ $< -MD -MF [email protected] #.表示隐藏文件,需要ls -a才可以看到 clean: rm *.o test *.d .PHONY: clean
执行make后ls -a得到如下结果
[email protected]:/work/arm9/hareware/5.Makefile/003.example$ ls -a . .. a.c a.o .a.o.d b.c b.o .b.o.d c.c c.h c.o .c.o.d Makefile test
有.a.o.d,.b.o.d,.c.o.d这三个依赖文件
4.3 我们需要在Makefile中将这些依赖文件包含进去,
objs = a.o b.o c.o dep_files := $(patsubst %, .%.d, $(objs)) #判断依赖文件是否存在 dep_files := $(wildcard $(dep_files)) test:$(objs) gcc -o test $^ #如果依赖文件不等于空,就将文件包含进去 ifneq ($(dep_files),) include $(dep_files) endif c.o:c.c c.h %.o:%.c gcc -c -o $@ $< -MD -MF [email protected] clean: rm *.o test disclean: rm $(dep_files) .PHONY: clean
再次执行make,会生成依赖文件和可执行程序,然后修改c.h文件的宏定义为4,再次make
[email protected]:/work/arm9/hareware/5.Makefile/003.example$ make gcc -c -o c.o c.c -MD -MF .c.o.d gcc -o test a.o b.o c.o [email protected]:/work/arm9/hareware/5.Makefile/003.example$ ./test This is func_b! This is func_c =
只编译了c.c文件
4.4 添加CFLAG
修改Makefile如下:
objs = a.o b.o c.o dep_files := $(patsubst %, .%.d, $(objs)) dep_files := $(wildcard $(dep_files)) #-Werror表示对所有的警告当作错误处理, #-I.指定了当前目录是编译器搜索的默认目录 CFLAGS = -Werror -I. test:$(objs) gcc -o test $^ ifneq ($(dep_files),) include $(dep_files) endif c.o:c.c c.h %.o:%.c gcc $(CFLAGS) -c -o $@ $< -MD -MF [email protected] clean: rm *.o test disclean: rm $(dep_files) .PHONY: clean