纯第三方依赖的Eclipse插件的构建和自动更新

背景

我们现在开发的一个RCP产品,内部需要集成一个后端的引擎,该引擎使用Java开发,由几个核心的jar包及一系列依赖的第三方jar包组成。而Eclipse插件是不支持直接调用普通jar包中的类的,需要先把这些外部jar包封装成插件。Eclipse支持将jar包内置到插件中,然后通过把这些内置的jar包添加到插件的classpath中来供插件内部的类调用,如果要开放给其他插件使用,还需要在插件的配置中导出要开放的包。
因为这个引擎的jar及其依赖比较多,每次发布时如果都需要手动拷贝jar包到插件中也是一件费时费力且容易出错的工作,考虑使用Maven来自动将依赖打包成插件的方式。

Maven方案

如果集成Maven的话,需要支持以下功能:

  1. 支持打包成Eclipse插件或OSGI Bundle。
  2. 支持下载pom中定义的依赖,并将这些依赖jar包打包到Eclipse插件或OSGI Bundle内部。
  3. 支持将工程内部的jar包发布到生成的插件或Bundle中。
  4. 支持在生成的插件或Bundle的MENIFEST.MF文件中将这些jar添加到Classpath路径中。
  5. 支持将要导出的包添加到插件或Bundle的MANIFEST.MF文件的Export-Package中。
  6. 支持自定义插件或Bundle的MANIFEST.MF文件中的其他配置项。

google后发现,maven插件maven-bundle-plugin完全满足上述要求,它是一个生成OSGI Bundle的插件,完全以Maven的方式,下载pom中定义的依赖,根据配置的MANIFEST信息,生成对应的OSGI Bundle。
项目地址:Apache Felix Maven Bundle Plugin (BND) :: Apache Felix

实施步骤

先假设我们要生成第三方依赖Bundle包的工程为:

- org.ming.bundle-wrapper
    + src
    + META-INF
    + libs
    - .project
    - build.properties
    - pom.xml

先假设我们要包装两个依赖:
一个依赖是Maven仓库中的依赖:

<groupId>org.ming</groupId>
<artifactId>test-engine</artifactId>
<version>1.0.0-SNAPSHOT</version>

另一个是普通jar包,jar包放在工程libs目录下,这个jar需要用到一些本地库,假设路径为:

- libs
    - test.jar
    - test.dll
    - test.so
    - test.jnilib

1、构建bundle

定义pom.xml:

<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>org.ming</groupId>
    <artifactId>org.ming.eclipse.bundlesupport</artifactId>
    <packaging>bundle</packaging>
    
    <properties>
        <engine-version>1.0.0-SNAPSHOT</engine-version>
    </properties>

    <dependencies>
        <!-- 列出Bundle中要包含的jar包 -->
        <!-- 间接依赖不用列,会自动解析间接依赖并下载。 -->
        <dependency>
            <groupId>org.ming</groupId>
            <artifactId>test-engine</artifactId>
            <version>${engine-version}</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <!-- 添加编译插件 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.felix</groupId>
                <artifactId>maven-bundle-plugin</artifactId>
                <version>3.5.0</version>
                <extensions>true</extensions>

                <configuration>
                    <instructions>
                        ①<Include-Resource>
                            libs/test.jar=./libs/test.jar,libs/test.dll=./libs/test.dll,libs/test.so=./libs/test.so,libs/test.jnilib=./libs/test.jnilib
                        </Include-Resource>
                        ②<Bundle-ClassPath>
                            .,{maven-dependencies},libs/test.jar
                        </Bundle-ClassPath>
            ③<Bundle-NativeCode>
                    libs/test.dll;osname=win32;osname=Windows10,libs/test.so;osname=linux,libs/test.jnilib;osname=macosx,*
            </Bundle-NativeCode>
                        ④<Export-Package>
                            org.ming.*;org.apache.commons.logging.*;org.apache.logging.log4j.*;org.apache.maven.*;org.codehaus.plexus.*;org.apache.commons.*;com.fasterxml.jackson.*;com.thoughtworks.xstream.*;org.apache.velocity.*;javax.wsdl.*;com.ibm.wsdl.*;org.dom4j.*
                        </Export-Package>
                        <Embed-Dependency>*</Embed-Dependency>
                        ⑤<Embed-StripGroup>true</Embed-StripGroup>
                        ⑥<Embed-Transitive>true</Embed-Transitive>
                        ⑦<Import-Package>org.eclipse.core.runtime;org.osgi.framework;org.osgi.framework.wiring
                        </Import-Package>
                        ⑧<Bundle-Name>Test Bundle</Bundle-Name>
                        ⑨<Bundle-Vendor>MING</Bundle-Vendor>
            ⑩<Bundle-SymbolicName>org.ming.test.bundle-support</Bundle-SymbolicName>
                    </instructions>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

