UML软件工程组织

 

 

自动进行团队构建和单元测试过程
 
作者:Mark Wilkinson 出处:IBM
 
本文内容包括:
极限编程和敏捷方法建议开发过程要包含持续集成和单元测试。支持这些实践的一个实际方法是设置一个自动系统,每次在源代码发生变化时,都自动构建和测试源代码的最新版本。这篇文章将介绍为 Java™ 项目建立基于 Linux™ 的构建服务器时涉及的实际问题。

这篇文章介绍的是 CruiseControl,这是一个开放源码软件,可以用它对有多个开发人员参与的软件项目自动进行构建和单元测试。我将解释为什么自动化构建对于成功的开发团队是至关重要的,并一步步介绍运行 CruiseControl 的持续集成系统的配置、安装和维护。

为什么要自动进行构建?

目前的一般实践是使用版本控制系统,例如 CVS 或 Subversion(请参阅 参考资料)。当有多个开发人员在同一个系统上工作时,这类协调就至关重要了。另一个正在流行起来的实践是编写单元测试,并把它们作为构建过程的一部分来运行。例如,Maven 这个构建工具就把运行 JUnit 单元测试作为正常构建过程的一部分(请参阅 参考资料)。但是采用这些实践仅仅是个开始。它们构成了近几年发展起来的许多轻量和实用软件开发方法的基础。

mozilla.org 上的持续集成
从 Mozilla 项目使用的开发方法中,可以学到许多东西,这是以公开方式进行的最大的开发项目之一。Mozilla 团队过程的关键部分,就是保持树的构建,而且他们还记录下了他们采用的实践。他们的一个重要工具就是 tinderbox 系统,它持续地在许多不同的平台上构建和测试源树(请参阅 参考资料)。

当有许多开发人员在同一个项目上工作时,重要的就是要确保构版本控制系统中代码的最新版本一直被构建。。这对于拥有封闭开发团队的项目来说是个好的实践;当开发人员周期性地把自己的工作区与主干同步时,一个不进行构建的源树会继续进行开发,直到能修正它为止。对于开放源码项目,保持主干可以工作是至关重要的。潜在的新开发人员可以在任何时候检出代码,但是如果代码不能构建,新开发人员可能就被挡在了做贡献的队伍之外。

极限编程(XP)方法论主张 持续集成。开发人员应当尽可能频繁地把他们的代码集成进主干 —— 典型的是几小时一次,同时还要确保所有单元测试都能通过。其他敏捷方法论也同意这个建议。

要采用持续集成和单元测试,需要团队接受这些方法和实践,但是这通常还不够。目前的实践依赖手工步骤 —— 集成代码、运行测试、在合适的时间检入代码,这样的实践有可能造成错误。让自动系统来构建代码、运行单元测试,可能是更可靠的解决方案。

配置构建服务器

这篇文章剩下的部分将介绍使用 CruiseControl 为 Java 项目配置构建服务器所涉及的步骤,CruiseControl 是一个管理自动构建过程的软件(请参阅 参考资料)。CruiseControl 需要一台可靠的机器,拥有充足的剩余磁盘空间,但并不需要特别快。(需要的是定期构建,但是构建过程本身需要花 2 分钟还是 20 分钟并不是问题。)将要构建的服务器基于 Fedora Core 4,这是一个由 Red Hat 资助的社区开发版的 Linux 发行版(请参阅 参考资料),所以需要有一些 Unix 经验。这篇文章涉及的主要任务有:

  • 系统的初始配置,以及设置一个运行 CruiseControl 的用户帐户
  • 安装 CruiseControl 并配置第一个构建
  • 让 CruiseControl 一直运行
  • 简化 CruiseControl 配置
  • 设置可选的基于浏览器的界面,用来监视 CruiseControl 构建

初始配置

第一件事是确保在系统上安装了 Java 的基本开发所需要的全部软件。Fedora Core 4 包含基于 gcj(来自 GNU 编译器集合(gcc)项目的 Java 编译器)的 Java 工具链,但是出于兼容性的原因,最好是安装来自 IBM 或 Sun 的 JDK。最干净的方法是按照 jpackage.org 上的说明(请参阅 参考资料),构建和安装自己的 Java RPM。Fedora Core 4 自带的 xerces-j2 包构建得不正确,造成 Xalan XSLT 实现不能工作。所以还需要从 Fedora 开发仓库安装更新的 xerces-j2 包(请参阅 参考资料)。

还需要使用其他一些软件:

  • XMLStarlet,一个有用的命令行程序,用来管理 XML 文档(请参阅 参考资料)。稍后 将用它来简化 CruiseControl 配置文件的维护。
  • CVS 和 Subversion:需要安装这些工具,以便从构建的源树中下载更新。幸运的是,Fedora Core 4 中包含这两个工具。

要执行这些步骤,必须以 root 登录。首先,下面是系统上应当有的 RPM:

[root@fcvm ~]# ls
java-1.4.2-sun-1.4.2.08-1jpp.i586.rpm
java-1.4.2-sun-alsa-1.4.2.08-1jpp.i586.rpm
java-1.4.2-sun-demo-1.4.2.08-1jpp.i586.rpm
java-1.4.2-sun-devel-1.4.2.08-1jpp.i586.rpm
java-1.4.2-sun-fonts-1.4.2.08-1jpp.i586.rpm
java-1.4.2-sun-jdbc-1.4.2.08-1jpp.i586.rpm
java-1.4.2-sun-plugin-1.4.2.08-1jpp.i586.rpm
java-1.4.2-sun-src-1.4.2.08-1jpp.i586.rpm
xerces-j2-2.6.2-5jpp_2fc.i386.rpm
xerces-j2-demo-2.6.2-5jpp_2fc.i386.rpm
xerces-j2-javadoc-apis-2.6.2-5jpp_2fc.i386.rpm
xerces-j2-javadoc-dom3-2.6.2-5jpp_2fc.i386.rpm
xerces-j2-javadoc-impl-2.6.2-5jpp_2fc.i386.rpm
xerces-j2-javadoc-other-2.6.2-5jpp_2fc.i386.rpm
xerces-j2-javadoc-xni-2.6.2-5jpp_2fc.i386.rpm
xerces-j2-scripts-2.6.2-5jpp_2fc.i386.rpm
xmlstarlet-1.0.1-1.i586.rpm
[root@fcvm ~]# 

