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顯然更具競爭力。


浙公網安備 33010602011771號