Include-Resource:指示需要把工程内的那些资源打包到bundle包中。格式为“src-path=dest-path”。
Bundle-ClassPath:要添加到生成的MANIFEST文件中的ClassPath的信息,```${maven-dependencies}是内置变量,表示当前pom中定义的maven依赖。
Bundle-NativeCode:要添加到生成的MANIFEST文件中的Native-Code的信息,具体解释可以查看关于OSGI Bundle的MANIFEST.MF文件的说明。
Export-Package:要导出的包的路径,支持通配符,插件会自动搜索当前添加到bundle中的jar中的包路径,如果符合通配符,就会将该包添加到export段中。
Embed-StripGroup:jar包封装到bundle中时是否去掉groupId相关信息,如果值为true,则只会生成artifactId-version.jar到bundle中;如果值为false,则会按groupId生成对应的文件夹,然后将jar按照artifactId-version.jar格式生成到对应的groupId文件夹中。
Embed-Transitive:是否下载间接依赖。
Import-Package:要添加到生成的MANIFEST文件中的Import-Package信息。
Bundle-Name:bundle的名称。
Bundle-Vendor:bundle的提供者(生产商)
Bundle-SymbolicName:bundle的标识符(唯一id)

构建完成后,即可以生成符合OSGI规范的bundle jar。该输出可以通过Maven仓库管理,即可以使用mvn install | deploy来构建完成后安装到本地仓库或远程仓库。

2、集成到RCP产品

如果要让RCP产品在构建时能自动包含该bundle,需要在产品的pom中加入该bundle的依赖,pom.xml的相关配置信息如下:

<modelVersion>4.0.0</modelVersion>
    <groupId>org.ming</groupId>
    <artifactId>org.ming.product</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <packaging>pom</packaging>

    <dependencies>
        ①<dependency>
            <groupId>org.ming</groupId>
            <artifactId>org.ming.eclipse.bundlesupport</artifactId>
            <version>1.0.0-SNAPSHOT</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.eclipse.tycho</groupId>
                <artifactId>tycho-maven-plugin</artifactId>
                <version>${tycho-version}</version>
                <extensions>true</extensions>
                <useProjectSettings>false</useProjectSettings> </configuration -->
            </plugin>
            <plugin>
                <groupId>org.eclipse.tycho</groupId>
                <artifactId>target-platform-configuration</artifactId>
                <version>${tycho-version}</version>
                <configuration>
                    ②<pomDependencies>consider</pomDependencies>
                    <resolver>p2</resolver>
                    <environments>
                        <environment>
                            <os>linux</os>
                            <ws>gtk</ws>
                            <arch>x86_64</arch>
                        </environment>
                        <environment>
                            <os>win32</os>
                            <ws>win32</ws>
                            <arch>x86_64</arch>
                        </environment>
                        <environment>
                            <os>macosx</os>
                            <ws>cocoa</ws>
                            <arch>x86_64</arch>
                        </environment>
                    </environments>
                </configuration>
            </plugin>
        </plugins>
    </build>

重要的就两个部分:
①:将bundle依赖加入pom。
②:将tycho的target-platform-configuration插件的pomDependencies属性设置为consider,这样tycho才会处理pom中定义的依赖。
如上配置后构建,在生成的RCP产品中,该bundle即会放到plugins,启动时即会以bundle的形式被加载。

3、支持bundle独立更新

前面两步完成后,支持第三方依赖的封装bundle已经可以成功构建并在产品构建时自动加到产品插件列表中,如果某个第三方依赖有更新,则发布步骤为:

  1. 修改bundle工程的pom,如果依赖有更新版本号,则修改pom中对应依赖的版本号。
  2. 修改bundle工程pom的版本号,也就是bundle的版本号,便于RCP产品检查更新。
  3. mvn clean deploy构建该bundle并推送到maven仓库。
  4. 修改产品的pom中该bundle依赖的版本号。
  5. mvn clean package构建产品。
  6. 将构建好的产品安装包和更新包发布到http站点。

可以看到这个发布流程有一个弊端,就是如果仅仅是第三方依赖有更新,RCP产品端没有任何更新,但是也必须完整的构建整个RCP产品,产生的后果一是完整构建产品比较费时间,二是产品构建时除了Eclipse插件外其他的本地插件构建时会生成qulifier小版本号(通常是构建日期),也就是说即使RCP插件没有更新,因为生成的小版本号比之前新,在更新的时候也会同时下载更新所有的本地插件。

所以在这一个章节中,我们要继续改进优化关于第三方依赖bundle,让它可以支持独立更新,这样以后如果仅仅是第三方依赖更新的话,我们只需要让用户更新这个bundle就可以了,不用完整构建产品和更新。

插件的安装和更新可以以插件为单位,也可以以feature为单位,为了以后扩展需要,我们以feature为单位来发布该bundle。

3.1 创建parent工程

创建一个parent的pom工程,将上面的bundle工程加入进来。

3.1 创建feature子工程

在parent工程下创建一个org.ming.eclipse.bundlesupport.feature的Eclipse feature项目,在feature.xml中定义好feature的信息(描述、版权、许可等),然后把org.ming.eclipse.bundlesupport插件添加进来。
这里可以取一个巧,我们可以在feature.xml中把feature的版本号定义为变量,如下:

<feature
      id="org.ming.eclipse.bundlesupport.feature"
      label="%featureName"
      version="${featureVersion}"
      provider-name="%featureVendor">

   <description>
      %description
   </description>

   <copyright url="%copyrightURL">
      %copyright
   </copyright>

   <license url="%licenseURL">
      %license
   </license>

version处的${featureVersion}变量我们可以在pom中来定义和生成,这样在更新版本号时可以不用来手动修改版本号。
pom中的变量定义如下:

<properties>
        ①<featureVersion>${parsedVersion.majorVersion}.${parsedVersion.minorVersion}.${parsedVersion.incrementalVersion}.qualifier</featureVersion>
    </properties>

    <build>
        <resources>
            <resource>
                <directory>.</directory>
                <includes>
                    <include>feature.xml</include>
                    <include>feature.properties</include>
                </includes>
                ②<filtering>true</filtering>
            </resource>
        </resources>

        <plugins>
            ③<plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>build-helper-maven-plugin</artifactId>
                <version>1.8</version>
                <executions>
                    <execution>
                        <id>parse-version</id>
                        <goals>
                            <goal>parse-version</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

①:定义feature-version变量,该变量同时使用其他变量来组成其值。末尾的qulifier是指示后续构建该feature时替换为具体的构建时间(Eclipse机制)。
②:定义filter为true,指示maven在复制该资源时替换变量值。
③:引入build-helper-maven-plugin来生成feature-version定义中使用的版本变量。

3.2 创建updatesite子工程

在parent工程下创建一个org.ming.eclipse.bundlesupport.updatesite的Eclipse updatesite项目,添加一个category.xml,在其中定义好一个分组,例如“Bundle Test”,然后将上述bundle的feature添加进来,详细定义如下:

<?xml version="1.0" encoding="UTF-8"?>
<site>
   <feature id="org.ming.eclipse.bundlesupport.feature">
      <category name="bundle-test"/>
   </feature>
   <category-def name="bundle-test" label="Bundle Test"/>
</site>

在本工程中,我们要引入p2-maven-plugin插件来生成p2类型的更新站点,该插件用来将定义好的feature和plugin生成p2站点。
关于该插件的详细信息可以参见:GitHub - reficio/p2-maven-plugin at v1.7.0

定义pom文件:

<properties>
    ①<bundlesupport.repo.name>Bundle Test Updates</bundlesupport.repo.name>
</properties>

<build>
    <plugins>
        ②<plugin>
            <groupId>org.reficio</groupId>
            <artifactId>p2-maven-plugin</artifactId>
            <version>1.7.0</version>
            <executions>
                <execution>
                    <id>generate-p2-site</id>
                    <phase>package</phase>
                    <goals>
                        <goal>site</goal>
                    </goals>
                    <configuration>
                        ③<categoryFileURL>${basedir}/category.xml</categoryFileURL>
                        ④<additionalArgs>-metadataRepositoryName "${bundlesupport.repo.name}" -artifactRepositoryName "${bundlesupport.repo.name}"</additionalArgs>
                        ⑤<features>
                            <artifact>
                                <id>org.ming:org.ming.eclipse.bundlesupport.feature:${project.version}
                                </id>
                                <source>false</source>
                                <transitive>false</transitive>
                            </artifact>
                        </features>
                        ⑥<artifacts>
                            <!-- specify your depencies here -->
                            <!-- groupId:artifactId:version -->
                            <artifact>
                                <id>org.ming:org.ming.eclipse.bundlesupport:${project.version}</id>
                                <override>false</override>
                                <transitive>false</transitive>
                            </artifact>
                        </artifacts>
                    </configuration>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

①:定义生成的bundle更新包中库文件的名称,该名称将会作为更新url的名称显示在Eclipse的更新站点里。
②:引入p2-maven-plugin插件。
③:指定updatesite工程中的category.xml文件,如果不指定的话,会自动生成一个,自动生成的分组信息不太满足要求。
④:要传递给打包器的附加参数,示例中添加的这两个参数是指定生成的artifacts.xml和content.xml中的name的值,如果不指定的话,name值会自动生成为repository的路径加上“-artifacts”或“-metadata”,该名称会同时作为Eclipse更新站点的名称,所以设置该值很有必要。
⑤:指定站点中要包含的feature,feature和⑥中的artifacts中的artifact以“groupId:artifactId:version”的方式指定,支持变量。例如示例中feature的版本号指定为当前pom的版本号,方便统一管理。
⑥:指定站点中要包含的artifacts。

使用上述pom构建后会在target目录中生成一个repository目录,repostory目录即是p2站点库,目录结构如下:

- repository
    + features
    + plugins
    - artifacts.jar
    - category.xml
    - content.jar

3.3 构建

每次如果第三方依赖有更新,可以通过以下步骤来构建和发布更新站点:

  1. 在命令行或终端下切换到parent目录。
  2. 使用`mvn versions:set -DnewVersion=x.x.x-SNAPSHOT来统一更新parent及其所有子module的版本。
  3. 使用mvn clean package构建parent及所有子module。
  4. 配合脚本将构建后的updatesite生成的repository目录发布到http站点。
    这样,bundle的更新站点就完成了。
    以上步骤可以使用Jenkins来完成一键编译、构建和发布。
