weidagang2046的专栏

物格而后知致
随笔 - 8, 文章 - 409, 评论 - 101, 引用 - 0
数据加载中……

用Ant构建

 尽管编程--编写和编译代码--是软件开发中最明显的部分,但并不意味着就是唯一的部分。就像你已经看到过的,测试也很重要。但交付一个完成的产品还需要更多的步骤:你还需要文档,打包,以及部署你所开发的软件。手工进行这些工作是烦闷的、重复性的,还容易出错。这是个迫切需要自动化进行的任务。

5.1 需要一个正式的构建过程


  你已经经历了测试--编码,测试--编码的循环,并最终得到了一些人们可以实际使用的东西。那么该如何交付该功能呢?
  下一步就是要以确切的词汇来标明这个产品到底是什么。它是一个API吗?还是一个具有图形界面的可执行程序?它是被打包在一个JAR文件中吗?它是否包含文档?示例代码呢?是否所有这些东西都要刻录在一张光盘上?还是要压缩在一块儿以便通过因特网上交付使用?
  一旦你作出了决定,Eclipse会简化这些步骤的执行。在你保存了源代码并编译了所有东西之后,就能立刻运行单元测试,用Javadoc向导创建文档(假设这对于你的读者来说足够了),把你的类导出到一个JAR文件,并把所有的东西放到一个p文件中。如果你只是要非正式地把你正在做的游戏的最新版本发给一个朋友,这个过程是不错的,但如果你要把一个产品交付给一个客户或公众,你是不会这么做的。有太多的事情会出错:忘了刷新源码树,忘了运行单元测试,没注意到一个失败的测试,生成一个Javadoc时选择了错误的选项,漏拷了源码文件,打错了p文件名--可能的问题是无穷无尽的。当从事该项目的人不止一个时,这些问题会变得更加复杂。
  进行一个独立的构建过程的主要原因之一是可重复性。最理想的是,你希望能够只需在一台工作站打入一条命令,机器就会搅拌一会儿它的硬盘,并且在最后(如果一切顺利并且成功运行了单元测试),吐出一个p文件或者一张光盘。减少了人工干预也就减少了构建过程中出错的几率,并且任何发生的错误都可以被持续改正。
  因为使用图形界面来自动化过程比较困难,构建过程通常设计为在命令行下运行。那样的话,通过使用一条简单的命令,或者使用Unix/Linux的chron命令或Windows的at命令设置每天的指定时间自动进行,就可以摆脱这个问题。
  使用外部的、独立的构建过程的额外好处是,它让开发者能够自由使用自己选择的开发环境。这看起来不像是一个紧迫的需求,但是强迫开发者--尤其是那些很有经验的--放弃他们已经精通的工具,对生产力是有害的。

5.1.1 创建构建目录结构

  在我们查看特定的构建工具之前,让我们来考虑一下这个任务:利用你在前面的章节中作为示例开始开发的基于文件的持久组件(Persistence),计划和组织构建过程。想象一下,由于某些原因,你(或你的主管)决定将该产品实用化。规范化构建过程的第一步就是组织你的文件,以便能够清晰的区分源代码和其他资源,临时文件,以及可交付文件。

5.1.2 分离源码和构建目录

  在你创建了persistence对象之后,你没有停下来考虑目录结构。你简单的接受了Eclipse提供的默认配置:项目目录等同于源码目录。这种结构的结果是,源码文件、测试类、编译好的类文件都混在一起了:

\persistence
  \org
    \eclipseguide
      \astronomy
        *.class
        *.java

          \persistence
            *.class
            *.java

  因为在源码文件和生成文件之间没有明确的界限,在开始一个新构建之前,删除生成文件就需要你在某些目录中删除某些文件。这不难,但使用一个独立的目录更加简单可靠,这样你就能在开始构建之前把整个目录弄走--这就避免了过期的文件意外地弄糟了构建过程,从而导致莫名其妙的错误的可能性。
  与混合源码文件和类文件相比,一个更好的设计是在项目目录中,为两者创建单独的目录,像这样:

\persistence
  \src
    \org
      \eclipseguide
        \astronomy
          *.java
        \persistence
          *.java
  \bin
    \org
      \eclipseguide
        \astronomy
          *.class
        \persistence
          *.class

  Eclipse在项目结构上十分灵活。如果你一开始就已经知道你需要一个正式构建过程,当你创建项目时就已经能指定src作为源码目录。那你就不会在输入名称后立即点[完成]来接受默认配置,而可以点[下一步]前进到另一个对话框,那里提供了选项以增加一个新的源码目录;在[源]页面上点击[添加文件夹]就可以创建新的文件夹名(参看图5.1)。

图5.1 新的源码文件夹。你可以在创建一个新项目时定义一个单独的源码文件夹,然后Eclipse会帮助建立一个单独的输出文件夹。


  但别担心--你不必全部倒回去重新照做一遍。所谓敏捷开发指的就是增量开发--按部就班,并且如果没有好的理由,绝不以吃力的方式做任何事情。就像你至今可能预想的那样,Eclipse轻松地完成这些变换。你所需做的一切只是新建一个源码文件夹(src)并把你的源码移动到里面。由Eclipse负责建立新的独立的输出文件夹(bin)及其子目录。(此外,当你用Eclipse构建时,它会将所有的class文件放置到正确的位置。)
  在Java透视图中,在项目名称(Persistence)上右击鼠标,选择[新建]->[源码文件夹],然后输入 src 作为文件夹名(参看图5.2)。注意对话框顶部的注释:为避免重复,现有的项目源码文件夹入口将被替换,并且输出文件夹将被设置为'Persistence/bin'。这正是你所想要的,那么就点击[完成]。(如果你不想把输出文件夹命名为bin--比如说想把它命名为build--你可以在稍后通过从项目的快捷菜单中选择[属性]->[Java 构建路径]->[源]来改变缺省的输出文件夹。)

图5.2 将一个源码文件夹添加到一个现存的项目中。Eclipse自动创建一个新的输出目录。


  现在,要把源码文件移动到新的src目录中,右键点击顶层源码目录(org),选择[重构]->[移动],并选择Persistence下面的src目录(参看图5.3)。(你可以安全地忽略那个警告:到缺省包的引用将不会被更新。)或者--甚至更简单--在包浏览器中点击org文件夹并拖动到src文件夹上。

图5.3 更多的重构:将源码树移动到新的src目录。


5.1.3 为可发布文件创建一个目录

  创建一个独立的输出目录是一个好的开始,但不是发布软件的全部。一堆目录里面的一堆类不是一个产品。你需要决定要将哪一片发布给你的客户,并把它们干净地包装在一个JAR文件中。任何好的软件产品都要求有文档,因为你的产品是为开发者设计的一个软件组件,它很可能只需包含Javadoc就足够了。(在前面章节的代码中,你对于包含Javadoc注释是不严格的,这种情形下,你应当考虑更正--但现在,让我们把它作为读者--你的一个借口。)
  假定Persistence组件是普遍关心的唯一部分。你需要将persistence类和astronomy类、test类分离开来。记住你仍需构建所有的类以便测试persistence类,要这么做,一个好的方法是再创建另一个目录,里面仅放置那些组成可发布产品的文件。你将把它称为dist(distribution的简称),而它将包含Persistence组件的JAR文件和Javadoc:

\persistence
  \src
    \...
  \bin
    \...
  \dist
    \lib
      persistence.jar
    \doc
      *.html

  在你更改src目录中的Java源文件时,Eclipse自动保持bin目录中的类文件最新,但除此之外的事情都得你来做。如前所述,你可以用Eclipse手动去做;但为了可靠性和一致性,使用一个构建工具来自动化这个过程会好很多。

