make 速查表
Make速查表包含Make最重要概念、函数、方法等,帮助初学者快速掌握Make。
Makefile 入门
示例
a.txt: b.txt c.txt
	cat b.txt c.txt > a.txt
工作流程
- 读入所有的 Makefile。
- 读入被 include的其它Makefile。
- 初始化文件中的变量。
- 推导隐晦规则,并分析所有规则。
- 为所有的目标文件创建依赖关系链。
- 根据依赖关系,决定哪些目标要重新生成。
- 执行生成命令。
文件命令
make命令会以 GNUmakefile(不推荐使用)、makefile、Makefile(推荐使用)的顺序查找当前目录下的文件。
自定义文件路径
$ make target -f FILE
我们可以使用 -f FILE 来指定makefile文件的路径
隐式生成
如果文件夹中没有 makefile 文件,只有 main.c 源文件,那么我们可以使用 make main.o 隐式生成链接文件
$ make main.o
# 实际执行: cc    -c -o main.o main.c
规则
TARGET: PREREQUISITES
  COMMAMD
...
- target: 规则的目标。目标可以是规则的动作(如- clean等),也可以是目标文件或者最后的可执行文件。
- prerequisites: 规则的依赖。生成规则目标文件所需要的文件名列表(通常一个目标依赖于一个或者多个文件)。
- command: 规则的命令行。规则要执行的动作(任意的 shell 命令或者在 shell 下执行的程序)。命令需要以 tab 键开头
$ make [TARGET ...]
$ make        # 没有参数首先运行 TARGET
$ make help   # 显示可用目标
$ make dist   # 从当前目录制作一个发布存档
$ make check  # 无需安装的单元测试
清空目标文件
.PHONY: clean
clean:
  rm *.o temp
.PHONY 内置命令将排除 clean 文件,不会因为当前目录中因为有 clean 文件而不会不执行 clean 伪目标
clean 从来都是放在文件的最后
注释
makefile 文件的注释与 bash 脚本一致
# 这是一个注释
main.o : main.c
	cc -c main.c
换行 \
# 这是一个注释
main.o : main.c
	cc -c \
	main.c
引用其它的 Makefile
include 关键字可以把别的 Makefile 包含进来。这样使用 make 运行的时候就会
# makefile
include foo.make
如果你想让 make 不理那些无法读取的文件,并且继续执行。
-include <filename>
变量
赋值符
在执行时扩展,允许递归扩展
VARIABLE = value
在声明时扩展
可以防止递归,并且只能引用之前声明过的变量
VARIABLE := value
只有在该变量为空时才设置值
VARIABLE ?= value
将值追加到变量的尾端
VARIABLE += value
override
如果变量前不指定 override,那么命令行中指定的变量可以对 Makefile 中的变量重新定义。
# 不会重新定义
override VARIABLE = value
override VARIABLE := value
override VARIABLE ?= value
override VARIABLE += value
override define
  #...
endef
变量
需要使用 $() 或者 ${} 对变量进行引用
file = main.c
run:
	clang -o hello ${file}
避免递归变量
# 这样会使变量陷入无穷递归
A = $(B)
B = $(A)
x := foo
y := $(x) bar
x := later
# 等价于
# x = later
# y = foo bar
Shell 变量
如果要使用 Shell 变量,需要在之前加上 $
run:
	echo $$HOME
定义多行变量
define foo
echo foo
echo bar
endef
run:
	${foo}
自动变量
$@
                                $@:指代当前目标,即 Make 命令当前构建的那个目标
foo:
	touch $@
$ make foo
# $@ 就是指的这里的 foo
$<
                                $< 指代第一个前置条件。比如,规则为 t: p1 p2,那么 $< 就指代 p1
a.md: b.md c.md
  cp $< $@
使用 make a.md,相当于以下写法
a.md: b.md c.md
    cp b.md a.md
$^
                                $^ 指代所有的前置条件,去除重复项
a.md: b.md c.md
  echo $^
$+
                                $^ 指代所有的前置条件,不会去除重复项
a.md: b.md c.md c.md
	echo $+