安装 Java、Xerces、XMLStarlet 和 Subversion 包:

[root@fcvm ~]# rpm -ivh java-1.4.2-sun-1.4.2.08-1jpp.i586.rpm \
java-1.4.2-sun-alsa-1.4.2.08-1jpp.i586.rpm \
java-1.4.2-sun-devel-1.4.2.08-1jpp.i586.rpm \
java-1.4.2-sun-fonts-1.4.2.08-1jpp.i586.rpm \
java-1.4.2-sun-plugin-1.4.2.08-1jpp.i586.rpm \
java-1.4.2-sun-src-1.4.2.08-1jpp.i586.rpm
Preparing...                ################################# [100%]
   1:java-1.4.2-sun         ################################# [ 17%]
   2:java-1.4.2-sun-alsa    ################################# [ 33%]
   3:java-1.4.2-sun-devel   ################################# [ 50%]
   4:java-1.4.2-sun-fonts   ################################# [ 67%]
   5:java-1.4.2-sun-plugin  ################################# [ 83%]
   6:java-1.4.2-sun-src     ################################# [100%]
[root@fcvm ~]# java -version
java version "1.4.2_08"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.4.2_08-b03)
Java HotSpot(TM) Client VM (build 1.4.2_08-b03, mixed mode)
[root@fcvm ~]# rpm -Uvh xerces-j2-2.6.2-5jpp_2fc.i386.rpm \
xerces-j2-demo-2.6.2-5jpp_2fc.i386.rpm \
xerces-j2-javadoc-apis-2.6.2-5jpp_2fc.i386.rpm \
xerces-j2-javadoc-dom3-2.6.2-5jpp_2fc.i386.rpm \
xerces-j2-javadoc-impl-2.6.2-5jpp_2fc.i386.rpm \
xerces-j2-javadoc-other-2.6.2-5jpp_2fc.i386.rpm \
xerces-j2-javadoc-xni-2.6.2-5jpp_2fc.i386.rpm \
xerces-j2-scripts-2.6.2-5jpp_2fc.i386.rpm
Preparing...                ################################# [100%]
   1:xerces-j2              ################################# [ 13%]
   2:xerces-j2-demo         ################################# [ 25%]
   3:xerces-j2-javadoc-apis ################################# [ 38%]
   4:xerces-j2-javadoc-dom3 ################################# [ 50%]
   5:xerces-j2-javadoc-impl ################################# [ 63%]
   6:xerces-j2-javadoc-other################################# [ 75%]
   7:xerces-j2-javadoc-xni  ################################# [ 88%]
   8:xerces-j2-scripts      ################################# [100%]
[root@fcvm ~]# rpm -ivh xmlstarlet-1.0.1-1.i586.rpm
Preparing...                ################################# [100%]
   1:xmlstarlet             ################################# [100%]
[root@fcvm ~]# yum install subversion
				[...]
Installed: subversion.i386 0:1.2.3-2.1
Complete!
[root@fcvm ~]# 

还需要在服务器上创建一个新的用户帐户,由它拥有运行 CruiseControl 时涉及的文件和进程:

[root@fcvm ~]# useradd cruise
[root@fcvm ~]# su - cruise
[cruise@fcvm ~]$ pwd
/home/cruise
[cruise@fcvm ~]$ 

最后,因为将要构建的某些项目要使用 Maven 构建工具,所以需要下载、安装它,并设置适当的环境变量(请参阅 参考资料)。(JAVA_HOME 应当设置为 /usr/lib/jvm/java。)我的习惯是把 Maven 和 CruiseControl 这样的外部包放在叫作 pkg 的目录中。在 Maven 的 Web 站点上有完整的安装说明,所以我在这里就不详细介绍这个步骤了:

[cruise@fcvm ~]$ mkdir pkg
[cruise@fcvm ~]$ cd pkg
[cruise@fcvm pkg]$ [install Maven]
			

安装 CruiseControl

下一个工作是下载 CruiseControl(请参阅 参考资料)并把它安装在 pkg 目录中:

[cruise@fcvm pkg]$ wget -q http://heanet.dl.sourceforge.net/\
sourceforge/cruisecontrol/cruisecontrol-2.2.1.zip
[cruise@fcvm pkg]$ unzip cruisecontrol-2.2.1.zip
Archive:  cruisecontrol-2.2.1.zip
   creating: cruisecontrol-2.2.1/
   creating: cruisecontrol-2.2.1/contrib/
[...]
  inflating: cruisecontrol-2.2.1/reporting/jsp/webcontent/xsl/testdeta
ils.xsl
  inflating: cruisecontrol-2.2.1/reporting/jsp/webcontent/xsl/unittest
s.xsl
[cruise@fcvm pkg]$ rm cruisecontrol-2.2.1.zip
[cruise@fcvm pkg]$ 

不需要构建 CruiseControl,因为发行包中包含一个预先构建好的 JAR 文件。

现在可以让第一个自动构建工作了。这里将采用 XStream 项目的源树作为初始示例(请参阅 参考资料)。稍后 您将会学习到如何添加来自本地和远程源代码仓库的更多项目。CruiseControl 从自己的启动目录中叫作 config.xml 的文件中读取要构建的项目的信息。在本文的安装中,这个目录是 /home/cruise。清单 1 显示了一个简单的 config.xml 文件的内容,可以从它开始。要创建它,只要把清单 1 中的文本拷贝到一个新文件就可以了:

