输出构建目标
2007年4月26日
在过去的几天里,我一直在审阅我同事朱利安·辛普森正在撰写的一篇文章,文章主题是重构 Ant 文件。朱利安是我们团队的“部署大牛”,他一直致力于将我们敏捷的工作习惯应用到系统部署中。在这一过程中,朱利安遇到了很多棘手的 ant 构建脚本。他的文章很好地描述了他最喜欢的清理这些混乱的方法。
他的一项观察结果特别引起了我的兴趣,他指出 Ant 文件往往分解成以任务命名的目标(例如,“编译”、“单元测试”)。这些任务通常被组合成命令式目标调用
<target name="cruise" depends="clean, compile, copy-static-files, unit-test, publish, javadoc, tag"/>
这种风格闻起来不对劲,因为它与构建文件所基于的 基于依赖关系的计算模型 不匹配。使用这种模型时,你需要查看每个目标,询问它依赖于什么,并将这些依赖关系添加到目标中。因此,这里的 unit-test
任务应该直接依赖于 compile
和 copy-static-files
目标,而不应该被后面的目标提及。
面对明显愚蠢的行为,最容易的做法是发表一些轻率的评论,比如“也许他们的系统很简单,可以在不编译的情况下运行测试”。但通常情况下,值得深入研究一下。
朱利安和我都有 Unix 背景,所以我们熟悉 make 构建语言。与 Ant 一样,make 使用基于依赖关系的编程。这两个系统都将工作分解成通过依赖关系链接的目标。但有一个细微的差别。在 Ant 中,你给目标命名,并指出你依赖于哪些其他目标。然而,在 make 中,你声明一个输出文件,它依赖的输入文件,以及如何从一个文件转换到另一个文件。
因此,在 make 中编译一个包含两个文件的 Hello World(用 C 语言编写)看起来像这样
hello : hello.o greet.o gcc -o hello hello.o greet.o hello.o : hello.c gcc -c hello.c greet.o: greet.c gcc -c greet.c
第一行表示目标文件 hello
依赖于文件 hello.o
和 greet.o
。第二行告诉你如何从源文件创建目标(在真正的 make 文件中,大部分内容是通用规则,因此你无需重复)。然后,我们有类似的规则来展示如何创建顶级规则的输入。
在 Ant 中(使用 Java)看起来像这样
<project name = "hello" default = "test"> <target name = "compile"> <javac srcdir = "src" destdir = "build" /> </target> <target name = "test" depends = "compile"> <junit printsummary="on"> <classpath location = "build"/> <test name = "Tester"/> </junit> </target> </project>
这里我们说我们有一个测试任务,它依赖于编译任务,这意味着编译任务需要在运行测试任务之前运行。
这两个片段都演示了依赖关系,但这种命名差异会影响我们对它们的思考方式。我想知道这种命名约定是如何将你的大脑从思考依赖关系拉到思考命令式规则的。
另一个方面是我们如何处理不必要的工作。构建总是比我们想要的要花更长时间,所以我们做了很多工作来避免做不需要做的事情。事实上,这正是使用基于依赖关系的编程的意义所在。即使目标被多次提及,它也只会运行一次。
但这种避免不必要工作的愿望也体现在运行目标本身中。make 中避免工作的基本机制是比较输出文件和输入文件的修改时间。如果输出文件比最年轻的输入文件年轻,那么它显然是最新的,不需要重新创建。
Ant 不会这样做,而是依靠 Ant 任务自行分析以确定需要运行什么。当编译器需要确定在更改后需要重建哪些输出类时,这样做很有意义。然而,人们经常编写自己不进行任何检查的目标。对于任何目标,你都应该确保它不会进行任何重大工作,除非可能发生了变化;关注输出是实现这一点的好方法。
有时输出并不明显。单元测试任务的输出是什么?戴上我的 make 帽子,我会说“某种文件”。你可以让测试报告成为结果,并在任务中检查测试报告文件是否比任何输入文件都旧——尽管这可能不像你想要的那样容易。报告甚至不需要包含任何内容,它可以只是一个 TouchFile。
我的 Unix-make 根源在这里过分了吗?也许吧。但我建议你查看你的构建文件,并考虑
- 为每个目标命名其产品(而不是它执行的活动)。
- 确保目标在不需要进行任何重大工作时不会进行任何重大工作。
- 直接声明每个目标的依赖关系,让构建系统确定要遵循的命令式序列。