Apache Ant™ 的愤怒

在生产开发系统中使用 Apache Ant

Steve Loughran
最后更新时间 2005-03-16

简介

Apache Ant 在团队开发过程中可以成为一项宝贵的工具 - 或者它可能成为我们称之为开发的持续危机中的另一个问题来源。本文档包含一些关于如何充分利用 Ant 的策略和战术。它在某些地方相当轻浮,并且几乎没有任何实际的 Ant XML 示例。缺乏示例完全是故意的 - 它可以降低文档维护成本。大多数涵盖的概念不需要 XML 表示提供的详细信息,因为我们关注的是流程,而不是语法。最后,请注意,这里的评论只是建议,需要根据自己的需求进行定制,而不是关于应该做什么和不应该做什么的严格规则。

首先,这里有一些关于本文档涵盖的项目的假设

所有这些意味着没有时间花在把事情做好上,你无法严格控制团队其他成员的工作方式,开发过程通常更像是一种混乱最小化,而不是其他任何事情。Ant 在此类项目中的作用是确保构建、测试和部署过程顺利运行,让你处理所有其他问题。

核心实践

明确你希望 Ant 做什么

Ant 不是银弹。它只是你处置的开发工具库中另一颗生锈的子弹。它的主要目的是加速 Java 项目的构建和部署。你当然可以扩展 Ant 来做 Java 允许的任何事情:例如,可以想象编写一个图像处理任务来帮助通过缩小和重新压缩 jpeg 文件来进行网站部署。但这将超出 Ant 的真正意图 - 因此应谨慎考虑。

Ant 也是 IDE 的一个很好的补充;一种执行所有部署家务和进行干净、自动构建的方法。但是一个好的现代 IDE 本身就是一个生产力工具 - 你应该继续使用它。Ant 只是让你在 IDE 选择方面给予团队更多自由 - “你可以在开发中使用任何你想要的,但部署构建使用 Ant”。现在,许多现代开源和商业 IDE 都包含 Ant 支持(包括 jEdit、Forte、Eclipse 和 IDEA),开发人员可以使用一个很棒的 IDE,Ant 提供一个严格且可移植的构建过程,集成到该工具中。

定义标准目标

当你有多个子项目时,定义一组标准目标。在接口和实现 jar 文件之间存在分割的项目可以考虑implintf 目标 - 对于调试版本,分别使用debug-impldebug-intf 目标。当然,还有无处不在的clean 目标。

使用标准目标名称,可以轻松构建包含 Ant 构建文件的构建文件,这些构建文件只是使用 ant 任务将工作传递给下面的类。例如,clean 目标可以从父目录传递给intfimpl子目录

<target name="clean"  depends="clean-intf, clean-impl">
</target>

<target name="clean-intf" >
    <ant dir="intf" target="clean" />
</target>

<target name="clean-impl">
    <ant dir="impl" target="clean" />
</target>  
如果你给目标一个description标签,然后调用ant -projecthelp将列出所有带有其描述的“主要目标”的任务,以及所有没有描述的子目标的任务。因此,描述所有入口点非常有用,即使在项目变得庞大而复杂之前也是如此。

通过新任务扩展 Ant

如果 Ant 不能满足你的需求,你可以使用 execjava 任务或 内联脚本 来扩展它。在一个包含许多build.xml文件的项目中,你会很快发现,在一个中心位置实现功能可以降低维护开销。通过 Java 代码实现任务扩展最初看起来是额外的努力,但它带来了额外的优势:-在某种程度上,正是这种将功能“任务”与使用声明“构建文件”分离,帮助 Ant 取得了成功。如果你必须在 Make 或 IDE 中完成一些复杂的事情,你将拥有一个每个人都害怕的复杂 makefile,或者一个总是非常脆弱的 IDE 配置。但是 Ant 任务是可重用且可共享的,所有 Ant 用户都可以使用。今天 Ant 中的许多核心和可选任务(你正在使用或将要依赖的任务)都是由试图解决自己迫切问题的人编写的。

拥抱自动化测试

(或者“尽早指责,经常指责”)

Ant 允许你调用 JUnit 任务,这些任务对你的团队编写的代码进行单元测试。自动化测试最初看起来像是额外的工作,但 JUnit 使编写单元测试变得如此容易,以至于你几乎没有理由不去做。花时间学习如何使用 JUnit,编写测试用例,并将它们集成到 Ant 的“test”目标中,以便你的每日或每小时团队构建可以自动应用测试。Java Development with Ant 的免费下载章节之一向你展示了如何在 Ant 内部使用 JUnit。

