Makefile幼儿园
程序的编译与链接
一般来说,无论是C、C++、还是pas,首先要把源文件编译成中间代码文件(参考 CSAPP 的那张图),在Windows下也就是 .obj 文件,UNIX下是 .o 文件,即 Object File,这个动作叫做编译(compile)。然后再把大量的Object File合成执行文件,这个动作叫作链接(link)
编译时,编译器需要的是语法的正确,函数与变量的声明的正确。
链接时,主要是链接函数和全局变量。在大多数时候,由于源文件太多,编译生成的中间目标文件太多,而在链接时需要明显地指出中间目标文件名,这对于编译很不方便,所以,我们要给中间目标文件打个包,在Windows下这种包叫“库文件”(Library File),也就是 .lib 文件,在UNIX下,是Archive File,也就是 .a 文件。
Makefile规则
Makefile 的书写规则:
- 如果这个工程没有编译过,那么我们的所有C文件都要编译并被链接。
- 如果这个工程的某几个C文件被修改,那么我们只编译被修改的C文件,并链接目标程序。
- 如果这个工程的头文件被改变了,那么我们需要编译引用了这几个头文件的C文件,并链接目标程序。
Makefile的规则(rules)
A simple makefile consists of “rules” with the following shape:
1 | target … : prerequisites … |
其中
target
是目标文件,可以是要生成的Object File,也可以是执行文件。还可以是要执行的动作名,如clean
prerequisites
是生成target
依赖的文件或目标。recipe
即make
要执行的命令(任何 shell 命令),即 command- 注意!每一行
recipe
命令都必须以一个[TAB]
开头而非空格!
- 注意!每一行
这是一个文件的依赖关系,target这一个或多个的目标文件依赖于prerequisites中的文件,其生成规则定义在recipe中。依赖关系的实质上就是说明了目标文件是由哪些文件生成的,换言之,目标文件是哪些文件更新的。
prerequisites中如果有一个以上的文件比target文件要新的话,recipe所定义的命令就会被执行。这就是Makefile的规则。也就是Makefile中最核心的内容。
make会比较targets文件和prerequisites文件的修改日期,如果prerequisites文件的日期要比targets文件的日期要新,或者target不存在的话,那么,make就会执行后续定义的命令。
下面这个 Makefile 文件的实例来自 GNU Make 文档 https://www.gnu.org/software/make/manual/make.html
1 | edit : main.o kbd.o command.o display.o \ |
这里的\
类似 C 中的连接两行代码,用于将太长的单行命令分成多行方便阅读
make 是如何工作的
根据此示例,我们在含有代码源文件和Makefile
文件的工作目录下只需要输入
1 | make |
- make会在当前目录下找名字叫“Makefile”或“makefile”的文件。
- 如果找到,它会找文件中的第一个目标文件(target),在上面的例子中,他会找到“edit”这个文件,并把这个文件作为最终的目标文件。
- 如果edit文件不存在,或是edit所依赖的后面的 .o 文件的文件修改时间要比edit这个文件新,那么,他就会执行后面所定义的命令来生成edit这个文件。
- 如果edit所依赖的.o文件也存在,那么make会在当前文件中找目标为.o文件的依赖性,如果找到则再根据那一个规则生成.o文件。(这有点像一个堆栈的过程)
- 如果.c文件和.h文件是存在的,make会生成 .o 文件,然后再用 .o 文件生成执行文件edit。
make会一层又一层地去找文件的依赖关系,直到最终编译出第一个目标文件。在找寻的过程中,如果出现错误,比如最后 被依赖的文件找不到,那么make就会直接退出,并报错,而对于所定义的命令的错误,或是编译不成功,make并不关心,唯一关心的是文件的依赖性。
变量
Such duplication is error-prone; if a new object file is added to the system, we might add it to one list and forget the other. We can eliminate the risk and simplify the makefile by using a variable. ——— GNU make manual
下面使用的变量同样来自 GNU make 文档
https://www.gnu.org/software/make/manual/make.html#Using-Variables
Makefile
中的变量更像是C语言中的宏,组织起赋给它的字符串:
1 | objects = main.o kbd.o command.o display.o \ |
这样原来的 Makefile 文件就可以写成
1 | objects = main.o kbd.o command.o display.o \ |
可以看到,在定义了变量名<varname>
后,只需要使用美元符号和括号包裹起来即可使用该变量
make自动推导
GNU make可以自动推导文件以及文件依赖关系后面的命令,于是我们就没必要去在每一个[.o]文件后都写上类似的命令,因为make会自动识别并自己推导命令。
只要make看到一个[.o]文件,它就会自动的把[.c]文件加在依赖关系中。比如,如果make找到一个whatever.o
,那么whatever.c
就会是whatever.o
的依赖文件。并且 cc -c whatever.c
也会被推导出来:
1 | objects = main.o kbd.o command.o display.o \ |
也就是说,所有的.o
文件的prerequisites都不再需要写上对应的.c
文件,只需要链接上用到的头文件或即可。并且如果是简单的gcc -c .c
命令,也可以省略
make clean
make clean
命令用于清除 make 生成的中间文件和目标文件,以便提交代码或者重新编译。clean
的规则在前面示例的最后部分给出,可以看到我们还添加了一行命令:.PHONY :clean
A phony target is one that is not really the name of a file; rather it is just a name for a recipe to be executed when you make an explicit request. ———GNU make manual.
这样,如果目录下有一个名为clean
的文件(一般也不会有),当执行make clean
命令时,make 不会去试图构建 clean 文件,而是执行Makefile
中的 clean
命令(也就是一个名为clean
的文件不会混淆 make 工具)
clean的规则不要放在文件的开头,不然,这就会变成make的默认目标。不成文的规矩是—— “clean从来都是放在文件的最后”。
Since
clean
is not a prerequisite ofedit
, this rule will not run at all if we give the command ‘make’ with no arguments. In order to make the rule run, we have to type ‘make clean’. See How to Run make. ———GNU make manual.
Makefile 总述
Makefile 的组成
Makefile里主要包含了五个东西:显式规则、隐晦规则、变量定义、文件指示和注释。
- 显式规则。显式规则说明了,如何生成一个或多的的目标文件。这是由Makefile的书写者明显指出,要生成的文件,文件的依赖文件,生成的命令。
- 隐晦规则。由于我们的make有自动推导的功能,所以隐晦的规则可以让我们比较粗糙地简略地书写Makefile,这是由make所支持的。
- 变量的定义。在Makefile中我们要定义一系列的变量,变量一般都是字符串,这个有点你C语言中的宏,当Makefile被执行时,其中的变量都会被扩展到相应的引用位置上。
- 文件指示。其包括了三个部分,一个是在一个Makefile中引用另一个Makefile,就像C语言中的include一样;另一个是指根据某些情况指 定Makefile中的有效部分,就像C语言中的预编译#if一样;还有就是定义一个多行的命令。有关这一部分的内容,我会在后续的部分中讲述。
- 注释。Makefile中只有行注释,和UNIX的Shell脚本一样,其注释是用“#”字符,这个就像C/C++中的“//”一样。如果你要在你的Makefile中使用“#”字符,可以用反斜框进行转义,如:“/#”。
注:Makefile 中的命令总是以[TAB]
开始。
Makefiles contain five kinds of things: explicit rules, implicit rules, variable definitions, directives, and comments. Rules, variables, and directives are described at length in later chapters.
- An explicit rule says when and how to remake one or more files, called the rule’s targets. It lists the other files that the targets depend on, called the prerequisites of the target, and may also give a recipe to use to create or update the targets. See Writing Rules.
- An implicit rule says when and how to remake a class of files based on their names. It describes how a target may depend on a file with a name similar to the target and gives a recipe to create or update such a target. See Using Implicit Rules.
- A variable definition is a line that specifies a text string value for a variable that can be substituted into the text later. The simple makefile example shows a variable definition for
objects
as a list of all object files (see Variables Make Makefiles Simpler). - A directive is an instruction for
make
to do something special while reading the makefile. These include:- Reading another makefile (see Including Other Makefiles).
- Deciding (based on the values of variables) whether to use or ignore a part of the makefile (see Conditional Parts of Makefiles).
- Defining a variable from a verbatim string containing multiple lines (see Defining Multi-Line Variables).
- ‘#’ in a line of a makefile starts a comment. It and the rest of the line are ignored, except that a trailing backslash not escaped by another backslash will continue the comment across multiple lines. A line containing just a comment (with perhaps spaces before it) is effectively blank, and is ignored. If you want a literal
#
, escape it with a backslash (e.g.,\#
). Comments may appear on any line in the makefile, although they are treated specially in certain situations.
文件名一定是 Makefile?
默认的情况下,make命令会 在当前目录下按顺序找寻文件名为“GNUmakefile”、“makefile”、“Makefile”的文件。
显然,GNUmakefile 这个名字只会由 GNU make 自动识别,一般推荐文件命名为 Makefile(比较显眼)
If you want to use a nonstandard name for your makefile, you can specify the makefile name with the ‘-f’ or ‘—file’ option. The arguments ‘-f name’ or ‘—file=name’ tell make
to read the file name as the makefile.如:make -f Make.Linux
或make --file Make.AIX
。
make 是如何工作的
读入所有的Makefile。
读入被include的其它Makefile。
初始化文件中的变量。
推导隐晦规则,并分析所有规则。
为所有的目标文件创建依赖关系链。
根据依赖关系,决定哪些目标要重新生成。
执行生成命令。
Makefile 书写规则
The order of rules is not significant, except for determining the default goal: the target for make
to consider, if you do not otherwise specify one. The default goal is the first target of the first rule in the first makefile
1 | foo.o : foo.c defs.h # module for twiddling the frobs |
This rule says two things:
- How to decide whether foo.o is out of date: it is out of date if it does not exist, or if either foo.c or defs.h is more recent than it.
- How to update the file foo.o: by running
cc
as stated. The recipe does not explicitly mention defs.h, but we presume that foo.c includes it, and that is why defs.h was added to the prerequisites.
语法
In general, a rule looks like this:
1 | targets : prerequisites |
or like this:
1 | targets : prerequisites ; recipe |
通配符
如果我们想定义一系列比较类似的文件,可以使用通配符。make支持三种通配符:*
,?
和[...]
。这是和Unix的B-Shell是相同的。
文件搜寻VPATH & vpath
VPATH
make默认只在当前的目录中去找寻依赖文件和目标文件。如果指明VPATH
的值,make就会在当当前目录找不到的情况下,到所指定的目录中去找寻文件
1 | VPATH = src:../headers |
上例指定了两个目录:src
和../headers
,目录间由:
分割
vpath
另一个设置文件搜索路径的方法是使用make的vpath
关键字(注意,它是全小写的)
- vpath < pattern> < directories>
为符合模式< pattern>的文件指定搜索目录< directories>。 - vpath < pattern>
清除符合模式< pattern>的文件的搜索目录。 - vpath
清除所有已被设置好了的文件搜索目录。
vapth使用方法中的<pattern>
需要包含%
字符。%
的意思是匹配零或若干字符,例如,%.h
表示所有以.h
结尾的文件。<pattern>
指定了要搜索的文件集,而<directories>
则指定了的文件集的搜索的目录。
1 | vpath %.h ../headers |
该语句表示,要求make在../headers
目录下搜索所有以.h
结尾的文件。
自动变量
自动变量是提前约定好的具有特定含义的变量,可以代表特定的文件或其他东西。这样做的好处是不必将所有静态文件名硬编码进 Makefile 中,以避免对文件的重命名或其他删改操作导致Makefile 还需要较为麻烦的修改
In particular, you cannot use them anywhere within the target list of a rule; they have no value there and will expand to the empty string.
https://www.gnu.org/software/make/manual/make.html#Automatic-Variables
$@
The file name of the target of the rule. 即规则的目标文件名。$@
is the name of whichever target caused the rule’s recipe to be run.
$%
The target member name, when the target is an archive member.仅当目标是函数库文件(.a)文件时,表示规则中目标成员名。如一个目标是(test.a(a.o)),此时$%表示a.o, $@表示test.a
$<
The name of the first prerequisite.
$^
The names of all the prerequisites, with spaces between them.表示所有依赖文件的集合
$+
This is like ‘$^’, but prerequisites listed more than once are duplicated in the order they were listed in the makefile. 表示所有依赖文件的集合(不去重),保留了依赖文件中重复出现的文件
$?
The names of all the prerequisites that are newer than the target, with spaces between them. 即所有比目标新的依赖目标的集合,或者说所有在目标依赖中被修改过的文件
函数
调用语法
1 | $(<function> <arguments>) |
示例:
1 | comma:= , |
在这个示例中, $(comma)
的值是一个逗号。 $(space)
使用了 $(empty)
定义了一个空格, $(foo)
的值是 a b c
, $(bar)
的定义用,调用了函数 subst
,这是一个替换函数,这个函数有三个参数,第一个参数是被替换字串,第二个参数是替换字串,第三个参数是替换操作作用的字串。这个函数也就是把 $(foo)
中的空格替换成逗号,所以 $(bar)
的值是 a,b,c
。
展开通配符函数wildcard
通配符(wildcard)会在规则中自动展开,但不会再设置变量时或者函数的参数内部展开。如果想要在这些地方展开通配符,就需要wildcard
函数:
wildcard
函数的一个用法是获得含有所有C源文件的列表:
1 | $(wildcard *.c) |
或者通过替换.c
为.o
,把结果中所有C源文件改为目标文件:
1 | $(patsubst %.c,%.o,$(wildcard *.c)) |
下面的规则可以编译目录中所有C源文件并链接:
1 | objects := $(patsubst %.c,%.o,$(wildcard *.c)) |
(This takes advantage of the implicit rule for compiling C programs, so there is no need to write explicit rules for compiling the files. See The Two Flavors of Variables, for an explanation of ‘:=’, which is a variant of ‘=’.)
其他特性(自行参考手册)
.PHONY
https://www.gnu.org/software/make/manual/make.html#Phony-Targets
多目标
https://www.gnu.org/software/make/manual/make.html#Multiple-Targets
一个显式的规则可以有多个目标,直接上例子:
- 只有
prerequisties
,没有recipe
1 | kbd.o command.o files.o: command.h |
等价于
1 | kbd.o: command.h |
- 对于所有的
targets
都有相似的recipes
,可以使用$@
代替targets
:
1 | bigoutput littleoutput : text.g |
等价于
1 | bigoutput : text.g |
其中,-$(subst output,,$@)
中的$
表示执行一个Makefile的函数,函数名为subst
,后面的为参数。这里的这个函数是截 字符串的意思,$@
表示目标的集合,就像一个数组,$@
依次取出目标,并执于命令。
静态模式
https://www.gnu.org/software/make/manual/make.html#Static-Pattern
静态模式是一种自动编译模式,可以定义多规则目标,语法如下:
1 | targets …: target-pattern: prereq-patterns … |
targets定义了一些列的目标文件,也就是多目标,可以有通配符,是目标的一个集合。
target-pattern 是targets的模式,也就是目标集模式
prereq-patterns 则是目标的“依赖”元素
一般将target-pattern
定义为%.o
,意为target
集合都以.o
为后缀。
prereq-patterns
则定义为%.c
,这意思就是对 target-pattern
中所形成的目标集进行二次定义,其计算方法是取target-pattern
模式中的%
代表部分(其实就是去掉.o
后的文件名),并为其加上[.c]
结尾,形成新的集合。
示例如下
1 | $(OBJS): %.o: %.c |
这两条命令的功能就是,大目标是OBJS
,这个OBJS
就是各种.o文件,然后%.o
就是具体的解释,而%.c
就是对应同样名字的.c
文件,<
表示依赖对象集中的第一个,@
则代表了目标集。所以这个功能就是要遍历所有的.c
文件,对所有的.c
文件进行编译,然后编译生成对应的.o
文件。我们在实际编写程序时,targets
是不需要的,可以简写如下:
1 | %.o: %.c |
自动生成依赖性
https://www.gnu.org/software/make/manual/make.html#index-automatic-generation-of-prerequisites
make中的函数
https://www.gnu.org/software/make/manual/make.html#Functions