├── README.md ├── assets ├── images │ ├── dependency_graph.png │ └── pie_ingredients.png └── styles │ └── docs.css ├── docs ├── automatic-variables-and-wildcards.md ├── commands-and-execution.md ├── conditional-part-of-makefiles.md ├── fancy-rules.md ├── functions.md ├── getting-started.md ├── makefile-cookbook.md ├── other-features.md ├── targets.md └── variables-pt-2.md └── index.html /README.md: -------------------------------------------------------------------------------- 1 |
2 |
3 |
学习 Makefiles
4 |
内附可口示例 🌰
5 |
6 | 起步 7 | Cookbook 8 |
9 |
10 | Pie Ingredients 11 |
12 | 13 | **我之所以编写这份指南,是因为我一直以来都不能完全理解 Makefiles 文件的内容。** 它们似乎充斥着各种隐藏规则和晦涩难懂的符号,还有就是简单的问题复杂化。为此,我静坐数周,尽我所能地阅读了所有关于 Makefiles 的资料,把最关键的知识点都浓缩在了该指南里。每个主题都对应了一段简述和一个你可以用来自己运行的自包含示例。 14 | 15 | 如果你大致理解了 Make,那么可以考虑阅读一下 [Makefile Cookbook](/docs/makefile-cookbook) 一节,该节包含了一个中等规模项目的 Makefile 模板,并对它的每部分功能都做了充分的注释。 16 | 17 | 祝你好运!我希望你能够在 Makefiles 的“混沌世界里大杀四方”。 18 | 19 | > 译者说明:该教程翻译自 [Makefile Tutorial](https://makefiletutorial.com/),仅用于学习交流。 20 | 21 | 22 | 23 | 本站已与官方仓库的提交 [a048a08](https://github.com/theicfire/makefiletutorial/commit/a048a0801bf6907873fbe5555ba9bdd43c063127) 保持了同步。 24 | 25 | 26 | -------------------------------------------------------------------------------- /assets/images/dependency_graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gavinliu6/Makefile-Tutorial-zh-CN/9d113702be3751a548b7570d4713d5a7c809d458/assets/images/dependency_graph.png -------------------------------------------------------------------------------- /assets/images/pie_ingredients.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gavinliu6/Makefile-Tutorial-zh-CN/9d113702be3751a548b7570d4713d5a7c809d458/assets/images/pie_ingredients.png -------------------------------------------------------------------------------- /assets/styles/docs.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css?family=Lato"); 2 | 3 | body { 4 | font-family: Lato, sans-serif; 5 | } 6 | 7 | .home { 8 | display: flex; 9 | justify-content: space-between; 10 | } 11 | 12 | .title-wrapper { 13 | display: flex; 14 | flex-direction: column; 15 | justify-content: center; 16 | } 17 | 18 | .home img { 19 | width: 600px; 20 | height: 257px; 21 | } 22 | 23 | @media (max-width: 1200px) { 24 | .home { 25 | justify-content: center; 26 | } 27 | 28 | .home img { 29 | display: none; 30 | } 31 | } 32 | 33 | .title { 34 | font-size: 30px; 35 | } 36 | 37 | .subtitle { 38 | font-size: 20px; 39 | } 40 | 41 | .actions { 42 | margin: 30px auto 10px; 43 | } 44 | 45 | .action-btn { 46 | display: inline-block; 47 | padding: 10px 16px; 48 | color: hsla(0, 0%, 100%, 0.88); 49 | background-color: #009688; 50 | border-radius: 6px; 51 | } 52 | 53 | .action-btn:not(:first-child) { 54 | margin-left: 20px; 55 | } 56 | -------------------------------------------------------------------------------- /docs/automatic-variables-and-wildcards.md: -------------------------------------------------------------------------------- 1 | # 自动变量和通配符 2 | 3 | ## 通配符 `*` 4 | 5 | 在 Make 中,`%` 和 `*` 都叫作通配符,但是它们是两个完全不同的东西。`*` 会搜索你的文件系统来匹配文件名。我建议你应该一直使用 `wildcard` 函数来包裹它,要不然你可能会掉入下述的常见陷阱中。真是搞不明白,不用 `wildcard` 包裹的 `*` 除了能给人徒增迷惑,还有什么可取之处。 6 | 7 | ```makefile 8 | # 打印出每个.c文件的文件信息 9 | print: $(wildcard *.c) 10 | ls -la $? 11 | ``` 12 | 13 | `*` 可以用在 `targets`、`prerequisites` 以及 `wildcard` 函数中。 14 | 15 | `*` 不能直接用在变量定义中。 16 | 17 | 当 `*` 匹配不到文件时,它将保持原样(除非被 `wildcard` 函数包裹)。 18 | 19 | ```makefile 20 | thing_wrong := *.o # 请不要这样做!'*.o' 将不会被替换为实际的文件名 21 | thing_right := $(wildcard *.o) 22 | 23 | all: one two three four 24 | 25 | # 失败,因为$(thing_wrong)是字符串"*.o" 26 | one: $(thing_wrong) 27 | 28 | # 如果没有符合这个匹配规则的文件,它将保持为 *.o :( 29 | two: *.o 30 | 31 | # 按预期运行!在这种情况下,什么都不会执行 32 | three: $(thing_right) 33 | 34 | # 与规则三相同 35 | four: $(wildcard *.o) 36 | ``` 37 | 38 | ## 通配符 `%` 39 | 40 | 通配符 `%` 虽然确实很有用,但是由于它的可用场景多种多样,着实有点让人摸不着头脑。 41 | 42 | - 在“匹配”模式下使用时,它匹配字符串中的一个或多个字符,这种匹配被称为词干(stem)匹配。 43 | - 在“替换”模式下使用时,它会替换匹配到的词干。 44 | - `%` 大多用在规则定义以及一些特定函数中。 45 | 46 | 有关它的用法示例可参阅以下章节: 47 | 48 | - [静态模式规则](fancy-rules#静态模式规则) 49 | - [模式规则](fancy-rules#模式规则) 50 | - [字符串替换](functions#字符串替换) 51 | - [`vpath` 指令](other-features#vpath-指令) 52 | 53 | ## 自动变量 54 | 55 | 虽然存在很多 [自动变量](https://www.gnu.org/software/make/manual/html_node/Automatic-Variables.html),但是经常用到的没几个: 56 | 57 | ```makefile 58 | hey: one two 59 | # 输出"hey",因为这是第一个目标 60 | echo $@ 61 | 62 | # 输出所有比目标新的依赖 63 | echo $? 64 | 65 | # 输出所有依赖 66 | echo $^ 67 | 68 | touch hey 69 | 70 | one: 71 | touch one 72 | 73 | two: 74 | touch two 75 | 76 | clean: 77 | rm -f hey one two 78 | ``` 79 | -------------------------------------------------------------------------------- /docs/commands-and-execution.md: -------------------------------------------------------------------------------- 1 | # 命令与执行 2 | 3 | ## 回显/静默命令 4 | 5 | 在一个命令前添加一个 `@` 符号就会阻止该命令输出内容。 6 | 7 | 你也可以使用 `make -s` 在每个命令前添加 `@`。 8 | 9 | ```makefile 10 | all: 11 | @echo "This make line will not be printed" 12 | echo "But this will" 13 | ``` 14 | 15 | ## 命令的执行 16 | 17 | 每个命令都运行在一个新的 shell 中(或者说运行效果等同于运行在一个新 shell 中)。 18 | 19 | ```makefile 20 | all: 21 | cd .. 22 | # The cd above does not affect this line, because each command is effectively run in a new shell 23 | echo `pwd` 24 | 25 | # This cd command affects the next because they are on the same line 26 | cd ..;echo `pwd` 27 | 28 | # Same as above 29 | cd ..; \ 30 | echo `pwd` 31 | ``` 32 | 33 | ## 默认的 Shell 34 | 35 | 系统默认的 shell 是 `/bin/sh`,你可以通过改变 `SHELL` 变量的值来改变它: 36 | 37 | ```makefile 38 | SHELL=/bin/bash 39 | 40 | cool: 41 | echo "Hello from bash" 42 | ``` 43 | 44 | ## 错误处理:`-k`,`-i` 和 `-` 45 | 46 | `make -k` 会使得即便遇到错误,构建也会继续执行下去。如果你想一次查看 Make 的所有错误,这会很有帮助。 47 | 48 | 在一个命令前添加 `-` 会抑制错误。 49 | 50 | `make -i` 等同于在每个命令前添加 `-`。 51 | 52 | ```makefile 53 | one: 54 | # This error will be printed but ignored, and make will continue to run 55 | -false 56 | touch one 57 | ``` 58 | 59 | ## 中断或杀死 `make` 60 | 61 | 如果你在 `make` 的过程中,使用了 `ctrl+c`,那么刚刚制作的新目标会被删除。 62 | 63 | ## `make` 的递归用法 64 | 65 | 为了递归应用一个 makefile,请使用 `$(MAKE)` 而不是 `make`,因为它会为你传递构建标志,而使用了 `$(MAKE)` 变量的这一行命令不会应用这些标志。 66 | 67 | ```makefile 68 | new_contents = "hello:\n\ttouch inside_file" 69 | all: 70 | mkdir -p subdir 71 | printf $(new_contents) | sed -e 's/^ //' > subdir/makefile 72 | cd subdir && $(MAKE) 73 | 74 | clean: 75 | rm -rf subdir 76 | ``` 77 | 78 | ## 在递归 make 中使用 `export` 79 | 80 | 指令 `export` 携带了一个变量,并且对子 `make` 命令可见。在下面的例子中,变量 `cooly` 被导出以便在子目录中的 makefile 可以使用它。 81 | 82 | `export` 的语法与 sh 相同,但二者并不相关(虽然功能类似)。 83 | 84 | ```makefile 85 | new_contents = "hello:\n\\techo \$$(cooly)" 86 | 87 | all: 88 | mkdir -p subdir 89 | echo $(new_contents) | sed -e 's/^ //' > subdir/makefile 90 | @echo "---MAKEFILE CONTENTS---" 91 | @cd subdir && cat makefile 92 | @echo "---END MAKEFILE CONTENTS---" 93 | cd subdir && $(MAKE) 94 | 95 | # Note that variables and exports. They are set/affected globally. 96 | cooly = "The subdirectory can see me!" 97 | export cooly 98 | # This would nullify the line above: unexport cooly 99 | 100 | clean: 101 | rm -rf subdir 102 | ``` 103 | 104 | 在 shell 中运行的变量也需要导出。 105 | 106 | ```makefile 107 | one=this will only work locally 108 | export two=we can run subcommands with this 109 | 110 | all: 111 | @echo $(one) 112 | @echo $$one 113 | @echo $(two) 114 | @echo $$two 115 | ``` 116 | 117 | `.EXPORT_ALL_VARIABLES` 可以为你导出所有变量。 118 | 119 | ```makefile 120 | .EXPORT_ALL_VARIABLES: 121 | new_contents = "hello:\n\techo \$$(cooly)" 122 | 123 | cooly = "The subdirectory can see me!" 124 | # This would nullify the line above: unexport cooly 125 | 126 | all: 127 | mkdir -p subdir 128 | echo $(new_contents) | sed -e 's/^ //' > subdir/makefile 129 | @echo "---MAKEFILE CONTENTS---" 130 | @cd subdir && cat makefile 131 | @echo "---END MAKEFILE CONTENTS---" 132 | cd subdir && $(MAKE) 133 | 134 | clean: 135 | rm -rf subdir 136 | ``` 137 | 138 | ## 给 `make` 传递参数 139 | 140 | [这里](http://www.gnu.org/software/make/manual/make.html#Options-Summary) 很好地列出了可以用于 `make` 的选项。看下 `--dry-run`,`--touch` 和 `--old-file` 选项吧。 141 | 142 | 你可以同时传递多个目标给 `make`,例如 `make clean run test` 会先后运行 `clean`、`run`、`test`。 143 | -------------------------------------------------------------------------------- /docs/conditional-part-of-makefiles.md: -------------------------------------------------------------------------------- 1 | # Makefiles 的条件判断 2 | 3 | ## if/else 4 | 5 | ```makefile 6 | foo = ok 7 | 8 | all: 9 | ifeq ($(foo), ok) 10 | echo "foo equals ok" 11 | else 12 | echo "nope" 13 | endif 14 | ``` 15 | 16 | ## 检查一个变量是否为空 17 | 18 | ```makefile 19 | nullstring = 20 | foo = $(nullstring) # end of line; there is a space here 21 | 22 | all: 23 | ifeq ($(strip $(foo)),) 24 | echo "foo is empty after being stripped" 25 | endif 26 | ifeq ($(nullstring),) 27 | echo "nullstring doesn't even have spaces" 28 | endif 29 | ``` 30 | 31 | ## 检查一个变量是否定义 32 | 33 | `ifdef` 不会扩展变量引用,它只会查看变量的内容究竟定义没。 34 | 35 | ```makefile 36 | bar = 37 | foo = $(bar) 38 | 39 | all: 40 | ifdef foo 41 | echo "foo is defined" 42 | endif 43 | ifdef bar 44 | echo "but bar is not" 45 | endif 46 | ``` 47 | 48 | ## `$(makeflags)` 49 | 50 | 此示例向你展示了如何使用 `findstring` 和 `MAKEFLAGS` 测试 `make` 标志。使用 `make -i` 来运行此例看下输出如何吧。 51 | 52 | ```makefile 53 | bar = 54 | foo = $(bar) 55 | 56 | all: 57 | # Search for the "-i" flag. MAKEFLAGS is just a list of single characters, one per flag. So look for "i" in this case. 58 | ifneq (,$(findstring i, $(MAKEFLAGS))) 59 | echo "i was passed to MAKEFLAGS" 60 | endif 61 | ``` 62 | -------------------------------------------------------------------------------- /docs/fancy-rules.md: -------------------------------------------------------------------------------- 1 | # 各种规则 2 | 3 | ## 隐式规则 4 | 5 | Make 钟爱 C 编译,它每次表达爱意时,都会做出一些“迷惑行为”。其中最令人迷惑的部分可能就是它的那些魔法般的规则了,Make 称之为“隐式规则”。我个人不认同这个设计方案,也不推荐使用它们。然而即便如此,它们也被经常使用,所以了解一下它们也是很有用处的。下面列出了隐式规则: 6 | 7 | - 编译 C 程序时:使用 `$(CC) -c $(CPPFLAGS) $(CFLAGS)` 形式的命令,`n.o` 会由 `n.c` 自动生成。 8 | - 编译 C++ 程序时:使用 `$(CXX) -c $(CPPFLAGS) $(CXXFLAGS)` 形式的命令,`n.o` 会由 `n.cc` 或 `n.pp` 自动生成。 9 | - 链接单个目标文件时:通过运行 `$(CC) $(LDFLAGS) n.o $(LOADLIBES) $(LDLIBS)` 命令,`n` 会由 `n.o` 自动生成。 10 | 11 | 上述隐式规则使用的变量的含义如下所示: 12 | 13 | - `CC`:编译 C 程序的程序,默认是 `cc` 14 | - `CXX`:编译 C++ 程序的程序,默认是 `G++` 15 | - `CFLAGS`:提供给 C 编译器的额外标志 16 | - `CXXFLAGS`:提供给 C++ 编译器的额外标志 17 | - `CPPFLAGS`:提供给 C 预处理器的额外标志 18 | - `LDFLAGS`:当编译器应该调用链接器时提供给编译器的额外标志 19 | 20 | 现在就让我们来看一下如何在不明确告诉 Make 该如何进行编译的情况下构件一个 C 程序。 21 | 22 | ```makefile 23 | CC = gcc # Flag for implicit rules 24 | CFLAGS = -g # Flag for implicit rules. Turn on debug info 25 | 26 | # Implicit rule #1: blah is built via the C linker implicit rule 27 | # Implicit rule #2: blah.o is built via the C compilation implicit rule, because blah.c exists 28 | blah: blah.o 29 | 30 | blah.c: 31 | echo "int main() { return 0; }" > blah.c 32 | 33 | clean: 34 | rm -f blah* 35 | ``` 36 | 37 | ## 静态模式规则 38 | 39 | 静态模式规则是另一种可以在 Makefile 文件中“少废笔墨”的方式,但是我认为它更加有用且更容易让人理解。其语法如下: 40 | 41 | ```makefile 42 | targets ...: target-pattern: prereq-patterns ... 43 | commands 44 | ``` 45 | 46 | 它的本质是:给定的目标 `target` 由 `target-pattern` 在 `targets` 中匹配得到(利用通配符 `%`)。匹配到的内容被称为 _词干 (stem)_。然后,将词干替换到 `prereq-pattern` 中去,并以此生成目标的 `prerequisites` 部分。 47 | 48 | 静态模式规则的一个典型用例就是把 `.c` 文件编译为 `.o` 文件。下面是 _手动_ 方式: 49 | 50 | ```makefile 51 | objects = foo.o bar.o all.o 52 | all: $(objects) 53 | 54 | # These files compile via implicit rules 55 | foo.o: foo.c 56 | bar.o: bar.c 57 | all.o: all.c 58 | 59 | all.c: 60 | echo "int main() { return 0; }" > all.c 61 | 62 | %.c: 63 | touch $@ 64 | 65 | clean: 66 | rm -f *.c *.o all 67 | ``` 68 | 69 | 下面则是使用了静态模式的一种 _更加有效的方式_: 70 | 71 | ```makefile 72 | objects = foo.o bar.o all.o 73 | all: $(objects) 74 | 75 | # These files compile via implicit rules 76 | # Syntax - targets ...: target-pattern: prereq-patterns ... 77 | # In the case of the first target, foo.o, the target-pattern matches foo.o and sets the "stem" to be "foo". 78 | # It then replaces the '%' in prereq-patterns with that stem 79 | $(objects): %.o: %.c 80 | 81 | all.c: 82 | echo "int main() { return 0; }" > all.c 83 | 84 | %.c: 85 | touch $@ 86 | 87 | clean: 88 | rm -f *.c *.o all 89 | ``` 90 | 91 | ## 静态模式规则与过滤器 92 | 93 | 虽然函数稍后才会介绍到,但是我会预先向你展示你可以用函数来做什么。函数 `filter` 可以用在静态模式规则中来匹配正确的文件。在下面这个例子中,我编造了 `.raw` 和 `.result` 文件扩展名。 94 | 95 | ```makefile 96 | obj_files = foo.result bar.o lose.o 97 | src_files = foo.raw bar.c lose.c 98 | 99 | .PHONY: all 100 | all: $(obj_files) 101 | 102 | $(filter %.o,$(obj_files)): %.o: %.c 103 | echo "target: $@ prereq: $<" 104 | $(filter %.result,$(obj_files)): %.result: %.raw 105 | echo "target: $@ prereq: $<" 106 | 107 | %.c %.raw: 108 | touch $@ 109 | 110 | clean: 111 | rm -f $(src_files) 112 | ``` 113 | 114 | ## 模式规则 115 | 116 | 模式规则虽然常用,但是很令人迷惑。你可以以两种方式来看待它们: 117 | 118 | - 一个定义你自己的隐式规则的方式 119 | - 一个静态模式规则的简化形式 120 | 121 | 先看个例子吧: 122 | 123 | ```makefile 124 | # Define a pattern rule that compiles every .c file into a .o file 125 | %.o : %.c 126 | $(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@ 127 | ``` 128 | 129 | 模式规则在目标中包含了一个 `%`,这个 `%` 匹配任意非空字符串,其他字符匹配它们自己。一个模式规则的 `prerequisite` 中的 `%` 表示目标中 `%` 匹配到的同一个词干。 130 | 131 | 132 | 133 | 自动变量 `$<` 表示第一个 `prerequisite`。 134 | 135 | 136 | 137 | 另一个例子: 138 | 139 | ```makefile 140 | # Define a pattern rule that has no pattern in the prerequisites. 141 | # This just creates empty .c files when needed. 142 | %.c: 143 | touch $@ 144 | ``` 145 | 146 | ## 双冒号规则 147 | 148 | 双冒号规则虽然很少用到,但是它能为同一个目标定义多个规则。如果换为单冒号的话,系统会输出警告,并且只有第 2 个规则定义的命令会运行。 149 | 150 | ```makefile 151 | all: blah 152 | 153 | blah:: 154 | echo "hello" 155 | 156 | blah:: 157 | echo "hello again" 158 | ``` 159 | -------------------------------------------------------------------------------- /docs/functions.md: -------------------------------------------------------------------------------- 1 | # 函数 2 | 3 | ## 第 1 个函数 4 | 5 | _函数_ 主要用于文本处理。函数调用的语法是 `$(fn, arguments)` 或 `${fn, arguments}`。你可以使用内置的函数 [`call`](https://www.gnu.org/software/make/manual/html_node/Call-Function.html#Call-Function) 来制作自己的函数。Make 拥有数量众多的 [内置函数](https://www.gnu.org/software/make/manual/html_node/Functions.html)。 6 | 7 | ```makefile 8 | bar := ${subst not, totally, "I am not superman"} 9 | all: 10 | @echo $(bar) 11 | ``` 12 | 13 | 如果你想替换空格或逗号,请使用变量: 14 | 15 | ```makefile 16 | comma := , 17 | empty:= 18 | space := $(empty) $(empty) 19 | foo := a b c 20 | bar := $(subst $(space),$(comma),$(foo)) 21 | 22 | all: 23 | @echo $(bar) 24 | ``` 25 | 26 | **不要** 在第一个参数之后的参数中包含空格,这将被视为字符串的一部分。 27 | 28 | ```makefile 29 | comma := , 30 | empty:= 31 | space := $(empty) $(empty) 32 | foo := a b c 33 | bar := $(subst $(space), $(comma) , $(foo)) 34 | 35 | all: 36 | # Output is ", a , b , c". Notice the spaces introduced 37 | @echo $(bar) 38 | ``` 39 | 40 | ## 字符串替换 41 | 42 | `$(patsubst pattern,replacement,text)` 做了下面这些事: 43 | 44 | > “在文本中查找匹配的以空格分隔的单词,用 `replacement` 替换它们。这里的 `pattern` 可以包含一个 `%` 作为通配符以匹配单词中任意数量的任意字符。如果 `replacement` 中也包含了一个 `%`,那它表示的内容将被 `pattern` 中的 `%` 匹配的内容替换。只有 `pattern` 和 `replacement` 中的第一个 `%` 才会采取这种行为,随后的任何 `%` 都将保持不变。(摘自 [GNU 文档](https://www.gnu.org/software/make/manual/html_node/Text-Functions.html#Text-Functions))” 45 | 46 | `$(text:pattern=replacement)` 是一个简化写法。 47 | 48 | 还有一个仅替换后缀的简写形式:`$(text:suffix=replacement)`,这里没有使用通配符 `%`。 49 | 50 | 在简写形式中,不要添加额外的空格,它会被当作一个搜索或替换项。 51 | 52 | ```makefile 53 | foo := a.o b.o l.a c.o 54 | one := $(patsubst %.o,%.c,$(foo)) 55 | # This is a shorthand for the above 56 | two := $(foo:%.o=%.c) 57 | # This is the suffix-only shorthand, and is also equivalent to the above. 58 | three := $(foo:.o=.c) 59 | 60 | all: 61 | echo $(one) 62 | echo $(two) 63 | echo $(three) 64 | ``` 65 | 66 | ## 函数 `foreach` 67 | 68 | 函数 `foreach` 看起来像这样:`$(foreach var,list,text)`,它用于将一个单词列表(空格分隔)转换为另一个。`var` 表示循环中的每一个单词,`text` 用于扩展每个单词。 69 | 70 | 在每个单词后追加一个感叹号: 71 | 72 | 73 | ```makefile 74 | foo := who are you 75 | # For each "word" in foo, output that same word with an exclamation after 76 | bar := $(foreach wrd,$(foo),$(wrd)!) 77 | 78 | all: 79 | # Output is "who! are! you!" 80 | @echo $(bar) 81 | ``` 82 | 83 | ## 函数 `if` 84 | 85 | `if` 函数用来检查它的第 1 个参数是否非空。如果非空,则运行第 2 个参数,否则运行第 3 个。 86 | 87 | ```makefile 88 | foo := $(if this-is-not-empty,then!,else!) 89 | empty := 90 | bar := $(if $(empty),then!,else!) 91 | 92 | all: 93 | @echo $(foo) 94 | @echo $(bar) 95 | ``` 96 | 97 | ## 函数 `call` 98 | 99 | Make 支持创建基本的函数。你只需通过创建变量来“定义”函数,只是会用到参数 `$(0)`、`$(1)` 等。然后,你就可以使用专门的函数 `call` 来调用它了,语法是 `$(call variable,param,param)`。`$(0)` 是变量名,而 `$(1)`、`$(1)` 等则是参数。 100 | 101 | ```makefile 102 | sweet_new_fn = Variable Name: $(0) First: $(1) Second: $(2) Empty Variable: $(3) 103 | 104 | all: 105 | # Outputs "Variable Name: sweet_new_fn First: go Second: tigers Empty Variable:" 106 | @echo $(call sweet_new_fn, go, tigers) 107 | ``` 108 | 109 | ## 函数 `shell` 110 | 111 | `shell` - 调用 shell,但它在输出中会用空格替代换行。 112 | 113 | ```makefile 114 | all: 115 | @echo $(shell ls -la) # Very ugly because the newlines are gone! 116 | ``` 117 | -------------------------------------------------------------------------------- /docs/getting-started.md: -------------------------------------------------------------------------------- 1 | # 起步 2 | 3 | ## Makefiles 简介 4 | 5 | Makefiles 用于帮助决定一个大型程序的哪些部分需要重新编译。在绝大多数情况下,需要编译的只是 C 或 C++ 文件。其他语言通常有它们自己的一套与 Make 用途类似的工具。Make 的用途并不局限于编程,当你需要根据哪些文件发生了变更来运行一系列指令时也可以使用它。而本教程将只关注 C/C++ 编译用例。 6 | 7 | 这里有一个你可能会使用 Make 进行构建的依赖关系示例图。如有任何文件的依赖项发生了改变,那么该文件就会被重新编译。 8 | 9 | 10 | 11 | ## Make 的替代品 12 | 13 | 除了 Make,还有一些比较流行的构建系统可选,像 [SCon](https://scons.org/)、[CMake](https://cmake.org/)、[Bazel](https://bazel.build/) 和 [Ninja](https://ninja-build.org/) 等。一些代码编辑器,像 [Microsoft Visual Studio](https://visualstudio.microsoft.com/),内置了它们自己的构建工具。Java 语言的构建工具有 [Ant](https://ant.apache.org/)、[Maven](https://maven.apache.org/what-is-maven.html) 和 [Gradle](https://gradle.org/) 可选,其他语言像 Go 和 Rust 则都有它们自己的构建工具。 14 | 15 | 像 Python、Ruby 和 JavaScript 这样的解释型语言是不需要类似 Makefiles 的东西的。Makefiles 的目标是基于哪些文件发生了变化来编译需要被编译的一切文件。但是,当解释型语言的文件发生了变化,是不需要重新编译的,程序运行时会使用最新版的源码文件。 16 | 17 | ## Make 的不同实现与版本 18 | 19 | 虽然 Make 有多种实现,但本指南的大部分内容都是版本间无差异的。然而,需要说明的是,本指南是专门为 GNU Make 编写的,这也是 Linux 与 macOS 平台上的标准实现。文中所有的例子在 Make 的版本 3 和版本 4 上都能很好地工作,除了一些细微的差别外,它们的行为表现也几乎一致。 20 | 21 | ## 如何运行示例 22 | 23 | 为了运行文中示例,你需要一个安装了 `make` 的终端。对于每一个例子,只需要把它的内容放在一个名为 `Makefile` 的文件中,再把该文件放在运行 `make` 命令的目录下就行了。让我们从最简单的一个 Makefile 开始吧: 24 | 25 | ```makefile 26 | hello: 27 | echo "hello world" 28 | ``` 29 | 30 | 以下是运行上述示例的输出: 31 | 32 | ```shell 33 | $ make 34 | echo "hello world" 35 | hello world 36 | ``` 37 | 38 | 是的,就是这样!如果你还心存困惑,这儿有一份介绍了完整步骤的视频,同时它还描述了 Makefiles 的基本结构。 39 | 40 | 41 | 42 | ## Makefile 语法 43 | 44 | Makefile 文件由一系列的 _规则 (rules)_ 组成,一个规则类似下面这样: 45 | 46 | ```makefile 47 | targets: prerequisites 48 | command 49 | command 50 | command 51 | ``` 52 | 53 | - `targets` 指的是文件名称,多个文件名以空格分隔。通常,一个规则只对应一个文件。 54 | - `commands` 通常是一系列用于制作(make)一个或多个目标(targets)的步骤。它们 _需要以一个制表符开头_,而不是空格。 55 | - `prerequisites` 也是文件名称,多个文件名以空格分隔。在运行目标(targets)的 `commands` 之前,要确保这些文件是存在的。它们也被称为 _依赖_。 56 | 57 | ## 新手示例 58 | 59 | 下面的 Makefile 有 3 个分离的 _规则 (rules)_。当你在终端运行 `make blah` 时,它会通过一系列的步骤构建一个名为 `blah` 的程序: 60 | 61 | - `blah` 给 `make` 提供了构建目标(target)的名称,所以它会在 makefile 中优先被 `make` 程序搜索 62 | - 构建系统发现 `blah` 依赖 `blah.o`,所以 `make` 开始搜索 `blah.o` 这个目标 63 | - `blah.o` 又依赖 `blah.c`,所以 `make` 又开始搜索 `blah.c` 这个目标 64 | - `blah.c` 没有依赖,直接运行 `echo` 命令 65 | - 接着运行 `cc -c` 命令,因为 `blah.o` 的依赖的所有 `commands` 都执行完了 66 | - 同理,接着运行顶部的 `cc` 命令 67 | - 就这样,一个编译好的 C 程序 `blah` 就诞生了 68 | 69 | ```makefile 70 | blah: blah.o 71 | cc blah.o -o blah # Runs third 72 | 73 | blah.o: blah.c 74 | cc -c blah.c -o blah.o # Runs second 75 | 76 | blah.c: 77 | echo "int main() { return 0; }" > blah.c # Runs first 78 | ``` 79 | 80 | 81 | 82 | `-c` 选项只编译不链接,`-o file` 将其前面命令的输出内容放在文件 _file_ 中。 83 | 84 | 详情可参阅:https://gcc.gnu.org/onlinedocs/gcc/Overall-Options.html#Overall-Options 85 | 86 | 87 | 88 | 下面的这个 makefile 只有一个目标,叫作 `some_file`。因为默认的目标就是第一个目标,所以,在执行 `make` 命令时,`some_file` 目标下的命令会运行。👇 89 | 90 | ```makefile 91 | some_file: 92 | echo "This line will always print" 93 | ``` 94 | 95 | 若应用下面这个 makefile,在第一次构建时将创建文件 *some_file*,第二次系统就会注意到该目标文件已经创建过了,结果就会得到这样的提示信息:*make: 'some_file' is up to date.*。👇 96 | 97 | ```makefile 98 | some_file: 99 | echo "This line will only print once" 100 | touch some_file 101 | ``` 102 | 103 | 敲黑板!目标 `some_file` 依赖 `other_file`。当我们运行 `make` 时,默认目标(即 `some_file`,因为它是第一个)会被“召唤”。构建系统首先查看目标的 _依赖_ 列表,若其中有旧的目标文件,构建系统首先会为这些依赖执行目标构建,此后才轮到默认目标。第二次执行 `make` 时,默认目标和依赖目标下的命令都不会再运行了,因为二者都存在了。👇 104 | 105 | ```makefile 106 | some_file: other_file 107 | echo "This will run second, because it depends on other_file" 108 | touch some_file 109 | 110 | other_file: 111 | echo "This will run first" 112 | touch other_file 113 | ``` 114 | 115 | 而下面这个 makefile 中的两个目标每次构建都会运行,因为 `some_file` 依赖的 `other_file` 从未被创建过。👇 116 | 117 | ```makefile 118 | some_file: other_file 119 | touch some_file 120 | 121 | other_file: 122 | echo "nothing" 123 | ``` 124 | 125 | 126 | 127 | 👆 类似上面 `other_file` 这样的目标就是俗称的 _伪目标_ 或 _虚拟目标_。 128 | 129 | 130 | 131 | `clean` 经常被用来作为移除其他目标的输出的目标名称,但是在 `make` 看来它并非是一个特殊用词。 132 | 133 | ```makefile 134 | some_file: 135 | touch some_file 136 | 137 | clean: 138 | rm -f some_file 139 | ``` 140 | 141 | ## 变量 142 | 143 | 在 Makefile 中,变量的值类型只能是字符串。下面是一个使用变量的例子: 144 | 145 | ```makefile 146 | files = file1 file2 147 | some_file: $(files) 148 | echo "Look at this variable: " $(files) 149 | touch some_file 150 | 151 | file1: 152 | touch file1 153 | file2: 154 | touch file2 155 | 156 | clean: 157 | rm -f file1 file2 some_file 158 | ``` 159 | 160 | 引用变量的语法是:`${}` 或 `$()`。 161 | 162 | ```makefile 163 | x = dude 164 | 165 | all: 166 | echo $(x) 167 | echo ${x} 168 | 169 | # Bad practice, but works 170 | echo $x 171 | ``` 172 | -------------------------------------------------------------------------------- /docs/makefile-cookbook.md: -------------------------------------------------------------------------------- 1 | # Makefile Cookbook 2 | 3 | 让我们来看一个真正生动有趣的例子吧,对于中型规模的项目,它能很好地工作。 4 | 5 | 这个 makefile 的巧妙之处在于它能自动为你确定依赖,你所要做的就是把你的 C/C++ 文件放到 `src/` 文件夹中。 6 | 7 | ```makefile 8 | # Thanks to Job Vranish (https://spin.atomicobject.com/2016/08/26/makefile-c-projects/) 9 | TARGET_EXEC := final_program 10 | 11 | BUILD_DIR := ./build 12 | SRC_DIRS := ./src 13 | 14 | # Find all the C and C++ files we want to compile 15 | # Note the single quotes around the * expressions. Make will incorrectly expand these otherwise. 16 | SRCS := $(shell find $(SRC_DIRS) -name '*.cpp' -or -name '*.c' -or -name '*.s') 17 | 18 | # String substitution for every C/C++ file. 19 | # As an example, hello.cpp turns into ./build/hello.cpp.o 20 | OBJS := $(SRCS:%=$(BUILD_DIR)/%.o) 21 | 22 | # String substitution (suffix version without %). 23 | # As an example, ./build/hello.cpp.o turns into ./build/hello.cpp.d 24 | DEPS := $(OBJS:.o=.d) 25 | 26 | # Every folder in ./src will need to be passed to GCC so that it can find header files 27 | INC_DIRS := $(shell find $(SRC_DIRS) -type d) 28 | # Add a prefix to INC_DIRS. So moduleA would become -ImoduleA. GCC understands this -I flag 29 | INC_FLAGS := $(addprefix -I,$(INC_DIRS)) 30 | 31 | # The -MMD and -MP flags together generate Makefiles for us! 32 | # These files will have .d instead of .o as the output. 33 | CPPFLAGS := $(INC_FLAGS) -MMD -MP 34 | 35 | # The final build step. 36 | $(BUILD_DIR)/$(TARGET_EXEC): $(OBJS) 37 | $(CC) $(OBJS) -o $@ $(LDFLAGS) 38 | 39 | # Build step for C source 40 | $(BUILD_DIR)/%.c.o: %.c 41 | mkdir -p $(dir $@) 42 | $(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@ 43 | 44 | # Build step for C++ source 45 | $(BUILD_DIR)/%.cpp.o: %.cpp 46 | mkdir -p $(dir $@) 47 | $(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $< -o $@ 48 | 49 | 50 | .PHONY: clean 51 | clean: 52 | rm -r $(BUILD_DIR) 53 | 54 | # Include the .d makefiles. The - at the front suppresses the errors of missing 55 | # Makefiles. Initially, all the .d files will be missing, and we don't want those 56 | # errors to show up. 57 | -include $(DEPS) 58 | ``` 59 | -------------------------------------------------------------------------------- /docs/other-features.md: -------------------------------------------------------------------------------- 1 | # 其他特性 2 | 3 | ## 包含 Makefiles 文件 4 | 5 | `include` 指令告诉 `make` 去读取其他 makefiles 文件,它是 makefile 中的一行,如下所示: 6 | 7 | ```makefile 8 | include filenames... 9 | ``` 10 | 11 | 当你使用像 `-M` 这样的编译器标志时,`include` 特别有用,它可以根据源代码创建 Makefile。例如,如果一些 c 文件包括一个头,这个头将被添加到由 gcc 编写的 Makefile 中。关于这一点,我在 [Makefile Cookbook](makefile-cookbook) 一节中有更详细的讨论。 12 | 13 | ## `vpath` 指令 14 | 15 | `vpath` 指令用来指定某些 `prerequisites` 的位置,使用格式是 `vpath `。 16 | 17 | `` 中可以使用 `%`,用来匹配 0 个或多个字符。 18 | 19 | 你也可以使用变量 `VPATH` 全局执行此操作。 20 | 21 | ```makefile 22 | vpath %.h ../headers ../other-directory 23 | 24 | some_binary: ../headers blah.h 25 | touch some_binary 26 | 27 | ../headers: 28 | mkdir ../headers 29 | 30 | blah.h: 31 | touch ../headers/blah.h 32 | 33 | clean: 34 | rm -rf ../headers 35 | rm -f some_binary 36 | ``` 37 | 38 | ## 多行处理 39 | 40 | 当命令过长时,反斜杠(`\`)可以让我们使用多行编写形式。 41 | 42 | ```makefile 43 | some_file: 44 | echo This line is too long, so \ 45 | it is broken up into multiple lines 46 | ``` 47 | 48 | ## `.PHONY` 49 | 50 | 向一个目标中添加 `.PHONY` 会避免把一个虚拟目标识别为一个文件名。在下面这个例子中,即便文件 `clean` 被创建了,`make clean` 仍会运行。`.PHONY` 非常好用,但是为了简洁起见,在其余示例中我会跳过它。 51 | 52 | ```makefile 53 | some_file: 54 | touch some_file 55 | touch clean 56 | 57 | .PHONY: clean 58 | clean: 59 | rm -f some_file 60 | rm -f clean 61 | ``` 62 | 63 | ## `.DELETE_ON_ERROR` 64 | 65 | 如果一个命令返回了一个非 0 的退出码,那么 `make` 会停止运行相应的规则(并会传播到它的依赖中)。 66 | 67 | 如果一个规则因为上述这种情况构建失败了,那么应用了 `.DELETE_ON_ERROR` 后,这个规则的目标文件就会被删除。不像 `.PHONY`,`.DELETE_ON_ERROR` 对所有的目标都有效。始终使用 `.DELETE_ON_ERROR` 是个不错的选择,即使由于历史原因,`make` 不支持它。 68 | 69 | ```makefile 70 | .DELETE_ON_ERROR: 71 | all: one two 72 | 73 | one: 74 | touch one 75 | false 76 | 77 | two: 78 | touch two 79 | false 80 | ``` 81 | -------------------------------------------------------------------------------- /docs/targets.md: -------------------------------------------------------------------------------- 1 | # 目标 2 | 3 | ## 目标 `all` 4 | 5 | 想制作多个目标,并且一次运行全部?那就使用 `all` 目标吧。 6 | 7 | ```makefile 8 | all: one two three 9 | 10 | one: 11 | touch one 12 | two: 13 | touch two 14 | three: 15 | touch three 16 | 17 | clean: 18 | rm -f one two three 19 | ``` 20 | 21 | 22 | 23 | 关于目标 `all` 的更多信息可参阅 [Stack Overflow - What does “all” stand for in a makefile?](https://stackoverflow.com/questions/2514903/what-does-all-stand-for-in-a-makefile) 24 | 25 | 26 | 27 | ## 多目标 28 | 29 | 当一个规则(rule)有多个目标时,那么对于每个目标,这个规则下面的 commands 都会运行一次。 30 | 31 | `$@` 是一个指代目标名称的 [自动变量](automatic-variables-and-wildcards#自动变量)。 32 | 33 | ```makefile 34 | all: f1.o f2.o 35 | 36 | f1.o f2.o: 37 | echo $@ 38 | # Equivalent to: 39 | # f1.o 40 | # echo $@ 41 | # f2.o 42 | # echo $@ 43 | ``` 44 | -------------------------------------------------------------------------------- /docs/variables-pt-2.md: -------------------------------------------------------------------------------- 1 | # 变量(第 2 部分) 2 | 3 | ## 变量类型和修改 4 | 5 | 变量的类型有两种: 6 | 7 | - 递归变量(使用 `=`)- 只有在命令执行时才查找变量,而不是在定义时 8 | - 简单的扩展变量(使用 `:=`)- 就像普通的命令式编程一样——只有当前已经定义的变量才会得到扩展 9 | 10 | ```makefile 11 | # Recursive variable. This will print "later" below 12 | one = one ${later_variable} 13 | # Simply expanded variable. This will not print "later" below 14 | two := two ${later_variable} 15 | 16 | later_variable = later 17 | 18 | all: 19 | echo $(one) 20 | echo $(two) 21 | ``` 22 | 23 | 扩展变量(使用 `:=`)使得你可以在一个变量的基础上追加内容,递归变量则会陷入死循环。 24 | 25 | ```makefile 26 | one = hello 27 | # one gets defined as a simply expanded variable (:=) and thus can handle appending 28 | one := ${one} there 29 | 30 | all: 31 | echo $(one) 32 | ``` 33 | 34 | `?=` 用于当变量还没被设置值时给它设置值,反之则忽略。 35 | 36 | ```makefile 37 | one = hello 38 | one ?= will not be set 39 | two ?= will be set 40 | 41 | all: 42 | echo $(one) 43 | echo $(two) 44 | ``` 45 | 46 | 变量值尾部的空格不会被删除,但开头的空格会被删除。想要一个值为单个空格的变量请使用 `$(nullstring)`。 47 | 48 | ```makefile 49 | with_spaces = hello # with_spaces has many spaces after "hello" 50 | after = $(with_spaces)there 51 | 52 | nullstring = 53 | space = $(nullstring) # Make a variable with a single space. 54 | 55 | all: 56 | echo "$(after)" 57 | echo start"$(space)"end 58 | ``` 59 | 60 | 一个未定义的变量实际上是一个空字符串。 61 | 62 | ```makefile 63 | all: 64 | # Undefined variables are just empty strings! 65 | echo $(nowhere) 66 | ``` 67 | 68 | `+=` 用来追加变量的值: 69 | 70 | ```makefile 71 | foo := start 72 | foo += more 73 | 74 | all: 75 | echo $(foo) 76 | ``` 77 | 78 | [字符串替换](functions#字符串替换) 也是一个常见且有用的修改变量的方式。其他相关可参阅 [文本函数](https://www.gnu.org/software/make/manual/html_node/Text-Functions.html#Text-Functions) 与 [文件名函数](https://www.gnu.org/software/make/manual/html_node/File-Name-Functions.html#File-Name-Functions)。 79 | 80 | ## 命令行参数与覆盖 81 | 82 | 你可以通过 `override` 来覆盖来自命令行的变量。假使我们使用下面的 makefile 执行了这样一条命令 `make option_one=hi`,那么变量 `option_one` 的值就会被覆盖掉。 83 | 84 | ```makefile 85 | # Overrides command line arguments 86 | override option_one = did_override 87 | # Does not override command line arguments 88 | option_two = not_override 89 | all: 90 | echo $(option_one) 91 | echo $(option_two) 92 | ``` 93 | 94 | ## 命令列表与 `define` 95 | 96 | `define` 实际上就是一个命令列表,它与函数 `define` 没有任何关系。这里请注意,它与用分号分隔多个命令的场景有点不同,因为前者如预期的那样,每条命令都是在一个单独的 shell 中运行的。 97 | 98 | ```makefile 99 | one = export blah="I was set!"; echo $$blah 100 | 101 | define two 102 | export blah=set 103 | echo $$blah 104 | endef 105 | 106 | # One and two are different. 107 | 108 | all: 109 | @echo "This prints 'I was set'" 110 | @$(one) 111 | @echo "This does not print 'I was set' because each command runs in a separate shell" 112 | @$(two) 113 | ``` 114 | 115 | ## 特定目标的变量 116 | 117 | 我们可以为特定目标分配变量。 118 | 119 | ```makefile 120 | all: one = cool 121 | 122 | all: 123 | echo one is defined: $(one) 124 | 125 | other: 126 | echo one is nothing: $(one) 127 | ``` 128 | 129 | ## 特定模式的变量 130 | 131 | 我们可以为特定的目标 _模式_ 分配变量。 132 | 133 | ```makefile 134 | %.c: one = cool 135 | 136 | blah.c: 137 | echo one is defined: $(one) 138 | 139 | other: 140 | echo one is nothing: $(one) 141 | ``` 142 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Makefile 教程 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | 24 | 95 | 96 | 97 | 98 | --------------------------------------------------------------------------------