一旦你添加了一种从 SCM 系统获取代码的方法,无论是作为 Ant 任务、一些 shell 脚本或批处理文件,还是通过一些持续集成工具,集成测试代码可以是运行在专用于该任务的任何机器上的纯 Ant 任务。这非常适合验证构建和单元测试是否在与通常的开发机器不同的目标上工作。例如,即使没有开发人员愿意选择使用 Win95/Java1.1 组合,也可以使用它。

系统测试比单元测试更难自动化,但如果你可以编写 Java 代码来对系统的很大一部分进行压力测试 - 即使代码不能作为 JUnit 任务运行 - 那么 java 任务可以用来调用它们。最好指定你希望为这些测试使用一个新的 JVM,这样重大崩溃就不会破坏整个构建。JUnit 扩展(如用于网页的 HttpUnit 和用于 J2EE 和 servlet 测试的 Cactus)有助于扩展测试框架。为了正确测试,你仍然需要投入大量精力来使它们与你的项目一起工作,并推导出出色的单元测试、系统测试和回归测试 - 但你的客户会因为你发布了有效的软件而喜欢你。

学习使用和热爱 Ant 的附加组件

Ant 发行版不是 Ant 宇宙的极限,它只是一个开始。查看 外部工具和任务页面 以获取最新列表。以下是一些。

跨平台 Ant

Ant 是迄今为止跨平台 Java 开发和测试的最佳基础。但如果你不注意,可能会生成仅在一个平台上(甚至在一个工作站上)有效的构建文件。

跨平台 Ant 的常见障碍是使用不可移植的命令行工具(exec 任务)、路径问题以及对事物位置的硬编码。

命令行应用程序:Exec / Apply

外部调用的问题在于,并非所有函数都可以在跨平台找到,而那些找到的函数通常具有不同的名称 - DOS 后裔通常期望.exe.bat在文件末尾。如果在命令的命名中明确包含扩展名(不要这样做!),这可能很糟糕,如果它允许你在项目的同一个 bin 目录中保留可执行文件的 unix 和 DOS 版本,而不会出现名称冲突,则很好。

命令行调用任务都允许你指定希望代码运行的平台,因此你可以为每个目标平台编写不同的任务。或者,可以在 Ant 调用的某些外部代码中处理平台差异。这可以是某个编译后的 Java 代码,作为新任务,也可以是外部脚本文件。

跨平台路径

Unix 路径在目录之间使用正斜杠,并使用冒号分隔条目。因此"/bin/java/lib/xerces.jar:/bin/java/lib/ant.jar" 是 unix 中的路径。在 Windows 中,路径必须使用分号分隔符,冒号用于指定磁盘驱动器,反斜杠分隔符"c:\bin\java\lib\xerces.jar;c:\bin\java\lib\ant.jar"

平台之间的这种差异(实际上,整个 java 类路径范式)会导致数小时的乐趣。

Ant 减少了路径问题;但并没有完全消除它们。你也需要付出一些努力。处理路径名的规则是“处理 DOS 式路径名”,“处理 Unix 式路径名”。磁盘驱动器 -“C:” - 在基于 DOS 的机器上得到处理,但将它们放在build.xmlfile 中会破坏所有可移植性机会。相对文件路径的可移植性要高得多。分号充当路径分隔符 - 如果你的 Ant 调用包装器在命令行中包含一个作为定义属性的 jar 列表,那么这个事实很有用。在构建文件中,你可能会发现最好通过列出单个文件(使用 location= 属性)或通过在类路径定义中包含一个*.jar的文件集来构建类路径。

还有 PathConvert 任务,它可以将完全解析的路径放入属性中。为什么要这样做?因为这样你就可以以其他方式使用该路径 - 例如,将其作为参数传递给你要调用的某个应用程序,或者使用 replace 任务将其修补到本地化的 shell 脚本或批处理文件中。

