首先,这里有一些关于本文档涵盖的项目的假设
Ant 也是 IDE 的一个很好的补充;一种执行所有部署家务和进行干净、自动构建的方法。但是一个好的现代 IDE 本身就是一个生产力工具 - 你应该继续使用它。Ant 只是让你在 IDE 选择方面给予团队更多自由 - “你可以在开发中使用任何你想要的,但部署构建使用 Ant”。现在,许多现代开源和商业 IDE 都包含 Ant 支持(包括 jEdit、Forte、Eclipse 和 IDEA),开发人员可以使用一个很棒的 IDE,Ant 提供一个严格且可移植的构建过程,集成到该工具中。
使用标准目标名称,可以轻松构建包含 Ant 构建文件的构建文件,这些构建文件只是使用 ant 任务将工作传递给下面的类。例如,clean 目标可以从父目录传递给intf和impl子目录
<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 允许你调用 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 的常见障碍是使用不可移植的命令行工具(exec 任务)、路径问题以及对事物位置的硬编码。
命令行调用任务都允许你指定希望代码运行的平台,因此你可以为每个目标平台编写不同的任务。或者,可以在 Ant 调用的某些外部代码中处理平台差异。这可以是某个编译后的 Java 代码,作为新任务,也可以是外部脚本文件。
平台之间的这种差异(实际上,整个 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 上正确执行。可以执行此操作的任务 - 例如 Get、Touch 和 Unjar/Unwar/Unzip,在 Java1.1 上会降低其功能,通常会使用当前时间戳代替。
最后,Perl 是一个很好的地方,可以跨平台包装 Java 调用,而不是批处理文件。它包含在大多数 Unix 发行版中,并且可以从 ActiveState 的 Win32 平台 上简单下载。一个带有.pl扩展名的 Perl 文件,第一行注释中包含通常的 Unix perl 路径,并且标记为可执行文件,可以在 Windows、OS/2 和 Unix 上运行,因此可以从 Ant 中调用而不会出现问题。Perl 代码可以自行解决其平台问题。不要忘记在重新分发 Perl 代码时将文件的行尾设置到相应的平台;fixCRLF 可以为您做到这一点。
另一个好策略是使用统一的目录树,并在该树中添加额外的工具。所有引用都可以相对于该树进行。如果团队成员需要在项目中添加一个目录到他们的路径中,那么命令行工具可以包含在那里 - 包括那些由 Ant exec 任务调用的工具。将所有内容放在源代码控制下,您就可以从 CVS 或等效工具中获得一个构建/执行环境的一站式商店。
Ant 可以 Jar、Tar 或 Zip 用于部署的文件,而 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 应用程序的任务。
最后,当然还有使用 Copy 和 Copydir 将文件复制到目标位置,或者使用 Mail 或支持附件的 MimeMail 将文件发送给人员或进程的备用方案。在一个项目中,我们的团队甚至使用 Ant 通过构建和一系列 Copy 任务来构建 CD 镜像,这出奇地有效,当然比我们将其发送到 myrealbox.com 上的免费电子邮件服务,然后从远端的 Web 浏览器中拉取它们要容易得多,而我们是在 WinNT 远程桌面连接上运行的,该连接通过 SSH 隧道连接。
bin | 通用二进制文件、脚本 - 将其放在路径中。 |
build | 这是构建的树;Ant 创建它,并且可以在 "clean" 项目中清空它。 |
dist | 分发输出放在这里;该目录是在 Ant 中创建的,clean 会清空它。 |
doc | 手工制作的文档 |
lib | 导入的 Java 库放在这个目录中 |
src | 源代码放在这个树下 在与包名匹配的层次结构中。<javac> 的依赖规则要求这样做。 |
Javadoc 输出可以定向到doc/文件夹在build/之下,或者到doc/javadoc.
为了仍然在子项目之间进行单一构建,请使用父build.xml文件,这些文件会调用子项目。
如果不同的团队拥有不同的代码访问/提交权限,这种风格非常有效。风险在于,通过给予子项目额外的自由度,您最终可能会得到不兼容的源代码、库、构建流程,并最终增加您的工作量和集成问题。
保持对相当松散集成的项目集合的控制的唯一方法是拥有一个完全自动化的构建和测试流程,该流程验证所有内容仍然兼容。Sam Ruby 为所有 apache java 库运行了一个这样的流程,并在出现问题时向所有人发送电子邮件;您自己的项目可能能够使用 Cruise Control 来实现自动化的、持续的、后台构建流程。
这种项目风格非常有效,如果每个人都相互信任,并且子项目不太庞大或复杂。风险在于,随着项目的进展,可能会需要拆分为更松散耦合的设计 - 但到意识到这一点时,进度压力和相互交织的构建文件使得执行拆分几乎不可能。如果发生这种情况,那就坚持下去,直到有时间重构项目目录结构。
通常,更新需要更改build.xml文件。大多数更改旨在向后兼容,但有时需要进行不兼容的更改。这就是为什么在大型里程碑之后进行更新很重要的原因。这也是为什么包含ant.jar以及 CVS 树中的相关文件有助于确保您的软件旧版本仍然可以构建。
最激进的策略是每周或每天获取 Ant 源代码的快照,构建它并使用它。这会迫使您更频繁地调整build.xml文件,因为新任务和属性可能需要一段时间才能稳定。您真的必须想要新功能,享受无端的额外工作或乐于让同事感到不安才能采用这种方法。
一旦您开始使用新任务扩展 Ant,定期下载构建版本就会变得更有吸引力。最新的 Ant 构建版本始终是编写扩展的最佳平台,因为您可以利用对基础类的定期增强。它还可以防止您浪费时间在已经完成的事情上。一个新提交的任务,用于执行诸如与 EJB 引擎、SOAP 服务器通信或仅将文本文件转换为大写之类的复杂操作,可能正是您所需要的 - 所以接受它,增强它,并将增强功能提供给全世界。这当然比在 Ant 0.8 上孤立地开始您“文本大小写转换器”任务的工作要好,六个月后宣布它的存在,然后发现除了赞美之外,您得到的只是对现有实现的有用提示。参与该过程的最终好处是,它使您的任务更容易添加到 Ant CVS 树中,从而提前了 Ant 采用您为使项目正常运行而需要进行的所有更改的日期。如果发生这种情况,您可以恢复到官方的 Ant 版本,并继续处理所有其他危机。
您还应该加入 dev 邮件列表 ,因为它是其他开发人员发布他们的工作、问题和经验的地方。邮件量可能相当大:每天 40 多条消息,因此请考虑将其路由到您不经常使用的电子邮件地址。并且不要让团队中的每个人都订阅;这可能会造成过多的干扰。
使用 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 不是真正安装工具的替代品,除非是在您控制的服务器的特殊情况下,但在这种情况下,它确实允许您将远程安装与您的构建集成。
有两种包含机制,一种适用于所有解析器的丑陋方法,另一种是干净的方法。丑陋的方法是 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> 任务的地方。
拥有一个自定义调用脚本,该脚本从PROJECT_HOME下的 CVS 控制的库树运行,还可以让您控制整个团队的 Ant 版本 - 开发人员可以拥有其他 Ant 副本(如果他们愿意),但 CVS 树始终包含用于构建项目的 jar 集。
您还可以编写包装脚本,这些脚本调用现有的 Ant 脚本。这是一种扩展它们的方法。包装脚本可以添加额外的定义和命名显式目标,重新定义ANT_HOME并通常使开发更容易。请注意,Windows 中的“ant”实际上是“ant.bat”,因此应该从另一个批处理文件中使用“CALL ant”语句调用 - 否则它永远不会返回到您的包装器。
更进一步,考虑为代码提供一个 Ant 任务接口,作为功能的次要、主要甚至唯一的接口。Ant 实际上是一个很棒的 Java 应用程序引导程序,因为它处理类路径设置,并且您可以重复使用所有内置任务来进行序言和尾声工作。一些项目,例如 XDoclet 只能在 Ant 下运行,因为那是正确的位置。
调试/发布切换可以通过在编译任务之前调用的单独的初始化目标来处理,这些目标定义了适当的属性。Antcall 这里很关键,因为它允许您在构建文件中拥有两条属性初始化路径。
内部目标应用于构建流程
<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 任务来处理。XSLT 可用于此目的。
您还需要某种变更控制流程来抵制不受控制的功能蔓延。Bugzilla 是一个简单且低成本的工具,使用 Ant 和持续测试流程可以快速演化代码以适应那些不可避免的更改。
对于与本文件相关的疑问,请使用 Ant 邮件列表。