羽翼飞扬

古人学问无遗力,少壮工夫老始成。纸上得来终觉浅,绝知此事要躬行。

导航

Linux之Makefile

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\n");
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\n");
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

 

本文会继续不断打磨、完善!

posted on 2018-08-10 16:21  羽翼飞扬  阅读(1709)  评论(0编辑  收藏  举报