Ant构建项目

前言:

Ant是java世界里第一个具有里程碑意义的项目构建工具。

官网地址:http://ant.apache.org/

文档地址(Ant task):http://ant.apache.org/manual/index.html

扩展工具地址(比如checkstyle和clover):http://ant.apache.org/external.html

本文并不打算长篇累牍的描述Ant的使用方法,因为官网已经够清楚,也有多位大神发过博文,这里只描述核心概念和一些基本实例,本文还会继续编辑,希望大家多提宝贵意见。

 

1,Ant的安装

从官网下载(http://ant.apache.org/bindownload.cgi)包,解压到任意目录,将bin目录加入到环境变量,一般推荐使用英文路径,并且先建立ANT_HOME的方式,以相对路径设置环境变量。(这是个好习惯)

2,Ant的核心概念

构建文件是以XML文件来描述,这就意味着,你可以通过xsd查看所有的熟悉和配置方式,这非常有用,当然eclipse支持Ant提示。

每个构建文件包含一个工程(project),其实是对一个项目概念的抽象,在Eclipse插件开发当中,也有这样的抽象。

每个工程包含若干个目标(target),目标是构成Ant构建文件的基本单元,在以后的Maven(另一种强大的项目管理工具)中类似于任务的概念。

目标可以依赖于其他目标(depends),可以称为项目内的依赖,Ant的目标可以构成链式,树形,网状的配置结构,目标的合理依赖可以使Ant文件有良好的扩展性和复用性。

易于扩展,很多优秀项目已经实现与Ant的整合,比如checkstyle,junit,clover。

目标的任务可以调用另一个工程的目标。(在一个项目中,可以引用另一个项目的配置,可以称为项目级别的依赖)

3,环境

环境准备:Eclipse + Ant 1.7 + jdk1.5+

项目准备:
    使用Eclipse建立一个maven目录结构的项目或者普通的Java工程。
    普通java项目:在classpath上显示的源目录为src
    Maven目录结构项目:普通java项目建立后,在classpath上删除src源目录,并在src目录下新建类似于Maven工程的目录结构。

4,简单Ant文件 

说明:我不会对每一个标签和属性做解释。最佳实践都属个人参考或总结,如有疏漏或错误,请高手指正并留下您的建议或指导。

如下是一份简单的通用构建的build.xml,并附上build.properties。

1),资源文件的配置
   最佳实践:首先,尽量较少的定义非关联的属性,而采用相对引用的方式。其次,尽量采用直观而明确的名称定义属性,保持层次感和语义的完整性
   顺便提到一句,对于稍大项目的构建,如果是构建依赖的基础包目录,尽量应该采取按功能模块或者相互依赖的关系分成独立的目录,在不破坏整个项目结构下,子项目能够采用引入最少依赖的方式完成构建。
build.properties:

#依赖资源包根目录
dependency.lib.path=../../web-libs
dependency.resource.path=${dependency.lib.path}/resources
#编译源依赖包
dependency.lib.main.path=${dependency.lib.path}/main
#编译测试依赖包
dependency.lib.test.path=${dependency.lib.path}/test
#工具包目录
dependency.lib.tool.path=${dependency.lib.path}/tool

#源目录 目前定义为 maven的默认目录格式,方便之后介绍maven
src.main.java=src/main/java
src.main.resources=src/main/resources
src.test.java=src/test/java
src.test.resources=src/test/resources

 
2),构建文件的配置
   最佳实践:target任务可以相互调用,我们采用depends属性来控制这个依赖,如果依赖的比较复杂,那么我们的Ant文件可能会偏向于倒立的树形结构,并且分支可能有所交叉,这从结构上并不是一个很好的脚本设计
由于Ant脚本是顺序执行的,我们也最好依照这个特点,尽量避免target的交叉依赖,也就是完成一个target,和完成其它的target不要有太多的交集。还有一个非常重要的概念,很多target原则上都是需要清理的,我们应当树立一种清理的意识,比如我们在新建一个目录的时候,最好先清理这个目录,因为你不清楚之前的一次构建是什么时候或者构建是否过期。

