持 续 集 成
编写:jerry 创建日期:2005-04-21 最后更新:2005-04-22 版次:1.0
本文针对Java项目的持续集成而编写,描述了Java项目持续集成中采用的工具、工具的简介以及简单的一个持续集成的实例。
1. 概述
持续集成作为软件开发过程中的重要项,为软件质量的保证提供了基础,并起到了保证软件构建的频率以及及早发现错误的可能性,减少了排错的成本、减少或者说免除了集成人员在系统集成、部署上的工作量,使得之前噩梦般的集成工作能够自动的得以进行,不仅仅如此,持续集成还使得软件进度可以一目了然的为众人所知。
项目的持续集成主要需要实现的是项目的自动编译、代码检查、自动测试、自动集成与部署、集成状态的通知、本次集成的改变项(即俗称的changelog)甚至是项目网站的生成。持续集成包括了日构建和感知版本工具(例如CVS)的变化进行实时集成两种,对于Java项目为实现持续集成通常采用的工具是CruiseControl+Maven(或Ant)。
2. 持续集成工具
2.1. CruiseControl
简称CC,持续集成工具,主要提供了基于版本管理工具(如CVS)感知变化或每天定时的持续集成,并提供持续集成报告、Email、Jabber等等方式通知相关负责人,同时也可通过网站查看持续集成的状况,其要求是需要进行持续集成的项目已编写好全自动的项目编译脚本(可基于Maven或Ant)。
CruiseControl为通过编写脚本的方法实现,其脚本通常由一个xml文件组成。
可去官方网站查看更为详细的关于CruiseControl的信息,官方网站为:CruiseControl.sf.net。
2.2. Maven
项目编译、集成、部署脚本工具,可称为ant的升级版,较之ant提供了更为方便的对于多项目(项目依赖性的编译)的支持,基于插件机制,提供了众多的插件(诸如代码检查工具、War生成工具、Jar生成工具、项目网站生成工具等等)以帮助脚本编写人员完成项目集成脚本的编写,并提供了Eclipse Plugin使得脚本的编写更加的简便。
通常采用编写Maven脚本的方法来实现项目的自动编译、集成和部署,Maven脚本通常包含四个文件:project.xml、build.properties、project.properties以及maven.xml。project.xml中描述当前项目的一些元数据信息,如项目名、项目的开发者、项目网站、项目的源码目录、项目中所引用的包等等;build.properties中通常描述当前项目在进行自动编译时用到的一些Maven性质的属性,如maven包仓库的路径,远程Maven包仓库的url等;project.properties通常用于描述项目编译过程时的一些项目属性信息;maven.xml作为项目编译的脚本,包含了项目在进行自动集成时所做的步骤的描述,如编译、运行单元测试、打包等,在脚本中调用的通常为Maven已提供的插件,当然,也可根据需要编写自己的插件以支持特殊的集成的要求。
可去官方网站查看更为详细的关于Maven的信息,官方网站为:maven.apache.org。
3. 实现
l 搭建CruiseControl、Maven环境。
l 制定项目中各工程的源码结构规范。(方便项目集成脚本的编写)
l 根据项目需求编写项目集成脚本。(Maven脚本)
l 根据持续集成需求编写持续集成脚本。(CruiseControl脚本)
l 部署脚本,启动CruiseControl,进行持续集成。
4. 示例
我们以一个Web应用系统为例说明整个持续集成的实现过程。
4.1. 背景
此Web应用系统的分为三个工程,分别为Container、Util、DemoPortlet共同组成,各工程的结构为src/java,src/test,src/java中放置了工程的源码、配置文件、图片等等,src/test中放置了工程的单元测试代码、功能测试代码,所引用的包均放入Maven的repository(关于此部分可参见Maven网站的简介)中,Container中包含了web应用目录,放置在webapp目录下。
4.2. 需求
l 持续集成需求
u 感知版本管理工具(CVS)的变化,如发现有变化,则进行集成。
u 调用项目编译脚本进行项目集成。
u 合并项目编译脚本产生的单元测试、功能测试的日志。
u 将集成报告发布至网站中。
u 将集成的结果以邮件方式通知相应的负责人。
l 项目集成需求
u 编译util工程,运行其中的单元测试代码并最终打成jar包放入Maven的repository中。
u 编译Container工程,运行其中的单元测试代码并最终打成war包放入应用服务器(Tomcat)中,启动Tomcat,运行功能测试。
u 编译DemoPortlet工程,运行其中的单元测试代码并最终打成Portlet插件包部署至位于Tomcat下的Container中,运行功能测试。
4.3. 实现
l 搭建CruiseControl、Maven环境。(这个就不在这讲了,请参看官方网站的文档)
l 编写项目集成脚本并测试。
根据项目集成需求中的描述,分别对三个工程编写脚本(Maven.xml)。
u Util工程
1) 编写project.xml,或者可在Eclipse中利用Maven Plugin图形化的编辑方式生成project.xml。project.xml(详细信息见官方网站的文档)如下:
<project>
<pomVersion>1</pomVersion>
<artifactId>util</artifactId>
<id> util </id>
<name> util </name>
<groupId>jite</groupId>
<currentVersion>1.0</currentVersion>
<organization>
<name>Jite</name>
<url>www.jite.net</url>
</organization>
<inceptionYear>2004</inceptionYear>
<package>net.jite.demo</package>
<description>Demo Util</description>
<issueTrackingUrl>bugs.jite.com</issueTrackingUrl>
<repository/>
<dependencies>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>2.1</version>
<jar>commons-collections-2.1.jar</jar>
<type>jar</type>
<properties/>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.0.3</version>
<jar>commons-logging-1.0.3.jar</jar>
<type>jar</type>
<properties/>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<jar>junit-3.8.1.jar</jar>
<type>jar</type>
<properties/>
</dependency>
</dependencies>
<build>
<sourceDirectory>src/java</sourceDirectory>
<unitTestSourceDirectory>src/test</unitTestSourceDirectory>
<unitTest>
<includes>
<include>**/*Test.java</include>
</includes>
</unitTest>
</build>
<properties/>
</project>
2) 编写build.properties。build.properties如下:
## Maven远程包仓库URL
maven.repo.remote = http://public.planetmirror.com/pub/maven,http://mirrors.sunsite.dk/maven
## 本地Maven环境路径
maven.local.home = D:/Maven
## 本地Maven包仓库路径
maven.repo.local = ${maven.local.home}/repository
3) 编写maven.xml。maven.xml如下:
<?xml version="1.0" encoding="GBK"?>
<project default="util:build" xmlns:maven="jelly:maven" xmlns:ant="jelly:ant"
xmlns:j="jelly:core" xmlns:util="jelly:util">
<!-- 项目自动集成:初始化、打包发布-->
<goal name="util:build">
<attainGoal name="util:init"/>
<attainGoal name="util:package"/>
<echo>+---------------------------------------------+</echo>
<echo>| 已完成自动集成 </echo>
<echo>+---------------------------------------------+</echo>
</goal>
<!--
完成一些初始化工作
1. 清空编译文件夹
2. 同步eclipse,在eclipse中建立MAVEN_REPO变量
-->
<goal name="util:init">
<echo>+---------------------------------------------+</echo>
<echo>| Build System </echo>
<echo>| ------- </echo>
<echo>+---------------------------------------------+</echo>
<attainGoal name="clean"/>
</goal>
<!--
自动设置eclipse project 属性
1. 添加 MAVEN_REPO 变量
2. 同步 dependencies 与 eclipse build path
-->
<goal name="util:eclipse">
<attainGoal name="eclipse:add-maven-repo"/>
<attainGoal name="eclipse:generate-classpath"/>
</goal>
<!--
打包为jar,并install到maven的repository中
-->
<goal name="util:package">
<attainGoal name="jar:install"/>
</goal>
</project>
u Container工程
Container工程基本同Util工程相应的编写project.xml、project.properties、build.properties以及maven.xml,不同的在于maven.xml的编写时需要生成的为war包(Maven已提供此插件),在Maven的官方网站上均可找到相似的示例。
u DemoPortlet工程
DemoPortlet工程的脚本的编写方法和Util工程也基本相同,不同之处在于需要将DemoPortlet工程按照项目的插件机制要求打包发布至Tomcat的相关目录中。
在编写完毕上述三个工程的Maven脚本后,在Util、Container、DemoPortlet的上一级目录编写一个project.xml和Maven.xml以支持此项目的多工程自动集成。
Project.xml:
<project>
<pomVersion>1</pomVersion>
<name>MultiProject</name>
<groupId>MultiProject</groupId>
<organization>
<name>jite.net</name>
</organization>
<inceptionYear>2005</inceptionYear>
<properties/>
</project>
Maven.xml:
<?xml version="1.0" encoding="gb2312"?>
<project default="demo:allbuild" xmlns:maven="jelly:maven" xmlns:ant="jelly:ant"
xmlns:j="jelly:core" xmlns:util="jelly:util">
<goal name="demo:allbuild">
<!-- 部署util-->
<maven:reactor
basedir="${basedir}"
includes="util 1.0/project.xml"
goals="util:build"
banner="install util"
postProcessing="false"
ignoreFailures="false"/>
<!-- 部署Container-->
<maven:reactor
basedir="${basedir}"
includes="Container 1.0/project.xml"
goals="container:build"
banner="install Container"
postProcessing="false"
ignoreFailures="false"/>
<!-- 部署DemoPortlet-->
<maven:reactor
basedir="${basedir}"
includes="DemoPortlet 1.0/project.xml"
goals="DemoPortlet:build"
banner="install DemoPortlet"
postProcessing="false"
ignoreFailures="false"/>
</goal>
</project>
在此目录下运行maven,如编译过程顺利执行完毕则表明项目自动集成脚本已编写完毕并且确认当前的CVS环境是可被自动集成的。
l 编写持续集成脚本并测试。
建立一个持续集成目录为ContinuousIntegration,从CVS下载一份项目目录至此目录中,假设此目录名为Demo。
根据持续集成的要求编写持续集成脚本如下:
<?xml version="1.0" encoding="UTF-8"?>
<cruisecontrol>
<project name="demo" buildafterfailed="true">
<!-- 每次检测是否有变化时先运行此处 -->
<bootstrappers>
<currentbuildstatusbootstrapper file="logs/demo/buildstatus.txt"/>
</bootstrappers>
<!-- 检测是否有变化,如有变化则开始集成 -->
<modificationset quietperiod="10">
<!-- 基于cvs的检测 -->
<cvs localworkingcopy="demo"/>
</modificationset>
<!-- 持续集成-->
<schedule interval="18">
<!-- 项目的编译脚本 -->
<maven mavenscript="D:/tools/maven/bin/maven.bat"
projectfile="demo/project.xml"
goal="demo:allbuild"/>
</schedule>
<!-- 持续集成过程的日志记录以及需要合并的日志 -->
<log dir="logs/demo">
<!-- 合并项目编译脚本中产生的单元、功能测试日志 -->
<merge dir="demo/util/target/test-reports"/>
<merge dir="demo/container/target/test-reports"/>
<merge dir="demo/demoportlet /target/test-reports"/>
</log>
<!-- 持续集成后结果的公布 -->
<publishers>
<currentbuildstatuspublisher file="logs/demo/buildstatus.txt"/>
<!-- 邮件通知相关的负责人 -->
<email mailhost="smtp.yourdomain.com"
returnaddress="buildmaster@yourdomain.com"
skipusers="true"
reportsuccess="fixes"
subjectprefix="[CruiseControl]"
buildresultsurl="http://buildserver:8080/cruisecontrol/buildresults">
<failure address="developers@yourdomain.com" />
<success address="developers@yourdomain.com" />
</email>
</publishers>
</project>
</cruisecontrol>
将此脚本文件放入ContinuousIntegration目录中,在此目录下运行cruisecontrol或cc,提交一文件至cvs检查持续集成的执行是否如需求中所预期的,如不正确则直至调为正确为止。
l 进行持续集成。
在ContiuousIntegration目录下运行cc,从此以后将会根据需求中所描述的一样进行项目的自动集成和部署。
通过以上示例大致的描述了持续集成实现的一些步骤,当然,在实际的项目持续集成实现的过程中脚本的编写必然比上述的更为复杂,这就要求了脚本编写人员需要对CruiseControl、Maven有足够的了解,但相对于持续集成所带来的好处这些都是值得的。