在Linux下开发裸板程序的时候,我们一般用Makefile来组织管理这些程序和一些文件,本篇文章主要讲Makefile最基本的规则.
目录
简单引入:
这里从一个简单的例子来引入Makefile
main.c:
Func_b.c
我们可以 gcc -o test main.c func_b.c 来生成可执行文件,最后 ./test输出结果:
C程序à可执行程序需要经过四个步骤:
预处理->编译->汇编->链接
一般吧前三个步骤统称为编译,于是这条gcc -o test main.c func_b.c需要经过下面几个步骤
1)对main.c执行: 预处理 编译 汇编的过程,main.c->xxx.s->xxx.o文件
2)对func_b.c执行: 预处理 编译 汇编的过程,func_b.c->yyy.s->yyy.o文件
3)最后讲1)和2)生成的.o文件链接在一起生成test可执行程序
这条语句有一个缺点,当.c文件多起来的时候,当我们修改其中一个.c文件的时候,其他所有的.c文件都需要经过预处理,编译,汇编过程,这是没有必要的,从上面步骤可以看出是先编译文件,最后将他们链接一起,所以只需要编译已经更改过的文件即可.
那如何确定哪些文件被修改?只需要比较文件的时间即可,被修改的文件的时间是新的,这里引入一个简单的Makefile最基本的规则
目标: 依赖1 依赖2 ….
[TAB]命令
当依赖比目标文件新的时候,或者目标文件不存在,就会执行下面的命令,注意命令前面是一个TAB键,不能用空格代替,将前面的例子用Makefile重写:
先新建一个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
/这里只需要输入make命令就可以生成可执行文件test了
Makefile语法:
统配符:
$.o: 表示所有的.o文件
%.c: 表示所有的.c文件
$@: 表示目标
$<: 表示第一个依赖文件
$^: 表示所有的依赖文件
于是上面的Makefile也可以写成:
后面的.PHONY是假想目标:
clean也是一个目标,但是一般直接make只是执行Makefile里面的第一个目标.但是它没有依赖,(所以只能是目标文件不存在的情况下才会执行命令)也就没有办法判断其依赖文件和clean的时间,所以如果文件里出现clean文件,我们又执行make clean命令的时候,就会提示:
clean is up to date
于是将clean确定为假象目标,这样就不会判断名为clean的存在了.
变量:
Makefile中有俩个变量:
1. 简单变量(即时变量) :=
即值在定义的时候就被确定
2. 延时变量 =
其值需要在使用的时候才能被确定
?= 延时变量,如果是第一次定义就起效,如果前面已经对该变量定义则忽略这句
+= 附加
下面用一个简单的例子来说明
注: 想使用这些变量的时候使用“$”来引用,如果不想看到命令是,可以在命令的前面加上"@"符号,就不会显示命令本身。当我们执行make命令的时候,make这个指令本身,会把整个Makefile读进去,进行全部分析,然后解析里面的变量。
make后输出:
分析:
-
A := $(C) A是即时变量,在定义时就确定,但是此时C的值未空,所以A的值为空.
-
B = $(C) B是延时变量,只有在使用到它的时候才确定,当执行make的时候,会解析makefile中所使用的变量, 先解析 C = abc 再去解析 C+= 123 ,此时C = abc123,故执行 @echo B = $(B)时,B的值为abc123
-
D ?= dog 由于D变量在之前定义了,所以D = book, 如果将第四行去掉,D就等于dog了
Makefile函数
Makefile里面可以包含很多函数,下面介绍几个常用的函数,引用一个函数用 “$”
函数foreach:遍历
语法如下:
$(foreach var, list, text)
Var是一个局部变量,list是一个文件列表,test 是对var进行操作
网上看到的介绍比较难理解,我个人理解是将list中的元素作为var取出,然后再用text对其做操作
实例:
Make后输出:
函数filter/filter-out:筛出
语法如下:
$(filter pattern, list) # 在list中取出符合pattern格式的值
$(filter-out pattern, list) #在list中取出不符合pattern格式的值
实例:
Make后结果:
%可以看作是代指
函数wildcard 寻找
语法如下:
$(wildcard pattern) # paterrn定义了文件名的格式,wildcard取出其中存在的文件
(和filter/filter-out不同这是对当前目录下文件进行操作)
返回存在文件的名字
实例:
在Makefile存在的目录下创建三个文件: a.c b.c c.d d.c
Makefile:
Files 是寻找出目录下所存在的 *.c文件
File3 是在当前目录下寻找是否存在列表中的文件
Make后结果
函数patsubst 替换
语法如下:
$(patsubst pattern, replacement, $(var))
Patsubst函数是从vat变量中取出每一个值,如果符合pattern格式,就将其替换成replacement格式
实例:
结果:
综合实例
前面讲了makefile的一些语法和函数,下面做一个实例:
建立 main.c a.c a.h文件
a.c中定义一个函数:
///
#include<stdio.h>
#include"a.h"
void func_A(void)
{
printf("A is %d\n",A);
}
a.h对这个函数进行声明,并定义一个宏
#define A 200
void func_A(void);
main.c中调用这个函数
#include<stdio.h>
#include"a.h"
int main(void)
{
func_A();
return 0;
}
Makefile中:
test:main.o a.o
gcc -o test a.o main.o
%.o : %.c
gcc -c -o $@ $<
编译可以正常输出 A is 200
然后修改A的宏定义为600
再次编译发现,输出还为200,并未改变,这是因为a.o依赖a.c和a.h但是在makefile中却并没有说明,所以我们更新a.h并不能导致a.o的更新,
于是需要添加:
a.o:a.c a.h
这样每次修改a,h都可以使得Makefile识别到更新动作,从而输出正确的文件
但是对于一些较大的文件,其中的头文件成百上千,不可能一一写出,于是需要使用gcc命令使其自动生成依赖
可以参考文章:https://blog.csdn.net/qq1452008/article/details/50855810
这里用里面的几个选项:
gcc –M a.c //打印出依赖
gcc –M –MF a.c a.d //将上面的依赖写入文件a.d中
gcc -c –o a.o a.c –MD –MF a.d //编译生成a,o,并将依赖写入文件c.d中
修改后的Makefile如下:
1 –> 将objs变量将.o文件放在一起,
3 –> 利用前面讲到的替换函数将里面的文件(%就是将里bojs 里的全部文件)替换成 .%.d格式,(.%.d类似于隐藏文件)
4 -> 用寻找函数,将目录中已经存在的 .%.d文件都寻找出来,
6 -> test目标文件依赖于objs 也就是%.o文件
8 -> 这个语句可以理解为这个变量里的头文件全都包含起来
9 –> 所有的.o文件都依赖于.c文件
10 –> 通过gcc命令生成.d依赖文件,
于是我们修改任何.h文件,最终都会影响最后生成的文件,不用手工添加.h,文件,完成了头文件的依赖