持续集成包括软件项目的持续构建与发布,通过持续性地编译与构建,完成项目的不断集成。持续集成通常应用与WEB开发领域,对于RCP项目的持续集成目前业界较少。对于RCP项目的持续集成,就是通过定期执行项目自动构建程序来完成项目版本的集成与发布,重点在于项目的自动化构建。经过笔者的研究与学习,通过本文向大家介绍一种基于PDE的RCP项目自动化构建方式,希望大家多多交流。
一、简介
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中,且不同Eclipse下PDE实现的方式略有不同,本文以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 过程的主脚本文件,在之前的启动命令中的 �Cbuildfile 后的值就是此 build.xml 文件的路径。而 genericTargets.xml 则定义了在 build 过程的各个子过程的行为。
templates 文件夹中则提供了一系列的模板库。您可在 templates 下的 headless-build 目录中找到前面所说的 allElements.xml、build.properties、customTargets.xml 文件的模板。您可以方便地将其拷贝和修改,用作您自己的构建配置文件。
(二)headless-build 的工作流程和通知机制
customTargets.xml 提供了对 headless-build 流程的多个节点的控制。而事实上 PDE 将 headless-build 分为多个子过程。customTargets.xml 正是针对了此些子过程提供了回调接口,以通知和触发用户定制的脚本被执行。以下是 headless-build 整体流程的简图。
在其中,您需要提供的是最初的引入脚本(Ant 文件,批处理文件或命令行指令),之后 Eclipse 宿主会被启动并调用您指定的通常位于 PDE 插件 scripts 目录下的 build.xml,而此文件则会相应找到您在配置目录中定义的 customTargets.xml 文件,执行用户设定的回调脚本,并通过 customTargets.xml 调用 PDE 插件 scripts 下的 genericTargets.xml 启动 PDE 内部的各项子过程的执行代码。而在 genericTargets 中则定义了 headless-build 的4个子过程fetch、generate、process 和 assemble,下面我们对他们做简单的介绍。
(1)Fetch
Fetch负责从源控制中下载所需构建的插件项目的源文件。其过程如下:
正如前文所述,customTargets.xml中包含了headless build执行所有过程的预置以及回调接口,开发者可以在其中自己编写所需的自定义脚本。Fetch操作主要在于fetchElement操作,它会根据PreBuild中用户定义的源文件库的文件及地址映射的MAP来以次获取需要构建的项目源码。同时,fetch taeget中有一个unless=”skipFetch”的属性,该属性表明当shipFetch存在时,fetch过程可以被跳过。
(2)Generate
Generate为每个插件分别生成自身的构建脚本。其过程如下:
同样可以自定义预置及回调操作,generate的主要过程在于generateScript操作。该操作使用eclipse.buildScript方法为每个插件分别生成自身的构建脚本。
(3)Process
process通过运行 generate 中生成的脚本,完成对每个插件的构建。其过程如下:
Process过程主要调用prcessElement操作,该操作通过processViaFeature和processFlat两个过程完成对各个插件以及依赖插件的构建。
(4)Assemble
assemble将 process 过程中生成的插件打包。其过程如下:
调用了assembleElement过程,根据Manifest清单中的依赖,把相关的插件集成到一起,打包发布为一个ZIP文件,该文件解压后点击.exe文件可运行。(打包后的的插件会被自动集成到宿主平台中)
三、示例
了解了PDE headless build的实现原理,我们可以为其量身打造自己的构建脚本。为了直接突出核心,我们对于获取代码管理工具上源码的部分就取消了,在配置文件中将SkitFetch和skipMap设置为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,新建完以后打开文件进行如下配置。
接着为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内容如下:
<projectname="com.rcpquickstart.helloworld.build"default="build"> <propertyfile="build.properties"/> <targetname="init"> <mkdirdir="${buildDirectory}"/> <mkdirdir="${buildDirectory}/plugins"/> <mkdirdir="${buildDirectory}/features"/> <copytodir="${buildDirectory}/plugins"> <filesetdir="../"> <includename="com.rcpquickstart.helloworld/**"/> </fileset> </copy> <copytodir="${buildDirectory}/features"> <filesetdir="../"> <includename="com.rcpquickstart.helloworld.feature/**"/> </fileset> </copy> </target> <targetname="pde-build"> <javaclassname="org.eclipse.equinox.launcher.Main"fork="true"failonerror="true"> <argvalue="-application"/> <argvalue="org.eclipse.ant.core.antRunner"/> <argvalue="-buildfile"/> <argvalue="${eclipseLocation}/plugins/org.eclipse.pde.build_${pdeBuildPluginVersion}/scripts/productBuild/productBuild.xml"/> <argvalue="-Dtimestamp=${timestamp}"/> <classpath> <pathelementlocation="${eclipseLocation}/plugins/org.eclipse.equinox.launcher_${equinoxLauncherPluginVersion}.jar"/> </classpath> </java> </target> <targetname="clean"> <deletedir="${buildDirectory}"/> </target> <targetname="build"depends="clean, init, pde-build"/> </project> |
从脚本可以看出,执行过程分为Clean、init、pde-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=<url for eclipse download site> eclipseBuildId=<Id of Eclipse build to get> 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版本;
equinoxLauncherPluginVersion:equinoxLauncher版本号,用于Eclipse项目构建的进程,其在Eclipse目录下的Plugin文件夹里找到
org.eclipse.equinox.launcher_1.0.201.R35x_v20090715.jar ,后面字符为其版本号;
Base:RCP runtime Binary和Delta Pack存放的目录路径,其二者包括了RCP运行时环境所需插件以及大量跨平台插件。
eclipseLocation:执行构建的Eclpise路径
Product:需要发布的Product文件
buildDirectory:执行构建的目标目录,包括了构建时产生的文件
skipMaps:true,跳过源文件的MAP获取。
skipFetch:true,跳过从源代码管理工具获取源文件。
(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项目,解压后目录结构如下:
双击helloworld.exe可运行程序。