Large Scale 的應(yīng)用通常意味著:
-
目錄較多, 層次較深
-
依賴較多, 構(gòu)建腳本依賴的第三方Ant Task, 項(xiàng)目依賴的第三方庫(kù)等
-
測(cè)試較多, 構(gòu)建時(shí)間反饋周期較長(zhǎng)
-
需要在不同操作系統(tǒng)上運(yùn)行
-
需要在不同團(tuán)隊(duì)成員的機(jī)器上運(yùn)行
-
由于以上原因, 導(dǎo)致Ant腳本較長(zhǎng)
1. 目錄較多, 層次較深
通常有兩種風(fēng)格的解決方案
-
一是使用Ant-Contrib中的<foreach>來(lái)遍歷子目錄并依次調(diào)用其中的構(gòu)建腳本,
一般是缺省的target
-
另外一種是用Ant自身的<subant>命令來(lái)搜索構(gòu)建腳本并調(diào)用特定的target
第一種方案可看作深度優(yōu)先, 不同的子目錄通常是系統(tǒng)的不同組件, 這種方式是把單個(gè)組件全部構(gòu)建完再構(gòu)建另外的組件
第二種方案可看作廣度優(yōu)先, 類似于模板方法, 要求每個(gè)子系統(tǒng)的構(gòu)建腳本都要定義特定的約定的target,
Root構(gòu)建腳本會(huì)先調(diào)用所有構(gòu)建腳本的某個(gè)target, 再調(diào)用另外的target
當(dāng)然也可以用<foreach>來(lái)實(shí)現(xiàn)第二種風(fēng)格
2. 依賴較多, 構(gòu)建腳本依賴的第三方Ant Task, 項(xiàng)目依賴的第三方庫(kù)等
-
明確區(qū)分構(gòu)建腳本的依賴和你的項(xiàng)目的依賴. 那些第三方的Ant
Task通常你只會(huì)在構(gòu)建過(guò)程中用到它們, 而不會(huì)在產(chǎn)品中用到, 如 Checkstyle, Emma 等, 把它們放在單獨(dú)的目錄中, 打包時(shí)不要理會(huì)它們.
關(guān)于區(qū)分不同依賴的經(jīng)驗(yàn), 參考<<CruiseControl
Enterprise 最佳實(shí)踐 (2) : Keep your dependencies to yourself>>
-
使用
ivy
來(lái)管理你項(xiàng)目的依賴
-
清理不必要的依賴或干擾:
-
總是提供 <clean> target, 并借助工具保證在需要的時(shí)刻總是能夠得到執(zhí)行,
參見后面的章節(jié)
-
定義單獨(dú)的目錄來(lái)存放項(xiàng)目所有的輸出, 通常是頂級(jí)目錄下的名字叫做
target或者build或者dist之類的目錄, 其內(nèi)部的層次結(jié)構(gòu)應(yīng)該與源文件的目錄結(jié)構(gòu)一致
-
構(gòu)建過(guò)程中拷貝需要的資源到上面定義的輸出目錄中, 而不是直接對(duì)資源操作
3. 測(cè)試較多, 構(gòu)建反饋周期較長(zhǎng)
一般你不會(huì)希望直到構(gòu)建過(guò)程的末尾某個(gè)任務(wù)才失敗, 這樣你修正后不得不從頭再跑一遍來(lái)校驗(yàn),
有幾種方式來(lái)來(lái)縮短反饋時(shí)間
-
一種是先運(yùn)行最可能失敗的任務(wù), 利用前面提到的<subant>或<foreach>
-
一種是為每個(gè)任務(wù)都定義單獨(dú)的target, 或用"property"來(lái)選擇或忽略特定的target.
參見 target 的 if/unless 屬性, 及
Condition
元素. 如用property來(lái)控制運(yùn)行所有測(cè)試還是某個(gè)測(cè)試:
<junit ...>
<!--
... -->
<test
name="${testcase}" if="testcase"/>
<batchtest
todir="${test.data.dir}" unless="testcase">
<fileset dir="${test.classes.dir}" includes="**/*Test.class" />
</batchtest>
</junit>
另外你希望一次構(gòu)建能夠發(fā)現(xiàn)盡可能多的問(wèn)題, 而不是出現(xiàn)第一個(gè)問(wèn)題后構(gòu)建就停止,
這樣的話可以批量修復(fù)它們?nèi)缓笤僦匦逻\(yùn)行一次構(gòu)建, 而不是一遍一遍的運(yùn)行構(gòu)建來(lái)逐個(gè)找出錯(cuò)誤
-
可以用一些任務(wù)提供的 haltonerror, haltonfailure,
errorproperty 等來(lái)控制構(gòu)建結(jié)束的時(shí)機(jī)和最后的結(jié)果, 如:
<junit haltonerror="false" haltonfailure="false" errorProperty="test.failed" failureProperty="test.failed" ...>
<!-- ... -->
</junit>
<fail if="test.failed">Tests failed. Check log or reports for details</fail>
4. 需要在不同操作系統(tǒng)上運(yùn)行
-
利用 <exec>
的 os 或 osfamily 屬性來(lái)控制不同操作系統(tǒng)上的行為
-
路徑分隔符統(tǒng)一使用 "/"
5. 需要在不同團(tuán)隊(duì)成員的機(jī)器上運(yùn)行, 每個(gè)人環(huán)境都不一樣
其實(shí)這是配置管理的問(wèn)題, 項(xiàng)目的所有文檔和依賴, 甚至包括Ant本身都應(yīng)該包含在一個(gè)單根目錄下,
并且Check in到版本控制系統(tǒng)中. 里面所有涉及路徑的地方都使用相對(duì)路徑. 這樣項(xiàng)目就可以即拷即用
但總有一些對(duì)外部環(huán)境的依賴, 這是就要借助 Ant 強(qiáng)大而合理的 immutable
property 體系
-
所有可能變化的地方都使用 property 來(lái)引用
-
優(yōu)先使用 JVM 的系統(tǒng)屬性, 而不是環(huán)境變量
-
使用 user.properties 文件定義環(huán)境相關(guān)的property,
并在構(gòu)建腳本中最先引入 <property
file="${user.home}/user.properties"
/>
-
在引入 user.properties 之后, 為所有屬性定義合理的缺省值,
以處理 user.properties 不存在不完整的情況
-
后面兩步也可以用
<propertyfile>
來(lái)代替
一個(gè)應(yīng)用就是可以在命令行傳入用戶名和密碼而不是把它們寫在腳本中, 這樣就避免了安全和隱私問(wèn)題
其它的例子包括用 property 來(lái)定義 test filter,
或者來(lái)定義碰到第一次錯(cuò)誤是退出還是繼續(xù)運(yùn)行構(gòu)建等
關(guān)于目錄的處理
-
為根目錄定義屬性, 以此為起點(diǎn)定義子目錄的屬性
-
總是使用 ${basedir}
作為相對(duì)路徑的前綴. 這樣可以保證即使 Ant 的工作路徑不同, 只要傳遞了正確的 basedir 屬性, 所有的相對(duì)路徑還是正確的.
-
使用 location
定義路徑, 而不是 value. Ant會(huì)將 location 展開為絕對(duì)路徑, 這樣即使傳遞到另外的 Ant Project 中, 它的值也不會(huì)變
6. 由于以上原因, 導(dǎo)致Ant腳本較長(zhǎng)
Ant不是Script Language, 你更應(yīng)該像編寫產(chǎn)品代碼一樣認(rèn)真對(duì)待它的編寫風(fēng)格
-
前面說(shuō)過(guò)通過(guò)命令行參數(shù)控制執(zhí)行的target, 參數(shù)多了, 就要有
Usage, Help
<target name="usage">
<echo message=" Execute 'ant a' for a."/>
<echo message=" Execute 'ant b' for b."/>
<echo message=" Execute 'ant -Dtest.filter=**/*SeleniumTest.class' for specific test cases."/>
<echo message=" Execute 'ant -Dsome.property=xxx' for xxx."/>
</target>
<target name="help" depends="usage"/>
-
模塊化構(gòu)建腳本, 使用
<macrodef>
等
-
抽取可復(fù)用的 macrodef 或 target 到單獨(dú)的腳本中,
并在其它腳本中 import
這個(gè)可復(fù)用的文件, 這樣有助于分離關(guān)注點(diǎn), 使腳本更易維護(hù)
借助第三方工具來(lái)彌補(bǔ)Ant的局限
-
缺乏異常處理機(jī)制, 任何 BuildException 都會(huì)終止
Ant 的執(zhí)行, 而任何 Task 都可能出現(xiàn)異常, 這樣有些清理操作就得不到執(zhí)行. 解決方案:
a. Ant-Contrib的
<trycatch>
b. CruiseControl 的 AntPublisher.
CruiseControl定義了構(gòu)建結(jié)束后的操作, 參見 <<CruiseControl
Enterprise 最佳實(shí)踐 (1) : Publish with a Publisher>>
-
缺乏依賴管理機(jī)制
前面已經(jīng)提到, 借助
ivy
來(lái)管理,
ivy
已經(jīng)成為了 Ant 的子項(xiàng)目
其它一些常用的風(fēng)格
-
Define tasks, datatypes, and properties
before targets.
-
Order attributes logically and
consistently.
-
Separate words in target names
with a hyphen.
-
Separate words with a dot (.)
character in property names.
-
Include an "all" target that builds
it all.
-
Define reusable paths.
-
Use explicit classpaths wherever
possible.
-
Provide visual and XML test results.
Use <formatter type="brief" usefile="false"/> , <formatter type="xml"/> and
<junitreport>.
-
用以連字符(-)開頭的target名字來(lái)定義無(wú)法從命令行調(diào)用的"內(nèi)部"target
其它一些常用的技巧
-
與用戶交互, 這在持續(xù)集成環(huán)境中應(yīng)該不多:
<input>
-
并發(fā)執(zhí)行:
<parallel>,
通常用來(lái)執(zhí)行在后臺(tái)運(yùn)行的任務(wù)
-
遞歸的property定義. Ant 缺省并不支持 property
的遞歸展開, 如 ${${os}.${prop}}. 但任何事情都可以用一個(gè)額外的中間層解決, 這里可以借用的機(jī)制是
<macrodef>
Here is the macro (along lines
suggested by Peter Reilly with reference to
http://ant.apache.org/faq.html#propertyvalue-as-name-for-property:
<!-- Allows you define a new property with a value of ${${a}.${b}} which can't be done by the Property task alone. -->
<macrodef name="dymanic-property">
<attribute name="name"/>
<attribute name="prop"/>
<attribute name="os"/>
<sequential>
<property name="@{name}" value="${@{os}.@{prop}}"/>
</sequential>
</macrodef>
這樣就可以動(dòng)態(tài)的定義屬性
<macro.compose-property
name="some.property" prop="${component} os="${targetOS}"/>
<do-something-with property="${some.property}"/>
-
spawn, 不要使用spawn=true, 使用前面的<parallel>
缺省spawn等于false, 這種情況下Ant退出時(shí)會(huì)把所有fork的進(jìn)程都?xì)⒌?
把spawn設(shè)置成true可以令進(jìn)程壽命長(zhǎng)于Ant, 從而可以不堵塞Ant的執(zhí)行而用來(lái)運(yùn)行后臺(tái)程序. 但會(huì)帶來(lái)很多不好的地方,比如不能即時(shí)在console看到信息等.
并且壽命長(zhǎng)于 Ant 在某些情況下不是我們期待的, 比如在 CruiseControl 的環(huán)境中運(yùn)行 Ant, 如果Ant fork的進(jìn)程沒有隨著Ant退出而退出,
那么CruiseControl會(huì)認(rèn)為Ant進(jìn)程還沒有結(jié)束, 從而一直在那里等待而不是執(zhí)行后面的 Publishers.
用<parallel>的<daemon>來(lái)運(yùn)行后臺(tái)程序
-
unix上的通配符 (Windows上沒問(wèn)題): On Unix-like
systems, wildcards are understood by shell intepreters, not by individual binary
executables as /usr/bin/ls or shell scripts.
<target name="list">
<exec executable="sh">
<arg value="-c"/>
<arg value="ls /path/to/some/xml/files/*.xml"/>
</exec>
</target>
-
幾個(gè)沒在 jdk 文檔中說(shuō)明的系統(tǒng)屬性? -Duser.country=EN -Duser.language=US
參考