$?
                                $? 指代更新的依赖,只有最近更新过的依赖才会引用
a.md: b.md c.md c.md
	cat $? > a.md
$*
                                $* 指代匹配符匹配的部分
main.o: main.c
	clang -o $* $*.c
$ make main
# 此时 cc  main.c -o main
$%
                                $%: 仅当目标是函数库文件中,表示规则中的目标成员名
- windows 中是 .lib文件
- unix 中是 .a文件
内置命名变量的参数
这些变量都是相关下面的命令的参数。如果没有指明其默认值,那么其默认值都是空。
| :- | :- | 
|---|---|
| ARFLAGS | 函数库打包程序AR命令的参数。默认值是 rv | 
| ASFLAGS | 汇编语言编译器参数。(当明显地调用 .s或.S文件时) | 
| CFLAGS | C 语言编译器参数。 | 
| CXXFLAGS | C++ 语言编译器参数。 | 
| COFLAGS | RCS 命令参数。 | 
| CPPFLAGS | C 预处理器参数。( C和Fortran编译器也会用到)。 | 
| FFLAGS | Fortran 语言编译器参数。 | 
| GFLAGS | SCCS get程序参数。 | 
| LDFLAGS | 链接器参数。(如: ld) | 
| PFLAGS | Pascal 语言编译器参数。 | 
| LFLAGS | Lex 文法分析器参数。 | 
| RFLAGS | Ratfor 程序的 Fortran 编译器参数。 | 
| YFLAGS | Yacc 文法分析器参数。 | 
内置已命名的变量
| :- | :- | 
|---|---|
| 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 | 
内置的变量
run:
	${CC} -o main main.c
书写规则
文件搜寻(vpath)
如果没有指定 vpath 变量,make 只会在当前的目录中去寻找依赖文件和目标文件。否则,如果当前目录没有,就会到指定的目录中去寻找
| :- | :- | 
|---|---|
| vpath <pattern> <directories> | 为符合模式 <pattern> 的文件指定搜索目录 <directories> | 
| vpath <pattern> | 清除符合模式 <pattern> 的文件的搜索目录。 | 
| vpath | 清除所有已被设置好了的文件搜索目录 | 
%
                                - vpath 使用方法中的 <pattern> 需要包含 %字符。%的意思是匹配零或若干字符(类似于通配符),并且引用规则是需要使用自动变量
vpath %.c dist
TARGET = hello
OBJ = bar.o foo.o
$(TARGET): $(OBJ)
	$(CC) -o $@ $^
%.o: $.c
	$(CC) -o $< -o #@
通配符
*
                                *:与 linux 系统下的一样
# 清除所有 .o 结尾的文件
clean:
    rm -f *.o
~
                                ~:在 linux 或 mac 下表示用户目录,win 下表示 HOME 环境变量
run:
    ls ~
?
                                ?: 与在 linux 等类似,可以匹配单个字符
run:
	ls -ll packag?.json
静态模式
TARGET: PREREQUISITES :PREREQUISITES
  COMMAMD
#...
- target定义了一系列的目标文件
- 第一个 prerequisites是指明了 target 的模式,也就是的目标集模式。
- 第二个 prerequisites是目标的依赖模式,它对第一个prerequisites形成的模式再进行一次依赖目标的定义
objects = foo.o main.o
$(objects): %.o: %.c
	$(CC) -c $(CFLAGS) $< -o $@
相当于:
foo.o : foo.c
    $(CC) -c $(CFLAGS) foo.c -o foo.o
main.o : main.c
    $(CC) -c $(CFLAGS) main.c -o main.o
伪目标
- 伪目标并不是一个文件,只是一个标签。只有通过显式地指明这个目标才能让其生效
- 使用 .PHONY来显式地指明目标是伪目标
.PHONY : clean
clean :
    rm *.o temp
