# Hello, and welcome to makefile basics. # # 这里我将向大家介绍 `make` 的优秀之处,虽然语法上有点“奇怪”,但却是一个高效、 # 快速、强大的程序构建解决方案。 # # 学完了这里的基础之后,建议你前往 GNU 官网 # http://www.gnu.org/software/make/manual/make.html # 更加深入 `make` 的用法。 # `make` 命令必须在一个存在 `Makefile` 文件的目录使用,当然,你也可以使用 # `make -f ` 来指定 `Makefile`。 # # Makefile 种存储了一系列的规则,每一条规则都对应一条任务,类似于 grunt 中的 # task 或者 npm package.json 脚本。 # # make 规则通常是这个样子的: # # : # # # `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 将被重新构建。 # # # Let's say that we were working on a project with 100 .c files, and each of # those .c files we wanted to turn into a corresponding .o file, and then link # all the .o files into a binary. (This is effectively the same if you have # 100 .styl files to turn into .css files, and then link together into a big # single concatenated main.min.css file.) # # It would be SUPER TEDIOUS to create a rule for each one of those. Luckily, # make makes this easy for us. We can create one generic rule that handles # any files matching a specific pattern, and declare that we're going to # transform it into the corresponding file of a different pattern. # # Within the ruleset, we can use some special syntax to refer to the input # file and the output file. Here are the special variables: # # $@ The file that is being made right now by this rule (aka the "target") # You can remember this because it's like the "$@" list in a # shell script. @ is like a letter "a" for "arguments. # When you type "make foo", then "foo" is the argument. # # $< The input file (that is, the first prerequisite in the list) # You can remember this becasue the < is like a file input # pipe in bash. `head $@ # Try running `make src/00.txt` and `make src/01.txt` now. # To not have to run make for each file, we define a "phony" target that # depends on all of the srcfiles, and has no other rules. It's good # practice to define your phony rules in a .PHONY declaration in the file. # (See the .PHONY entry at the very bottom of this file.) # # Running `make source` will make ALL of the files in the src/ dir. Before # it can make any of them, it'll first make the src/ dir itself. Then # it'll copy the "stem" value (that is, the number in the filename matched # by the %) into the file, like the rule says above. # # Try typing "make source" to make all this happen. source: $(srcfiles) # So, to make a dest file, let's copy a source file into its destination. # Also, it has to create the destination folder first. # # The destination of any dest/*.txt file is the src/*.txt file with # the matching stem. You could just as easily say that %.css depends # on %.styl dest/%.txt: src/%.txt @[ -d dest ] || mkdir dest cp $< $@ # So, this is great and all, but we don't want to type `make dest/#.txt` # 100 times! # # Let's create a "phony" target that depends on all the destination files. # We can use the built-in pattern substitution "patsubst" so we don't have # to re-build the list. This patsubst function uses the same "stem" # concept explained above. destfiles := $(patsubst src/%.txt,dest/%.txt,$(srcfiles)) destination: $(destfiles) # Since "destination" isn't an actual filename, we define that as a .PHONY # as well (see below). This way, Make won't bother itself checking to see # if the file named "destination" exists if we have something that depends # on it later. # # Let's say that all of these dest files should be gathered up into a # proper compiled program. Since this is a tutorial, we'll use the # venerable feline compiler called "cat", which is included in every # posix system because cats are wonderful and a core part of UNIX. kitty: $(destfiles) @# Remember, $< is the input file, but $^ is ALL the input files. @# Cat them into the kitty. cat $^ > kitty # Note what's happening here: # # kitty -> (all of the dest files) # Then, each destfile depends on a corresponding srcfile # # If you `make kitty` again, it'll say "kitty is up to date" # # NOW TIME FOR MAGIC! # # Let's update just ONE of the source files, and see what happens # # Run this: touch src/25.txt; make kitty # # Note that it is smart enough to re-build JUST the single destfile that # corresponds to the 25.txt file, and then concats them all to kitty. It # *doesn't* re-generate EVERY source file, and then EVERY dest file, # every time # It's good practice to have a `test` target, because people will come to # your project, and if there's a Makefile, then they'll expect `make test` # to do something. # # We can't test the kitty unless it exists, so we have to depend on that. test: kitty @echo "miao" && echo "tests all pass!" # Last but not least, `make clean` should always remove all of the stuff # that your makefile created, so that we can remove bad stuff if anything # gets corrupted or otherwise screwed up. clean: rm -rf *.txt src dest kitty # What happens if there's an error!? Let's say you're building stuff, and # one of the commands fails. Make will abort and refuse to proceed if any # of the commands exits with a non-zero error code. # To demonstrate this, we'll use the `false` program, which just exits with # a code of 1 and does nothing else. badkitty: $(MAKE) kitty # The special var $(MAKE) means "the make currently in use" false # <-- this will fail echo "should not get here" .PHONY: source destination clean test badkitty