清单 1. 构建 XStream 的简单的 CruiseControl config.xml 文件
 

				
<?xml version="1.0"?>
<cruisecontrol>
  <project name="xstream" buildafterfailed="false">
    <listeners>
      <currentbuildstatuslistener
           file="log/build/xstream/status.txt"/>
    </listeners>
    <modificationset>
      <filesystem folder="/home/cruise/force-build/xstream"/>
      <svn LocalWorkingCopy="src/xstream"/>
    </modificationset>
    <schedule interval="3600">
      <ant antscript="/usr/bin/ant"
           uselogger="true"
           antworkingdir="src/xstream"
           multiple="1"
           target="library"/>
      <ant antscript="/usr/bin/ant"
           uselogger="true"
           antworkingdir="src/xstream"
           multiple="5"
           target="clean library"/>
    </schedule>
    <log dir="log/build/xstream"/>
    <dateformat format="dd/MM/yyyy HH:mm:ss"/>
  </project>
</cruisecontrol>

配置文件向 CruiseControl 提供了关于要构建的每个项目的三部分主要信息:

  • 如何构建项目,在 <schedule> 元素中指定:
    • 每 3,600 秒(即每小时)构建项目一次。
    • 用 Ant 进行构建过程。
    • 每进行到第 5 次构建时,清理构建制品(类文件以及前面构建中的类似内容)的源树。
  • 如何检测什么时候 应当 构建项目,在 <modificationset> 元素中指定:
    • 用 Subversion(svn)检查源树的本地工作拷贝是否过期。(如果源代码没有变化,就不需要构建。)
    • 检测强制构建目录中叫作 xstream 的文件的时间戳。这样即使源树没有变化,也可以手动强制进行下一次安排的构建。(在这篇文章后面,我将多次谈到可能需要这种手工覆盖。)
  • 对构建的结果要做什么,在 <listeners><log> 元素中指定:
    • 把构建过程的输出放在 log/build/xstream 目录中加了时间戳的文件中。
    • 把构建的整体状态写入这个目录中的一个文件。

现在需要从 XStream 项目的 Subversion 仓库中签出 XStream 源树。为了保持一致,请把所有源树签出为 /home/cruise/src 的子目录,并把 XStream 源树放在 src/xstream 中,就像 config.xml 文件所指定的那样:

[cruise@fcvm pkg]$ cd
[cruise@fcvm ~]$ mkdir src
[cruise@fcvm ~]$ cd src
[cruise@fcvm src]$ svn co https://svn.codehaus.org/\
xstream/trunk/xstream
A    xstream/LICENSE.txt
A    xstream/continuous-integration.xml
[...]
A    xstream/build.xml
 U   xstream
Checked out revision 614.
[cruise@fcvm src]$ 

然后,设置强制构建子目录:

[cruise@fcvm src]$ cd ..
[cruise@fcvm ~]$ mkdir force-build
[cruise@fcvm ~]$ touch force-build/xstream
[cruise@fcvm ~]$ 

最后这一步是必需的,因为如果 config.xml 文件的 <filesystem> 元素中指定的文件不存在,CruiseControl 会拒绝启动。

现在构建工作可能没有正常工作,也有可能遗漏了一些依赖项。所以这个时候,应当做一些手动检查,确保能够成功构建 XStream 源树:

构建 XStream 有问题?
有可能发现 XStream 构建失败,报告 org.w3c.dom.TypeInfoNoClassDefFoundError 错误。实际上,构建几乎就成功了,但是 XStream 的 Ant 构建脚本用 <junitreport> 任务生成 JUnit 测试结果的 HTML 报告。这个工作使用 XSLT 转换,所以触发了前面我在 初始配置 一节中提到过的 xerces-j2 包中的 bug。
[cruise@fcvm ~]$ cd src/xstream
[cruise@fcvm xstream]$ ant library
Buildfile: build.xml
compile:
    [mkdir] Created dir: /home/cruise/src/xstream/build/java
     [echo] Java version used for compile: 1.4.2_08
    [javac] Compiling 150 source files to /home/cruise/src/xstream/bui
ld/java
[...]
library:
BUILD SUCCESSFUL
Total time: 1 minute 44 seconds
[cruise@fcvm xstream]$ 

而且,在添加新项目时,需要找到用来构建源和清理其中制品的目标的名称。必须把这个信息放在 config.xml 文件中。

现在应当做好准备,可以让 CruiseControl 自动执行这个构建了。只要启动 CruiseControl,然后坐下来等待就可以了:

[cruise@fcvm xstream]$ cd
[cruise@fcvm ~]$ java -jar \
pkg/cruisecontrol-2.2.1/main/dist/cruisecontrol.jar
[cc]Aug-24 20:09:31 Main          - CruiseControl Version 2.2.1
[cc]Aug-24 20:09:32 trolController- projectName = [xstream]
[cc]Aug-24 20:09:32 trolController- No previously serialized project f
ound: /home/cruise/xstream
[cc]Aug-24 20:09:32 Project       - Project xstream:  reading settings
 from config file [/home/cruise/config.xml]
[cc]Aug-24 20:09:32 BuildQueue    - BuildQueue started
[cc]Aug-24 20:09:32 Project       - Project xstream starting
[cc]Aug-24 20:09:32 Project       - Project xstream:  idle
[cc]Aug-24 20:09:32 Project       - Project xstream started
[cc]Aug-24 20:09:32 Project       - Project xstream:  next build in 1 
hours
[cc]Aug-24 20:09:32 Project       - Project xstream:  waiting for next
 time to build
[cc]Aug-24 21:09:33 Project       - Project xstream:  in build queue
[cc]Aug-24 21:09:33 BuildQueue    - now adding to the thread queue: xs
tream
[cc]Aug-24 21:09:33 Project       - Project xstream:  reading settings
 from config file [/home/cruise/config.xml]
[cc]Aug-24 21:09:33 Project       - Project xstream:  bootstrapping
[cc]Aug-24 21:09:33 Project       - Project xstream:  checking for mod
ifications
[cc]Aug-24 21:09:59 Project       - Project xstream:  No modifications
 found, build not necessary.