约定俗成的规则
| :- | :- | 
|---|---|
| all | 该伪目标是所有目标的目标,一般用于编译所有的目标 | 
| clean | 该伪目标用于删除所有由 make 创建的文件 | 
| install | 该伪目标用于安装已编译好的程序,即将目标执行文件拷贝到指定目标中 | 
| print | 该伪目标用于例出改变过的源文件 | 
| tar | 该伪目标用于把源程序打包备份为 tar 文件 | 
| dist | 该伪目标用于创建压缩文件,一般将 tar 文件压成 Z 或 gz 文件 | 
| TAGS | 该伪目标用于更新所有的目标,以备完整地重编译使用 | 
| check/test | 这两个伪目标用于测试 makefile 的流程 | 
命令
回声(@)
正常情况下,make会打印每条命令,然后再执行,这就叫做回声(echoing)
all:
  # 会有命令执行显示
	echo Hello, world
all:
  # 不会有命令执行的显示
	@echo Hello, world
显示命令、禁止命令
显示命令
如果我们只希望显示命令,而不希望执行命令,可以使用 -n 或者 --just-print
$ make all --just-print
$ make all -n
禁止命令
-s 或 --silent 或 --quiet 与 @ 一样,用于禁止回声
$ make all -s
执行命令
使用 tab 及换行
exec:
    cd /home/hchen
    pwd
使用 ;
exec:
    cd /home/hchen; pwd
make 参数
| :- | :- | 
|---|---|
| -b,-m | 忽略和其它版本make的兼容性 | 
| -B | ( --always-make) 认为所有的目标都需要重编译 | 
| -C <dir> | ( --directory=<dir>) 指定读取makefile的目录 | 
| -e | ( --environment-overrides) 指明环境变量的值覆盖 makefile 中定义的变量的值 | 
| -f=<file> | 指定需要执行的makefile | 
| -h | 显示帮助信息 | 
| -i | ( --ignore-errors)在执行时忽略所有的错误 | 
| -I <dir> | ( --include-dir=<dir>) 指定一个被包含 makefile 的搜索目标 | 
| -j [<nums>] | ( --jobs[=<jobsnum>])指同时运行命令的个数 | 
| -k | ( --keep-going)出错也不停止运行 | 
| -l <load> | --load-average[=<load>]、-max-load[=<load>]指定make运行命令的负载 | 
| -n | ( --just-print,--dry-run,--recon) 仅输出执行过程中的命令序列,但不执行 | 
| -o <file> | ( --old-file=<file>,--assume-old=<file>)不重新生成的指定的 <file>,即使目标的依赖文件新于它 | 
| -p | ( --print-data-base) 输出 makefile 中的所有数据,包括所有的规则和变量 | 
| -q | ( --question) 不运行命令,也不输出。仅仅是检查所指定的目标是否需要更新 | 
| -r | ( --no-builtin-rules) 禁止 make 使用任何隐含规则 | 
| -R | ( --no-builtin-variabes) 禁止 make 使用任何作用于变量上的隐含规则 | 
| -s | ( --silent,--quiet) 在命令运行时不输出命令的输出 | 
| -S | ( --no-keep-going,--stop) 取消“-k”选项的作用 | 
| -t | --touch相当于 UNIX 的 touch 命令,只是把目标的修改日期变成最新的,也就是阻止生成目标的命令运行 | 
| -v | ( --version) 输出 make 程序的版本、版权等关于 make 的信息 | 
| -w | ( --print-directory) 输出运行 makefile 之前和之后的信息。--no-print-directory可以禁止-w | 
| -W <file> | --what-if=<file>,--new-file=<file>,--assume-file=<file>假定目标 <file> 需要更新,如果和-n选项使用,那么这个参数会输出该目标更新时的运行动作 | 
| --warn-undefined-variables | 只要 make 发现有未定义的变量,那么就输出警告信息 | 
-debug[=<options>]
输出 make 的调试信息。下面是 <options>的取值:
| options | :- | 
|---|---|
| a | all,输出所有的调试信息 | 
| b | basic,只输出简单的调试信息。即输出不需要重编译的目标 | 
| v | verbose,包括 b 的信息。输出包括 makefile 被解析的信息,不需要被重编译的依赖文件等 | 
| i | implicit,输出所有的隐含规则 | 
| j | jobs,输出执行规则中命令的详细信息,如命令的 PID、返回码等 | 
| m | makefile,输出 make 读取 makefile,更新 makefile,执行 makefile 的信息 | 
make 的退出码
| :- | :- | 
|---|---|
| 0 | 成功执行 | 
| 1 | 运行时出现错误 | 
| 2 | 使用了 -q选项,并且一些目标不需要更新 | 
判断和循环
单分支条件判断
- ifeq的意思表示条件语句的开始,表达式包含两个参数,如果相同则为真。
- ifneq的意思表示条件语句的开始,表达式包含两个参数,如果不同则为真。
- else表示条件表达式为假的情况。
- endif表示一个条件语句的结束,任何一个条件表达式都应该以- endif结束。
run:
ifeq ($(CC), cc)
	$(CC) -o foo foo.c
