下面是我的学习笔记,主要是对些关键的地方的总结,还有就是一些我个人觉得比较有难度和技巧的地方的总结。
首先是一些在使用ant前应该思考的问题,基本上这些问题的答案可以指导你怎么样用好ant
buildfile怎么开始
在build过程中我需要定义哪些property和datatype
build过程中需要创建哪些目录
谁负责一个完整的创建,还有库文件,安装和运行的脚本文件,以及静态的和生成的动态的文档?
如果文件发生了变化,怎么重建?是否需要删除所有的class文件?是否需要删除生成的jar文件?
哪些目录应该在发布前准备好?是否需要将源码和应用一起发布?谁负责发布?
一、ANT的安装和配置(略)
二、ANT入门(略)
三、ANT的学习方法
学习ANT应该从“到底要用ANT来做什么”开始,针对自己的目的来学习,只要能够满足自己的工作的需要就可以了,ANT设计为可扩展的插件式的结构,每天都有人为ANT增加新的插件,因此要将ANT的所有应用掌握是不可能的,从我们的工作需求出发,ANT主要就是用来 compile, test, build, package, deloy, integration test,这也是我使用ANT的一种模式,我已经为这个模式写了一个模板,每次编写新的ANT的时候只需要针对具体的项目把这个模板修改一下就可以了,其实使用ANT是很简单的。
使用来build项目有两个需要重点关注的地方:项目的组织和ANT的buildfile。
四、buildfile的结构
对于ANT的使用者来说,理解ANT的配置文件是最重要的,ANT的XML配置文件的结构很简单,这也是ANT的一个优点,简单易懂,容易上手,ANT的配置文件主要由project,target和task三层树型结构组成如下:
<project name="helloworld" default="master">
<property name="" value=""/>
<target name="master">
<task attribute1="" attribute2=""/>
<!--这是一个虚拟的task,具体到应用可能是ant的core task也可能是optional task-->
</target>
</project>
关于project,target,task的概念,请参考官方文档的6 Using Ant部分。
五、ANT的DataType
前面说到了ANT的配置文件的结构主要由project,target和task组成,其实project,target和task是ANT的四个重要的概念里面的三个,还有一个很重要的概念就是Da ta Elements,这是ANT最容易让人搞不清楚的一个方面,它们表达的是数据而不是任务。数据类型定义包括三个级别,project-level,target-level和task-level,有点类似于类里面定义全局变量,局部变量的方式,不同的是,这里代表了其在ant里面的可视性,如果直接定义在project节点下那就是project-level,所有的target和task都可以引用它们,以次类推。
数据类型分为两个部分:property和DataType。
property:<property/>标签里定义了name-value对。
DataType:用于表示一套复杂的数据集,例如FileSet和Path
数据类型的定义通常和target平级,也就是是数据类型一般直接在project下一层定义,当然也有一些直接定义在task里面,通常我们定义在和target平级的时候,可以在task下面引用这些数据类型。
总结ANT的数据类型如下:
argument参数:从命令行传递到buildfile的参数
environment环境变量:传递到buildfile的环境变量
filelist文件列表:文件不一定存在
fileset文件集:文件必须存在
patternset模式集
filterset过滤集
path路径,还有classpath
mapper映射:定义了输入与输出文件之间的一套复杂的关系。
ANT的数据类型很多,以上列出的是一些常用的数据类型,下面重点总结一些我自己常用的数据类型,如environment,fileset,patternset,path
1、property属性
一个项目可以配置很多property,property可以在在buildfile里面设置,也是可以在外部设置,在task中可以引用property,通过'{'和'}'符号来引用
ANT属性的定义原则是取第一次定义的值,也就是说相同的属性,定义了多次,只有第一次定义的值是有效的。
例如:
#override.properties
test=first
<!-- buildfile -->
<property file="override.properties"/>
<property name="test" value="second"/>
因为override.properties文件中已经设置了test的值,所以在buildfile里面第二次设置的值是无效的,test的值仍然是first
build-in properties
ANT提供了访问系统properties的机制,也就是build-in properites,所谓系统properties,可以查看System.getProperties返回的值,以下是我的机器上的系统properties
-- listing properties --
java.runtime.name=Java(TM) 2 Runtime Environment, Stand...
sun.boot.library.path=D:\jdk1.3.1_03\jre\bin
java.vm.version=1.3.1_03-b03
java.vm.vendor=Sun Microsystems Inc.
java.vendor.url=::URL::http://java.sun.com/
path.separator=;
java.vm.name=Java HotSpot(TM) Client VM
file.encoding.pkg=sun.io
java.vm.specification.name=Java Virtual Machine Specification
user.dir=G:\server
java.runtime.version=1.3.1_03-b03
java.awt.graphicsenv=sun.awt.Win32GraphicsEnvironment
os.arch=x86
java.io.tmpdir=C:\TMP\
line.separator=
java.vm.specification.vendor=Sun Microsystems Inc.
java.awt.fonts=
os.name=Windows 2000
java.library.path=D:\jdk1.3.1_03\bin;.;C:\WINNT\system3...
java.specification.name=Java Platform API Specification
java.class.version=47.0
os.version=5.0
user.home=C:\Documents and Settings\anonymous
user.timezone=
java.awt.printerjob=sun.awt.windows.WPrinterJob
file.encoding=GBK
java.specification.version=1.3
user.name=anonymous
java.class.path=D:\eclipse2.1.2\plugins\org.eclipse.j...
java.vm.specification.version=1.0
java.home=D:\jdk1.3.1_03\jre
user.language=zh
java.specification.vendor=Sun Microsystems Inc.
awt.toolkit=sun.awt.windows.WToolkit
java.vm.info=interpreted mode
java.version=1.3.1_03
java.ext.dirs=D:\jdk1.3.1_03\jre\lib\ext
sun.boot.class.path=D:\jdk1.3.1_03\jre\lib\rt.jar;D:\jdk1...
java.vendor=Sun Microsystems Inc.
file.separator=\
java.vendor.url.bug=::URL::http://java.sun.com/ cgi-bin/bugreport...
sun.cpu.endian=little
sun.io.unicode.encoding=UnicodeLittle
user.region=CN
sun.cpu.isalist=pentium i486 i386
这些build-in properties可以直接象引用properties那样,例如:{java.home}
除此之外,ANT还提供了自身的build-in properties:
basedir
ant.file
ant.version
ant.project.name
ant.java.version
2、Environment环境变量
假设你根据你的工作环境配置了下面的property:
<property name="xalan.home" value="C:/java/xalan-j_2_1_0"/>
显然你的buildfile到其他开发人员的环境下面能够成功运行的可能性很小,因为其他人的xalan很可能在其他目录,使用Environment环境变量可以解决这个问题
<property environment="env"/>
<property name="xalan.home" value="{env.XALAN_HOME}"/>
<target name="checkXalanHome" unless="env.XALAN_HOME">
<fail message="XALAN_HOME must be set!"/>
</target>
关键是:
<property environment="env"/>
设置了这个属性之后,以后可以直接引用环境变量,只要前面加一个env.的前缀就可以了。
3、fileset文件集
fileset 表示了一套文件的集合,通常定义为project-level,ANT的task可以通过显式(explicit)的方式来引用或者直接嵌套 fileset,如果是直接嵌套,这个时候fileset是task-level的,很多ANT的task都支持隐式(implicit)的 fileset,也就是说这些TASK支持fileset的所有属性和嵌套的子元素,和filelist不同的是fileset所表示的文件必须存在。 Fileset可以定义为target-level的,并通过他们的id来引用。
fileset支持以下常用的属性(casesensitive,defaultexcludes请查阅官方文档):
dir 必须指定,用于指定文件所在的目录
excludes 可选,用于指定在dir指定的目录下那些不应该包括进来的文件,内容是逗号分割的文件模式(file pattern)。
excludesfile 跟excludes类似,区别是只能包括一个模式,通常情况下我习惯使用excludes,因为excludes也包括了excludesfile的功能
includes 可选,用于指定在dir指定的目录下那些需要包括进来的文件,内容是逗号分割的文件模式(file pattern)。
includesfile 跟includes类似,区别是只能包括一个模式,通常情况下我习惯使用includes,因为includes也包括了includesfile的功能
除了上面的属性之外,fileset还可以嵌套多个(或0个)以下的patternset元素
<exclude> , <include> , <patternset> <excludesfile> , <includesfile> .
关于patternset的用法,我会在patternset这个部分专门总结。
例子:
<fileset id="sources1" dir="src" includes="**/*.java" excludes="**/test/**/*.java">
</fileset>
等价于
<fileset id="sources2" dir="src">
<include name="**/*.java"/>
<exclude name="**/test/**/*.java"/>
</fileset>
等价于
<fileset id="sources3" dir="src">
<patternset>
<include name="**/*.java"/>
<exclude name="**/test/**/*.java"/>
</patternset>
</fileset>
也等价于
<patternset id="non.test.source">
<include name="**/*.java"/>
<exclude name="**/test/**/*.java"/>
</patternset>
<!-- later in the same buildfile -->
<fileset id="sources4" dir="src">
<patternset refid="non.test.source"/>
</fileset>
4、patternset模式集
fileset 将文件合成一个组,patternset将模式合成一个组,它们的概念很接近,其实fileset依赖于patternset来选择文件,patternset可以表达为target-level,然后通过它的id来引用,也可以直接嵌套在fileset下面,那些支持隐式 (implicit)的fileset的task也支持嵌套的patternset。
patternset支持includes, excludes, includesfile,excludesfile四个属性,这跟fileset的用法一样,还支持以下的嵌套的元素:
0..n 个嵌套的 <include>和<exclude>元素,它们支持以下 属性
name 必须指定,内容为相应的include和exclude的模式。
if 可选,和target的if属性的用法一样
unless 可选,和target的unless属性的用法一样
0..n 个嵌套的 <includesfile> 和 <excludesfile> 元素,它们支持以下 属性:
name 必须指定,内容为相应的include和exclude的模式,但是只能是单个
if 可选,和target的if属性的用法一样
unless 可选,和target的unless属性的用法一样
例子:
patternset的定义
<patternset id="xml.files">
<include name="**/*.dtd,**/*.xml,**/*.xslt"/>
</patternset>
patternset的引用
<fileset dir="{src.dir}">
<patternset refid="{xml.files}"/>
</fileset>
</copy>
5、path路径
path这个DataType可以是attribute也可以是一个嵌套的元素,最常用的是classpath这个形式,但是也用于其他用途。
当作为XML元素使用的时候,path的用法非常的灵活,path支持以下 attribute
location 可选,表示单个文件或者单个路径,它跟path的区别是数量上的,path可以表示多个文件和路径
path 可选,路径或者文件列表,用;或者:作为分隔符
refid 可选,引用其他的path的id,如果在buildfile里面会经常用到同样的path,可以将其定义为一个公用的,然后在使用到这个path的地方通过 refid来引用,如果refid被指定了,path的其他attribute和其嵌套的元素都是禁止使用的。
path支持的嵌套元素如下:
0..n 个嵌套的<pathelement> pathlelement定义在路径下的一个或者多个文件, pathelement支持location和path这两个attribute,用法和path元素一样。
0..n 个嵌套的<fileset>
0..n 个嵌套的<path>
classpath是path元素的一个针对类路径的特殊的元素,具有和path用样的使用语法,但是使用的场合主要是<javac>task下面。
例子:
<classpath>
<pathelement path="{builddir}"/>
</classpath>
等价于
<classpath path="{builddir}"/>
<classpath>
<pathelement path="{builddir1}"/>
<pathelement path="{builddir2}"/>
</classpath>
等价于
<classpath path="{builddir1};{builddir2}"/>
<classpath>
<pathelement path="{builddir}"/>
<fileset dir="{libdir}" includes="**/*.jar"/>
</classpath>
<path>
<pathelement location="{libdir}/servlet.jar"/>
<pathelement location="{libdir}/logging.jar"/>
<pathelement path="{builddir}"/>
<pathelement path="{utilpath}"/>
</path>
六、目标驱动ANT
为什么说目标驱动ANT呢,这其实是我的学习方法,也就是说我使用ANT来做什么,而“做什么”就是我们的目标,也就是buildfile里面的 target,只学习跟我们的目标有关的部分,其他的留到用的时候查阅有关资料,现学现卖,我学习ANT的目的是为了更好的辅助我的项目的创建,而不是要成为ANT的专家。
就我目前的工作内容出发,我需要ANT协助我完成编译,测试,打包,发布,生成文档,有些时候,还会使用到版本控制系统。
组织一个良好的目录结构是完成一次成功的build所必须的,apache的开源项目的目录结构提供了事实上(de facto)的标准,如非特殊需要,我们可以直接采取那样的目录结构。
project_home #项目所在目录
\src #源码
\java #Java主程序代码
\test #Java测试程序代码
\conf #manifest声明文件
\lib #库文件目录
\doc #项目有关说明文档,如果是开源项目,通常该目录下是相应的web content
\api #生成项目的Java API文档的地方
\build #用于创建的临时目录
\classes #编译的结果保存在该目录
\dist #用于发布的临时目录,通常会将build\classes下的文件打包成jar,保存在该目录下
README #项目的说明文件
RELEASE #项目的发布文件
LICENSE #项目的license文件
下面是一个和目录有关的配置的例子:
<project name="helloworld" default="all" basedir=".">
<property name="src.dir" value="src"/>
<property name="lib.dir" value="lib"/>
<property name="build.dir" value="build"/>
<property name="dist.dir" value="dist"/>
<property name="doc.dir" value="doc"/>
<path id="compile.classpath">
<pathelement dir="{lib.dir}"/>
</path>
<path id="test.classpath">
<pathelement refid="compile.classpath"/>
<pathelement dir="{build.dir}/classes"/>
</path>
<!-- the following targets are ignored -->
</project>
编译<javac>:
1。编译器的选择
对于<javac>,除了classic(JDK 1.1/1.2)和modern(JDK 1.3/1.4/1.5),还有不同的编译器可以选择,例如:jikes,jvc,kjc,gcj,sj等等,不过我还没有用过这些编译器,<javac>默认的编译器是你用来运行ANT的 JDK 1.X所带的编译器,如果你想使用自己的编译器,需要如下声明,ANT运行<javac>的时候会自动用你配置的编译器来编译:
<property name="build.compiler" value="jikes"/><!-- 使用jikes编译器 -->
2。<javac>的语法
编译可以说是ANT里面最基本的任务,首先回顾一下命令行下进行编译的命令:
c:\hellworld>javac -d build\classes
-classpath %CLASSPATH%;lib\outlib.jar
-sourcepath src
-g
下面是常用的<javac>语法列表,并附上对应到javac命令的参数
srcdir Java文件所在的目录,相当于javac命令的-sourcepath参数,srcdir也是<javac>的隐式(implicit)的FileSet,因此srcdir支持FileSet的所有特征,有关FileSet的用法请参考前面。
destdir classes文件输出的目录,相当于javac命令的-d参数
debug 生成并输出调试信息,debug="yes"相当于javac命令的-g参数,debug="no"相当于javac命令的-g:none参数
deprecation 输出对于那些使用了deprecation的API的源文件的位置,默认是off,deprecation="on"相当于javac命令的-deprecation参数
optimize 优化,默认是off,optimize="on"相当于javac命令的-o参数
fork 使用外部的JDK编译器来运行javac,默认是no,采用yes可以取得更好的效率,当然对机器的要求也高
failonerror 如果编译错误,build是否继续,默认是true
compiler 类似于全局的属性build.compiler,不过这里是<javac>的attribute而已,直接指定<javac>的编译器,用法和build.compiler一样。
还有很多attribute,请参考官方文档吧。
补充两点:
<javac>支持隐式的FileSet,支持<fileset>的所有attribute,不同的是dir在<javac>里面是srcdir。
<javac> 的srcdir, classpath, sourcepath, bootclasspath, 和extdirs attributes都是 path-like 结构,可以完全通过嵌套的 <src>, <classpath>, <sourcepath>, <bootclasspath> and <extdirs> 元素来代替。
例子:
<javac destdir="{build.classes.dir}"
debug="{build.debug}"
includeAntRuntime="yes"
srcdir="{sirc.dir}">
<classpath refid="compile.classpath"/>
<include name="**/*.java"/>
</javac>
测试<junit>和<junitreport>:
JUnit是事实上(de facto)的Java单元测试工具,ANT集成了JUnit,可以在build过程中执行test suite,并捕获结果和生成报表,关于JUnit的用法,请查阅有关资料,这里只总结ANT怎么将JUnit集成到Build里面去的方法。
发布
让我们先看看发布前必须做的一些准备工作:
1。撰写文档。
2。撰写和平台相关的自启动脚本(bootstrap script),批处理文件(batch file) ,或者程序。
3。撰写安装脚本,使用安装工具。
4。检查版本控制系统中和项目相关的源码,文档,以及其他资料。
5。将版本控制系统中的源码打上标签。
6。运行一次完整的build。
7。运行一次完整的测试。
8。将软件打包成适当的形式,用于发布和安装。
对于打包这个步骤,如下图
Da ta files Java source Documentation
| | |
| _____|_____ |
| | | |
| <javac> <javadoc> |
| | | |
|_____________| |__________________|
| |
jar |
| |
jar file |
|____________________________|
|
|
<zip><gzip><tar>
|
|
Distribution
package
将源码和数据文件打包成JAR,将文档和生成的文档保存在某个目录,然后将整体打包zip或者tar,为不同的平台提供最终的版本以供下载,这就是打包的过程。
打包和发布的一个通用的过程是复制和移动文件的过程,在深入打包的细节之前,有必要总结一下ANT里面和复制,移动以及删除有关的任务(这部分内容大部分来自官方文档,经过翻译和整理)。
<copy>
<copy>将文件或者FileSet复制到一个新的文件或者目录。FileSet用于指定用于复制的文件集合,如果使用了FileSet,<copy>的todir必须指定一个值,<copy>支持的常用的attribute如下:
attribute | description
----------|------------------------------------------------
file | 要复制的文件
tofile | 复制到新的文件的文件名
todir | 复制到新的目录的目录名
overwrite | 默认值为false,也就是只有当被复制的文件比目标文件新的时候才复制,
| 如果需要强制覆盖目标文件,需要将overwrite设置为true
<copy>嵌套<fileset>用于选择用于复制的文件集合,这个时候需要指定todir
下面的例子均选自官方文档
例一
<copy file="myfile.txt" tofile="mycopy.txt"/>
例二
<copy file="myfile.txt" todir="../some/other/dir"/>
例三
<copy todir="../new/dir">
<fileset dir="src_dir"/>
</copy>
例四
<copy todir="../dest/dir">
<fileset dir="src_dir">
<exclude name="**/*.java"/>
</fileset>
</copy>
例五
<copy todir="../dest/dir">
<fileset dir="src_dir" excludes="**/*.java"/>
</copy>
<move>
<move>跟<copy>的区别是移动而不是复制,语法跟<copy>一样,这里不多写了,只提供一些例子:
下面的例子均选自官方文档
例一
<move file="file.orig" tofile="file.moved"/>
例二
<move file="file.orig" todir="dir/to/move/to"/>
例三
<move todir="new/dir/to/move/to">
<fileset dir="src/dir"/>
</move>
例四
<move todir="some/new/dir">
<fileset dir="my/src/dir">
<include name="**/*.jar"/>
<exclude name="**/ant.jar"/>
</fileset>
</move>
<delete>删除单个文件,单个目录,多个文件,多个目录,支持FileSet,通常在清除build产生的临时文件和目录的时候会用到<delete>,一个典型的buildfile一般都会有一个名叫clean的target。
<delete>支持的最常见的attribute如下:
attribute | description
----------|------------------------------------------------
file | 指定文件,<delete file="somefile"/>
dir | 指定目录,<delete dir="somedir"/>
可以嵌套<fileset>,但是如果没有指定FileSet,file和dir其中之一必须指定。
下面的例子均选自官方文档
例一
<delete file="/lib/ant.jar"/>
例二
<delete dir="lib"/>
例三
<delete>
<fileset dir="." includes="**/*.bak"/>
</delete>
例四
<delete includeEmptyDirs="true">
<fileset dir="build"/>
</delete>
例五
<delete includeemptydirs="true">
<fileset dir="build" includes="**/*"/>
</delete>
补充:关于复制,移动,删除,还有一个重要的用法就是filter,不过我用到的机会不多,所以没有怎么研究过,有关详细的资料请查阅官方文档。
发布前还应该准备文档,特别是javadoc文档,<javadoc>是一个非常复杂的task,一共有50多个attribute,幸好只有三个attribute是必须的,那就是sourcepath,sourcefiles,sourcepathref,destdir,对于 sourcepath,sourcefiles,sourcepathref只需要指定其中一个,如果都没有指定,那么必须提供嵌套的<sourcepath>, <fileset> 或者 <packageset>,从字面上很容易理解 sourcepath,sourcefiles,sourcepathref是源文件所在的目录,而destdir是生成的javadoc API所在的目录。
下面是一些我们常用的attribute,没有必要去死记这些attribute,最好的学习方式是理解它们的意义,然后写一个template,再用到的时候根据需要修改这个template就可以了:
attribute | description
----------------|------------------------------------------------
sourcepath |源文件目录,可用嵌套的<sourcepath>代替
sourcefiles |逗号分割的文件列表,可用嵌套的<source>代替
destdir |输出文件所在的目录,通常我们习惯将结果保存在doc/api下
packagenames |逗号分割的java包列表,可用嵌套的<package>代替
packageList |指定一个文件,文件内容是<javadoc>需要处理的包的列表
classpath |指定class文件所在的位置,可用嵌套的<classpath>代替
use |生成class和package的用法
version |生成@version的信息
author |生成@author的信息
windowtitle |生成的doc的浏览的窗口的标题
header |每一页的header信息
footer |每一页的footer信息
bottom |每一页的bottom信息
nodeprecated |不生成@deprecated的信息
常见的可以嵌套的参数如下:
packageset,fileset,package,excludepackage,source,doctitle,header,footer,bottom,它们的具体用法请参考官方文档。
<javadoc> 的sourcepath, classpath 和 bootclasspath是path-like机构,也就是完全可以用嵌套的<sourcepath>, <classpath> 和 <bootclasspath>来代替
给几个例子:
例一
<javadoc packagenames="com.dummy.test.*"
sourcepath="src"
defaultexcludes="yes"
destdir="docs/api"
author="true"
version="true"
use="true"
windowtitle="Test API">
<classpath refid="compile.classpath"/>
</javadoc>
例二
<javadoc author="true"
destdir="{javadoc.dir}"
packagenames="org.example.helloworld.*"
sourcepath="src"
use="true"
version="true"
windowtitle="Helloworld api spec"
private="true">
<classpath refid="compile.classpath"/>
</javadoc>
例三
<javadoc
destdir="docs/api"
author="true"
version="true"
use="true"
windowtitle="Test API">
<packageset dir="src" defaultexcludes="yes">
<include name="com/dummy/test/**" />
<exclude name="com/dummy/test/doc-files/**"/>
</packageset>
<doctitle><![CDATA[<h1>Test</h1>]]></doctitle>
<bottom><![CDATA[<i>Copyright © 2000 Dummy Corp. All Rights Reserved.</i>]]></bottom>
<tag name="todo" scope="all" description="To do:" />
<group title="Group 1 Packages" packages="com.dummy.test.a*"/>
<group title="Group 2 Packages" packages="com.dummy.test.b*:com.dummy.test.c*"/>
<link offline="true" href="::URL::http://java.sun.com/ products/jdk/1.2/docs/api/" packagelistLoc="C:\tmp"/>
<link href="::URL::http://developer.java.sun.com/developer/products/xml/docs/api/"/>
</javadoc>
准备文档的内容除了javadoc之外,那些install script和其他document也是内容之一,将其复制到待打包的目录。
例如
<target name="prepare-docs" depends="init">
<property name="readme.file" location="readme.txt"/>
<copy file="{readme.file}" todir="{doc.dir}"/>
<copy file="{readme.file}" tofile="{doc.dir}/README"/>
</target>
因为windows平台的文本文件行结束标志为\r\n,而unix平台的行结束标志为\n,所以还要考虑平台之间的纯文本文件的格式问题,整理中,即将推出。
任何一个复杂的程序除了代码之外还包括了数据,例如初始化数据,配置文件,XML文件,schema文件,还有些国际化和本地化文本文件,最理想化的方式是将这些数据打包在JAR文件里面,然后通过java的getResource或者getResourceAsStream这样的方法来访问这些数据,对于打包在JAR文件例的数据,java提供了目录模式(reference pattern)来访问它们,如org/example/helloworld /helloworld.properties。
首先是一些在使用ant前应该思考的问题,基本上这些问题的答案可以指导你怎么样用好ant
buildfile怎么开始
在build过程中我需要定义哪些property和datatype
build过程中需要创建哪些目录
谁负责一个完整的创建,还有库文件,安装和运行的脚本文件,以及静态的和生成的动态的文档?
如果文件发生了变化,怎么重建?是否需要删除所有的class文件?是否需要删除生成的jar文件?
哪些目录应该在发布前准备好?是否需要将源码和应用一起发布?谁负责发布?
一、ANT的安装和配置(略)
二、ANT入门(略)
三、ANT的学习方法
学习ANT应该从“到底要用ANT来做什么”开始,针对自己的目的来学习,只要能够满足自己的工作的需要就可以了,ANT设计为可扩展的插件式的结构,每天都有人为ANT增加新的插件,因此要将ANT的所有应用掌握是不可能的,从我们的工作需求出发,ANT主要就是用来 compile, test, build, package, deloy, integration test,这也是我使用ANT的一种模式,我已经为这个模式写了一个模板,每次编写新的ANT的时候只需要针对具体的项目把这个模板修改一下就可以了,其实使用ANT是很简单的。
使用来build项目有两个需要重点关注的地方:项目的组织和ANT的buildfile。
四、buildfile的结构
对于ANT的使用者来说,理解ANT的配置文件是最重要的,ANT的XML配置文件的结构很简单,这也是ANT的一个优点,简单易懂,容易上手,ANT的配置文件主要由project,target和task三层树型结构组成如下:
<project name="helloworld" default="master">
<property name="" value=""/>
<target name="master">
<task attribute1="" attribute2=""/>
<!--这是一个虚拟的task,具体到应用可能是ant的core task也可能是optional task-->
</target>
</project>
关于project,target,task的概念,请参考官方文档的6 Using Ant部分。
五、ANT的DataType
前面说到了ANT的配置文件的结构主要由project,target和task组成,其实project,target和task是ANT的四个重要的概念里面的三个,还有一个很重要的概念就是Da
数据类型分为两个部分:property和DataType。
property:<property/>标签里定义了name-value对。
DataType:用于表示一套复杂的数据集,例如FileSet和Path
数据类型的定义通常和target平级,也就是是数据类型一般直接在project下一层定义,当然也有一些直接定义在task里面,通常我们定义在和target平级的时候,可以在task下面引用这些数据类型。
总结ANT的数据类型如下:
argument参数:从命令行传递到buildfile的参数
environment环境变量:传递到buildfile的环境变量
filelist文件列表:文件不一定存在
fileset文件集:文件必须存在
patternset模式集
filterset过滤集
path路径,还有classpath
mapper映射:定义了输入与输出文件之间的一套复杂的关系。
ANT的数据类型很多,以上列出的是一些常用的数据类型,下面重点总结一些我自己常用的数据类型,如environment,fileset,patternset,path
1、property属性
一个项目可以配置很多property,property可以在在buildfile里面设置,也是可以在外部设置,在task中可以引用property,通过'{'和'}'符号来引用
ANT属性的定义原则是取第一次定义的值,也就是说相同的属性,定义了多次,只有第一次定义的值是有效的。
例如:
#override.properties
test=first
<!-- buildfile -->
<property file="override.properties"/>
<property name="test" value="second"/>
因为override.properties文件中已经设置了test的值,所以在buildfile里面第二次设置的值是无效的,test的值仍然是first
build-in properties
ANT提供了访问系统properties的机制,也就是build-in properites,所谓系统properties,可以查看System.getProperties返回的值,以下是我的机器上的系统properties
-- listing properties --
java.runtime.name=Java(TM) 2 Runtime Environment, Stand...
sun.boot.library.path=D:\jdk1.3.1_03\jre\bin
java.vm.version=1.3.1_03-b03
java.vm.vendor=Sun Microsystems Inc.
java.vendor.url=::URL::http://java.sun.com/
path.separator=;
java.vm.name=Java HotSpot(TM) Client VM
file.encoding.pkg=sun.io
java.vm.specification.name=Java Virtual Machine Specification
user.dir=G:\server
java.runtime.version=1.3.1_03-b03
java.awt.graphicsenv=sun.awt.Win32GraphicsEnvironment
os.arch=x86
java.io.tmpdir=C:\TMP\
line.separator=
java.vm.specification.vendor=Sun Microsystems Inc.
java.awt.fonts=
os.name=Windows 2000
java.library.path=D:\jdk1.3.1_03\bin;.;C:\WINNT\system3...
java.specification.name=Java Platform API Specification
java.class.version=47.0
os.version=5.0
user.home=C:\Documents and Settings\anonymous
user.timezone=
java.awt.printerjob=sun.awt.windows.WPrinterJob
file.encoding=GBK
java.specification.version=1.3
user.name=anonymous
java.class.path=D:\eclipse2.1.2\plugins\org.eclipse.j...
java.vm.specification.version=1.0
java.home=D:\jdk1.3.1_03\jre
user.language=zh
java.specification.vendor=Sun Microsystems Inc.
awt.toolkit=sun.awt.windows.WToolkit
java.vm.info=interpreted mode
java.version=1.3.1_03
java.ext.dirs=D:\jdk1.3.1_03\jre\lib\ext
sun.boot.class.path=D:\jdk1.3.1_03\jre\lib\rt.jar;D:\jdk1...
java.vendor=Sun Microsystems Inc.
file.separator=\
java.vendor.url.bug=::URL::http://java.sun.com/ cgi-bin/bugreport...
sun.cpu.endian=little
sun.io.unicode.encoding=UnicodeLittle
user.region=CN
sun.cpu.isalist=pentium i486 i386
这些build-in properties可以直接象引用properties那样,例如:{java.home}
除此之外,ANT还提供了自身的build-in properties:
basedir
ant.file
ant.version
ant.project.name
ant.java.version
2、Environment环境变量
假设你根据你的工作环境配置了下面的property:
<property name="xalan.home" value="C:/java/xalan-j_2_1_0"/>
显然你的buildfile到其他开发人员的环境下面能够成功运行的可能性很小,因为其他人的xalan很可能在其他目录,使用Environment环境变量可以解决这个问题
<property environment="env"/>
<property name="xalan.home" value="{env.XALAN_HOME}"/>
<target name="checkXalanHome" unless="env.XALAN_HOME">
<fail message="XALAN_HOME must be set!"/>
</target>
关键是:
<property environment="env"/>
设置了这个属性之后,以后可以直接引用环境变量,只要前面加一个env.的前缀就可以了。
3、fileset文件集
fileset 表示了一套文件的集合,通常定义为project-level,ANT的task可以通过显式(explicit)的方式来引用或者直接嵌套 fileset,如果是直接嵌套,这个时候fileset是task-level的,很多ANT的task都支持隐式(implicit)的 fileset,也就是说这些TASK支持fileset的所有属性和嵌套的子元素,和filelist不同的是fileset所表示的文件必须存在。 Fileset可以定义为target-level的,并通过他们的id来引用。
fileset支持以下常用的属性(casesensitive,defaultexcludes请查阅官方文档):
dir 必须指定,用于指定文件所在的目录
excludes 可选,用于指定在dir指定的目录下那些不应该包括进来的文件,内容是逗号分割的文件模式(file pattern)。
excludesfile 跟excludes类似,区别是只能包括一个模式,通常情况下我习惯使用excludes,因为excludes也包括了excludesfile的功能
includes 可选,用于指定在dir指定的目录下那些需要包括进来的文件,内容是逗号分割的文件模式(file pattern)。
includesfile 跟includes类似,区别是只能包括一个模式,通常情况下我习惯使用includes,因为includes也包括了includesfile的功能
除了上面的属性之外,fileset还可以嵌套多个(或0个)以下的patternset元素
<exclude> , <include> , <patternset> <excludesfile> , <includesfile> .
关于patternset的用法,我会在patternset这个部分专门总结。
例子:
<fileset id="sources1" dir="src" includes="**/*.java" excludes="**/test/**/*.java">
</fileset>
等价于
<fileset id="sources2" dir="src">
<include name="**/*.java"/>
<exclude name="**/test/**/*.java"/>
</fileset>
等价于
<fileset id="sources3" dir="src">
<patternset>
<include name="**/*.java"/>
<exclude name="**/test/**/*.java"/>
</patternset>
</fileset>
也等价于
<patternset id="non.test.source">
<include name="**/*.java"/>
<exclude name="**/test/**/*.java"/>
</patternset>
<!-- later in the same buildfile -->
<fileset id="sources4" dir="src">
<patternset refid="non.test.source"/>
</fileset>
4、patternset模式集
fileset 将文件合成一个组,patternset将模式合成一个组,它们的概念很接近,其实fileset依赖于patternset来选择文件,patternset可以表达为target-level,然后通过它的id来引用,也可以直接嵌套在fileset下面,那些支持隐式 (implicit)的fileset的task也支持嵌套的patternset。
patternset支持includes, excludes, includesfile,excludesfile四个属性,这跟fileset的用法一样,还支持以下的嵌套的元素:
0..n 个嵌套的 <include>和<exclude>元素,它们支持以下 属性
name 必须指定,内容为相应的include和exclude的模式。
if 可选,和target的if属性的用法一样
unless 可选,和target的unless属性的用法一样
0..n 个嵌套的 <includesfile> 和 <excludesfile> 元素,它们支持以下 属性:
name 必须指定,内容为相应的include和exclude的模式,但是只能是单个
if 可选,和target的if属性的用法一样
unless 可选,和target的unless属性的用法一样
例子:
patternset的定义
<patternset id="xml.files">
<include name="**/*.dtd,**/*.xml,**/*.xslt"/>
</patternset>
patternset的引用
<fileset dir="{src.dir}">
<patternset refid="{xml.files}"/>
</fileset>
</copy>
5、path路径
path这个DataType可以是attribute也可以是一个嵌套的元素,最常用的是classpath这个形式,但是也用于其他用途。
当作为XML元素使用的时候,path的用法非常的灵活,path支持以下 attribute
location 可选,表示单个文件或者单个路径,它跟path的区别是数量上的,path可以表示多个文件和路径
path 可选,路径或者文件列表,用;或者:作为分隔符
refid 可选,引用其他的path的id,如果在buildfile里面会经常用到同样的path,可以将其定义为一个公用的,然后在使用到这个path的地方通过 refid来引用,如果refid被指定了,path的其他attribute和其嵌套的元素都是禁止使用的。
path支持的嵌套元素如下:
0..n 个嵌套的<pathelement> pathlelement定义在路径下的一个或者多个文件, pathelement支持location和path这两个attribute,用法和path元素一样。
0..n 个嵌套的<fileset>
0..n 个嵌套的<path>
classpath是path元素的一个针对类路径的特殊的元素,具有和path用样的使用语法,但是使用的场合主要是<javac>task下面。
例子:
<classpath>
<pathelement path="{builddir}"/>
</classpath>
等价于
<classpath path="{builddir}"/>
<classpath>
<pathelement path="{builddir1}"/>
<pathelement path="{builddir2}"/>
</classpath>
等价于
<classpath path="{builddir1};{builddir2}"/>
<classpath>
<pathelement path="{builddir}"/>
<fileset dir="{libdir}" includes="**/*.jar"/>
</classpath>
<path>
<pathelement location="{libdir}/servlet.jar"/>
<pathelement location="{libdir}/logging.jar"/>
<pathelement path="{builddir}"/>
<pathelement path="{utilpath}"/>
</path>
六、目标驱动ANT
为什么说目标驱动ANT呢,这其实是我的学习方法,也就是说我使用ANT来做什么,而“做什么”就是我们的目标,也就是buildfile里面的 target,只学习跟我们的目标有关的部分,其他的留到用的时候查阅有关资料,现学现卖,我学习ANT的目的是为了更好的辅助我的项目的创建,而不是要成为ANT的专家。
就我目前的工作内容出发,我需要ANT协助我完成编译,测试,打包,发布,生成文档,有些时候,还会使用到版本控制系统。
组织一个良好的目录结构是完成一次成功的build所必须的,apache的开源项目的目录结构提供了事实上(de facto)的标准,如非特殊需要,我们可以直接采取那样的目录结构。
project_home #项目所在目录
\src #源码
\java #Java主程序代码
\test #Java测试程序代码
\conf #manifest声明文件
\lib #库文件目录
\doc #项目有关说明文档,如果是开源项目,通常该目录下是相应的web content
\api #生成项目的Java API文档的地方
\build #用于创建的临时目录
\classes #编译的结果保存在该目录
\dist #用于发布的临时目录,通常会将build\classes下的文件打包成jar,保存在该目录下
README #项目的说明文件
RELEASE #项目的发布文件
LICENSE #项目的license文件
下面是一个和目录有关的配置的例子:
<project name="helloworld" default="all" basedir=".">
<property name="src.dir" value="src"/>
<property name="lib.dir" value="lib"/>
<property name="build.dir" value="build"/>
<property name="dist.dir" value="dist"/>
<property name="doc.dir" value="doc"/>
<path id="compile.classpath">
<pathelement dir="{lib.dir}"/>
</path>
<path id="test.classpath">
<pathelement refid="compile.classpath"/>
<pathelement dir="{build.dir}/classes"/>
</path>
<!-- the following targets are ignored -->
</project>
编译<javac>:
1。编译器的选择
对于<javac>,除了classic(JDK 1.1/1.2)和modern(JDK 1.3/1.4/1.5),还有不同的编译器可以选择,例如:jikes,jvc,kjc,gcj,sj等等,不过我还没有用过这些编译器,<javac>默认的编译器是你用来运行ANT的 JDK 1.X所带的编译器,如果你想使用自己的编译器,需要如下声明,ANT运行<javac>的时候会自动用你配置的编译器来编译:
<property name="build.compiler" value="jikes"/><!-- 使用jikes编译器 -->
2。<javac>的语法
编译可以说是ANT里面最基本的任务,首先回顾一下命令行下进行编译的命令:
c:\hellworld>javac -d build\classes
-classpath %CLASSPATH%;lib\outlib.jar
-sourcepath src
-g
下面是常用的<javac>语法列表,并附上对应到javac命令的参数
srcdir Java文件所在的目录,相当于javac命令的-sourcepath参数,srcdir也是<javac>的隐式(implicit)的FileSet,因此srcdir支持FileSet的所有特征,有关FileSet的用法请参考前面。
destdir classes文件输出的目录,相当于javac命令的-d参数
debug 生成并输出调试信息,debug="yes"相当于javac命令的-g参数,debug="no"相当于javac命令的-g:none参数
deprecation 输出对于那些使用了deprecation的API的源文件的位置,默认是off,deprecation="on"相当于javac命令的-deprecation参数
optimize 优化,默认是off,optimize="on"相当于javac命令的-o参数
fork 使用外部的JDK编译器来运行javac,默认是no,采用yes可以取得更好的效率,当然对机器的要求也高
failonerror 如果编译错误,build是否继续,默认是true
compiler 类似于全局的属性build.compiler,不过这里是<javac>的attribute而已,直接指定<javac>的编译器,用法和build.compiler一样。
还有很多attribute,请参考官方文档吧。
补充两点:
<javac>支持隐式的FileSet,支持<fileset>的所有attribute,不同的是dir在<javac>里面是srcdir。
<javac> 的srcdir, classpath, sourcepath, bootclasspath, 和extdirs attributes都是 path-like 结构,可以完全通过嵌套的 <src>, <classpath>, <sourcepath>, <bootclasspath> and <extdirs> 元素来代替。
例子:
<javac destdir="{build.classes.dir}"
debug="{build.debug}"
includeAntRuntime="yes"
srcdir="{sirc.dir}">
<classpath refid="compile.classpath"/>
<include name="**/*.java"/>
</javac>
测试<junit>和<junitreport>:
JUnit是事实上(de facto)的Java单元测试工具,ANT集成了JUnit,可以在build过程中执行test suite,并捕获结果和生成报表,关于JUnit的用法,请查阅有关资料,这里只总结ANT怎么将JUnit集成到Build里面去的方法。
发布
让我们先看看发布前必须做的一些准备工作:
1。撰写文档。
2。撰写和平台相关的自启动脚本(bootstrap script),批处理文件(batch file) ,或者程序。
3。撰写安装脚本,使用安装工具。
4。检查版本控制系统中和项目相关的源码,文档,以及其他资料。
5。将版本控制系统中的源码打上标签。
6。运行一次完整的build。
7。运行一次完整的测试。
8。将软件打包成适当的形式,用于发布和安装。
对于打包这个步骤,如下图
Da
| | |
| _____|_____ |
| | | |
| <javac> <javadoc> |
| | | |
|_____________| |__________________|
| |
jar |
| |
jar file |
|____________________________|
|
|
<zip><gzip><tar>
|
|
Distribution
package
将源码和数据文件打包成JAR,将文档和生成的文档保存在某个目录,然后将整体打包zip或者tar,为不同的平台提供最终的版本以供下载,这就是打包的过程。
打包和发布的一个通用的过程是复制和移动文件的过程,在深入打包的细节之前,有必要总结一下ANT里面和复制,移动以及删除有关的任务(这部分内容大部分来自官方文档,经过翻译和整理)。
<copy>
<copy>将文件或者FileSet复制到一个新的文件或者目录。FileSet用于指定用于复制的文件集合,如果使用了FileSet,<copy>的todir必须指定一个值,<copy>支持的常用的attribute如下:
attribute | description
----------|------------------------------------------------
file | 要复制的文件
tofile | 复制到新的文件的文件名
todir | 复制到新的目录的目录名
overwrite | 默认值为false,也就是只有当被复制的文件比目标文件新的时候才复制,
| 如果需要强制覆盖目标文件,需要将overwrite设置为true
<copy>嵌套<fileset>用于选择用于复制的文件集合,这个时候需要指定todir
下面的例子均选自官方文档
例一
<copy file="myfile.txt" tofile="mycopy.txt"/>
例二
<copy file="myfile.txt" todir="../some/other/dir"/>
例三
<copy todir="../new/dir">
<fileset dir="src_dir"/>
</copy>
例四
<copy todir="../dest/dir">
<fileset dir="src_dir">
<exclude name="**/*.java"/>
</fileset>
</copy>
例五
<copy todir="../dest/dir">
<fileset dir="src_dir" excludes="**/*.java"/>
</copy>
<move>
<move>跟<copy>的区别是移动而不是复制,语法跟<copy>一样,这里不多写了,只提供一些例子:
下面的例子均选自官方文档
例一
<move file="file.orig" tofile="file.moved"/>
例二
<move file="file.orig" todir="dir/to/move/to"/>
例三
<move todir="new/dir/to/move/to">
<fileset dir="src/dir"/>
</move>
例四
<move todir="some/new/dir">
<fileset dir="my/src/dir">
<include name="**/*.jar"/>
<exclude name="**/ant.jar"/>
</fileset>
</move>
<delete>删除单个文件,单个目录,多个文件,多个目录,支持FileSet,通常在清除build产生的临时文件和目录的时候会用到<delete>,一个典型的buildfile一般都会有一个名叫clean的target。
<delete>支持的最常见的attribute如下:
attribute | description
----------|------------------------------------------------
file | 指定文件,<delete file="somefile"/>
dir | 指定目录,<delete dir="somedir"/>
可以嵌套<fileset>,但是如果没有指定FileSet,file和dir其中之一必须指定。
下面的例子均选自官方文档
例一
<delete file="/lib/ant.jar"/>
例二
<delete dir="lib"/>
例三
<delete>
<fileset dir="." includes="**/*.bak"/>
</delete>
例四
<delete includeEmptyDirs="true">
<fileset dir="build"/>
</delete>
例五
<delete includeemptydirs="true">
<fileset dir="build" includes="**/*"/>
</delete>
补充:关于复制,移动,删除,还有一个重要的用法就是filter,不过我用到的机会不多,所以没有怎么研究过,有关详细的资料请查阅官方文档。
发布前还应该准备文档,特别是javadoc文档,<javadoc>是一个非常复杂的task,一共有50多个attribute,幸好只有三个attribute是必须的,那就是sourcepath,sourcefiles,sourcepathref,destdir,对于 sourcepath,sourcefiles,sourcepathref只需要指定其中一个,如果都没有指定,那么必须提供嵌套的<sourcepath>, <fileset> 或者 <packageset>,从字面上很容易理解 sourcepath,sourcefiles,sourcepathref是源文件所在的目录,而destdir是生成的javadoc API所在的目录。
下面是一些我们常用的attribute,没有必要去死记这些attribute,最好的学习方式是理解它们的意义,然后写一个template,再用到的时候根据需要修改这个template就可以了:
attribute | description
----------------|------------------------------------------------
sourcepath |源文件目录,可用嵌套的<sourcepath>代替
sourcefiles |逗号分割的文件列表,可用嵌套的<source>代替
destdir |输出文件所在的目录,通常我们习惯将结果保存在doc/api下
packagenames |逗号分割的java包列表,可用嵌套的<package>代替
packageList |指定一个文件,文件内容是<javadoc>需要处理的包的列表
classpath |指定class文件所在的位置,可用嵌套的<classpath>代替
use |生成class和package的用法
version |生成@version的信息
author |生成@author的信息
windowtitle |生成的doc的浏览的窗口的标题
header |每一页的header信息
footer |每一页的footer信息
bottom |每一页的bottom信息
nodeprecated |不生成@deprecated的信息
常见的可以嵌套的参数如下:
packageset,fileset,package,excludepackage,source,doctitle,header,footer,bottom,它们的具体用法请参考官方文档。
<javadoc> 的sourcepath, classpath 和 bootclasspath是path-like机构,也就是完全可以用嵌套的<sourcepath>, <classpath> 和 <bootclasspath>来代替
给几个例子:
例一
<javadoc packagenames="com.dummy.test.*"
sourcepath="src"
defaultexcludes="yes"
destdir="docs/api"
author="true"
version="true"
use="true"
windowtitle="Test API">
<classpath refid="compile.classpath"/>
</javadoc>
例二
<javadoc author="true"
destdir="{javadoc.dir}"
packagenames="org.example.helloworld.*"
sourcepath="src"
use="true"
version="true"
windowtitle="Helloworld api spec"
private="true">
<classpath refid="compile.classpath"/>
</javadoc>
例三
<javadoc
destdir="docs/api"
author="true"
version="true"
use="true"
windowtitle="Test API">
<packageset dir="src" defaultexcludes="yes">
<include name="com/dummy/test/**" />
<exclude name="com/dummy/test/doc-files/**"/>
</packageset>
<doctitle><![CDATA[<h1>Test</h1>]]></doctitle>
<bottom><![CDATA[<i>Copyright © 2000 Dummy Corp. All Rights Reserved.</i>]]></bottom>
<tag name="todo" scope="all" description="To do:" />
<group title="Group 1 Packages" packages="com.dummy.test.a*"/>
<group title="Group 2 Packages" packages="com.dummy.test.b*:com.dummy.test.c*"/>
<link offline="true" href="::URL::http://java.sun.com/ products/jdk/1.2/docs/api/" packagelistLoc="C:\tmp"/>
<link href="::URL::http://developer.java.sun.com/developer/products/xml/docs/api/"/>
</javadoc>
准备文档的内容除了javadoc之外,那些install script和其他document也是内容之一,将其复制到待打包的目录。
例如
<target name="prepare-docs" depends="init">
<property name="readme.file" location="readme.txt"/>
<copy file="{readme.file}" todir="{doc.dir}"/>
<copy file="{readme.file}" tofile="{doc.dir}/README"/>
</target>
因为windows平台的文本文件行结束标志为\r\n,而unix平台的行结束标志为\n,所以还要考虑平台之间的纯文本文件的格式问题,整理中,即将推出。
任何一个复杂的程序除了代码之外还包括了数据,例如初始化数据,配置文件,XML文件,schema文件,还有些国际化和本地化文本文件,最理想化的方式是将这些数据打包在JAR文件里面,然后通过java的getResource或者getResourceAsStream这样的方法来访问这些数据,对于打包在JAR文件例的数据,java提供了目录模式(reference pattern)来访问它们,如org/example/helloworld /helloworld.properties。