持续集成包括软件项目的持续构建与发布,通过持续性地编译与构建,完成项目的不断集成。持续集成通常应用与WEB开发领域,对于RCP项目的持续集成目前业界较少。对于RCP项目的持续集成,就是通过定期执行项目自动构建程序来完成项目版本的集成与发布,重点在于项目的自动化构建。经过笔者的研究与学习,通过本文向大家介绍一种基于PDERCP项目自动化构建方式,希望大家多多交流。

一、简介

PDE (Plug-in Development Environment) headless-build是一种基于 Ant 脚本的构建方式,它主要适用于Eclipse plug-in与 Eclipse RCP项目的导出。由于headless-build是特定于 Eclipse 插件的构建平台,而 Eclipse 插件的编译构建都离不开Eclipse本身的类库与资源,因而运行headless-build之前,必须已经安装好带有PDE环境的Eclipse SDK以及相应的插件PDE作为Eclipse的一个插件项目存在于Eclipse中,且不同EclipsePDE实现的方式略有不同,本文以Eclipse3.5.1为例,介绍PDE headless-build实现自动化构建的原理及示例。

二、原理

(一)headless-build核心库文件

headless-build核心脚本文件都存储于 Eclipse 的 PDE 插件中。以Eclipse3.5.1为例,PDE build 插件位于 plugins 目录下的 org.eclipse.pde.build 插件目录中。在此目录的 lib 文件夹中包含有 pde build 库的核心类文件,其中包括了绝大多数和 PDE build 相关的 Ant task 的实现,有兴趣的读者可以在 org.eclipse.pde.source 源代码插件中看到相应的代码

对于我们的 build 任务,PDE headless-build 的核心脚本文件位于 scripts 和 templates 两个目录下。

scripts 文件夹中提供了 headless-build 的基础控制脚本。事实上无论是使用 headless-build 还是IDE 导出,我们都间接地通过执行这些脚本完成了一系列的过程。其中的 build.xml 文件是 build 过程的主脚本文件,在之前的启动命令中的 –buildfile 后的值就是此 build.xml 文件的路径。而 genericTargets.xml 则定义了在 build 过程的各个子过程的行为。

templates 文件夹中则提供了一系列的模板库。您可在 templates 下的 headless-build 目录中找到前面所说的 allElements.xmlbuild.propertiescustomTargets.xml 文件的模板。您可以方便地将其拷贝和修改,用作您自己的构建配置文件。

(二)headless-build 的工作流程和通知机制

customTargets.xml 提供了对 headless-build 流程的多个节点的控制。而事实上 PDE 将 headless-build 分为多个子过程。customTargets.xml 正是针对了此些子过程提供了回调接口,以通知和触发用户定制的脚本被执行。以下是 headless-build 整体流程的简图。

基于PDE的RCP项目自动构建_第1张图片

在其中,您需要提供的是最初的引入脚本(Ant 文件,批处理文件或命令行指令),之后 Eclipse 宿主会被启动并调用您指定的通常位于 PDE 插件 scripts 目录下的 build.xml,而此文件则会相应找到您在配置目录中定义的 customTargets.xml 文件,执行用户设定的回调脚本,并通过 customTargets.xml 调用 PDE 插件 scripts 下的 genericTargets.xml 启动 PDE 内部的各项子过程的执行代码。而在 genericTargets 中则定义了 headless-build 4个子过程fetchgenerateprocess 和 assemble,下面我们对他们做简单的介绍。

1Fetch

Fetch负责从源控制中下载所需构建的插件项目的源文件。其过程如下:

基于PDE的RCP项目自动构建_第2张图片

正如前文所述,customTargets.xml中包含了headless build执行所有过程的预置以及回调接口,开发者可以在其中自己编写所需的自定义脚本。Fetch操作主要在于fetchElement操作,它会根据PreBuild中用户定义的源文件库的文件及地址映射的MAP来以次获取需要构建的项目源码。同时,fetch taeget中有一个unless=skipFetch的属性,该属性表明当shipFetch存在时,fetch过程可以被跳过。