build.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project name="common-web-demo" default="build" basedir=".">
 <description>common project automatic build script</description>

 <!--加载资源文件 -->
 <property file="build.properties" />

 <!-- 源目录classpath-->
 <path id="main.classpath">
  <fileset dir="${dependency.lib.main.path}" includes="**/*.jar" />
 </path>

 <!--测试目录classpath -->
 <path id="test.classpath">
  <fileset dir="${dependency.lib.main.path}" includes="**/*.jar" />
  <fileset dir="${dependency.lib.test.path}" includes="**/*.jar" />
  <fileset dir="${dependency.lib.tool.path}" includes="**/*.jar" />
 </path>

 <!--工具包classpath -->
 <path id="tool.classpath">
  <fileset dir="${dependency.lib.tool.path}" includes="**/*.jar" />
 </path>

 <!--工具包任务定义 -->
 <taskdef resource="net/sf/antcontrib/antlib.xml">
  <classpath refid="tool.classpath" />
 </taskdef>

 <target name="clean">
  <delete dir="${build.target.dir}" />
  <mkdir dir="${build.target.dir}" />
 </target>

 <!-- 主目录编译清理  -->
 <target name="main.clean">
  <delete dir="${build.target.bin.dir}" />
  <mkdir dir="${build.target.bin.dir}" />
 </target>

 <!-- 主目录编译  -->
 <target name="main.compile" depends="main.clean">
  <javac srcdir="${src.main.java}" destdir="${build.target.bin.dir}" source="1.5" target="1.5" debug="on">
   <classpath refid="main.classpath" />
  </javac>
  <copydir src="${src.main.resources}" dest="${build.target.bin.dir}" />
 </target>

 <!--主目录打包 -->
 <target name="package.main.jar" depends="main.compile">
  <jar destfile="${build.target.dir}/${build.target.assembly.name}.jar">
   <fileset dir="${build.target.bin.dir}" />
  </jar>
 </target>

 <!-- === 循环构建 === -->
 <target name="build" depends="clean,package.main.jar" />
</project>


xml编写和标签解释:
1,关于xml的编写:在独立标签的编写上,应该选择倾向于<el .../> 而不是<el></el>,很多开源的Java框架都推荐前者的写法,比如Spring。
2,project:default属性指定默认执行target(命令行), basedir定义构建目标路径 basedir="."指定为当前项目路径(SystemUtils.getUserDir()),其实对于构建一个独立的项目或者一个模块下的多个项目,这样设定相对路径是一个很好的做法。
3,target(目标):一个build.xml最为核心的标签,它可以调用其它的target,也可以调用一些扩展的target,target的名称不可重复,在名称编写上也最好遵照某些规则建立分组,这样在一个Ant文件的Outline视图中可以很清晰的分辨结构和依赖关系。目前官网上的文档的task of list命令基本上都属于target的范畴。
4,特别说明关于上述build.xml中工具包任务的定义,也就是antcontrib,它定义了一些非常有用的扩展功能,比如for for each,更有意思的是try catch,try catch真没用过,有兴趣的可以试试。

task浅析:
1,property :资源标签,它能定义单个属性,也可以加载多个属性。定义单个:<property name="life" value="time" />;加载多个:<property file="build.properties"/>
2,taskdef :定义扩展,定义扩展有两种方式,一种是引入扩展标签,也就是上面的xml当中的格式,另一种可以自定义为<typedef name="urlset" classname="com.mydomain.URLSet"/> 和具体的处理类挂钩,后一种我不太了解。
3,关于delete 与 mkdir一类有关文件的操作,包括fileset这类提供作用域的标签。这些在Ant官网中介绍的非常详细,命令众多,就不一一列举了(关于删除命令,个人推荐delete,因为它的容错性要高一些,在目录不存在时不会有任何动作)。
4,javac,Ant整个构建体系当中最为核心的task之一,比较值得关注的属性有debug="on",source="1.5" ,前者打开debug,后者定义编译级别。
5,jar,打包命令,既然有jar,自然就有zip,war,ear了,它们大同小异。
6,fock 一个重要的属性,在javac junit很多相对比较耗资源的任务当中都有这个属性,只要开启,一般的意思就是采用独立jvm执行编译或运行测试等等之类,对于出现内存不足的错误很有效果。当然小项目不需要这么做,大一点的项目也可以通过调整Ant和虚拟机的内存来实现。

 

