《Ant权威指南》笔记(一)
Ant的由来(序)
James Duncan Davidson当年用纯Java开发Tomcat的时候,不仅想让它跨平台运行,还想要在不同的操作系统上都能够进行开发和构建。这种较大的项目的编译构建过程是很复杂,需要用到很多工具和脚本(比如GNU make,Shell脚本,批处理文件等等)处理资源、处理依赖项、控制编译过程、打包(Jar,War),某些特别的Java技术如EJB、RMI在编译打包时还需要特别处理。James尝试了很多工具和方案,最后的结论就是太TM难用了。
首先是慢。举个栗子,编译java代码要用javac,javac其实就是包装了下java用于编译的Java工具类(JDK工具都是用纯Java写的,JDK目录下的那些exe文件实际上都是调用Java类来实现具体功能的)。但是用make,shell这些工具每次调用javac,都要开新进程创建新的VM实例,如果每编译一个文件调用一次javac,开销就老大了,不慢才怪呢。调用命令后还必须要解析控制台输出信息才能知道执行情况,更不可能使用Java提供的异常和错误信息来确定执行状态。
然后是累。make,shell脚本要跨平台太困难,在不同的系统上要用不同的版本;而且要编写shell脚本和make脚本(make只是shell基础上的语言扩展),得有linux编程基础,门槛还是比较高的;这类脚本编写起来容易出错(Makefile的tab问题能烦死人),难以调试。总之,如果构建过程用了一堆这样的脚本,维护起来肯定是又烦又累。慢的问题还能通过一次编译一批文件来缓解,但这个问题可就无解了。
James实在受不了了,就自己用Java开发了一个小工具,就是Ant,用来编译和打包Java项目。构建中用到JDK中工具时都是直接调用Java类,而不是从命令行调用,慢的问题就解决了。构建中直接向Java编译类提供或获取数据,编译过程中有什么问题,也能进行错误或异常处理。因为是用Java写的,天然就是可以跨平台的使用的。Ant从配置文件中读取数据来控制构建过程,刚开始的时候用Properties文件,但是Properties文件难用表达比较复杂的层次结构关系,当他把Ant解决方案设计成"工程-目标-任务"的层次结构时,改用XML作为构建配置文件,XML文件是很好理解和掌握的,学习门槛变得非常低。再后来又利用反射功能,支持自定义任务,Ant的功能就不再仅限于构建Java项目了,可以使用在更广泛的场合。至此Ant就基本成型了。
James完成这个工具之后,自己用着很爽并且在网上共享,然后就没怎么再关注了,直到后来有一天忽然发现老多人都已经在用他的这个杰作了。James说他很庆幸当时没有把精力一直花在这上面,否则可以能会不自觉的加入很多更本不必要的功能,让Ant变得臃肿复杂,反而不好了。
这个故事除了说了Ant的来源和特点外,还有一点启示:如果遇到问题,已有的工具都不能满足需求,就应当另找一个。如果不存在这样的工具,就自己动手创建一个。然后与大家共享,其他数以千计的人可能有着类似的难题。
第一章 Ant入门
ant的使用非常简单:安装好Ant后(最好配置Path环境变量),在构建文件中(默认名字为build.xml)配置好构建任务,然后调用ant命令,配置的任务就开始执行了。
1.编写构建文件
创建一个用于编译和打包Java项目的配置文件build.xml(名字可以随便起,如mybuild.xml,执行的时候用-f 选项指定这个文件就可以):
<?xml version="1.0" encoding="UTF-8" ?> <project name="AntDemo" default="compile" basedir="."> <property name="src.dir" value="src" /> <property name="build.dir" value="build" /> <property name="build.classes" value="${build.dir}/classes" /> <property name="build.lib" value="${build.dir}/lib"/> <target name="prepare" description="create the build directories prior to the comile target"> <mkdir dir="${build.dir}" /> <mkdir dir="${build.classes}" /> <mkdir dir="${build.lib}" /> </target> <target name="clean" description="removes all generated files"> <delete dir="${build.dir}" /> </target> <target name="compile" depends="prepare" description="compiles all source code"> <javac srcdir="${src.dir}" destdir="${build.classes}" /> </target> <target name="jar" depends="compile" description="generates jar file "> <jar jarfile="${build.lib}/antDemo.jar" basedir="${build.classes}" excludes="**/*Test.class"/> </target> <target name="all" depends="clean, jar" description="clean, compile, generate jar file"/> </project>
这个配置文件很好理解,用任何文本编辑器都能编写,只要保证是一个合法的XML文件并且用了正确的标签和属性,就可以使用ant运行。
根据这个配置文件就可以看出。Ant把任何任务都分成了3层。
- 最顶层的Project
- 目标Target(可以在ant命令中调用的基本单位)。如clean, compile, jar 等,在命令行可以这样调用:ant clean或 ant clean jar。Target之间可以存在依赖关系,一个Target执行前会先执行它依赖的那些Target。Project可以设置一个默认的Targe, ant命令中不指定任何target时就调用这个默认的。
- 任务Task。 各种Task可以执行各种不同的具体任务。如mkdir, delete, javac, jar等。Task可以分3种,核心任务和可选任务有100多种,常用的功能全都覆盖了
- 核心Task。Ant内置的任务
- 可选任务。第三方提供的,把相应的Jar包放到Ant安装目录的lib目录下就能使用。
- 自定义任务
Task详细说明可以查看官方文档: http://ant.apache.org/manual/tasklist.html
Ant文件中还能用Property标签配置属性值,在定义之后的其他地方就可以引用,避免硬编码。
所有的构建文件都要有且只能有一个<project>元素,其中至少要有一个<target> 元素。project的defualt属性没有默认值,如果这个属性没有设置,不指定Target运行ant不会调用任何target。
2.运行Ant
ant命令语法如下,详细选项说明列在后面。
ant [option [option...]] [target [target...]]
调用Ant时默认会在当前目录中查找默认的构建文件名:build.xml。
当然也可以用ant -f buildfile的方式手动指定构建文件(-f, -buildfile, -file都是等效的)。
调用Ant时可以指定一个或多个要执行的Target。如果不指定就调用Project标签default属性中配置的默认Target(上面这个例子中就是compile目标)。
以上面的构建配置文件为例,下面的几种调用都可以:
ant 调用默认构建文件(build.xml)中的默认目标(compile) ant -f mybuild.xml jar 调用构建文件mybuild.xml中的jar目标 ant clean jar 调用默认构建文件(build.xml)中的clean和jar目标。需要注意多个目标会按调用先后顺序执行。如果调用ant jar clean就是先编译打包(jar),然后全清理掉(clean),就白干了。
Ant执行时会按执行顺序显示每个Target的名字,也会显示每个任务的名字([任务名])和任务中输出的信息。直接调用ant的输出结果为:
Buildfile: e:\wsJava\AntDemo\build.xml clean: [delete] Deleting directory e:\wsJava\AntDemo\build prepare: [mkdir] Created dir: e:\wsJava\AntDemo\build [mkdir] Created dir: e:\wsJava\AntDemo\build\classes [mkdir] Created dir: e:\wsJava\AntDemo\build\lib compile: [javac] Compiling 2 source files to e:\wsJava\AntDemo\build\classes jar: [jar] Building jar: e:\wsJava\AntDemo\build\lib\antDemo.jar all: BUILD SUCCESSFUL Total time: 0 seconds
3.查看构建文件中所有目标
构建文件中的目标description属性可有可无,除了方便人看外没什么卵用。有没有这个属性叫法也不一样的(没有实际作用),有该属性的叫主目标,没有的叫子目标。下面这个命令可以列出构建文件中的所有目标和description信息。
ant [-f BUILDFILE] -projecthelp
4.Ant命令选项
- -h, -help 查看帮助信息
- -p, -projecthelp 查看构建文件中的所有目标信息
- -version 显示Ant版本
- -q, -quiet 抑制并非由构建文件中echo任务所产生的大多数输出消息
- -S, -silent 只显示Task输出和构建失败信息
- -v, -verbose 显示构建过程中每个操作的详细消息, 不能和-debug同时使用
- -d, -debug 显示Ant和任务开发人员已经标志位调试信息的消息。不能与-verbose同时使用
- -e, -emacs 对日志消息进行格式化,使其能够Emacs的shell模式解析。具体就是打印任务消息时不缩排也不输出前面的 [任务名]
- -diagnostics 显示对调试有用的信息
- -f <file>, -buildfile <file>, -file <file> 指定一个构建文件,而不是使用默认的build.xml
- -D<property>=<value> 通过命令行向构建过程中传递属性值
- -propertyfile <propertyfile> 从property文件中加载属性值并传递到构建过程
- -s <file>, -find <file> 指定Ant应当使用的构建文件,如果指定的filename文件在当前目录中没找到,就到父目录中进行搜索,直到到达文件系统的根,还找不到则构建失败。
- -k, -keep-going 执行不依赖失败目标的所有目标。
- -lib <path> 指定查找jars和classes的目录
- -l <file>, -logfile <file> 将日志重定向到指定文件
- -logger <class> 指定一个类来处理Ant的日志记录。该类必须实现了org.apache.tools.ant.BuildLogger接口
- -listener <class> 为Ant设置一个监听类,将其增加到Ant的监听器列表中。Ant与IDE或其他程序集成时非常有用,后面会专门写这个。
- -inputhandler <class> 指定用于处理输入请求的类
- -main <class> 覆盖Ant正常的入口点
- -noinput 不允许交互式输入
- -autoproxy Java1.5+,使用OS的代理设置
- -nice number
- -nouserlib
- -noclasspath
第二章 安装和配置
基本使用
- 下载ant发布包
- 解压缩到一个目录既可
- 将该目录下的子目录bin添加到PATH环境变量
高级配置
留坑待填....
第三章 构建文件
构建文件需要根据具体项目的特性编写,不过同一类型的项目基本上是可以使用一套构建配置的。
Java提供了用于构建的Java工具库,使用Java语言编写Ant是最容易实现和维护的。XML有丰富的解析类库,并且被开发人员广泛使用,也能够表达Ant的数据模型,使用XML作为构建文件时最好的选择。
XML是一种树形的文档对象模型(DOM),其中的Project,Target等元素与Ant的模型组件相对应。
1.Ant的构建块
Project(工程) 任何构建文件的第一个元素必须是<project>标签,而且只能有一个。
- name属性 工程的名字,也是构建文件的标识符。
- default属性 运行Ant不指定Target时,默认执行的Target。可以设置成一个构建文件中定义的Target名字。推荐默认Target显示构建文件的帮助信息或者执行完成的构建。
- basedir属性 定义工程的根目录,一般情况下都是" . ",也就是构建文件所在的目录(不是运行Ant命令是的目录)。在一个多层次的项目中,basedir还可以定义不同的参考点。
Target(目标) 一个Project可以包含多个Target,一个总的任务过程可以拆分成几个target,每个Target可以单独调用。可以把target理解成能够单独执行的一个个步骤(阶段)。具体怎么拆分这个总任务,拆分粒度是粗还是细,把哪些Task放在哪几个Target中,都是编写构建文件要考虑的问题。一般来说,粒度更小可以更灵活的组合,有些target失败也不会影响另一些的执行。但是粒度也不能太小,太小了会很琐碎不好维护。
- name属性
- depends属性
- description属性
前面的build.xml实例中,编译打包拆成了两个target,如果每次这两个target都是一起被调用的,把他们放在一个target中也是可以的。
<target name="build" depends="prepare"> <javac srcdir="${src.dir}" destdir="${build.classes}" /> <jar jarfile="${build.lib}/antDemo.jar" basedir="${build.classes}" excludes="**/*Test.class"/> </target>
Task(任务) 任务是构建文件中的最小构建单位。通过Target把一个总过程组织成了几个大的Target目标(步骤),但是Target并不做任何具体的工作,Target下面有包含一些Task,所有的具体工作都是靠这些task来完成。Ant提供非常多的Task,如编译,大包,文件系统操作等等。Ant中每个任务对应于Ant对象模型中的一个Java对象,要自定义新的任务就是要编写执行该任务类然后提供给Ant调用。很多系统命令也都是用Task包装,而不是直接调用shell命令,在不同的操作系统上Task的使用方式是完全一致的。
构建文件中任务标签内部也可以有很多层次。但是Task内部的这些层次结构和Java类的层次结构没有任何关系了。
Task标签不再有统一的属性和子元素,内部层次完全取决于具体的任务。
2.数据元素(data element)
构建文件中除了与任务构建过程相关的元素外。还包括了保存数据的变量和抽象数据类型等元素。数据元素有两类:Property和DataType
Property
- 表示字符串型的“键-值”对,只能用在可使用字符串的位置。
- Propery和Java中的Property对象是兼容的,可以使用Property文件或JVM命令行-Dproperty=value选项,在运行ant时动态定义。
- 可以使用${propName}的形式引用Property数据。
property数据定义和引用
<property name="data1" value="string value1"/> <property name="data2" value="${data1} and value2">
加载config.properties文件中定义的property数据
<proppery file="config.properties"/>
通过命令行在运行时动态定义propery数据
ant mytarget -Dname=value
DataType
Property数据值都是字符串,ant并不知道这些字符串代表了什么对象。如果将包含很多个Jar文件的长串路径保存在Property数据中,就很容易出错,修改也不方便。
Ant还提供了很多种具体的数据类型(DataType),各种数据类型能够更清晰地描述特定类型的数据,如Path(路径), FileSet(文件集合,可以使用通配符)等,修改起来更加方便。
使用Property表示路径和使用Path/FileSet对象表示路径的对比:
<property name="classpath" value="${lib.dir}/j2ee.jar:${lib.dir}/servlet.jar:${lib.dir}/jasper.jar:........"/>
<path id="classpath"> <fileset dir="${lib.dir}"> <include name="j2ee.jar"/> <include name="servlet.jar"/> <include name="**/*.jar"> ..... </fileset> </path>
3.工程结构和构建文件
要编写工程的构建文件,必须要了解项目的结构。项目类型不同,项目结构往往存在较大的差异(如Web项目和GUI项目),没有最佳的工程组织模式。工程结构是比较复杂的,需要考虑跨平台(使用相对路径),依赖自包含无外部需求,功能模块和不同类型文件分离等等。
以下面的项目结构为例:
- projectName
- build.xml 构建文件
- src/
- api
- module
- doc/ 项目相关文档(非JavaDoc文档,不能自动生成),如readme,license等
- lib/ 依赖的外部库,统一依赖
- bin/ 可选目录,包含安装、执行等脚本或者是难找的、定制的可执行工具(为跨平台,最好不要使用可执行程序)
- build/ 构建产出目录
- classes/
- doc/ javadoc产出
- lib/
- bin/
- dist/ 最终用于发布的目录(内容一般都是从其他目录复制而来)
- lib/
- bin/
- doc/
- config/
编写构建文件