Makefile #
gnu官方手册:make.pdf
这里是我根据手册写的简略介绍:
什么是make #
首先GNU提供了一个命令工具:make,其最大的作用就是能够自动决定一个项目需要编译/重新编译哪些源文件。每次修改某些源文件的时候,只需要在终端输入:
make就能够自动执行所有必要的重新编译了。更具体的,make使用文件的元数据和最后一次更改的时间,来决定哪些文件需要更新和重新编译。
Makefile的规则 #
我们需要一个文件来告诉make到底要做什么,也就是这里介绍的Makefile文件,它会告诉make命令该如何编译和链接各个源代码。一个简单的书写makefile的规则如下:
target ... : prerequisites ...
command
...
...
target就是一个目标文件,能够是Object File,也能够是运行文件。还可以是要执行的某种动作的名称(A target can also be the name of an action to carry out),比如clean。
prerequisites就是要生成那个target所须要的文件或是目标,也就是作为输入的文件,往往是多个。也有可能不需要输入文件,比如一个删除规则clean就无需这些文件,而仅仅需要下面说的command中的东西。
command也就是make须要运行的命令(随意的Shell命令)。需要注意的是在每一天命令前要有一个tab。
当一个target是文件的时候,如果它的prerequisites中有任何一个文件发生了改动,其都需要被重新编译或链接。
一个例子 #
下面给出一个例子,假设一个工程有3个头文件,和8个C文件,我们要写一个Makefile来告诉make命令怎样编译和链接这几个文件。我们的规则是:
- 假设这个工程没有编译过,那么我们的全部C文件都要编译并被链接。
- 假设这个工程的某几个C文件被改动,那么我们仅仅编译被改动的C文件,并链接目标程序。
- 假设这个工程的头文件被改变了,那么我们须要编译引用了这几个头文件的C文件,并链接目标程序。 我们的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
在该文件夹下直接输入命令make就能够生成运行文件edit。假设要删除运行文件和全部的中间目标文件,那么,仅仅要简单地运行一下make clean就能够了。其中这个clean不依赖任何文件且仅仅是一个动作,叫做伪目标(phony targets)。
make是如何处理Makefile的 #
默认行为是:make完成makefile文件的第一个目标。比如对于上述例子,在执行make之后,会查找当前文件夹下的makefile文件并处理其第一个规则,也就是edit;但是在完全处理好这个规则之前,还需要处理这个目标所依赖的那些文件,在该例中就是那些object files。也就是说需要先把prerequisites处理好,再对目标进行编译或者链接。
设置变量来简化Makefile #
在上例中edit目标需要罗列所有的目标文件两次,在未来如果要添加一个新的目标文件,就有可能忘记添两处。为了消除这个风险和简化内容可以使用变量。按照惯例,每个Makefile都应该定义一个名为objects / OBJECTS / objs / OBJS / obj / OBJ等等来作为所有目标文件名称的总和,格式为:
objects = main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
那么在其他地方想放这些目标文件就只需要写:$(objects)来代替即可。
在替换之后,例子就可以简化为:
objects = main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
edit : $(objects)
cc -o edit $(objects)
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 $(objects)
用一个等号来定义的变量,其引用会推迟到变量被使用的时候才展开,比如:
# 例1
A = foo
B = $(A) bar # B 的值是字符串 "$(A) bar",此时并不展开 A
A = later
all:
@echo $(B) # 输出: later bar
而使用:=来定义的变量,会在定义时立即展开,比如:
# 例2
A := foo
B := $(A) bar # B 的值在定义时立即展开为 "foo bar"
A := later # 修改 A 对 B 已无影响
all:
@echo $(B) # 输出: foo bar
对于项目中的基础配置、工具路径等等优先用:=防止意外发生。
此外,命令中加上@的作用是禁止回显,在例1中输出只有B的值,假设没有@的话输出会第一行打印echo later bar然后再打印B的值。
使用隐式规则来简化Makefile #
make可以自行推导出这样的命令:cc -c main.c -o main.o,也就是说如果需要目标文件main.o,那么make会自动把main.c添加到依赖项中,那么在书写依赖性的时候就可以把main.c忽略掉。下面是利用这个规则简化后的常见的makefile内容:
objects = main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
edit : $(objects)
cc -o edit $(objects)
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
.PHONY : clean
clean :
-rm edit $(objects)
除此之外,还有另一种写法,上面这是对每一个目标罗列依赖项;还可以反过来,对每一个依赖性罗列目标:
objects = main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
edit : $(objects)
cc -o edit $(objects)
$(objects) : defs.h
kbd.o command.o files.o : command.h
display.o insert.o search.o files.o : buffer.h
.PHONY : clean
clean :
-rm edit $(objects)
上述内容中多出的.PHONE : clean声明其是一个伪目标,意味着make不会期望真的有clean为名字的文件。假设目录里面真的有这个文件,且其已经是最新的,若是没有这个声明,就会拒绝执行clean规则。-rm告诉make即使rm命令执行出错(比如要删除的文件不存在),也要继续执行下去,从而可以友好多次执行make clean。