在Ant出现之前Q构建和部vJava应用需要用包括特定^台的脚本、Make文g、各U版本的IDE甚至手工操作的“大杂烩”。现在,几乎所有的开源Java目都在使用AntQ大多数公司的内部项目也在用Ant。Ant在这些项目中的广泛用自然导致了读者对一整套Ant最佛_늚q切需求?/p>
本文ȝ了我喜爱的Ant技巧或最佛_践,多数是从我亲w经历的目错误或我听说的其他hl历?“恐怖”故事中得到灉|的。比如,有h告诉我有个项目把XDoclet 生成的代码放入带有锁定文件功能的版本控制工具中。当开发者修Ҏ代码Ӟ他必记住手工检出(Check outQƈ锁定所有将要重新生成的文g。然后,手工q行代码生成器,只到q时他才能够让Ant~译代码Q这一Ҏq存在如下一些问题:
- 生成的代码无法存储在版本控制pȝ中?
- AntQ本案例中是XdocletQ应该自动确定下一ơ构建涉及的源文Ӟ而不应由E序员手工确定?
- Ant的构建文件应该定义好正确的Q务依赖关p,q样E序员就不必Z完成构徏而不得不按照特定序调用d?
当我开始一个新目Ӟ我首先编写Ant构徏文g。Ant文g明确地定义构建的q程Qƈ被团队中的每个程序员使用。本文所列的技巧基于这L假定QAnt构徏文g是一个必Ml编写的重要文gQ它应在版本控制pȝ中得到维护,q被定期q行重构。下面是我的十五大Ant最佛_c?/p>
1. 采用一致的~码规范
Ant用户有的喜欢有的痛恨其构建文件的XML语法。与其蟩q这一令hqh的争ZQ不如让我们先看一些能保持XML构徏文gz的Ҏ?/p>
首先也是最重要的,p旉格式化你的XML让它看上d清晰。不论XML是否观QAnt都可以工作。但是丑陋的XML很难令hL。倘若你在d之间留出IQ有规则的羃q,每行文字不超q?0列左叻I那么XML令h惊讶地易诅R再加上使用能够高亮XML语法的优U~辑器或IDE工具Q你׃会有阅读的麻烦?/p>
同样Q精选含意明、容易读懂的词汇来命名Q务和属性。比如,dir.reports比rpts?em>?/em>特定的编码规范ƈ不重要,只要拿出一套规范ƈ坚持使用p?/p>
2. ?em>build.xml攑֜目根目录中
Ant构徏文gbuild.xml可以攑֜M位置Q但是放在项目顶U目录中可以保持目z。这是最常用的规范,开发者能够在目录中找到预期的build.xml。把构徏文g攑֜根目录中Q也能够使hҎ了解目目录树中不同目录之间的逻辑关系。以下是一个典型的目目录层次Q?/p>
[root dir]
| build.xml
+--src
+--lib (包含W三?JAR?
+--build (?buildd生成)
+--dist (?buildd生成)
?em>build.xml在顶U目录时Q假设你处于目某个子目录中Q只要输入:ant -find compile 命oQ不需要改变工作目录就能够以命令行方式~译代码。参?find告诉AntL存在于上U目录中?em>build.xmlq执行?/p>
3. 使用单一的构建文?/h3>
有h喜欢一个大目分解成几个小的构建文Ӟ每个构徏文g分担整个构徏q程的一部分工作。这实是看法不同的问题Q但是应该认识到Q将构徏文g分割会增加对整体构徏q程的理解难度。要注意在单一构徏文g能够清楚表现构徏层次的情况下不要q工E化(over-engineer)?/p>
即你把目划分为多个构建文Ӟ也应使程序员能够在项目根目录下找到核?em>build.xml。尽该文g只是实际构建工作委z下构徏文gQ也应保证该文g可用?/p>
4. 提供良好的帮助说?/h3>
应尽量构徏文g自文档化。增加Q务描q是最单的Ҏ。当你输入ant -projecthelpӞ你就可以看到带有描述的Q务清单。比如,你可以这样定义Q务:
<target name="compile"description="Compiles code, output goes to the build dir.">
最单的规则是把所有你惌E序员通过命o行就可以调用的Q务都加上描述。对于一般用来执行中间处理过E的内部dQ比如生成代码或建立输出目录{,无法用描q属性?/p>
q时Q可以通过在构建文件中加入XML注释来处理。或者专门定义一个helpdQ当E序员输入ant help时来昄详细的用说明?/p><target name="help" description="Display detailed usage information">
<echo>Detailed help...</echo></target>
5. 提供清除d
每个构徏文g都应包含一个清除Q务,用来删除所有生成的文g和目录,使系l回到构建文件执行前的初始状态。执行清IZQ务后q存在的文g都应处在版本控制pȝ的管理之下。比如:
<target name="clean"description="Destroys all generated files and dirs.">
<delete dir="${dir.build}"/>
<delete dir="${dir.dist}"/>
</target>
除非是在产生整个pȝ版本的特DQ务中Q否则不要自动调用cleand。当E序员仅仅执行编译Q务或其他dӞ他们不需要构建文件事先执行既令h讨厌又没有必要的清空d。要怿E序员能够确定何旉要清I所有文件?/p>
6. 使用ANT理d从属关系
假设你的应用由Swing GUIlg、Web界面、EJB层和公共应用代码l成。在大型pȝ中,你需要清晰地定义每个Java包属于系l的哪一层。否则Q何一点修攚w要被q重新编译成百上千个文g。糟p的d从属关系理会导致过度复杂而脆qpȝ。改变GUI面板的设计不应造成Servlet和EJB的重~译?/p>
当系l变得庞大后Q稍不注意就可能依赖于客户端的代码引入到服务端。这是因为典型的IDE目文g~译M文g都用单一的classpath。而Ant能让你更有效地控制构建活动?/p>
设计你的Ant构徏文g~译大型目的步骤:首先Q编译公共应用代码,编译结果打成JAR包文件。然后,~译上一层的目代码Q编译时依靠W一步生的JAR文g。不断重复这一q程Q直到最高层的代码编译完成?/p>
分步构徏强化了Q务从属关pȝ理。如果你工作在底层Java框架上,偶然引用到高层的GUI模板lgQ这时代码不需要编译。这是由于构建文件在~译底层框架时在源\径中没有包含高层GUI面板lg的代码?/p>
7. 定义q用文件\?/h3>
如果文g路径在一个地方一ơ性集中定义,q在整个构徏文g中得到重用,那么构徏文g更易于理解。以下是q样做的一个例子:
<project name="sample" default="compile" basedir="."><path id="classpath.common">
<pathelement location="${jdom.jar.withpath}"/>
...etc </path>
<path id="classpath.client">
<pathelement location="${guistuff.jar.withpath}"/>
<pathelement location="${another.jar.withpath}"/>
<!-- reuse the common classpath -->
<path refid="classpath.common"/>
</path>
<target name="compile.common" depends="prepare">
<javac destdir="${dir.build}" srcdir="${dir.src}">
<classpath refid="classpath.common"/>
<include name="com/oreilly/common/**"/>
</javac>
</target>
</project>
当项目不断增长构建日益复杂时Q这一技术越发体现出其h倹{你可能需要ؓ~译不同层次的应用定义各自的文g路径Q比如运行单元测试的、运行应用程序的、运行Xdoclet的、生成JavaDocs的等{不同\径。这U组件化路径定义的方法比为每个Q务单独定义\径要优越得多。否则,很容易丢׃Q务从属关pȝ轨迹?/p>
8. 定义恰当的Q务从属关p?/h3>
假设distd从属于jardQ那么哪个Q务从属于compiled哪个d从属于prepared呢?Ant构徏文g最l定义了d的从属关pdQ它必须被仔l地定义和维护?/p>
应该定期查Q务的从属关系以保证构建工作得到正执行。大的构建文仉着旉推移向于增加更多的dQ所以到最后可能由于不必要的从属关pd致构建工作非常困难。比如,你可能发现在E序员只需~译一些没有用EJB的GUI代码时又重新生成了EJB代码?/p>
以“优化”的名义忽略d的从属关pL另一U常见的错误。这U错误迫使程序员Z得到恰当的结果必记住ƈ按照特定的顺序调用一串Q务。更好的做法是:提供描述清晰的公׃Q务,q些d包含正确的Q务从属关p;另外提供一套“专家”Q务让你能够手工执行个别的构徏步骤Q这些Q务不提供完整的构E,但是让那些专家用户在快速而恼人的~码期间能够跌某些步骤?/p>
9.使用属?/h3>
M需要配|或可能发生变化的信息都应作为Ant属性定义下来。对于在构徏文g中多ơ出现的g同样处理。属性既可以在构建文件头部定义,也可以ؓ了更好的灉|性而在单独的属性文件中定义。以下是在构建文件中定义属性的样式Q?/p><project name="sample" default="compile" basedir=".">
<property name="dir.build" value="build"/>
<property name="dir.src" value="src"/>
<property name="jdom.home" value="../java-tools/jdom-b8"/>
<property name="jdom.jar" value="jdom.jar"/>
<property name="jdom.jar.withpath"
value="${jdom.home}/build/${jdom.jar}"/>
etc...
</project>
或者你可以使用属性文Ӟ
<project name="sample" default="compile" basedir="."><property file="sample.properties"/>
etc...
</project>
在属性文?sample.properties?
dir.build=builddir.src=src
jdom.home=../java-tools/jdom-b8
jdom.jar=jdom.jarjdom.jar.withpath=${jdom.home}/build/${jdom.jar}
用一个独立的文g定义属性是有好处的Q它可以清晰地定义构Z的可配置部分。另外,在开发者工作在不同操作pȝ的情况下Q你可以在不同的q_上提供该文g的不同版本?/p>
10. 保持构徏q程独立
Z最大限度的扩展性,不要应用外部路径和库文g。最重要的是不要依赖于程序员的CLASSPATH讄。取而代之的是,在构建文件中使用相对路径q定义自q路径。如果你引用了绝对\径如C:\java\toolsQ其他开发者未必用与你相同的目录l构Q所以就无法使用你的构徏文g?/p>
如果你部|开放源码项目,应该提供包含~译代码所需的所有JAR文g的发行版本。当Ӟq是在遵守许可协议的基础上。对于内部项目,相关的JAR文g都应在版本控制系l的理中,q捡出(check outQ到大家都知道的位置?/p>
当你必须引用外部路径Ӟ应将路径定义为属性。ɽE序员能够用适合他们自己的机器环境的参数重蝲q些属性。你也可以用以下语法引用环境变量:
<property environment="env"/><property name="dir.jboss" value="${env.JBOSS_HOME}"/>
11. 使用版本控制pȝ
构徏文g是一个重要的制品Q应该像代码一栯行版本控制。当你标C的代码时Q也应用同样的标{标记构建文件。这样当你需要回溯到旧版本ƈq行构徏Ӟ能够使用相应版本的构建文件?/p>
除构建文件之外,你还应在版本控制中维护第三方JAR文g。同Pq你能够重新构建旧版本的Y件。这也能够更Ҏ保证所有开发者拥有一致的JAR文gQ因Z们都是同构徏文g一起从版本控制pȝ中捡出的?/p>
通常应避免在版本控制pȝ中存放构建成果。倘若你的源代码很好地得到了版本控Ӟ那么通过构徏q程你能够重新生成Q何版本的产品?/p>
12. 把Ant作ؓ“最公分母?/h3>
假设你的开发团队用IDE工具Q当E序员通过点击图标p够构建整个应用时Z么还要ؓAnt而烦恼呢Q?/p>
IDE的问题是一个关于团队一致性和重现性的问题。几乎所有的IDE设计初衷都是Z提高E序员的个h生率,而不是开发团队的持箋构徏。典型的IDE要求每个E序员定义自q目文g。程序员可能拥有不同的目录结构,可能使用不同版本的库文gQ还可能工作在不同的q_上。这导致出现这U情况:在Bob那里q行良好的代码,到Sally那里无法运行?/p>
不管你的开发团队用何UIDEQ一定要建立所有程序员都能够用的Ant构徏文g。要建立一个程序员在将C码提交版本控制系l前必须执行Ant构徏文g的规则。这确保代码是l过同一个Ant构徏文g构徏的。当出现问题Ӟ要用项目标准的Ant构徏文gQ而不是通过某个IDE来执行一个干净的构建?/p>
E序员可以自由选择M他们习惯使用的IDE工具或编辑器。但是Ant应作为公共基U以保证代码永远是可构徏的?/p>
13. 使用zipfileset属?/h3>
Zl常使用Ant产生WAR、JAR、ZIP?EAR文g。这些文仉常都要求有一个特定的内部目录l构Q但其往往与你的源代码和编译环境的目录l构不匹配?/p>
一个最常用的方法是写一个AntdQ按照期望的目录l构把一大堆文g拯C时目录中Q然后生成压~文件。这不是最有效的方法。用zipfileset属性是更好的解x案。它让你从Q何位|选择文gQ然后把它们按照不同目录l构放进压羃文g中。以下是一个例子:
<ear earfile="${dir.dist.server}/payroll.ear"appxml="${dir.resources}/application.xml">
<fileset dir="${dir.build}" includes="commonServer.jar"/>
<fileset dir="${dir.build}">
<include name="payroll-ejb.jar"/>
</fileset>
<zipfileset dir="${dir.build}" prefix="lib">
<include name="hr.jar"/>
<include name="billing.jar"/>
</zipfileset>
<fileset dir=".">
<include name="lib/jdom.jar"/>
<include name="lib/log4j.jar"/>
<include name="lib/ojdbc14.jar"/>
</fileset>
<zipfileset dir="${dir.generated.src}" prefix="META-INF">
<include name="jboss-app.xml"/>
</zipfileset>
</ear>
在这个例子中Q所有JAR文g都放在EAR文g包的lib目录中。hr.jar和billing.jar是从构徏目录拯q来的。因此我们用zipfileset属性把它们Ud到EAR文g包内部的lib目录。prefix属性指定了其在EAR文g中的目标路径?/p>
14. 试Cleand
假设你的构徏文g中有clean和compile的Q务,执行以下的测试。第一步,执行ant cleanQ第二步Q执行ant compileQ第三步Q再执行ant compile。第三步应该不作M事情。如果文件再ơ被~译Q说明你的构建文件有问题?/p>
构徏文g应该只在与输出文件相兌的输入文件发生变化时执行d。一个构建文件在不必执行诸如~译、拷贝或其他工作d的时候执行这些Q务是低效的。当目规模增长Ӟ即是小的低效工作也会成为大的问题?/p>
15. 避免特定q_的Ant装
不管什么原因,有h喜欢用简单的、名U叫?em>compile之类的批文g或脚本装载他们的产品。当你去看脚本的内容你会发现以下内容Q?/p>
ant compile
其实开发h员都很熟悉AntQƈ且完全能够自己键入ant compile。请不要仅仅Z调用Ant而用特定^台的脚本。这只会使其他h在首ơ用你的脚本时增加学习和理解的烦扰。除此之外,你不可能提供适用于每个操作系l的脚本Q这是真正烦扰其他用L地方?/p>
ȝ
太多的公怾靠手工方法和特别E序来编译代码和生成软g发布版本。那些不使用Ant或类似工具定义构E的开发团队,p了太多的旉来捕捉代码编译过E中出现的问题:在某些开发者那里编译成功的代码Q到另一些开发者那里却p|了?/p>
生成q维护构本不是一富有魅力的工作Q但却是一必需的工作。一个好的Ant构徏文g你能够集中到更喜Ƣ的工作——写代码中去Q?/p>
参?/h3>- Ant
- AntGraph: Ant依赖性的可视化工?
- Ant: The Definitive Guide, O'Reilly
- Java Extreme Programming Cookbook, O'Reilly
Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=675230