Angelo Lee's Blog
This is my kingdom .If i don't fight for it ,who will ?

1. 认识CruiseControl

CruiseControl CI 服务器的老者,诞生已是多年,在许多方面, CruiseControl 服务器已经成为持续集成实践的同义词。而现在, CruiseControl 已发展成为一个家族式系统,包括 CruiseControl.java CruiseControl.net CruiseControl.ruby 等适应不同语言环境的实现,其强大的插件和扩展能力也是诸多同类系统无法比你的。而在这里,我只介绍该家族的本家 CruiseControl.java ,即 CruiseControl
下图是 CruiseControl 系统的架构图:
 
CruiseControl系统架构图
图中我们可以看到, CruiseControl 系统的主体是 Build Loop 机制,它采用了 Source Code 轮询机制,对持续集成环境的状态进行定时检测,并根据 config.xml 配置信息做出相应处理。 CruiseControl 服务器则使用 HTTP RMI 机制将持续集成服务公开到 Reporting 模块,同时使用 XML 数据格式对每次集成的数据进行归档。同时 CruiseControl 还可以集成了 RSS IM E-MAIL 等信息发布机制,最大程度将信息广播到团队的每个成员。
CruiseControl 系统集成循环的流程图见下图,图中显示了 CruiseControl 使用轮询机制对版本库进行检测,并对发生变更的代码进行预定操作的流程:
CruiseControl系统运行流程图
  2. CruiseControl的安装
CruiseControl 的安装有许多方式。例如,如果使用 Windows ,会发现最简单的方式是下载二进制可执行文件,当然下载最新版本,然后运行它。要是高兴想研究,还可以下载源代码。
安装之后, CruiseControl 预先配置了一个配置文件,轮询 SVN (或其它版本控制系统)存储库并执行 ANT 构建脚本。服务环境不需要安装 Web 服务器, CruiseControl 已经内嵌了 Jetty web 服务程序。
  3. CruiseControl的配置
CruiseControl 服务器启动时,会自动检测配置信息中的任务信息,并对其进行校验和初始化,之后所有的工作,都是依据该配置信息进行的。
CruiseControl 的配置使用了其安装目录下的 config.xml 文件,我们可以通过任一文本编辑器打开该配置文件,编辑各种配置信息。配置信息的定义遵循了标准 XML 文件格式,同时遵循了 ANT 自动构建配置信息的规则,因此建立一个普通工程的配置信息并非难事。
config.xml的节点配置说明如下:

 


<?
xml version='1.0' encoding='gb2312' ?>
 2 < cruisecontrol >
 3      < project  name ="projectName" >
 4
 5          <!--  用于处理一些项目有关的事件  -->
 6          < listeners >
 7              <!--  用来访问项目当前创建的状态   file: 指定了状态文件的位置  -->
 8              < currentbuildstatuslistener  file ="logs/${project.name}/status.txt" />
 9          </ listeners >
10         
11          <!--  在 CC 进行创建之前运行,是创建前的准备工作  -->
12          < bootstrappers >
13              <!--  从源码控制系统更新本地文件: cvsbootstrappers、vssbootstrappers、svnbootstrapper  -->
14              < svnbootstrapper  localWorkingCopy ="projects/${project.name}"   />
15          </ bootstrappers >
16
17          <!--  检查各个源码控制系统中是否发生变化;quietperiod: 单位为秒 设定等待的时间  -->
18          <!--  第一次的取出工作为手动执行  -->
19          < modificationset  quietperiod ="300" >
20              < svn  localWorkingCopy ="projects/${project.name}" />
21          </ modificationset >
22
23          <!--  指定了构建的时间间隔 单位为秒 -->
24          < schedule  interval ="60" >
25              < ant  anthome ="apache-ant-1.7.0"  buildfile ="projects/${project.name}/build.xml" />
26          </ schedule >
27         
28          <!--  指定项目日志保存的地点  -->
29          < log >
30              <!--  通常是指定 CC 的合并日志的目录  -->
31              < merge  dir ="projects/${project.name}/test-reports" />
32          </ log >
33         
34          <!--  在 build loop 结束之后运行,发布 build 的结果  -->
35          < publishers >
36              <!-- onsuccess -->
37                  <!-- 用于对创建过程中产生的人工制品进行发布 -->
38                  <!-- artifactspublisher dest="artifacts/${project.name}" file="projects/${project.name}/target/${project.name}.jar"/>
39             </onsuccess -->
40
41              <!--
42                 mailhost=邮件主机
43                 returnname=发件人
44                 returnaddress=发件地址
                       defaultsuffix=默认邮件后缀