else
	$(CC) -o bar bar.c
endif
多分支条件判断
ifneq 语法
ifneq (<arg1>, <arg2>)
ifneq '<arg1>' '<arg2>'
ifneq "<arg1>" "<arg2>"
ifneq "<arg1>" '<arg2>'
ifneq '<arg1>' "<arg2>"
ifeq 语法
ifeq (<arg1>, <arg2>)
ifeq '<arg1>' '<arg2>'
ifeq "<arg1>" "<arg2>"
ifeq "<arg1>" '<arg2>'
ifeq '<arg1>' "<arg2>"
ifdef
ifdef <variable-name>
ifdef 会根据 variable-name 判断,如果为空则为真
bar =
foo = $(bar)
ifdef foo
    frobozz = yes
else
    frobozz = no
endif
run:
	echo $(frobozz)
ifndef 则和 ifdef 是相反的意思
for 循环
LIST = one two three
all:
	for i in $(LIST); do \
	    echo $$i; \
	done
函数
字符串处理函数 - 替换函数(subst)
把字串 <text> 中的 <from> 字符串替换成 <to> 。
$(subst <from>,<to>,<text>)
示例
$(subst ee,EE,feet on the street)
字符串处理函数 - 模式字符串替换函数(patsubst)
查找 <text> 中的单词(单词以空格、Tab或回车换行分隔)是否符合模式 <pattern>。匹配,则以 <replacement> 替换。
$(patsubst <pattern>,<replacement>,<text>)
- 示例
$(patsubst %.c,%.o,x.c.c bar.c)
把字串 x.c.c bar.c 符合模式 %.c 的单词替换成 %.o ,返回结果是 x.c.o bar.o
字符串处理函数 - 去空格函数(strip)
去掉 
$(strip <string>)
示例
$(strip a b c )
把字串 a b c 去掉开头和结尾的空格,结果是 a b c。
字符串处理函数 - 查找字符串函数(findstring)
在字串 <in> 中查找 <find> 字串。
$(findstring <find>,<in>)
示例:
$(findstring a,a b c)
$(findstring a,b c)
第一个函数返回 a 字符串,第二个返回空字符串
字符串处理函数 - 过滤函数(filter)
以 <pattern> 模式过滤 <text> 字符串中的单词,保留符合模式 <pattern> 的单词。可以有多个模式。
$(filter <pattern...>,<text>)
示例
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)
以 <pattern> 模式过滤 <text> 字符串中的单词,去除符合模式 <pattern> 的单词。可以有多个模式。
$(filter-out <pattern...>,<text>)
示例:
objects=main1.o foo.o main2.o bar.o
mains=main1.o main2.o
$(filter-out $(mains),$(objects))
# 返回值是 foo.o bar.o 。
字符串处理函数 - 排序函数(sort)
给字符串 <list> 中的单词排序(升序)。
$(sort <list>)
- 示例:$(sort foo bar lose)返回bar foo lose
- 注意:sort 函数会去掉 <list>中相同的单词
字符串处理函数 - 取单词函数(word)
取字符串 <text> 中第 <n> 个单词。(从一开始)
$(word <n>,<text>)
示例:$(word 2, foo bar baz) 返回值是 bar
字符串处理函数 - 取单词串函数(wordlist)
- 从字符串 <text> 中取从 <s> 开始到 <e> 的单词串。<s> 和 <e> 是一个数字。
$(wordlist <ss>,<e>,<text>)
示例:$(wordlist 2, 3, foo bar baz) 返回值是 bar baz。
字符串处理函数 - 单词个数统计函数(words)
- 统计 <text> 中字符串中的单词个数。
$(words <text>)
- 示例:$(words, foo bar baz)返回值是 3。
字符串处理函数 - 首单词函数(firstword)
- 取字符串 <text> 中的第一个单词。
$(firstword <text>)
- 示例:$(firstword foo bar)返回值是foo
文件名操作函数
取目录函数(dir)
                                从文件名序列 <names> 中取出目录部分。目录部分是指最后一个反斜杠(/)之前的部分。如果没有反斜杠,那么返回 ./。
