前言:
以下文档参考:
https://www.gnu.org/software/make/manual/make.html#toc-Overview-of-make
https://seisman.github.io/how-to-write-makefile/introduction.html
这个文件记录了GNU make实用程序,它自动地确定一个大型程序的哪些部分需要重新编译,并发出重新编译它们的命令
适用版本:
This is Edition 0.75, last updated 17 January 2020, of The GNU Make Manual, for GNU make version 4.3.
make的基本功能和使用简介:
make实用程序自动确定大型程序的哪些部分需要重新编译,并发出命令重新编译它们。本手册描述了由Richard Stallman和Roland McGrath实现的GNU make。从3.76版本开始的开发由Paul D. Smith负责
GNU make符合IEEE标准1003.2-1992 (POSIX.2)第6.2节。
我们的例子展示了C程序,因为它们是最常见的,但是您可以使用make与任何可以用shell命令运行编译器的编程语言一起使用。事实上,make并不局限于程序。您可以使用它来描述任何任务,其中一些文件必须在其他文件发生更改时自动更新其他文件。
makefile是什么样的文件:
make命令执行时,需要一个makefile文件,以告诉make命令需要怎么样的去编译和链接程序。
要准备使用make,您必须编写一个名为makefile的文件,该文件描述程序中文件之间的关系,并提供用于更新每个文件的命令。通常,在一个程序中,可执行文件从目标文件中更新,而目标文件又通过编译源文件而生成。
一旦有了合适的makefile,每次你修改一些源文件时,这个简单的shell命令: make
足够执行所有必要的重新编译。make程序使用makefile数据库和文件的最后修改时间来决定哪些文件需要更新。对于这些文件中的每一个,它都会发出在数据库中记录的recipes
makefiles介绍:
您需要一个名为makefile的文件来告诉make做什么。通常,makefile告诉make如何编译和链接程序。
1 若一个头文件变了,那每个包含该头文件的c源文件必须被重新编译
2 当make开始重新编译时,每个改变过的c源文件必须被重新编译;
3 每个编译过程产生一个目标文件关联源文件;
4 最后,若任一源文件发生重编,全部目标文件,必须重新连接产生新的可执行目标程序,不管这个时候是新产生还是覆盖之前的编译保存的;
makefile中的规则:
每个规则由以下三部分组成:
1 | target … : prerequisites … |
一个简单的例子:
1 | edit : main.o kbd.o command.o display.o \ |
解释
In fact, each ‘.o’ file is both a target and a prerequisite.
Recipes include ‘cc -c main.c’ and ‘cc -c kbd.c’.
main.o 也可以通过make main.o生成
什么时候会真的重编译链接:注意当一个目标是文件时,若任何它的依赖改变,它需要被重新编译或链接;
并且:任何依赖自身也应该先被自动更新再生成;
When a target is a file, it needs to be recompiled or relinked if any of its prerequisites change
In addition, any prerequisites that are themselves automatically generated should be updated first.
生成规则是由使用者定的,make会执行给定的规则中的每一行:
比如下面给了两行,当main.o发生改变时,make的时候就会执行下面两个gcc
1 | main: main.o |
phony targets
虚假目标的概念,不依赖其他文件,执行时需要明确指出目标如: make clean
default goal.
make会把第一个目标当成默认目标,则 注意位置:
1 | hh.o: hh.cpp hh.h |
这样make的时候,是只会执行gcc -c hh.cpp 注意这里hh.o不依赖main
- 规则的递归依赖重生成 解释下:makefile如下 重要!!!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27think@think-VirtualBox:~/c++$ cat makefile
main: hh.o main.o
gcc -o main main.o hh.o
hh.o: hh.cpp hh.h
gcc -c hh.cpp
main.o: main.cpp
gcc -c main.cpp
首次make
think@think-VirtualBox:~/c++$ make
gcc -c main.cpp
gcc -o main main.o hh.o
改变hh.h,重新生成hh.o和main
think@think-VirtualBox:~/c++$ vim hh.h
think@think-VirtualBox:~/c++$ make
gcc -c hh.cpp
gcc -o main main.o hh.o
删除main.o make重新生成main.o和main,不改变hh.o
think@think-VirtualBox:~/c++$ rm -rf main.o
think@think-VirtualBox:~/c++$ make
gcc -c main.cpp
gcc -o main main.o hh.o
修改main.cpp,重新生成main.o和main
think@think-VirtualBox:~/c++$ vim main.cpp
think@think-VirtualBox:~/c++$ make
gcc -c main.cpp
gcc -o main main.o hh.o
think@think-VirtualBox:~/c++$
But make would update automatically generated C programs, such as those made by Bison or Yacc, by their own rules at this time.
特例,暂时没用上
Thus, if we change the file insert.c and run make, make will compile that file to update insert.o, and then link edit. If we change the file
command.h and run make, make will recompile the object files kbd.o, command.o and files.o and then link the file edit.
makefile中的变量使用:
We would define such a variable objects with a line like this in the makefile:
1 | objects = main.o kbd.o command.o display.o \ |
makefile中的隐式规则:
上述的可以简化为:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19implicit rule for updating a ‘.o’ file from a correspondingly named ‘.c’ file using a ‘cc -c’ command.
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)继续简化:
1
2
3
4
5
6
7
8
9objects = 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是伪目标
This prevents make from getting confused by an actual file called clean and causes it to continue in spite of errors from rm.
(See Phony Targets, and Errors in Recipes.)
如何写makefile?
make依赖读取一个叫makefile的数据库中的信息来知道怎么重新编译一个系统
makefile文件包含什么:
组成:显示规则,隐式规则,变量定义,指令和注释
显示规则:说明什么时候和怎么去重新make一个或多个称为目标的文件;它列出了目标依赖的所有文件,即prerequisites,它也给出怎么创建和更新目标的指令如gcc
隐式规则:说明什么时候和怎么去重新make一类文件基于他们的名字,它描述了目标如何依赖于名称与目标类似的文件,并给出了创建或更新这样一个目标的方法。
变量:是为以后可以替换到文本中的变量指定文本字符串值的一行。
指令是哪些呢?1
2
3读取另一个makefile
类似条件判断,忽略一部分逻辑
从包含多行的逐字字符串定义变量关于注释: # 开头,\ 可以跨行注释,不能在变量中用#,# 可以起到在字符中转义的作用;
makefile对行的处理:
makefile使用一种“基于行”的语法,其中换行符是特殊的,并标记语句的结束。GNU make对语句行的长度没有限制,直到您计算机中的内存大小。
通过\反斜杠转义内部的换行符,从而延长一个行;以换行符结束(不管是否转义)作为物理行,以完整语句直到非转义换行符作为逻辑行;
eg:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18main: edit.o main.o \ 物理行
k.o # 逻辑行
gcc -o main main.o k.o edit.o
注意:指令行:recipe行可能处理有点不同,对非指令行来讲,换行时多个连续的空格会被压缩成一个;
一个小技巧:
若你不想在通过\续行后,出现空格,比如:
var := one\
word
后解析为:
var := one word
则可以写成这样:
var := one$\
word
这样会得到:
var := oneword
原因:美元符号加\ 在make将\和前面的空格压缩为一个空格后为: one$ word, 而之后,'$ '会被make识别为' '为名的变量,而这个变量不存在,就会用空字符串替代
变成 var := oneword
makefile文件的命名;
makefile文件可以命名为:makefile/Makefile/GNUmakefile,推荐第二种;不推荐第三种,因为其他make程序不认;
如果不想用默认的,即make时会默认找上面的三种文件,找不到又没有指定,则无法make,可以通过-f /–file 来指定make的文件;
如make -f mymakefile;还可以指定多个: make -f mymakefile -f yourmakefile;
一个makefile文件如何使用其他makefile文件
语法:
include filename…
1 | eg: |
如果指定的名称不以斜杠开头,并且在当前目录中找不到该文件,则会搜索其他几个目录。
首先,搜索使用’ -I ‘或’——include-dir ‘选项指定的任何目录。
然后搜索以下目录(如果它们存在的话),顺序如下:prefix/include(通常是/usr/local/include 1) /usr/gnu/include, /usr/local/include, 找不到则警告,若不想要警告信息,可以用这个语法:
-include filenames…
MAKEFILES变量;类似于include
如果你的当前环境中定义了环境变量 MAKEFILES ,那么,make会把这个变量中的值做一个类似于 include 的动作。这个变量中的值是其它的Makefile,用空格分隔。只是,它和 include 不同的是,从这个环境变量中引入的Makefile的“目标”不会起作用,如果环境变量中定义的文件发现错误,make也会不理。
但是在这里我还是建议不要使用这个环境变量,因为只要这个变量一被定义,那么当你使用make时,所有的Makefile都会受到它的影响,这绝不是你想看到的。
在这里提这个事,只是为了告诉大家,也许有时候你的Makefile出现了怪事,那么你可以看看当前环境中有没有定义这个变量。
overrding makefiles
有时,拥有一个与另一个makefile基本相同的makefile是很有用的。
你可以经常使用’ include ‘指令将一个包含到另一个中,并添加更多的目标或变量定义。但是,两个makefile为同一个目标提供不同的recipes(指令)是无效的。但还有另一种方法。
make如何读取makefile
GNU的make工作时的执行步骤如下:(想来其它的make也是类似)
1 读入所有的Makefile。
2 读入被include的其它Makefile。
3 初始化文件中的变量。
4 推导隐晦规则,并分析所有规则。
5 为所有的目标文件创建依赖关系链。
6 根据依赖关系,决定哪些目标要重新生成。
7 执行生成命令。
1-5步为第一个阶段,6-7为第二个阶段。第一个阶段中,如果定义的变量被使用了,那么,make会把其展开在使用的位置。但make并不会完全马上展开,make使用的是拖延战术,如果变量出现在依赖关系的规则中,那么仅当这条依赖被决定要使用了,变量才会在其内部展开。
当然,这个工作方式你不一定要清楚,但是知道这个方式你也会对make更为熟悉。有了这个基础,后续部分也就容易看懂了。
make如何解析makefile
How and when secondary expansion is performed.
makefile的规则书写
如果默认目标没有指定,则默认第一个规则的第一个目标为默认目标,有两个例外,以句号开头的目标除非包含/ ;
所以我们经常用第一个规则来编整个程序或者以all: 描述整个程序;
Rule的例子
一个例子和解释:
1 | foo.o : foo.c defs.h # module for twiddling the frobs |
这个规则说明:
- 如何确定foo.o过期了: 它不存在或者foo.c/defs.h 比它更新
- 如何更新foo.o: 通过运行cc,这个规则没有显示指定defs.h,但是c文件肯定包含,需要加到依赖;
Rule语法
1 | targets : prerequisites |
- 目标: 一般是文件名,由空格分割,可以使用通配符,并且a(m)代表归档文件中a中的成员m,通常一个规则一个目标,也可能有多个;
- 规则: 以tab开头,可以是上面的两种方式,注意分号;
- 变量:因为变量是以美元符号$开头的,所以若要在目标和依赖中写$,则必须用两个$,如$$.若已经使用了二次扩展,且想在其中放$,则要用四个$
- 反斜杠用来延长行;
- 指令其实不止是gcc,还可以是其他shell指令
依赖文件的类型
一般的任何依赖更新了,目标都要更新;
Wildcards(通配符)用法
单个文件名可以使用通配符匹配多个文件,在make中,通配符有:*,?,[…]等,和shell相同,如*.c表示所有.c结尾的文件;
文件名开头的“”字符也具有特殊意义,/bin表示主目录下的bin,~joh/bin表示/home/joh/bin,即主目录名为joh
通配符扩展由make在目标和先决条件中自动执行。在recipes中,shell负责通配符扩展。在其他情况下,通配符扩展只有在使用通配符函数显式请求时才会发生
可以通过反斜杠来转义通配符;
这里用的是os中的shell所支持的通配符;
通配符的例子:
1 | 在指令中使用,由shell扩展: |
通配符的陷阱,比如变量中的通配符需要加wildcard,往往忘记;
通配符函数:
通配符扩展在规则中自动发生。但是,当设置了变量或函数的参数时,通配符展开通常不会发生。如果你想在这些地方进行通配符扩展,你需要使用通配符函数,像这样:
1 | $(wildcard pattern…) |
一般我们可以使用“$(wildcard *.c)”来获取工作目录下的所有的.c文件列表。复杂一些用法;可以使用“$(patsubst %.c,%.o,$(wildcard *.c))”,
首先使用“wildcard”函数获取工作目录下的.c文件列表;之后将列表中所有文件名的后缀.c替换为.o。这样我们就可以得到在当前目录可生成的.o文件列表。
因此在一个目录下可以使用如下内容的Makefile来将工作目录下的所有的.c文件进行编译并最后连接成为一个可执行文件:
1 | objects := $(patsubst %.c,%.o,$(wildcard *.c)) |
目录搜索,为源文件搜索其他路径
对一些大型系统,常常将源文件放在各种分离的目录下,则目录搜索作为make的一个有用特性,可以轻易搜索一些目录,自动找到依赖;当在这些目录中重新分配文件时,不需要改规则,只需要改搜索路径;
需要注意的是,make的搜索,和指令中的编译器等搜索不同,虽然你在make中指定了,但是如果头文件在不同的目录,使用gcc时,
或者通过#include的时候,协商所有路径,否则,需要指定-I gcc -I dir
make这里的搜索,更多的体现在依赖文件,指令中指定的文件等
整体搜索
具体化一个搜索路径应用到所有依赖;
make uses VPATH as a search list for both prerequisites and targets of rules.
所以,如果列在目标或依赖中的文件不在当前的目录,make会在VPATH定义的目录里面找文件名,找到就算;仿佛它们存在当前的目录一样;
VPATH定义的目录,由空格或冒号隔开:
eg: VPATH = src:../headers
所以假设没有在当前目录找到,在src中找到,则:这样的规则:
foo.o:foo.c ===> foo.o:src/foo.c
注意顺序,即会先从本地找,没有再src找,没有再../headers中找;
选择性搜索
为一类名字具体化一个搜索路径
使用vpath,注意是小写,允许你具体化一类文件名,只要匹配上具体的模式;因此你可以指定一类名字匹配这个目录,其他名字匹配其他目录:
有三种组成:
1 | 1) vpath pattern directories : 具体化搜索目录为那些匹配上这个pattern的文件名;注意目录间隔是冒号 |
搜索算法
搜索路径什么时候,如何被应用;
当通过目录搜索找到先决条件时,不管类型是什么(通用的还是选择性的),所找到的路径名可能不是make在先决条件列表中实际提供给您的路径名。有时,通过目录搜索发现的路径被丢弃。
make通过如下的信息来决定是否保持和丢弃一个路径:
1 | 1)如果在makefile中指定的路径上不存在目标文件,则执行目录搜索。 |
对其他版本的make也是类似的,如果想要做到在搜索到的路径下生成目标:如:
如果文件不存在,并且通过目录搜索找到了它,那么无论是否需要构建目标,都会使用该路径名。因此,如果重新构建目标,则在目录搜索期间发现的路径名处创建目标。
则使用GPATH:
具有与VPATH相同的语法和格式(即以空格或冒号分隔的路径名列表)。如果通过目录搜索在GPATH中也出现的目录中找到一个过期的目标,那么该路径名不会被丢弃。使用扩展的路径重新生成目标。
指令搜索: 如何写一个指令能和搜索路径一起工作
比如:你的依赖通过目录搜索在其他路径中被找到,但是在指令中,写的依赖文件的路径还是原来的,那么如何改变使得make可以在指令执行时找到正确的依赖呢?
这是通过自动变量完成的,比如’ $^ ‘(参见自动变量)。例如,’ $^ ‘的值是规则的所有先决条件的列表,包括在其中找到它们的目录名,’ $@ ‘的值是目标
1 | foo.o : foo.c |
如果你只想要依赖中的第一个文件,比如下面的不要依赖头文件,则:
1 | VPATH = src:../headers |
隐式规则索索;
搜索路径如何影响隐式规则,如果没有声明显示规则,那make会使用隐式规则,若在当前目录找不到,则会去VPATH/vpath指定的路径搜索,找到则会被应用;
链接库搜索;
链接库的目录搜索,当依赖中有库文件时,可以使用-lname的方式来指定依赖:
当依赖中的名字有类似 ‘-lname’,则make会在特别的在当前目录搜索文件libname.so,如果找不到,就在当前目录找libname.a;然后找不到继续在VPATH/vpath/ /usr/lib等找;
比如:有文件/usr/lib/libcurses.a ,但是没有so则
1 | foo : foo.c -lcurses |
会找到.a ,执行‘cc foo.c /usr/lib/libcurses.a -o foo’
解释下:尽管要搜索的默认文件集是libname。所以,库名.a,这是通过. libpatterns变量定制的。这个变量值中的每个单词都是一个模式字符串。
当看到像’ -lname ‘这样的先决条件时,make将用name替换列表中每个模式中的百分比,并使用每个库文件名执行上述目录搜索。
.LIBPATTERNS is ‘lib%.so lib%.a’
伪目标,强制目标 空目标 特殊目标,多目标
伪目标
虚假目标不是真正的文件名;相反,它只是在发出显式请求时要执行的配方的名称。使用假目标有两个原因:避免与同名文件的冲突,以及提高性能。
因为rm命令不会创建一个名为clean的文件,所以可能永远不会存在这样的文件。因此,每当您说“make clean”时,rm命令将被执行。
1 | clean: |
在本例中,如果在此目录中创建了一个名为clean的文件,则clean目标将不能正常工作。因为它没有先决条件,
所以clean总是被认为是最新的,它的指令不会被执行。为了避免这个问题,您可以显式地声明目标为假的,方法是将它作为特殊目标.PHONY的依赖,如下所示:
1 |
|
一旦完成,’ make clean ‘将运行指令,而不管是否有一个名为clean的文件
假目标在与make的递归调用结合使用时也很有用(请参阅make的递归使用)。在这种情况下,
makefile通常会包含一个变量,其中列出了许多要构建的子目录。用一个循环来处理,像这样:
1 | SUBDIRS = foo bar baz |
然而,这种方法存在一些问题。首先,在子make中检测到的任何错误都会被该规则忽略,所以即使有一个失败了,它也会继续构建其余的目录。这可以通过添加shell命令来注意错误和退出来克服,
但是即使使用-k选项调用make,它也会这样做,这是不幸的。其次,也许是更重要的一点,您不能利用make的能力并行构建目标(请参阅并行执行),因为只有一条规则。
通过声明子目录为.PHONY目标(你必须这样做,因为子目录显然总是存在的;否则它不会被建立)你可以移除上述问题:
1 | SUBDIRS = foo bar baz |
这样的话,就需要baz先完成,才能完成foo;
以上也可以用伪目标来实现:
伪目标一般没有依赖的文件。但是,我们也可以为伪目标指定所依赖的文件。伪目标同样可以作为“默认目标”,只要将其放在第一个。一个示例就是,如果你的Makefile需要一口气生成若干个可执行文件,
但你只想简单地敲一个make完事,并且,所有的目标文件都写在一个Makefile中,那么你可以使用“伪目标”这个特性:
1 | all : prog1 prog2 prog3 |
我们知道,Makefile中的第一个目标会被作为其默认目标。我们声明了一个“all”的伪目标,其依赖于其它三个目标。由于默认目标的特性是,总是被执行的,但由于“all”又是一个伪目标,
伪目标只是一个标签不会生成文件,所以不会有“all”文件产生。于是,其它三个目标的规则总是会被决议。也就达到了我们一口气生成多个目标的目的。 .PHONY :
all 声明了“all”这个目标为“伪目标”。(注:这里的显式“.PHONY : all” 不写的话一般情况也可以正确的执行,这样make可通过隐式规则推导出, “all” 是一个伪目标,
执行make不会生成“all”文件,而执行后面的多个目标。建议:显式写出是一个好习惯。)
随便提一句,从上面的例子我们可以看出,目标也可以成为依赖。所以,伪目标同样也可成为依赖。看下面的例子:
1 | .PHONY : cleanall cleanobj cleandiff |
“make cleanall”将清除所有要被清除的文件。
“cleanobj”和“cleandiff”这两个伪目标有点像“子程序”的意思。我们可以输入“make cleanall”和“make cleanobj”和“make cleandiff”命令来达到清除不同种类文件的目的。
强制目标:没有依赖或规则的目标:
如果一个规则没有依赖或指令,并且规则的目标是一个不存在的文件,那么make就假设这个目标在其规则运行时已被更新。这意味着依赖于此的所有目标都将始终运行它们的指令。
1 | clean: FORCE |
在这里,目标“FORCE”满足了特殊条件,所以依赖于它的目标clean将被迫运行其指令。“FORCE”这个名称并没有什么特别之处,但这是一个常用的名称。
空目标文件用来记录事件
1 | print: foo.c bar.c |
有了这个规则,如果任何一个源文件在上次’ make print ‘之后发生了变化,’ make print ‘将执行lpr命令。自动变量’ $?’仅用于打印那些已更改的文件
具体的内建目标名
.PHONY/.SUFFIXES/.DEFAULT/.PRECIOUS/.INTERMEDIATE/.SECONDARY/.SECONDEXPANSION/…
一个规则中的多个目标
1 | eg: |
多个规则
一个目标,多个规则;
Makefile的规则中的目标可以不止一个,其支持多目标,有可能我们的多个目标同时依赖于一个文件,并且其生成的命令大体类似。于是我们就能把其合并起来。当然,
多个目标的生成规则的执行命令不是同一个,这可能会给我们带来麻烦,不过好在我们可以使用一个自动化变量 $@ (关于自动化变量,将在后面讲述),
这个变量表示着目前规则中所有的目标的集合,这样说可能很抽象,还是看一个例子吧。
1 | bigoutput littleoutput : text.g |
静态模式
静态模式规则的语法:
当需要定义多个目标规则,且他们都类似时,可以用静态模式避免写很多规则,就像正则表达式一样;
它的语法:
1 | <targets ...> : <target-pattern> : <prereq-patterns ...> |
eg:
1 | objects = foo.o bar.o |
上面的例子中,指明了我们的目标从$object中获取, %.o 表明要所有以 .o 结尾的目标,也就是 foo.o bar.o ,也就是变量 $object 集合的模式,而依赖模式 %.c 则取模式 %.o 的 % ,
也就是 foo bar ,并为其加下 .c 的后缀,于是,我们的依赖目标就是 foo.c bar.c 。而命令中的 $< 和 $@ 则是自动化变量, $< 表示第一个依赖文件, $@ 表示目标集(也就是“foo.o bar.o”)。
于是,上面的规则展开后等价于下面的规则:
1 | foo.o : foo.c |
所以对于很多需要这样生成的,可以省去很多,提高效率
另一个例子:eg:
1 | files = foo.elc bar.o lose.o |
$(filter %.o,$(files))表示调用Makefile的filter函数,过滤“$files”集,只要其中模式为“%.o”的内容。
自动化生成先决条件:How to automatically generate rules giving prerequisites from source files themselves.
在Makefile中,我们的依赖关系可能会需要包含一系列的头文件,比如,如果我们的main.c中有一句 #include “defs.h” ,那么我们的依赖关系应该是:
main.o : main.c defs.h
但是,如果是一个比较大型的工程,你必需清楚哪些C文件包含了哪些头文件,并且,你在加入或删除头文件时,也需要小心地修改Makefile,这是一个很没有维护性的工作。
为了避免这种繁重而又容易出错的事情,我们可以使用C/C++编译的一个功能。大多数的C/C++编译器都支持一个“-M”的选项,即自动找寻源文件中包含的头文件,并生成一个依赖关系。
例如,如果我们执行下面的命令:
cc -M main.c
输出:
main.o : main.c defs.h
于是由编译器自动生成的依赖关系,这样一来,你就不必再手动书写若干文件的依赖关系,而由编译器自动生成了。需要提醒一句的是,如果你使用GNU的C/C++编译器,你得用 -MM 参数,不然,
-M 参数会把一些标准库的头文件也包含进来。
gcc -M main.c的输出是:
1 | main.o: main.c defs.h /usr/include/stdio.h /usr/include/features.h \ |
gcc -MM main.c的输出则是:main.o: main.c defs.h
那么,编译器的这个功能如何与我们的Makefile联系在一起呢。因为这样一来,我们的Makefile也要根据这些源文件重新生成,让 Makefile自已依赖于源文件?这个功能并不现实,
不过我们可以有其它手段来迂回地实现这一功能。GNU组织建议把编译器为每一个源文件的自动生成的依赖关系放到一个文件中,为每一个 name.c 的文件都生成一个 name.d 的Makefile文件,
.d 文件中就存放对应 .c 文件的依赖关系。
于是,我们可以写出 .c 文件和 .d 文件的依赖关系,并让make自动更新或生成 .d 文件,并把其包含在我们的主Makefile中,这样,我们就可以自动化地生成每个文件的依赖关系了。
这里,我们给出了一个模式规则来产生 .d 文件:
1 | %.d: %.c |
这个规则的意思是,所有的 .d 文件依赖于 .c 文件, rm -f $@ 的意思是删除所有的目标,也就是 .d 文件,第二行的意思是,为每个依赖文件 $< ,也就是 .c 文件生成依赖文件,
$@ 表示模式 %.d 文件,如果有一个C文件是name.c,那么 % 就是 name , $$$$ 意为一个随机编号,第二行生成的文件有可能是“name.d.12345”,第三行使用sed命令做了一个替换,
关于sed命令的用法请参看相关的使用文档。第四行就是删除临时文件。
总而言之,这个模式要做的事就是在编译器生成的依赖关系中加入 .d 文件的依赖,即把依赖关系:
main.o : main.c defs.h
转成
main.o main.d : main.c defs.h
于是,我们的 .d 文件也会自动更新了,并会自动生成了,当然,你还可以在这个 .d 文件中加入的不只是依赖关系,包括生成的命令也可一并加入,让每个 .d 文件都包含一个完赖的规则。
一旦我们完成这个工作,接下来,我们就要把这些自动生成的规则放进我们的主Makefile中。我们可以使用Makefile的“include”命令,来引入别的Makefile文件(前面讲过),例如:
1 | sources = foo.c bar.c |
上述语句中的 $(sources:.c=.d) 中的 .c=.d 的意思是做一个替换,把变量 $(sources) 所有 .c 的字串都替换成 .d ,关于这个“替换”的内容,在后面我会有更为详细的讲述。
当然,你得注意次序,因为include是按次序来载入文件,最先载入的 .d 文件中的目标会成为默认目标。
一个综合的例子:
1 | #VPATH = |
规则中指令如何写?
指令是一个或多个可以执行的shell命令行组成,按顺序执行。而执行的结果,一般就是更新目标文件;
使用者使用很多不同的shell指令,但在makefile中总是被/bin/sh解释,除非被配置其他;
指令语法–指令语法特性和陷阱
tab开头(或者由.RECIPEPREFIX 定义的字符,#开头为注释
makefiles中其实有两个不同的语法,一个是make自己的,一个是shell,其实只会在解释指令时,make做很小的解释后,交给shell
- 有几个个共同的特性:
1
2
3
41) 空格开头的空白行,不是一个空行,是一个空指令
2) 一个注释不是make的注释,是make传递给shell,shell会根据自己的语法,来判断它是不是注释
3) 在指令中(rule context)中定义的变量,不是make的变量,是被当做rule的一部分,被传递到shell中;
4) 条件表达式表现也和3)一样 - 如何换行(同个逻辑行物理行换行)
在makefile中,反斜杠也可以在指令中使用,但是是传递给shell去判断的,所以支持与否取决于你的shell工具
一个例子:注意如果反斜杠在字符串中,则可能会被忽略;1
2
3
4
5
6
7
8
9
10ksance0@ubuntu:~/makefiletest$ make
no hspace
notother
ksance0@ubuntu:~/makefiletest$ cat makefile
all:
@echo no h\
space
@echo not\
other
ksance0@ubuntu:~/makefiletest$ - 如何在指令中使用变量:
指令中的变量和函数引用与makefile中其他地方的引用具有相同的语法和语义。他们也有相同的引用规则,如果你想在指令中使用$符号,你需要使用双$如下,
因为在make中引用要用$,在传递给shell前剥离$,但在shell中引用也需要$,所以这里要用双$1
2
3
4
5
6
7
8
9
10
11
12
13
14LIST = one two three
all:
for i in $(LIST); do \
echo $$i; \
done
解释后;
for i in one two three; do \
echo $i; \
done
执行结果:
one
two
three
如何控制什么时候指令回显
- make中可以使用echo指令,默认情况下,make会把要执行的指令打印到屏幕上,但当使用@echo时,只会显示echo后面的字符如:
1
2
3
4
5@echo xxxx
make执行时: 输出:xxxx
而当去掉@echo时,则输出:
echo xxxx
xxxx - -n和–just-print参数:使用时不会执行命令而是只显示命令
- -s或–silent或–quiet,则是全面禁止指令的显示;
指令是如何执行的
当更新目标时,会执行指令,指令是由shell指令构成的,会执行指令中的每个子shell;.ONESHELL会影响具体的目标;
而一个注意的是: cd ,会影响指令的运行环境,比如cd到别的目录执行;不过若想要cd影响到下个指令,需要将下个指令和cd放在同一行;
1 | foo: bar/lose |
这里使用的&& ,所以当cd失败时,整个指令会失败;注意不会执行&&后面的指令;
关于.ONESHELL:
声明这个后,指令中的多行指令会按单行指令执行,会有以上cd的影响;同时 特殊字符只会在第一行被去掉;比如 @,这可能造成影响,详细见文档;
eg:
1 | .ONESHELL: |
关于Choosing the Shell选择shell:
默认使用/bin/sh ,如果.SHELL没被设置的话;
而.SHELLFLAGS默认是-c或-ec
这个在DOS和win下比较常见;
指令如何同时执行
通常make是顺序执行一条一条指令的,当make -j时,会同时并行执行多个指令;所以为了make更快,可以采用这种形式;若-j后是一个整数,则按整数个同时执行的job来运行,
否是是1,串行执行;
如果一个指令失败(被一个信号杀死或以非零状态退出),并且不会忽略该指令的错误(请参阅配方中的错误),那么重新构建同一目标的剩余配方行将不会运行。
如果指令失败,并且没有给出’ -k ‘或’——keep-going ‘选项(参见选项摘要),则make aborts执行。如果make由于任何原因(包括信号)终止了子进程,它会在实际退出之前等待子进程结束。
当系统负载较重时,您可能希望比负载较轻时运行更少的作业。您可以使用’ -l ‘选项告诉make根据平均负载限制一次运行的作业数量。选项’ -l ‘或’——max-load ‘后面跟着一个浮点数。例如, -l 2.5
如果平均负载超过2.5,将不会让make启动一个以上的作业。-l后面无数字指定则无效
更准确地说,当make启动一个作业时,并且它已经有至少一个作业在运行时,它会检查当前的平均负载;如果它不低于’ -l ‘所给出的极限,则make等待,直到平均负载低于该极限,或直到所有其他作业完成。
默认的,是没有负载限制的;
- 关于并行执行的输出
当并行执行的时候,输出信息很难读;为了避免这个,可以用–output-sync选项;该选项指示make保存它调用的命令的输出,并在命令完成后全部打印输出。
此外,如果有多个递归make调用并行运行,它们将进行通信,以便每次只生成其中一个输出。
如果启用了工作目录打印(参见’——print-directory ‘选项),enter/leave消息将围绕每个输出分组打印。如果不希望看到这些消息,可以在MAKEFLAGS中添加’——no-print-directory ‘选项。
在同步输出时,有四种级别的粒度,通过给选项一个参数来指定(例如,’ -Oline ‘或’——output-sync=recurse ‘)。
1 | none:默认的,不同步直接输出 |
- 关于并行执行的输入
两个进程不能同时从同一设备获取输入。为了确保一次只有一个配方尝试从终端获取输入,make将使所有正在运行的配方的标准输入流失效。如果另一个配方试图从标准输入读取,它通常会导致一个致命的错误(“断管道”信号)。
指令执行失败后会怎么样;
每行指令会返回成功与否的状态值,成功为0 ,成功时会自动执行下一行,直到规则结束;,若碰到异常,非0值,则make会放弃当前的规则,或者可能所有的规则;
有时候指令的失败不代表是一个问题,例如mkdir;目录已存在;为了忽略这行指令的错误,则使用-,如:
1 | clean: |
这样即使这行rm失败也能继续执行;
如果执行make的时候指定-i,或者–ignore-errors,则会忽略全部的异常,和在makefile中声明.IGNORE效果类似;当用在无依赖的规则下比较有用;
上述情况下,虽然返回成功,但它会打印出一条消息,告诉您shell退出时使用的状态代码,并告诉您该错误已被忽略。
当错误发生时,make没有被告知要忽略它,这意味着当前目标不能被正确地重新创建,任何直接或间接依赖于它的其他目标也不能正确地重新创建。不会对这些目标执行进一步的配方,因为它们的先决条件还没有实现。
通常make在这种情况下立即放弃,返回一个非零状态。但是,如果指定了’ -k ‘或’——keep-going ‘标志,make将继续考虑挂起目标的其他先决条件,并在必要时重新生成它们,然后放弃并返回非零状态。
例如,在编译一个目标文件时出现错误后,’ make -k ‘将继续编译其他目标文件,即使它已经知道不可能链接它们。
指令被中断会怎么样
如果make在shell执行时获得一个致命信号,它可能会删除配方要更新的目标文件。如果目标文件的最后一次修改时间在第一次检查之后发生了改变,就会这样做。
为什么呢:
删除目标的目的是确保在下一次运行make时从头重新创建目标。这是为什么呢?假设您在编译器运行时输入Ctrl-c,并且编译器已经开始编写一个对象文件foo.o。Ctrl-c杀死编译器,导致一个不完整的文件,
它的最后修改时间比源文件foo.c更新。但是make也会接收Ctrl-c信号并删除这个不完整的文件。如果make没有这样做,下一次调用make时会认为foo.o不需要更新—当链接器试图链接一个文件时,会产生一个奇怪的错误消息如何设置例外?
您可以通过使特殊的目标.PRECIOUS 依赖于它来防止以这种方式删除目标文件。在重新创建目标之前,检查它是否在. precious的先决条件下出现,从而决定在信号发生时是否应该删除目标。
这样做的一些原因是,目标是以某种原子方式更新的,或者目标的存在只是为了记录修改时间(其内容并不重要),或者目标必须一直存在以防止其他类型的麻烦无法覆盖全部:
尽管make尽力清理,但在某些情况下,清理是不可能的。例如,make可能会被一个无法捕获的信号杀死。或者,某个程序make调用可能被杀死或崩溃,留下一个最新的但已损坏的目标文件:make不会意识到这个故障需要清除目标文件。或者使自己可能遇到bug而崩溃。防御:
由于这些原因,最好编写防御性指令,这样即使失败了,也不会留下损坏的目标。通常,这些指令会创建临时文件,而不是直接更新目标,然后将临时文件重命名为最终的目标名称。有些编译器已经采用了这种方式,因此您不需要编写防御性指令。
递归;指令中调用makefiles 通过make
有时候,我们在一个makefile中,编译后,想接着编译其他目录的内容,但是又不想退出重新make,这个时候可以利用makefile中调用make的功能;也叫递归
make的递归使用意味着将make作为makefile中的命令使用。当您需要为组成较大系统的各种子系统单独生成文件时,这种技术非常有用。
例如,假设您有一个子目录subdir,它有自己的makefile,并且您希望包含目录的makefile在子目录上运行make。你可以这样写
1 | subsystem: |
结合将目标声明为.PHONY会更有效;
为方便起见,当GNU make启动时(在它处理了任何-C选项之后),它将变量CURDIR设置为当前工作目录的路径名。make不会再碰这个值:
特别要注意,如果包含其他目录的文件,CURDIR的值不会改变。
这个值与在makefile中设置的值具有相同的优先级(默认情况下,环境变量CURDIR不会覆盖这个值)。注意,设置这个变量对make的操作没有影响(例如,它不会导致make更改其工作目录)。
- make指令变量如何工作
递归的make命令应该总是使用变量make,而不是显式的命令名’ make ‘,如下所示该变量的值是调用make时使用的文件名,如果这个文件名是/bin/make,那么执行的配方是’ cd subdir && /bin/make ‘。如果使用make的特殊版本来运行顶级makefile,则对于递归调用将执行相同的特殊版本。1
2subsystem:
cd subdir && $(MAKE)
作为一个特殊特性,在规则的指令中使用变量MAKE会改变’ -t ‘(‘——touch ‘)、’ -n ‘(‘——just-print ‘)或’ -q ‘(‘——question ‘)选项的效果。
使用MAKE变量的效果与在recipe行开头使用“+”字符的效果相同
只有当MAKE变量直接出现在指令中时才启用此特殊特性:如果通过展开另一个变量引用MAKE变量,则不适用此特性。在后一种情况下,您必须使用“+”标记来获得这些特殊效果。
考虑上面示例中的命令’ make -t ‘。(“-t”选项将目标标记为最新,无需实际运行任何指令;看,而不是执行。)遵循’ -t ‘的通常定义,
示例中的’ make -t ‘命令将创建一个名为subsystem的文件,不做其他事情。你真正想要做的是运行’ cd subdir && make -t ‘;但这需要执行指令,而“-t”表示不执行指令。
所以在这种情况下,make做了特殊处理,即在指令中若存在MAKE变量,则,当外部make时传入 -t/ -n/-q时,不传到这里的make ,这样对指令中的make可以正常运行;
而其实通常情况下,外部的make带的参数是会传递到这里的;
- 子make之间如何交互 export unexport
顶级make的变量值可以通过显式请求通过环境传递给子make,这些变量在子make中定义为默认值,但它们不会覆盖子make使用的makefile中定义的变量,除非你使用’ -e ‘开关。
为了传递或导出变量,make将变量及其值添加到环境中,以便运行指令的每一行。子make依次使用环境来初始化其变量值表。
除非是通过显式请求,否则只有在环境中初始定义或在命令行中设置了导出变量,并且它的名称仅由字母、数字和下划线组成时,才可以将导出设置为变量。有些shell不能处理由字母、数字和下划线以外的字符组成的环境变量名。
不导出make变量SHELL的值。相反,调用环境中的SHELL变量的值被传递给子make。您可以使用下面描述的export指令强制make为SHELL导出它的值。
特殊变量MAKEFLAGS总是被导出(除非您反导出它)。如果您将其设置为任何内容,则会导出MAKEFILES。
make通过将在命令行中定义的变量值放入MAKEFLAGS变量中,自动传递这些值。
如果变量是由make默认创建的,那么它们通常不会被传递(参见隐式规则使用的变量)。子make将为自己定义这些。
如果你想导出特定的变量到子make中,使用export指令,像这样:
export variable …
如果你想阻止一个变量被导出,使用unexport指令,像这样
unexport variable …
在这两种形式中,export和unexport的参数都是展开的,因此可以是展开为要(un)导出的变量名(列表)的变量或函数。
为了方便,你可以定义一个变量并同时导出它,方法如下:
1 | export variable = value |
您可能会注意到,export和unexport指令在make中的工作方式与它们在shell sh中的工作方式相同。
如果你想在默认情况下导出所有变量,你可以单独使用export:
export
unexport也是类似的效果
关于MAKELEVEL变量:
作为一个特殊的特性,变量MAKELEVEL在从一个级别传递到另一个级别时发生了变化。这个变量的值是一个字符串,它以十进制数字表示级别的深度。
顶级make的值是’ 0 ‘;sub-make是’ 1 ‘,sub-sub-make是’ 2 ‘,以此类推。当make为配方设置环境时发生增量
MAKELEVEL的主要用途是在一个条件指令中测试它(参见Makefiles的条件部分);通过这种方式,您可以编写一个makefile,递归运行时采用一种方式,直接运行时采用另一种方式。
关于MAKEFILES变量
您可以使用变量MAKEFILES使所有子make命令使用额外的makefile。MAKEFILES的值是一个以空格分隔的文件名列表。这个变量,如果定义在外部的makefile中,将通过环境传递;然后,它作为额外makefile的列表,供子make在通常的或指定的makefile之前读取。
- 将选项传递给子make
像-s,-k这种会直接通过变量MAKEFLAGS传递给子make. 这个变量由make自动设置,以包含make received的标志字母。因此,如果您使用’ make -ks ‘,那么MAKEFLAGS将获得值’ ks ‘。
因此,每个子make在它的环境中都会得到MAKEFLAGS的值。作为响应,它从该值中获取标记并处理它们,就像它们被作为参数给出一样。
同样,在命令行中定义的变量也通过MAKEFLAGS传递给子make。包含’ = ‘的MAKEFLAGS值中的单词,将处理作为变量定义,就像它们出现在命令行中一样。
选项’ -C ‘, ‘ -f ‘, ‘ -o ‘和’ -W ‘不会被放入MAKEFLAGS中;这些选项没有传递下去。
‘ -j ‘选项是一种特殊情况(参见并行执行)。如果你将它设置为一些数值’ N ‘并且你的操作系统支持它(大多数UNIX系统都会支持;其他的通常不会),父make和所有子make将进行通信,以确保它们之间只有“N”个作业同时运行。
如果您的操作系统不支持上述通信,那么没有’ -j ‘添加到MAKEFLAGS,以便子make运行在非并行模式
如果你不想传递其他标志,你必须改变MAKEFLAGS的值,像这样:
1 | subsystem: |
命令行变量定义实际出现在变量MAKEOVERRIDES中,并且MAKEFLAGS包含对该变量的引用。如果你想正常传递标志,但不想传递命令行变量定义,你可以重置MAKEOVERRIDES为空,像这样:
MAKEOVERRIDES =
这样做通常是没有用的。然而,一些系统对环境的大小有一个很小的固定限制,将如此多的信息放入MAKEFLAGS的值中可能会超过这个限制。
为了历史兼容性,也存在一个类似的变量MFLAGS。它有相同的值作为MAKEFLAGS除了它不包含命令行变量定义,它总是开始于一个连字符,除非它是空的(MAKEFLAGS以连字符开始只有当它开始于一个选项,
没有单字符版本,如“——warn-undefined-variables”)。MFLAGS传统上是在递归的make命令中显式使用的,像这样:
1 | subsystem: |
如果您希望拥有某些选项,比如在每次运行make时设置’ -k ‘(请参阅选项摘要),那么MAKEFLAGS变量也很有用。您只需将MAKEFLAGS的值放入您的环境中。您还可以在makefile中设置MAKEFLAGS,
以指定对该makefile也有效的附加标志。(注意你不能这样使用MFLAGS。设置该变量只是为了兼容;make不会以任何方式解释你为它设置的值。)
…
- 打印目录的指令
如果您使用多个层次的递归make调用,则’ -w ‘或’——print-directory ‘选项可以使输出更容易理解,
因为它将每个目录显示为make开始处理它和make完成处理它。例如,如果’ make -w ‘在目录/u/gnu/make中运行,make将打印下面的一行:
make: Entering directory `/u/gnu/make’.
before doing anything else, and a line of the form:
make: Leaving directory `/u/gnu/make’.
when processing is completed.
通常,你不需要指定这个选项,因为’ make ‘会替你指定:当你使用’ -C ‘选项时,’ -w ‘会自动打开,在子make中也是如此。make不会自动打开’ -w ‘,如果你也使用’ -s ‘,它表示静默,或者如果你使用’——no-print-directory ‘来显式禁用它。
定义指令集
当相同的命令序列在创建各种目标时有用时,可以使用define指令将其定义为固定序列,并从这些目标的配方中引用固定序列。这个固定序列实际上是一个变量,所以这个名称不能与其他变量名称冲突。
例如:
1 | define run-yacc = |
这里,run-yacc是被定义的变量的名称;endef表示定义的结束;中间的行是命令。define指令不会以固定的顺序展开变量引用和函数调用;’ $ ‘字符、圆括号、变量名等等都成为要定义的变量值的一部分。
要使用固定序列,请将变量替换到规则的指令中。您可以像替换其他变量一样替换它(。因为define定义的变量是递归展开的变量,所以在define中编写的所有变量引用现在都展开了。例如:
foo.c : foo.y
$(run-yacc)
当变量“ $ ^”以run-yacc的值出现时,“ foo.y”将被替换,变量“ $ @”将被“ foo.c”替换。
定义有用的没,没做任何事的指令;
有时定义不做任何事情的指令是很有用的。这可以通过提供一个只包含空格的指令来实现。例如:
target: ;
这样做的一个原因是防止目标获取隐式指令规则
空的指令也可以用来避免错误为目标创建另一个指令的副作用:如果目标不存在空配方确保让不会抱怨不知道如何建立目标,并使假设目标是过时了。
如何使用变量
一个变量是一个定义在makefile中用来表示一个文本中的字符串的名字,这个字符串是变量的值;
这些变量是可以被显示替换目标,依赖和指令,以及makefile中的其他部分;在其他版本的makefile中也叫宏;
makefile中所有部分的变量和函数在读取时都是展开的,除了在recipes中,变量定义的右边使用’ = ‘,变量定义的主体使用define指令。
变量可以表示文件名列表、传递给编译器的选项、要运行的程序、要查找源文件的目录、要写入输出的目录,或者您可以想象的任何其他内容。
变量名可以是不包含’:’、’ # ‘、’ = ‘或空格的任意字符序列。然而,包含字母、数字和下划线以外的字符的变量名应该仔细考虑,因为在某些shell中,它们不能通过环境传递给子make(请参阅通信变量到子make)。
变量名以`开头。在未来的make版本中,大写字母可能会被赋予特殊的含义。
变量名区分大小写。名字’ foo ‘, ‘ foo ‘和’ foo ‘都指不同的变量。
传统使用大写字母的变量名,但我们建议使用小写字母的变量名内部用途的makefile,并保留大写参数控制隐性规则或命令选项的参数,用户应该覆盖(见最重要的变量)。
一些变量的名称可以是单个标点字符,也可以是几个字符。这些是自动变量,它们有特殊的用途。看到自动变量。
如何使用变量的值
通过$符号可以引用变量的值,用两种方式: $(foo)或${foo},如果要用包含$字符,需要用两个$;
1 | objects = program.o foo.o utils.o |
变量按严格的文本替换:
1 | foo = c |
一个美元符号后面跟着一个不是美元符号、开括号或开括号的字符作为变量名。因此,你可以用’ $x ‘引用变量x。然而,这种做法可能会导致混淆(例如,’ $foo ‘指的是变量f后面跟着字符串oo),所以我们建议在所有变量周围使用圆括号或大括号,
即使是单字母的变量,除非省略它们可以显著提高可读性。可读性经常得到提高的一个地方是自动变量(参见自动变量)。
两种赋值变量的方式: = /:=
= 原封不动的拷贝,可以进行递归赋值,如:
1 | foo = $(bar) |
所以可以这样使用:这个功能有好的地方,也有不好的地方,好的地方是,我们可以把变量的真实值推到后面来定义
1 | CFLAGS = $(include_dirs) -O |
- 另一种:”:=”
为了避免上面的这种方法,我们可以使用make中的另一种用变量来定义变量的方法。这种方法使用的是 := 操作符,如:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29x := foo
y := $(x) bar
x := later
其等价于:
y := foo bar
x := later
值得一提的是,这种方法,前面的变量不能使用后面的变量,只能使用前面已定义好了的变量。如果是这样:
y := $(x) bar
x := foo
那么,y的值是“bar”,而不是“foo bar”。
下面再介绍两个定义变量时我们需要知道的,请先看一个例子,如果我们要定义一个变量,其值是一个空格,那么我们可以这样来:
nullstring :=
space := $(nullstring) # end of the line
nullstring是一个Empty变量,其中什么也没有,而我们的space的值是一个空格。因为在操作符的右边是很难描述一个空格的,这里采用的技术很管用,先用一个Empty变量来标明变量的值开始了,而后面采用“#”注释符来表示变量定义的终止,这样,我们可以定义出其值是一个空格的变量。请注意这里关于“#”的使用,注释符“#”的这种特性值得我们注意,如果我们这样定义一个变量:
dir := /foo/bar # directory to put the frobs in
dir这个变量的值是“/foo/bar”,后面还跟了4个空格,如果我们这样使用这样变量来指定别的目录——“$(dir)/file”那么就完蛋了。
还有一个比较有用的操作符是 ?= ,先看示例:
FOO ?= bar
其含义是,如果FOO没有被定义过,那么变量FOO的值就是“bar”,如果FOO先前被定义过,那么这条语将什么也不做,其等价于:
ifeq ($(origin FOO), undefined)
FOO = bar
endif
引用一个变量的高级特性
变量值的替换
们可以替换变量中的共有的部分,其格式是 $(var:a=b) 或是 ${var:a=b} ,其意思是,把变量“var”中所有以“a”字串“结尾”的“a”替换成“b”字串。这里的“结尾”意思是“空格”或是“结束符”。
还是看一个示例吧:
1 | foo := a.o b.o c.o |
把变量的值再当成变量
1 | x = y |
如何在一个变量中基于旧值添加更多内容–追加
+=
1 | objects += another.o |
如何在makefile中设置一个变量,即使之前已经被设置;
如果有变量是通过make的命令行参数设置的,那么Makefile中对这个变量的赋值会被忽略。如果你想在Makefile中设置这类参数的值,那么,你可以使用“override”指示符。其语法是:
1 | override <variable>; = <value>; |
当然,你还可以追加:
override
对于多行的变量定义,我们用define指示符,在define指示符前,也同样可以使用override指示符,如:
1 | override define foo |
一个设置多行字符串变量的可选方式
还有一种设置变量值的方法是使用define关键字。使用define关键字设置变量的值可以有换行,这有利于定义一系列的命令(前面我们讲过“命令包”的技术就是利用这个关键字)。
define指示符后面跟的是变量的名字,而重起一行定义变量的值,定义是以endef 关键字结束。其工作方式和“=”操作符一样。
变量的值可以包含函数、命令、文字,或是其它变量。因为命令需要以[Tab]键开头,所以如果你用define定义的命令变量中没有以 Tab 键开头,那么make 就不会把其认为是命令。
下面的这个示例展示了define的用法:
1 | define two-lines |
如何去定义一个变量,使它未被赋值
如果要清除一个变量,通常将其值设置为空就足够了。展开这样的变量将产生相同的结果(空字符串),而不管它是否被设置。但是,如果您正在使用flavor(参见flavor函数)和origin(参见origin函数)函数,
则从未设置的变量和空值的变量之间存在差异。在这种情况下,你可能想要使用undefine指令使一个变量看起来像从未设置过一样。例如:
1 | foo := foo |
来自环境的变量值;
make运行时的系统环境变量可以在make开始运行时被载入到Makefile文件中,但是如果Makefile中已定义了这个变量,或是这个变量由make命令行带入,那么系统的环境变量的值将被覆盖。(如果make指定了“-e”参数,那么,系统环境变量将覆盖Makefile中定义的变量)
因此,如果我们在环境变量中设置了 CFLAGS 环境变量,那么我们就可以在所有的Makefile中使用这个变量了。这对于我们使用统一的编译参数有比较大的好处。如果Makefile中定义了CFLAGS,那么则会使用Makefile中的这个变量,如果没有定义则使用系统环境变量的值,一个共性和个性的统一,很像“全局变量”和“局部变量”的特性。
当make嵌套调用时(参见前面的“嵌套调用”章节),上层Makefile中定义的变量会以系统环境变量的方式传递到下层的Makefile 中。当然,默认情况下,只有通过命令行设置的变量会被传递。而定义在文件中的变量,如果要向下层Makefile传递,则需要使用exprot关键字来声明。(参见前面章节)
当然,我并不推荐把许多的变量都定义在系统环境中,这样,在我们执行不用的Makefile时,拥有的是同一套系统变量,这可能会带来更多的麻烦。
变量值可以在每个目标的基础上定义
前面我们所讲的在Makefile中定义的变量都是“全局变量”,在整个文件,我们都可以访问这些变量。当然,“自动化变量”除外,如 $< 等这种类量的自动化变量就属于“规则型变量”,这种变量的值依赖于规则的目标和依赖目标的定义。
当然,我也同样可以为某个目标设置局部变量,这种变量被称为“Target-specific Variable”,它可以和“全局变量”同名,因为它的作用范围只在这条规则以及连带规则中,所以其值也只在作用范围内有效。而不会影响规则链以外的全局变量的值。
其语法是:
1 | <target ...> : <variable-assignment>; |
这个特性非常的有用,当我们设置了这样一个变量,这个变量会作用到由这个目标所引发的所有的规则中去。如:
1 | prog : CFLAGS = -g |
特定于目标的变量值可以应用于匹配模式的一组目标。
在GNU的make中,还支持模式变量(Pattern-specific Variable),通过上面的目标变量中,我们知道,变量可以定义在某个目标上。模式变量的好处就是,我们可以给定一种“模式”,可以把变量定义在符合这种模式的所有目标上。
我们知道,make的“模式”一般是至少含有一个 % 的,所以,我们可以以如下方式给所有以 .o 结尾的目标定义目标变量:
%.o : CFLAGS = -O
同样,模式变量的语法和“目标变量”一样:
1 | <pattern ...>; : <variable-assignment>; |
抑制变量的继承
1 | EXTRA_CFLAGS = |
由于private修饰符,a.o和b.o不会从prog目标继承EXTRA_CFLAGS变量赋值。
具有特殊意义或行为的变量。
.VARIABLES
。。。
如何使用条件语句
使用条件判断,可以让make根据运行时的不同情况选择不同的执行分支。条件表达式可以是比较变量的值,或是比较变量和常量的值。
条件例子
下面的例子,判断 $(CC) 变量是否 gcc ,如果是的话,则使用GNU函数编译目标。
1 | libs_for_gcc = -lgnu |
可见,在上面示例的这个规则中,目标 foo 可以根据变量 $(CC) 值来选取不同的函数库来编译程序。
我们可以从上面的示例中看到三个关键字: ifeq 、 else 和 endif 。 ifeq 的意思表示条件语句的开始,并指定一个条件表达式,表达式包含两个参数,以逗号分隔,表达式以圆括号括起。 else 表示条件表达式为假的情况。 endif 表示一个条件语句的结束,任何一个条件表达式都应该以 endif 结束。
当我们的变量 $(CC) 值是 gcc 时,目标 foo 的规则是:
1 | foo: $(objects) |
条件的语法
条件表达式的语法为:
1 | <conditional-directive> |
第一个是我们前面所见过的 ifeq
1 | ifeq (<arg1>, <arg2>) |
第二个条件关键字是 ifneq 。语法是:
1 | ifneq (<arg1>, <arg2>) |
第三个条件关键字是 ifdef 。语法是:
ifdef
如果变量
示例一:
1 | bar = |
第一个例子中, $(frobozz) 值是 yes ,第二个则是 no。
第四个条件关键字是 ifndef 。其语法是:
1 | ifndef <variable-name> |
特别注意的是,make是在读取Makefile时就计算条件表达式的值,并根据条件表达式的值来选择语句,所以,你最好不要把自动化变量(如 $@ 等)放入条件表达式中,因为自动化变量是在运行时才有的。
而且为了避免混乱,make不允许把整个条件语句分成两部分放在不同的文件中。
测试标志
TODO
如何使用函数
函数允许您在makefile中进行文本处理,以计算要操作的文件或在指令中使用的命令。
在函数调用中使用函数,其中提供函数的名称和一些文本(参数)供函数操作。函数处理的结果在调用时被替换到makefile中,就像变量可能被替换一样。
函数的语法,如何写一个函数调用
函数调用类似于变量引用。它可以出现在变量引用可能出现的任何地方,并且使用与变量引用相同的规则展开它。函数调用是这样的:
$(function arguments)
或
${function arguments}
这里的function是指一个函数名,一部分是make中的,你也可以通过call关键字自己创建函数,
函数调用以 $ 开头,以圆括号或花括号把函数名和参数括起。感觉很像一个变量,是不是?
函数中的参数可以使用变量,为了风格的统一,函数和变量的括号最好一样,如使用 $(subst a,b,$(x)) 这样的形式,而不是 $(subst a,b, ${x}) 的形式。
通过替换变量和函数调用来处理为每个参数编写的文本,以生成参数值,该参数值是函数作用的文本。替换按参数出现的顺序进行。
逗号和不匹配的括号或大括号不能像所写的那样出现在参数的文本中;前导空格不能像所写的那样出现在第一个参数的文本中。可以通过变量替换将这些字符放入参数值中。
首先定义变量逗号和空格,它们的值是孤立的逗号和空格字符,然后在需要这些字符的地方替换这些变量,像这样:
1 | comma:= , |
这里,subst函数通过foo的值用逗号替换每个空格,并替换结果
Functions for String Substitution and Analysis 字符串操作函数列表
字符串处理函数
subst
$(subst
名称:字符串替换函数
功能:把字串
返回:函数返回被替换过后的字符串。
示例:
$(subst ee,EE,feet on the street)
把 feet on the street 中的 ee 替换成 EE ,返回结果是 fEEt on the strEEt 。
patsubst
$(patsubst
名称:模式字符串替换函数。
功能:查找
返回:函数返回被替换过后的字符串。
示例:
$(patsubst %.c,%.o,x.c.c bar.c)
把字串 x.c.c bar.c 符合模式 %.c 的单词替换成 %.o ,返回结果是 x.c.o bar.o
备注:这和我们前面“变量章节”说过的相关知识有点相似。如 $(var:
例如有:
objects = foo.o bar.o baz.o,
那么, $(objects:.o=.c) 和 $(patsubst %.o,%.c,$(objects)) 是一样的。
strip
$(strip
名称:去空格函数。
功能:去掉
返回:返回被去掉空格的字符串值。
示例:
$(strip a b c )
把字串 a b c 去到开头和结尾的空格,结果是a b c 。
findstring
$(findstring
名称:查找字符串函数
功能:在字串
返回:如果找到,那么返回
示例:
$(findstring a,a b c)
$(findstring a,b c)
第一个函数返回 a 字符串,第二个返回空字符串
filter
$(filter <pattern…>,
名称:过滤函数
功能:以
返回:返回符合模式
示例:
sources := foo.c bar.c baz.s ugh.h
foo: $(sources)
cc $(filter %.c %.s,$(sources)) -o foo
$(filter %.c %.s,$(sources)) 返回的值是 foo.c bar.c baz.s 。
filter-out
$(filter-out <pattern…>,
名称:反过滤函数
功能:以
返回:返回不符合模式
示例:
objects=main1.o foo.o main2.o bar.o
mains=main1.o main2.o
$(filter-out $(mains),$(objects)) 返回值是 foo.o bar.o 。
sort
$(sort )
名称:排序函数
功能:给字符串 中的单词排序(升序)。
返回:返回排序后的字符串。
示例: $(sort foo bar lose) 返回 bar foo lose 。
备注: sort 函数会去掉 中相同的单词。
word
$(word
名称:取单词函数
功能:取字符串
返回:返回字符串
示例: $(word 2, foo bar baz) 返回值是 bar 。
wordlist
$(wordlist
名称:取单词串函数
功能:从字符串
返回:返回字符串
示例: $(wordlist 2, 3, foo bar baz) 返回值是 bar baz 。
words
$(words
名称:单词个数统计函数
功能:统计
返回:返回
示例: $(words, foo bar baz) 返回值是 3 。
备注:如果我们要取
firstword
$(firstword
名称:首单词函数——firstword。
功能:取字符串
返回:返回字符串
示例: $(firstword foo bar) 返回值是 foo。
备注:这个函数可以用 word 函数来实现: $(word 1,
以上,是所有的字符串操作函数,如果搭配混合使用,可以完成比较复杂的功能。这里,举一个现实中应用的例子。我们知道,make使用 VPATH 变量来指定“依赖文件”的搜索路径。于是,我们可以利用这个搜索路径来指定编译器对头文件的搜索路径参数 CFLAGS ,如:
override CFLAGS += $(patsubst %,-I%,$(subst :, ,$(VPATH)))
如果我们的 $(VPATH) 值是 src:../headers ,那么 $(patsubst %,-I%,$(subst :, ,$(VPATH))) 将返回 -Isrc -I../headers ,这正是cc或gcc搜索头文件路径的参数。
如何用函数操作文件名
下面我们要介绍的函数主要是处理文件名的。每个函数的参数字符串都会被当做一个或是一系列的文件名来对待。
dir
$(dir <names…>)
名称:取目录函数——dir。
功能:从文件名序列
返回:返回文件名序列
示例: $(dir src/foo.c hacks) 返回值是 src/ ./ 。
notdir
$(notdir <names…>)
名称:取文件函数——notdir。
功能:从文件名序列
返回:返回文件名序列
示例: $(notdir src/foo.c hacks) 返回值是 foo.c hacks 。
suffix
$(suffix <names…>)
名称:取後缀函数——suffix。
功能:从文件名序列
返回:返回文件名序列
示例: $(suffix src/foo.c src-1.0/bar.c hacks) 返回值是 .c .c。
basename
$(basename <names…>)
名称:取前缀函数——basename。
功能:从文件名序列
返回:返回文件名序列
示例: $(basename src/foo.c src-1.0/bar.c hacks) 返回值是 src/foo src-1.0/bar hacks 。
addsuffix
$(addsuffix
名称:加后缀函数——addsuffix。
功能:把后缀
返回:返回加过后缀的文件名序列。
示例: $(addsuffix .c,foo bar) 返回值是 foo.c bar.c 。
addprefix
$(addprefix
名称:加前缀函数——addprefix。
功能:把前缀
返回:返回加过前缀的文件名序列。
示例: $(addprefix src/,foo bar) 返回值是 src/foo src/bar 。
join
$(join
名称:连接函数——join。
功能:把
返回:返回连接过后的字符串。
示例: $(join aaa bbb , 111 222 333) 返回值是 aaa111 bbb222 333 。
wildcard
$(wildcard pattern)
参数模式是一个文件名模式,通常包含通配符(如shell文件名模式)。通配符的结果是一个与模式匹配的现有文件名称的以空格分隔的列表。
eg:
objects := $(wildcard *.o)
realpath
$(realpath names…)
对于names中的每个文件名,返回规范的绝对名称。规范名称不包含任何内容。或. .组件,或任何重复路径分隔符(/)或符号链接。如果失败,则返回空字符串。
abspath
$(abspath names…)
对于names中的每个文件名,返回一个不包含任何内容的绝对名称。或. .组件,或任何重复路径分隔符(/)。
注意,与realpath函数不同,abspath不解析符号链接,也不要求文件名引用现有的文件或目录。使用通配符函数来测试是否存在。
实现条件的函数
if函数很像GNU的make所支持的条件语句——ifeq(参见前面所述的章节),if函数的语法是:
1 | $(if <condition>,<then-part>) |
而if函数的返回值是,如果
1 | 所以, <then-part> 和 <else-part> 只会有一个被计算。 |
foreach函数
foreach函数和别的函数非常的不一样。因为这个函数是用来做循环用的,Makefile中的foreach函数几乎是仿照于Unix标准Shell(/bin/sh)中的for语句,或是C-Shell(/bin/csh)中的foreach语句而构建的。它的语法是:
1 | $(foreach <var>,<list>,<text>) |
注意,foreach中的 参数是一个临时的局部变量,foreach函数执行完后,参数 的变量将不在作用,其作用域只在foreach函数当中。
1 | eg2: |
文件相关函数:写文本到一个文件中
file函数允许makefile写出到文件或从文件读取;支持两种模式的写入: 覆盖写,从文件的开始写,覆盖存在的内容;追加,即追加到存在文件的末尾;
两种模式都是文件不存在就创建,有一个致命错误就是当文件不能打开去写时,或者写操作失败;当写入文件时,file函数展开为空字符串。
从文件中读取时,file函数展开为文件的逐字内容,但最后的换行(如果有的话)将被删除。试图从不存在的文件中读取将展开为空字符串。
语法:
$(file op filename[,text])
当对文件函数求值时,首先展开它的所有参数,然后将以op描述的模式打开由filename指定的文件。
运算符op可以是>,表示文件将被新的内容覆盖,>>表示文件的当前内容将被追加,或者<表示文件的内容将被读入。文件名指定要写入或读取的文件。在操作符和文件名之间可以有空格。
读取文件时,提供text的值是错误的。
当写入文件时,文本text将写入文件。如果文本text还没有以换行符结束,则将写入最后的换行符(即使文本是空字符串)。如果根本没有给出text参数,则不会写入任何内容。
eg:
1 | 例如,如果构建系统的命令行大小有限,并且指令运行的命令也可以接受文件中的参数,那么file函数就很有用。 |
call函数
call函数是唯一一个可以用来创建新的参数化的函数。你可以写一个非常复杂的表达式,这个表达式中,你可以定义许多参数,然后你可以call函数来向这个表达式传递参数。其语法是:
$(call
当make执行这个函数时,
1 | reverse = $(1) $(2) |
需要注意:在向 call 函数传递参数时要尤其注意空格的使用。call 函数在处理参数时,第2个及其之后的参数中的空格会被保留,因而可能造成一些奇怪的效果。因而在向call函数提供参数时,最安全的做法是去除所有多余的空格。
value函数
alue函数提供了一种方法,可以使用变量的值,而不必将其展开。请注意,这不会撤销已经发生的展开;
例如,如果您创建了一个简单展开的变量,它的值会在定义过程中展开;在这种情况下,value函数将返回与直接使用变量相同的结果。
语法:
1 | $(value variable) |
eval函数:Evaluate the arguments as makefile syntax
eval函数非常特别,它允许你定义一个新的makefile构造非常规的;
这是对其他变量和函数求值的结果。eval函数的参数被展开,然后将展开的结果作为makefile语法进行解析。扩展的结果可以定义新的make变量、目标、隐式或显式规则等。
eval函数的结果总是空字符串;因此,它可以放在makefile中的任何位置,而不会导致语法错误。
重要的是要意识到eval参数被展开了两次;首先是eval函数,然后在作为makefile语法解析时再次展开该展开的结果。
这意味着在使用eval时,可能需要为“$”字符提供额外级别的转义。值value函数有时在这些情况下很有用,可以避免不必要的展开。
下面是如何使用eval的示例;这个示例结合了许多概念和其他函数。尽管在本例中使用eval似乎过于复杂,而不只是写出规则,但要考虑两件事:
首先,模板定义(PROGRAM_template中)可能需要比这里复杂得多;
其次,您可以将这个示例中复杂的“通用”部分放入另一个makefile中,然后将其包含在所有单独的makefile中。现在,您的单个makefile非常简单。
1 | PROGRAMS = server client |
origin函数:寻找一个变量并拿它的值
origin函数不像其它的函数,他并不操作变量的值,他只是告诉你你的这个变量是哪里来的?其语法是:
1 | $(origin <variable>) |
当然,你也许会说,使用 override 关键字不就可以重新定义环境中的变量了吗?为什么需要使用这样的步骤?是的,我们用 override 是可以达到这样的效果,可是 override 过于粗暴,它同时会把从命令行定义的变量也覆盖了,而我们只想重新定义环境传来的,而不想重新定义命令行传来的
flavor函数
flavor函数,像origin函数一样,不作用于变量的值而是告诉你关于变量的一些东西。具体来说,它告诉您变量的特性(
语法:
$(flavor variable)
注意,variable是要查询的变量的名称,而不是对该变量的引用。因此,在写它的时候,你通常不会使用’ $ ‘或圆括号。(不过,如果希望名称不是常量,也可以在名称中使用变量引用。)
结果是以下的形式:
1 | ‘undefined’ |
控制make运行的函数
make提供了一些函数来控制make的运行。通常,你需要检测一些运行Makefile时的运行时信息,并且根据这些信息来决定,你是让make继续执行,还是停止。
1 | $(error <text ...>) |
shelll函数
shell函数也不像其它的函数。顾名思义,它的参数应该就是操作系统Shell的命令。它和反引号“`”是相同的功能。这就是说,shell函数把执行操作系统命令后的输出作为函数返回。于是,我们可以用操作系统命令以及字符串处理命令awk,sed等等命令来生成一个变量,如:
1 | contents := $(shell cat foo) |
注意,这个函数会新生成一个Shell程序来执行命令,所以你要注意其运行性能,如果你的Makefile中有一些比较复杂的规则,并大量使用了这个函数,那么对于你的系统性能是有害的。特别是Makefile的隐晦的规则可能会让你的shell函数执行的次数比你想像的多得多。
guile函数
如果GNU make在构建时支持将GNU Guile作为嵌入式扩展语言,那么Guile函数将可用。guile函数接受一个参数,这个参数首先由make以正常方式展开,然后传递给GNU guile计算器。求值器的结果被转换成一个字符串,并在makefile中用作guile函数的展开。关于在Guile中编写扩展的详细信息,请参阅GNU Guile Integration。
你可以通过检查Guile这个词的. .FEATURES变量来决定是否支持GNU Guile。
如何运行make
makefile的执行,一般是直接make就可以了;make会针对过期的文件重新编译,但是你可能想去只更新部分文件;你可能想使用不同的编译器或者
编译器选项;你可能只想找出过期文件而不想更新改变它们;
通过运行make的时候给定参数,你可以做下这些事以及许多其他:
make的退出状态值总是这三个:
1 | 0 |
如何具体化哪个makefile被使用
使用-f/–file参数:
make -f almake / make –file almake
即以almake文件来执行make;
如果不指定,则使用默认的文件:GNUmakefile, makefile, and Makefile
Golas
如何指定生成的目标:默认的,目标是makefile文件中的第一个目标;因此一般makefile文件总是以第一个目标来生成整个程序;
如果第一个规则由几个目标,只有第一个目标会是默认目标,而不是整个列表;当然你也可以使用.DEFAULT_GOAL来改变默认目标;
也可以通过显性指定make的目标来生成对应的目标:若你指定多个模板,则make会按序执行生成他们;
makefile中的任何目标都可以指定为目标(除非它以’ - ‘开头或包含’ = ‘,在这种情况下,它将分别被解析为switch或变量定义)。
即使目标不在makefile中也可以指定,如果make可以找到规定如何创建它们的隐式规则的话。
Make将特殊变量MAKECMDGOALS设置为您在命令行上指定的目标列表。如果在命令行上没有给出目标,则此变量为空。注意,这个变量应该只在特殊情况下使用。
一个合适的例子是,为了避免在clean的时候删除.d 文件;所以make不会仅仅为了立即删除它们而创建它们
1 | sources = foo.c bar.c |
指定目标的一种用法是只编译程序的一部分,或者只编译几个程序中的一个。将希望重制的每个文件指定为目标。
例如,考虑一个包含几个程序的目录,其makefile开头是这样的:
.PHONY: all
all: size nm ld ar as
若你只想生成size,你可以执行make size
指定目标的另一种用法可能是,不是常规的生成文件,而是一个文件用于调试信息的输出,或者一个测试版本的生成;
指定目标的另一种用法可能是,指定假的目标,比如make clean ;
有以下几种类型,直接贴原文:
1 | all |
除了执行
除了生成,更新目标文件之外,还可以有别的活动,比如打印等:
1 | ‘-n’ |
避免重编译指定的文件
有时候你可能已经改变了一些源文件,但是你不想重新编译依赖它的全部文件;例如:假设你添加了一个宏或者一个声明在一个头文件中,很多
其他文件依赖这个头文件;保守的,make假设头文件的任何改变都需要重新编译所有的依赖它的文件,但你知道他们不需要重新编译,你可能不想浪费时间等待他们的编译;
如果你期望上面的文件得到解决,你可以使用-t ,make -t,这个flag告诉make不要运行规则中的指令;但是改变目标文件的最后修改时间;
为了避免异常,你最好按一下步骤进行:
1 | 1 先make执行重编译你需要重编译的文件; |
覆盖变量:
一个包含=的参数,具体化一个变量的值: v=x ,设置变量v的值是x,如果你具体化一个值,按照这种方式,makefile中相同变量的所有普通赋值都会被忽略;我们说它们已经被命令行参数覆盖了
最常见的方式就是传递额外的flag给编译器:例如:
CFLAGS
cc -c $(CFLAGS) foo.c
这个变量被包含在每个指令中,被C编译器运行采纳;因此,为CFLAGS设置的任何值都会影响每次发生的编译。makefile可能会指定常用的CFLAGS值,如下所示:
CFLAGS=-g
每次运行make时,如果愿意,可以重写这个值。例如,如果你说’ make CFLAGS=’-g -O’ “,每一次C编译都将使用’ cc -c -g -O’来完成。
(这也说明了在覆盖变量时,如何在shell中使用引号将空格和其他特殊字符括在变量值中。)
变量CFLAGS只是众多标准变量中的一个,您可以通过这种方式更改它们。请参阅隐式规则使用的变量的完整列表
您还可以对makefile进行编程,以查看您自己的附加变量,从而使用户能够通过更改变量来控制makefile工作方式的其他方面
当使用命令行参数覆盖变量时,可以定义递归展开的变量或简单展开的变量。上面的例子是递归展开的变量;要创建一个简单展开的变量,可以使用’:= ‘或’::= ‘来代替’ = ‘。
但是,除非您希望在指定的值中包含变量引用或函数调用,否则创建哪种类型的变量没有区别。
makefile有一种方法可以更改已覆盖的变量。这是使用override指令,它是这样一行:’ override variable = value ‘
测试标志,测试一个程序的编译
通常,当一个错误反生在执行shell指令时,make会立即放弃,并返回一个非0值;不会继续执行来生成任何目标;
这个错误,暗示着目标无法正确重新生成,make会报告它;
当你正在编译一个你刚刚改变过的程序时,上面的结果可能不是你想要的;相反,你可能想要make 去尝试编译每个可以尝试的文件不中断,然后尽可能多的生成编译错误;
在这些场景下,你应该使用-k or –keep-going 标志,这告诉make在放弃并返回非零状态之前继续考虑目标的其他先决条件,并在必要时重新生成它们
例如,在编译一个目标文件时出现错误后,’ make -k ‘将继续编译其他目标文件,即使它已经知道不可能链接它们。
除了在shell命令失败后继续执行之外,在发现不知道如何创建目标文件或先决文件时,’ make -k ‘还将尽可能地继续执行。这将总是导致一个错误消息,但如果没有’ -k ‘,它将是一个致命错误
make的通常行为是假定你的目的是更新目标;一旦make知道这是不可能的,它不妨立即报告失败。’ -k ‘标志表示,真正的目的是尽可能多地测试程序中所做的更改,
可能是找到几个独立的问题,以便在下一次尝试编译之前纠正它们。这就是Emacs的M-x编译命令在默认情况下传递’ -k ‘标志的原因。
选项总结;
1 | -B / --always-make: 不管先决条件是否过期,目标是否过期,总是重新生成目标; |
如何使用隐式规则
remake目标文件的确定的标准方法经常被使用。例如,一种make目标文件的习惯方法是使用C编译器cc从C源文件中获取目标文件。
隐式规则,告诉make如何使用习惯的技术,以便于你不用具体化他们的细节,当你想要用他们的时候;例如:
这是一个为C编译使用的隐式规则:文件名决定了哪种隐式规则被运行;例如,c编译典型就是用.c文件生成.o文件; 所以make为此应用了隐式规则,当他看到这种字符结尾的文件;
一系列的隐式规则可以按顺序应用;例如,make将通过.c文件从.y文件重制一个.o文件。
内置的隐式规则在其方法中使用几个变量,因此,通过更改变量的值,可以更改隐式规则的工作方式。例如,变量CFLAGS控制C编译的隐式规则给C编译器的标志。
您可以通过编写模式规则来定义自己的隐式规则。
后缀规则是定义隐式规则的一种更有限的方式。模式规则更加通用和清晰,但后缀规则保留以保持兼容性
使用隐式规则
为了让make找到更新目标文件的常用方法,您所需要做的就是避免自己指定指令。
要么写一个没有指令的规则,要么根本不写规则。然后,make将根据存在或可以创建哪种源文件来确定使用哪种隐式规则
例如,有个makefile如下:
1 | foo : foo.o bar.o |
这里因为你提到了foo.o,但是却没有给一个规则,所以make自动的寻找一个隐式规则来告知如何更新它;这个事情的发生不管当前是否有foo.o存在;
如果一个隐式规则被找到,它能应用到一个指令和一个或多个依赖(源文件)。如果你需要具体化格外的依赖文件比如头文件,你可以写一个只包含依赖文件的,不包含指令的
规则;
每个隐式规则由一个目标模板和一个依赖模板。可能有许多隐式规则用相同的目标模板;例如: 生成.o的,可以是.c,.p,等等,对应的c和pascal编译器。
这个规则实际上应用在依赖存在的目标上,或者能被生成;所以如果你有一个foo.c文件,make将运行c编译器,否则,如果有foo.p,make可能会运行pascal编译器来生成
;以此类推;
当然,当你写makefile,你得知道哪些隐式规则你想让make使用,并且你知道它会选择哪些依赖文件,那些依赖文件存在等等;
上面,我们说过,如果必要的先决条件“存在或可以建立”,就应用隐式规则。如果在makefile中显式地提到文件作为目标或先决条件,
或者可以递归地找到创建文件的隐式规则,则“可以创建”文件。当一个隐式前提是另一个隐式规则的结果时,我们说链接正在发生。
一般而言,make会为每个目标,每个只有冒号但是没有指令的目标寻找隐式规则;一个文件只在依赖中被提到也会被当做目标,这个时候它也没有具体化目标;
所以隐式规则寻找这里也会发生;
要注意显示规则不会影响到隐式规则的寻找:如
foo.o:foo.p
注意这里,依赖是foo.p,并不意味着make会依据隐式规则来从foo.p生成foo.o,例如,如果foo.c存在,则会从foo.c生成foo.o ,因为在
隐式规则列表中,c这个存在于p之前;
如果您不希望隐式规则用于没有指令的目标,可以通过编写分号为该目标提供一个空配方
内建规则清单
下面是一组预定义的隐式规则,它们总是可用的,除非makefile显式地覆盖或取消它们。
有关取消或重写隐式规则的信息,请参阅取消隐式规则。选项’ -r ‘或’——no-built -rules ‘取消所有预定义的规则。
本手册仅记录基于posix的操作系统上可用的默认规则。其他操作系统,如VMS、Windows、OS/2等可能有不同的默认规则集。
要查看您版本的GNU make中可用的默认规则和变量的完整列表,请在一个没有makefile的目录中运行’ make -p ‘。
当然,即使是我们指定了 -r 参数,某些隐含规则还是会生效,因为有许多的隐含规则都是使用了“后缀规则”来定义的,所以,只要隐含规则中有 “后缀列表”(也就一系统定义在目标 .SUFFIXES 的依赖目标),
那么隐含规则就会生效。默认的后缀列表是:.out, .a, .ln, .o, .c, .cc, .C, .p, .f, .F, .r, .y, .l, .s, .S, .mod, .sym, .def, .h, .info, .dvi, .tex, .texinfo, .texi, .txinfo, .w, .ch .web, .sh, .elc, .el。具体的细节,我们会在后面讲述。
还是先来看一看常用的隐含规则吧。
编译C程序的隐含规则。
编译C++程序的隐含规则。
编译Pascal程序的隐含规则。
编译Fortran/Ratfor程序的隐含规则。
.f $(FC) –c $(FFLAGS)
.F $(FC) –c $(FFLAGS) $(CPPFLAGS)
.f $(FC) –c $(FFLAGS) $(RFLAGS)
预处理Fortran/Ratfor程序的隐含规则。
.F $(FC) –F $(CPPFLAGS) $(FFLAGS)
.r $(FC) –F $(FFLAGS) $(RFLAGS)
编译Modula-2程序的隐含规则。
汇编和汇编预处理的隐含规则。
链接Object文件的隐含规则。
x : y.o z.o
并且 x.c 、 y.c 和 z.c 都存在时,隐含规则将执行如下命令:
1 | cc -c x.c -o x.o |
如果没有一个源文件(如上例中的x.c)和你的目标名字(如上例中的x)相关联,那么,你最好写出自己的生成规则,不然,隐含规则会报错的。
Yacc C程序时的隐含规则。
Lex C程序时的隐含规则。
Lex Ratfor程序时的隐含规则。
从C程序、Yacc文件或Lex文件创建Lint库的隐含规则。
更多参考文档
隐式规则中变量的使用:
在隐含规则中的命令中,基本上都是使用了一些预先设置的变量。你可以在你的makefile中改变这些变量的值,或是在make的命令行中传入这些值,或是在你的环境变量中设置这些值,无论怎么样,只要设置了这些特定的变量,那么其就会对隐含规则起作用。当然,你也可以利用make的 -R 或 –no–builtin-variables 参数来取消你所定义的变量对隐含规则的作用。
例如,第一条隐含规则——编译C程序的隐含规则的命令是 $(CC) –c $(CFLAGS) $(CPPFLAGS) 。Make默认的编译命令是 cc ,如果你把变量 $(CC) 重定义成 gcc ,把变量 $(CFLAGS) 重定义成 -g ,那么,隐含规则中的命令全部会以 gcc –c -g $(CPPFLAGS) 的样子来执行了。
我们可以把隐含规则中使用的变量分成两种:一种是命令相关的,如 CC ;一种是参数相的关,如 CFLAGS 。下面是所有隐含规则中会用到的变量:
关于命令的变量。
AR : 函数库打包程序。默认命令是 ar
AS : 汇编语言编译程序。默认命令是 as
CC : C语言编译程序。默认命令是 cc
CXX : C++语言编译程序。默认命令是 g++
CO : 从 RCS文件中扩展文件程序。默认命令是 co
CPP : C程序的预处理器(输出是标准输出设备)。默认命令是 $(CC) –E
FC : Fortran 和 Ratfor 的编译器和预处理程序。默认命令是 f77
GET : 从SCCS文件中扩展文件的程序。默认命令是 get
LEX : Lex方法分析器程序(针对于C或Ratfor)。默认命令是 lex
PC : Pascal语言编译程序。默认命令是 pc
YACC : Yacc文法分析器(针对于C程序)。默认命令是 yacc
YACCR : Yacc文法分析器(针对于Ratfor程序)。默认命令是 yacc –r
MAKEINFO : 转换Texinfo源文件(.texi)到Info文件程序。默认命令是 makeinfo
TEX : 从TeX源文件创建TeX DVI文件的程序。默认命令是 tex
TEXI2DVI : 从Texinfo源文件创建军TeX DVI 文件的程序。默认命令是 texi2dvi
WEAVE : 转换Web到TeX的程序。默认命令是 weave
CWEAVE : 转换C Web 到 TeX的程序。默认命令是 cweave
TANGLE : 转换Web到Pascal语言的程序。默认命令是 tangle
CTANGLE : 转换C Web 到 C。默认命令是 ctangle
RM : 删除文件命令。默认命令是 rm –f
关于命令参数的变量
下面的这些变量都是相关上面的命令的参数。如果没有指明其默认值,那么其默认值都是空。
ARFLAGS : 函数库打包程序AR命令的参数。默认值是 rv
ASFLAGS : 汇编语言编译器参数。(当明显地调用 .s 或 .S 文件时)
CFLAGS : C语言编译器参数。
CXXFLAGS : C++语言编译器参数。
COFLAGS : RCS命令参数。
CPPFLAGS : C预处理器参数。( C 和 Fortran 编译器也会用到)。
FFLAGS : Fortran语言编译器参数。
GFLAGS : SCCS “get”程序参数。
LDFLAGS : 链接器参数。(如: ld )
LFLAGS : Lex文法分析器参数。
PFLAGS : Pascal语言编译器参数。
RFLAGS : Ratfor 程序的Fortran 编译器参数。
YFLAGS : Yacc文法分析器参数。
隐式规则链:
有些时候,一个目标可能被一系列的隐含规则所作用。例如,一个 .o 的文件生成,可能会是先被 Yacc的[.y]文件先成 .c ,然后再被C的编译器生成。
我们把这一系列的隐含规则叫做“隐含规则链”。
在上面的例子中,如果文件 .c 存在,那么就直接调用C的编译器的隐含规则,如果没有 .c 文件,但有一个 .y 文件,那么Yacc的隐含规则会被调用,生成 .c 文件,
然后,再调用C编译的隐含规则最终由 .c 生成 .o 文件,达到目标。
我们把这种 .c 的文件(或是目标),叫做中间目标。不管怎么样,make会努力自动推导生成目标的一切方法,不管中间目标有多少,
其都会执着地把所有的隐含规则和你书写的规则全部合起来分析,努力达到目标,所以,有些时候,可能会让你觉得奇怪,怎么我的目标会这样生成?怎么我的 makefile发疯了?
在默认情况下,对于中间目标,它和一般的目标有两个地方所不同:第一个不同是除非中间的目标不存在,才会引发中间规则。第二个不同的是,只要目标成功产生,
那么,产生最终目标过程中,所产生的中间目标文件会被以 rm -f 删除。
通常,一个被makefile指定成目标或是依赖目标的文件不能被当作中介。然而,你可以明显地说明一个文件或是目标是中介目标,你可以使用伪目标 .INTERMEDIATE 来强制声明。
(如: .INTERMEDIATE : mid )
你也可以阻止make自动删除中间目标,要做到这一点,你可以使用伪目标 .SECONDARY 来强制声明(如: .SECONDARY : sec )。你还可以把你的目标,
以模式的方式来指定(如: %.o )成伪目标 .PRECIOUS 的依赖目标,以保存被隐含规则所生成的中间文件。
在“隐含规则链”中,禁止同一个目标出现两次或两次以上,这样一来,就可防止在make自动推导时出现无限递归的情况。
Make会优化一些特殊的隐含规则,而不生成中间文件。如,从文件 foo.c 生成目标程序 foo ,按道理,make会编译生成中间文件 foo.o ,然后链接成 foo ,
但在实际情况下,这一动作可以被一条 cc 的命令完成( cc –o foo foo.c ),于是优化过的规则就不会生成中间文件。
如何定义新的隐式规则
你可以使用模式规则来定义一个隐含规则。一个模式规则就好像一个一般的规则,只是在规则中,目标的定义需要有 % 字符。 % 的意思是表示一个或多个任意字符。
在依赖目标中同样可以使用 % ,只是依赖目标中的 % 的取值,取决于其目标。
有一点需要注意的是, % 的展开发生在变量和函数的展开之后,变量和函数的展开发生在make载入 Makefile时,而模式规则中的 % 则发生在运行时。
模式规则介绍
模式规则中,至少在规则的目标定义中要包含 % ,否则,就是一般的规则。目标中的 % 定义表示对文件名的匹配, % 表示长度任意的非空字符串。
例如: %.c 表示以 .c 结尾的文件名(文件名的长度至少为3),而 s.%.c 则表示以 s. 开头, .c 结尾的文件名(文件名的长度至少为5)。
如果 % 定义在目标中,那么,目标中的 % 的值决定了依赖目标中的 % 的值,也就是说,目标中的模式的 % 决定了依赖目标中 % 的样子。例如有一个模式规则如下:
%.o : %.c ; <command ……>;
其含义是,指出了怎么从所有的 .c 文件生成相应的 .o 文件的规则。如果要生成的目标是 a.o b.o ,那么 %c 就是 a.c b.c 。
一旦依赖目标中的 % 模式被确定,那么,make会被要求去匹配当前目录下所有的文件名,一旦找到,make就会规则下的命令,所以,在模式规则中,
目标可能会是多个的,如果有模式匹配出多个目标,make就会产生所有的模式目标,此时,make关心的是依赖的文件名和生成目标的命令这两件事。
模式规则示例
下面这个例子表示了,把所有的 .c 文件都编译成 .o 文件.
%.o : %.c
$(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@
其中, $@ 表示所有的目标的挨个值, $< 表示了所有依赖目标的挨个值。这些奇怪的变量我们叫“自动化变量”,后面会详细讲述。
下面的这个例子中有两个目标是模式的:
%.tab.c %.tab.h: %.y
bison -d $<
这条规则告诉make把所有的 .y 文件都以 bison -d
自动化变量
在上述的模式规则中,目标和依赖文件都是一系例的文件,那么我们如何书写一个命令来完成从不同的依赖文件生成相应的目标?因为在每一次的对模式规则的解析时,都会是不同的目标和依赖文件。
自动化变量就是完成这个功能的。在前面,我们已经对自动化变量有所提涉,相信你看到这里已对它有一个感性认识了。所谓自动化变量,就是这种变量会把模式中所定义的一系列的文件自动地挨个取出,直至所有的符合模式的文件都取完了。这种自动化变量只应出现在规则的命令中。
下面是所有的自动化变量及其说明:
1 | $@ : 表示规则中的目标文件集。在模式规则中,如果有多个目标,那么, $@ 就是匹配于目标中模式定义的集合。 |
下面是对于上面的七个变量分别加上 D 或是 F 的含义:
1 | $(@D) |
还得要注意的是,这些变量只使用在规则的命令中,而且一般都是“显式规则”和“静态模式规则”(参见前面“书写规则”一章)。其在隐含规则中并没有意义。
模式的匹配
一般来说,一个目标的模式有一个有前缀或是后缀的 % ,或是没有前后缀,直接就是一个 % 。因为 % 代表一个或多个字符,所以在定义好了的模式中,我们把 % 所匹配的内容叫做“茎”,例如 %.c 所匹配的文件“test.c”中“test”就是“茎”。因为在目标和依赖目标中同时有 % 时,依赖目标的“茎”会传给目标,当做目标中的“茎”。
当一个模式匹配包含有斜杠(实际也不经常包含)的文件时,那么在进行模式匹配时,目录部分会首先被移开,然后进行匹配,成功后,再把目录加回去。在进行“茎”的传递时,我们需要知道这个步骤。例如有一个模式 e%t ,文件 src/eat 匹配于该模式,于是 src/a 就是其“茎”,如果这个模式定义在依赖目标中,而被依赖于这个模式的目标中又有个模式 c%r ,那么,目标就是 src/car 。(“茎”被传递)
重载内建隐含规则
你可以重载内建的隐含规则(或是定义一个全新的),例如你可以重新构造和内建隐含规则不同的命令,如:
1 | %.o : %.c |
你可以取消内建的隐含规则,只要不在后面写命令就行。如:
%.o : %.s
同样,你也可以重新定义一个全新的隐含规则,其在隐含规则中的位置取决于你在哪里写下这个规则。朝前的位置就靠前。
老式风格的隐式规则
后缀规则是一个比较老式的定义隐含规则的方法。后缀规则会被模式规则逐步地取代。因为模式规则更强更清晰。
为了和老版本的Makefile兼容,GNU make同样兼容于这些东西。后缀规则有两种方式:“双后缀”和“单后缀”。
双后缀规则定义了一对后缀:目标文件的后缀和依赖目标(源文件)的后缀。如 .c.o 相当于 %o : %c 。单后缀规则只定义一个后缀,也就是源文件的后缀。
如 .c 相当于 % : %.c 。
后缀规则中所定义的后缀应该是make所认识的,如果一个后缀是make所认识的,那么这个规则就是单后缀规则,而如果两个连在一起的后缀都被make所认识,那就是双后缀规则。
例如: .c 和 .o 都是make所知道。因而,如果你定义了一个规则是 .c.o 那么其就是双后缀规则,意义就是 .c 是源文件的后缀, .o 是目标文件的后缀。如下示例:
1 | .c.o: |
make的参数 -r 或 -no-builtin-rules 也会使用得默认的后缀列表为空。而变量 SUFFIXE 被用来定义默认的后缀列表,你可以用 .SUFFIXES 来改变后缀列表,
但请不要改变变量 SUFFIXE 的值。
隐式规则的搜索算法
比如我们有一个目标叫 T。下面是搜索目标T的规则的算法。请注意,在下面,我们没有提到后缀规则,
原因是,所有的后缀规则在Makefile被载入内存时,会被转换成模式规则。如果目标是 archive(member) 的函数库文件模式,那么这个算法会被运行两次,
第一次是找目标T,如果没有找到的话,那么进入第二次,第二次会把 member 当作T来搜索。
1 把T的目录部分分离出来。叫D,而剩余部分叫N。(如:如果T是 src/foo.o ,那么,D就是 src/ ,N就是 foo.o )
2 创建所有匹配于T或是N的模式规则列表。
3 如果在模式规则列表中有匹配所有文件的模式,如 % ,那么从列表中移除其它的模式。
4 移除列表中没有命令的规则。
5 对于第一个在列表中的模式规则:
- 推导其“茎”S,S应该是T或是N匹配于模式中 % 非空的部分。
- 计算依赖文件。把依赖文件中的 % 都替换成“茎”S。如果目标模式中没有包含斜框字符,而把D加在第一个依赖文件的开头。
- 测试是否所有的依赖文件都存在或是理当存在。(如果有一个文件被定义成另外一个规则的目标文件,或者是一个显式规则的依赖文件,那么这个文件就叫“理当存在”)
- 如果所有的依赖文件存在或是理当存在,或是就没有依赖文件。那么这条规则将被采用,退出该算法。
6 如果经过第5步,没有模式规则被找到,那么就做更进一步的搜索。对于存在于列表中的第一个模式规则: - 如果规则是终止规则,那就忽略它,继续下一条模式规则。
- 计算依赖文件。(同第5步)
- 测试所有的依赖文件是否存在或是理当存在。
- 对于不存在的依赖文件,递归调用这个算法查找他是否可以被隐含规则找到。
- 如果所有的依赖文件存在或是理当存在,或是就根本没有依赖文件。那么这条规则被采用,退出该算法。
- 如果没有隐含规则可以使用,查看 .DEFAULT 规则,如果有,采用,把 .DEFAULT 的命令给T使用。
一旦规则被找到,就会执行其相当的命令,而此时,我们的自动化变量的值才会生成。
使用make更新archive文件
函数库文件也就是对Object文件(程序编译的中间文件)的打包文件。在Unix下,一般是由命令 ar 来完成打包工作。
Archive files are files containing named sub-files called members; they are maintained with the program ar and their main use is as subroutine libraries for linking.
archive members
一个函数库文件由多个文件组成。你可以用如下格式指定函数库文件及其组成:
1 | archive(member) |
archive后缀规则
函数库成员的隐含规则
当make搜索一个目标的隐含规则时,一个特殊的特性是,如果这个目标是 a(m) 形式的,其会把目标变成 (m) 。于是,如果我们的成员是 %.o 的模式定义,并且如果我们使用 make foo.a(bar.o) 的形式调用Makefile时,隐含规则会去找 bar.o 的规则,如果没有定义 bar.o 的规则,那么内建隐含规则生效,make会去找 bar.c 文件来生成 bar.o ,如果找得到的话,make执行的命令大致如下:
1 | cc -c bar.c -o bar.o |
函数库文件的后缀规则
你可以使用“后缀规则”和“隐含规则”来生成函数库打包文件,如:
1 | .c.a: |
陷阱:
在进行函数库打包文件生成时,请小心使用make的并行机制( -j 参数)。如果多个 ar 命令在同一时间运行在同一个函数库打包文件上,就很有可以损坏这个函数库文件。
所以,在make未来的版本中,应该提供一种机制来避免并行操作发生在函数打包文件上。
但就目前而言,你还是应该不要尽量不要使用 -j 参数