45              -->
46              < htmlemail
47                  charset ="UTF-8"
48                 mailhost ="test163.com"  
49                 defaultsuffix ="@xxx.com"
50                 username ="xxx@163.com"
51                 password ="xxx"
52                 returnname ="CruiseControl"
53                 returnaddress ="xxx@163.com"
54                 subjectprefix ="构建日志"
55                 xsldir ="webapps/cruisecontrol/xsl"
56                 css ="webapps/cruisecontrol/css/cruisecontrol.css" >
57                  < always  address ="xxx@xxx.com.cn" />
58                  < failure  address ="xxx@xxx,yyy@yyy.com.cn" />
59                 
60              </ htmlemail >
61
62          </ publishers >
63
64      </ project >
65 </ cruisecontrol >

 

配置文件有一个 cruisecontrol 根结点,其下则是代表一个项目的 project 子节点,再下层是项目的具体配置,在此示例中,存在了 listeners bootstrappers modificationset schedule log 几个节点,分别代表了监听器、本地目录、变更目录、构建任务、日志工作。
   3.1. 创建一个项目

 

<project name="CMAssess">
    <listeners>
        <currentbuildstatuslistener file="logs/${project.name}/status.txt"/>
</listeners>
...............
</project>

项目工程下的build.xml说明:

<? xml version="1.0" encoding="UTF-8"  ?>
  2
  3 <!--  Continuous Integration 工程名称 Gms 默认任务 CI  -->
  4 < project  default ="CI"  name ="xxx"  basedir ="." >
  5
  6     
  7      <!--  特性文件 在特性文件中 注意路径的正反斜杠问题  -->
  8      < property  file ="build.properties" />
  9     
 10      <!--  ================================ 工程参数 ================================  -->
 11     
 12      <!--  工程中文名称  -->
 13      < property  name ="projectName_CN"  value ="xxx" />
 14      <!--  工程目录  -->
 15      < property  name ="projectFolder"  value ="${projcet.path}/${projcet.name}" />
 16     
 17      <!--  源程序目录  -->
 18      < property  name ="sourceFolder"  value ="${projectFolder}/src/main/java" />
 19      <!--  配置文件目录  -->
 20      < property  name ="configFolder"  value ="${projectFolder}/src/main/config" />
 21      <!--  测试程序目录  -->
 22      < property  name ="testFolder"  value ="${projectFolder}/src/test/java" />
 23      <!--  lib目录  -->
 24      < property  name ="libFolder"  value ="${projectFolder}/WebRoot/WEB-INF/lib" />
 25      <!--  编译程序目录  -->
 26      < property  name ="classFolder"  value ="${projectFolder}/WebRoot/WEB-INF/classes" />
 27
 28      <!--  单元测试报告目录  -->
 29      < property  name ="reportFolder"  value ="${projectFolder}/test-reports" />
 30      <!--  单元测试报告文件名  -->
 31      < property  name ="reportFileName"  value ="junit-noframes.html" />
 32     
 33      <!--  ================================ 发布设置 ================================  -->
 34     
 35      <!--  生成war文件  -->
 36      < property  name ="warFile"  value ="${projectFolder}/${projcet.name}.war" />
 37      <!--  web.xml文件  -->
 38      < property  name ="webFile"  value ="${projectFolder}/WebRoot/WEB-INF/web.xml" />
 39      <!--  生成war文件的基础路径  -->
 40      < property  name ="warSource"  value ="${projectFolder}/target/classes" />
 41
 42      <!--  ================================ 路径设置 ================================  -->
 43     
 44      <!--  编译过程中用到的路径  -->
 45      < path  id ="compilePath" >
 46          <!--  编译程序目录  -->
 47          < pathelement  path ="${classFolder}"   />
 48          <!--  编译时lib路径  -->
 49          < path  refid ="libPath"   />
 50      </ path >
 51
 52      <!--  单元测试时用到的路径  -->
 53      < path  id ="jUnitPath" >
 54          <!--  编译程序目录  -->
 55          < pathelement  path ="${classFolder}"   />
 56          <!--  编译时lib路径  -->
 57          < path  refid ="libPath"   />
 58      </ path >
 59
 60      <!--  编译时lib路径  -->
 61      < path  id ="libPath" >
 62          <!--  lib目录  -->
 63          < fileset  dir ="${libFolder}" >
 64              < include  name ="**/*.jar"   />
 65          </ fileset >
 66      </ path >
 67     
 68      <!--  ================================ 持续集成 ================================  -->
 69     
 70      <!--  集成流程 暂时没有加入 Test  -->
 71      < target  name ="CI"  depends ="init,compile,test,makewar,deploy-catalina"  description ="持续集成" />
 72
 73      <!--  1.初始化目标目录, class ; report  -->
 74      < target  name ="init"  description ="初始化" >
 75         
 76          < echo > 正在删除编译程序目录 </ echo >
 77          < delete  dir ="${classFolder}"   />
 78          < echo > 正在创建编译程序目录 </ echo >
 79          < mkdir  dir ="${classFolder}"   />
 80         
 81          < echo > 正在删除单元测试报告目录 </ echo >
 82          < delete  dir ="${reportFolder}"   />
 83          < echo > 正在创建单元测试报告目录 </ echo >
 84          < mkdir  dir ="${reportFolder}"   />
 85      </ target >
 86
 87      <!--  2.编译程序生成目标类  -->
 88      < target  name ="compile"  depends ="init"  description ="编译" >
 89          < echo > 编译源程序 </ echo >
 90          <!--  classpathref="编译路径" destdir="${编译程序目录}"  -->
 91          < javac  classpathref ="compilePath"  fork ="true"  memorymaximumsize ="128m"
 92                destdir ="${classFolder}"  debug ="true"  deprecation ="false"
 93                failonerror ="false"  verbose ="false" >
 94              <!--  源程序目录  -->
 95              < src  path ="${sourceFolder}"   />
 96              <!--  测试程序目录  -->
 97              < src  path ="${testFolder}"   />
 98              <!--  配置文件目录  -->
 99              < src  path ="${configFolder}"   />