5 相对复杂的Ant文件

   实现的功能:编译,打包,测试运行,代码检查,覆盖率检查。

 1) 主要插件:
    junit
    checkstyle 
    clover
   
2)build.properties

#依赖资源包根目录
dependency.lib.path=../../web-libs
dependency.resource.path=${dependency.lib.path}/resources
#编译源依赖包
dependency.lib.main.path=${dependency.lib.path}/main
#编译测试依赖包
dependency.lib.test.path=${dependency.lib.path}/test
#工具包目录
dependency.lib.tool.path=${dependency.lib.path}/tool

test.jar.names=junit-4.10,spring-test-3.0.5.RELEASE

#源目录
src.main.java=src/main/java
src.main.resources=src/main/resources
src.test.java=src/test/java
src.test.resources=src/test/resources

src.webapp.folder=src/main/webapp

#构建主目录
build.target.dir=target
#构建源编译目录
build.target.bin.dir=${build.target.dir}/bin
#构建测试编译目录
build.target.test.bin.dir=${build.target.dir}/test-bin
#Junit 测试记录
build.target.junit.record.dir=${build.target.dir}/test-record

clover.db.dir=${build.target.dir}/clover-db

#测试报告
build.target.test.report.dir=${build.target.dir}/output
junit.test.report.dir=${build.target.test.report.dir}/junit.report
checkstyle.report.dir=${build.target.test.report.dir}/checkstyle.report
clover.report.dir=${build.target.test.report.dir}/clover.report

#构建目标包名称
build.target.assembly.name=common-web-demo


3)build.xml