5.2 Make:一个回顾


  在我们考虑事实上的Java标准构建工具Ant之前,让我们来快速地回顾一下传统的构建工具--Make--以便从其他角度看看Ant所带来的优点。有许多不同的增强版Make,包括各种Unix变种,Gnu make,以及微软的NMAKE(New Make),不过它们大体上都是相似的。
  Make接收一个make文件,文件中包含一个目标列表,每个目标后都接着一个脚本,当调用对应目标时运行。(默认状况下,Make查找一个特定名字的文件,通常是makefile,但你一般可以在命令行强制指定一个其他名字的文件。)一个目标和它的命令有时称为规则(rules)
  一个目标可以有依赖(dependencies);也就是其他的必须首先被评估的目标。当Make运行时,它查找第一个目标的依赖以查看它们是否已经存在。如果目标是文件,并且依赖也是文件,Make会比较它们的时间戳;如果依赖比目标新,Make会运行规则以更新目标。
  这是一个make文件规则的一般格式:

TARGET: dependencies ...
  commands
  ...

  一个小的,简单的,用来从两个C源文件构建一个程序的make文件看起来可能会是这样:

myapp.exe: main.obj aux.obj
  link main.obj aux.obj -o myapp.exe

main.obj: main.c
  cc main.c

aux.obj: aux.c
  cc aux.c

  如果你不熟悉C,别担心具体细节,只要注意从C源代码创建一个可执行程序一般需要的两个步骤:编译源代码为目标文件(示例中的.obj文件)以及把所有的目标文件链接为一个单一的可执行文件(示例中的.exe文件)。
  第一个目标,myapp.exe,是一条规定了如何通过连接两个目标文件以构建可执行文件的规则。默认情况下,make文件中的第一个目标就是由Make执行的。Make计算依赖链以确保链上的每个东西都是最新的。你第一次运行这个make文件时,它首先编译main.obj和aux.obj,然后把它们连接起来以构建程序myapp.exe。
  如果你修改了aux.c并再次运行make文件,main.obj仍然是最新的。因此,Make仅编译aux.obj文件,之后就连接新的aux.obj和已有的main.obj以创建myapp.exe。
  除了一个构建程序的目标外,你还可以添加其他的目标来执行特定任务,例如删除所有生成的文件以便强制完全重建。你可以将以下两个目标添加到文件末尾:

CLEANALL: CLEAN
  del *.exe
  echo Deleted executable
CLEAN:
  del *.obj
  echo Deleted object files

  因为它们并没有标识一个要构建的真正目标,像这样的目标通常称为“伪目标”(pseudo-targets)。这两个目标并没有包含在默认目标的依赖链中,因此执行它们的唯一途径就是在启动Make时在命令行中明确指定它们。
  指定CLEAN会删除所有中间目标文件。指定CLEANALL会首先删除所有中间目标文件(因为它依赖于CLEAN目标),然后删除可执行文件--以这种方式使用的依赖和方法调用的效果类似。
  除了规则之外,一个make文件还能对变量赋值以及访问环境变量。因为本概述只是打算提供一个对Make的概览,在这里我们不会谈及这些问题。
  正如例子中所示范的那样,Make具有一些批处理文件和shell脚本所不具有的重要优点:
  • Make减少了构建时间。Make能够计算哪些构建目标比它们的源码更旧,并且只会构建对于更新构建目标来说必要的那些。当你在做一个在编译时可能需要花费很长时间的大系统时,这能够节省大量时间。
  • Make是说明性的。你不需要去一步一步地告诉它如何构建。相反的,你只需将需要构建的东西指定为一组相关的目标集。执行顺序,或者说控制流程,不会被正规地显式规定--尽管你在必要时可以用伪目标来顺序执行一组命令。
  • Make是可扩展的。命令是由Shell来执行的,所以如果你需要任何shell命令没有提供的功能,你可以编写你自己的工具程序--比如,用C语言。
  除了一些古怪行为外(就比如令每个Make用户至少发疯一次的对空格和制表符(Tab)的过度区分),Make是一个完美的可用的构建工具。但示例用C来写的一个原因是:为了更好的演示Make的增量构建的能力。将Make运用在Java中则无法在这点上提供相当的益处,因为绝大多数的Java编译器自动地计算了依赖关系。假如你有一个Java类,HelloWorld

// HelloWorld.java

public class HelloWorld
{
  public static void main(String[] args)
  {
    Printer printer = new Printer();
    printer.out("Hello, world");
  }
}

  它使用这个Java类,Printer

// Printer.java

public class Printer
{
  void out(String s)
  {
    System.out.println(s);
  }
}

  你可以用一条命令来编译这两个类:

javac HelloWorld.java

  Java编译器,javac,计算了HelloWorld.java并判断它使用了Printer.java。如果还没有编译Printer.java,或者如果Printer.java比Printer.class要新,javac就会编译Printer.java。换句话说,这一条命令实际上等同于你先前看到的那个C程序的make文件的第一部分--也就是开头那三条指定了如何构建程序的规则。
  因为增量编译是Make与批处理文件或者shell脚本相比所能提供的最大优点,Java编译器能自动判断依赖关系的能力看来减少了在Java开发中对Make工具的需求。然而,这并没有完全消除对某些类型的构建工具的需求,因为对象的类型在编译时并不总是知道。类能包含集合引发了问题,因为一个集合可以存放任意类型的对象;例如,一个Company类可能包含有一个Employee的Vector,而编译器可能无法在编译时判断出来。如果你需要可靠的增量编译,你仍会需要某些类型的构建工具。
  最后也是最重要的一点,编译并不是构建工具所做的唯一一件事。正如示例所示,传统的make文件一般也执行一些像删除旧文件这样的简单的“管家”任务。Java项目经常需要额外的步骤,例如生成Javadoc以及把包归档。

5.3 新的Java标准:Ant


  将传统的Make工具应用到Java中有一个严重的缺点:这些工具运行的是shell命令,而它们在各个平台上都不一样。这个事实使得采用Java开发的主要因素之一:一次编写,到处运行的能力,变得毫无用处。明显的解决方案就是实现一个能说得上是“Java上的Make”的工具。Ant,一个来自Apache软件基金会(ASF)的开源构建工具,走上了这条路。除了成为了比Make更好的跨平台解决方案之外,Ant还更新了make文件的语法,采用了标准说明格式:XML。由于这些缘故,Ant迅速地成为了Java上的标准构建工具,并被紧密地集成到Eclipse中;这种集成包括了一个特殊的编辑器,用来和Ant构建脚本一起工作。

注意
目前为止,因为你一直在使用Eclipse来编译你的Java源代码,你还不需要一个单独的Java编译器。如前所述,Eclipse包含了它自己的特殊的增量编译器;你所需添加的只是一个Java运行时环境(JRE)。
要用Ant来构造,尤其是在命令行下,你需要有一个完整的Java开发工具包(JDK)。依据你的平台而定,你可能会有许多选择;但至少你应该使用JDK 1.3.x(JDK 1.4.x更好),Sun的或是其它公司的都行。确保在你的PATH环境变量中,该JDK的bin目录要比其它任何包含有JRE的目录靠前。同样应确保,在你的CLASS环境变量中,删除所有到旧的JDK和JRE的引用。你不需要在classpath中包含任何JDK的标准目录或者JAR,因为它们会依据Java编译器的可执行文件(Windows中是javac.exe)和Java虚拟机(java.exe)的位置来自动定位。