100             
101              < include  name ="**/*.java"   />
102          </ javac >
103          < copy  todir ="${classFolder}" >
104              < fileset  dir ="${configFolder}" >
105                  < include  name ="**/*.*" />
106              </ fileset >
107              < fileset  dir ="${sourceFolder}" >
108                  < include  name ="**/*.xml" />
109              </ fileset >
110          </ copy >
111      </ target >
112
113      <!--  3.运行JUnit测试  -->
114      < target  name ="test"  description ="执行单元测试" >
115          < echo > 运行单元测试用例 </ echo >
116          <!--  haltonfailure="true" 出现fail即终止build  -->
117          < junit  printsummary ="true" >
118              < classpath  refid ="jUnitPath"   />
119              <!--  指定测试结果类型  -->
120              < formatter  type ="xml" />
121              <!--  批量测试集 todir="${reportFolder}" 单元测试报告目录  -->
122              < batchtest  todir ="${reportFolder}" >
123                  <!--  dir="${classFolder}" 编译程序目录  -->
124                  < fileset  dir ="${classFolder}" >
125                      <!--  包含  -->
126                      < include  name ="**/*Test.class" />
127                  </ fileset >
128              </ batchtest >
129          </ junit >
130         
131          < echo > 生成单元测试报告 </ echo >
132          <!--  XSLT格式化XML,生成HTML的报告 todir="${reportFolder}" 单元测试报告目录  -->
133          < junitreport  todir ="${reportFolder}"  description ="生成单元测试报告" >
134              < fileset  dir ="${reportFolder}" >
135                  <!--  formatter生成报告文件的默认命名规范是TEST-*.xml  -->
136                  < include  name ="TEST-*.xml"   />
137              </ fileset >
138              <!--  format="frames" 生成有框架 noframes 无框架 -->
139              < report  format ="frames"  todir ="${reportFolder}"   />
140          </ junitreport >
141      </ target >
142
143      <!--  4.打包成war文件  -->
144      < target  name ="makewar"  depends =""  description ="发布WAR文件" >
145          < echo > 打包成war文件 </ echo >
146          < war  destfile ="${warFile}"  webxml ="${webFile}" >
147              <!--  添加web文件,过滤web.xml与测试类  -->
148              < fileset  dir ="${projectFolder}/WebRoot"  excludes ="WEB-INF/web.xml,WEB-INF/classes/**/*Test.class" />
149          </ war >      
150      </ target >

 

一个持续集成项目是从 project 节点开始的,可以对该项目进行的操作都会作为子节点存在与该节点之下。对于每一个 project 节点, CruiseControl 系统都会将其当作一个处理单元,并在最初建立该项目的时候进行初始化操作。
name project 节点常用的属性,也是必设且值唯一的属性,该属性的值会在整个 CruiseControl 系统使用,包括配置文件的 ${project.name} 变量值。
在一个项目节点中,我们可以定义监听器、本地目录位置、变更轮询目录位置、轮询间隔、任务执行、单元测试、日志记录等多种项目操作,同时 CruiseControl 系统支持插件扩展功能,这些扩展插件可以直接在一个项目中使用。
   3.2. 轮询版本控制系统