(2)Generate

Generate为每个插件分别生成自身的构建脚本。其过程如下:

基于PDE的RCP项目自动构建_第3张图片

同样可以自定义预置及回调操作,generate的主要过程在于generateScript操作。该操作使用eclipse.buildScript方法为每个插件分别生成自身的构建脚本

(3)Process 

process通过运行 generate 中生成的脚本,完成对每个插件的构建。其过程如下:

基于PDE的RCP项目自动构建_第4张图片

Process过程主要调用prcessElement操作,该操作通过processViaFeatureprocessFlat两个过程完成对各个插件以及依赖插件的构建。

(4)Assemble

assemble将 process 过程中生成的插件打包。其过程如下:

基于PDE的RCP项目自动构建_第5张图片

调用了assembleElement过程,根据Manifest清单中的依赖,把相关的插件集成到一起,打包发布为一个ZIP文件,该文件解压后点击.exe文件可运行。(打包后的的插件会被自动集成到宿主平台中)

三、示例

了解了PDE headless build的实现原理,我们可以为其量身打造自己的构建脚本。为了直接突出核心,我们对于获取代码管理工具上源码的部分就取消了,在配置文件中将SkitFetchskipMap设置为true即可,接下来将把本地的RCP项目完成自动化构建。

(1)新建一个Plug-in Project,项目名称为com.rcpquickstart.helloworld,模板选择“RCP Helloworld,新建的步骤不做详细讲解,顺着向导操作即可。

(2)新建一个Feature Project,名称为com.rcpquickstart.helloworld.feature,其插件选择com.rcpquickstart.helloworld,新建的步骤不做详细讲解,顺着向导操作即可,完成的Feature结构如下:

(3)com.rcpquickstart.helloworld项目中新建一个Product Configratiuon,名称为helloworld.product,新建完以后打开文件进行如下配置。

基于PDE的RCP项目自动构建_第6张图片

接着为product添加功能部件(Feature),点击选项卡“Dependencies”,添加“com.rcpquickstart.helloworld.feature”和“org.eclipse.rcp”两个功能部件即可。到目前为止,我们的RCP产品项目配置完毕。

(4)新建一个Java Project,名称为com.rcpquickstart.helloworld.build,接着再新建两个文件“build.xml”和“build.properties”。该项目为自动构建插件项目,使用ant脚本。

(5)编辑build.xml内容如下:

name="com.rcpquickstart.helloworld.build"default="build">

file="build.properties"/>

name="init">

dir="${buildDirectory}"/>

dir="${buildDirectory}/plugins"/>

dir="${buildDirectory}/features"/>

todir="${buildDirectory}/plugins">

dir="../">

name="com.rcpquickstart.helloworld/**"/>

todir="${buildDirectory}/features">

dir="../">

name="com.rcpquickstart.helloworld.feature/**"/>

name="pde-build">

classname="org.eclipse.equinox.launcher.Main"fork="true"failonerror="true">

value="-application"/>

value="org.eclipse.ant.core.antRunner"/>

value="-buildfile"/>

value="${eclipseLocation}/plugins/org.eclipse.pde.build_${pdeBuildPluginVersion}/scripts/productBuild/productBuild.xml"/>

value="-Dtimestamp=${timestamp}"/>

location="${eclipseLocation}/plugins/org.eclipse.equinox.launcher_${equinoxLauncherPluginVersion}.jar"/>

name="clean">

dir="${buildDirectory}"/>

name="build"depends="clean, init, pde-build"/>

从脚本可以看出,执行过程分为Cleaninitpde-build三部,Clean为清理构建的目标目录,即产品构建目录,init为新建构建目录结构,同时,将所需构建的插件项目以及其feature拷贝到相应的目录下(如果是依赖多个插件的话都需要执行拷贝)。Pde-build则是启动一个Eclipse进程调用pde构建。

(6)build.properties内容如下:

# Version of org.ecilpse.pdebuild