<?xml version="1.0" encoding="UTF-8"?>
<project name="common-web-demo" default="build" basedir=".">
 <description>common project automatic build script</description>

 <!--加载资源文件 -->
 <property file="build.properties" />

 <!-- 源目录classpath-->
 <path id="main.classpath">
  <fileset dir="${dependency.lib.main.path}" includes="**/*.jar" />
 </path>

 <!--测试目录classpath -->
 <path id="test.classpath">
  <fileset dir="${dependency.lib.main.path}" includes="**/*.jar" />
  <fileset dir="${dependency.lib.test.path}" includes="**/*.jar" />
  <fileset dir="${dependency.lib.tool.path}" includes="**/*.jar" />
 </path>

 <!--工具包classpath -->
 <path id="tool.classpath">
  <fileset dir="${dependency.lib.tool.path}" includes="**/*.jar" />
 </path>

 <!--工具包任务定义 -->
 <taskdef resource="net/sf/antcontrib/antlib.xml">
  <classpath refid="tool.classpath" />
 </taskdef>

 <target name="clean">
  <delete dir="${build.target.dir}" />
  <mkdir dir="${build.target.dir}" />
 </target>

 <!-- 主目录编译清理  -->
 <target name="main.clean">
  <delete dir="${build.target.bin.dir}" />
  <mkdir dir="${build.target.bin.dir}" />
 </target>

 <!-- 主目录编译  -->
 <target name="main.compile" depends="main.clean">
  <javac srcdir="${src.main.java}" destdir="${build.target.bin.dir}" source="1.5" target="1.5" debug="on">
   <classpath refid="main.classpath" />
  </javac>
  <copydir src="${src.main.resources}" dest="${build.target.bin.dir}" />
 </target>

 <!--主目录打包 -->
 <target name="package.main.jar" depends="main.compile">
  <jar destfile="${build.target.dir}/${build.target.assembly.name}.jar">
   <fileset dir="${build.target.bin.dir}" />
  </jar>
 </target>

 <!-- === 循环构建 === -->
 <target name="build" depends="clean,package.main.jar">

 </target>

 <!-- 测试目录清理 -->
 <target name="test.clean">
  <delete dir="${build.target.test.bin.dir}" />
  <mkdir dir="${build.target.test.bin.dir}" />
 </target>

 <!-- 测试编译 -->
 <target name="test.compile" depends="test.clean">
  <javac srcdir="${src.main.java}" destdir="${build.target.test.bin.dir}" source="1.5" target="1.5" debug="on">
   <classpath refid="test.classpath" />
  </javac>
  <copydir src="${src.main.resources}" dest="${build.target.test.bin.dir}" />
  <javac srcdir="${src.test.java}" destdir="${build.target.test.bin.dir}" source="1.5" target="1.5" debug="on">
   <classpath refid="test.classpath" />
  </javac>
  <copydir src="${src.test.resources}" dest="${build.target.test.bin.dir}" />
 </target>

 <target name="test.record.clean">
  <delete dir="${build.target.junit.record.dir}" />
  <mkdir dir="${build.target.junit.record.dir}" />
 </target>

 <path id="test.run.classpath">
  <path refid="test.classpath" />
  <pathelement path="${build.target.test.bin.dir}" />
 </path>

 <!-- 运行Junit测试 -->
 <target name="junit.test" depends="test.compile,test.record.clean">
  <junit printsummary="true" fork="true" showoutput="true" dir="${basedir}">
   <classpath>
    <path refid="test.run.classpath">
    </path>
   </classpath>
   <formatter type="xml" />
   <batchtest fork="true" todir="${build.target.junit.record.dir}">
    <fileset dir="${build.target.test.bin.dir}">
     <include name="org/wit/ff/dao/*Test.class" />
    </fileset>
   </batchtest>
  </junit>
 </target>

 <!--生成测试报告目录 -->
 <target name="report.clean">
  <delete dir="${build.target.test.report.dir}" />
  <mkdir dir="${build.target.test.report.dir}" />
 </target>

 <!-- 生成Junit报告目录 -->
 <target name="junit.report.clean">
  <delete dir="${junit.test.report.dir}" />
  <mkdir dir="${junit.test.report.dir}" />
 </target>

 <!-- Junit 测试报告 -->
 <target name="junit.report" depends="junit.report.clean,junit.test">
  <junitreport todir="${build.target.junit.record.dir}">
   <fileset dir="${build.target.junit.record.dir}" includes="**/TEST-*.xml" />
   <report todir="${junit.test.report.dir}" />
  </junitreport>
 </target>

 <!-- checkstyle报告 -->
 <target name="checkstyle.report.clean">
  <delete dir="${checkstyle.report.dir}" />
  <mkdir dir="${checkstyle.report.dir}" />
 </target>

 <target name="checkstyle.report" depends="checkstyle.report.clean">
  <taskdef resource="checkstyletask.properties" classpath="${dependency.lib.tool.path}/checkstyle-5.3-all.jar" />
  <checkstyle config="${dependency.resource.path}/test_ii_checks.xml" failureProperty="checkstyle.failure" failOnViolation="false">
   <formatter type="xml" tofile="${checkstyle.report.dir}/checkstyle.xml" />
   <fileset dir="${src.main.java}" includes="**/*.java" />
   <fileset dir="${src.test.java}" includes="**/*.java" />
  </checkstyle>
  <style in="${checkstyle.report.dir}/checkstyle.xml" out="${checkstyle.report.dir}/checkstyle.html" style="${dependency.resource.path}/checkstyle.xsl" />
 </target>

 <!-- clover覆盖率报告 -->
 <taskdef resource="cloverlib.xml" classpath="${dependency.lib.tool.path}/clover.jar" />
 <taskdef resource="cloverjunitlib.xml" classpath="${dependency.lib.tool.path}/clover.jar" />

 <target name="clover.db.init" >
  <delete dir="${clover.db.dir}" />
  <mkdir dir="${clover.db.dir}" />
 </target>

 <!-- clover数据库,其实就是类执行信息存储单元 -->
 <target name="with.clover" depends="clover.db.init">
  <clover-setup initstring="${clover.db.dir}/coverage.db">
   <statementContext name="log" regexp="^log\..*" />
   <statementContext name="iflog" regexp="^if \(log\.is.*" />
   <methodcontext name="main" regexp="public static void main\(String\[\] args\).*" />
  </clover-setup>
 </target>

 <target name="clover.report.clean" >
  <delete dir="${clover.report.dir}" />
  <mkdir dir="${clover.report.dir}" />
 </target>

 <!-- 生成Clover测试报告-->
 <target name="clover.report" depends="clover.report.clean">
  <!-- 非常简洁的循环语句 -->
  <foreach target="clover.single.report" param="prop" list="html;xml" delimiter=";" />
 </target>

 <!-- 类型参数化的clover 报告 -->
 <target name="clover.single.report">
  <clover-report initstring="${clover.db.dir}/coverage.db">
   <current outfile="${clover.report.dir}/clover.${prop}" title="${build.target.assembly.name}" summary="true">
    <format type="${prop}" filter="log,iflog,main" />
    <fileset dir="${src.main.java}">
     <exclude name="org/wit/ff/dao/Base*.java" />
     <exclude name="org/wit/ff/dao/impl/Base*.java" />
     <exclude name="org/wit/ff/model/*Entity.java" />
     <exclude name="org/wit/ff/dao/*DAO.java" />
    </fileset>
    <fileset dir="${src.test.java}">
     <exclude name="org/wit/ff/test/Base*.java" />
    </fileset>
   </current>
  </clover-report>
 </target>

 <!-- html 格式clover 报告 -->
 <target name="clover.html">
  <clover-html-report outdir="clover_html" title="${build.target.assembly.name}" />
 </target>

 <!-- xml 格式clover 报告 -->
 <target name="clover.xml">
  <clover-report>
   <current outfile="coverage.xml">
    <format type="xml" />
   </current>
  </clover-report>
 </target>

 <!-- 运行所有目标-->
 <target name="test" depends="with.clover,build,junit.report,checkstyle.report,clover.report" />