$(dir <names...>)
$(dir src/foo.c hacks)
#返回值是 src/ ./
取文件函数(notdir)
                                从文件名序列 <names> 中取出非目录部分。非目录部分是指最後一个反斜杠(/)之后的部分。
$(notdir <names...>)
$(notdir src/foo.c hacks)
# 返回值是 foo.c hacks
取后缀函数(suffix)
                                从文件名序列 <names> 中取出各个文件名的后缀
$(suffix <names...>)
$(suffix src/foo.c src-1.0/bar.c hacks)
# 返回值是 .c .c
取前缀函数(basename)
                                从文件名序列 <names> 中取出各个文件名的前缀部分。
$(basename <names...>)
$(basename src/foo.c src-1.0/bar.c hacks)
# 返回值是 src/foo src-1.0/bar hacks
加后缀函数(addsuffix)
                                把后缀 <suffix> 加到 <names> 中的每个单词后面
$(addsuffix <suffix>,<names...>)
$(addsuffix .c,foo bar)
# 返回值是 foo.c bar.c 
加前缀函数(addprefix)
                                把前缀 <prefix> 加到 <names> 中的每个单词前面。
$(addprefix <prefix>,<names...>)
$(addprefix src/,foo bar)
# 返回值是 src/foo src/bar 。
连接函数(join)
                                把 <list2> 中的单词对应地加到 <list1> 的单词后面。
$(join <list1>,<list2>)
$(join aaa bbb , 111 222 333)
# 返回值是 aaa111 bbb222 333 。
其它函数
foreach 函数
$(foreach <var>,<list>,<text>)
# $(name) 中的单词会被挨个取出,并存到变量 n 中,
# $(n).o 每次根据 $(n) 计算出一个值,这些值以空格分隔,最后作为 foreach 函数的返回
names := a b c d
files := $(foreach n,$(names),$(n).o)
run:
	echo $(files)
if 函数
与之前的条件语句——ifeq 类似
$(if <condition>,<then-part>)
# 或者
$(if <condition>,<then-part>,<else-part>)
call 函数
call 函数是唯一一个可以用来创建新的参数化的函数。
$(call <expression>,<parm1>,<parm2>,...,<parmn>)
reverse =  $(2) $(1)
foo = $(call reverse,a,b)
run:
	echo $(foo)
# b a
shell 函数
使用操作系统 Shell 的命令
contents := $(shell cat foo)
files := $(shell echo *.c)
控制 make 的函数
$(error <text ...>)
# and
$(warning <text ...>)
origin 函数
$(origin <variable>)
origin 函数用于告诉这个变量的从何而来
| :- | :- | 
|---|---|
| undefined | 如果 <variable> 未定义,返回 undefined | 
| default | 如果 <variable> 是默认的,如 CC | 
| environment | 如果 <variable> 是环境变量,并且当 Makefile 执行时,-e 参数没有被打开 | 
| file | 如果 <variable> 这个变量被定义在 Makefile 中。 | 
| command line | 如果 <variable> 这个变量是被命令行定义的。 | 
| override | 如果 <variable> 是被 override 指示符重新定义的。 | 
| automatic | 如果 <variable> 是一个命令运行中的自动化变量 | 
另见
- make 中文教程 (seisman.github.io)
- make 手册 (<www.gnu.org>)
- make 官网 <www.gnu.org>
评论
欢迎提交文档错误或者建议。提交成功后自己可见,其他用户待审核通过后才可见。