Apache Ant™ 任务设计指南

本文档介绍如何编写 Apache Ant 任务,以符合纳入 Ant 发行版的标准。即使您只是为了个人使用而编写任务,您也可能会发现本文档很有用,因为它所解决的问题在这种情况中仍然存在。

不要破坏现有的构建

即使您在 Ant 中发现了一些非常糟糕的问题,一个很容易修复的问题,如果您的修复破坏了现有的构建文件,那么我们就会遇到问题。确保所有现有的构建文件都能正常工作是所有更改的目标之一。例如,Ant 1.5 在字符串中传递单个美元符号“$”;Ant 1.4 及更早版本会将其删除。为了实现此修复,我们首先必须编写测试套件来暴露当前的行为,然后更改某些内容,以便传递单个“$”,但将双“$$”映射到“$”以实现向后兼容性。

不要破坏 Java API

Ant 的任务可以被第三方程序和任务使用。我们不能进行破坏 API 的更改。这包括
  1. 移动类而不留下向后兼容的 фасады。
  2. 删除类。
  3. 删除方法或字段,或降低其可访问性。
  4. 更改方法的签名setAttribute(Type)方法。如果您需要添加一个限制性类型,请添加一个新属性,并将其放置在源代码中的原始属性之上。XML 映射器将获得限制性类型,旧程序仍然可以使用旧类型。
  5. 不要更改语义。至少,不要大幅度更改。毕竟,所有错误修复都是语义的隐式更改。

使用内置的辅助类

Ant 包含辅助任务来简化您的许多工作。出于开发、维护和代码大小的原因,使用它们比自己编写要好得多。

执行

Execute 将在 Ant 支持的所有平台下生成单独的程序,处理 Java 版本问题以及平台问题。始终使用此类来调用其他程序。

Java、ExecuteJava

这些类可用于在单独的 VM 中生成 Java 程序(它们使用 execute)或在同一个 VM 中生成 Java 程序——使用或不使用不同的类加载器。当从该类派生任务时,允许指定类路径以及将分叉作为可选属性通常对用户有利。

项目和相关类

Project、FileUtils、JavaEnvUtils 都具有辅助函数来执行诸如触摸文件、复制文件等操作。使用这些函数,而不是自己编写代码或尝试使用可能不太稳定且更难使用的任务。

遵守 Sun/Java 风格指南

Ant 代码库的目标是拥有一个统一的编码标准,该标准是 Sun Java 编码指南

并不是说它们比任何其他替代方案更好,但它们是一个标准,并且它们是其他任务中始终使用的标准。代码只有在符合这些标准后才能纳入数据库。

如果您正在为个人或组织使用编写任务,您可以自由使用任何您喜欢的风格。但是,使用 Sun Java 风格将帮助您熟悉 Ant 的其余源代码,这可能很重要。

一个重要的规则是“不使用制表符”。使用四个空格代替。不是两个,不是八个,而是四个。即使您的编辑器配置为使用四个空格的制表符,许多其他编辑器却没有。空格在编辑器和平台之间具有更高的一致性。一些 IDE(JEdit)可以突出显示制表符,以防止您意外插入它们。

在主 ant 目录中有一个 Ant 构建文件 check.xml,它在 Ant 的源代码上运行 checkstyle

属性和元素

使用 Ant 基于内省的属性到 Java 数据类型的映射,而不是将所有属性实现为 setFoo(String) 并自己进行到 int、boolean 或 File 的映射。这节省了您的工作量,让 Java 调用者能够以类型安全的方式使用您,并让 Xdocs 文档生成器找出参数是什么。

Ant 1.x 任务在属性命名方面非常不一致——一些任务使用source,而其他任务使用src。以下是首选属性名称列表

failonerror 布尔值,用于控制执行失败是否应该抛出BuildException或只打印错误。参数验证失败应始终抛出错误,无论此标志如何。
destdir 输出的目标目录
destfile 输出的目标文件
srcdir 源目录
srcfile 源文件

是的,这是一个非常简短的列表。至少尝试与核心任务保持大致一致。

支持类路径

如果您需要外部库,请尝试使人们能够为您的任务提供类路径,而不是让他们将所有内容添加到 ANT_HOME/lib 目录中。这使人们能够将外部库保留在他们的基于 Ant 的项目中,而不是强迫所有用户更改其 Ant 系统配置。

设计用于受控的重用

保持成员变量私有。如果子类需要读取访问权限,请添加访问器方法,而不是更改成员的可访问性。这使子类能够访问内容,但仍然与实际实现分离。

