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的配置文件是最新的. 在这篇文章中我会继续讨论这个话题. 这里便是导致在CruiseControl中引入Bootstrapper的问题:
当你破坏了你的构建脚本时你如何修复你的构建?
想象一下这种情形: 你欢天喜地的对构建脚本做了点修改来支持一个新的特性.
你的同事Bob走到你的桌旁想讨论一下这个特性先. 但他并不能阻止你完成这个特性的热情. 无论如何你都几乎已经快做完了. 于是你迅速的check
in, 然后转过身在Bob提到钓鱼之前拉着他聊工作.
于是最终Bob逛到饮水机旁去诱陷别人, 而你回来继续工作.
你看了一下显示你构建结果的界面, 它居然是红的! "业余的家伙们", 你想. "他们总是弄坏构建". 你刷新了一下CruiseControl
Dashboard来检查一下这次构建中有哪些checkin (这里每个人都很频繁的check in). 最终你发现构建在一秒钟之内就失败了,
因为 build.xml 是非法的.
突然之间多了点对你的构建破坏者同事们的容忍, 你盯着那个 build.xml,
它依然显示在你的IDE里面. 就是它了, 文件结尾处有一个多余的大大的 '#' 号. "诅咒这键盘布局", 你叫嚷着.
当你输入文件的最后一行时, 你错过了回车键而敲了个 '#'. 你匆匆忙忙check in的时候没有发现它,
而现在构建脚本不会更新checkout了. 构建脚本根本就不会运行, 因为它是非法的. 通常情况下它做的第一件事就是更新CruiseControl服务器上的源代码工作拷贝来得到最新的修改.
看起来你得check in你的修复, 然后到CruiseControl服务器上手动更新工作拷贝. 噢!
曾经干过这些事(有时根本不需要假想一个同事来破坏构建脚本),
因此我可以为 "bootstrapper"这种方法做担保:
<?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在构建发生之前运行, 不管构建是否必要, 但是如果project被暂停了则不会运行. 但何必到此为止呢?
这将确保你能拿到最新的构建脚本, 而后者将负责从你的版本控制系统中获取最近的更新. 但是你的构建脚本需要做这些事吗? No,
Bootstrapper可以更新所有的工作拷贝, 如果你像这样配置它的话:
<bootstrappers>
<svnbootstrapper localWorkingCopy="${working.dir}/${project.name}"/>
</bootstrappers>
现在CruiseControl将更新服务器上的工作拷贝中所有最近更改过的文件.
关于这种方法, 过去有个问题 - Bob 会抱怨说: "这就像我钓住了一条10磅的鱼,
但是线却断了. 我提交了我的修改, 然后看见一次构建被触发了. 我等到构建结束. 我的更改信息出现在了更改信息列表中, 但是 QA
人员说我的修改根本就不在这次构建中! "
这个问题通常发生在你check in的时候CruiseControl正在
bootstrapping 以获取最新的版本. 由于最初的那段和不支持原子提交的版本控制系统一起工作的历史, CruiseControl内部使用日期/时间来引用修改集.
在CruiseControl 2.7.1 中, 来自 ThoughtWorks Studios 的 CCE 团队修正了这个问题. 他们在
Subversion modification set 中引入了一个 "useLocalRevision " 属性:
<bootstrappers>
<svnbootstrapper localWorkingCopy="${working.dir}/${project.name}" useLocalRevision="true"/>
</bootstrappers>
一旦 bootstrapper 更新了CruiseControl服务器上的工作拷贝, 它就使工作拷贝的svn版本号对ModificationSet来说是可用的了.
当ModificationSet试图计算出这次构建中哪些修改是新增的时候, 它就会限制它的查询只查到工作拷贝的版本号. 这就使CruiseControl准确的报告修改.
它对于使用CruiseControl的大项目来说是一个巨大的进步. 这也将把你从与Bob的更多次谈话中解救出来. <svn> 上的 "useLocalRevision"
另一个有用的副作用是svn的版本号能够作为一个叫做"${svnrevision}"的属性传递到构建中.
这就使稍后用正确的版本号给这次构建打标签变得易如反掌.
当我们涉及CruiseControl和Subversion(或者任何真正支持原子提交的版本控制系统)捆绑工作的时候: 永远不要使用 QuietPeriod. QuietPeriod是在当你使用CVS或者其它不支持原子操作的版本控制系统的时候,
用来避免CruiseControl过早构建的. 它只会拖慢你的构建.
有更多的 bootstrapper
可用来做一些有意思或有用的事: CruiseControl支持很多版本控制系统. 它们中的大多数都带有一个可选的 bootstrapper.
Bob和我前面已经描述了版本控制系统的bootstrapper的用处, 下面简单的说一下剩余的几个:
-
AntBootStrapper 会在构建开始之前执行一个Apache Ant脚本文件.
大部分情况下你用不到这个, 但是我曾经见过用它来获取依赖项, 如果依赖项有变化的话就会触发一次构建
-
ExecBootStrapper 会执行任何你告诉他的脚本或命令
-
LockFileBootStrapper 比较有意思. 继续往下读
你有很多项目运行在同一个CruiseControl服务器上? 很好.
你配置了两个或更多的构建线程? 你有两个或多个项目同时构建? 太好了. 多个, 并发的项目是CruiseControl从2004年夏天开始支持的特性.
什么? 其中的两个项目有时会竞争相同的资源? 噢, 这不好. 尤其烦人如果它只是一天或一段时间只发生一次. 你可以这样做:
<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 会在构建开始之前运行, 就像bootstrapper应该做的那样. 它将在 "lockfile"
属性指定的文件中查找项目的名称. 如果 "lockfile" 指定的文件存在, 并且里面的项目名称和在配置中设置的期待不匹配的话,
它将取消整个构建企图. 这样的话, 一个项目就可以警告其它项目说我正在构建, 而你永远也不会看到两个不能坐在一起的项目在同时构建.
然而你其它的那些不与第一个项目共享相同外部依赖的项目则可以同时运行. lockfilelistener 会在构建之后清除任何旧的 "lockfile",
这样的话你就不会永久性的阻止某些构建的运行.
"等等", Bob说, "我刚刚不得不花了10分钟来等待FooLib项目得到构建! 这怎么办?" "Bob," 你并非不近人情的说:
"你知道我们只有一台集成测试服务器. 现在它用来运行CruiseControl. 下次你和CTO一起钓鱼的时候, 你可以告诉他项目的延迟,
再问问他我们能否花点明年的预算再购买一台测试服务器. "
©Julian Simpson 2008. All rights reserved.
在
bootstrapper 概念之前, CruiseControl只是检查源代码是否有修改, 不会更新源代码工作拷贝,
通常都是用户的构建脚本来负责更新源代码. 而 bootstrapper 最开始的设计意图就是作者在文中提到的那样, 只是用来更新构建脚本,
再由构建脚本负责更新源代码. 这在一些
bootstrapper 文档中可略见端倪. 只是人们马上就发现 boostrapper 可以用来更新所有源文件,
进而可以用来在构建之前做任何事情.
其实 bootstrapper 和
publisher 是一对兄弟, 分别用来在构建前后插入用户定义的操作, 就像 AOP 里的 before/after 一样.
See also:
-
<<CruiseControl
的 108 种调度模式>>
-
<<CruiseControl
Enterprise 最佳实践 (1) : Publish with a Publisher>>
-
<<CruiseControl
Enterprise 最佳实践 (2) : Keep your dependencies to yourself>>
-
<<CruiseControl
Enterprise 最佳实践 (3) : Configuring CruiseControl the CruiseControl
way>>