I was 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.
請(qǐng)告訴我在計(jì)算機(jī)系統(tǒng)中重復(fù)是一件好事. 我諒你也不敢. 給我你的意見, 我們可以討論它. 我就是那個(gè)在過去十年中, 把最好的時(shí)光花費(fèi)在系統(tǒng)管理,
運(yùn)行別人代碼上的人. 這樣一來你可能會(huì)猜出我是"重復(fù)是魔鬼"俱樂部的持卡會(huì)員. 如果你不是會(huì)員的話, 這可能不是給你看的文章.
重復(fù)不只是蔓延到你的業(yè)務(wù)代碼中. 當(dāng)你不注意的時(shí)候它同樣會(huì)滲透到構(gòu)建和持續(xù)集成系統(tǒng)中.
很可能你的團(tuán)隊(duì)中有個(gè)家伙就正在拷貝粘貼點(diǎn)什么東西到構(gòu)建中. 沒必要著急去阻止他們. 繼續(xù)往下讀你就會(huì)發(fā)現(xiàn)如何把CruiseControl的配置拉回正確的軌道.
這里有個(gè)關(guān)于重復(fù)的例子. 你可以看到文件中有大量的重復(fù). 我并不是一字不差的把它從某個(gè)項(xiàng)目中拿出來講(我愿意在一個(gè)叫做 Groucho
的項(xiàng)目中工作), 不過它離真實(shí)生活中的情景也差不多.
<?xml version="1.0"?>
<cruisecontrol>
<project name="chico" buildafterfailed="false">
<listeners>
<currentbuildstatuslistener file="/var/tmp/cruise/logs/chico/status.txt"/>
</listeners>
<bootstrappers>
<svnbootstrapper localWorkingCopy="/var/tmp/cruise/projects/chico/"/>
</bootstrappers>
<log dir="/var/tmp/cruise/logs">
<merge dir="/var/tmp/cruise/projects/chico/build/test/log"/>
</log>
<schedule interval="60">
<ant antWorkingDir="/var/tmp/cruise/projects/chico" antscript="/var/tmp/cruise/ant/bin/ant" uselogger="true"/>
</schedule>
</project>
<project name="groucho" buildafterfailed="false">
<listeners>
<currentbuildstatuslistener file="/var/tmp/cruise/logs/groucho/status.txt"/>
</listeners>
<bootstrappers>
<svnbootstrapper localWorkingCopy="/var/tmp/cruise/projects/groucho/"/>
</bootstrappers>
<log dir="/var/tmp/cruise/logs">
<merge dir="/var/tmp/cruise/projects/groucho/build/test/log"/>
</log>
<schedule interval="60">
<ant antWorkingDir="/var/tmp/cruise/projects/groucho" antscript="/var/tmp/cruise/ant/bin/ant" uselogger="true"/>
</schedule>
</project>
</cruisecontrol>
幸運(yùn)的是, CruiseControl中有辦法來消除這種重復(fù)
CruiseControl從2005年開始就支持在config.xml中使用屬性. 他們被設(shè)計(jì)成Apache Ant風(fēng)格的屬性,
使用美元符號(hào)和花括號(hào): ${cruise.home}. 這個(gè)特性立馬就可以用來替換你的config.xml中常數(shù)類的值,
比如你Ant腳本的位置和日志文件的目錄
這是一個(gè)巨大的進(jìn)步, 但還不夠. 那些在配置文件本身里面定義的東西怎么辦? 比如項(xiàng)目的名字? 作者已經(jīng)提前考慮到這一點(diǎn)了:
有個(gè)魔法屬性叫做"${project.name}"可以供你使用. 它的解釋被限制在包含它的項(xiàng)目?jī)?nèi), 這樣你就不會(huì)在 chico 項(xiàng)目中引用到
harpo, 反之亦然.
來看一下引入這些屬性后配置文件的樣子:
<?xml version="1.0"?>
<cruisecontrol>
<property name="cruise.dir" value="/var/tmp/cruise" />
<property name="log.dir" value="${cruise.dir}/logs/" />
<property name="projects.dir" value="${cruise.dir}/projects" />
<property name="ant.script" value="${cruise.dir}/ant/bin/ant" />
<project name="chico" buildafterfailed="false">
<listeners>
<currentbuildstatuslistener file="${log.dir}/${project.name}/status.txt"/>
</listeners>
<bootstrappers>
<svnbootstrapper localWorkingCopy="${projects.dir}/${project.name}/"/>
</bootstrappers>
<log dir="${log.dir}">
<merge dir="${projects.dir}/${project.name}/build/test/log"/>
</log>
<schedule interval="60">
<ant antWorkingDir="${projects.dir}/${project.name}" antscript="${ant.script}" uselogger="true"/>
</schedule>
</project>
<project name="groucho" buildafterfailed="false">
<listeners>
<currentbuildstatuslistener file="${log.dir}/${project.name}/status.txt"/>
</listeners>
<bootstrappers>
<svnbootstrapper localWorkingCopy="${projects.dir}/${project.name}/"/>
</bootstrappers>
<log dir="${log.dir}">
<merge dir="${projects.dir}/${project.name}/build/test/log"/>
</log>
<schedule interval="60">
<ant antWorkingDir="${projects.dir}/${project.name}" antscript="${ant.script}" uselogger="true"/>
</schedule>
</project>
</cruisecontrol>
看起來好多了. 如果你想改變項(xiàng)目的名稱, 或者它引用的某個(gè)路徑, 你只需要修改一個(gè)地方. 但是依然有某些樣板一遍又一遍的重復(fù)著. -
也許我們可以做的更好.
屬性其實(shí)有更多魔術(shù). ${project.name}動(dòng)態(tài)的隨著包圍著它的項(xiàng)目的改變而改變.
你不僅能夠把它用在配置值中, 你還可以把它定義為另外某個(gè)屬性值的一部分. 這對(duì)你長(zhǎng)期忍受的配置文件來說意味著你可以為所有的項(xiàng)目路徑聲明 一個(gè)
屬性, 讓它惰性求值來適應(yīng)每個(gè)項(xiàng)目. 這可能引起你的同事的困惑, 當(dāng)他們第一次看到這個(gè)配置文件的時(shí)候 -
"同一個(gè)屬性怎么能夠在這么多不同的情況下都工作呢?"
<?xml version="1.0"?>
<cruisecontrol>
<property name="cruise.dir" value="/var/tmp/cruise" />
<property name="log.dir" value="${cruise.dir}/logs/" />
<property name="project.dir" value="${cruise.dir}/projects/${project.name}" />
<property name="ant.script" value="${cruise.dir}/ant/bin/ant" />
<property name="status.file" value="${log.dir}/${project.name}/status.txt" />
<project name="chico" buildafterfailed="false">
<listeners>
<currentbuildstatuslistener file="${status.file}"/>
</listeners>
<bootstrappers>
<svnbootstrapper localWorkingCopy="${project.dir}"/>
</bootstrappers>
<log dir="${log.dir}">
<merge dir="${project.dir}/build/test/log"/>
</log>
<schedule interval="60">
<ant antWorkingDir="${project.dir}" antscript="${ant.script}" uselogger="true"/>
</schedule>
</project>
<project name="groucho" buildafterfailed="false">
<listeners>
<currentbuildstatuslistener file="${status.file}"/>
</listeners>
<bootstrappers>
</bootstrappers>
<log dir="${log.dir}">
<merge dir="${project.dir}/build/test/log"/>
</log>
<schedule interval="60">
<ant antWorkingDir="${project.dir}" antscript="${ant.script}" uselogger="true"/>
</schedule>
</project><svnbootstrapper localWorkingCopy="${project.dir}"/>
</cruisecontrol>
大點(diǎn)兒的項(xiàng)目中經(jīng)常發(fā)生的一件事是配置文件變得很龐大. 雖然你可以做些用 XSLT
來生成配置文件之類的事(這種方法曾經(jīng)弄得我很頭疼), 但實(shí)際上你能夠把配置文件分散到小文件中. 在CruiseControl的配置文件中這個(gè)強(qiáng)大的特性叫做
<include.projects> - 它允許你引用另外一個(gè)配置文件. 它和Ant中的<import>功能很像. 最終你維護(hù)著一個(gè)
config.xml 文件, 里面包含了變量定義(它們對(duì)于include進(jìn)來的文件也生效), 和一堆小配置文件, 里面包含單個(gè)項(xiàng)目的定義.
在我現(xiàn)在的項(xiàng)目中這個(gè)特性讓添加和移除項(xiàng)目變得非常容易. 甚至當(dāng)有人刪除了一個(gè)小配置文件卻忘了移除對(duì)應(yīng)的 <include.projects>
的時(shí)候也沒問題 - 文件不存在的時(shí)候它就不會(huì)被導(dǎo)入進(jìn)來. 跟蹤 CruiseControl 配置文件的改變也變得容易很多.
下面是現(xiàn)在的配置文件:
<?xml version="1.0"?>
<cruisecontrol>
<property name="cruise.dir" value="/var/tmp/cruise" />
<property name="log.dir" value="${cruise.dir}/logs/" />
<property name="project.dir" value="${cruise.dir}/projects/${project.name}" />
<property name="ant.script" value="${cruise.dir}/ant/bin/ant" />
<property name="status.file" value="${log.dir}/${project.name}/status.txt" />
<include.projects file="projects/chico.xml" />
<include.projects file="projects/groucho.xml" />
</cruisecontrol>
這里是其中一個(gè)項(xiàng)目:
<?xml version="1.0"?>
<cruisecontrol>
<project name="chico" buildafterfailed="false">
<listeners>
<currentbuildstatuslistener file="${status.file}"/>
</listeners>
<bootstrappers>
<svnbootstrapper localWorkingCopy="${project.dir}"/>
</bootstrappers>
<log dir="${log.dir}">
<merge dir="${project.dir}/build/test/log"/>
</log>
<schedule interval="60">
<ant antWorkingDir="${project.dir}" antscript="${ant.script}" uselogger="true"/>
</schedule>
</project>
</cruisecontrol>
希望這能幫助讓你的CruiseControl的配置文件更容易維護(hù).
我相信持續(xù)集成應(yīng)該很簡(jiǎn)單, 即使這會(huì)讓我失業(yè).
©Julian Simpson 2008. All rights reserved.
------流行的分割線------
其實(shí) Julian 忘了說 CruiseControl 的另外一種機(jī)制: 用<plugin>來定義項(xiàng)目模板:
<?xml version="1.0"?>
<cruisecontrol>
<property name="cruise.dir" value="/var/tmp/cruise" />
<property name="log.dir" value="${cruise.dir}/logs/" />
<property name="project.dir" value="${cruise.dir}/projects/${project.name}" />
<property name="ant.script" value="${cruise.dir}/ant/bin/ant" />
<property name="status.file" value="${log.dir}/${project.name}/status.txt" />
<plugin name="project" buildafterfailed="false">
<listeners>
<currentbuildstatuslistener file="${status.file}"/>
</listeners>
<bootstrappers>
<svnbootstrapper localWorkingCopy="${project.dir}"/>
</bootstrappers>
<log dir="${log.dir}">
<merge dir="${project.dir}/build/test/log"/>
</log>
<schedule interval="60">
<ant antWorkingDir="${project.dir}" antscript="${ant.script}" uselogger="true"/>
</schedule>
</plugin>
<project name="chico">
<project name="groucho" />
</cruisecontrol>
如果結(jié)合模版和include.projects, 最終的配置文件可能會(huì)非常簡(jiǎn)單; 只不過CruiseControl有一種限制, 模版必須在主配置文件中定義才有效.