5.3.1 一个对XML的非常简短的介绍

  XML(可扩展标记语言,Extensible Markup Language)已经成为了表述任何类型语言,因此你可能已经用过它诸多功能中的其中一些了。如果你还没有用过它,或者已经用过但对它的一些技术还不了解,这份介绍可以让你能更容易地理解后面对Ant构建文件的讨论。
  XML来源于SGML(标准广义标记语言,Standard Generalized Markup Language),与其很相似的HTML(超文本标记语言,Hypertext Markup Language)也是如此。都使用标签,也就是用尖括号括起来的标识符,就像这样:

<TITLE>

  不过在HTML和XML之间还是有少数一些重要的不同点。因为HTML是设计用来为一个较有限的目标--描述如何将数据显示为一个网页--提供服务的,它定义了一组标准标签。TITLE是一个有效的HTML标签,但ORDER_NUMBER不是。然而,XML是可扩充的。由你正在使用的应用程序来定义哪些标签是有效的。在一个使用XML来表示一个在线商店的数据的程序中,ORDER_NUMBER就很有可能是一个有效的标签。
  一个像<TITLE>这样的标签称为一个开标签(Opening Tag);它标记了一段数据的开头。开标签通常需要有一个闭标签,也就是和开标签名字相同,前面有个斜杠的标签。下面定义了一个网页的标题:

<TITLE>A very brief introduction to XML</TITLE>

  HTML对语法要求相当松。开标签并不总是要求有闭标签。例如,标签<P>被用来标记段落开头,而</P>应该是用来标记末尾。然而实际上,你可以简单地使用<P>来在一个网页上表明文字段落间的间隔。这在XML中绝对不是如此--每个开标签都必须要有一个闭标签。
  有时候在HTML中你可以回避不正确的嵌套的开标签和闭标签;但在XML中就不行了。以下代码由于嵌套不正确,在XML中是无效的:

<B><I>This is not valid in XML!</B></I>

  HTML和XML的一个最大不同就是XML是大小写敏感的。在HTML中,<TITLE>和<title>都是有效且是等价的。在XML中,取决于应用程序,它们都可能有效,但并不等价。

元素和属性
  一个开标签和一个闭标签定义了一个元素(element)。每个XML文档必须有一个根元素(或文档元素)来把文档中的所有其它元素包围起来。
  每个元素的开标签可能包含了形式为名字-值对(name-value pairs)的与元素相关的附加信息,我们称之为属性(attributes)。其中的值必须总是用引号包围。按标签不同,特定的属性可能是必需的,或是可选的。比如,Ant定义了一个<target>标签来标识构建目标。这个target标签接受好几个属性,例如depends和description,但只有name属性是必需的:

<target name="Compile" depends="Init">
  <!-- do compilation stuff here-->
</target>

  (注意了,如同在HTML中一样,你可以插入以<!--开头,-->结尾的注释。)
  有时候元素会没有任何内容。例如,用来运行Java程序的Ant标签,<java>,允许你在属性中给定所需的所有信息。如果你有一个类文件Hello.class,你可以放置在一个target里面来运行它,如下所示:

<target name="SayHello">
  <java classname="Hello.class"> </java>
</target>

  作为一种便捷的形式,像这里那个具有/<java>标签的空元素可以把开标签写成以/>结尾,然后忽略闭标签。以下和前面的例子是等同的:

<target name="SayHello">
  <java classname="Hello.class"/>
</target>

用属性和嵌套元素来表示数据
  属性(例如<target>标签中的name属性)和嵌套元素(例如被<TITLE>和</TITLE>标签所包围的文本)在XML中都能被用来说明数据。由应用程序来选择。有时应用程序会同时支持两种格式并让用户来选择。Ant有时会提供属性以选择单个选项(例如classname),复杂些的就用嵌套标签,例如文件集,或是路径与个别文件的组合。
  举个例子,标签<java>,让你既可以用一个属性来指定类路径(classpath),也可以用嵌套元素。你可以用classpath来将路径设置为预定义好的属性java.class.path(Ant把它设为你的环境的类路径),如下所示:

<target name="SayHello">
  <java classname="Hello.class" classpath="${java.class.path}"/>
</target>

  或者你可以使用一个嵌套的classpath元素,两者是等价的:

<target name="SayHello">
  <java classname="Hello.class">
    <classpath path="${java.class.path}"/>
  </java>
</target>

  嵌套元素可以依次包含嵌套元素。例如,你可以用一个或多个的嵌套<pathelement>元素来替换掉<classpath>标签中的path属性,别的元素也可如此:

<target name="SayHello">
  <java classname="Hello.class">
    <classpath>
      <pathelement path="${java.class.path}"/>
      <pathelement location="c:/junit/lib/junit.jar"/>
    </classpath>
  </java>
</target>

  由于允许像这样的多个选项,有时Ant和其它使用XML的程序会让人难以理解。嵌套元素提供了比属性强得多的灵活性,后者局限于单个值。如果你仅仅需要一个值,使用简单些的语法来设置选项会很方便。
  使用嵌套元素这种方式来扩展选项显示了XML的主要问题:冗长。你包含进去的每一点点的数据都添加了另一对开标签和闭标签。好在(或者说是因为很有必要),大多数基于XML的程序都提供了工具,以使得编写XML的工作更轻松些。

5.3.2 一个简单的Ant示例

  在探究Ant及其构建脚本的细节之前,让我们通过“Hello,World.”的Ant等价物来看看Eclipse中的Ant的使用技巧。如同Make默认将一个make文件命名为makefile一样,Ant的默认构建脚本名为build.xml。和Make一样,你可以通过在调用Ant时显式指定一个文件来跳过默认设置。然而,通常还是遵守这个约定较好,尤其是因为Eclipse也同样将构建脚本默认命名为build.xml,并且会自动用Ant脚本编辑器打开叫这个名字的文件。(你可以到[窗口]->[首选项]->[工作台]->[文件关联]对话框去更改它的行为,但把它修改为打开所有的.xml文件并不是个好主意--将来你会见到其它类型的XML文件,它们将更能从其它专用编辑器获益。如果你不愿把你的构建脚本叫做build.xml,在[文件关联]对话框中逐个输入所有的构建脚本名,以便用Ant编辑器打开它们。)
  要创建构建文件,可以在一个现存的项目(比如你的老项目Hello)上右击鼠标,然后在快捷菜单中选择[新建]->[文件]。输入build.xml作为文件名并点击[完成]。如果编辑器没有自动为你打开build.xml,那么就双击build.xml文件。输入以下内容:

<?xml version="1.0"?>
<project name="Hello" default="print message">
  <target name="print message">
    <echo message="Hello from Ant!"/>
  </target>
</project>

  Ant编辑器并不像Java编辑器那么有用,但是它提供了一些基本的方便,例如你可以在任何时候通过按下[Ctrl]+[空格]键来调用自动代码完成功能。在标签外时,它会显示有效的标签;在标签里时,它将显示该标签的有效属性。(后面的功能尤其有用,因为各个标签的属性名并不相同。)同时,Ant编辑器还提供了语法高亮和一个大纲视图。
  要运行这个脚本,首先保存它,然后在[包浏览器]中右击build.xml,在快捷菜单中选择[运行Ant]。这样将打开一个已经选定了默认目标的对话框(参看图5.4)。

图5.4 运行一个Ant文件。默认目标已经被自动选定。


  点击对话框底部的[运行]按钮,Eclipse的控制台视图会输出如下内容:

Buildfile: c:\eclipse\workspace\hello\build.xml

print message:
  [echo] Hello from Ant!
BUILD SUCCESSFUL
Total time: 2 seconds

