Large Scale 的應用通常意味著:
-
目錄較多, 層次較深
-
依賴較多, 構建腳本依賴的第三方Ant Task, 項目依賴的第三方庫等
-
測試較多, 構建時間反饋周期較長
-
需要在不同操作系統上運行
-
需要在不同團隊成員的機器上運行
-
由于以上原因, 導致Ant腳本較長
1. 目錄較多, 層次較深
通常有兩種風格的解決方案
-
一是使用Ant-Contrib中的<foreach>來遍歷子目錄并依次調用其中的構建腳本,
一般是缺省的target
-
另外一種是用Ant自身的<subant>命令來搜索構建腳本并調用特定的target
第一種方案可看作深度優先, 不同的子目錄通常是系統的不同組件, 這種方式是把單個組件全部構建完再構建另外的組件
第二種方案可看作廣度優先, 類似于模板方法, 要求每個子系統的構建腳本都要定義特定的約定的target,
Root構建腳本會先調用所有構建腳本的某個target, 再調用另外的target
當然也可以用<foreach>來實現第二種風格
2. 依賴較多, 構建腳本依賴的第三方Ant Task, 項目依賴的第三方庫等
-
明確區分構建腳本的依賴和你的項目的依賴. 那些第三方的Ant
Task通常你只會在構建過程中用到它們, 而不會在產品中用到, 如 Checkstyle, Emma 等, 把它們放在單獨的目錄中, 打包時不要理會它們.
關于區分不同依賴的經驗, 參考<<CruiseControl
Enterprise 最佳實踐 (2) : Keep your dependencies to yourself>>
-
使用
ivy
來管理你項目的依賴
-
清理不必要的依賴或干擾:
-
總是提供 <clean> target, 并借助工具保證在需要的時刻總是能夠得到執行,
參見后面的章節
-
定義單獨的目錄來存放項目所有的輸出, 通常是頂級目錄下的名字叫做
target或者build或者dist之類的目錄, 其內部的層次結構應該與源文件的目錄結構一致
-
構建過程中拷貝需要的資源到上面定義的輸出目錄中, 而不是直接對資源操作
3. 測試較多, 構建反饋周期較長
一般你不會希望直到構建過程的末尾某個任務才失敗, 這樣你修正后不得不從頭再跑一遍來校驗,
有幾種方式來來縮短反饋時間
-
一種是先運行最可能失敗的任務, 利用前面提到的<subant>或<foreach>
-
一種是為每個任務都定義單獨的target, 或用"property"來選擇或忽略特定的target.
參見 target 的 if/unless 屬性, 及
Condition
元素. 如用property來控制運行所有測試還是某個測試:
<junit ...>
<!--
... -->
<test
name="${testcase}" if="testcase"/>
<batchtest
todir="${test.data.dir}" unless="testcase">
<fileset dir="${test.classes.dir}" includes="**/*Test.class" />
</batchtest>
</junit>
另外你希望一次構建能夠發現盡可能多的問題, 而不是出現第一個問題后構建就停止,
這樣的話可以批量修復它們然后再重新運行一次構建, 而不是一遍一遍的運行構建來逐個找出錯誤
-
可以用一些任務提供的 haltonerror, haltonfailure,
errorproperty 等來控制構建結束的時機和最后的結果, 如:
<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. 需要在不同操作系統上運行
-
利用 <exec>
的 os 或 osfamily 屬性來控制不同操作系統上的行為
-
路徑分隔符統一使用 "/"
5. 需要在不同團隊成員的機器上運行, 每個人環境都不一樣
其實這是配置管理的問題, 項目的所有文檔和依賴, 甚至包括Ant本身都應該包含在一個單根目錄下,
并且Check in到版本控制系統中. 里面所有涉及路徑的地方都使用相對路徑. 這樣項目就可以即拷即用
但總有一些對外部環境的依賴, 這是就要借助 Ant 強大而合理的 immutable
property 體系
-
所有可能變化的地方都使用 property 來引用
-
優先使用 JVM 的系統屬性, 而不是環境變量
-
使用 user.properties 文件定義環境相關的property,
并在構建腳本中最先引入 <property
file="${user.home}/user.properties"
/>
-
在引入 user.properties 之后, 為所有屬性定義合理的缺省值,
以處理 user.properties 不存在不完整的情況
-
后面兩步也可以用
<propertyfile>
來代替
一個應用就是可以在命令行傳入用戶名和密碼而不是把它們寫在腳本中, 這樣就避免了安全和隱私問題
其它的例子包括用 property 來定義 test filter,
或者來定義碰到第一次錯誤是退出還是繼續運行構建等
關于目錄的處理
-
為根目錄定義屬性, 以此為起點定義子目錄的屬性
-
總是使用 ${basedir}
作為相對路徑的前綴. 這樣可以保證即使 Ant 的工作路徑不同, 只要傳遞了正確的 basedir 屬性, 所有的相對路徑還是正確的.
-
使用 location
定義路徑, 而不是 value. Ant會將 location 展開為絕對路徑, 這樣即使傳遞到另外的 Ant Project 中, 它的值也不會變
6. 由于以上原因, 導致Ant腳本較長
Ant不是Script Language, 你更應該像編寫產品代碼一樣認真對待它的編寫風格
-
前面說過通過命令行參數控制執行的target, 參數多了, 就要有
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"/>
-
模塊化構建腳本, 使用
<macrodef>
等
-
抽取可復用的 macrodef 或 target 到單獨的腳本中,
并在其它腳本中 import
這個可復用的文件, 這樣有助于分離關注點, 使腳本更易維護
借助第三方工具來彌補Ant的局限
-
缺乏異常處理機制, 任何 BuildException 都會終止
Ant 的執行, 而任何 Task 都可能出現異常, 這樣有些清理操作就得不到執行. 解決方案:
a. Ant-Contrib的
<trycatch>
b. CruiseControl 的 AntPublisher.
CruiseControl定義了構建結束后的操作, 參見 <<CruiseControl
Enterprise 最佳實踐 (1) : Publish with a Publisher>>
-
缺乏依賴管理機制
前面已經提到, 借助
ivy
來管理,
ivy
已經成為了 Ant 的子項目
其它一些常用的風格
-
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名字來定義無法從命令行調用的"內部"target
其它一些常用的技巧
-
與用戶交互, 這在持續集成環境中應該不多:
<input>
-
并發執行:
<parallel>,
通常用來執行在后臺運行的任務
-
遞歸的property定義. Ant 缺省并不支持 property
的遞歸展開, 如 ${${os}.${prop}}. 但任何事情都可以用一個額外的中間層解決, 這里可以借用的機制是
<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}.$} 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>
這樣就可以動態的定義屬性
<macro.compose-property
name="some.property" prop="${component} os="${targetOS}"/>
<do-something-with property="${some.property}"/>
-
spawn, 不要使用spawn=true, 使用前面的<parallel>
缺省spawn等于false, 這種情況下Ant退出時會把所有fork的進程都殺掉.
把spawn設置成true可以令進程壽命長于Ant, 從而可以不堵塞Ant的執行而用來運行后臺程序. 但會帶來很多不好的地方,比如不能即時在console看到信息等.
并且壽命長于 Ant 在某些情況下不是我們期待的, 比如在 CruiseControl 的環境中運行 Ant, 如果Ant fork的進程沒有隨著Ant退出而退出,
那么CruiseControl會認為Ant進程還沒有結束, 從而一直在那里等待而不是執行后面的 Publishers.
用<parallel>的<daemon>來運行后臺程序
-
unix上的通配符 (Windows上沒問題): 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>
-
幾個沒在 jdk 文檔中說明的系統屬性? -Duser.country=EN -Duser.language=US
參考