Makefile 指南
前言
此文档内容是对于博客 跟我一起写Makefile 的归纳整理。
规范了文档格式,重构为参考手册的形式,不适合作为教程,适合作为参考。
Makefile 介绍
编写原则
- 如果这个工程没有编译过,那么所有的 C 文件都需要编译并链接;
- 如果这个工程的某几个 C 文件被修改,那么只编译被修改的 C 文件,并连接目标程序;
- 如果这个工程的头文件被改变了,那么需要编译引用了这几个头文件的 C 文件,并连接目标程序。
Makefile 规则
1 | target ...: prerequisites ... |
target 一个目标文件(Object File),或一个可执行文件,或一个标签(Label)
prerequisites 生成该 target 所依赖的文件以或/和 target
command 该 target 要执行的命令
Makefile 示例
1 | edit: main.o kbd.o command.o display.o \ |
Make 工作方式
输入 make 命令后:
- Make 会在当前目录下寻找名字叫做“Makefile”或“makefile”的文件;
- 如果找到,继续找到文件中第一个 target(目标文件);
- 如果 target 不存在,或者 target 后面依赖的“.o”文件的修改时间比 target 要新,那么,它会执行后面所定义的命令来生成 target 文件;
- 如果 target 依赖的“.o”文件也不存在,那么 Make 会在当前文件中寻找目标为“.o”文件的依赖性,如果找到则再根据哪个规则生成“.o”文件;
- 如果 C 文件和 H 文件存在,则 Make 会根据规则生成“.o”文件,从而生成最终的 target 文件。
Make 会层层递进的寻找依赖关系,直到编译出最终的 target,一旦依赖的文件不存在,那么 Make 就会报错。
clean 命令没有被 target 依赖,那么 Make 不会执行后面定义的命令,除非显式的执行:
1 | make clean |
Makefile 中的变量
1 | objects = main.o kbd.o command.o display.o \ |
Makefile 自动推导
Make 可以自动推导文件以及文件依赖后面的命令.
1 | objects = main.o kbd.o command.o display.o \ |
.PHONY
表示 clean
是一个伪目标文件。
另类风格的 Makefile
1 | objects = main.o kbd.o command.o display.o \ |
clean 规则
1 | clean: |
1 |
|
“-”表示也许某些文件会出现问题,忽略继续做后面的事
Makefile 里有什么?
- 显式规则,它说明了如何生成一个或者多个目标文件。这是由 Makefile 的书写者明显指出要生成的文件、文件的依赖文件和生成的命令;
- 隐晦规则,由于 Make 有自动推导功能,所以隐晦规则支持简写 Makefile;
- 变量定义,在 Makefile 中我们要定义一些列的变量,一般都是字符串,类似 C 语言中的宏;
- 文件指示,包括 3 部分:在一个 Makefile 中引用另一个 Makefile,类似 C 语言中的“include”;另一个是根据某些情况指定 Makefile 的有效部分,类似 C 语言中的预编译“#if”;最后就是定义一个多行命令;
- 注释,Makefile 中只有行注释,使用“#”字符进行注释。
Makefile 中命令必须以 [Tab]
开始。
Makfefile 文件名
默认情况下,Make 会在当前目录按顺序寻找名为“GNUmakefile”、“makefile”、“Makefile”的文件。
最好使用“Makefile”,更醒目,通用性更强。
可指定 Makefile 文件名:
1 | make -f Make.my |
引用其他的 Makfile
使用 include
关键字包含别的 Makefile,类似 C 语言中的 #include,被包含的文件内容会被扩展到当前包含的位置。
1 | include <filename> |
1 | include foo.make *.mk $(bar) |
Make 首先寻找 include 指出其他 Makefile,并将它们的内容扩展到当前位置。
Make 首先在当前目录寻找文件,找不到时,Make 还会在如下目录寻找:
- 如果 Make 执行时,被指定了
-I
或--include-dir
参数,那么 Make 就会在此参数指定的目录下寻找; - 如果目录
<prefix>/include
(一般为/usr/local/bin
或/usr/include
)存在,Make 也会在里面寻找。
1 | -include <filename> |
“-”表示,无论 include 过程中出现什么错误,都不要报错,继续执行。
其他版本中有类似的 sinclude
兼容命令。
环境变量 MAKEFILES
如果当前环境中定义了 MAKFEFILES
变量,那么 Make 会把它当作类似 include
的动作。
此变量中的值为其他 Makefile 的定义,使用空格隔开。
Make 工作方式
- 读入所有 Makefile;
- 读入被 include 的其他 Makefile;
- 初始化文件中的变量;
- 推导隐晦规则,并分析所有规则;
- 为所有的目标文件创建依赖关系链;
- 根据依赖关系,决定那些目标要重新生成;
- 执行生成命令。
书写规则
规则包含两部分,一个是依赖关系,一个是生成目标的方法。
1 | foo.o: foo.c defs.h |
规则语法
1 | targets: prerequisites |
1 | targets: prerequisites; command |
targets 是文件名,以空格分开,可以使用通配符。
command 是命令行,如果不与前面的内容在一行,那么必须以 [Tab]
键开头,如果在一行,可以以分号作为分隔。
prerequisites 也就是目标所依赖的文件。如果其中的某个文件比目标文件新,那么目标文件被认为是“过时的”,需要被重新生成。
如果命令太长,可以使用 \
作为换行符。
一般,Make 会以 UNIX 的标准 Shell,也就是 /bin/sh
来执行。
规则中使用通配符
Make 支持 3 个通配符,*
、?
和 ~
。
~
表示当前用户的 $HOME
目录。
1 | objects: $(wildcard *.o) |
文件搜寻
VAPTH
可指明 Make 在当前目录找不到的情况下,去指定的目录中去寻找文件,多个路径使用 :
分隔。
1 | VPATH = src:.../headers |
或使用 Make 中的 vpath
关键字。
1 | vpath <pattern> <directories> |
vpath
中的 <pattern>
需要包含 %
字符,%
的意思是匹配零或若干字符。
1 | vpath %.h ../headers |
多行 vpath
关键字,Make 将会按顺序执行搜索。
1 | vpath %.c foo:bar |
伪目标
伪目标并不是一个文件,只是一个标签。
避免和文件重名,使用 .PHONY
标记显式指定一个“伪目标”。
当伪目标放在第一行时,可作为“默认目标”,利用它实现同时编译出多个可执行文件。
1 | all: prog1 prog2 prog3 |
伪目标也可以成为依赖:
1 |
|
多目标
Makefile 支持多个目标,有时多个目标会依赖于同一个文件,并且生成命令类似,可以将其合并起来。
自动化变量 $@
表示目前规则中所有目标的集合。
1 | bigoutput littleoutput: text.g |
等价于
1 | bigoutout: text.g |
静态模式
静态模式可以更容易的定义多目标的规则,使得规则更加灵活。
1 | <target ...>: <target-pattern>: <prereq-patterns ...> |
targets 定义了一系列的目标文件,可以有通配符,是目标的集合。
target-pattern 指明了 targets 的模式,也就是目标集模式。
prereq-patterns 是目标的依赖模式,对 target-pattern 再进行一次依赖目标的定义。
1 | objects = foo.o bar.o |
上面指明了目标从 $object
中获取,%.o
表明所有以 .o
结尾的目标,后面的 %.c
取模式 %.o
的 %
部分,也就是 foo bar
,并添加上 .c
的后缀,表示依赖的目标是 foo.c bar.c
。
展开上述规则:
1 | foo.o: foo.c |
自动生成依赖性
使用编译器命令自动生成依赖关系:
1 | cc -M main.c |
会生成:
1 | main.o: main.c defs.h |
GNU 建议把每一个源文件的依赖关系保存到一个对应的 .d
文件中。
可以让 Make 自动生成 .d
文件,并包含在 Makefile 中。
1 | %.d: %.c |
意思是,所有的 .d
文件依赖于 .c
文件, rm -f $@
意思是删除所有的目标,也就是 .d
文件,第二行意思是,为每个依赖文件 $<
,也就是 .c
文件生成依赖文件, $@
表示模式 %.d
文件,如果有一个 C 文件是 a.c
,那么 %
就是 a
, $$$$
表示一个随机编号,第二行生成的文件可能是“name.d.12345”,第三行使用 sed
命令做了替换。
从而在编译器生成的依赖关系中加入了 .d
文件依赖。即:
1 | main.o: main.c defs.h |
变成了
1 | main.o main.d: main.c defs.h |
书写命令
显示命令
通常 Make 会把执行的命令在命令执行前打印出来,使用 @
字符可以不让 Make 显示命令。
1 | @echo 正在编译 xxx 模块…… |
Make 执行时,只会显示出 正在编译 xxx 模块……
,如果没有 @
,那么显示:
1 | @echo 正在编译 xxx 模块…… |
使用 Make 带上 -n
或 --just-print
参数,那么只显示命令,不执行命令。
-s
和 --silent
或 --quiet
是全面禁止命令的显式。
命令执行
如果需要在前一个命令执行的基础上执行下一个命令,需要放在一行,以 ;
隔开:
不能分两行执行。
1 | exec: |
嵌套执行 Make
大工程中,可能会把不同模块或者不同功能的源文件放在不同目录中,每一个目录中可以写一个 Makefile,有利于让 Makefile 变得更简洁,而不是全部都写在一个 Makefile 中。
1 | subsystem: |
等价于:
1 | subsystem: |
$(MAKE)
是默认变量表示 Make 本身,使我们可以使用 Make 携带参数,上面两个 Makefile 都表示进入 subdir,然后执行 make 命令。
此 Makefile 被称为总控 Makefile,它可以将一些参数带入下一级 Makefile。
如果要传递变量到下一级,使用 export
,如果不想,那么使用 unexport
。
1 | export <varible ...>; |
1 | unexport <varible ...>; |
如:
1 | export variable = value |
当单独使用 export
占一行时,那么表示传递所有变量。
两个特殊变量一定会传递,SHELL
和 MAKEFLAGS
。
-w
或是 --print-directory
会在 Make
的过程中输出目前的工作目录信息:
1 | make: Entering directory `/home/l0neman/hello'. |
定义命令包
使用 define
开始,endef
结束,可以将一组命令定义为一个变量,成为命令包。
1 | define run-yacc |
1 | foo.c: foo.y |
使用变量
变量基础
使用变量,需要在变量名前加上 $
符号,最好使用 ()
和 {}
被变量括起来,使用真实 $
变量,需要用 $$
表示。
1 | objects = program.o foo.o utils.o |
变量和 C 语言的宏一样,会在使用它的位置展开。
1 | foo = c |
得到:
1 | prog.o: prog.c |
变量的变量
使用 =
来将变量值赋值给另一个变量
1 | foo = $(bar) |
好处是可以把变量移动到后面定义:
1 | CFLAGS = $(include_dirs) -O |
但可能会出现递归定义,Make 会检测到这种定义,从而报错。
1 | CFLAGS = $(CFLAGS) -o |
可以使用 :=
来避免这种情况,:=
不允许使用后面定义的变量。
1 | x := foo |
一个复杂的变量例子:
1 | ifeq (0,${MAKELEVEL}) |
定义一个空格变量:
1 | nullstring := |
nullstring
为一个 Empty 变量,表示什么都没有,那么 space
表示一个空格(在 #
号前面)。
注意,#
号符号前面的空格将会包含在变量中:
1 | dir := /foo/bar # directory to put the frobs in |
使用 ?=
,表示如果变量没有被定义过,那么变量的值就是右边的,否则什么也不做。
1 | FOO ?= bar |
等价于:
1 | ifeq ($(origin FOO), undefined) |
变量高级用法
可以替换变量中共有的部分,格式是 $(var:a=b
或 $(var:a=b)
,表示把变量 foo
中所有以 a
字符串结尾的 a
部分替换成 b
。
1 | foo := a.o b.o c.o |
或“静态模式”
1 | foo := a.o b.o c.o |
- 变量再次当作变量
1 | x = y |
1 | x = $(y) |
1 | first_second = Hello |
1 | a_objects := a.o b.o c.o |
1 | ifdef do_sort |
1 | dir = foo |
追加变量值
使用 +=
给变量追加值。
1 | objects = main.o foo.o bar.o utils.o |
可以解释为:
1 | objects = main.o foo.o bar.o utils.o |
如果变量之前没有定义过,那么 +=
会自动变成 =
。+=
会继承上次操作的赋值符。如果前一次的是 := ,那么 += 会以 :=
作为其赋值符,那么 +=
会以 :=
作为其赋值符。
override 指示符
如果有变量是通过 Make 的命令行设置的,那么 Makefile 对这个变量的赋值将会被忽略。
如果想要设置这类参数,可使用 override
指示符。
1 | override <variable>; = <value>; |
在 define
前使用 override
进行多行形式的变量定义:
1 | override define foo |
多行变量
使用 define
关键字可以设置带有换行的变量值。
define
指示符后面跟的变量的名字,而重起一行定义变量的值,定义是以 endef
关键字结束。
1 | define two-lines |
环境变量
Make 运行时的系统环境变量在 Make 开始执行时被载入到 Makefile 文件中,如果 Makefile 中已经定义了这个变量,或者变量由 make 命令带入,那么系统的环境变量的值将被覆盖。
如果系统中定义了 CFLAGS
环境变量,那么就可以在所有的 Makefile
中使用这个变量了。
目标变量
可以为某个目标设置局部变量,被称为 Target-specific Variable
,它可以和全局变量同名,由于它的作用域值在这条规则以及连带规则中,所以其值只在作用范围内有效,不会影响规则链以外的全局变量的值。
1 | <target ...>: <variable-assignment> |
=
、:=
、+=
或 ?=
。
第二行针对 make 命令带入的变量,或是系统环境变量。
1 | prog : CFLAGS = -g |
上面的示例,不管全局的 $(CFLAGS)
的值是什么,在目标 prog
中,以及其所引发的所有规则中(prog.o foo.o bar.o 的规则), $(CFLAGS)
的值都是 -g
。
模式变量
GNU 的 Make 还支持模式变量(Pattern-specific Variable),可以给定一种模式,把变量定义在符合这种模式的所有目标上。
模式(pattern)至少含有一个 %
,定义如下,给所有以 .o
结尾的目标定义目标变量。
1 | %.o: FLAGS = -o |
模式变量的语法和目标变量一样:
1 | <pattern ...>; : <variable-assignment> |
使用条件判断
示例
条件判断,可以让 Make 根据运行时的不同情况选择不同分支。
1 | libs_for_gcc = -lgnu |
上面的示例,目标 foo
根据变量 $(CC)
的值来选取不同的函数库编译程序。
上面也可写成如下:
1 | libs_for_gcc = -lgnu |
语法
1 | <conditional-directive> |
1 | <conditional-directive> |
- ifeq
比较两个参数的值是否相同,参数中还可以使用 Make 支持的函数。
1 | ifeq (<arg1>, <arg2>) |
包含函数:
1 | ifeq ($(strip $(foo)),) |
上面表示如果函数的返回值是空(Empty),那么
- ifneq
比较两个参数是否不相同。
1 | ifneq (<arg1>, <arg2>) |
- ifdef
判断变量的值不为空,则通过。
1 | ifdef <vaiable-name> |
它不会扩展变量值到当前位置,只会判断是否有值。
- ifndef
与 ifdef 相反。
1 | ifndef <variable-name> |
在 [Tab]
键作为开始。那么注释符 #
是安全的。
Make 在读取 Makefile 时就计算条件表达式的值,所以避免将自动化变量如 $@
放入条件表达式,因为它们是运行时才有的。
Make 不允许把整个条件语句分成两部分放在不同的文件中。
使用函数
Makefile 中可以使用函数来处理变量。
函数的调用语法
1 | $(<function> <arguments>) |
或:
1 | ${<function> <arguments>} |
,
分隔。
1 | comma:= , |
上面使用了字符串替换函数 subst
,将 $(foo)
中的空格替换成了 ,
结果是 $(bar)=a,b,c
字符串处理函数
- subst
1 | $(subst, <from>,<to>,<text>) |
- patsubst
1 | $(patsubst <pattern>,<replacement>,<text>) |
- strip
1 | $(strip <string>) |
- findstring
1 | $(findstring <find>,<in>) |
- filter
1 | $(filter <pattern...>,<text>) |
- filter-out
1 | $(filter-out <pattern...>,<text>) |
- sort
1 | $(sort <list>) |
- word
1 | $(word <n>,<text>) |
- wordlist
1 | $(wordlist <ss>,<e>,<text>) |
- words
1 | $(words <text>) |
- firstword
1 | $(firstword <text>) |
字符串函数实例:
利用搜索路径 VPATH
来指定编译器对头文件的搜索路径参数 CFLAGS
1 | override CFLAGS += $(patsubst %,-I%,$(subst :, ,$(VPATH))) |
文件名操作函数
每个函数的参数字符串都会被当做一个或是一系列的文件名来对待
- dir
1 | $(dir <names...>) |
- notdir
1 | $(notdir <names...>) |
- suffix
1 | $(suffix <names...>) |
- basename
1 | $(basename <names...>) |
- addsuffix
1 | $(addsuffix <suffix>,<names...>) |
- addprefix
1 | $(addprefix <prefix>,<names...>) |
- join
1 | $(join <list1>,<list2>) |
foreach 函数
1 | $(foreach <var>,<list>,<text>) |
,把参数 <list>
中的单词逐一取出放到参数 <var>
所指定的变量中,然后再执行 <text>
所包含的表达式。每一次 <text>
会返回一个字符串,循环过程中, <text>
的所返回的每个字符串会以空格分隔,最后当整个循环结束时, <text>
所返回的每个字符串所组成的整个字符串(以空格分隔)将会是 foreach
函数的返回值
实例:
1 | names := a b c d |
$(name)
中的单词会被挨个取出,并存到变量 n
中,$(n).o
每次根据 $(n)
计算出一个值,这些值以空格分隔,最后作为foreach函数的返回,那么,$(files)
的值是 a.o b.o c.o d.o
if 函数
1 | $(if <condition>,<then-part>) |
if
函数可以包含“else”部分,或是不含。即 if
函数的参数可以是两个,也可以是三个。
<condition>
参数是 if
的表达式,如果其返回的为非空字符串,那么这个表达式就相当于返回真,于是,<then-part>
会被计算,否则 <else-part>
会被计算。
if
函数的返回值:如果 <condition>
为真(非空字符串),那个 <then-part>
会是整个函数的返回值,如果 <condition>
为假(空字符串),那么 <else-part>
会是整个函数的返回值,此时如果 <else-part>
没有被定义,那么,整个函数返回空字串。
所以,<then-part>
和 <else-part>
只会有一个被计算
call 函数
唯一一个可以用来创建新的参数化的函数,可以写一个非常复杂的表达式,这个表达式中,可以定义许多参数,然后可以用 call
函数来向这个表达式传递参数。
1 | $(call <expression>,<parm1>,<parm2>,...,<parmn>) |
当 Make 执行这个函数时,<expression>
参数中的变量,如 $(1)
、$(2)
等,会被参数 <parm1>
、<parm2>
、<parm3>
依次取代。而 <expression>
的返回值就是 call
函数的返回值。
示例:
1 | reverse = $(1) $(2) |
foo
的值就是 a b
,参数的次序可以是自定义的,不一定是顺序的。
1 | reverse = $(2) $(1) |
此时的 foo
的值就是 b a
备注:在向 call
函数传递参数时要尤其注意空格的使用。call
函数在处理参数时,第 2
个及其之后的参数中的空格会被保留,因而可能造成一些奇怪的效果。因而在向 call
函数提供参数时,最安全的做法是去除所有多余的空格。
origin 函数
它并不操作变量的值,它告诉你这个变量的来源。
1 | $(origin <variable>) |
$
符号)
返回值:
1 | undefined -> 如果 <variable> 从来没有定义过 |
用法实例:
有一个 Makefile 包含了一个定义文件 Make.def,在 Make.def 中定义了一个变量“bletch”,而此时环境中也有一个环境变量“bletch”,此时,判断如果变量来源于环境,那么就把之重定义,如果来源于 Make.def 或是命令行等非环境的,那么就不重新定义它。
1 | ifdef bletch |
shell 函数
它的参数就是操作系统 Shell 的命令,shell 函数把执行操作系统命令后的输出作为函数返回。
示例:
1 | contents := $(shell cat foo) |
备注:这个函数会新生成一个 Shell 程序来执行命令,所以你要注意其运行性能。
控制 Make 的函数
1 | $(error <text ...>) |
可以将函数提前保存到变量,在合适的时候使用:
1 | ifdef ERROR_001 |
Make 的运行
Make 的退出码
make 命令执行后有三个退出码:
0 表示成功执行
1 Make 运行时出现任何错误
2 如果使用了 Make 的 -q
选项,并且 Make
使得一些目标不需要更新
指定 Makefile
可以使用 -f
或 -makefile
给 Make 指定特殊名字的 Makefile 文件。
1 | make -f hello.mk |
指定目标
在 make 命令后面跟目标名字即可指定目标。
1 | make clean |
make 的环境变量 MAKECMDGOALS
会存放命令指定的目标的列表,如果命令行没有指定,则是空值。
可以将它用于特殊情况:
1 | sources = foo.c bar.c |
如果没有执行 make clean
,那么会包含 foo.d
和 bar.d
这两个 Makefile
1 |
|
可 make all
编译所有目标,也可以 make prog1
单独编译目标。
GNU Makefile 目标编写规范:
伪目标 | 含义 |
---|---|
all | 编译所有的文件 |
clean | 删除所有被 make 创建的文件 |
install | 安装已编译好的程序,就是把目标文件复制到指定的目标 |
列出改变过的源文件 | |
tar | 把源程序打包备份成一个 tar 文件 |
dist | 创建一个压缩文件,一般先把 tar 文件压缩成 Z 文件,或者 gz 文件 |
TAGS | 更新所有的目标,以准备完整地编译使用 |
check/test | 一般用来测试 Makefile 的流程 |
检查规则
检查命令,或执行序列,不执行 Makefile 中的规则,指定使用如下参数:
1 | -n, --just-print, --dry-run, --recon |
不执行,只打印命令,不管目标是否更新,把规则和连带规则下的命令打印出来,这些参数对于调试 Makefile 很有用处。
1 | -t, --touch |
把目标文件的时间更新,但不更改目标文件。就是说,Make 假装编译目标,但不是真正的编译目标,只是把目标变成已编译过的状态。
1 | -q, --question |
寻找目标的意思,如果目标存在,那么什么也不输出,也不会执行编译,如果目标不存在,打印出一条出错信息。
1 | -W <file>, --what-if=<file>, --assume-new=<file>, --new-file=<file> |
指定一个文件。一般是是源文件(或依赖文件),Make 会根据规则推导来运行依赖于这个文件的命令,一般可以和“-n”参数同时使用,用来查看这个依赖文件发生的规则命令。
结合 -p
和 -v
来输出 Makefile 被执行时的信息。
Make 参数
GNU Make 3.80 的所有参数。
参数 | 含义 |
---|---|
-b, -m | 作用是忽略和其它版本 Make 的兼容性 |
-B, –always-make | 认为所有的目标都需要更新(重编译) |
-C |
指定读取 Makefile 的目录。如果有多个 -C 参数,Make 的解释是后面的路径以前面的作为相对路径,并以最后的目录作为被指定目录。如:make -C ~hchen/test -C prog 等价于 make -C ~hchen/test/prog |
-debug[= |
输出 Make 的调试信息。它有几种不同的级别可供选择,如果没有参数,那就是输出最简单的调试信息。下面是 1. a:all,输出所有的调试信息; 2. b:basic,只输出简单的调试信息。即输出不需要重编译的目标; 3. v:verbose,在 b 选项的级别之上。输出的信息包括哪个makefile被解析,不需要被重编译的依赖文件(或是依赖目标)等; 4. i:implicit,输出所以的隐含规则; 5. j:jobs,输出执行规则中命令的详细信息,如命令的 PID、返回码等; 6. m:Makefile,输出 Make 读取 Makefile,更新 Makefile,执行 Makefile 的信息。 |
-d | 相当于 –debug=a |
-e, –environment-overrides | 指明环境变量的值覆盖 Makefile 中定义的变量的值 |
-f= |
指定需要执行的 Makefile |
-h, –help | 显示帮助信息 |
-i , –ignore-errors | 执行时忽略所有的错误 |
-I |
定一个被包含 Makefile 的搜索目标。可以使用多个 -I 参数来指定多个目录 |
-j [ |
指同时运行命令的个数。如果不指定此参数,Make 运行命令时能运行多少就运行多少。如果有一个以上的 -j 参数,那么仅最后一个 -j 才是有效的。(这个参数在 MS-DOS 中是无用的) |
-k, –keep-going | 出错也不停止运行。如果生成一个目标失败了,那么依赖于其上的目标就不会被执行 |
-l |
指定 Make 运行命令的负载 |
-n, –just-print, –dry-run, –recon | 仅输出执行过程中的命令序列,但并不执行 |
-o |
不重新生成的指定的 <file> ,即使这个目标的依赖文件新于它 |
-p, –print-data-base | 输出 Makefile 中的所有数据,包括所有的规则和变量。这会让一个简单的 Makefile 都输出一堆信息。如果只是想输出信息而不想执行 Makefile,可以使用 make -qp 命令。如果想查看执行 Makefile 前的预设变量和规则,你可以使用 make –p –f /dev/null 。这个参数输出的信息会包含着你的 Makefile 文件的文件名和行号,所以,用来调试 Makefile 会很有用,特别是当环境变量很复杂时 |
-q, –question | 不运行命令,也不输出。仅检查所指定的目标是否需要更新。如果是 0 说明要更新,如果是 2 说明有错误发生 |
-r, –no-builtin-rules | 禁止 Make 使用任何隐含规则 |
-R, –no-builtin-variabes | 禁止 Make 使用任何作用于变量上的隐含规则 |
-s, –silent, –quiet | 在命令运行时不输出命令的输出 |
-S, –no-keep-going, –stop | 取消 -k 选项的作用。因为有些时候,Make 的选项是从环境变量 MAKEFLAGS 中继承下来的。所以可以在命令行中使用这个参数让环境变量中的 -k 选项失效 |
-t, –touch | 相当于 UNIX 的 touch 命令,只是把目标的修改日期变成最新的,就是阻止生成目标的命令运行 |
-v, –version | 输出 Make 程序的版本、版权等关于 Make 的信息 |
-w, –print-directory | 输出运行 Makefile 之前和之后的信息。这对于跟踪嵌套式调用 Make 时很有用 |
–no-print-directory | 禁止 -w 选项 |
-W |
假定目标 <file> 需要更新,如果和 -n 选项使用,那么这个参数会输出该目标更新时的运行动作。如果没有 -n 那么就像运行 UNIX 的 touch 命令一样,使 <file> 的修改时间为当前时间 |
–warn-undefined-variables | 只要 Make 发现有未定义的变量,那么输出警告信息 |
隐含规则
Makefile 中包含了一些隐含规则,是一种默认约定,例如将 .c
文件自动编译为 .o
文件。
1 | foo : foo.o bar.o |
可以省略如下使用 cc
编译器生成 .o
的规则。
1 | foo.o : foo.c |
隐含规则列表
make 命令的参数 -r
或 --no-builtin-rules
选项可取消所有的预置的隐含规则。
一个特殊的隐含规则,不能通过 -r
选项关闭。就是后缀规则,文件名中包含如下后缀,那么隐含规则生效。
1 | .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程序的隐含规则
<n>.o
的目标的依赖目标会自动推导为 <n>.c
,并且其生成命令是 $(CC) –c $(CPPFLAGS) $(CFLAGS)
。
- 编译 C++ 程序的隐含规则
<n>.o
的目标的依赖目标会自动推导为 <n>.cc
或是 <n>.C
,并且其生成命令是 $(CXX) –c $(CPPFLAGS) $(CFLAGS)
(建议使用 .cc
作为 C++
源文件的后缀,而不是 .C
)。
- 编译
Pascal
程序的隐含规则
<n>.o
的目标的依赖目标会自动推导为 <n>.p
,并且其生成命令是 $(PC) –c $(PFLAGS)
。
- 编译 Fortran/Ratfor 程序的隐含规则
<n>.o
的目标的依赖目标会自动推导为 <n>.r
或 <n>.F
或 <n>.f
,并且其生成命令是:
1 | .f $(FC) –c $(FFLAGS) |
- 预处理 Fortran/Ratfor 程序的隐含规则
<n>.f
的目标的依赖目标会自动推导为 <n>.r
或 <n>.F
。这个规则只是转换 Ratfor 或有预处理的 Fortran 程序到一个标准的 Fortran 程序。其使用的命令是:
1 | .F $(FC) –F $(CPPFLAGS) $(FFLAGS) |
- 编译 Modula-2 程序的隐含规则
<n>.sym
的目标的依赖目标会自动推导为 <n>.def
,并且其生成命令是:$(M2C) $(M2FLAGS) $(DEFFLAGS)
;<n>.o
的目标的依赖目标会自动推导为 <n>.mod
,并且其生成命令是:$(M2C) $(M2FLAGS) $(MODFLAGS)
。
- 汇编和汇编预处理的隐含规则
<n>.o
的目标的依赖目标会自动推导为 <n>.s
,默认使用编译器 as
,并且其生成命令是:$ (AS) $(ASFLAGS)
;<n>.s
的目标的依赖目标会自动推导为 <n>.S
,默认使用 C 预编译器 cpp
,并且其生成命令是:$(AS) $(ASFLAGS)
。
- 链接 Object 文件的隐含规则
<n>
目标依赖于 <n>.o
,通过运行 C 的编译器来运行链接程序生成(一般是 ld
),其生成命令是:$(CC) $(LDFLAGS) <n>.o $(LOADLIBES) $(LDLIBS)
。这个规则对于只有一个源文件的工程有效,同时也对多个 Object 文件(由不同的源文件生成)的也有效。
- Yacc C 程序时的隐含规则
<n>.c
的依赖文件被自动推导为 n.y
(Yacc 生成的文件),其生成命令是:$(YACC) $(YFALGS)
。
- Lex C 程序时的隐含规则
<n>.c
的依赖文件被自动推导为 n.l
(Lex 生成的文件),其生成命令是:$(LEX) $(LFALGS)
。
- Lex Ratfor 程序时的隐含规则
<n>.r
的依赖文件被自动推导为 n.l
(Lex 生成的文件),其生成命令是:$(LEX) $(LFALGS)
。
- 从 C 程序、Yacc 文件或 Lex 文件创建 Lint 库的隐含规则
<n>.ln
(lint 生成的文件)的依赖文件被自动推导为 n.c
,其生成命令是:$(LINT) $(LINTFALGS) $(CPPFLAGS) -i
。对于 <n>.y
和 <n>.l
也是同样的规则。
隐含规则使用的变量
隐含规则的命令使用了一些预置的变量。
make 命令的 -R
或 --no–builtin-variables
参数可以取消自定义的变量对隐含规则的作用。
隐含规则会使用两类变量,命令类型和命令参数类型。
- 关于命令的变量
变量 | 含义 |
---|---|
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 | 链接器参数 |
LFLAGS | Lex 文法分析器参数 |
PFLAGS | Pascal 语言编译器参数 |
RFLAGS | Ratfor 程序的 Fortran 编译器参数 |
YFLAGS | Yacc 文法分析器参数 |
隐含规则链
有时一个目标可能被一系列的隐含规则所作用,例如 .o
文件可能先被 Yacc 生成 .c
文件,再由 C 编译器编译 C 文件生成,如果 .c
文件存在, 那么直接调用 C 的编译隐含规则,否则先寻找 Yacc 的 .y
文件,产生 .c
。
这一系列的隐含规则叫做“隐含规则链”。
上面由隐含规则推断出来的中间过程的 .c
文件,被称为中间目标。
默认情况下,中间目标和一般目标的 2 个区别:除非中间的目标不存在,才会引发中间规则;只要目标成功产生,那么所产生的中间目标文件会被以 rm -f
删除。
可以使用伪目标 .INTERMEDIATE
来强制声明某个目标为中间目标。例如:.INTERMEDIATE : mid
。
可以使用伪目标 .SECONDARY
来强制声明阻止 Make 删除中间目标。例如:.SECONDARY : sec
。
定义模式规则
模式规则中,至少在规则的目标定义中要包含 %
。目标中的 %
定义表示对文件名的匹配,%
表示长度任意的非空字符串。
- 模式规则示例
1 | %.o : %.c ; <command ......>; |
1 | %.o : %.c |
1 | %.tab.c %.tab.h: %.y |
- 自动化变量
这种变量会把模式中所定义的一系列的文件自动依次取出,直至所有的符合模式的文件都取完了。
自动化变量只应出现在规则的命令中。
自动化变量 | 含义 |
---|---|
$@ | 表示规则中的目标文件集。在模式规则中,如果有多个目标,那么 $@ 就是匹配于目标中模式定义的集合 |
$% | 仅当目标是函数库文件中,表示规则中的目标成员名 |
$< | 依赖目标中的第一个目标名字。如果依赖目标是以模式(即 % )定义的,那么 $< 将是符合模式的一系列的文件集 |
$? | 所有比目标新的依赖目标的集合,以空格分隔 |
$^ | 所有的依赖目标的集合。以空格分隔。如果在依赖目标中有多个重复的,那么这个变量会去除重复的依赖目标,只保留一份 |
$+ | 这个变量类似 $^ ,也是所有依赖目标的集合。只是它不去除重复的依赖目标 |
$* | 这个变量表示目标模式中 % 及其之前的部分。如果目标是 dir/a.foo.b ,并且目标的模式是 a.%.b ,那么,$* 的值就是 dir/a.foo |
最好给 $
后面的那个特定字符都加上圆括号,比如 $(<)
就要比 $<
要好。
四个变量($@
、$<
、$%
、$*
)在扩展时只会有一个文件,而外另三个的值是一个文件列表。
搭配上 D
或 F
字符,可以取得文件的目录名或是在当前目录下的符合模式的文件名。
这是 GNU Make 旧版的特性,在新版,使用函数 dir
或 notdir
就可以做到。
七个变量分别加上 D 或是 F 的含义:
- $(@D)
表示 $@ 的目录部分(不以斜杠作为结尾),如果 $@ 值是 dir/foo.o ,那么 $(@D) 就是 dir ,而如果 $@ 中没有包含斜杠的话,其值就是 . (当前目录)。
$(@F)
表示 $@ 的文件部分,如果 $@ 值是 dir/foo.o ,那么 $(@F) 就是 foo.o , $(@F) 相当于函数 $(notdir $@) 。$(*D), $(*F)
和上面所述的同理,也是取文件的目录部分和文件部分。对于上面的那个例子, $(*D) 返回 dir ,而 $(*F) 返回 foo
- $(%D), $(%F)
分别表示了函数包文件成员的目录部分和文件部分。这对于形同 archive(member) 形式的目标中的 member 中包含了不同的目录很有用。
- $(<D), $(<F)
分别表示依赖文件的目录部分和文件部分。
- $(^D), $(^F)
分别表示所有依赖文件的目录部分和文件部分。(无相同的)
- $(+D), $(+F)
分别表示所有依赖文件的目录部分和文件部分。(可以有相同的)
- $(?D), $(?F)
分别表示被更新的依赖文件的目录部分和文件部分
模式的匹配
%
所匹配的内容被称为“茎”,例如 %.c
所匹配的文件 test.c
中 test
就是“茎”。
在目标和依赖目标中同时有 %
时,依赖目标的“茎”会传给目标,当做目标中的“茎”。
当一个模式匹配包含有斜杠(实际也不经常包含)的文件时,那么在进行模式匹配时,目录部分会首先被移开,然后进行匹配,成功后,再把目录加回去。
例如有一个模式 e%t
,文件 src/eat
匹配于该模式,于是 src/a
就是其“茎”,如果这个模式定义在依赖目标中,而被依赖于这个模式的目标中又有个模式 c%r
,那么目标就是 src/car
(“茎”被传递)。
重载内建隐含规则
可以重新构造和内建隐含规则不同的命令:
1 | %.o : %.c |
旧版“后缀规则”
后缀规则是一个比较老式的定义隐含规则的方法。后缀规则会被模式规则逐步取代。
后缀规则有两种方式:“双后缀”和“单后缀”
- 双后缀
双后缀规则定义了一对后缀:目标文件的后缀和依赖目标(源文件)的后缀。
如 .c.o
相当于 %o : %c
- 单后缀
定义源文件的后缀。如 .c
相当于 % : %.c
后缀规则中所定义的后缀应该是 Make 所认识的。
1 | .c.o: |
后缀规则不允许任何的依赖文件。
1 | .c.o: foo.h |
使用伪目标 .SUFFIXES
来定义或是删除特定后缀。
1 | .SUFFIXES: # 删除默认的后缀 |
make 命令的参数 -r
或 -no-builtin-rules
也会使默认的后缀列表为空。
变量 SUFFIXE
被用来定义默认的后缀列表,可以用 .SUFFIXES
来改变后缀列表,但不要改变变量 SUFFIXE
的值。
隐含规则搜索算法
使用 Make 更新函数库文件
设有目标 T
,搜索目标 T
的规则的算法如下:
所有的后缀规则在 Makefile 被载入内存时,会被转换成模式规则。如果目标是 archive(member)
的函数库文件模式,这个算法会被运行两次,第一次是找目标 T
,如果没有找到,进入第二次,第二次把 member
当作 T
来搜索。
把
T
的目录部分分离出来。叫D
,而剩余部分叫N
。(如:如果T
是src/foo.o
,那么D
就是src/
,N
就是foo.o
);创建所有匹配于T或是N的模式规则列表;
如果在模式规则列表中有匹配所有文件的模式,如
%
,那么从列表中移除其它的模式;移除列表中没有命令的规则;
对于第一个在列表中的模式规则:
1> 推导其“茎”
S
,S
应该是T
或是N
匹配于模式中%
非空的部分;2> 计算依赖文件。把依赖文件中的
%
都替换成“茎”S
。如果目标模式中没有包含斜框字符,而把D
加在第一个依赖文件的开头;3> 测试是否所有的依赖文件都存在或是理当存在。(如果有一个文件被定义成另外一个规则的目标文件,或者是一个显式规则的依赖文件,那么这个文件就叫“理当存在”);
4> 如果所有的依赖文件存在或是理当存在,或是就没有依赖文件。那么这条规则将被采用,退出该算法。
如果经过第5步,没有模式规则被找到,那么就做更进一步的搜索。对于存在于列表中的第一个模式规则:
1> 如果规则是终止规则,那就忽略它,继续下一条模式规则;
2> 计算依赖文件(同第 5 步);
3> 测试所有的依赖文件是否存在或是理当存在;
4> 对于不存在的依赖文件,递归调用这个算法查找他是否可以被隐含规则找到;
5> 如果所有的依赖文件存在或是理当存在,或是就根本没有依赖文件。那么这条规则被采用,退出该算法;
6> 如果没有隐含规则可以使用,查看
.DEFAULT
规则,如果有,采用,把.DEFAULT
的命令给T使用。
一旦规则被找到,就会执行其相当的命令,而此时自动化变量的值才会生成。
使用 Make 更新函数库文件
函数库文件就是对 Object 文件(程序编译的中间文件)的打包文件。
在Unix下,一般是由 ar
命令来完成打包工作。
函数库文件的成员
一个函数库文件由多个文件组成,使用如下格式指定函数库文件及其组成:
1 | archive(member) |
例如:
1 | foolib(hack.o) : hack.o |
指定多个 member
,使用空格隔开:
1 | foolib(hack.o kludge.o) |
等价于:
1 | foolib(hack.o) foolib(kludge.o) |
使用通配符:
1 | foolib(*.o) |
函数库成员的隐含规则
当 Make 搜索一个目标的隐含规则时,一个特殊的特性是,如果这个目标是 a(m)
形式的,其会把目标变成 (m)
。
如果成员是 %.o
的模式定义,并且如果使用 make foo.a(bar.o)
的形式调用 Makefile,隐含规则会去找 bar.o
的规则,如果没有定义 bar.o
的规则,那么内建隐含规则生效,Make 会去找 bar.c
文件来生成 bar.o
。
流程如下:
1 | cc -c bar.c -o bar.o |
函数库文件的后缀规则
可以使用“后缀规则”和“隐含规则”来生成函数库打包文件:
1 | .c.a: |
等效于:
1 | (%.o) : %.c |
注意事项
在进行函数库打包文件生成时,需要小心使用 Make 的并行机制(-j
参数)。如果多个 ar
命令在同一时间运行在同一个函数库打包文件上,就很有可能损坏这个函数库文件。