</project>

结束语:

  关于Ant依赖的思考

  通常情况下,当我们使用Ant构建项目的时候,我们需要依赖某个目录下的包来完成构建,对于很多很多个使用Ant脚本构建的项目而言,可能整个依赖包的目录会非常的庞大,虽然我们可以依据模块将依赖分为多个子目录,但是在实际的运行构建中,通常还是依赖了多余的jar,甚至有时候依赖了很多不需要的jar。实际上要解决这个问题,我们可以使用原生的Ant精确定义classpath,但是如果依赖过多,ant文件太过庞大,远不如**/*.jar好管理,很多情况下,模块的划分也是为了更简化classpath的定义。但是这些其实远远不够,因为项目之间的依赖,如果也是用Ant来解决的话看起来会有些复杂了,也难以控制,因为你不得不修改脚本或者修改properties文件。并且会产生很多人为因素的问题,比如依赖包需要同步时还得拷贝。
  关于Ant的精确依赖,其实有一种方法。eg:首先将依赖配置到一个properties文件,然后通过Ant引入;其次使用antcotrib的定义任务for将所有的依赖拷贝到本地或者本项目的目录之下(hudson在构建Ant项目的时候就是采取这种策略)。最后按照普通Ant脚本编写就可以了。
  虽说如此,但Ant的依赖控制难以精确的确是事实,并且它作为一个自定义程序化构建的工具,还是需要不少的脚本开发工作。虽然Ivy(Spring源码也采用这种构建方式)的出现也能对依赖进行很好的管理,但是一个更有力的工具,Maven显然更具竞争力。

原文地址:https://www.cnblogs.com/fangfan/p/2834606.html