Makefile文件的作用是指导make程序该如何工作。
make的工作原理
当我们只输入make命令的工作流程是:
1. make会在当前目录下找名字叫“Makefile”或“makefile”的文件;
2. 如果找到,它会找文件中的第一个目标文件(target),在上面的例子中,他会找到“main”这个文件,并把这个文件作为最终的目标文件;
3. 如果main文件不存在,或是main所依赖的后面的 .o 文件的文件修改时间要比main这个文件新,那么,make会执行下面定义的命令来生成main文件;
4. 如果main所依赖的.o文件也存在,那么make会在当前文件中找目标为.o文件的依赖性,如果找到再根据命令生成.o文件(这是一个递归的过程);
如果在找寻的过程中,出现了被依赖的文件找不到的错误,那么make就会直接退出,并报错。
如果在一条依赖链中,比如:A依赖B,B依赖C,C依赖D。那么当D更新后,make发现D比C新则会重新构建C,以此类推,最终A也会被更新。
Makefile文件的语法组成
基本的结构形式:
1 target: prerequisites 2 command 3 command 4 ...
说明:
target:可以是任何类型的文件,也可以是一个标签(Label),或叫作“伪目标”,这个我们一会儿再讲。
prerequisites:就是要生成target所需要的文件、目标。
command:当prerequisites比target要新,就会执行这里定义的动作(任意的Shell命令)
其实就是一个文件的依赖关系处理,也就是说,target目标依赖于prerequisites中的文件,其生成规则定义在command中。说白一点就是说,prerequisites中如果有任何一个及以上的文件比target文件要新的话,command所定义的命令就会被执行。这就是Makefile的规则。也是Makefile中最核心的内容。
Makefile中使用变量
我们通过脚本实验来了解定义变量的几种形式:
1 .PHONY: target 2 3 VAR_0:=$(VAR) 4 VAR_1=$(VAR) 5 VAR="hello world" 6 VAR2:=$(VAR) 7 VAR="hello world2" 8 VAR3="hello world3" 9 VAR3?=$(VAR) 10 VAR_0+="abc" 11 VAR_1+="abc" 12 13 target: 14 @echo $(VAR_0) 15 @echo $(VAR_1) 16 @echo $(VAR) 17 @echo $(VAR2) 18 @echo $(VAR3)
$ make
abc
hello world2 abc
hello world2
hello world
hello world3
= 直接赋值,比较直观。值得说的是当赋值的是变量时,如果引用的变量不存在,那么赋值的是空字符串。
:= 延迟引用变量,也就是说只有当脚本中使用到VAR2变量时,make才会去找被引用的VAR变量的值。
?= 条件赋值,被称为条件赋值是因为:只有此变量在之前没有被赋值的情况下才会对这个变量进行赋值。
+= 追加赋值,上面的例子的输出很明显了。
Makefile中也可以直接使用shell进程的环境变量,比如可以在Makefile中输出@echo $(PATH)等等。
make自动推导
首先让我在本地创建几个文件:
$ vim lib1.h
1 #ifndef __LIB1_H__ 2 #define __LIB1_H__ 3 void lib1(); 4 #endif
$ vim lib1.c
1 #include <stdio.h> 2 void lib1() 3 { 4 printf("this is lib1 "); 5 }
$ vim lib2.h
1 #ifndef __LIB2_H__ 2 #define __LIB2_H__ 3 void lib2(); 4 #endif
$ vim lib2.c
1 #include <stdio.h> 2 void lib2() 3 { 4 printf("this is lib2 "); 5 }
$ vim main.c
1 #include "lib1.h" 2 #include "lib2.h" 3 4 int main(int argc, char *argv[]) { 5 lib1(); 6 lib2(); 7 return 0; 8 }
$ vim Makefile
1 .PHONY: clean 2 3 CC=gcc 4 CFLAGS=-O3 5 OBJS=main.o lib1.o lib2.o 6 LIB=libtest.a 7 BIN=main 8 9 $(BIN): $(LIB) $(BIN).o 10 $(CC) $(CFLAGS) -o $@ $(BIN).o -L. -Wl,-Bstatic -ltest -Wl,-Bdynamic 11 echo $? 12 13 %.o: %.c 14 $(CC) -c -o $*.o $*.c 15 16 $(LIB): lib1.o lib2.o 17 ar crv $@ $^ 18 19 clean: 20 rm -rf $(BIN) 21 rm -rf $(OBJS) 22 rm -rf $(LIB)
$ make
大家可以自行改动代码进行测试!
说一下.PHONY的作用,.PHONY后面写的是伪目标,也就是说这种目标只是占用一个符号一个名字而已,无论当前目录下是否有clean文件,不会对比是否最新,只要执行make clean,clean目标下面定义的命令永远都会执行!
$@:表示目标文件名称
$<:prerequisites依赖列表中的第一个依赖的名字
$?:所有比目标新的依赖文件名称的集合,以空格分隔
$^:当前目标中依赖的所有文件,它并不关心这些文件是不是比目标文件新。然而,重复的依赖文件名会被移除。这会在你需要将所有的依赖文件输出到屏幕时变得非常有用
$+:很像$^,也是所有依赖文件的集合,但是它不去除重复的依赖
$*:匹配目标模式中“%”之前的部分
好方法和技巧:
1. 引入外部Makefile
2. 变量值替换
从一个已有的宏创建一个新宏并非不可能。例如宏SRC代表一系列的源文件,你希望生成一个对应的目标文件宏OBJ。要这样做,你只需要指定OBJ = SRC,除了扩展名不同以外:OBJ = $(SRC:.c=.o)
陷阱:
1. 环境变量 MAKEFILES
2. 万能通配符的陷阱
3. 环境变量 VPATH
本文会继续不断打磨、完善!