makefile入门
# 初识makefile
makefile是程序员们在构建大型项目时必不可少的工具,它能够帮助理清工程项目间的依赖关系。在项目的某些文件更新时,就不至于重新编译链接整个工程,而是只需要更新某些依赖项,大大提高了构建效率。
# makefile的基本规则
target: prerequisites
command
2
- target是要构建的目标文件
- prerequisites是目标文件所需要的依赖文件
- command是构建命令,注意命令前一定是个tab制表键。
依赖文件发生改动时,make会自动执行后面的命令以重新构造目标文件。
一个makefile文件往往由多组这样的命令组成,下面是陈皓老师教程中的实例:
edit : main.o kbd.o command.o display.o /
insert.o search.o files.o utils.o
cc -o edit main.o kbd.o command.o display.o /
insert.o search.o files.o utils.o
main.o : main.c defs.h
cc -c main.c
kbd.o : kbd.c defs.h command.h
cc -c kbd.c
command.o : command.c defs.h command.h
cc -c command.c
display.o : display.c defs.h buffer.h
cc -c display.c
insert.o : insert.c defs.h buffer.h
cc -c insert.c
search.o : search.c defs.h buffer.h
cc -c search.c
files.o : files.c defs.h buffer.h command.h
cc -c files.c
utils.o : utils.c defs.h
cc -c utils.c
clean :
rm edit main.o kbd.o command.o display.o /
insert.o search.o files.o utils.o
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<kbd>代码 1-1 makefile文件实例</kbd>
“/”是换行符的意思。这是由3个头文件和8个.c源文件构建的工程,输入命令make就可以生成可执行文件edit。输入命令make clean就可以删除所列文件。
注意这里的clean不是一个文件,而是一个动作名字(lable),可以看到它后面什么也没有,没有依赖文件也就不会自动执行下面的命令,如果需要执行,就需要在输入make命令时指出这个lable的名字。这在打包或者备份程序时很有用。
# makefile工作原理
makefile文件中的第一个目标文件作为最终的目标文件。当输入make命令时,make会在当前目录下寻找名为makefile的文件,并寻找第一个目标文件,如果依赖项的时间戳比它新或者目标文件不存在,则执行后面的命令;如果依赖项不存在则报错。之后则是按上述过程依次寻找依赖项,直到能够构成最终目标文件。
如果我们修改某一个源文件如files.c则它对应的目标文件file.o就会被更新,则最终的目标文件也会重新链接。但其余编译过的.o文件无需重新编译,节约时间。
如果我们修改了command.h,则kbd.o,command.o,files.o都会被重新编译。
# makefile中的变量
可以看到最终目标文件的依赖项有一大堆,如果我们需要添加或者删除某个依赖项,则需要在依赖项,命令处更改两次(实际应用中可能更多),所以需要用一个变量来定义这组文件,后面的更改只需要在变量定义处声明一次就好了。下面是应用变量后的makefile文件
变量引用格式:$(var)
obj = main.o kbd.o command.o display.o insert.o search.o files.o utils.o
edit : $(obj)
cc -o $(obj)
main.o : main.c defs.h
cc -c main.c
kbd.o : kbd.c defs.h command.h
cc -c kbd.c
command.o : command.c defs.h command.h
cc -c command.c
display.o : display.c defs.h buffer.h
cc -c display.c
insert.o : insert.c defs.h buffer.h
cc -c insert.c
search.o : search.c defs.h buffer.h
cc -c search.c
files.o : files.c defs.h buffer.h command.h
cc -c files.c
utils.o : utils.c defs.h
cc -c utils.c
clean :
rm edit $(obj)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<kbd>代码1-2 应用变量后的makefile实例</kbd>
# makefile自动推导
尽管应用变量后看起来简洁了一些,但是每个作为依赖项和目标文件的.o文件也都需要挨个写类似的依赖项与命令。这看起来还是有点麻烦,好在make足够强大,它可以进行自动推导。
原理:make每看到一个.o文件,就会自动把与它同名的.c源文件加入到依赖项中,并且命令cc -c xxx.c也会被自动推导。
obj = main.o kbd.o command.o display.o insert.o search.o files.o utils.o
edit : $(obj)
cc -o $(obj)
main.o : defs.h
kbd.o : defs.h command.h
command.o : defs.h command.h
display.o : defs.h buffer.h
insert.o : defs.h buffer.h
search.o : defs.h buffer.h
files.o : defs.h buffer.h command.h
utils.o : defs.h
clean :
rm edit $(obj)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
代码1-3 应用自动推导功能后的makefile
# makefileの潜规则
每个makefile的文件在最后都最好写一个clean的动作,即清空最终目标文件和.o文件。这样有利于重新编译和整洁。这是一种修养(by陈皓)。
一般做法是:
clean
rm edit $(obj)
2
更为稳健的做法是:
.PHONY:clean
clean:
-rm edit $(obj)
2
3
.PHONY表明clean是个伪目标,“-”的意思是即使某些文件出了问题,也强制执行后面的命令。
# 简谈makefile规则
# makefileの组成
主要由五个部分组成
- 显式规则:由makefile的编写者显式地指出,要生成的目标文件,目标文件的依赖文件,生成命令。
- 隐式规则:是由make规定的,自动推导的功能,能让我们的makefile看起来更加简洁。
- 变量:一般由字符串表示,类似于c语言中的宏定义。
- 文件指示:在一个makefile中引用另一个makefile,类似于c语言中的#include。根据不同情况指定文件的有效部分,类似于c语言中的#if条件编译。定义一个多行命令。
- 注释:“#”表明注释,若要打印“#”,则使用转义符反斜杠/。
makefile文件名
当输入make命令的时候,make会在当前目录依次寻找名为“GNUmakefile”,“makefile”,“Makefile”的文件,找到就开始解释文件。一般来说最好用“Makefile”,因为比较醒目。最好不要用“GUNmakefile”,因为这是GNU的make才会识别。
也可以使用其他的文件名,比如“wjy_make”,这个时候就需要指定参数,比如make -f wjy_make或者make --file wjy_make。
# makefile环境变量与引用
先来谈引用,makefile中使用include关键字来引用其他文件,这与c语言中的#include很像。被引用的文件会原封不动的放在该位置。
引用的语法是:include <filename>
include前面可以有空格,但不能有tab制表符。我们来举个例子,假设我们有a.mk, b.mk, c.mk, wjy.make和一个变量bar,变量中包含了e.mk, f.mk. 现在有以下语句:
include wjy.make *.mk $(bar)
它相当于:
include wjy.make a.mk b.mk c.mk e.mk f.mk
当make命令开始执行时,make会先看include的文件有没有指定的绝对路径或者相对路径,如果没有,则到当前目录里寻找,如果再没有,则到目录<prefix>/include(一般是/usr/local/bin或者/usr/include)中找,或者到用户用-i 或 --include-dir参数指定的目录中去寻找。
使用引用可以实现文件的共享,进一步使得工程更加简洁。
makefile的环境变量定义为MAKEFILES,和include类似,不同的是,他的目标不会起作用(?)
陈皓老师不建议使用MAKEFILES变量,原因是,一但定义它,所有的makefile文件都会受影响,可能会出现怪事。
# makefile工作方式
GNU的make执行步骤如下:
- 读入所有makefile文件
- 读入被include的其他makefile
- 初始化变量
- 推导隐晦规则,并分析所有规则
- 为目标文件创建依赖链
- 根据依赖关系决定哪些目标文件要(重新)生成
- 执行生成命令
变量不会立刻展开,如果使用到了才会展开,类似于程序有些模块在运行时才会装入,这样有利于资源的利用,提高速度。
# makefile的通配符
可以用三种类型的通配符来定义一系列类似的文件。make支持三种:“*”, “?”,“【…】”.其中*和%的区别是,*是应用在系统中的,而%是应用在makefile中的。%表示匹配0或多个字符,*表示任意长度的任意字符。准确说%并不是一个通配符,而是一种特殊的替换符号。
“~”波浪号字符一般用在路径中,比如“~/test”表示当前用户home目录下的test目录。“~wjy/test”则表示用户wjy的home下的test目录。
“*”例1:强制清除所有.o文件。
clean:
rm -f *.o
2
“*”例2:定义所有.c文件
obj = *.c
注意,这里的变量obj就是*.c,如果想让通配符在变量中展开,也就是obj代指所有.c文件,则可以用以下表达
obj := $(wildcard *.c)
这里wildcard是make中的一个关键字,后续补充。
# makefile文件搜索
在工程中往往有多级目录,也可能涉及到不同目录下的重名文件,所以有时候需要指定文件搜索目录。
下面介绍特殊变量VPATH和关键字vpath。
VPATH的作用是,在当前目录找不到目标文件和依赖文件时,就会到VPATH变量里的目录去找。
VPATH = src: ../hardware
这个就是表明,如果当前目录找不到目标文件或依赖文件,则到src目录下去找,若也没找到,则到../hardware目录下去找。注意顺序为:“当前目录”->“src”->“../hardware”
vpath关键字的使用更加灵活,它有三种使用方法:
- vpath <pattern> <catalogue>
为符合模式pattern的文件指定搜索目录catalogue,如
vpath %.c ../hardware
这句就是表明,如果在当前目录没有找到某个.c文件的话,则到hardware目录下去找那个.c文件。
- vpath <pattern>
清除所有符合pattern模式的搜索目录
- vpath
清除所有已经设置好的搜索目录
vpath也可以连续地使用。如:
vpath %.c cata1:cata2
vpath % cata3
vpath %.c cata4
2
3
这几句的意思就是先到cata1找,再到cata2找,再到cata3找,最后到cata4找。
# makefile中的伪目标
上文(1.5节)中提到一种更稳健的写clean动作的方法
.PHONY: clean
clean:
rm -f *.c
2
3
在这里.PHONY标志就表明clean是个伪目标。这样的好处是,由于可能存在重名的clean目标文件,但是在我们用.PHONY指明clean是个伪目标后,make clean指令就一定能够执行想要的动作。
一般伪目标不会有依赖文件,但是也可以为它添加依赖文件,同时它也可以作为终极目标文件,比如想一口气生成多个可执行文件,可以这样写:
.PHONY: all
all: exe1 exe2 exe3
exe1: a.o
cc -o a.c
exe2: b.o
cc -o b.c
exe3: c.o
cc -o c.c
2
3
4
5
6
7
8
9
10
11
这样我们只需敲入make即可生成三个可执行文件。
同样,伪目标也可作为依赖项,如:
.PHONY: E1 E2 E3
E1:E2 E3
rm program
E2:
rm *.o
E3:
rm *.c
2
3
4
5
6
7
make E1:删除所有文件
make E2:删除所有.o文件
make E3:删除所有.c文件
# 简谈makefile规则
# makefile多目标与静态规则
在makefile中可以生成多个目标,targets的依赖文件可能是同一个,也有可能是不同的。为了在一组命令中用不同的依赖文件生成多个目标文件,引入了一个自动化变量$@,它是目标文件的集合,再提一个$<,它是依赖文件的集合。后面会见到。下面看一个生成多目标的实例:
bigoutput littleoutput:text.g
generate text.g -$(subst output, , $@)>$@
2
它等价于
bigoutput:text.g
generate text.g big>bigoutput
littleoutput:text.g
generate text.g little>littleoutput
2
3
4
了解了多目标再来看静态规则,它帮助我们更加灵活的定义多目标的规则。下面是静态规则的语法
targets:targets-parttern:prerep-pattern
command
2
targets是目标集合,targets-parttern是目标的模式,prerep-pattern是依赖项的模式。下面举个例子:
obj=a.o b.o c.o
$(obj):%.o:%.c
$(CC) -c $(CFLAGS) %< -o %@
2
3
第一行是定义了obj变量,第二行规定目标为obj,指定每一个与.o重名的.c文件都是其依赖文件,在这里%.o就是目标项的模式,%.c就是依赖项的模式。第三行是命令,$(CC)规定使用哪种编译器,-c表示只生成中间文件
# 初识makefile
makefile是程序员们在构建大型项目时必不可少的工具,它能够帮助理清工程项目间的依赖关系。在项目的某些文件更新时,就不至于重新编译链接整个工程,而是只需要更新某些依赖项,大大提高了构建效率。
# makefile的基本规则
target: prerequisites
command
2
- target是要构建的目标文件
- prerequisites是目标文件所需要的依赖文件
- command是构建命令,注意命令前一定是个tab制表键。
依赖文件发生改动时,make会自动执行后面的命令以重新构造目标文件。
一个makefile文件往往由多组这样的命令组成,下面是陈 皓老师教程中的实例: