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