Ant 中的另一个常见的重用机制是让一个任务创建和配置另一个任务。这相当简单。Ant 的 API 中提供了设施,使任务能够通过其熟悉的名称(“java”、“exec”等)进行实例化。建议您不要使用这种方法,因为用户完全有可能覆盖了该名称以指向完全不同的类。使用直接构造函数调用(或反射)来实例化您的子任务。从 Ant 1.6.3 开始,您可以调用org.apache.tools.ant.Task#bindToOwner() 来将辅助任务“掩盖”为其父任务。

执行您自己的依赖项检查

Make 在其集成的依赖项检查方面具有优势;命令行应用程序不需要执行自己的工作。Ant 任务确实需要执行自己的依赖项工作,但如果可以做到这一点,那么就可以做得很好。一个好的依赖项感知任务可以无需构建文件中的显式依赖项信息即可找出依赖项,并且足够智能以找出真正的依赖项,也许可以通过一些文件解析。该depends任务是这方面的最佳示例。一些 zip/jar 任务也相当不错,因为它们可以在需要时更新存档。大多数任务只是比较源和目标时间戳并从那里开始。没有进行任何依赖项检查的任务无法像它们可以的那样帮助用户,因为它们不必要的工作会渗透到整个构建、测试和部署过程中。

支持适当的 Java 版本

Ant 1.5 及更低版本旨在支持 Java 1.1。Ant 1.6 及更高版本旨在支持 Java 1.2:在它之上构建,在它之上运行。Ant 1.8 需要 Java 1.4;1.9 需要 1.5(“JDK 5”)。有时,任务的功能必须在较旧或较新的环境中降级——通常是由于库限制;这种行为变化必须始终在文档中注明。

有问题的是依赖于比当前基线更新的 Java 版本中的功能的代码,例如java.nio.file.Path在 JDK 7 中。还要注意较旧类中添加的方法;这些方法不能被任何代码直接使用,并且仍然能够在较旧的系统上编译和运行。如果要使用现有类中的新方法,则必须通过反射使用它,并且NoSuchMethodException以某种方式处理。

如果代码在较旧版本的 Java 上根本无法工作怎么办?它可能会发生。将任务作为可选任务可能没问题,通过 build.xml 修改将编译限制为更新的 JDK(或更高版本)。更好的是,使用反射在运行时链接到类。

类似的考虑适用于新的语言特性,例如 JDK 7 字符串 switch 语句。

显式地扩展嵌套文本中的属性

出于历史原因,addText(String text)用于设置任务的嵌套文本,没有任何属性扩展发生。调用Project.replaceProperties()手动执行此操作。如果您忘记,您会创建一个无法修复的问题,除非破坏用户的构建文件。

重构

如果对任务所做的更改使其变得过于笨拙,请将其拆分为更清晰的设计,重构代码并提交不仅仅是功能蔓延,而是更清晰的任务。在 Ant 过程中,一种常见的出现的设计模式是采用适配器模式,其中一个基类(例如 Javac 或 Rmic)从简单开始,然后通过对多个后端的支持变得复杂:javac、jikes、jvc。将可编程前端与提供后端的类分离的重构清理了设计,并使添加新的后端变得更加容易。但要实现这一点,需要保持前端的接口和行为相同,并确保没有子类直接访问数据成员,因为这些数据成员可能不存在于重构后的设计中。这就是为什么拥有私有数据成员如此重要的原因。

我们绝对不能做的一件事是移动或删除现有的任务。请记住,Ant 具有 Java API 以及 XML 语言。我们不想破坏该 API,也不想破坏任何子类化现有 Ant 任务的内容。重构时,您需要在原始类所在的位置留下 фасады。因此,现有代码不会中断。

测试

查看ant/src/testcases您将找到用于交付的 Ant 任务的 JUnit 测试,以了解如何完成以及对新任务的期望。它们中的大多数都很基础,毫无疑问,您可以为您的任务做得更好——请随意这样做!

一套编写良好的测试用例将在 Ant 任务开发过程中将其破坏,直到代码真正完成。并且,以后出现的每个错误都应该添加一个测试用例来演示问题并修复它。

测试用例是您在开发过程中测试任务的好方法。在 ant 源代码树中简单地调用“build run-test”将运行所有 ant 测试,以验证您的更改是否没有破坏任何东西。要测试单个任务,请使用一次性ant run-single-test -Dtestcase=${testname},其中${testname} 是您的测试类的名称。

测试用例也由提交者用来验证更改和补丁是否按预期执行。如果您有测试用例,它会大大提高您的可信度。确切地说,我们讨厌没有测试用例的提交,因为这意味着我们必须自己编写它们。只有在我们需要该任务或它被认为对许多用户至关重要时,才会这样做。