请注意,DOS 后裔文件系统不区分大小写(除了针对 NTFS 运行的 WinNT POSIX 子系统的模糊异常),并且 Windows 假装所有具有四个或更多个字母的文件扩展名也是三个字母的扩展名(尝试 DELETE *.jav在您的 Java 目录中,您可以看到一个灾难性的例子。

Ant 对大小写敏感性的策略是底层文件系统实现的策略,它对文件扩展名的处理是*.jav找不到任何.java文件。当然,Java 编译器是区分大小写的 - 您不能在 "examplethree.java" 中实现一个名为 "ExampleThree" 的类。

有些任务只在一个平台上工作 - Chmod 就是一个典型的例子。这些任务通常在不支持的平台上只产生一个警告消息 - 目标的其余任务仍然会被调用。其他任务在平台或 Java 版本上会降低其功能。特别是,任何调整文件时间戳的任务都无法在 Java 1.1 上正确执行。可以执行此操作的任务 - 例如 GetTouch Unjar/Unwar/Unzip,在 Java1.1 上会降低其功能,通常会使用当前时间戳代替。

最后,Perl 是一个很好的地方,可以跨平台包装 Java 调用,而不是批处理文件。它包含在大多数 Unix 发行版中,并且可以从 ActiveState 的 Win32 平台 上简单下载。一个带有.pl扩展名的 Perl 文件,第一行注释中包含通常的 Unix perl 路径,并且标记为可执行文件,可以在 Windows、OS/2 和 Unix 上运行,因此可以从 Ant 中调用而不会出现问题。Perl 代码可以自行解决其平台问题。不要忘记在重新分发 Perl 代码时将文件的行尾设置到相应的平台;fixCRLF 可以为您做到这一点。

团队开发流程

即使每个团队成员都可以选择自己的 IDE/编辑器,甚至操作系统,您也需要在每个机器上设置一个功能基线。特别是,JDK 和 jar 需要完全同步。理想情况下,选择所有开发人员/目标系统上可用的最新稳定 Java/JDK 版本,并坚持一段时间。考虑指定一个人作为所有传入工具的联络点 - 特别是在 nightly 构建可用时,开源工具。除非需要,否则这些工具应该只在每月更新一次,或者在正式发布时更新。

另一个好策略是使用统一的目录树,并在该树中添加额外的工具。所有引用都可以相对于该树进行。如果团队成员需要在项目中添加一个目录到他们的路径中,那么命令行工具可以包含在那里 - 包括那些由 Ant exec 任务调用的工具。将所有内容放在源代码控制下,您就可以从 CVS 或等效工具中获得一个构建/执行环境的一站式商店。

使用 Ant 部署

Ant 和 Make 等旧工具之间的一个主要区别在于,Ant 中用于将 Java 部署到远程站点的流程已经相当完善。这是因为我们现在都必须这样做,因此许多人付出了努力使这些任务变得更容易。

Ant 可以 JarTarZip 用于部署的文件,而 War 任务扩展了 jar 任务,以更好地进行 servlet 部署。 Jlink 是一个 jar 生成文件,它允许您合并多个子 jar。这非常适合在子项目生成单独的 jar 的构建过程中,最终输出是一个合并的 jar。 Cab 可用于 Win32 机器构建 cab 文件,如果您仍然需要针对 IE 部署,这将非常有用。

The ftp 任务允许您将内容移动到服务器。注意不要将 ftp 密码放在构建文件中 - 具有严格访问控制的属性文件会稍微好一些。 FixCRLF 任务通常是一个有用的中间步骤,如果您需要调整文件的行尾。WebDav 任务长期以来一直在讨论中,它将提供更安全的上传到 Web 服务器,但它仍然在待办事项列表中。有传言说 jakarta-slide 库中有一个这样的任务。由于 MacOS X、Linux 和 Windows XP 都支持 WebDAV 文件系统,您甚至可以使用 copy 通过防火墙进行部署。

EJB 部署由 ejb 任务辅助,而 serverdeploy 套件可以部署到多个服务器。Ant 的流行促使供应商生产他们自己的部署任务,并将其与他们的服务器一起重新分发。例如,Tomcat4.1 安装包括用于部署、取消部署和重新加载 Web 应用程序的任务。

最后,当然还有使用 CopyCopydir 将文件复制到目标位置,或者使用 Mail 或支持附件的 MimeMail 将文件发送给人员或进程的备用方案。在一个项目中,我们的团队甚至使用 Ant 通过构建和一系列 Copy 任务来构建 CD 镜像,这出奇地有效,当然比我们将其发送到 myrealbox.com 上的免费电子邮件服务,然后从远端的 Web 浏览器中拉取它们要容易得多,而我们是在 WinNT 远程桌面连接上运行的,该连接通过 SSH 隧道连接。

目录结构

您如何构建目录树很大程度上取决于项目。以下是一些可以作为起点使用的目录布局模式。所有 jakarta 项目都遵循大致相似的风格,这使得在项目之间轻松导航,并在需要时轻松清理。

简单项目

项目包含子目录
bin 通用二进制文件、脚本 - 将其放在路径中。
build 这是构建的树;Ant 创建它,并且可以在 "clean" 项目中清空它。
dist 分发输出放在这里;该目录是在 Ant 中创建的,clean 会清空它。
doc 手工制作的文档
lib 导入的 Java 库放在这个目录中
src 源代码放在这个树下 在与包名匹配的层次结构中。<javac> 的依赖规则要求这样做。
bin、lib、doc 和 src 目录应该在源代码控制下。略微的变体包括一个额外的内容树,要包含在分发 jar 中 - inf 文件、图像等。这些也可以放在源代码下,使用一个metadata目录用于web.xml以及类似的清单,以及一个web文件夹用于 Web 内容 - JSP、html、图像等等。将内容保存在这个文件夹(或子层次结构)中,可以更容易地在部署之前测试链接。实际的部署镜像(例如 war 文件)的生成可以留给相应的 Ant 任务:没有必要完全根据部署层次结构对源代码树进行建模。

Javadoc 输出可以定向到doc/文件夹在build/之下,或者到doc/javadoc.

接口和实现分离

如果接口与实现代码分离,那么只需为接口目录设置一个单独的构建路径即可支持这一点 - 或者更好的是,只需在 jar 构建中进行:一个 jar 用于接口,一个 jar 用于实现。

松散耦合的子项目

在松散耦合的方法中,多个项目可以拥有自己的树副本,并拥有自己的源代码访问权限。要考虑的一个区别是,所有项目只有一个 bin 和 lib 目录实例。这有时是好的 - 它有助于保持 xerces.jar 副本的同步,有时是坏的 - 它可以在单元测试完成之前更新基础 jar 文件。

为了仍然在子项目之间进行单一构建,请使用父build.xml文件,这些文件会调用子项目。

如果不同的团队拥有不同的代码访问/提交权限,这种风格非常有效。风险在于,通过给予子项目额外的自由度,您最终可能会得到不兼容的源代码、库、构建流程,并最终增加您的工作量和集成问题。

保持对相当松散集成的项目集合的控制的唯一方法是拥有一个完全自动化的构建和测试流程,该流程验证所有内容仍然兼容。Sam Ruby 为所有 apache java 库运行了一个这样的流程,并在出现问题时向所有人发送电子邮件;您自己的项目可能能够使用 Cruise Control 来实现自动化的、持续的、后台构建流程。

集成子项目

紧密耦合的项目将所有源代码放在同一个树中;不同的项目拥有不同的子目录。构建文件可以移动到这些子目录(例如src/com/iseran/coresrc/com/iseran/extras),或者保留在顶部 - 具有独立的构建文件,命名为core.xmlextras.xml.

这种项目风格非常有效,如果每个人都相互信任,并且子项目不太庞大或复杂。风险在于,随着项目的进展,可能会需要拆分为更松散耦合的设计 - 但到意识到这一点时,进度压力和相互交织的构建文件使得执行拆分几乎不可能。如果发生这种情况,那就坚持下去,直到有时间重构项目目录结构。

Ant 更新策略

一旦您开始使用 Ant,您应该制定一个关于团队何时以及如何更新其副本的策略。一个简单的策略是 "在任何高压里程碑之后,所有不重要的任务(例如睡觉和看到阳光)都被推迟到后台之后,进行每次正式发布"。这可以保护您免受 Ant 在开发过程中经历的更改和偶尔的不稳定性的影响。它的主要缺点是它将您与 Ant 不断添加的新任务和功能隔离开来。

通常,更新需要更改build.xml文件。大多数更改旨在向后兼容,但有时需要进行不兼容的更改。这就是为什么在大型里程碑之后进行更新很重要的原因。这也是为什么包含ant.jar以及 CVS 树中的相关文件有助于确保您的软件旧版本仍然可以构建。

最激进的策略是每周或每天获取 Ant 源代码的快照,构建它并使用它。这会迫使您更频繁地调整build.xml文件,因为新任务和属性可能需要一段时间才能稳定。您真的必须想要新功能,享受无端的额外工作或乐于让同事感到不安才能采用这种方法。

一旦您开始使用新任务扩展 Ant,定期下载构建版本就会变得更有吸引力。最新的 Ant 构建版本始终是编写扩展的最佳平台,因为您可以利用对基础类的定期增强。它还可以防止您浪费时间在已经完成的事情上。一个新提交的任务,用于执行诸如与 EJB 引擎、SOAP 服务器通信或仅将文本文件转换为大写之类的复杂操作,可能正是您所需要的 - 所以接受它,增强它,并将增强功能提供给全世界。这当然比在 Ant 0.8 上孤立地开始您“文本大小写转换器”任务的工作要好,六个月后宣布它的存在,然后发现除了赞美之外,您得到的只是对现有实现的有用提示。参与该过程的最终好处是,它使您的任务更容易添加到 Ant CVS 树中,从而提前了 Ant 采用您为使项目正常运行而需要进行的所有更改的日期。如果发生这种情况,您可以恢复到官方的 Ant 版本,并继续处理所有其他危机。

您还应该加入 dev 邮件列表 ,因为它是其他开发人员发布他们的工作、问题和经验的地方。邮件量可能相当大:每天 40 多条消息,因此请考虑将其路由到您不经常使用的电子邮件地址。并且不要让团队中的每个人都订阅;这可能会造成过多的干扰。

使用 Ant 安装。

由于 Ant 可以读取环境变量、复制、解压缩和删除文件以及进行 Java 和操作系统调用,因此它可以用于简单的安装任务。例如,Tomcat 的安装程序可以提取环境变量TOMCAT_HOME,停止运行的 Tomcat,并将 war 文件复制到TOMCAT_HOME/webapps。它甚至可以再次启动 Tomcat,但构建不会在 Tomcat 退出之前完成,这可能不是预期的结果。

使用 Ant 的优势首先是相同的安装目标可以从您的本地构建文件中使用(通过ant调用install.xml文件),其次是基本安装目标很容易编写。这种方法的缺点是目标必须预先安装了最新版本的 Ant,并且 Ant 不允许您很好地处理故障 - 并且一个好的安装程序就是关于处理出现问题时的所有情况,从文件正在使用到 jar 版本不同。这意味着 Ant 不适合压缩软件,但它确实适用于部署和安装到您的本地服务器。

我参与的一个主要构建项目有一个用于 Bluestone 应用程序服务器的 Ant 安装构建文件,它会关闭一台机器上应用程序服务器的所有四个实例,将 war 文件的新版本(带有日期戳和构建戳)复制到存档目录,清理当前部署的 war 版本,然后安装新版本。由于 Bluestone 按需重新启动 JVM,因此此脚本是您进行 Web 服务部署所需的一切。在防火墙后面的系统上,我们通过使用 ftp 任务复制 war 和构建文件,然后使用 telnet 任务远程调用构建文件,从而提高了部署过程的难度。结果是我们实现了从 IDE(Jedit)或命令行内部自动重新编译和重新部署到本地服务器,这简直是无价的。想象一下,在您的 IDE 工具栏上按一个按钮来构建、单元测试、部署,然后对您的 Web 应用程序进行功能测试。

我后来添加的一个额外技巧是,一个 junit 测试用例用于运行安装检查列表。通过测试来验证网络驱动器上的访问权限、服务器之间的近似时钟同步、DNS 功能、生成可执行文件的能力以及所有其他故障点,安装脚本可以在安装时自动执行系统运行状况测试并报告问题。[相同的测试也可以从 JMX MBean 中调用,但那是另一个故事]。

因此,Ant 不是真正安装工具的替代品,除非是在您控制的服务器的特殊情况下,但在这种情况下,它确实允许您将远程安装与您的构建集成。

提示和技巧

get
The get 任务可以获取任何 URL,因此可以用于在构建过程中触发远程服务器端代码,从远程服务器重启到向开发人员手机发送短信/寻呼机消息。
i18n
国际化总是很麻烦。Ant 在这里提供了 native2ascii 任务,该任务可以将所有非 ASCII 字符转义为 Unicode。您可以使用它来编写包含字符串(甚至注释)的 Java 文件,这些字符串(甚至注释)使用您自己的非 ASCII 语言,然后使用 native2ascii 在通过 javac 之前将其转换为 ASCII。其余的 i18n 和 l12n 留给您处理...
使用属性文件
使用外部属性文件将每个用户的设置保留在构建文件之外 - 特别是密码。属性文件还可以用于根据单个属性的值动态设置多个属性,只需从源属性动态生成属性文件名即可。它们还可以用作多个构建文件之间常量的来源。
使用 Jikes 加速编译
The jikes compiler 通常比 javac 快得多,执行依赖项检查并具有更好的错误消息(通常)。获取它。然后设置build.compiler为“jikes”,以便在您的构建文件中使用它。在您的构建文件中明确执行此操作有点可疑,因为它要求整个团队(以及子项目)也使用 jikes - 这是您只能在小型封闭源代码项目中控制的事情。但是,如果您设置ANT_OPTS = -Dbuild.compiler=jikes在您的环境中,那么您系统上的所有构建都将自动使用 Jikes,而其他人可以选择自己的编译器,或者让 ant 选择适合当前 Java 版本的编译器。
#include 目标以简化多build.xml项目
您可以使用 XML 解析器本身将 XML 文件导入构建文件。这允许多项目开发程序通过引用共享代码,而不是通过剪切和粘贴来重复使用代码。它还允许您构建一个标准任务文件,这些任务文件可以随着时间的推移重复使用。由于导入机制处于 Ant 意识到的级别以下,因此将其视为等同于“传统”语言 C 和 C++ 的 #include 机制。

有两种包含机制,一种适用于所有解析器的丑陋方法,另一种是干净的方法。丑陋的方法是 Ant1.5 及更早版本中唯一可用的方法:-

    <!DOCTYPE project [
      <!ENTITY propertiesAndPaths SYSTEM "propertiesAndPaths.xml">
      <!ENTITY taskdefs SYSTEM "taskdefs.xml">
    ]>  
    
        &propertiesAndPaths;
        &taskdefs;
Ant1.6 中更干净的方法是<import>任务,它将整个构建文件导入其他项目。实体包含示例几乎可以替换为两个导入语句:-
 <import file="propertiesAndPaths.xml">
 <import file="taskdefs.xml">
我们说几乎,因为顶级声明(属性和 taskdefs)不会完全插入到 XML 文件中导入语句所在的位置,而是添加到文件末尾。这是因为导入过程发生在解析主构建文件之后,在执行期间,而 XML 实体扩展是在解析过程中处理的。

The<import>任务执行强大的操作,例如允许您覆盖目标,并使用 ant 属性来命名要导入的文件的位置。有关这些功能的详细信息,请参阅 文档

在您过度使用 XML 包含之前,请注意ant任务允许您调用任何其他构建文件中的任何目标 - 您的所有属性设置都会传播到该目标。因此,您实际上可以有一套实用程序目标 - "deploy-to-stack-a", "email-to-team", "cleanup-installation" 可以从您的任何主构建文件中调用,也许参数略有不同。实际上,在几个项目之后,您可能能够创建一个可重复使用的核心构建文件,其中包含基本 Java 开发项目的核心目标 - 编译、调试、部署 - 项目特定的构建文件使用它们自己的设置调用这些目标。如果您能做到这一点,那么您肯定正在软件成熟度阶梯上向上攀登。经过一番努力,您可能会从 SEI CMM 级别 0 组织“个人英雄主义还不够”发展到 SEI CMM 级别 1,“项目只有在个人英雄主义的情况下才能成功”

注意,ant复制所有属性,除非inheritall 属性设置为 false。在该属性存在之前,您必须仔细命名所有构建文件中的所有属性定义,以防止调用者无意中覆盖被调用者的属性,现在您只需要记住设置inheritall="false"在所有使用 <ant> 任务的地方。

通过 XSL 实现复杂的 Ant 构建
XSLT 可用于从源 xml 文件动态生成 build.xml 文件,其中 xslt 任务控制转换。这是当前推荐的用于动态创建复杂构建文件的策略。但是,它的使用似乎仍然很少见 - 这意味着您将处于技术的前沿。
更改调用脚本
通过编写您自己的调用脚本 - 使用 DOS、Unix 或 Perl 脚本作为起点 - 您可以修改 Ant 的设置和行为以适应单个项目。例如,您可以使用一个备用变量来ANT_HOME作为基础,以不同的方式扩展类路径,或动态创建一个新的命令行属性“project.interfaces" 来自所有.jar接口目录中的文件。

拥有一个自定义调用脚本,该脚本从PROJECT_HOME下的 CVS 控制的库树运行,还可以让您控制整个团队的 Ant 版本 - 开发人员可以拥有其他 Ant 副本(如果他们愿意),但 CVS 树始终包含用于构建项目的 jar 集。

您还可以编写包装脚本,这些脚本调用现有的 Ant 脚本。这是一种扩展它们的方法。包装脚本可以添加额外的定义和命名显式目标,重新定义ANT_HOME并通常使开发更容易。请注意,Windows 中的“ant”实际上是“ant.bat”,因此应该从另一个批处理文件中使用“CALL ant”语句调用 - 否则它永远不会返回到您的包装器。

编写所有代码,以便可以从 Ant 中调用它
这似乎有点奇怪和理想化,但它的意思是您应该编写所有 Java 代码,就好像它将来可能作为库被调用一样。因此,不要在代码深处放置对System.exit()的调用 - 如果您想退出几个函数,请改用抛出异常,并让main()处理它。

更进一步,考虑为代码提供一个 Ant 任务接口,作为功能的次要、主要甚至唯一的接口。Ant 实际上是一个很棒的 Java 应用程序引导程序,因为它处理类路径设置,并且您可以重复使用所有内置任务来进行序言和尾声工作。一些项目,例如 XDoclet 只能在 Ant 下运行,因为那是正确的位置。

使用 replace 任务以编程方式修改项目中的文本文件。
想象一下您的项目有一些源文件 - BAT 文件、ASPX 页面 (!),任何需要在编译时针对特定安装进行静态自定义的文件,例如从项目的某些属性驱动,例如 JVM 选项或指向错误的 URL。replace 任务可用于修改文件,替换文本并创建针对该构建或目标定制的版本。当然,针对目标的自定义应该延迟到安装时,但是如果您使用 Ant 进行远程安装,那么这突然变得可行。
使用邮件列表
有两个与 Ant 相关的 邮件列表,用户和开发者。Ant 用户是所有与使用 Ant 相关的问题应该去的地方。安装、语法、代码示例等 - 在那里发布您的问题或搜索档案以查看该查询是否已发布并回答过。Ant-developer 是 Ant 开发发生的地方 - 所以它不是发布诸如“我在构建项目时遇到编译错误”或“如何制作 zip 文件”之类内容的地方。另一方面,如果您实际上正在扩展 Ant,那么它是询问有关如何添加新任务、更改现有任务以及发布工作结果(如果您希望将其合并到 Ant 源代码树中)的理想场所。

将所有内容整合在一起

在这个世界上,Ant 构建过程是什么样的?假设为了简单起见,使用单个目录结构,构建文件应包含多个顶级目标子项目“web”、“bean-1”、“bean-2”可以被赋予自己的构建文件 -web.xml, bean-1.xml, bean-2.xml- 具有相同的入口点。如果数据库、网站图像等是流程的一部分,则应考虑与之相关的额外顶级任务。

调试/发布切换可以通过在编译任务之前调用的单独的初始化目标来处理,这些目标定义了适当的属性。Antcall 这里很关键,因为它允许您在构建文件中拥有两条属性初始化路径。

内部目标应用于构建流程

调试和发布之间的切换可以通过使 init-release 依赖于属性来完成,例如release.build被设置 :-
<target name="init-release" if="release.build">
    <property name="build.debuglevel" value="lines,source"/>    
  </target>
然后,您有依赖目标,例如“compile”,依赖于此条件目标;在那里设置“默认”属性,然后实际使用该属性。因为 Ant 属性是不可变的,如果执行了发布目标,它的设置将覆盖默认值
<target name="compile" depends="init,init-release">
    <property name="build.debuglevel" value="lines,vars,source"/> 
    <echo>debug level=${build.debuglevel}</echo>
    <javac destdir="${build.classes.dir}"
       debug="true"
       debuglevel="${build.debuglevel}"
       includeAntRuntime="false"
       srcdir="src">
      <classpath refid="compile.classpath"/>
    </javac>
  </target>
因此,我们现在有一个构建,其中发布模式仅包含文件名和行调试信息(对错误报告很有用),而开发系统也包含变量。

定义一个项目名称属性很有用,它可以在 init 任务中回显。这可以让您在多文件构建中找出哪个 Ant 文件出现故障。

内部 Ant 任务中包含的内容取决于您自己的项目。一个非常重要的策略是“通过引用保持路径重新定义向下传递” - 您可以通过为路径指定 ID 然后通过“refid”属性引用它们来重复使用路径,您只需要在文件中定义一次共享类路径;文件集可以以类似的方式重复使用。

设置好目录结构并定义好 Ant 任务后,就可以开始编码了。首要任务必须是设置自动化测试流程,因为它不仅有助于确保代码正常工作,而且还验证构建流程是否正常工作。

就是这样。构建文件不需要随着新源文件的添加而更改,只有当您想要更改交付内容或构建过程的一部分时才需要更改。在某些时候,您可能希望彻底重构整个构建过程,重构项目等,但即使那样,您拥有的构建文件也应该作为拆分构建文件过程的基础 - 只需将通用属性提取到所有构建文件读取的属性文件中,保持目标名称统一,并继续进行项目。重构源代码控制系统通常要困难得多。

Ant 的局限性

在您开始将 Ant 作为构建过程的唯一机制之前,您需要了解它不能做什么。

它不是脚本语言

Ant 允许您声明您想要完成的操作,首先对平台和类库进行一些测试,以使一些特定于平台的构建能够进行。它不允许您指定如何处理出现错误的情况(监听器类可以做到这一点),也不支持复杂的条件语句。

如果您的构建需要处理异常,请查看声音监听器,它是一个关于如何编写自己的监听器类的简单示例。复杂的条件语句可以通过让其他东西进行测试,然后构建相应的 Ant 任务来处理。XSLT 可用于此目的。

它不是 Make

Make 的一些功能,特别是推断规则和依赖项检查,Ant 中没有包含。这是因为它们是“不同”的构建方式。Make 需要您声明依赖项和构建步骤,Ant 希望您声明任务及其之间的顺序,任务本身可以进行依赖项检查或不进行检查。使用 Jikes 的完整 Java 构建速度非常快,因此依赖项检查相对来说无关紧要,而许多其他任务(但并非全部)在采取行动之前会比较源文件的最后修改时间与目标文件的最后修改时间。

它不是为人类设计的友好语言

XML 对于人类来说不是一种友好的信息表示方式。它对于程序来说是一种合理的表示方式,文本编辑器和源代码管理系统都可以很好地处理它。但是,一个复杂的 Ant 文件可能会变得很丑陋,因为 XML 有点丑陋,而一个复杂的构建本身就很复杂。使用 XML 注释,这样您上个月编写的文件在您回来时仍然有意义,并且如果您喜欢,请使用适当的 xml 编辑器来编辑文件。

大型项目仍然会很快变得复杂

大型软件项目会产生自己的复杂性,包括相互依赖的库、漫长的测试周期、困难的部署过程以及许多人都在处理解决方案的各自部分。这甚至是在截止日期临近之前,集成问题变得无法克服,周末在工作量方面与工作日没有区别,并且一半的团队停止与另一半团队交谈。Ant 可能会简化构建和测试过程,并且可以消除全职的“makefile 工程师”角色,但这并不意味着有人可以停止“拥有构建”。负责构建意味着比他们在系统上键入“ant all”更多,这意味着他们需要设置使用哪些构建工具的标准、常见的目标、属性名称和文件应该是什么,以及通常监督子项目的构建过程。在一个小型项目中,您不需要这样做 - 但请记住:当您没有注意时,小型项目会变成大型项目。如果您从一开始就进行一些流程,那么您可以在需要时进行扩展。如果您从一开始就没有,那么当您需要它时,就为时已晚。

您仍然需要软件项目的所有其他基础部分

如果您没有源代码管理系统,您将最终陷入困境。如果您没有将所有内容都放在 SCM 下,包括网页、依赖的 jar、安装文件,您仍然会最终陷入困境,这只是一个时间问题。CVS 实际上是免费的,并且与 Ant 配合良好,但 Sourcesafe、Perforce、Clearcase 和 StarTeam 也具有 Ant 任务。这些任务允许您拥有自动递增的构建计数器以及自动文件更新过程。

您还需要某种变更控制流程来抵制不受控制的功能蔓延。Bugzilla 是一个简单且低成本的工具,使用 Ant 和持续测试流程可以快速演化代码以适应那些不可避免的更改。

结尾

软件开发应该是有趣的。在紧迫的项目中,在集成的漩涡中,试图为疯狂的截止日期编写所有代码,这可能是很有趣的 - 当然令人振奋。在流程中添加一些自动化可能会使事情不那么混乱,也可能不那么有趣,但这是让您控制开发流程的开始。您仍然可以享受乐趣,只是应该少担心,构建/测试/部署周期更短,有更多时间花在功能蔓延或滑雪等重要的事情上。所以,出去玩得开心吧!

进一步阅读

关于作者

Steve Loughran 是一位企业研发实验室的研究科学家,目前正在休假,为了乐趣而构建在不切实际的截止日期下运行的生产 Web 服务。他也是 Apache Ant 和 Apache Axis 的贡献者,以及《Java Development with Ant》一书的合著者。他认为,如果你喜欢这份文档,你一定会喜欢那本书,因为它不仅解释了 Ant,还深入探讨了流程、部署、最佳实践以及其他真正使 Ant 有用的内容。(仅仅重新整理手册会更容易,但那不会那么有用,也不会那么有趣)。

对于与本文件相关的疑问,请使用 Ant 邮件列表。