《跟我一起写Makefile (六)》摘抄

书写命令

  每条规则中的命令和操作系统Shell的命令行是一致的。make 会按顺序一条条执行命令, 每条命令的开头必须以 Tab 键开头, 除非, 命令是紧跟在依赖规则后面的分好后的。在命令行之间的空格或是空行会被忽略, 但是如果该空格或空行是以 Tab 键开头的, 那么 make 会认为其是一个空命令。

  我们再 UNIX 下可能会使用不同的Shell, 但是 make 的命令默认是被 /bin/sh —— UNIX的标准Shell解释执行的。除非你特别指定一个其他的Shell。Makefile 中, # 是注释符, 很像 C/C++ 中的 “\“, 其后的本行字符都被注释。

一、显示命令

通常, make 会把其要执行的命令在命令执行前输出到屏幕上。当我们用”@”字符在命令行前, 那么, 这个命令将不被 make 显示出来, 最具代表性的例子是, 我们用这个功能来向屏幕显示一些信息。如:

@echo 正在编译XXX模块

当 make 执行时, 会输出”正在编译XXX模块”, 但不会输出命令, 如果没有@, 那么 make 将输出:

1
2
echo 正在编译XXX模块
正在编译XXX模块

如果 make 执行时, 带入 make 参数 “-n” 或 “–just-print”, 那么其只是显示命令, 但不会执行命令, 这个功能很有利于我们调试我们的 Makefile, 看看我们书写的命令执行起来是什么样子或是什么顺序。

而 make 参数 “-s” 或 “–slient” 则是全面禁止命令的显示。

二、命令执行

当依赖目标新于目标时,也就是当规则的目标需要被更新时, make 会一条一条的执行其后的命令。需要注意的是, 如果你要让上一条的命令的结果应用在下一条命令时, 你应该使用分号分隔这两条命令。比如你的第一条命令是 cd 命令, 你希望第二条命令得在 cd 的基础上运行, 那么你就不能把这两条命令写在两行上, 而应该把这两条命令卸载一行上, 用分号分隔。

如:

示例一:

1
2
3
exec:
cd /home/littleboy
pwd

示例二:

1
2
exec:
cd /home/littleboy; pwd

当我们执行 “make exec” 时, 第一个例子中的 cd 没有作用, pwd会打印出当前的 Makefile 目录。而第二个例子中, cd 就起作用了, pwd 会打印 /home/littleboy

make 一般是使用环境变量 SHELL 所定义的系统 Shell 来执行命令, 默认情况下使用 UNIX 标准 Shell —— /bin/sh 来执行命令。但在 MS-DOS 下有点特殊, 因为 MS-DOS 下没有 SHELL 环境变量, 当然你也可以指定。如果你指定了 UNIX 风格的目录形式, 首先, make 会在 SHELL 所指定的路径中找寻命令解释器, 如果找不到, 其会在当前盘符中的当前目录寻找, 如果再找不到, 其会在 PATH 环境变量中所定义的所有路径中寻找。MS-DOS 中, 如果你定义的命令解释器没有找到, 其会给你的命令解释器加上诸如.exe.com.bat.sh 等后缀。

三、命令出错

每当命令运行完后, make 会检测每个命令的返回码, 如果命令返回成功, 那么 make 会执行下一条命令, 当规则中所有的命令成功返回后, 这个规则就算是成功完成了。如果一个规则中的某个命令出错了(命令退出码非零), 那么 make 就会终止执行当前规则, 这将有可能终止所有规则的执行。

有些时候, 命令的出错并不表示就是错误的。例如 mkdir 命令, 我们一定需要建立一个目录, 如果目录不存在, 那么 mkdir 就成功执行, 万事大吉。如果目录存在, 那 mkdir 就出错了, 但是并不代表命令有问题。所以有些错误我们需要忽略掉, 为了做到这一点, 我们可以在 Makefile 的命令行前加一个减号-(在 Tab 键之后), 标记为不管命令出不出错都认为是成功的。如:

1
2
clean:
-rm -f *.o

还有一个全局的办法是, 给 make 加上-i或是--ignore-errors参数, 那么, Makefile 中所有命令都会忽略这个错误。而如果一个规则是以.INGNORE作为目标的, 那么这个规则中的所有命令都会忽略这个错误。这些是不同级别的防止命令出错的方法, 你可以根据你的不同喜欢设置。

还有一个要提一下的是 make 的参数 -k 或是 --keep-going, 这个参数的意思是, 如果某规则中的命令出错了, 那么就终止该规则执行, 但继续执行其他规则。