还要记住,Ant 1.x 旨在编译并在 Java 1.2 上运行,因此您应该在 Java 1.2 以及您使用的任何更高版本上进行测试。您应该能够为此目的从 Sun 下载旧的 SDK。

最后,在开始开发项目之前和之后运行完整的build test,以确保您没有意外地破坏其他任何东西。

文档

没有文档,任务就无法使用。因此,请务必提供一个简洁明了的 HTML(很快,XML)页面,以类似于现有任务的风格描述任务。它应该包含属性和元素列表,以及至少一个可工作的任务示例。许多用户将示例剪切粘贴到他们的构建文件中作为起点,因此请确保示例实用且经过测试。

您可以使用 proposal/xdocs 中的 xdocs 内容从源代码的 javadoc 自动生成文档页面;这将使生活更轻松,并将使向完全由 xdoclet 生成的文档构建过程过渡变得微不足道。

许可和版权

提交给 Apache 项目的任何代码必须与 Apache 许可证兼容,并且提交行为必须被视为将提交的代码隐式许可给 Apache 软件基金会。

这一点很重要。

Apache 的相当自由的许可证目前不被认为与自由软件基金会(Gnu 项目)的 GPL 或 Lesser GPL 兼容。这些许可证具有更严格的条款,“版权所有”,这些条款不在 Apache 许可证中。这允许个人和组织在 Apache 库和源代码之上构建商业和闭源应用程序。

由于 Gnu GPL 许可证会立即扩展到涵盖任何包含它的更大应用程序(或在 LGPL 的情况下为库),因此 Ant 团队无法将基于 GPL 或 LGPL 源代码的任何任务合并到 Ant 代码库中。您可以随意提交,但它将被礼貌而坚定地拒绝。

如果您通过 `import` 或反射链接到 GPL 或 LGPL 库,则您的任务必须在相同的条款下获得许可。因此,链接到 (L)GPL 代码的任务不能进入 Apache 管理的代码库。调用此类代码的任务可以使用 `exec` 或 `java` 任务来运行程序,因为您此时只是在执行它们,而不是链接到它们。

即使我们无法将您的任务包含到 Apache 代码库中,我们仍然可以指向您托管它的位置;只需提交一个指向您任务的 xdocs/external.html 的 diff 即可。

如果您的任务直接链接到专有代码,我们遇到了不同的问题:构建任务非常困难。请使用反射。

不要重复造轮子

我们都做过:编写并提交了一个任务,结果发现它已经在另一个任务的小角落里实现了,或者它已经被其他人提交但尚未提交。您可以通过了解最新 CVS 树中的内容来避免这种情况;继续获取每日源代码更新,查看手动更改并订阅开发邮件列表。

如果您正在考虑编写一个任务,在列表中发布您想法的说明可能是有益的 - 您将获得其他人的见解,也许还有一些半写好的任务来完成基础工作,所有这些都不需要编写一行代码。

提交到 Ant

提交 Ant 任务的基本机制是将其发送到开发邮件列表。加入此列表会有所帮助,因为您将看到其他提交,以及关于您自己提交的任何讨论。

您可以使用以下两种方法之一创建补丁文件(提交者推荐第一种方法)

补丁应作为附件发送到主题为 [PATCH] 的邮件中,并在主题中包含一个独特的单行摘要。文件名/任务和更改通常就足够了。将更改作为附件包含在内非常重要,因为太多邮件程序会重新格式化粘贴的文本,这会破坏补丁。

然后,您等待其中一位提交者提交补丁,如果认为这样做是合适的。错误修复会很快进行,其他更改通常会引发一些讨论,然后才会进行(可能经过修改的)提交。

新提交应以 [SUBMIT] 开头。邮件守护程序会拒绝任何超过 100KB 的邮件,因此任何大型更新都应压缩。如果您的提交大于此,为什么不将其分解为单独的任务呢。

我们还希望将提交添加到 bugzilla 中,这样它们就不会丢失。请先使用有意义的名称提交报告,然后将文件作为附件添加。请使用 CVS diff 文件!

如果您在几周后没有收到任何回复,请提醒邮件列表。有时,非常好的提交会淹没在其他问题的噪音中而丢失。这在产品发布新版本之前尤其如此。在那段时间里,除了错误修复之外的任何内容都可能被忽略。

清单

以下是在提交补丁和新任务之前应验证的事项。事情不必完美;可能需要几次迭代才能提交补丁或提交,并且可以在此过程中解决这些问题。但是,在代码提交时,包括文档和一些测试用例在内的一切都将完成,因此提前解决它们可以节省时间。提交者对包含测试用例的补丁和提交更感兴趣,而文档有助于说明任务的原因。提交补丁之前的清单

提交补丁之前的清单

提交新任务之前的清单