规则格式
一个 Makefile 通常由一条或若干条规则组成,每条规则格式通常如下:
target … : prerequisites …
recipe
…
target
: 通常是文件名或要执行的动作名- 单个文件
- 多个文件,使用空格分隔
- 使用通配符,表示多个文件名
- 压缩包中的文件:
archive(member)
- 要执行的动作名,参见:伪目标
prerequisites
: 通常是目录文件所依赖的文件- 可以为空,表示目录不依赖任何文件
- 多个文件使用空格分隔,当一行太长时可以使用
\
分割成多行写 - 也可以使用通配符,表示多个文件
- 也可是压缩包中的文件
recipe
: 通常是产生目标文件或执行动作名,所执行的具体命令- 每条命令必须: 以
Tab
开头,或以.RECIPEPREFIX
中设置的值开头 - 可以一条命令写一行,也可以多条命令写在同一行。当多条命令写在同一行时需用
;
分隔每条命令。有时命令写在同一行和写在多行特殊情况下的差异
- 每条命令必须: 以
.PHONY target
.PHONY: target
定 target 伪目标,伪目标不是文件的真正名称,它只时用来显示的执行一系列recipe
的名称。
使用伪目标有两个原理: 避免与同名文件冲突,并提高性能。
避免与同名文件冲突
假如你在Makefile
文件中定义了如下 1 条规则:
clean:
rm *.o temp
因为该规则的recipe
不会创建名为 clean 的文件,所以当你执行mingw32-make clean
命令时,该规则的recipe
(rm 命令)将总是被执行
但是,如果文件夹存在一个名称clean的文件,你再执行mingw32-make clean
命令时,
又因 clean 依赖任何文件,所以不会更新。recipe
里的 rm 命令不会被执行。
为了让每次执mingw32-make clean
命令时,都执行recipe
里的 rm 命令(即使当前目录下存在名为clean的文件),就需将 clean 定义成伪目标:
.PHONY: clean
clean:
rm *.o temp
提高性能
假如当前文件目录如下:
.
├── Makefile
├── Makefile_phony
├── bar
│ └── Makefile
├── baz
│ └── Makefile
└── foo
└── Makefile
根目录下的Makefile_phony
是将subdirs
目标定义为伪目标的规则文件,根目录下有 3 个文件夹,每个文件夹有自己的规则文件。
规则文件对应内容如下(注:因为以下 Makefile 中使用了 shell 的 for 和 sleep 等,所以建议在 bash 下执行):
$ cat ./Makefile
SUBDIRS = bar foo baz
subdirs:
@for dir in $(SUBDIRS); do \
$(MAKE) -C $$dir; \
done
$ cat ./Makefile_phony
SUBDIRS = bar foo baz
.PHONY: subdirs $(SUBDIRS)
subdirs: $(SUBDIRS)
$(SUBDIRS):
@$(MAKE) -C $@
$ cat ./bar/Makefile
bar:
@sleep 1
$(error some error in $@)
@echo $@
$ cat ./baz/Makefile
baz:
@sleep 2
@echo $@
$ cat ./foo/Makefile
foo:
@sleep 3
@echo $@
我在./bar/Makefile
中模拟了一个错误,分另用用单线程和多线程对根目录下的规则文件进行make
1. 单线程 make 对比
$ mingw32-make
mingw32-make[1]: Entering directory 'C:/Users/TGL/Desktop/VsCode_wxWidgets/bar'
Makefile:2: *** some error in bar. Stop.
mingw32-make[1]: Leaving directory 'C:/Users/TGL/Desktop/VsCode_wxWidgets/bar'
mingw32-make[1]: Entering directory 'C:/Users/TGL/Desktop/VsCode_wxWidgets/foo'
foo
mingw32-make[1]: Leaving directory 'C:/Users/TGL/Desktop/VsCode_wxWidgets/foo'
mingw32-make[1]: Entering directory 'C:/Users/TGL/Desktop/VsCode_wxWidgets/baz'
baz
mingw32-make[1]: Leaving directory 'C:/Users/TGL/Desktop/VsCode_wxWidgets/baz'
可以看出即使在 make ./bar/Makefile 时出错了,后续的两个子项目仍然依次 make 了
$ mingw32-make -f ./Makefile_phony
mingw32-make[1]: Entering directory 'C:/Users/TGL/Desktop/VsCode_wxWidgets/bar'
Makefile:2: *** some error in bar. Stop.
mingw32-make[1]: Leaving directory 'C:/Users/TGL/Desktop/VsCode_wxWidgets/bar'
Makefile_phony:8: recipe for target 'bar' failed
mingw32-make: *** [bar] Error 2
可以看出伪目标在子项目出错时,主项目就立即停止。且主 make 会显示哪个子项目出错了
2. 多线程 make 对比
$ mingw32-make -j4
mingw32-make[1]: Entering directory 'C:/Users/TGL/Desktop/VsCode_wxWidgets/bar'
Makefile:2: *** some error in bar. Stop.
mingw32-make[1]: Leaving directory 'C:/Users/TGL/Desktop/VsCode_wxWidgets/bar'
mingw32-make[1]: Entering directory 'C:/Users/TGL/Desktop/VsCode_wxWidgets/foo'
foo
mingw32-make[1]: Leaving directory 'C:/Users/TGL/Desktop/VsCode_wxWidgets/foo'
mingw32-make[1]: Entering directory 'C:/Users/TGL/Desktop/VsCode_wxWidgets/baz'
baz
mingw32-make[1]: Leaving directory 'C:/Users/TGL/Desktop/VsCode_wxWidgets/baz'
可以看出这儿单线程和多线程 make 是无差别的,都是依次 make 每个子项目
$ mingw32-make -f ./Makefile_phony -j4
mingw32-make[1]: Entering directory 'C:/Users/TGL/Desktop/VsCode_wxWidgets/foo'
mingw32-make[1]: Entering directory 'C:/Usmingwe3r2s-/make[1]: Entering directory 'C:/Users/TGL/Desktop/VsCode_wxWidgets/bar'
ktoMp/aVskCodee_wfxWiidgletes/b:az'3
: *** some error in bar. Stop.
mingw32-make[1]: Leaving directory 'C:/Users/TGL/Desktop/VsCode_wxWidgets/bar'
Makefile_phony:8: recipe for target 'bar' failed
mingw32-make: *** [bar] Error 2
mingw32-make: *** Waiting for unfinished jobs....
baz
mingw32-make[1]: Leaving directory 'C:/Users/TGL/Desktop/VsCode_wxWidgets/baz'
foo
mingw32-make[1]: Leaving directory 'C:/Users/TGL/Desktop/VsCode_wxWidgets/foo'
可以看出多线伪目标在多线程 make 时,同时启用了多个线程来 make 子项目,项目的总时间缩短,且子项目出错主项目就会停止 make
recipe 命令写法带来的差异
我们以下面这个 Makefile 文件为例来讲解一些特殊情况下的单行命令和和多行命令的区别
Makefile.PHONY: test1 test2
test1:
@echo "-------------两条命令写在两只一行-----------------"
cd subdir; pwd
test2:
@echo "-------------两条命令分别写在两行-----------------"
cd subdir
pwd
在该 Makefile 中创建了两个虚拟动作"test1"和"test2"两个动作都是执行相同的两条命令。
唯一的区别就是:一个是把两条命令写在同一行,而另一个是把两条命令写在了两行。
TGL233@TGL-ThinkPad MINGW64 ~/Desktop/Hello_wxWidgets
$ mingw32-make test1
-------------两条命令写在两只一行-----------------
cd subdir; pwd
/c/Users/TGL233/Desktop/Hello_wxWidgets/subdir
mingw32-make test2 执行结果
TGL233@TGL-ThinkPad MINGW64 ~/Desktop/Hello_wxWidgets
$ mingw32-make test2
-------------两条命令分别写在两行-----------------
cd subdir
pwd
/c/Users/TGL233/Desktop/Hello_wxWidgets
从上面示例的执行结果可以看出:
写在同一行时执行完cd subdir
命令后 make 并没有返回当前目录,而是留在 subdir 目录下继续执行了pwd
命令所以输出是 subdir 目录。
而分别写在两行时,在执行完cd subdir
命令后 make 返回到当前目录了,所以再执行pwd
命令时就输出是当前目录。