四、嵌套执行 make

在一些大的工程中, 我们会把我们不同模块或是不同功能的源文件放在不同的目录中, 我们可以在每个目录中都书写一个该目录的 Makefile, 这有利于我们的 Makefile 变得更加的简洁, 而不至于把所有的东西全部写在一个 Makefile 中, 这样会很难维护我们的 Makefile, 这个技术对于我们模块编译和分段编译有着非常大的好处。

例如, 我们有一个子目录叫 subdir, 这个目录有个 Makefile 文件, 来指明了这个目录下文件的编译规则。那么我们总控的 Makefile 可以这样书写:

1
2
subsystem:
cd subdir && $(MAKE)

其等价于:

1
2
subsystem:
$(MAKE) -C subdir

定义$(MAKE)宏变量的意思是, 也许我们的 make 需要一些参数, 所以定义成一个变量比较利于维护。这两个例子的意思都是先进入subdir目录, 然后执行 make 命令。

我们把这个 Makefile 叫做 “总控 Makefile”, 总控 Makefile 的变量可以传递到下级的 Makefile 中, 但是不会覆盖下层的 Makefile 中所定义的变量, 除非指定了 -e 参数。

如果你要传递变量到下级 Makefile 中, 那么你可以使用这样的声明:

1
export <variable...>

如果你不想让某些变量传递到下级 Makefile 中, 那么你可以这样声明:

1
unexport <variable...>

如:

1
export variable = value

其等价于:

1
2
variable = value
export variable

等价于:

1
export variable := value

等价于:

1
2
variable := value
export variable

再如:

1
export variable += value

其等价于:

1
2
variable += value
export variable

如果你要传递所有的变量, 那么, 只要有一个export就行了, 表示传递所有的变量。

需要注意的是, 有两个变量, 一个是SHELL, 一个是 MAKEFILES, 这两个变量不管是否 export, 都会传递到下层的 Makefile 中, 特别是 MAKEFILES 变量, 其中包含了 make 的参数信息, 如果我们执行”总控 Makefile”时有 make 参数或是在上层 Makefile 中定义了这个变量, 那么 MAKEFILES 变量将会是这些参数, 并会传递到下层 Makefile 中, 这是一个系统级的变量。

但是 make 命令中的有几个参数并不往下传递, 他们是-C, -f, -h, -o-W, 如果你不想往下层传递参数, 那么你可以这样来:

1
2
subsystem:
cd subdir && $(MAKE) MAKEFLAGS=

如果你定义了环境变量 MAKEFLAGS, 那么你得确信其中的选项是大家都会用到的, 如果其中有-t, -n-q参数, 那么将会有意想不到的错误。

还有一个在嵌套执行中比较有用的参数, -w或是--print-directory会在 make 的过程中输出一些信息, 让你看到目前的工作目录。比如, 如果我们的下级 make 目录是 /home/littleboy/gnu/make, 如果我们使用make -w来执行, 那么当进入该目录时, 我们将会看到:

1
make: Entering directory /home/littleboy/gnu/make`

而在完成下层 make 后离开目录时, 我们会看到:

1
make: Leaving directory `/home/littleboy/gnu/make`

当你使用-C参数来指定 make 下层 Makefile 时, -w 会被自动打开。如果参数中有-s(--slient)或是--no-print-directory, 那么-w总是失效的。

五: 定义命令包

如果 Makefile 中出现一些相同命令序列, 那么我们可以为这些相同的命令序列定义一个变量。定义这种命令序列的语法以define开始, 以endef结束, 如:

1
2
3
4
define run-yacc
yacc $(firstword $^)
mv y.tab.c $@
endef

这里, “run-yacc”是这个命令包的名字, 不要和 Makefile 中的变量重名。在defineendef中的两行就是命令序列。这个命令包中的第一个命令是运行 Yacc 程序, 因为 Yacc 程序总是生成 “y.yab.c” 的文件, 所以第二行的命令就是把这个文件改名字。还是把这个命令包放到一个实例中看看:

1
2
foo.c: foo.y
$(run-yacc)

我们可以看见, 要使用这个命令包, 我们就好像使用变量一样。在这个命令包的使用中, 命令包 “run-yacc” 中的 $^ 就是 foo.y, $@ 就是 foo.c, make 在执行命令包时, 命令包中的每个命令会被依次独立执行。


《跟我一起写Makefile (六)》摘抄
http://icecreamzhao.github.io/note/blog_note/edit-makefile/edit-makefile-six.html
作者
littleboyDK
发布于
2019年6月11日
许可协议