[cc]Aug-24 21:09:59 Project       - Project xstream:  Building anyway,
 since build was explicitly forced.
[cc]Aug-24 21:09:59 Project       - Project xstream:  now building
Buildfile: build.xml
[cc]Aug-24 21:11:29 Project       - Project xstream:  merging accumula
ted log files
[cc]Aug-24 21:11:30 Project       - Project xstream:  build successful
[cc]Aug-24 21:11:30 Project       - Project xstream:  publishing build
 results
[cc]Aug-24 21:11:30 Project       - Project xstream:  idle
[cc]Aug-24 21:11:30 Project       - Project xstream:  next build in 1 
hours
[cc]Aug-24 21:11:30 Project       - Project xstream:  waiting for next
 time to build
[stop CruiseControl using Ctrl-C]
[cruise@fcvm ~]$ 

让 CruiseControl 一直运行

现在是让 CruiseControl 运行了,但是还不能让它在没人参与的情况下运行。它目前在一个终端窗口中运行,所以需要让 cruise 用户永远登录,才能保持它一直运行。终端还是控制程序的唯一方式:可以按下 Ctrl+C 停止 CruiseControl,并再次运行程序重新启动它。除非使用虚拟网络计算(VNC)会话或类似的东西,否则就不能远程地做上面这些事。如果 CruiseControl(或者 JVM)崩溃,就需要手动地重启它。而且当重新启动机器时,也需要手动地建立新会话、创建终端、再次启动程序,CruiseControl 才能重启。所以需要让 CruiseControl 作为一个服务运行,或者用 Unix 的术语来说,作为一个 守护程序 运行。

让程序在 Linux 下持续运行的方法有许多种。最常用的方法可能是把合适的脚本挂上 init 系统初始化进程,在系统启动时启动程序。这些脚本可以启动和停止程序,但是不能在程序出现故障时自动重启程序。

我用的方法 I 是下载并安装 Daniel J. Bernstein 的 daemontools(请参阅 参考资料)。这是一个小的程序包,负责启动一组服务并保持这些服务一直运行。要执行自己的 daemontools 安装,需要登录为 root

[root@fcvm ~]# mkdir -p /package
[root@fcvm ~]# chmod 1755 /package
[root@fcvm ~]# cd /package
[root@fcvm package]# wget -q http://cr.yp.to/\
daemontools/daemontools-0.76.tar.gz
[root@fcvm package]# gunzip daemontools-0.76.tar.gz
[root@fcvm package]# tar -xpf daemontools-0.76.tar
[root@fcvm package]# rm daemontools-0.76.tar
rm: remove regular file 'daemontools-0.76.tar'? y
[root@fcvm package]# 

要让这个包能够在 Fedora Core 4 上干净地构建,必须对包的 C 源代码稍做调整。请用文本编辑器,把 src/error.h 的第 6 行从 extern int errno; 改成 #include <errno.h>。下面是使用 ed 的处理方法:

[root@fcvm package]# cd admin/daemontools-0.76
[root@fcvm daemontools-0.76]# ed src/error.h
595
6
extern int errno;
c
				#include <errno.h>
				.
				wq
596
[root@fcvm daemontools-0.76]# 

现在可以完成安装了:

