Overview of Make
The make utility automatically determines which pieces of a large program need to be
recompiled, and issues commands to recompile them.
To prepare to use make, you must write a file called the _makefile _that describes the
relationships among files in your program and provides commands for updating each file.
简单一句话:make是一个决定哪些需要被重新编译和确定用哪些命令来重新编译的工具,makefile是指导工具如何运行的。
An Introduction to Makefiles
What a Rule Looks Like
命令前面必须用tab
空格,如果想用别的需用.RECIPEPREFIX
指定。不建议修改
A Simple Makefile
c文件
#include <stdio.h>
int main()
{
printf("hello,make\n");
}
makefile文件
hello:hello.c
gcc hello.c -o hello
clean:
rm hello
clean不是别的目标的依赖,所以只在执行 make clean
时候才被执行,这种被称为伪目标。为避免存在clean文件。我们显式指定为伪目标,用关键词.PHONY
. 出错继续执行在 rm
前面加-
hello:hello.c
gcc hello.c -o hello
.PHONY: clean
clean:
-rm hello
How make Processes a Makefile**
- 通常我们运行make,它只会查找第一个目标执行。如果我们想指定运行某个目标可以使用
.DEFAULT_GOAL
关键词手动指定。 - make根据依赖是否发生变化来判断是否需要重新生成目标。
可以看到依赖未变化,不会重新生成目标。
Variables Make Makefiles Simpler
变量使Makefile变的更简单。
objs = hello.o
hello: $(objs)
gcc $(objs) -o hello
$(objs): hello.c
gcc -c hello.c -o $(objs)
.PHONY: clean
clean:
-rm hello hello.o
Letting make Deduce the Recipes
make自动推断命令
我们不需要指明编译的C文件,make可以自动推导出来。隐式规则。上述例子可以简化为:
objs = hello.o
hello:
$(objs):
.PHONY: clean
clean:
-rm hello hello.o
Writing Makefiles
What Makefiles Contain
Makefiles contain five kinds of things: explicit rules, implicit rules, variable definitions,
directives, and comments
显示规则,隐式规则,变量定义,指令和注释
Splitting Without Adding Whitespace
如果你想把一行长串字符分行后,在中间不加空格,需要用$
符号
var :=one$\
world
oneworld. $代表一个变量,为空
What Name to Give Your Makefile
- 对于GNU的make来说有三种命名:makefile、Makefile、GNUmakefile
- 可以用 -f 或者 -file来指定要执行的makefile
如果三个文件都存在,优先级的顺序为: GNUmakefile > makefile >Makefile
Including Other Makefile
- The
include
directive tells make to suspend reading the current makefile and read one or
more other makefiles before continuing 。
include ./inc/makefile.mk
makefile
include ./inc/makefile.mk
.PHNOY: test
test:
@echo $(var2)
./inc/makefile.mk
var2 = inc/makefile
- 头文件查找
.INCLUDE_DIRS
这个变量记录了默认搜索头文件的路径
-I
可以指定搜索头文件路径
**The Variable **MAKEFILES
make会使用环境变量,env
和set
命令可查看。
.PHONY test
test:
echo $(USER)
通常我们也可以通过export来定义临时环境变量。
export HELLO := hi
How Makefiles Are Remade
双冒号规则:
- 对于一个没有依赖而只有命令行的双冒号规则,当引用此目标时,规则的命令将会被无条件执行。
- 而普通单冒号规则,当规则的目标文件存在时,此规则的命令永远不会被执行(目标文件永远是最新的
hello::
gcc hello.c -o hello
clean:
rm hello
跟下面伪目标的效果是相同的。
.PHONY: hello
hello:
gcc hello.c -o hello
Overriding Part of Another Makefile
如果存在多个目标相同,以最后一个目标为准
How make Reads a Makefile
GNU make 分两个不同的阶段完成它的工作。立即展开和延迟展开
- 读取所有makefiles和include makefile,(internalizes )内在化所有显式规则和隐式规则的变量和值,建立一个所有目标和所有依赖的关系图
- make通过这些内在化的数据来决定执行更新目标和执行命令
变量和函数的展开发生在第一阶段,就叫做立即展开,否则称为延迟展开
How Makefiles Are Parsed
GNU make是一行一行解析makefiles的,解析的流程:
- 读取完整的逻辑行,包含反斜线的转义
- 去除注释
- 如果该行以命令前缀字符(tab)开头,并且我们处于规则上下文中,将该行添加到当前命令并阅读下一行
- 将立即展开的元素展开
- 扫描行中的分隔符(如“:”或“=”),以确定该行是宏定义还是规则
- 内化(Internalize )生成的操作并阅读下一行
Secondary Expansion
.SECONDEXPANSION: #添加二阶段展开关键词
objs = hello
.PHONY: print
print: $$(objs)
@echo $^
objs = hello.o
Writing Rules
Rule Syntax
2种规则语法:
如果你要用
,
需要写
‘
,需要写`
,需要写‘
‘
,
开启了二阶段展开则需要写成
‘
`,开启了二阶段展开则需要写成`
‘,开启了二阶段展开则需要写成‘$$$`
Types of Prerequisites
有两种前提:一种是普通依赖,另外一种是order-only依赖
语法:
targets : normal-prerequisites | order-only-prerequisites
普通依赖比目标新,则目标会被更新。但是order-only不管比不比目标新,都不会更新目标
Using Wildcard Characters in File Names
Wildcard Examples
objects = *.o
这种变量定义,在目标或先决条件中使用对象的值,则将在此处进行通配符扩展,如果在命令行中使用可能会被认为shell中的值,为避免这种麻烦,需要写成这种形式objects := $(wildcard *.o)
**The Function **wildcard
语法:$(wildcard _pattern_...)
Searching Directories for Prerequisites
VPATH**: Search Path for All Prerequisites**
VPATH指定make目标和依赖的寻找的一系列路径。VPATH = src:../headers
标记**src**
和**../headers**
两个路径
VPATH = src:headers
hello: hello.o
gcc -o hello hello.o
hello.o: hello.c hello.h
gcc -c ./src/hello.c -o hello.o -I./headers/
.PHONY:clean
clean:
rm *.o
rm hello
**The **vpath Directive
vpath %.h ../headers
在../header
中查找头文件vpath %.c foo:bar
先在foo中查找后缀为.c
的文件,然后再bar中查找。
How Directory Searches are Performed
Phony Targets
伪目标仅仅是为了执行相关的命令,而没有实际的目标文件。几种用法:
- 为了不与实际的文件产生冲突
避免冲突,修改为
- 递归调用发挥make的多线程能力:
原始写法:
使用伪目标,发挥多线程能力:
- 同时构建多个目标
- 当一个伪目标是另外一个的前提,那么这个伪目标作为另外一个的子线程
Rules without Recipes or Prerequisites
如果一个规则,既没有前提也没有执行的命令。那么make会认为这个这个目标总是需要被更新的。那么依赖这个规则的目标,每次执行都会更新。
clean: FORCE
rm $(objects)
FORCE:
这种方式跟.PHONY clean 是一个效果,伪目标方式更有效率些。但是有些make不支持伪目标。
Empty Target Files to Record Events
空目标是伪目标的变体;它用于保存您不时明确请求的某个操作的配方。与虚假目标不同,此目标文件确实可以存在;但是文件的内容并不重要,通常是空的。
我们只希望依赖改动后,执行print,那么我们touch 一个print 用于记录更新时间,当前提的修改时间比print时间更新时,执行。
Special Built-in Target Names
- .PHONY:特殊目标 .PHONY 的先决条件被认为是假目标。当需要考虑这样的目标时,make 将无条件地运行其配方,无论是否存在具有该名称的文件或其上次修改时间。
- .SUFFIXES:特殊目标 .SUFFIXES 的先决条件是用于检查后缀规则的后缀列表。
- .DEFAULT:为A指定的配方将用于任何没有找到规则的目标(无论是显式规则还是隐式规则)。如果指定了一个配方,那么在规则中作为先决条件而不是目标提到的每个文件都将执行该配方。
- .PRECIOUS:.PRECIOUS 所依赖的目标被给予以下特殊处理:如果在执行其配方期间杀死或中断了目标,则不会删除目标。此外,如果目标是一个中间文件,那么在不再需要它之后,它将不会被删除,就像通常所做的那样。在后一方面,它与 .SECONDARY 特殊目标重叠。
- .INTERMEDIATE:.INTERMEDIATE 所依赖的目标被视为中间文件。没有先决条件的 .INTERMEDIATE 没有效果。
- .SECONDARY:.SECONDARY 所依赖的目标被视为中间文件,只是它们永远不会被自动删除。没有先决条件的 .SECONDARY 会导致所有目标都被视为次要目标(即,没有目标被删除,因为它被认为是中间目标)。
- .SECONDEXPANSION:如果在 makefile 的任何地方提到 .SECONDEXPANSION 作为目标,那么在它出现后定义的所有先决条件列表将在所有 makefile 被读取后第二次展开。
- .DELETE_ON_ERROR:如果在 makefile 中的任何位置将 .DELETE_ON_ERROR 作为目标提及,则 make 将删除规则的目标(如果规则已更改,并且其配方以非零退出状态退出),就像它收到信号时一样。
- .IGNORE:如果为 .IGNORE 指定先决条件,则 make 将忽略在执行这些特定文件的配方时出现的错误。.IGNORE 的配方(如果有)将被忽略。如果将它作为一个没有先决条件的目标,.IGNORE 表示忽略所有文件的配方执行过程中的错误。’.IGNORE’ 的这种用法只支持历史兼容性。因为这会影响到makefile中的每个配方,所以它不是很有用;我们建议您使用更有选择性的方法来忽略特定配方中的错误。
- .LOW_RESOLUTION_TIME:如果为 .LOW_RESOLUTION_TIME 指定先决条件,则假定这些文件是由生成低分辨率时间戳的命令创建的。.LOW_RESOLUTION_TIME 目标的配方将被忽略。许多现代文件系统的高分辨率文件时间戳减少了错误地得出文件是最新的结论的机会。遗憾的是,某些主机不提供设置高分辨率文件时间戳的方法,因此像 ‘cp -p’ 这样显式设置文件时间戳的命令必须丢弃其亚秒部分。如果文件是由此类命令创建的,则应将其列为 A 的先决条件,以便 make 不会错误地断定该文件已过期。例如:
- .LOW_RESOLUTION_TIME(连接上面):由于 ‘cp -p’ 丢弃了 src 时间戳的亚秒部分,因此即使 dst 是最新的,它通常也比 src 略旧。如果dst的时间戳与 src 的时间戳在同一秒的开始,则 .LOW_RESOLUTION_TIME 行使dst被认为是最新的。由于归档格式的限制,归档成员时间戳的分辨率总是很低。您无需将存档成员列为 .LOW_RESOLUTION_TIME 的先决条件,因为 make 会自动执行此操作。
- .SILENT:如果为 .SILENT 指定先决条件,则 make 在执行这些文件之前不会打印用于重制这些特定文件的配方。.SILENT 的配方将被忽略。如果作为一个没有先决条件的目标,a说在执行之前不要打印任何配方。您还可以使用更有选择性的方法来静默特定的配方命令行。如果您想对特定运行的 make 禁用所有配方,请使用 ‘-s’ 或 ‘–silent’ 选项。
- .EXPORT_ALL_VARIABLES:只需将其作为目标提及,这就会告诉 make 在默认情况下将所有变量导出到子进程。
- .NOTPARALLEL:如果将 .NOTPARALLEL 作为目标提及,则即使给出了 ‘-j’ 选项,也会按顺序运行此 make 调用。任何递归调用的 make 命令仍将并行运行配方(除非其 makefile 也包含此目标)。此目标上的任何先决条件都将被忽略。
- .ONESHELL:如果将 .ONESHELL 作为目标提到,那么在构建目标时,配方的所有行都将被交给shell的单个调用,而不是单独调用每一行。
- .POSIX:如果将 .POSIX 作为目标提及,则将解析 makefile 并在符合 POSIX 的模式下运行。这并不意味着只有符合 POSIX 的 makefile 才会被接受:所有高级 GNU make 功能仍然可用。相反,此目标会导致 make 在 make 的默认行为不同的区域中按照 POSIX 的要求运行。特别是,如果提到此目标,则将调用配方,就好像 shell 已传递 -e 标志一样:配方中的第一个失败命令将导致配方立即失败。