Created
March 1, 2018 16:45
-
-
Save kxxoling/e8f2a34dd7c406620ca52072e1622636 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 你好,欢迎学习 Makefile 基础! | |
# | |
# 这里我将向大家介绍 `make` 的优秀之处,虽然语法上有点“奇怪”,但却是一个高效、 | |
# 快速、强大的程序构建解决方案。 | |
# | |
# 学完了这里的基础之后,建议你前往 GNU 官网 | |
# http://www.gnu.org/software/make/manual/make.html | |
# 更加深入 `make` 的用法。 | |
# `make` 命令必须在一个存在 `Makefile` 文件的目录使用,当然,你也可以使用 | |
# `make -f <makefile>` 来指定 `Makefile`。 | |
# | |
# Makefile 种存储了一系列的规则,每一条规则都对应一条任务,类似于 grunt 中的 | |
# task 或者 npm package.json 脚本。 | |
# | |
# make 规则通常是这个样子的: | |
# | |
# <target>: <prerequisites...> | |
# <commands> | |
# | |
# `target` 是必须的,`prerequisites` 和 `command` 是可选,但是这两者必须存在一个。 | |
# | |
# 输入 `make` 命令看看会出现什么: | |
tutorial: | |
@# todo: have this actually run some kind of tutorial wizard? | |
@echo "Please read the 'Makefile' file to go through this tutorial" | |
# 如果你不指定任务,默认会执行第一条任务,所以在本例中,`make` 和 `make tutorial` 是等价的。 | |
# | |
# 默认情况下,make 会在运行一条任务前将其输出在控制台,让你清楚现在正在执行的 | |
# 究竟是什么任务,这并不符合 UNIX “success should be silent” 理念,但如果不这样的话, | |
# 你将很难知道构建日志中究竟有什么。 | |
# | |
# 我们在每一行行首的位置加上 @ 字符防止其输出。 | |
# | |
# 命令列表中的每一行都是当作独立的 shell 命令执行,因此你在上一行定义的变量将 | |
# 无法在下一行中取到。不相信的话,你可以执行 `make var-lost` 看看结果。 | |
var-lost: | |
export foo=bar | |
echo "foo=[$$foo]" | |
# 注意:我们必须在命令行中使用 double-$ 。这是因为,每一行命令都是先作为 makefile 命令被读取, | |
# 然后才将其传向 shell。 | |
# 想要在同样的环境下执行 shell 命令,我们可以使用 \n 换行符“连接”两行语句。 | |
# 运行 `make var-kept` 看看跟上面的命令有什么不同。 | |
var-kept: | |
export foo=bar; \ | |
echo "foo=[$$foo]" | |
# 接下来,我们尝试根据一个文件来生成另一个文件。 | |
# 比如,我们可以根据 "source.txt" 来创建一个 "result.txt"。 | |
result.txt: source.txt | |
@echo "building result.txt from source.txt" | |
cp source.txt result.txt | |
# 运行 `make result.txt`,出错了! | |
# $ make result.txt | |
# make: *** No rule to make target `source.txt', needed by `result.txt'. Stop. | |
# | |
# 错误在于,我们告诉 make 根据 source.txt 来创建 result.txt,但是并没有告诉它如何 | |
# 找到这个 source.txt,而 source.txt 现在并不存在于我们的目录树中。 | |
# | |
# 将下面这组任务取消注释就能解决这个问题。 | |
# | |
#source.txt: | |
# @echo "building source.txt" | |
# echo "this is the source" > source.txt | |
# | |
# 运行 `make result.txt` 你将发现它会先创建一个 source.txt 文件,再将其复制到 result.txt 中。 | |
# 现在我们再一次运行 `make result.txt`,什么都不会发生! | |
# 这是因为 source.txt 并没有发生变化,因此没有必要重新构建 result.txt。 | |
# | |
# 运行 `touch source.txt` 或者使用编辑器对它进行修改,这时在运行 `make result.txt` | |
# 你会发现 result.txt 将被重新构建。 | |
# | |
# | |
# 假设我们现在的工作目录中有 100 个 .c 文件,而它们都需要被转换为 .o 文件, | |
# 最后将 .o 文件链接到二进制文件中。(或者你需要将 100 个 .styl 文件转换为 .css 文件 | |
# 最后合并到一个 main.min.css 文件当中。) | |
# | |
# 如果手动为每一个文件创建一个任务,这将会无聊又麻烦。幸运的是,make 支持模式匹配, | |
# 可以批量处理符合模式的所有文件。 | |
# | |
# 我们可以使用特殊的规则来匹配输入、输出文件的规则来设置任务,特殊变量如下: | |
# | |
# $@ 当前正在被 make 的文件(即”目标“) | |
# 记住这点,它和 shell 中 ”$@” 列一样。 | |
# @ 符号和字母 a 很相似,它代表“argument/参数”的意思。 | |
# 当你输入 make foo 的时候,“foo” 就是这个参数。 | |
# | |
# $< 输入文件(即列表中的第一先决条件) | |
# 你可以把 < 当作 bash 中的管道输入符来记忆,`head <foo.txt` 使用 foo.txt 中的文件作为输入。 | |
# < 也通用会将输入导向 $。 | |
# | |
# $^ 它代表所有输入文件,而不仅仅是其中的第一个。 | |
# 它和 $< 很像,不过是个上箭头。如果文件在输入列表中出现多次,它也仅仅会在 $^ 中出现一次。 | |
# | |
# $? 所有比目标新的输入文件。 | |
# | |
# $$ 文本中的普通 $ 符号,用于转义。 | |
# | |
# $* 规则匹配的主干部分 | |
# 在 make 规则中,% 类似于 shell 中的 *,想反 $* 则代表模式所代表的结果。 | |
# | |
# 你还可以使用 $(@D) 以及 $(@F) 这样的特殊符号来分别代表当前目录及文件部分。 | |
# $(<D) 及 $(<F) 和 $< 变量的使用方法一样。你可以在任何类似文件名的变量上使用 | |
# 这种 D/F 技巧。 | |
# | |
# 此外还有一些其它可用变量,不过大多书情况你并不需要依赖它们。 | |
# 当然我们也可以自己定义变量。 | |
# | |
# 因此 result.txt 的规则可以像下面这样改写: | |
result-using-var.txt: source.txt | |
@echo "buildling result-using-var.txt using the $$< and $$@ vars" | |
cp $< $@ | |
# 假设现在有 100 多个原文件需要批量转换为目标文件,我们可以使用脚本 | |
# 来转换文件,并使用一个变量存储下来,而不需要为每一个文件专门写一个任务。 | |
# | |
# 注意赋值应该使用 := 而非 = ,这一点和 bash/sh 并不一样,我也不清楚具体原因, | |
# 不过你最好尽快习惯这一点. | |
# | |
# 通常你会使用 `$(wildcard src/*.txt)` 这样的通配符。 | |
# 另外,你还需要注意目标文件是否已经存在于项目当中了,在这个示例我们使用 | |
# make 来构建目标文件。 | |
# | |
# 这行命令将会调用 shell 命令来声称特定文件: | |
srcfiles := $(shell echo src/{00..99}.txt) | |
# 如何在 src 目录创建一个文本文件? | |
# 我们使用 % 作为文件名主体的占位符,它代表 src/*.txt 匹配的所有文件, | |
# 并且会将 % 匹配到的部分传入 $* 变量。 | |
src/%.txt: | |
@# 如果目录不存在则首先创建目录 | |
@# 用 @ 隐藏掉,毕竟谁会关心 src 目录的创建啊 | |
@[ -d src ] || mkdir src | |
@# 然后将数据 echo 到文件中 | |
@# $* 会扩展为 % 匹配到的文件名主体 | |
@# 这样我们就得到了一系列文件名为数字的 .txt 格式文件,并保存了对应数字作为文件内容。 | |
echo $* > $@ | |
# 现在运行 `make src/00.txt` 和 `make src/01.txt` 试试。 | |
# 对于不需要进行构建的文件,我们可以各级所有的 srcfiles 使用 "phony" 标记, | |
# 通常建议在文件中声明 .PHONY 来定义 phony 规则。(见本文件底部) | |
# | |
# 现在运行 `make source` 即可构建 src/ 目录下的所有文件,但在这之前 `make source` | |
# 首先会生成 src/ 目录本身,然后将会将 "stem" 值(文件名中由 % 所匹配的数字部分)复制给文件。 | |
# | |
# 运行 `make source` 来亲自试一试: | |
source: $(srcfiles) | |
# 接下来我们将源文件复制为目标文件,同样我们首先要创建目标目录。 | |
# | |
# dest/*.txt 匹配的文件将对应 src/*.txt 文件,举个具体的例子:比如 | |
# %.css 文件依赖于 %.styl 文件。 | |
dest/%.txt: src/%.txt | |
@[ -d dest ] || mkdir dest | |
cp $< $@ | |
# 很不错!但是我仍然不希望输入 `make dest/#.txt` 100 次。 | |
# | |
# 我们可以根据目标文件来创建 "phony" 目标。 | |
# 我们可以使用内置的模式替换 "patsubst" 来避免重新构建列表,patsubst 使用 | |
# 和上面同样的 "stem" 规则。 | |
destfiles := $(patsubst src/%.txt,dest/%.txt,$(srcfiles)) | |
destination: $(destfiles) | |
# “destination” 并非真正的文件名,因此我们同样需要将他定义在 .PHONY 中。(如下) | |
# 这样 make 就不需要检测名为 ”destination“ 的文件是否存在了。 | |
# | |
# 接下来所有目标文件都合并到一个文件中,在本例中我们使用古老的 UNIX 命令 cat。 | |
kitty: $(destfiles) | |
@# 记住, $< 是输入文件, $^ 是所有的输出文件 | |
@# 使用 cat 命令将它们合并到 kitty 中 | |
cat $^ > kitty | |
# 注意,这里的过程: | |
# | |
# kitty -> (all of the dest files) | |
# 于是每一个目标文件都都对应上了一个源文件 | |
# | |
# 如果你再次运行 `make kitty` ,将会得到 "kitty is up to date" 的输出。 | |
# | |
# 接下来是见证奇迹的时刻! | |
# | |
# 现在我们对其中一个源文件稍作修改看看会发生什么 | |
# | |
# 运行:`touch src/25.txt; make kitty` | |
# | |
# 注意:make 非常智能,只重新构建了修改过的 25.txt 这一个文件,然后将它们 | |
# 合并到 kitty 中来,而不是每次都对所有的源文件进行构建操作。 | |
# 记得编写 test 任务是一个好习惯,因为其他人在使用你的项目时可能会使用 | |
# `make test` 来做一些事情。 | |
# | |
# 如果 kitty 不存在则无法测试,也就是说 test 依赖于 kitty: | |
test: kitty | |
@echo "miao" && echo "tests all pass!" | |
# 最后,也很重要的是,实现 `make clean` 来清除 Makefile 所生成的所有文件。 | |
clean: | |
rm -rf *.txt src dest kitty | |
# 出错了怎么办?假设你正在构建很多东西,其中一个命令执行失败了。 | |
# 这时候 Make 会忽略返回非 0 错误代码的命令并退出。 | |
# 这里我们使用 false 来故意触发错误(将返回错误代码 1) | |
badkitty: | |
$(MAKE) kitty # 特殊变量 $(MAKE) 表示“当前正在使用的 make” | |
false # <-- 这里会执行出错 | |
echo "should not get here" | |
.PHONY: source destination clean test badkitty |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
https://gist.github.com/isaacs/62a2d1825d04437c6f08