自動進行團隊構建和單元測試過程

自動進行團隊構建和單元測試過程

類別:Linux 編程技術 發佈時間:2008年12月01日 出處:Mark Wilkinson 收藏此篇文章

極限編程和敏捷方法建議開發過程要包含持續集成和單元測試。支持這些實踐的一個實際方法是設置一個自動系統,每次在源代碼發生變化時,都自動構建和測試源 代碼的最新版本。這篇文章將介紹爲 Java™ 項目建立基於 Linux™ 的構建服務器時涉及的實際問題。

這篇文章介紹的是 CruiseControl,這是一個開放源碼軟件,可以用它對有多個開發人員參與的軟件項目自動進行構建和單元測試。我將解釋爲什麼自動化構建對於成功 的開發團隊是至關重要的,並一步步介紹運行 CruiseControl 的持續集成系統的配置、安裝和維護。

爲什麼要自動進行構建?

目前的一般實踐是使用版本控制系統,例如 CVS 或 Subversion。 當有多個開發人員在同一個系統上工作時,這類協調就至關重要了。另一個正在流行起來的實踐是編寫單元測試,並把它們作爲構建過程的一部分來運行。例 如,Maven 這個構建工具就把運行 JUnit 單元測試作爲正常構建過程的一部分。 但是採用這些實踐僅僅是個開始。它們構成了近幾年發展起來的許多輕量和實用軟件開發方法的基礎。

當有許多開發人員在同一個項目上工作時,重要的就是要確保構版本控制系統中代碼的最新版本一直被構建。。這對於擁有封閉開發團隊的項目來說是個好的實踐; 當開發人員週期性地把自己的工作區與主幹同步時,一個不進行構建的源樹會繼續進行開發,直到能修正它爲止。對於開放源碼項目,保持主幹可以工作是至關重要 的。潛在的新開發人員可以在任何時候檢出代碼,但是如果代碼不能構建,新開發人員可能就被擋在了做貢獻的隊伍之外。

極限編程(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 源樹:

[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 發行版中的拷貝。需要安裝 tomcat5tomcat5-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 提供的一些安全工具。

這篇文章的目的是通過採取持續集成方式,讓開發過程更敏捷並提高軟件的質量。創建構建服務器是具體而實用的一步,採用敏捷開發方法的更多實踐還會得到進一 步提高。我鼓勵您閱讀關於這些方法的更多內容並用它們的想法來提高和調整開發過程。

下載本文代碼

回到文章開頭擴展閱讀

 http://www.oschina.net/docs/article/436

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章