[root@fcvm daemontools-0.76]# package/install
Linking ./src/* into ./compile...
Compiling everything in ./compile...
[...]
Creating /service...
Adding svscanboot to inittab...
init should start svscan now.
[root@fcvm daemontools-0.76]# ps -ef | grep svs
root     21160     1  0 16:09 ?        00:00:00 /bin/sh /command/svsca
nboot
root     21162 21160  0 16:09 ?        00:00:00 svscan /service
root     21173 20051  0 16:10 pts/1    00:00:00 grep svs
[root@fcvm daemontools-0.76]# 

daemontools 提供了叫作 svscan 的守护进程,它负责管理服务集合。每个服务都由 /service 目录中的一个目录代表,所以需要在这里为 CruiseControl 服务创建一个目录。对于 /service 中的每个子目录,svscan 都启动一个子进程,运行 supervise 程序。

supervise 是负责管理 CruiseControl 这样的独立服务的程序。它创建子进程,运行服务子目录中的 run(例如 /service/cruisecontrol/run),从而启动服务。如果子进程中止,supervise 会重新启动它。supervise 也可以向子进程发送信号,停止或重新启动子进程。

daemontools 还提供了两个机制,负责处理它管理的服务的日志记录。首先,叫作 readproctitle 的程序捕捉写入标准错误流(在 Java 世界中,是 System.err)的输出并把输出拷贝到一个小缓冲区中,这个小缓冲区是 ps 命令显示的进程标题的一部分:

[root@fcvm daemontools-0.76]# ps -ef | grep proctitle
root     25040 25037  0 20:58 ?        00:00:00 readproctitle service 
errors: ..............................................................
......................................................................
......................................................................
......................................................................
......................................................................
..........................................................
root     25047 24006  0 20:59 pts/1    00:00:00 grep proctitle
[root@fcvm daemontools-0.76]# 

在启动时,缓冲区被初始化为包含点号,但是在出现错误时就被错误信息替代。这个机制对于少量信息(例如关键错误信息)来说很好。但是缓冲区尺寸小造成它不适合更大数量的日志信息,而且记录的信息不能保存到磁盘也使得难以分析一段时间内的性能。daemontools 提供了第二种机制 —— multilog 程序,它负责这种大量日志。第二种机制在命令行参数输出的指令控制下,把自己标准输入中的行写入日志文件。它包含对日志轮转的控制,日志轮转可以保持定量的日志信息,以使存储空间不会耗尽。例如,multilog /home/cruise/log 这个简单的命令就可以把信息记录到 /home/cruise/log 目录中的文件,当日志文件的尺寸达到 99,999 个字节时就轮转日志文件,并保持 10 个旧的日志文件。

multilog 也由 supervise 管理,就像其他服务一样。在 svsccan 发现的每个目录中,它都会查找叫作 log 的子目录,并创建一个 supervise 进程来管理这个目录下 run 脚本的执行。它还安排一个管道,把主服务的标准输出作为日志进程的标准输入。

那么,要让 daemontools 管理 CruiseControl,需要做什么呢?必须为这个服务和它的 multilog 伙伴创建目录结构。还必须创建它们各自的 run 脚本,并为日志文件创建目录。开始时,把服务目录命名为 .cruisecontrol。前导点号会让 svscan 忽略这个目录,从而可以在第一次启动服务之前进行设置:

[cruise@fcvm ~]$ mkdir -p log/cruisecontrol
[cruise@fcvm ~]$ su -
Password: [enter root password]
[root@fcvm ~]# cd /service
[root@fcvm service]# mkdir .cruisecontrol
[root@fcvm service]# cd .cruisecontrol
[root@fcvm .cruisecontrol]# mkdir log
[root@fcvm .cruisecontrol]# 

然后,创建叫作 env 的目录。要用这个目录的内容设置 CruiseControl 的环境变量以及它要启动的其他进程。在这里要确保 JAVA_HOME 有合适的值。在这里还要设置将要使用的构建工具需要的环境变量,例如 MAVEN_HOME

[root@fcvm .cruisecontrol]# mkdir env
[root@fcvm .cruisecontrol]# cd env
[root@fcvm env]# echo /usr/lib/jvm/java >JAVA_HOME
[root@fcvm env]# echo /home/cruise/pkg/maven-1.0.2 >MAVEN_HOME
[root@fcvm env]# ls
JAVA_HOME  MAVEN_HOME
[root@fcvm env]# cd ..
[root@fcvm .cruisecontrol]# 

清单 2 显示了 /service/cruisecontrol/run 脚本

清单 2. /service/cruisecontrol/run 的内容
 

				#!/bin/sh 
svc=`pwd`
cd /home/cruise
exec 2>&1
exec setuidgid cruise \
        envdir ${svc}/env \
        java -jar pkg/cruisecontrol-2.2.1/main/dist/cruisecontrol.jar

这个脚本相当简单。它执行以下这些步骤:

  1. 保存服务目录的名称(在这个示例中是 /service/cruisecontrol)留待后用。
  2. 把当前目录变为 /home/cruise。
  3. 让标准错误流写入到 multilog 进程的管道,这个管道已经连接到了标准输出流。
  4. 启动 JVM,运行 CruiseControl,以 cruise 这个用户身份运行进程,并根据 /service/cruisecontrol/env 目录中创建的文件设置环境。

清单 3 演示了 /service/cruisecontrol/log/run 脚本,它更简单。它以 cruise 用户的身份运行 multilog

清单 3. /service/cruisecontrol/log/run 的内容
 

				#!/bin/sh
exec setuidgid cruise multilog /home/cruise/log/cruisecontrol

请注意,必须使用 chmod 把两个脚本变成可执行的。而且,这两个脚本都要小心地使用 exec 外壳命令,这个命令用一个程序替代另一个程序,但是没有创建新进程。这一点很重要,因为 supervise 只能管理自己的直接子进程。如果没有使用 exec,那么 JVM 会作为执行 run 脚本的外壳的一个子进程启动。如果向 supervise 发送了杀死其子进程的信息,那么外壳会接收到信号并退出,但是 JVM 会继续运行,从而变成孤儿。supervise 并不会知道这一点,所以可能会接着启动守护程序的第二个拷贝 —— 这并不是想要的结果。

在设置好服务目录之后,可以把它改名,删除前导点号。然后 svscan 就会自动启动 CruiseControl,它的输出也会出现在日志文件中:

[root@fcvm .cruisecontrol]# cd ..
[root@fcvm service]# mv .cruisecontrol cruisecontrol
[root@fcvm service]# cat /home/cruise/log/cruisecontrol/current
[cc]Aug-24 21:45:45 Main          - CruiseControl Version 2.2.1
[cc]Aug-24 21:45:46 trolController- projectName = [xstream]
[cc]Aug-24 21:45:46 Project       - Project xstream:  reading settings
 from config file [/home/cruise/config.xml]
[cc]Aug-24 21:45:47 BuildQueue    - BuildQueue started
[cc]Aug-24 21:45:47 Project       - Project xstream starting
[cc]Aug-24 21:45:47 Project       - Project xstream:  idle
[cc]Aug-24 21:45:47 Project       - Project xstream started
[cc]Aug-24 21:45:47 Project       - Project xstream:  next build in 1 
hours
[cc]Aug-24 21:45:47 Project       - Project xstream:  waiting for next
 time to build
[root@fcvm service]# 

简化 CruiseControl 的配置

现在已经让 CruiseControl 自动运行在一个很好的受控环境中。下面要做的就是向配置中添加自己的项目。正如所料,config.xml 文件中的项目看起来非常相似,不同之处只是要用哪个工具来构建项目的细节。可以在文本编辑器中用拷贝粘贴的方式手工维护 config.xml 文件,但是一种更少出错的技术是使用 XSLT 样式表从更简单的 XML 文档生成 config.xml 文件。实现这个方案的文件集合已经合成了一个可以下载的压缩 tar 文件(请参阅 下载)。请解压缩这些文件到 /home/cruise 目录:

[cruise@fcvm ~]$ ls
config.xml         force-build  pkg  xstream.ser
cruisecontrol.log  log          src
[cruise@fcvm ~]$ tar xvzf [...]/simple-cc.tar.gz
meta-config-params.xsl
meta-config.xsl
meta-config.xml
mkconfig
[cruise@fcvm ~]$ ls
config.xml         meta-config-params.xsl  pkg
cruisecontrol.log  meta-config.xml         src
force-build        meta-config.xsl         xstream.ser
log                mkconfig
[cruise@fcvm ~]$ 

简化的配置文件名为 meta-config.xml。这个文件要用 meta-config.xsl 样式表进行变换,生成 CruiseControl 的 config.xml 文件。叫作 mkconfig 的简单脚本用早先安装的 XMLStarlet 工具执行转换操作。请运行 mkconfig 重新生成 CruiseControl 的 config.xml 文件。

构建监视

前面开始的简单 CruiseControl 配置对于让第一个构建工作来说足够了,但是对于监视构建过程来说还不够。最常见的需求是把集成构建的结果通过电子邮件发送给相关开发人员。meta-config.xsl 样式表生成发送电子邮件消息的配置,但是要做到这一点,它需要一些本地环境变量的信息。它必须知道的许多变量要从 meta-config-params.xsl 文件读取;在开始之前应该根据实际情况修改这个文件。这个文件中的设置如下所示:

  • home :构建进程的主目录。如果采用的是这篇文章中描述的目录布局,那么默认设置就可以了。
  • cruisecontrol-home :CruiseControl 发行包解压后所在的目录。同样,默认设置应当就可以了。
  • ant-home :Ant 的安装目录。要使用 Fedora Core 4 自带的 Ant,应当把这个值设为 /usr。
  • maven-home :Maven 的安装目录(如果需要用它的话)。默认值假设把 Maven 解压缩到 /home/cruise/pkg 目录。
  • return-address :CruiseControl 构建电子邮件的返回电子邮件地址。
  • return-name :构建电子邮件返回地址的名称。
  • developers-address :除了上次成功构建之后进行过提交的开发人员之外,应当总是得到构建电子邮件拷贝的一个电子邮件地址。

对于构建成功或失败时应当给谁发送电子邮件,CruiseControl 在这方面相当灵活。在这里使用的配置将向最后一次构建之后向版本控制系统中提交变更的每个开发人员发送电子邮件。持续集成构建可能还包含在其他地方开发的开放源码项目(我将把它们称作 远程项目),这时,当远程项目的开发人员弄糟了什么事的时候,可能并不想让构建系统向他们发送电子邮件。在这种情况下,可以向一个地址发送邮件,这个地址通常是一个邮件列表,如果团队成员经常想知道构建的状态,可以订阅这个邮件列表。这可以让团队负责人尽早发现构建发生了损坏。

清单 4 显示了 meta-config.xml 文件的语法:

清单 4. meta-config.xml 的语法
 

				<projects>
  <project name="project-name" [interval="seconds"]>
    <svn/>|<cvs/>
    <ant/>|<maven/>
    <clean>goals or targets to clean source tree</clean>
    <build>goals or targets to build</build>
    [<srcdir>source directory</srcdir>]
    [<remote-project/>]
    [<repo-dependency>groupId</repo-dependency>*]
    [<srcdir-dependency>project-name</srcdir-dependency>*]
    [<modificationset>CruiseControl elements</modificationset>]
  </project>*
</projects>

配置文件基本上是个 <project> 元素列表。每个项目都有一个 name 属性。可选的 interval 属性覆盖了 CruiseControl 默认的 5 分钟的构建间隔时间。可以提高远程项目的构建间隔,以减轻它们的版本控制仓库的负担。

利用 <svn/><cvs/> 空元素,每个项目必须指定自己用来更新源树的版本控制工具。项目还必须指定要使用的构建工具,或者是 <ant/> 或者是 <maven/>。项目还必须包含两个元素,说明要使用哪个目标(或哪个 Maven 的目标)清理和构建源树。对于 Maven,典型的值可能是 <clean>clean</clean><build>jar:install-snapshot</build>。对于 Ant,可能需要检查 build.xml 文件来找到目标的名称。

假设项目的源在 /home/cruise/src 目录下,根据项目命名,那么 name 属性为 my-project 的项目的源应当在 /home/cruise/src/my-project 中。有些项目拥有大型源树,拥有可以单独构建的子目录;为了处理这种情况,<project> 元素可以包含可选的 <srcdir> 元素,由它指定 /home/cruise/src 目录的特定子目录。例如:

<project name="my-utils">
  <srcdir>big-project/my-utils</srcdir>
  ...

CruiseControl 默认的行为是向上次构建之后签入变更的每个人发送电子邮件。如果是从远程版本控制仓库中拉出源代码,请添加 <remote-project/> 元素,这可以使电子邮件发送到 meta-config.xsl 文件中指定的 developers-address 地址。

项目间的依赖关系

CruiseControl 并不知道项目之间的依赖关系。可能有一个项目生成的 JAR 文件包含的工具类集合是其他许多项目依赖的,但是除非向 CruiseControl 解释这个关系,否则它对这个关系将毫不知情。可以对工具类的项目进行修改,从而造成它被重新构建,但是依赖它的项目不会针对工具类的新版本重新被构建和测试。这可能会降低集成测试的价值,所以出现了对这一问题的一些解决方案。

CruiseControl 为这个目的提供的主要工具是 <filesystem> 元素。可以把这个元素包含在某个项目的 <modificationset> 小节中,这样只要文件系统的某些区域发生了修改,项目就会被重新构建。清单 1 中初始的 config.xml 文件就采用这种方式,在强制构建目录中的文件发生修改时,触发重新构建。所有的项目都会在文件系统的某个位置创建或更新制品。(例如,作为工具类项目的构建结果,会更新自己产生的 JAR 文件。)可以在 <filesystem> 元素中用这些位置,触发依赖这些制品的项目进行构建。

Ant 在项目构建的方式方面有很高的灵活性,所以不可能确定地指出项目重新构建时会更新文件系统的哪个区域。在这里可以采用的唯一方法就是检查每个文件的 build.xml 文件,找出它把构建的制品放在哪儿。然后才能把合适的 <filesystem> 元素添加到依赖这些制品的项目中。简化的 meta-config.xml 文件支持一个 <modificationset> 元素,里面可以包含任何 CruiseControl 元素。它们会被拷贝到 config.xml 文件中。例如,依赖 XStream 的项目可能包含以下内容:

<project name="my-project">
  [...]
  <modificationset>
    <filesystem
          folder="/home/cruise/src/xstream/xstream-SNAPSHOT.jar"/>
  </modificationset>
</project>

Maven 在每个项目上放了一个公共构建过程,所以在 Maven 项目间指定依赖性时,可以提供一些公共规则。项目可以对 Maven 仓库中由指定群组创建的制品指定依赖。更准确地说,包含 <repo-dependency>classworlds</repo-dependency> 可以让项目在 /home/cruise/.maven/repository/classworlds 下的文件发生变化时,重新进行构建。假设 classworlds 构建是在本地 Maven 仓库中安装了制作好的 JAR 文件,那么任何包含这个元素的项目都会自动重新构建。

项目也可以指定对其他项目的构建输出的依赖。包含 <srcdir-dependency>classworlds</srcdir-dependency> 可以让项目在 ${srcdir}/target 下的文件发生变化时重新进行构建,其中 ${srcdir} 是命名项目的源目录。

向构建添加项目

下面是向持续集成构建添加新项目的步骤:

  1. 作为 cruise 用户,把源代码签出到 /home/cruise/src 目录。
  2. 检查是否可以手工构建源树。
  3. 向 meta-config.xml 添加适当的条目。
  4. 运行 ./mkconfig
  5. 重启 CruiseControl,以便它能从 config.xml 读取新的项目条目。可以用 ps 命令找到运行 CruiseControl 的 JVM 的进程 ID,然后用 kill 命令杀死进程。也可以用 root 用户身份运行 svc -t /service/cruisecontrol,让 daemontools 杀死进程。不管采用哪种方法,supervise 都可以保证 CruiseControl 会被重启。
  6. 可选地,更新 /home/cruise/force-build/${project-name} 的时间戳也可以让 CruiseControl 触发自动重新构建。

CruiseControl Web 应用程序

目前为止运行的 CruiseControl 安装把每个构建的结果用电子邮件消息发送给开发人员。但是开发过程可能包含不是这些消息收件人的人员 —— 例如,项目管理人员或测试人员。CruiseControl 包含一个简单的 Web 应用程序,可以让这些人员监视持续集成构建。

CruiseControl Web 应用程序在 Apache Tomcat 应用程序服务器中运行,使用的是包含在 Fedora Core 4 发行版中的拷贝。需要安装 tomcat5 tomcat5-admin-webapps 包:

[root@fcvm ~]# yum install tomcat5 tomcat5-admin-webapps
				[...]
Installed: tomcat5.i386 0:5.0.30-5jpp_6fc tomcat5-admin-webapps.i386 0:5.0.30-5jpp_6fc
Dependency Installed: tomcat5-jasper.i386 0:5.0.30-5jpp_6fc
Complete!
[root@fcvm ~]# 

还需要安装 Java 事务 API(JTA)的一个实现。可以用 JPackage 中(请参阅 参考资料)的 RPM 规范文件构建自己的 JTA RPM,但是最简单的选择就是安装来自 Fedora 仓库的 geronimo-specsgeronimo-specs-compat

[root@fcvm ~]# rpm -Uvh http://download.fedora.redhat.com/\
pub/fedora/linux/core/development/i386/Fedora/RPMS/\
geronimo-specs-1.0-0.M2.2jpp_4fc.i386.rpm
Preparing...                ################################### [100%]
   1:geronimo-specs         ################################### [100%]
[root@fcvm ~]# rpm -Uvh http://download.fedora.redhat.com/\
pub/fedora/linux/core/development/i386/Fedora/RPMS/\
geronimo-specs-compat-1.0-0.M2.2jpp_4fc.i386.rpm
Preparing...                ################################### [100%]
   1:geronimo-specs-compat  ################################### [100%]
[root@fcvm ~]# 

使用默认的 Tomcat 安装,CruiseControl Web 应用程序会找不到合适的 JAXP TransformerFactory 实现,所以需要向选定的类目录添加默认 JAXP XML 转换器:

[root@fcvm ~]# cd /usr/share/tomcat5/common/endorsed
[root@fcvm endorsed]# ln -s /usr/share/java/jaxp_transform_impl.jar \
\[jaxp_transform_impl\].jar
[root@fcvm endorsed]# ls -l
total 12
lrwxrwxrwx  1 root root 36 Sep 19 01:33 [jaxp_parser_impl].jar -> /usr
/share/java/jaxp_parser_impl.jar
lrwxrwxrwx  1 root root 39 Sep 19 01:47 [jaxp_transform_impl].jar -> /
usr/share/java/jaxp_transform_impl.jar
lrwxrwxrwx  1 root root 36 Sep 19 01:33 [xml-commons-apis].jar -> /usr
/share/java/xml-commons-apis.jar
[root@fcvm endorsed]# 

CruiseControl Web 应用程序可以绘制重要的构建统计图,例如成功构建与失败构建的比例。画图的库要使用 Java AWT,所以需要确保 JVM 运行在 headless 模式。要做到这一点,请编辑 /etc/tomcat5/tomcat5.conf 文件,并插入下面这一行:JAVA_OPTS="-Djava.awt.headless=true",位置大约在第 10 行。

现在,在 /etc/tomcat5/Catalina/localhost 下创建一个叫作 cruisecontrol.xml 的文件,把 CruiseControl Web 应用程序添加到 Tomcat 的配置。清单 5 显示了这个文件的内容:


清单 5. /etc/tomcat5/Catalina/localhost/cruisecontrol.xml 的内容
 

				
<Context path="/cruisecontrol"
         docBase="/home/cruise/pkg/cruisecontrol-2.2.1/reporting/jsp/d
ist/cruisecontrol.war">
  <Parameter name="logDir"
             value="/home/cruise/log/build"
             override="false"/>
  <Parameter name="cacheRoot"
             value="/var/cache/tomcat5/cruisecontrol"
             override="false"/>
</Context>

请注意,清单 5 中的第二行出于显示的原因进行了回绕。docBase 属性在创建的文件中应该单独占一行。

还需要为 CruiseControl Web 应用程序创建一个保存页面缓存的目录:

[root@fcvm ~]# cd /var/cache/tomcat5
[root@fcvm tomcat5]# mkdir cruisecontrol
[root@fcvm tomcat5]# chgrp tomcat cruisecontrol
[root@fcvm tomcat5]# chmod g+w cruisecontrol
[root@fcvm tomcat5]# ls -l
total 24
drwxrwxr-x  2 root tomcat 4096 Sep 16 09:32 cruisecontrol
drwxrwxr-x  2 root tomcat 4096 May 10 11:57 temp
drwxrwxr-x  3 root tomcat 4096 Sep 15 22:53 work
[root@fcvm tomcat5]# 

现在可以启动 Tomcat,并把它设置成在系统启动时重启。启动脚本目前会生成一些警告信息,但是可以忽略它们:

[root@fcvm ~]# service tomcat5 start
Starting tomcat5: find: warning: you have specified the -mindepth opti
on after a non-option argument -type, but options are not positional (
-mindepth affects tests specified before it as well as those specified
 after it).  Please specify options before other arguments.
find: warning: you have specified the -maxdepth option after a non-opt
ion argument -type, but options are not positional (-maxdepth affects 
tests specified before it as well as those specified after it).  Pleas
e specify options before other arguments.
Using CATALINA_BASE:   /usr/share/tomcat5
Using CATALINA_HOME:   /usr/share/tomcat5
Using CATALINA_TMPDIR: /usr/share/tomcat5/temp
Using JAVA_HOME:       /usr/lib/jvm/java
                                                           [  OK  ]
[root@fcvm ~]# chkconfig tomcat5 on
[root@fcvm ~]# chkconfig --list tomcat5
tomcat5         0:off   1:off   2:on    3:on    4:on    5:on    6:off
[root@fcvm ~]# 

现在应当能够用浏览器在 http://localhost:8080/cruisecontrol/ 上访问 CruiseControl Web 应用程序了。图 1 显示了将会看到的输出的示例:

图 1. CruiseControl Web 应用程序
CruiseControl Web 应用程序截屏

安全问题

在我总结之前,我要指出两个配置和运行自己的持续集成服务器所涉及的安全性问题。首先,我没有解决构建服务器的访问安全问题。您应当参考其他信息来源以确保您的系统安全,或者在提供适当保护的内部网络上运行它。

其次,对于要在持续集成服务器上构建的外部项目的开发人员,您要考虑对他们要信任到什么程度。项目的构建过程和单元测试可以访问服务器的资源,包括服务器连接的网络。自动构建过程意味着提交给远程版本控制仓库的变更会在没有人为干预的情况下,自动下载到构建服务器并在上面执行。这就把构建服务器置于提交给源树的 bug 和恶意代码的风险之下。您可能想限制在构建服务器上构建的外部项目,或者做些工作来保护系统和网络不受正在构建的项目的影响。

结束语

这篇文章介绍了运行 CruiseControl 的持续集成服务器的设置步骤。您安装了 CruiseControl 并学习了保持服务器一直运行需要做的工作,还了解了持续集成服务器的日常管理工作。而且还把配置的重要元素提取到更简单的 XML 文档中,包括版本控制和构建工具的选择以及用来构建每个项目的目标。

现在您学会了如何指定项目之间的依赖性。对于 Maven 项目来说指定比较容易,因为它们拥有一致的构建过程,生成的制品也有共享的仓库。Ant 则把这些机制留给每个项目,但是如果许多 Ant 项目都有公共的构建过程,那么可以用生成的 <filesystem> 元素对配置进行扩展,模拟这些项目之间的依赖性。CruiseControl 还有其他许多控制,可以用来增强持续集成过程。可以通过我介绍的 XSLT 样式表轻松地利用它们。

我快速介绍了运行 CruiseControl Web 应用程序需要的步骤,但是您可以提高自己安装的安全性和可靠性。更安全的配置可以使用 Apache 的 httpd 处理请求并把它们交给 Tomcat。让 daemontools 管理 Tomcat JVM(就像配置它来管理 CruiseControl 本身那样)可能更可靠。除此之外,还应当考虑构建服务器和构建服务器所在网络的安全需求,并尝试 Linux 提供的一些安全工具。

这篇文章的目的是通过采取持续集成方式,让开发过程更敏捷并提高软件的质量。创建构建服务器是具体而实用的一步,采用敏捷开发方法的更多实践还会得到进一步提高。我鼓励您阅读关于这些方法的更多内容(请参阅 参考资料)并用它们的想法来提高和调整开发过程。

下载

描述 名字 大小 下载方法
Sample tools j-simple-cc.tar.gz 2 KB  FTP

参考资料

学习 获得产品和技术
  • CruiseControl:在 CruiseControl 项目的 Web 站点找到它的下载和文档。
  • CVSSubversion:把源代码置于版本控制之下。
  • JUnit:JUnit 是 Java 编程流行的单元测试框架。
  • Maven:Apache Maven 项目提供的构建系统集成了许多最佳实践,包括 JUnit。
  • Fedora Core 4:这个 Linux 发行版是这篇文章描述的构建服务器的基础。
  • JPackage:JPackage 项目源自对 Java 项目进行 RPM 打包,Fedora 项目采用了这一技术。项目不能把 Sun JDK 作为 RPM 重新进行发布,但是您可能找到自行构建 RPM 的指南和 RPM 规范文件。
  • Fedora Core development repository:在这里可以获得更新的 Xerces 包。只需对 xerces-j2-2.6.2-4jpp_8fc 包做最小的更新;xerces-j2-2.6.2-5jpp_2fc 是从仓库中可以得到的最新版本。
  • XMLStarlet:这个命令行程序支持在 XML 文档上执行有用的操作。
  • daemontools:D. J. Bernstein 的 daemontools 可以让 CruiseControl 一直运行。
讨论
 

组织简介 | 联系我们 |   Copyright 2002 ®  UML软件工程组织 京ICP备10020922号

京公海网安备110108001071号