版本库轮询机制是使用 modificationset 进行配置的。 CruiseControl 系统内置的轮询模块会根据设定的时间段对版本库进行检测,一旦发现版本库发生变更,就会调用 Check Out à Build à Test à Publish 等一系列的操作,对最新的代码进行集成,并运行一系列预先设定的任务。
CruiseControl 支持十多种版本控制系统,比如 CVS SVN Starteam VSS 等,对这些版本控制系统的调用,只需要配置相应的节点名称即可。本文以 SVN 版本管理系统为例。
modificationset 节中,我们看到唯一的一个子节点:

 

<svn localWorkingCopy="projects/CM/${project.name}/src/${project.name}"/>

 

该节点使用了 svn 关键字,表示当前工程使用的版本控制系统是 SVN localWorkingCopy 属性的值是告诉 CruiseControl 本地的拷贝目录 ,其余的工作则由 CruiseControl 内置的操作模块进行。
   3.3. 执行构建脚本
实质上,执行构建脚本也是 CruiseControl 的一个可定制任务,但由于自动构建是持续集成的一个重要组成部分,因此在此单独提出。
CruiseControl 的自动构建是使用 ANT 工具来进行的,当然我们也可以使用其它的自动构建工具来替代(比如 Maven ),这些的工具仅仅是体现在一个任务配置语句上,在此,以 ANT 工具为例。
schedule 节,我们同样使用了一个子节点来描述自动构建工作:

 

<ant anthome="ant6" buildfile="projects/CM/${project.name}/src/${project.name}/build.xml"/>

 

该节点使用了 ant 关键字,表示该任务使用了 ant 来进行自动构建任务, anthome 属性则指定了 ANT 工具的具体位置, buildfile 属性则指定了 ANT 进行自动构建所使用的配置文件 Build.xml 文件的路径。
这样,在 CruiseControl 决定进行自动构建任务时,会根据设定找到执行所使用到的信息。
   3.4. 执行单元测试
CruiseControl 可以自动侦测自动构建配置文件中单元测试的任务,并在代码集成完毕将执行结果反馈到相应的模块。
ANT 支持 xUnit 系列的单元测试框架,只需要在配置文件中加入一个 target 节,即可在自动构建完成之后,进行单元测试工作,有关的详细配置,请参阅 ANT 官方文档。
  4. CruiseControl的使用
   4.1. 启动CruiseControl服务器
通过上一节的配置, CruiseControl 系统已经可以正常运行并管理项目了。我们可以通过执行其安装目录下的 cruisecontrol.bat 文件来启动 CruiseControl 。控制台显示“ BuildQueue    - BuildQueue started ”,则说明 CruiseControl 服务器已成功启动。
在启动服务器之前,我们可以打开 cruisecontrol.bat 文件并修改倒数第三行相应功能的端口数值来指定服务器占用的系统端口,在本例中我们将 -webport 指定到了 8088 端口。
   4.2. 管理工程
服务器启动之后,我们就可以在浏览器里访问使用了,如 http://127.0.0.1:8088/dashboard/tab/builds 访问 CruiseControl Web 服务器 当前 CruiseControl 系统就会存在 一些项目 工程,列表中显示了各个工程的相关信息,包括当前状态、上次失败时间、上次成功时间、构建次数等信息,同时 Build 按钮可以让 CruiseControl 立即检测工程状态。
点击相应工程名称,则可以进入该工程的详细页面首页, 首页清晰明了,主要包括了构建历史信息、最近构建结果、最近构建版本库信息等,该页面可以让我们对当前工程的集成信息一目了然,便于我们对不同的集成结果做出相应的处理。
页面上部的导航按钮可以让我们进入不同信息的查看界面,包括测试结果、 XML 详细日志文件、状态统计等信息。这些导航按钮根据工程设置的不同扩展任务而有所不同。

  5. 工作总结
持续集成为我们带来诸多好处,大量降低集成时间的同时,更重要的是它可以迅速对我们的工作做出反馈,保证了项目的质量。持续集成已经不像以前只存在于理论的名词,只要你愿意你都可以实施持续集成。
当然这些好处是建立在制度的遵循和详尽的单元测试的基础上的,持续集成系统仅仅是我们项目过程中的一个工具而已,正如 SCM 工具、单元测试一样。工具就是工具,而真正默默起作用的则是项目团队人员之间标准制度的完善与执行。
posted on 2011-06-14 10:47  Angelo Lee  阅读(267)  评论(0编辑  收藏  举报