I'm an Infrastructure Specialist at
ThoughtWorks. In my role I
make sure that we are building our software so it can successfully be
deployed to production. In this series of blog posts I hope
to
pass on my top ten tips for using CruiseControl Enterprise
effectively. I'm writing these with the developers or systems
administrators in mind: the people who most often manage
CruiseControl. However, I hope that anybody who is interested
in
Continuous Integration will get something from these articles.
在我上次的文章中,
我演示了如何使用bootstrapper來確保CruiseControl的配置文件是最新的. 在這篇文章中我會繼續(xù)討論這個話題. 這里便是導(dǎo)致在CruiseControl中引入Bootstrapper的問題:
當(dāng)你破壞了你的構(gòu)建腳本時你如何修復(fù)你的構(gòu)建?
想象一下這種情形: 你歡天喜地的對構(gòu)建腳本做了點修改來支持一個新的特性.
你的同事Bob走到你的桌旁想討論一下這個特性先. 但他并不能阻止你完成這個特性的熱情. 無論如何你都幾乎已經(jīng)快做完了. 于是你迅速的check
in, 然后轉(zhuǎn)過身在Bob提到釣魚之前拉著他聊工作.
于是最終Bob逛到飲水機(jī)旁去誘陷別人, 而你回來繼續(xù)工作.
你看了一下顯示你構(gòu)建結(jié)果的界面, 它居然是紅的! "業(yè)余的家伙們", 你想. "他們總是弄壞構(gòu)建". 你刷新了一下CruiseControl
Dashboard來檢查一下這次構(gòu)建中有哪些checkin (這里每個人都很頻繁的check in). 最終你發(fā)現(xiàn)構(gòu)建在一秒鐘之內(nèi)就失敗了,
因為 build.xml 是非法的.
突然之間多了點對你的構(gòu)建破壞者同事們的容忍, 你盯著那個 build.xml,
它依然顯示在你的IDE里面. 就是它了, 文件結(jié)尾處有一個多余的大大的 '#' 號. "詛咒這鍵盤布局", 你叫嚷著.
當(dāng)你輸入文件的最后一行時, 你錯過了回車鍵而敲了個 '#'. 你匆匆忙忙check in的時候沒有發(fā)現(xiàn)它,
而現(xiàn)在構(gòu)建腳本不會更新checkout了. 構(gòu)建腳本根本就不會運行, 因為它是非法的. 通常情況下它做的第一件事就是更新CruiseControl服務(wù)器上的源代碼工作拷貝來得到最新的修改.
看起來你得check in你的修復(fù), 然后到CruiseControl服務(wù)器上手動更新工作拷貝. 噢!
曾經(jīng)干過這些事(有時根本不需要假想一個同事來破壞構(gòu)建腳本),
因此我可以為 "bootstrapper"這種方法做擔(dān)保:
<?xml version="1.0"?>
<cruisecontrol>
<project name="my_great_app">
<bootstrappers>
<svnbootstrapper localWorkingCopy="${working.dir}/${project.name}" file="build.xml">
<svnbootstrapper localWorkingCopy="${working.dir}/${project.name}" file="ccbuild.xml">
</bootstrappers>
<modificationset quietperiod="30">
<svn LocalWorkingCopy="${working.dir}/${project.name}"/>
</modificationset>
<schedule interval="60">
<ant
antWorkingDir="${working.dir}/${project.name}"
antscript="${working.dir}/tools/apache-ant-1.6.5/bin/ant" />
</schedule>
</project>
</cruisecontrol>
在上面的例子中, bootstrapper會始終從Subversion中抓取最新的文件.
Bootstrapper在構(gòu)建發(fā)生之前運行, 不管構(gòu)建是否必要, 但是如果project被暫停了則不會運行. 但何必到此為止呢?
這將確保你能拿到最新的構(gòu)建腳本, 而后者將負(fù)責(zé)從你的版本控制系統(tǒng)中獲取最近的更新. 但是你的構(gòu)建腳本需要做這些事嗎? No,
Bootstrapper可以更新所有的工作拷貝, 如果你像這樣配置它的話:
<bootstrappers>
<svnbootstrapper localWorkingCopy="${working.dir}/${project.name}"/>
</bootstrappers>
現(xiàn)在CruiseControl將更新服務(wù)器上的工作拷貝中所有最近更改過的文件.
關(guān)于這種方法, 過去有個問題 - Bob 會抱怨說: "這就像我釣住了一條10磅的魚,
但是線卻斷了. 我提交了我的修改, 然后看見一次構(gòu)建被觸發(fā)了. 我等到構(gòu)建結(jié)束. 我的更改信息出現(xiàn)在了更改信息列表中, 但是 QA
人員說我的修改根本就不在這次構(gòu)建中! "
這個問題通常發(fā)生在你check in的時候CruiseControl正在
bootstrapping 以獲取最新的版本. 由于最初的那段和不支持原子提交的版本控制系統(tǒng)一起工作的歷史, CruiseControl內(nèi)部使用日期/時間來引用修改集.
在CruiseControl 2.7.1 中, 來自 ThoughtWorks Studios 的 CCE 團(tuán)隊修正了這個問題. 他們在
Subversion modification set 中引入了一個 "useLocalRevision " 屬性:
<bootstrappers>
<svnbootstrapper localWorkingCopy="${working.dir}/${project.name}" useLocalRevision="true"/>
</bootstrappers>
一旦 bootstrapper 更新了CruiseControl服務(wù)器上的工作拷貝, 它就使工作拷貝的svn版本號對ModificationSet來說是可用的了.
當(dāng)ModificationSet試圖計算出這次構(gòu)建中哪些修改是新增的時候, 它就會限制它的查詢只查到工作拷貝的版本號. 這就使CruiseControl準(zhǔn)確的報告修改.
它對于使用CruiseControl的大項目來說是一個巨大的進(jìn)步. 這也將把你從與Bob的更多次談話中解救出來. <svn> 上的 "useLocalRevision"
另一個有用的副作用是svn的版本號能夠作為一個叫做"${svnrevision}"的屬性傳遞到構(gòu)建中.
這就使稍后用正確的版本號給這次構(gòu)建打標(biāo)簽變得易如反掌.
當(dāng)我們涉及CruiseControl和Subversion(或者任何真正支持原子提交的版本控制系統(tǒng))捆綁工作的時候: 永遠(yuǎn)不要使用 QuietPeriod. QuietPeriod是在當(dāng)你使用CVS或者其它不支持原子操作的版本控制系統(tǒng)的時候,
用來避免CruiseControl過早構(gòu)建的. 它只會拖慢你的構(gòu)建.
有更多的 bootstrapper
可用來做一些有意思或有用的事: CruiseControl支持很多版本控制系統(tǒng). 它們中的大多數(shù)都帶有一個可選的 bootstrapper.
Bob和我前面已經(jīng)描述了版本控制系統(tǒng)的bootstrapper的用處, 下面簡單的說一下剩余的幾個:
-
AntBootStrapper 會在構(gòu)建開始之前執(zhí)行一個Apache Ant腳本文件.
大部分情況下你用不到這個, 但是我曾經(jīng)見過用它來獲取依賴項, 如果依賴項有變化的話就會觸發(fā)一次構(gòu)建
-
ExecBootStrapper 會執(zhí)行任何你告訴他的腳本或命令
-
LockFileBootStrapper 比較有意思. 繼續(xù)往下讀
你有很多項目運行在同一個CruiseControl服務(wù)器上? 很好.
你配置了兩個或更多的構(gòu)建線程? 你有兩個或多個項目同時構(gòu)建? 太好了. 多個, 并發(fā)的項目是CruiseControl從2004年夏天開始支持的特性.
什么? 其中的兩個項目有時會競爭相同的資源? 噢, 這不好. 尤其煩人如果它只是一天或一段時間只發(fā)生一次. 你可以這樣做:
<cruisecontrol>
<system>
<configuration>
<threads count="2" />
</configuration>
</system>
<project name="test-1" >
<listeners>
<lockfilelistener lockFile="/tmp/lock" projectName="${project.name}"/>
</listeners>
<bootstrappers>
<lockfilebootstrapper lockFile="/tmp/lock" projectName="${project.name}" />
</bootstrappers>
<schedule interval="30">
<exec command="/bin/true" />
</schedule>
</project>
<project name="test-2" >
<listeners>
<lockfilelistener lockFile="/tmp/lock" projectName="${project.name}"/>
</listeners>
<bootstrappers>
<lockfilebootstrapper lockFile="/tmp/lock" projectName="${project.name}" />
</bootstrappers>
<schedule interval="30">
<exec command="/bin/true" />
</schedule>
</project>
</cruisecontrol>
lockfilebootstrapper 會在構(gòu)建開始之前運行, 就像bootstrapper應(yīng)該做的那樣. 它將在 "lockfile"
屬性指定的文件中查找項目的名稱. 如果 "lockfile" 指定的文件存在, 并且里面的項目名稱和在配置中設(shè)置的期待不匹配的話,
它將取消整個構(gòu)建企圖. 這樣的話, 一個項目就可以警告其它項目說我正在構(gòu)建, 而你永遠(yuǎn)也不會看到兩個不能坐在一起的項目在同時構(gòu)建.
然而你其它的那些不與第一個項目共享相同外部依賴的項目則可以同時運行. lockfilelistener 會在構(gòu)建之后清除任何舊的 "lockfile",
這樣的話你就不會永久性的阻止某些構(gòu)建的運行.
"等等", Bob說, "我剛剛不得不花了10分鐘來等待FooLib項目得到構(gòu)建! 這怎么辦?" "Bob," 你并非不近人情的說:
"你知道我們只有一臺集成測試服務(wù)器. 現(xiàn)在它用來運行CruiseControl. 下次你和CTO一起釣魚的時候, 你可以告訴他項目的延遲,
再問問他我們能否花點明年的預(yù)算再購買一臺測試服務(wù)器. "
©Julian Simpson 2008. All rights reserved.
在
bootstrapper 概念之前, CruiseControl只是檢查源代碼是否有修改, 不會更新源代碼工作拷貝,
通常都是用戶的構(gòu)建腳本來負(fù)責(zé)更新源代碼. 而 bootstrapper 最開始的設(shè)計意圖就是作者在文中提到的那樣, 只是用來更新構(gòu)建腳本,
再由構(gòu)建腳本負(fù)責(zé)更新源代碼. 這在一些
bootstrapper 文檔中可略見端倪. 只是人們馬上就發(fā)現(xiàn) boostrapper 可以用來更新所有源文件,
進(jìn)而可以用來在構(gòu)建之前做任何事情.
其實 bootstrapper 和
publisher 是一對兄弟, 分別用來在構(gòu)建前后插入用戶定義的操作, 就像 AOP 里的 before/after 一樣.
See also:
-
<<CruiseControl
的 108 種調(diào)度模式>>
-
<<CruiseControl
Enterprise 最佳實踐 (1) : Publish with a Publisher>>
-
<<CruiseControl
Enterprise 最佳實踐 (2) : Keep your dependencies to yourself>>
-
<<CruiseControl
Enterprise 最佳實踐 (3) : Configuring CruiseControl the CruiseControl
way>>