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