3.4 集成RCP产品

如果要RCP产品既要支持内置该bundle,又要支持该bundle独立更新的话,需要做以下改变。

  1. 修改product文件。
  1. product的“内容”选项卡中加入该bundle的feature,并将install mode设置为root。设置为root表示该feature将以独立的形式安装到产品中,表现为在“关于”的“安装细节”的“已安装软件”中,root模式的feature是独立列出的,并不包括在产品软件中。
  2. product的“更新”选项卡中加入该bundle的更新站点url。此处添加的更新站点会在构建产品时自动加入到产品软件的“更新站点”中,Eclipse检查更新的时候只会使用“更新站点”中的url来检查更新。
  1. 修改product的pom文件,去掉以前的maven形式的依赖,通过p2 url的形式引入。
<properties>
    <tycho-version>1.2.0</tycho-version>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    
    <eclipse>http://ming.org/p2/eclipse416/</eclipse>
    <lang-pack>http://ming.org/p2/babel-416/</lang-pack>
    <orbit>http://ming.org/p2/orbit/</orbit>
    <bundlesupport>http://ming.org/bundlesupport/updates/</bundlesupport>
</properties>
<repositories>
    <repository>
        <id>eclipse</id>
        <layout>p2</layout>
        <url>${eclipse}</url>
    </repository>
    <repository>
        <id>lang-pack</id>
        <layout>p2</layout>
        <url>${lang-pack}</url>
    </repository>
    <repository>
        <id>Orbit</id>
        <layout>p2</layout>
        <url>${orbit}</url>
    </repository>
    <repository>
        <id>ecd</id>
        <layout>p2</layout>
        <url>${ecd}</url>
    </repository>
    <repository>
        <id>bundlesupport</id>
        <layout>p2</layout>
        <url>${bundlesupport}</url>
    </repository>
</repositories>

至此,所有工作已完成,如果使用Jenkins来构建bundle的话,那么bundle的独立更新步骤为:

  1. 在Jenkins上一键构建bundle。
  2. 产品软件用户点击软件的“帮助”主菜单,选择“检查更新”,就会弹出bundle插件的更新对话框,一路next安装更新后重启即可。甚至可以集成自动更新功能,每次启动软件时检查更新,如果有更新自动弹出更新对话框,用户体验更好。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容