1 Ant是什麼?
Apache Ant 是一個基於 Java的生成工具。
生成工具在軟體開發中用來將原始碼和其他輸入檔案轉換為可執行檔案的形式(也有可能轉換為可安裝的產品映像形式)。隨著應用程式的生成過程變得更加複雜,確保在每次生成期間都使用精確相同的生成步驟,同時實現儘可能多的自動化,以便及時產生一致的生成版本
2 下載、安裝Ant
安裝Ant
下載.zip檔案,解壓縮到c:\ant1.3(後面引用為%ANT_HOME%)
2.1 在你執行Ant之前需要做一些配置工作。
? 將bin目錄加入PATH環境變數。
? 設定ANT_HOME環境變數,指向你安裝Ant的目錄。在一些OS上,Ant的指令碼可以猜測ANT_HOME(Unix和Windos NT/2000)-但最好不要依賴這一特性。
? 可選地,設定JAVA_HOME環境變數(參考下面的高階小節),該變數應該指向你安裝JDK的目錄。
注意:不要將Ant的ant.jar檔案放到JDK/JRE的lib/ext目錄下。Ant是個應用程式,而lib/ext目錄是為JDK擴充套件使用的(如JCE,JSSE擴充套件)。而且透過擴充套件裝入的類會有安全方面的限制。
2.2 執行Ant
執行Ant非常簡單,當你正確地安裝Ant後,只要輸入ant就可以了。
? 沒有指定任何引數時,Ant會在當前目錄下查詢build.xml檔案。如果找到了就用該檔案作為buildfile。如果你用 -find 選項。 Ant就會在上級目錄中尋找buildfile,直至到達檔案系統的根。要想讓Ant使用其他的buildfile,可以用引數 - buildfile file,這裡file指定了你想使用的buildfile。
? 可以指定執行一個或多個target。當省略target時,Ant使用標籤<project>的default屬性所指定的target。
命令列選項總結:
ant [options] [target [target2 [target3] ...]]
Options:
-help print this message
-projecthelp print project help information
-version print the version information and exit
-quiet be extra quiet
-verbose be extra verbose
-debug print debugging information
-emacs produce logging information without adornments
-logfile file use given file for log output
-logger classname the class that is to perform logging
-listener classname add an instance of class as a project listener
-buildfile file use specified buildfile
-find file search for buildfile towards the root of the filesystem and use the first one found
-Dproperty=value set property to value
例子
ant
使用當前目錄下的build.xml執行Ant,執行預設的target。
ant -buildfile test.xml
使用當前目錄下的test.xml執行Ant,執行預設的target。
ant -buildfile test.xml dist
使用當前目錄下的test.xml執行Ant,執行一個叫做dist的target。
ant -buildfile test.xml -Dbuild=build/classes dist
使用當前目錄下的test.xml執行Ant,執行一個叫做dist的target,並設定build屬性的值為build/classes。
3 編寫build.xml
Ant的buildfile是用XML寫的。每個buildfile含有一個project。
buildfile中每個task元素可以有一個id屬性,可以用這個id值引用指定的任務。這個值必須是唯一的。(詳情請參考下面的Task小節)
3.1 Projects
project有下面的屬性:
Attribute Description Required
name 專案名稱. No
default 當沒有指定target時使用的預設target Yes
basedir 用於計算所有其他路徑的基路徑。該屬性可以被basedir property覆蓋。當覆蓋時,該屬性被忽略。如果屬性和basedir property都沒有設定,就使用buildfile檔案的父目錄。 No
專案的描述以一個頂級的<description>元素的形式出現(參看description小節)。
一個專案可以定義一個或多個target。一個target是一系列你想要執行的。執行Ant時,你可以選擇執行那個target。當沒有給定target時,使用project的default屬性所確定的target。
3.2 Targets
一個target可以依賴於其他的target。例如,你可能會有一個target用於編譯程式,一個target用於生成可執行檔案。你在生成可執行檔案之前必須先編譯透過,所以生成可執行檔案的target依賴於編譯target。Ant會處理這種依賴關係。
然而,應當注意到,Ant的depends屬性只指定了target應該被執行的順序-如果被依賴的target無法執行,這種depends對於指定了依賴關係的target就沒有影響。
Ant會依照depends屬性中target出現的順序(從左到右)依次執行每個target。然而,要記住的是隻要某個target依賴於一個target,後者就會被先執行。
<target name="A"/>
<target name="B" depends="A"/>
<target name="C" depends="B"/>
<target name="D" depends="C,B,A"/>
假定我們要執行target D。從它的依賴屬性來看,你可能認為先執行C,然後B,最後A被執行。錯了,C依賴於B,B依賴於A,所以先執行A,然後B,然後C,最後D被執行。
一個target只能被執行一次,即時有多個target依賴於它(看上面的例子)。
如 果(或如果不)某些屬性被設定,才執行某個target。這樣,允許根據系統的狀態(java version, OS, 命令列屬性定義等等)來更好地 控制build的過程。要想讓一個target這樣做,你就應該在target元素中,加入if(或unless)屬性,帶上target因該有所判斷的 屬性。例如:
<target name="build-module-A" if="module-A-present"/>
<target name="build-own-fake-module-A" unless="module-A-present"/>
如果沒有if或unless屬性,target總會被執行。
可選的description屬性可用來提供關於target的一行描述,這些描述可由-projecthelp命令列選項輸出。
將你的tstamp task在一個所謂的初始化target是很好的做法,其他的target依賴這個初始化target。要確保初始化target是出現在其他target依賴表中的第一個target。在本手冊中大多數的初始化target的名字是"init"。
target有下面的屬性:
Attribute Description Required
name target的名字 Yes
depends 用逗號分隔的target的名字列表,也就是依賴表。 No
if 執行target所需要設定的屬性名。 No
unless 執行target需要清除設定的屬性名。 No
description 關於target功能的簡短描述。 No
3.3 Tasks
一個task是一段可執行的程式碼。
一個task可以有多個屬性(如果你願意的話,可以將其稱之為變數)。屬性只可能包含對property的引用。這些引用會在task執行前被解析。
下面是Task的一般構造形式:
<name attribute1="value1" attribute2="value2" ... />
這裡name是task的名字,attributeN是屬性名,valueN是屬性值。
有一套內建的(built-in)task,以及一些可選task,但你也可以編寫自己的task。
所有的task都有一個task名字屬性。Ant用屬性值來產生日誌資訊。
可以給task賦一個id屬性:
<taskname id="taskID" ... />
這裡taskname是task的名字,而taskID是這個task的唯一識別符號。透過這個識別符號,你可以在指令碼中引用相應的task。例如,在指令碼中你可以這樣:
<script ... >
task1.setFoo("bar");
</script>
設定某個task例項的foo屬性。在另一個task中(用java編寫),你可以利用下面的語句存取相應的例項。
project.getReference("task1").
注意1:如果task1還沒有執行,就不會被生效(例如:不設定屬性),如果你在隨後配置它,你所作的一切都會被覆蓋。
注意2:未來的Ant版本可能不會相容這裡所提的屬性,因為很有可能根本沒有task例項,只有proxies。
3.4 Properties
一 個project可以有很多的properties。可以在buildfile中用property task來設定,或在Ant之外設定。一個 property有一個名字和一個值。property可用於task的屬性值。這是透過將屬性名放在"${"和"}"之間並放在屬性值的位置來實現的。 例如如果有一個property builddir的值是"build",這個property就可用於屬性值:${builddir} /classes。這個值就可被解析為build/classes。
內建屬性
如果你使用了<property> task 定義了所有的系統屬性,Ant允許你使用這些屬性。例如,${os.name}對應作業系統的名字。
要想得到系統屬性的列表可參考the Javadoc of System.getProperties。
除了Java的系統屬性,Ant還定義了一些自己的內建屬性:
basedir project基目錄的絕對路徑 (與<project>的basedir屬性一樣)。
ant.file buildfile的絕對路徑。
ant.version Ant的版本。
ant.project.name 當前執行的project的名字;由<project>的name屬性設定.
ant.java.version Ant檢測到的JVM的版本; 目前的值有"1.1", "1.2", "1.3" and "1.4".
例子
<project name="MyProject" default="dist" basedir=".">
<!-- set global properties for this build -->
<property name="src" value="."/>
<property name="build" value="build"/>
<property name="dist" value="dist"/>
<target name="init">
<!-- Create the time stamp -->
<tstamp/>
<!-- Create the build directory structure used by compile -->
<mkdir dir="${build}"/>
</target>
<target name="compile" depends="init">
<!-- Compile the java code from ${src} into ${build} -->
<javac srcdir="${src}" destdir="${build}"/>
</target>
<target name="dist" depends="compile">
<!-- Create the distribution directory -->
<mkdir dir="${dist}/lib"/>
<!-- Put everything in ${build} into the MyProject-${DSTAMP}.jar file -->
<jar jarfile="${dist}/lib/MyProject-${DSTAMP}.jar" basedir="${build}"/>
</target>
<target name="clean">
<!-- Delete the ${build} and ${dist} directory trees -->
<delete dir="${build}"/>
<delete dir="${dist}"/>
</target>
</project>
3.5 Path-like Structures
你可以用":"和";"作為分隔符,指定類似PATH和CLASSPATH的引用。Ant會把分隔符轉換為當前系統所用的分隔符。
當需要指定類似路徑的值時,可以使用巢狀元素。一般的形式是
<classpath>
<pathelement path="${classpath}"/>
<pathelement location="lib/helper.jar"/>
</classpath>
location屬性指定了相對於project基目錄的一個檔案和目錄,而path屬性接受逗號或分號分隔的一個位置列表。path屬性一般用作預定義的路徑--其他情況下,應該用多個location屬性。
為簡潔起見,classpath標籤支援自己的path和location屬性。所以:
<classpath>
<pathelement path="${classpath}"/>
</classpath>
可以被簡寫作:
<classpath path="${classpath}"/>
也可透過<fileset>元素指定路徑。構成一個fileset的多個檔案加入path-like structure的順序是未定的。
<classpath>
<pathelement path="${classpath}"/>
<fileset dir="lib">
<include name="**/*.jar"/>
</fileset>
<pathelement location="classes"/>
</classpath>
上面的例子構造了一個路徑值包括:${classpath}的路徑,跟著lib目錄下的所有jar檔案,接著是classes目錄。
如果你想在多個task中使用相同的path-like structure,你可以用<path>元素定義他們(與target同級),然後透過id屬性引用--參考Referencs例子。
path-like structure可能包括對另一個path-like structurede的引用(透過巢狀<path>元素):
<path id="base.path">
<pathelement path="${classpath}"/>
<fileset dir="lib">
<include name="**/*.jar"/>
</fileset>
<pathelement location="classes"/>
</path>
<path id="tests.path">
<path refid="base.path"/>
<pathelement location="testclasses"/>
</path>
前面所提的關於<classpath>的簡潔寫法對於<path>也是有效的,如:
<path id="tests.path">
<path refid="base.path"/>
<pathelement location="testclasses"/>
</path>
可寫成:
<path id="base.path" path="${classpath}"/>
命令列變數
有些task可接受引數,並將其傳遞給另一個程式。為了能在變數中包含空格字元,可使用巢狀的arg元素。
Attribute Description Required
value 一個命令列變數;可包含空格字元。 只能用一個
line 空格分隔的命令列變數列表。
file 作為命令列變數的檔名;會被檔案的絕對名替代。
path 一個作為單個命令列變數的path-like的字串;或作為分隔符,Ant會將其轉變為特定平臺的分隔符。
例子
<arg value="-l -a"/>
是一個含有空格的單個的命令列變數。
<arg line="-l -a"/>
是兩個空格分隔的命令列變數。
<arg path="/dir;/dir2:\dir3"/>
是一個命令列變數,其值在DOS系統上為\dir;\dir2;\dir3;在Unix系統上為/dir:/dir2:/dir3 。
References
buildfile元素的id屬性可用來引用這些元素。如果你需要一遍遍的複製相同的XML程式碼塊,這一屬性就很有用--如多次使用<classpath>結構。
下面的例子:
<project ... >
<target ... >
<rmic ...>
<classpath>
<pathelement location="lib/"/>
<pathelement path="${java.class.path}/"/>
<pathelement path="${additional.path}"/>
</classpath>
</rmic>
</target>
<target ... >
<javac ...>
<classpath>
<pathelement location="lib/"/>
<pathelement path="${java.class.path}/"/>
<pathelement path="${additional.path}"/>
</classpath>
</javac>
</target>
</project>
可以寫成如下形式:
<project ... >
<path id="project.class.path">
<pathelement location="lib/"/>
<pathelement path="${java.class.path}/"/>
<pathelement path="${additional.path}"/>
</path>
<target ... >
<rmic ...>
<classpath refid="project.class.path"/>
</rmic>
</target>
<target ... >
<javac ...>
<classpath refid="project.class.path"/>
</javac>
</target>
</project>
所有使用PatternSets, FileSets 或 path-like structures巢狀元素的task也接受這種型別的引用。
4.1 File(Directory)類
4.1.1 Mkdir
? 建立一個目錄,如果他的父目錄不存在,也會被同時建立。
? 例子:
<mkdir dir="build/classes"/>
? 說明: 如果build不存在,也會被同時建立
4.1.2 Copy
? 複製一個(組)檔案、目錄
? 例子:
1. 複製單個的檔案:
<copy file="myfile.txt" tofile="mycopy.txt"/>
2. 複製單個的檔案到指定目錄下
<copy file="myfile.txt" todir="../some/other/dir"/>
3. 複製一個目錄到另外一個目錄下
<copy todir="../new/dir">
<fileset dir="src_dir"/>
</copy>
4. 複製一批檔案到指定目錄下
<copy todir="../dest/dir">
<fileset dir="src_dir">
<exclude name="**/*.java"/>
</fileset>
</copy>
<copy todir="../dest/dir">
<fileset dir="src_dir" excludes="**/*.java"/>
</copy>
5. 複製一批檔案到指定目錄下,將檔名後增加。Bak字尾
<copy todir="../backup/dir">
<fileset dir="src_dir"/>
<mapper type="glob" from="*" to="*.bak"/>
</copy>
6. 複製一組檔案到指定目錄下,替換其中的@標籤@內容
<copy todir="../backup/dir">
<fileset dir="src_dir"/>
<filterset>
<filter token="TITLE" value="Foo Bar"/>
</filterset>
</copy>
4.1.3 Delete
? 刪除一個(組)檔案或者目錄
? 例子
1. 刪除一個檔案
<delete file="/lib/ant.jar"/>
2. 刪除指定目錄及其子目錄
<delete dir="lib"/>
3. 刪除指定的一組檔案
<delete>
<fileset dir="." includes="**/*.bak"/>
</delete>
4. 刪除指定目錄及其子目錄,包括他自己
<delete includeEmptyDirs="true">
<fileset dir="build"/>
</delete>
4.1.4 Move
? 移動或重新命名一個(組)檔案、目錄
? 例子:
1. 移動或重新命名一個檔案
<move file="file.orig" tofile="file.moved"/>
2. 移動或重新命名一個檔案到另一個資料夾下面
<move file="file.orig" todir="dir/to/move/to"/>
3. 將一個目錄移到另外一個目錄下
<move todir="new/dir/to/move/to">
<fileset dir="src/dir"/>
</move>
4. 將一組檔案移動到另外的目錄下
<move todir="some/new/dir">
<fileset dir="my/src/dir">
<include name="**/*.jar"/>
<exclude name="**/ant.jar"/>
</fileset>
</move>
5. 移動檔案過程中增加。Bak字尾
<move todir="my/src/dir">
<fileset dir="my/src/dir">
<exclude name="**/*.bak"/>
</fileset>
<mapper type="glob" from="*" to="*.bak"/>
</move>
4.2 Java相關
4.2.1 Javac
? 編譯java原始碼
? 例子
1. <javac srcdir="${src}"
destdir="${build}"
classpath="xyz.jar"
debug="on"
/>
編譯${src}目錄及其子目錄下的所有。Java檔案,。Class檔案將放在${build}指定的目錄下,classpath表示需要用到的類檔案或者目錄,debug設定為on表示輸出debug資訊
2. <javac srcdir="${src}:${src2}"
destdir="${build}"
includes="mypackage/p1/**,mypackage/p2/**"
excludes="mypackage/p1/testpackage/**"
classpath="xyz.jar"
debug="on"
/>
編 譯${src}和${src2}目錄及其子目錄下的所有。Java檔案,但是package/p1/**,mypackage/p2/**將被編譯,而 mypackage/p1/testpackage/**將不會被編譯。Class檔案將放在${build}指定的目錄下,classpath表示需要 用到的類檔案或者目錄,debug設定為on表示輸出debug資訊
3. <property name="classpath" value=".;./xml-apis.jar;../lib/xbean.jar;./easypo.jar"/>
<javac srcdir="${src}"
destdir="${src}"
classpath="${classpath}"
debug="on"
/>
路徑是在property中定義的
4.2.2 java
? 執行指定的java類
? 例子:
1. <java classname="test.Main">
<classpath>
<pathelement location="dist/test.jar"/>
<pathelement path="${java.class.path}"/>
</classpath>
</java>
classname中指定要執行的類,classpath設定要使用的環境變數
2. <path id="project.class.path">
<pathelement location="lib/"/>
<pathelement path="${java.class.path}/"/>
<pathelement path="${additional.path}"/>
</path>
<target ... >
<rmic ...>
<classpath refid="project.class.path"/>
</rmic>
</target>
4.3 打包相關
4.3.1 jar
? 將一組檔案打包
? 例子:
1. <jar destfile="${dist}/lib/app.jar" basedir="${build}/classes"/>
將${build}/classes下面的所有檔案打包到${dist}/lib/app.jar中
2. <jar destfile="${dist}/lib/app.jar"
basedir="${build}/classes"
includes="mypackage/test/**"
excludes="**/Test.class"
/>
將${build}/classes下面的所有檔案打包到${dist}/lib/app.jar中,但是包括mypackage/test/所有檔案不包括所有的Test.class
3. <jar destfile="${dist}/lib/app.jar"
basedir="${build}/classes"
includes="mypackage/test/**"
excludes="**/Test.class"
manifest=”my.mf”
/>
manifest屬性指定自己的META-INF/MANIFEST.MF檔案,而不是由系統生成
4.3.2 war
? 對Jar的擴充套件,用於打包Web應用
? 例子:
? 假設我們的檔案目錄如下:
thirdparty/libs/jdbc1.jar
thirdparty/libs/jdbc2.jar
build/main/com/myco/myapp/Servlet.class
src/metadata/myapp.xml
src/html/myapp/index.html
src/jsp/myapp/front.jsp
src/graphics/images/gifs/small/logo.gif
src/graphics/images/gifs/large/logo.gif
? 下面是我們的任務的內容:
<war destfile="myapp.war" webxml="src/metadata/myapp.xml">
<fileset dir="src/html/myapp"/>
<fileset dir="src/jsp/myapp"/>
<lib dir="thirdparty/libs">
<exclude name="jdbc1.jar"/>
</lib>
<classes dir="build/main"/>
<zipfileset dir="src/graphics/images/gifs"
prefix="images"/>
</war>
? 完成後的結果:
WEB-INF/web.xml
WEB-INF/lib/jdbc2.jar
WEB-INF/classes/com/myco/myapp/Servlet.class
META-INF/MANIFEST.MF
index.html
front.jsp
images/small/logo.gif
images/large/logo.gif
4.3.3 ear
? 用於打包企業應用
? 例子
<ear destfile="${build.dir}/myapp.ear" appxml="${src.dir}/metadata/application.xml">
<fileset dir="${build.dir}" includes="*.jar,*.war"/>
</ear>
4.4 時間戳
在生成環境中使用當前時間和日期,以某種方式標記某個生成任務的輸出,以便記錄它是何時生成的,這經常是可取的。這可能涉及編輯一個檔案,以便插入一個字串來指定日期和時間,或將這個資訊合併到 JAR 或 zip 檔案的檔名中。
這種需要是透過簡單但是非常有用的 tstamp 任務來解決的。這個任務通常在某次生成過程開始時呼叫,比如在一個 init 目標中。這個任務不需要屬性,許多情況下只需 <tstamp/> 就足夠了。
tstamp 不產生任何輸出;相反,它根據當前系統時間和日期設定 Ant 屬性。下面是 tstamp 設定的一些屬性、對每個屬性的說明,以及這些屬性可被設定到的值的例子:
屬性 說明 例子
DSTAMP 設定為當前日期,預設格式為yyyymmdd 20031217
TSTAMP 設定為當前時間,預設格式為 hhmm 1603
TODAY 設定為當前日期,帶完整的月份 2003 年 12 月 17 日
例如,在前一小節中,我們按如下方式建立了一個 JAR 檔案:
<jar destfile="package.jar" basedir="classes"/>
在呼叫 tstamp 任務之後,我們能夠根據日期命名該 JAR 檔案,如下所示:
<jar destfile="package-${DSTAMP}.jar" basedir="classes"/>
因此,如果這個任務在 2003 年 12 月 17 日呼叫,該 JAR 檔案將被命名為 package-20031217.jar。
還可以配置 tstamp 任務來設定不同的屬性,應用一個當前時間之前或之後的時間偏移,或以不同的方式格式化該字串。所有這些都是使用一個巢狀的 format 元素來完成的,如下所示:
<tstamp>
<format property="OFFSET_TIME"
pattern="HH:mm:ss"
offset="10" unit="minute"/>
</tstamp>
上面的清單將 OFFSET_TIME 屬性設定為距離當前時間 10 分鐘之後的小時數、分鐘數和秒數。
用於定義格式字串的字元與 java.text.SimpleDateFormat 類所定義的那些格式字元相同
4.5 執行SQL語句
? 透過jdbc執行SQL語句
? 例子:
1. <sql
driver="org.gjt.mm.mysql.Driver"
url="jdbc:mysql://localhost:3306/mydb"
userid="root"
password="root"
src="data.sql"
/>
2. <sql
driver="org.database.jdbcDriver"
url="jdbc:database-url"
userid="sa"
password="pass"
src="data.sql"
rdbms="oracle"
version="8.1."
>
</sql>
只有在oracle、版本是8.1的時候才執行
4.6 傳送郵件
? 使用SMTP伺服器傳送郵件
? 例子:
<mail mailhost="smtp.myisp.com" mailport="1025" subject="Test build">
<from address="me@myisp.com"/>
<to address="all@xyz.com"/>
<message>The ${buildname} nightly build has completed</message>
<fileset dir="dist">
<includes name="**/*.zip"/>
</fileset>
</mail>
? mailhost: SMTP伺服器地址
? mailport: 伺服器埠
? subject: 主題
? from: 傳送人地址
? to: 接受人地址
? message: 傳送的訊息
? fileset: 設定附件
====================================================================
在ANT 出現之前,編譯和部署Java應用需要使用包括特定平臺的指令碼、Make檔案、不同的IDE以及手工操作等組成的大雜燴。現在,幾乎所有的開源Java項 目都在使用Ant,許多公司的開發專案也在使用Ant。Ant的大量使用,也自然帶來了對總結Ant最佳實踐的迫切需求。
本文總結了我 喜好的Ant最佳實踐,很多是從親身經歷的專案錯誤,或從其他開發者的“恐怖”故事中得到的靈感的。比如,有人告訴我有個專案將 XDoclet 生成的 程式碼放入鎖定檔案的版本控制工具中。單開發者修改原始碼時,他必須記住手工檢出(Check out)並鎖定所有將要重生成的檔案。然後,手工執行程式碼生 成器,當他能夠讓Ant編譯程式碼時,這一方法還存在一些問題:
生成的程式碼無法儲存在版本控制系統中
Ant(本案例中是Xdoclet)應該自動確定下一次構建涉及的原始檔,而不應由程式設計師人工確定。
Ant的構建檔案應該定義好正確的任務依賴關係,這樣程式設計師不必按照特定順序呼叫任務。
當我開始一個新專案時,我首先編寫Ant構建檔案。檔案定義構建的過程,併為團隊中的每個程式設計師都使用。本文所有的最佳實踐假設Ant構建檔案是一個必須精心編寫的重要檔案,它應在版本控制系統中得到維護,並定期進行重構。下面是我的十五大Ant最佳實踐。
1. 採用一致的編碼規範
Ant使用者不管是喜歡還是痛恨XML構建檔案的語法,都願意跳進這一迷人的爭論中。讓我們先看一些保持XML構建檔案簡潔的方法。
首 先,也是最重要的,化費時間格式化你的XML讓它看上去很清晰。不過XML是否美觀,Ant都可以工作。但是醜陋的XML很難讀懂。倘若你在任務之間留出 空行,有規則的縮排,每行文字不超過90列,那麼XML令人驚訝的易讀。再加上好的編輯器或IDE高亮相應的語句,你就不會有如何閱讀的麻煩。同樣,精選 有意義明確、容易讀懂的詞彙來命名任務和屬性。比如,dir.reports就比rpts好。並不需要特定的編碼規範,只要有一種規範並堅持使用就好。
2. 將build.xml 放在專案根目錄中
Ant構建檔案build.xml可以放在如何位置,但是放在專案頂層目錄中可以保持專案簡潔。這是最普遍的規範,使開發者能夠在根目錄找到它。同時,也能夠容易瞭解專案中不同目錄之間的邏輯關係。以下是一個典型的專案層次:
[root dir] | build.xml +--src +--lib (包含第三方 JAR包) +--build (由 build任務生成) +--dist (由 build任務生成)
當build.xml在頂級目錄時,倘若你在專案某個子目錄中,只要輸入:ant -find compile 命令,不需要改變工作目錄就能夠以命令列方式編譯程式碼。引數-find告訴Ant尋找存在於上級目錄中的build.xml並執行。
3. 使用單一構建檔案
有人喜歡將一個大專案分解到幾個小的構建檔案,每個構建檔案分擔整個構建過程的一小部分工作。但是應該認識到,將構建檔案分割會增加對整個構建過程的理解難度。要注意在單一構建檔案能夠清楚表現構建層次的情況下,不要過工程化(over-engineer)。
即使你把專案劃分為多個構建檔案,也應使程式設計師能夠在專案根目錄下找到核心build.xml。儘管該檔案只是將實際構建工作委派給下級構建檔案,也應保證該檔案可用。
4. 提供良好的幫助說明
應儘量使構建檔案自文件化。增加任務描述是最簡單的方法。當你輸入ant -projecthelp時,你就可以看到帶有描述的任務清單。比如,你可以這樣定義任務:
<target name="compile" description="Compiles code, output goes to the build dir.">
最簡單的規則是對所有你希望程式設計師透過命令列直接呼叫的任務都加上描述。對於一般用來執行中間處理過程的內部任務,比如生成程式碼或建立輸出目錄等,就無法使用描述屬性。
這時,可以透過在構建檔案中加入XML註釋來處理。或者專門定義一個help任務,當程式設計師輸入ant help時來顯示詳細的使用說明。
<target name="help" description="Display detailed usage information"> <echo>Detailed help...</echo></target>
5. 提供清空任務
每個構建檔案都應包含一個清空任務,刪除所有生成的檔案和目錄,使系統回到構建檔案執行前的初始狀態。執行清空任務後還存在的檔案應處在版本控制系統的管理下。
比如:
<target name="clean" description="Destroys all generated files and dirs."> <delete dir="${dir.build}"/> <delete dir="${dir.dist}"/></target>
除非是在產生整個系統版本的特殊任務中,否則不要自動呼叫clean任務。當程式設計師僅僅執行編譯任務或其他任務時,他們不需要構建檔案事先執行即令人討厭有沒有必要的清空任務。要相信程式設計師能夠確定何時需要清空所有檔案。
6. 使用ANT管理任務從屬關係
假 設你的應用由Swing GUI元件、Web介面、EJB層和公共應用程式碼組成。在大型系統中,你需要清晰地定義Java包屬於系統的哪一層。否則如何一 點修改都要重新編譯成千上百個檔案。任務從屬關係管理差會導致過度複雜而脆弱的系統。改變GUI皮膚的設計不應造成Servlet和EJB的重編譯。
當系統變得龐大後,稍不注意就可能將依賴於客戶端的程式碼引入到服務端。這是因為IDE在編譯檔案時使用單一的classpath。Ant讓你更有效地控制構建活動。
設計你的構建檔案編譯大型專案的步驟:首先,編譯公共應用程式碼,將編譯結果打成JAR包檔案。然後,編譯上一層的專案程式碼,編譯時依靠第一步產生的JAR檔案。不斷重複這一過程,直到最高層的程式碼編譯完成。
分步構建強化了任務從屬關係管理。如果你工作在底層Java框架上,引用高層的GUI模板元件,這時程式碼不需要編譯。這是由於構建檔案在編譯底層框架時,在源路徑中沒有包含高層GUI皮膚元件的程式碼。
7. 定義並重用檔案路徑
如果檔案路徑在一個地方集中定義,並在整個構建檔案中得到重用,那麼構建檔案更易於理解。以下是這樣做的一個例子:
<project name="sample" default="compile" basedir="."> <path id="classpath.common"> <pathelement location="${jdom.jar.withpath}"/> ...etc </path> <path id="classpath.client"> <pathelement location="${guistuff.jar.withpath}"/> <pathelement location="${another.jar.withpath}"/> <!-- reuse the common classpath --> <path refid="classpath.common"/> </path> <target name="compile.common" depends="prepare"> <javac destdir="${dir.build}" srcdir="${dir.src}"> <classpath refid="classpath.common"/> <include name="com/oreilly/common/**"/> </javac> </target></project>
當 專案不斷增長,構建日益複雜時,這一技術越發體現出其價值。你可能為編譯不同層次的應用定義各自的檔案路徑,比如執行單元測試的、執行應用程式的、執行 Xdoclet的、生成JavaDocs的等等不同路徑。這種元件化路徑定義的方法比為每個任務單獨定義路徑要優越得多。否則,很容易丟失任務任務從屬關 系的軌跡。
8. 定義恰當的任務引數關係
假設dist任務從屬於jar任務,那麼哪個任務從屬於compile任 務,哪個任務從屬於prepare任務呢?Ant構建檔案最終定義了任務的從屬關係圖,它必須被仔細地定義和維護。應該定期檢查任務的從屬關係以保證構建 工作得到正確執行。大的構建檔案隨著時間推移趨向於增加更多的任務,所以到最後由於不必要的從屬關係導致構建工作非常困難。比如,你可能發現在程式設計師只是 需要編譯一些沒有使用EJB的GUI程式碼時,重新生成EJB程式碼。
以“最佳化”的名義忽略任務的從屬關係是另一種常見的錯誤。這種錯誤迫 使程式設計師為了得到恰當的結果必須記住並按照特定的順序呼叫一串任務。更好的做法是:提供描述清晰的公共任務,這些任務包含正確的任務從屬關係;另外提供一 套“專家”任務讓你能夠手工執行個別的構建步驟,這些任務不提供完整的構建過程,但是讓那些專家在快速而惱人的編碼期間跳過某些步驟
9.使用配置屬性
任何需要配置或可能發生變化的資訊都應作為Ant屬性定義下來。對於在構建檔案中多次出現的值也同樣處理。屬性既可以在構建檔案頭部定義,也可以為了更好的靈活性而在單獨的屬性檔案中定義。以下是在構建檔案中定義屬性的樣式:
<project name="sample" default="compile" basedir="."> <property name="dir.build" value="build"/> <property name="dir.src" value="src"/> <property name="jdom.home" value="../java-tools/jdom-b8"/> <property name="jdom.jar" value="jdom.jar"/> <property name="jdom.jar.withpath" value="${jdom.home}/build/${jdom.jar}"/> etc...</project>
或者你可以使用屬性檔案:
<project name="sample" default="compile" basedir="."> <property file="sample.properties"/> etc...</project>
在屬性檔案 sample.properties中:
dir.build=builddir.src=srcjdom.home=../java-tools/jdom-b8jdom.jar=jdom.jarjdom.jar.withpath=${jdom.home}/build/${jdom.jar}
用一個獨立的檔案定義屬性是有好處的,它可以清晰地定義構建中的可配置部分。另外,在開發者工作在不同作業系統的情況下,你可以在不同的平臺上提供該檔案的不同版本。
10. 保持構建過程獨立
為 了最大限度的擴充套件性,不要應用外部路徑和庫檔案。最重要的是不要依賴於程式設計師的CLASSPATH設定。取而代之的是,在構建檔案中使用相對路徑並定義自 己的路徑。如果你引用了絕對路徑如C:\java\tools,其他開發者未必使用與你相同的目錄結構,所以就無法使用你的構建檔案
如果你部署開發原始碼專案,應該提供包括所有需要的JAR檔案的發行版本,當然是在遵守許可協議的基礎上。對於內部專案,相關的JAR檔案都應在版本控制系統的管理中,並撿出到大家都知道的位置。
當你不得不應用外部路徑時,應將路徑定義為屬性。使程式設計師能夠湧適合他們自己的機器的引數過載這些屬性。你也可以使用以下語法引用環境變數:
<property environment="env"/><property name="dir.jboss" value="${env.JBOSS_HOME}"/>
11. 使用版本控制系統
構建檔案是一個重要的檔案,應該象程式碼一樣進行版本控制。當你標記你的程式碼時,也應用同樣的標籤標記構建檔案。這樣當你需要回溯構建舊版本的軟體時,能夠使用相對應的舊版本構建檔案。
除構建檔案之外,你還應在版本控制中維護第三方JAR檔案。同樣,這使你能夠重新構建舊版本的軟體。這也能夠更容易保證所有開發者擁有一致的JAR檔案,因為他們都是同構建檔案一起從版本控制系統中撿出的。
通常應避免在版本控制系統中存放構建輸出品。倘若你的原始碼很好地得到了版本控制,那麼透過構建過程你能夠重新生成任何版本的產品。
12. 把Ant作為“最小公分母”
假設你的開發團隊使用IDE,為什麼要為程式設計師透過點選圖示就能夠構建整個應用而煩惱呢?
IDE 的問題在團隊中是一個關於一致性和重現性的問題。幾乎所有的IDE設計初衷都是為了提高程式設計師的個人生產率,而不是開發團隊的持續構建。典型的IDE要求 每個程式設計師定義自己的專案檔案。程式設計師可能擁有不同的目錄結構,可能使用不同版本的庫檔案,還可能工作在不同的平臺上。這將導致出現這種情況:在A那裡運 行良好的程式碼,到B那裡就無法執行。
不管你的開發團隊使用何種IDE,一定要建立所有程式設計師都能夠使用的Ant構建檔案。要建立一個程 序員在將新程式碼提交版本控制系統前必須執行Ant 構建檔案的規則。這將確保程式碼是經過同一個Ant構建檔案構建的。當出現問題時,要使用專案標準的 Ant構建檔案,而不是透過某個IDE來執行一個乾淨的構建。
程式設計師可以自由選擇任何他們習慣使用的IDE。但是Ant應作為公共基線以保證永遠是可構建的。
13. 使用 zipfileset屬性
人們經常使用Ant產生WAR、JAR、ZIP和 EAR檔案。這些檔案通常都要求有一個特定的內部目錄結構,但其往往與你的原始碼和編譯環境的目錄結構不匹配。
一個最常用的方法是寫一個Ant任務按照期望的目錄結構把一大堆檔案複製到臨時目錄中,然後生成壓縮檔案。這不是最有效的方法。使用zipfileset屬性是更好的解決方案。它讓你從任何位置選擇檔案,然後把它們按照不同目錄結構放進壓縮檔案中。以下是一個例子:
<ear earfile="${dir.dist.server}/payroll.ear" appxml="${dir.resources}/application.xml"> <fileset dir="${dir.build}" includes="commonServer.jar"/> <fileset dir="${dir.build}"> <include name="payroll-ejb.jar"/> </fileset> <zipfileset dir="${dir.build}" prefix="lib"> <include name="hr.jar"/> <include name="billing.jar"/> </zipfileset> <fileset dir="."> <include name="lib/jdom.jar"/> <include name="lib/log4j.jar"/> <include name="lib/ojdbc14.jar"/> </fileset> <zipfileset dir="${dir.generated.src}" prefix="META-INF"> <include name="jboss-app.xml"/> </zipfileset></ear>
在這個例子中,所有JAR檔案都放在EAR檔案包的lib目錄中。hr.jar和billing.jar是從構建目錄複製過來的。因此我們使用zipfileset屬性把它們移動到EAR檔案包內部的lib目錄。prefix屬性指定了其在EAR檔案中的目標路徑。
14. 執行 Clean 構建任務的測試
假設你的構建檔案中有clean和compile的任務,執行以下的測試。第一步,執行ant clean;第二步,執行ant compile;第三步,再執行ant compile。第三步應該不作任何事情。如果檔案再次被編譯,說明你的構建檔案有問題。
構建檔案應該只在與輸出檔案相關聯的輸入檔案發生變化時,才應該執行任務。一個構建檔案在不必執行諸如編譯、複製或其他工作任務的時候執行這些等任務是低效的。當專案規模增長時,即使是小的低效工作也會成為大的問題。
15. 避免特定平臺的Ant包
不管什麼原因,有人喜歡用簡單的、名稱叫做compile之類的批檔案或指令碼裝載他們的產品。當你去看指令碼的內容,你會發現以下內容:
ant compile
其實開發人員熟悉Ant,並且完全能夠自己鍵入ant compile。請不要僅僅為了呼叫Ant而使用特定平臺的指令碼。這隻會使其他人在首次使用你的指令碼時,增加學習和理解的煩擾。除此之外,你不可能提供適用於每個作業系統的指令碼,這是真正煩擾其他使用者的地方。
總結
太多的公司依靠手工方法和程式來編譯程式碼和生成軟體釋出版本。那些不使用Ant或類似工具定義構建過程的開發團隊,花費了令人驚異的時間來捕捉程式碼編譯過程中出現的問題,這些在某些開發者那裡編譯成功的程式碼,到另一些開發者那裡卻失敗了。
生成並維護構建指令碼不是一項迷人的工作,但卻是一項必需的工作。一個好的Ant構建檔案將使你集中到更喜歡的工作——寫程式碼中!
轉載自:http://www.blogjava.net/sutao/articles/133961.html