pdeBuildPluginVersion=3.5.1.R35x_20090820

# Version of org.eclipse.equinox.launcher

equinoxLauncherPluginVersion=1.0.201.R35x_v20090715

base=c:/helloworld-build-target

eclipseLocation=F:/Install/PDE/eclipse-SDK-3.5.1-win32/eclipse

############# PRODUCT/PACKAGING CONTROL #############

product=/com.rcpquickstart.helloworld/helloworld.product

runPackager=true

#Set the name of the archive that will result from the product build.

#archiveNamePrefix=

archivePrefix=helloworld

# The location underwhich all of the build output will be collected.

collectingFolder=${archivePrefix}

configs=win32, win32, x86

allowBinaryCycles = true

#Sort bundles depenedencies across all features instead of just within a given feature.

flattenDependencies = true

#Arguments to send to the zip executable

zipargs=

#Arguments to send to the tar executable

tarargs=

############## BUILD NAMING CONTROL ################

# The directory into which the build elements are fetched and where

# the build takes place.

buildDirectory=c:/helloworld-build

# Type of build.  Used in naming the build output.  Typically this value is

# one of I, N, M, S, ...

buildType=I

# ID of the build.  Used in naming the build output.

buildId=HelloWorld

# Label for the build.  Used in naming the build output

buildLabel=${buildType}.${buildId}

Timestamp for the build.  Used in naming the build output

timestamp=007

baseLocation=${base}/eclipse

filteredDependencyCheck=false

skipBase=true

eclipseURL=download site>

eclipseBuildId=

eclipseBaseURL=${eclipseURL}/eclipse-platform-${eclipseBuildId}-win32.zip

skipMaps=true

mapsRepo=:pserver:anonymous@example.com/path/to/repo

mapsRoot=path/to/maps

mapsCheckoutTag=HEAD

#tagMaps=true

mapsTagTag=v${buildId}

skipFetch=true

############# JAVA COMPILER OPTIONS ##############

# Specify the output format of the compiler log when eclipse jdt is used

logExtension=.log

# Whether or not to include debug info in the output jars

javacDebugInfo=false 

# Whether or not to fail the build if there are compiler errors

javacFailOnError=true

# Enable or disable verbose mode of the compiler

javacVerbose=true

需要注意几个关键配置:

pdeBuildPluginVersion

PDE BUILD的版本号,可以在Eclipse目录下的Plugin文件夹里找到org.eclipse.pde.build_3.5.1.R35x_20090820文件夹,文件夹名后面的字符为PDE版本;

equinoxLauncherPluginVersionequinoxLauncher版本号,用于Eclipse项目构建的进程,其在Eclipse目录下的Plugin文件夹里找到

org.eclipse.equinox.launcher_1.0.201.R35x_v20090715.jar ,后面字符为其版本号;

BaseRCP runtime BinaryDelta Pack存放的目录路径,其二者包括了RCP运行时环境所需插件以及大量跨平台插件。

eclipseLocation:执行构建的Eclpise路径

Product:需要发布的Product文件

buildDirectory:执行构建的目标目录,包括了构建时产生的文件

skipMapstrue,跳过源文件的MAP获取。

skipFetchtrue,跳过从源代码管理工具获取源文件。

(7)准备构建环境。根据配置文件中配置路径,分别在C盘建立“helloworld-build”和“helloworld-build-target”两个文件夹,同时,从eclipse官网上下载“eclipse-RCP-3.5.1-win32.zip”和“eclipse-3.5.1-delta-pack.zip”压缩文件,将其解压到刚才的helloworld-build-target目录下(相同的文件可以覆盖),下载地址:

http://archive.eclipse.org/eclipse/downloads/drops/R-3.5.1-200909170800/index.php

(8)执行build.xml,待构建完成后,在“/helloworld-build/I.HelloWorld”目录下找到HelloWorld-win32.win32.x86.zip,其为构建好的RCP项目,解压后目录结构如下:

基于PDE的RCP项目自动构建_第7张图片

双击helloworld.exe可运行程序。