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有一种限制, 模版必须在主配置文件中定义才有效.