在Eclipse外部运行Ant
  除了可以在Eclipse内部运行Ant脚本外,你也可以在Eclipse外部使用构建脚本。要这么做,你需要从Apache软件基金会(ASF,Apache Software Foundation)(Ant项目可以在http://ant.apache.org找到)下载并安装完整的Ant发行版。如果当前版本和Eclipse自带的那个不同(或者不兼容),你就得找到旧版来下载或是升级Eclipse自带的Ant。
  要升级Eeclipse自带的Ant,在Eclipse主菜单中选择[窗口]->[首选项]->[Ant]->[运行时]。然后从classpath中清除1.5.1版的ant.jar和optional.jar,并添加新版的这两个JAR文件的路径。
  在下载适合于你系统的p文件(或tar文件)并解压之后,将bin目录路径添加到你的path,lib目录路径添加到classpath。如果你将Ant安装在Windows上的c:\jakarta-ant-1.5.1目录,你可以通过在命令行下键入如下命令来将这些目录加入到你的path中:

SET PATH=c:\jakarta-ant-1.5.1\bin;%PATH%
SET CLASSPATH= c:\jakarta-ant-1.5.1\lib;%CLASSPATH%

  这些更改只会对当前命令行窗口有效。要想让设置长期生效,需要用Windows NT/2000/XP中的[控制面板]的[系统],或是Windows 95/98/ME中的autoexec.bat文件来修改它们,之后你打开的任何命令行窗口都将设置适合于Ant的选项。在执行这些步骤后,现在你可以进入c:\eclipse\workspace\Hello目录,输入ant以运行Ant:

C:\eclipse\workspace\Hello>ant
Buildfile: build.xml

print message:
  [echo] Hello from Ant!

BUILD SUCCESSFUL
Total time: 2 seconds

  如果你在项目的正式构建中使用Ant,但在日常工作中继续使用Eclipse的自动编译(这非常方便),你可能要在Eclipse中--或者更愿意在命令行下,偶尔运行ant来构建。这么做可以确保你没有干扰正式构建过程,并且在构建中包含了你最近创建的任何文件。
  在你制作一个更大的构建文件前,让我们首先来看看构成一个Ant的make文件的重要标签及属性的相关细节。

5.3.3 项目

  一个构建文件必须具备的文档元素是<project>标签,它必须指定一个默认目标,还可以指定一个名字(可选的)。此外,还可以标明项目的基本目录。表5.1中列出了它的属性:

表5.1 <project>标签属性
属性 描述 是否必需
default 要运行的默认目标
name 项目名称
basedir 基本目录
description 项目描述

  在basedir属性中,你既可以指定相对路径也可以指定绝对路径;不管哪种情况,这都将被解析为其他标签可以使用的绝对路径。然而使用一个相对路径会较好,因为这样能让构建更具可移植性。在进行一次构建时,其它开发者的机器以及正式构建用机不需要设置得和你的一样。以下示例将basedir属性设置为当前路径(.)--也就是说,定位到build.xml所在的目录:

<project name="Hello" default="compile" basedir="." description = "Hello, world build file">

<project>标签可以有以下嵌套元素:
  • <desciption>--如果你需要将描述扩展到超过一行,你可以用嵌套元素来包含项目描述,而非用一个属性。强烈推荐写上描述。
  • <target>--如5.3.4节所述。
  • <property>--如5.3.6节所述。

5.3.4 目标

  一个目标(target)就是一个任务或是一组相关任务的容器标签,可以(粗略地)比喻为一个方法(method)。它可具有如表5.2所列属性:

表5.2 <target>标签属性
属性 描述 是否必需
name 目标名称
depends 依赖关系列表
if 仅当设置了指定属性时执行
unless 仅当未设置指定属性时执行
description 目标描述

  为你的主要目标给出一份描述是个好主意,因为Ant提供了一个-projecthelp选项来列出所有具有描述的目标,并把它们作为主要目标。这个选项令你的构建文档在一定程度上可以进行自我文档编制。
  这里是一个例子:

<target name="compile" depends="init" description="Compile all sources">

5.3.5 任务

  如果把一个目标比喻为一个方法,一个任务(task)可以比喻为方法中的一条语句。Ant提供了大量的任务--超过100条,如果你把核心任务和可选任务都算上。
  Ant的巨大优点之一是它对跨平台问题是透明处理的。例如,在UNIX中,一个文件路径在目录和文件间用的是向前的斜线(/),而在Windows中,用的是一个反斜线(\)。在Ant中,你都可以使用,然后Ant会提供对你正在使用的系统来说正确的格式。对于类路径来说也是一样的。在UNIX中,一个类路径中不同的路径是用一个冒号分隔的,而在Windows中使用的则是一个分号;你两个都可以用,剩下的事情就交给Ant了。
  以下是一些常见的人物,它们都有一组基本属性--足以理解示例并开始编写你自己的构建文件。需要一份所有任务及其选项的完整描述的话,请参考http://ant.apache.org/manual/index.html上的Ant文档。

<buildname>
  该任务从一个文件中读取构建号(build number),将属性build.number设置为该号码,然后将build.number的值+1后写回文件。它只有一个属性,如表5.3中所列:

表5.3 <buildname>任务属性
属性 描述 是否必需
file 要读取的文件(默认:build.number)

  这里是一个例子:

<buildnumber file="buildnum.txt" />

<copy>
  该任务复制一个或一组文件。要拷贝单个的文件,用file属性即可。要拷贝多个文件,需要用一个嵌套的<fileset>元素。
  通常,该任务仅当目标文件不存在或比源文件旧时才执行复制,但你可以通过设置override属性为true来改变默认行为。<copy>任务的属性如表5.4中所列:

表5.4 <copy>任务属性
属性 描述 是否必需
file 源文件名 是,除非使用了<fileset>
tofile 目标文件名 是,除非使用了todir
todir 目标目录 是,如果拷贝一个以上的文件
overwrite 覆盖更新的目标文件 否;默认是false
includeEmptyDirs 复制空目录 否;默认是true
failonerror 如果找不到文件则停止构建 否;默认是true
verbose 列出已复制的文件 否;默认是false

  一个<fileset>嵌套元素可以用来指定一个以上的文件。(参看5.3.7节。)
  这里是一个示例:

<copy file="log4k.properties" todir="bin"/>

<delete>
  该任务删除一个或一组文件,或者一个目录。要删除单个的文件,用file属性即可。要删除多个文件,则要用一个嵌套的<fileset>元素。要删除目录,用directory属性即可。<delete>任务的属性如表5.5中所列:

表5.5 <delete>任务属性
属性 描述 是否必需
file 要删除的文件 是,除非使用了<fileset>或dir
dir 要删除的目录 是,除非使用了<fileset>或file
verbose 列出已删除的文件 否;默认是false
failonerror 如果出错则停止构建 否;默认是true
includeEmptyDirs 使用<fileset>时删除文件夹 否;默认是false

  一个嵌套的<fileset>元素可以用来指定一个以上的文件。(参看5.3.7节。)
  这里是两个例子:

<delete file="ant.log"/>
<delete dir="temp"/>

<echo>
  该任务将一条信息写到System.out(默认),一个文件,一份日志,或者一个侦听器。它的属性如表5.6中所列:

表5.6 <echo>任务属性
属性 描述 是否必需
message 要写入的文本 是,除非用了文本作为元素内容
file 输出文件
append 附加到文件(而非覆盖) 否;默认是false

这里有一些例子:

<echo message="Hello"/">
<echo>
  This is a message from Ant.
</echo>

<jar>
  该任务将一组文件压缩到一个JAR文件中。允许的选项如表5.7中所示:

表5.7 <jar>任务属性
属性 描述 是否必需
destfile JAR文件名
basedir 要打包的文件的基本目录
includes 要打包的文件的模式列表
excludes 要排除的文件的模式列表

  模式列表是由逗号或空格分隔的文件匹配模式列表。<jar>接受如同<fileset>元素的嵌套元素。(参看5.3.7节。)
  这里是一些例子:

<jar destfile="dist/persistence.jar"
  basedir="bin"
  includes=
  "org/eclipseguide/persistence/**, org/eclipseguide/astronomy/**"
  excludes="*Test*.class "/>

<jar destfile="dist/persistence.jar">
  <include name="**/*.class"/>
  <exclude name="**/*Test*"/>
</jar>

<java>
  任务java使用一个JVM(Java虚拟机)来调用一个类。默认情况下,所用的JVM和Ant用的是同一个。如果你是在调用一个稳定的自定义构建工具,这样可以节省时间;但如果你是在用它运行没测试过的代码,你将冒错误代码乃至构建进程崩溃的风险。你可以将fork选项设置为true来调用一个新的JVM。该任务的属性如表5.8中所列:

表5.8 <java>任务属性
属性 描述 是否必需
classname 要运行的类的名称 是,除非指定了jar
jar 要运行的可执行JAR文件的名称 是,除非指定了classname
classpath 要用的Classpath
fork 用一个新的JVM运行类或JAR 否;默认是false
failonerror 当发生错误时停止构建 否;默认是false
output 输出文件
append 附加或覆盖默认文件

  任务<java>可用以下嵌套元素:
  • <classpath>--可用于代替classpath属性
  • <arg>--可用于指定命令行参数
  这是一些例子:

<java classname="HelloWorld"/>
<java classname="Add" classpath="${basedir}/bin">
  <arg value="100"/>
  <arg value="200"/>
</java>

<javac>
  该任务编译一个或一组Java文件。它有一组复杂的选项(参看表5.9),但它比你所想象的要更容易使用,因为很多选项是提供给你以控制编译器选项的。Ant设定的选项是面向目录工作的,而非单个的Java文件,这让构建项目更容易。

表5.9 <javac>任务属性
属性 描述 是否必需
srcdir 源码树的根 是,除非使用了嵌套的<src>
destdir 输出目录
includes 要编译的文件的模式列表 否;默认包含所有的.java文件
excludes 用忽略的文件的模式列表
classpath 要用的Classpath
debug 包含调试信息 否;默认是false
optimize 使用优化 否;默认是false
verbose 提供详细输出
failonerror 发生错误时停止构建 否;默认是true

  默认情况下,<javac>编译时包含调试信息。这样做通常适合于将被用于产品环境的构建。你可能希望有一种方法来打开或关闭这个选项,也许会采取分开调试构建和发布构建的目标的方法。
  <javac>可用以下嵌套元素:
  • <classpath>--可用于代替classpath属性。
  • <jar>接受如同一个<fileset>元素那样的嵌套元素。(参看5.3.7节。)
  这是一些例子:

<javac srcdir="src" destdir="bin"/>

<javac srcdir="${basedir}" destdir="bin"
  includes="org/eclipseguide/persistence/**"
  excludes="**/*Test*">
  <classpath>
    <pathelement path="${java.class.path}"/>
    <pathelement location="D:/log4j/jakarta-log4j-1.2.7/dist/lib/log4j-1.2.7.jar"/>
  </classpath>
</javac>

<javadoc>
  任务<javadoc>从Java源代码文件生成一份Javadoc。任务jarjava中用于选择要包含哪些文件的选项应该比较熟悉了。指定给javadoc的首要选项设定了要包含哪些Javadoc注释;参看表5.10。

表5.10 <javadoc>任务属性
属性 描述 是否必需
sourcepath 源码树的根 是,除非指定了sourcefiles或者sourcepathref
sourcepathref 到一个指定源码树根的路径结构的引用 是,除非指定了sourcepath或者sourcefiles
sourcefiles 源码文件的逗号分隔列表 是,除非指定了sourcepath或者sourcepathref
destdir 目标目录 是,除非已经指定了doclet
classpath 类路径(Classpath)
public 仅显示公共类及成员
protected 显示公共和保护的类及成员 否;默认是true
package 显示包,保护和公共的类及成员
private 显示所有的类及成员
version 包含@version信息
use 包含@use信息
author 包含@author信息
failonerror 出错时停止构建 否;默认是true

  任务<javadoc>可用以下嵌套元素:
  • <fileset>--可用于选择一组文件。Ant自动把**/*.java添加到每个组。
  • <packageset>--可用于选择目录。目录路径默认为和包名称一致。
  • <classpath>--可用于设置classpath。
  这是一些例子:

<javadoc destdir="doctest"
  sourcefiles ="src/org/eclipseguide/persistence/ObjectManager.java"/>

<javadoc destdir="doc"
  author="true"
  version="true"
  use="true"
  package="true">
  <fileset dir = "${src}/org/eclipseguide/astronomy/">
    <include name="**/*.java"/>
    <exclude name="**/*Test*"/>
  </fileset>
  <classpath>
    <pathelement path="${java.class.path}"/>
    <pathelement location ="D:/log4j/jakarta-log4j-1.2.7/dist/lib/log4j-1.2.7.jar"/>
  </classpath>
</javadoc>

<mkdir>
  该任务创建一个目录。它具有如表5.11所示的一个属性。如果指定了一个嵌套目录,那么若必要的话会连父目录也一起创建。

表5.11 <mkdir>任务属性
属性 描述 是否必需
dir 要创建的目录

  这是一个示例:

<mkdir dir="dist/doc">

<tstamp>
  该任务设置属性DSTAMP,TSTAMP以及TODAY。一个嵌套元素,<format>,可用于使用Java类SimpleDateFormat定义的格式来改变它们的格式,但默认情况下,这些格式如下所示:

DSTAMP yyyyMMdd
TSTAMP hhmm
TODAY MMM dd yyyy

  关于<tstamp>和<format>元素的更多信息,请参看Ant文档。

5.3.6 属性

  属性是你可以在一个构建文件中用作符号常数的名称-值对。属性的值是通过用${和}括起名字的方式来引用的。例如,如果一个属性junit_home已经用值D:/junit/junit3.8.1定义,你可以使用该属性以在编译时添加junit的JAR文件到classpath中:

<javac srcdir="src" destdir="bin" classpath="${junit_home}/lib/junit.jar"/>

  属性可以用几种方式定义:
  • 由Ant预定义
  • 在Ant命令行,用-D选项(例如,ant -Djunit_home=D:/junit/junit3.8.1)定义
  • 在一个构建文件中用<property>任务定义
  由Ant预定义的属性包含了所有的标准Java系统属性,包括了以下内容:
  • java.class.path
  • os.name
  • os.version
  • user.name
  • user.home
  Ant特有的属性包括:
  • ant.version
  • ant.file
  • ant.project.name

<property>和名字属性
  在一个Ant构建文件中设置属性的最常用方式是利用<property>任务及其属性name,以及属性value或location。属性value用于设置一个直接的值:

<property name="jar_name" value="myapp.jar"/>
<property name="company" value="Acme Industrial Software Inc."/>

  属性location用于设置一个绝对路径或文件名。如果你设定了一个相对路径,Ant会认为它是基于basedir属性的,然后把它转换为一个绝对路径并解析它。此外,文件路径分隔符会被转换为适合于平台的字符(/,\,或者是:)。例如:

<property name="junit_home" location= "D:/junit/junit3.8.1"/>
<property name="src" location="src"/>

  第一个例子不会被改动(除了文件路径分隔符外),因为它表示一个绝对路径。第二个例子会被扩展,因为它是个相对路径;假设basedir(基本路径)是c:\eclipse\workspace\persistence,${src}就等同于"c:\eclipse\workspace\persistence\src"。

<property>和文件属性
  你可以用file属性使用标准Java属性文件格式来从一个文件中读取属性。假设有个叫build.properties的文件放在基本目录中或是在classpath中:

# build.properties
junit_home= D:/junit/junit3.8.1
log4j_home=D:/log4j/jakarta-log4j-1.2.7

  你可以用以下标签来读取这些属性:

<property file="build.properties"/>

<property>和环境属性
  通过用environment属性给环境设置前缀的方法,像读取属性那样去读取环境变量也是可行的。以下给环境设置了前缀myenv:

<property environment="myenv">

  之后,你只要使用前缀myenv就可以像访问属性一样访问环境变量。例如,如果环境中定义了JUNIT_HOME,你可以用${myenv.JUNIT_HOME}获取它的值。
  你应该小心使用该技术,因为并不是在所有操作系统上都支持它。同时,即使底层操作系统是大小写不敏感的,在Ant中,属性名也是大小敏感的。这很容易在各版本的Windows中因疏忽而导致问题,因为系统保留了环境变量的大小写而执行比较时却不区分大小写。
  例如,如果有一个变量CLASSPATH,其值是c:\mylibs,以下既不会创建一个叫classpath的新变量,也不会改变原CLASSPATH变量的大小写:

set classpath=.%classpath%;c:\anotherlib

  相反的,这样做会更新原CLASSPATH变量的值为c:\mylibs;c\anotherlib。要确保大小写像你期望的那样,你可以先清除该变量然后重新定义它。以下几行可以放在命令行或是一个批处理文件中,它们可以强制把classpath变成小写:

set tmpvar=%classpath%
set classpath=
set classpath=%tmpvar%;c:\anotherlib

  如果你正要为Ant设置环境变量,像这样使用Windows批处理文件来预防问题是值得考虑的──尤其是当该批处理文件会用在其它系统上时。

5.3.7 文件集和路径结构

  由于Ant的特性,许多Ant任务,如<javac>和<jar>要求你设置路径和文件集。Ant提供了元素以使你能给它们指定足够多的细节,你既可以明确地选择文件和目录,也可以使用模式(patterns)来包含或排除一组文件或目录。因为这些元素除了引用对象以外,什么事情也不做,所以被称为类型(types)。在这里你仅使用两种类型:<fileset>和<classpath>。

<fileset>
  正如其名字所表述的那样,<fileset>元素让你选择文件集。一个<fileset>必需的属性只有基本目录。如果你别的什么也没有设定,该目录下的所有文件及其子目录都将被选定──特定的临时文件以及特定工具(如CVS)生成的文件除外。(这样的文件通常都有特殊的名字,用一个波纹号(~)或#开头和结尾,或者具有像CVS和SCCS这样的特定的名字和扩展名;它们和一个典型项目的一般文件相同的情况极为罕见。要获得一份Ant要默认排除的完整的模式列表,请参考Ant文档。)
  你也可以选择或排除那些和你提供的模式相匹配文件。模式可以包含以下通配符:

? 匹配一个任意字符
* 匹配零或多个字符
** 匹配零或多个目录

  考虑这两个常见的例子:你可以在include属性中用模式**/*.java来包含所有的Java源文件,也可以在exclude属性中用模式**/*Test*来排除测试用例。共同使用时,它们指定了除测试用例外的所有Java文件。
  <fileset>元素的属性如表5.12所列。

表5.12 <fileset>元素属性
属性 描述 是否必需
dir 目录树的根
defaultexcludes 排除通常的临时文件和工具文件 否;默认是true
includes 要包含的文件的模式列表
excludes 要排除的文件的模式列表
followsymlinks 使用符号链接所指定的文件

  嵌套元素<include>和<exclude>可以分别用来代替includes属性和excludes属性。
  这是一些例子:

<fileset dir = "src/org/eclipseguide/astronomy"
  includes = "**/*.java"
  excludes = "**/*Test*"/>

<fileset dir = "src/org/eclipseguide/astronomy/">
  <include name="**/*.java"/>
  <exclude name="**/*Test*"/>
</fileset>

<classpath>
  <classpath>元素让你可以指定哪些目录和JAR文件是当类要运行时(或者,对于Java编译器来说,要编译时),程序应该搜索的。默认情况下,Ant继承了环境的类路径,但你都需要为特定程序(如JUnit)添加额外的目录或JAR文件。使用类路径的任务提供了一个classpath属性,但有时使用一个<classpath>嵌套元素会更方便些──尤其是当类路径很长的时候。路径可以包含用一个冒号或一个分号隔开的多个文件或目录;Ant会将分隔符转换为适合于当前操作系统的字符。
  表5.13中列出了<classpath>元素的属性。

表5.13 <classpath>元素属性
属性 描述 是否必需
path 冒号或分号分隔的路径
location 单个文件或目录

  一个或多个<pathelement>元素可以通过嵌套来构造一个更长的类路径。<pathelement>接受和<classpath>:path以及location相同的属性。
  此外,一个<fileset>可以用于指定文件。
  这是一些例子:

<classpath path = "bin"/>
<classpath>
  <pathelement path="${java.class.path}"/>
  <pathelement location="${junit_path}"/>
  <pathelement location="${log4j_path}"/>
</classpath>

5.3.8 Ant的额外能力

  这里讲述的基本知识已经足以让你开始用Ant来工作而不会不知所措了。当你用Ant做更多的事情之后,你很可能会遇到一种情况──也许会发觉你一直在反复使用相同的<filelist>──并想知道有没有一种比剪切、粘贴更优雅的解决方案。通常,你会发现在Ant中,几乎没有什么是不可能的。
  减少冗余代码的一种方法是使用引用。例如,Ant中的每个元素都能指定一个ID;并且(取决于涉及的元素的类型)你可以在构建文件中的其它地方使用ID来引用那个元素。例如,你可以使用id属性给一个<classpath>指定一个标识符:

<classpath id="common_path">
  <pathelement path="${java.class.path}"/>
  <pathelement location="${junit_path}"/>
  <pathelement location="${log4j_path}"/>
</classpath>

  这样一来,这个类路径就可以在别的地方用refid属性来引用:

<javac srcdir="src" destdir="bin">
  <classpath refid=common_path/>
</javac>

  Ant提供了任务和类型以供你过滤文件,如同以文本来复制、替换符号一样,这样你就能够在你的构建中包含版本信息──例如,使用带有一个<filterset>的<copy>任务。它让你可以通过使用选择器类型(如<contains>,<date>和<size>)来按照复杂的条件选择文件。在很罕见的情况下,Ant没有可以符合你需求的任务,你会发现编写自己的Ant任务是很简单的事情。

5.4 一个Ant构建的示例


  以下是你的构建过程需要做的主要步骤:
  • 编译程序,输出到bin目录
  • 在bin目录中运行单元测试
  • 生成一份Javadoc文档,输出到dist/doc目录
  • 将程序的类文件打包到dist目录下的一个JAR文件中
  因为你可能需要能逐步完成这些工作,它们在Ant构建文件中是独立的目标。然而通常,你会需要一次性完成所有步骤,所以你也需要有一个目标依赖于这些独立的目标。
  通常这些独立的目标都有共通的安装需求。你可以创建一个初始化目标来执行安装,该目标可以以依赖的方式包含这些独立目标。因为这是一个相当简单的示例,在这里,你所需要做的一切只是通过调用tstamp任务,初始化属性DSTAMP,TSTAMP和TODAY为当前的日期和时间,然后打印日期和时间。

5.4.1 创建构建文件build.xml

  要创建构建文件,请遵循以下步骤:
      
  1. 在[包资源管理器]中的项目Persistence上右击鼠标,选择[新建]->[文件]。  
  2. 在[新建文件]对话框中键入build.xml并点击[完成]。
  在定义任何目标前,让我们来创建一些属性,之后就可以像符号常量那样使用,而无需在构建文件中杂乱地堆放实际值。这个方法会让构建文件更容易维护。这是build.xml的开头:

<?xml version="1.0"?>
<project name="Persistence" default="BuildAll" basedir=".">

  <description>
  Build file for persistence component,
  org.eclipseguide.persistence
  </description>

    <!-- Properties -->
    <property name="bin" location="bin"/>
    <property name="src" location="src"/>
    <property name="dist" location="dist"/>
    <property name="doc" location="${dist}/doc"/>
    <property name="jardir" location="${dist}/lib"/>
    <property name="jarfile" location="${jardir}/persistence.jar"/>
    <property name="logpropfile" value="log4j.properties"/>
    <property name="relpersistencepath" value="org/eclipseguide/persistence"/>
    <property name="alltests" value="org.eclipseguide.persistence.AllTests"/>
    <property name="junit_path" location="D:/junit/junit3.8.1/junit.jar"/>
    <property name="log4j_path" location="D:/log4j/jakarta-log4j-1.2.7/dist/lib/log4j-1.2.7.jar"/>

  如你所料,目录一般是用location属性来指定的,Ant会基于项目的基本目录将它扩展为绝对路径。有一个例外:relpersistencepath,这是一个你会在多个不同地方会用到的相对路径,它是基于不同目录的;要避免Ant将它转变为绝对路径,你需要用value属性来设置它。
  也请注意你显式指定了几个类路径。这不是最好的方法──因为这意味着该构建只能在一台以特殊方式配置的机器上工作──但这是最简单的。你可能想知道你能否用你在Eclipse中设置的classpath变量来代替。答案是肯定的,使用一个定制的第三方Ant任务可以实现;但这将意味着你只能在Eclipse中使用该构建过程。(如果你不介意这个限制,你可以通过到eclipse.tools新闻组搜索一个定制的Ant任务的代码。)
  除了该方法之外,特别是当你在Eclipse外部用命令行构建时,你可以用其它几种方法来设置这些类路径。第一个方法,也可能是最简单的,就是通过以下命令将它们添加到环境的CLASSPATH变量中:

  <Set CLASSPATH=%CLASSPATH%;D:/junit/junit3.8.1/junit.jar;D:/log4j/jakarta-log4j-1.2.7/dist/lib/log4j-1.2.7.jar>

  第二个方法是在命令行显式用-D选项把它们传递给Ant:

  <ant -Djunit_path=D:/junit/junit3.8.1/junit.jar -Dlog4j_path=D:/log4j/jakarta-log4j-1.2.7/dist/lib/log4j-1.2.7.jar>

  更好一点的方法是将这些路径存在它们自己的环境变量中。你可以在构建文件中用以下属性标签读取它们:

  <property environment="env"/>
  <property name="junit_path" value="${env.JUNIT_HOME}/lib"/>
  <property name="log4j_path" value="${env.LOG4J_HOME}/lib"/>

  或者是在命令行中像这样来传递它们:

  <ant -Djunit_path=%JUNIT_HOME%\lib -Dlog4j_path=%LOG4J_HOME%\lib>

  最后,其它的选项是使用一个属性文件。你可能有一个名为build.properties的文件,其中有如下几行:

  <cjunit_path=D:/junit/junit3.8.1/junit.jar>
  <log4j_path=D:/log4j/jakarta-log4j-1.2.7/dist/lib/log4j-1.2.7.jar>

  要在文件中使用这些值,需要在build.xml包含如下标签:

  <property file="build.properties"/>

  在设置了属性之后,就需要包含主要目标了。先放置默认目标不是必要的(就像用Make时一样),但由于这是个特殊的目标──它仅仅是以一组依赖性的方式来把其它目标链接起来,你就需要这样写:

  <!-- Main targets -->
  <target name="BuildAll"
    depends="-Init, -Prep, Compile, Test, Javadoc, Jar"
    description="Complete rebuild. Calls Init, Compile, Test, Javadoc, Package"/>
    <echo message="Build complete."/>
  </target>

  按照被BuildAll调用的顺序包含剩下的主目标:下一个是编译目标。注意,通过将源目录标识为org,你可以编译org.eclipseguide.persistence包和 org.eclipseguide.astronomy包中的任何东西,包括单元测试。该技术是拷贝任何所需资源──在这个例子中是log4j.properties文件──的极好方法:

  <target name="Compile"
    depends="-Init"
    description="Compile all Java classes">
    <!-- Compile org.* (${src}) -->
    <javac srcdir="${src}" destdir="${bin}">
      <classpath>
        <pathelement path="${java.class.path}"/>
        <pathelement location="${junit_path}"/>
        <pathelement location="${log4j_path}"/>
      </classpath>
    </javac>
    <!-- Copy log4j.properties files -->
    <copy file="${logpropfile}" todir="${bin}"/>
    <echo message="Compiled."/>
  </target>

  下一个目标是运行单元测试。要在Eclipse外部运行JUnit测试,你需要用到一个JUnit的TestRunner类。由于你需要能够在命令行下运行该构建文件并且记录到一个文件中,你需要使用基于文本的TestRunner,即 junit.textui.TestRunner,而不是漂亮的图形界面版本。要作为一个Java应用程序运行,需要用Ant的java任务。要确保它不会连同你的构建过程一同崩溃,你需要指定它使用独立的JVM,通过设置fork属性为true就可以了。同时,你还必需以嵌套值的方式提供其它的一些值,包括TestRunner要运行的测试类名以及需要用到的类路径:

  <target name="Test" depends="-Init " description="Run JUnit
    <!-- Run test suite using separate JVM -->
    <java fork="yes" classname="junit.textui.TestRunner" taskname="junit" failonerror="true">
      <arg value="${alltests}"/>
      <classpath>
        <pathelement path="${java.class.path}"/>
        <pathelement location="${bin}"/>
        <pathelement location="${log4j_path}"/>
        <pathelement location="${junit_path}"/>
      </classpath>
    </java>
    <echo message="Tested!"/>
  </target>

  Javadoc目标包括了你在这里用到的绝大多数复杂任务。首先,你需要在javadoc标签中,以属性来指定包名(packagename)和你要包含的Javadoc备注。然后,由于要排除掉单元测试,你要使用一个嵌套的<fileset>,里面依次包含嵌套的标签<include>和<exclude>:

  <target name="Javadoc" depends="-Init" description="Create Javadoc">
    <!-- Javadoc, only for persistence classes -->
    <javadoc destdir="${doc}" author="true" version="true" use="true" package="true">
      <fileset dir="${src}/${relpersistencepath}">
        <include name="**/*.java"/>
        <exclude name="**/*Test*"/>
      </fileset>
      <classpath>
        <pathelement path="${java.class.path}"/>
        <pathelement location="${junit_path}"/>
        <pathelement location="${log4j_path}"/>
      </classpath>
    </javadoc>
    <echo message="Javadoc complete."/>
  </target>

  目标Jar相当明了。正如你在javadoc任务中所做的一样,你在这里使用一个<fileset>来指定要排除的测试文件。由于用户需要log4j.properties文件,你还得拷贝它:

  <target name="Jar" depends="-Init ">
    <!-- Jar for persistence classes -->
    <jar destfile="${jarfile}"
      basedir="${bin}"
      includes="${relpersistencepath}/*.class"
      excludes="**/*Test*"
    />
    <echo message="${bin}${relpersistencepath}/**"/>
    <!-- Copy log4j.properties to provide a sample -->
    <copy file="log4j.properties" todir="${dist}"/>
    <echo message="Packaging complete"/>
  </target>

  最后,该是内部目标了:-Init和-Prep。(它们的名字用一个连字符开头,这可以避免它们被直接使用。虽然这不是必需的,但实践证明这是个好办法,因为这能令你的意图更明确清晰。)-Init打印时间。所有的主目标都依赖于它:

  <!-- Internal targets -->
  <target name="-Init"> <!-- private target, omit description-->
    <!-- Set timestamp and print time -->
    <tstamp/>
    <echo message="Build time: ${TODAY} ${TSTAMP}"/>
  </target>

  -Prep仅当你指定了BuildAll目标时调用。它从以前的构建中删除所有东西──特别是bin和dist目录:

  <target name="-Prep">
    <!-- Delete output directories -->
    <delete dir="${bin}"/>
    <delete dir="${dist}"/>
    <delete dir="${jardir}"/>
    <!-- Create output directories -->
    <mkdir dir="${bin}"/>
    <mkdir dir="${dist}"/>
    <mkdir dir="${jardir}"/>
  </target>
</project>

5.4.2 执行一个构建

  运行Ant构建文件和以前一样──在build.xml上右击鼠标并选择[运行Ant]。但现在你有更多的选项,这是因为你有更多目标。注意,默认目标BuildAll已经被自动选中了,不过你可以用复选框选择其它目标。例如,你可以选择Compile和Javadoc(见图5.5)。

图5.5 你可以在Ant构建对话框中显式选择目标


  你也可以设置目标执行的顺序,只需点击[排序]按钮来打开如图5.6所示的对话框。点击一个目标并点击[上移]或[下移]来改变它的构建顺序。选择好之后,点击[确定]并点击[运行]来开始构建。

图5.6 目标排序对话框。在这里,你可以改变目标执行的顺序。


  和以前一样,输出会显示在控制台视图。不同类型的消息会用不同颜色显示:Ant状态消息是绿色,<echo>消息是橙色,而错误──有的话──是红色。
  如果你很小心,而且没有作出仅对当前Eclipse环境有效的假定(例如所依赖的类路径设置只有你的Eclipse配置才有),你应该能够在命令行构建。要这么做的话,只需在命令行下键入ant,就像你在示例Hello中所做的一样。
  你已经注意包含了项目描述和主目标,这样一来,别人能比较容易使用你的构建文件,因为他们可以在命令行下键入ant -projecthelp。这样会输出以下内容:

C:\eclipse\workspace\persistence>ant -projecthelp
Buildfile: build.xml

Build file for persistence component,
org.eclipseguide.persistence

Main targets:

BuildAll Complete rebuild. Calls Init, Compile, Test, Javadoc, Pa
Compile Compile all Java classes
Javadoc Create Javadoc
Test Run JUnit tests

Default target: BuildAll

  不过由于Ant和Eclipse集成在一起,在Eclipse里运行构建文件有不少好处──尤其是当你刚开始开发时。

5.4.3 调试构建

  虽然Eclipse和Ant没有为Ant提供一个调试器,但它们可以帮助识别和纠正可能发生的多种类型的错误。抵御错误的最前线,自然是由编辑器提供的语法高亮了。在Ant编辑器中,备注一般是红色的,文本内容是黑色的,标签和属性是蓝色的,属性值是绿色的。如果什么东西的颜色和应有的不同(例如有几行代码显示为红色),这会很明显,而你就能知道有什么错了。
  然而,Ant编辑器的语法高亮并不能标识所有错误;除了备注缺少括号和闭标签之外,你你还需要保存你的构建文件以便能够正确解析它。在你保存文件之后,错误会被标识在Ant编辑器旁边的大纲视图中,也会标识在编辑器的右边空白处。点击边缘的红框,就会直接跳到错误处(如图5.7)。

图5.7 Ant编辑器在右边空白处标识了一个语法错误。


  有些错误只有在你运行构建文件时才能识别出来。比如在你使用了一个无效属性时。假设你记得<javac>使用了<fileset>所使用的属性和嵌套元素的一个超集,并且写了如下内容:

<javac dir="${src}" destdir="${bin}">

  确实,<javac>几乎具有<fileset>所使用的所有属性;但唯一的不同之处是,<javac>使用srcdir而<fileset>用的是<dir>。如果运行带有该错误的构建文件,当Ant尝试执行该任务时,会导致在调试器窗口显示如下问题:

[javac] BUILD FAILED: file:C:/eclipse/workspace/persistence/
build.xml:38: The <javac> task doesn't support the "dir" attribute.

  点击该错误,会导致Ant尝试跳转到非法代码。当你在文本[javac]上点击时──这时就成功地找到问题了。(点击任何方括号中的任务名,不只是那些有错误的,都会跳转到构建文件中的对应代码。)
  当然,一旦你解决了最初的一些在构建过程和构建文件中的问题,构建问题经常都涉及到源代码了。这正是Ant和Eclipse集成的闪光点,因为在一个编译错误上点击,就会跳转到源代码中产生问题的地方。
  例如,假设在FileObjectManager类的createObjectManager()方法的参数列表中,你把变量type的名字错拼为typo。当编译时,你会得到一个“未解析的符号”的错误(见图5.8)。

图5.8 Ant调试输出。在一个错误上点击就会跳转到源代码中的对应行。


  在调试视图中的错误信息上点击,就会在编辑器中打开相应的源文件,同时光标会跳到错误所在行。

5.5 总结


  团队开发对开发过程提出了新的需求。其中之一就是对协同团队中不同开发者的工作并给出一个正式构建的需求。因为这应该是一个可重复的过程,简单地给出一个特别的构建是不够的。执行一个正式构建的传统方法是使用一个叫做Make的命令行工具;这个工具具有广泛的兼容性,在多种不同平台上都有其对应版本,但它们都有一个共同的格式,而且语法有些晦涩。
  你当然可以使用Make来构建Java产品,但Java和传统编程语言(如C/C++)在需求上有些许不同;首要的一点就是,Java很努力地要成为一门跨平台语言,这就使得Make不够理想。开发Ant很大程度上就是为了满足这个需求。同时,除了保留Make的精神外,它还引入了一些新特性,包括XML语法和使用Java类的可扩展性。
  因为它和Eclipse的是集成的,一个Ant构建过程既可以在Eclipse内部运行,也可以在外部用命令行运行。这样,在命令行下运行的正式过程,可以完全独立于Eclipse。这个能力提供了额外的好处──开发者们将可以使用任何自己喜欢的开发环境。
  尽管团队开发需要一个构建工具,但Ant的使用并不只局限于团队。即便是在一个像Eclipse这样能够自动编译代码并且为创建Javadoc文档、JAR文件以及p文件提供了很易用的向导的开发环境中,个人也可以从Ant的使用中获益。一个构建过程由多个步骤组成,虽然简单,但可能冗长乏味并很容易出错,所以花一点时间用Ant把构建过程自动化将是一项值得考虑的投资。
转自:http://addone.blogchina.com/1389346.html

posted on 2005-05-26 18:24 weidagang2046 阅读(3220) 评论(0)  编辑  收藏 所属分类: Java


只有注册用户登录后才能发表评论。


网站导航: