- Notifications
You must be signed in to change notification settings - Fork28
Build OSGi bundles and Eclipse Update Sites from existing JARs, e.g. from Maven repositories (Plugin for Gradle)
License
stempler/bnd-platform
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
Using OSGi and having trouble to get all the dependencies as proper OSGi bundles?Even worse, sometimes you need to adapt bundles due to class loading issues or make an annoying dependency optional?
bnd-platform can help you solve that problem - it builds OSGi bundles and even Eclipse Update Sites from existing JARs, for instance retrieved from Maven repositories together with transitive dependencies. If needed you can adapt the creation of bundles via general or individual configuration options.bnd-platform is aGradle plugin and usesbnd to create bundles andEclipse for the creation of p2 repositories.
For a quick start, check out the (outdated)sample project on GitHub or usethis minimal example as a template for your build.
Whatbnd-platform can do:
- Create bundles for any JARs that can be defined as dependencies using Gradle (e.g. local JARs, JARs from Maven repositories) and their transitive dependencies
- Download dependency sources and create source bundles (withEclipse-SourceBundle manifest header)
- AddBundle-License andBundle-Vendor headers based on information from associated POM files
- Merge multiple JARs/dependencies into one bundle, e.g. where needed due to duplicate packages or classloader issues
- Adapt the configuration for wrapping JARs or adapting existing bundles, e.g. to influence the imported packages
- Create an Eclipse Update Site / p2 repository from the created bundles
- Automatically associate version numbers to imported packages (experimental)
The simplest way to apply the plugin to your Gradle build is using the latest release on theGradle Plugin Portal.
The plugin is also hosted on Maven Central:
buildscript {repositories {mavenCentral()}dependencies {classpath'org.standardout:bnd-platform:<version>'}}applyplugin:'org.standardout.bnd-platform'
You can look up the latest release version in theGitHub releases.
Alternatives are including the repository content in thebuildSrc folder as done in thesample project or by installing the plugin to your local Maven repository usinggradlew install
and adding it as dependency to your build script viamavenLocal()
repository.
Snapshot versions are available in the related sonatype repository:
buildscript { repositories { mavenCentral() maven { url'https://oss.sonatype.org/content/repositories/snapshots/' } } dependencies { classpath'org.standardout:bnd-platform:<version>' }}applyplugin:'org.standardout.bnd-platform'
The latest snapshot version can be found in theSonatype repository.
If changes were made after the last release, the snapshot version is the last release version with the minor version number increased by one and the qualifier set to-SNAPSHOT
.For example, for the 3.1.0 release the snapshot version would be 3.2.0-SNAPSHOT.
Theplatform plugin comes with several Gradle tasks - the following are the main tasks and build upon each other:
- bundles - create bundles and write them tobuild/plugins
- potentialOptionalImports Creates a potentialOptionalImports.txt file of imported packages of all generated bundles with the optionalImport instruction (See "Optional Dependencies" section below)
- updateSite - create a p2 repository from the bundles and write it tobuild/updatesite (default)
- updateSiteZip - create a ZIP archive from the p2 repository and write it tobuild/updatesite.zip (default)
In addition, theclean task deletes all previously created bundles or update site artifacts. Usually you will want to clean the created bundles when building an update site, e.g.gradle clean updateSite
.
Be aware that for building the p2 repository Eclipse is used. If no path to a local Eclipse installation is configured (see the settings section later on) the plugin will by default download Eclipse Indigo and use it for that purpose.
bnd-platform adds a configuration namedbndplatform to a Gradle build.Prior to version 3 the configuration was namedplatform, but this clashes with aplatform method that was introduced in Gradle 5.
You can add dependencies to thebndplatform configuration and configure them like you would with any other Gradle build - for example:
// add Maven Central so the dependency can be resolvedrepositories {mavenCentral()}dependencies {// add pegdown as dependency to the platform configuration bndplatform'org.pegdown:pegdown:1.4.2'}
That's it - if you combine the previous code snippet with this one you have your firstbnd-platform build script. A callgradle updateSite
would create a p2 repository containing bundles for thepegdown library and its dependencies.
Please see the Gradle documentation onbasic andadvanced dependency management for more details on the dependency configration and advanced issues like resolving version conflicts or dealing with transitive dependencies.
As an alternative to the conventional dependency declaration you can use theplatform extension to add a dependency using thebundle keyword:
platform {// add pegdown as dependency to the platform configuration bundle'org.pegdown:pegdown:1.4.2'}
Both notations support adapting the dependency configuration as supported by Gradle, e.g. excluding specific transitive dependencies. However, adapting the OSGi bundle creation is only possible with the second notation, as it supports an additionalbnd configuration.
Thebnd library is used as a tool to create OSGi bundles from JARs.bnd analyzes the class files in a JAR to determine packages to import and export.bnd also allows to configure its behavior through a set of header instructions that resemble the OSGi manifest headers (see thebnd website for a detailed description).
The bundle configuration can be applied when adding a dependency:
platform { bundle(group:'net.sf.ehcache',name:'ehcache-core',version:'2.6.6') { bnd {// make hibernate packages optionaloptionalImport'org.hibernate','org.hibernate.*'}}}
Or independently - just registering the configuration without adding a dependency. The configuration is only applied if the dependency is either added directly or as a transitive dependency at another point in the script.
platform { bnd(group:'net.sf.ehcache',name:'ehcache-core') {// make hibernate packages optionaloptionalImport'org.hibernate','org.hibernate.*'}}
Note that in the example above the version was omitted - the configuration applies to any dependency matching the group and name, regardless of its version.
The configuration options inside thebnd call are sketched below:
platform { bnd(<DependencyNotation>) {// override/set the symbolic name symbolicName=<SymbolicNameString>// override/set the bundle name bundleName=<BundleNameString>// override/set bundle version version=<VersionString>// generic bnd header instruction instruction<Header>,<Instruction>// adapt the Import-Package instruction to import the given packages optionally optionalImport<Package1>,<Package2>,...// adapt the Import-Package instruction to add the given package instruction prependImport<PackageInstruction1>,<PackageInstruction2>,...// override the default behavior, if a (generated) qualifier should be added to// the version of wrapped bundles (see plugin settings) addQualifier=true |false }}
You can enable auto-determining versions for package imports by enabling thedetermineImportVersions
plugin setting. For each bundle to be created from a JAR retrieved via Maven/Ivy, their direct dependencies are analysed in turn and package imports are determined by the packages present there and the version of the dependency modules. This works for most cases, but is not as good as if the information would be determined based on the packages exported by the dependencies. What comes as a bonus is that for packages that are not found in the direct dependencies the imports are made optional automatically.
A default strategy defines how the versions are represented for theImport-Package instructions, i.e. what lower and upper bounds are allowed for an imported package. Pre-defined strategies that can be used are:
- MINIMUM - the module version is the minimum version for the package import, there is no upper boundary
- MAJOR - like MINIMUM, but with the next major version (excluded) as upper boundary (default)
- MINOR - like MINIMUM, but with the next minor version (excluded) as upper boundary
- NONE - no version constraint for package imports
Set the version strategy for the whole platform like this:
platform { determineImportVersions=true importVersionStrategy=MINIMUM}
You can also adapt the configuration for specific dependencies, or define a custom version strategy using a Closure. See the definitions of the pre-defined strategies in the sources forPlatformPluginExtension
for more information on how this can be done. The following example demonstrates both:
platform { imports(group:'com.google.inject',name:'guice') { versionStrategy= {// guice uses a strange versioning scheme for its package exports// e.g. version 2.0 of exports packages with version 1.2, version 3.0 with 1.3 etc."[1.${it.major},1.${it.major + 1})" } }}
The above example influences the package imports for the packages provided byguice for all bundles that haveguice as their dependency. This is needed in this case asguice already is provided as OSGi bundle with the exported package versions differing from the module version - and becausebnd-platform currently only uses the information of the module version and applies it to all imports instead of using available package export information. This might be improved in the future if there is need.
bnd-platform will by default leave JARs that are recognized as bundles (meaning they have aBundle-SymbolicName header already defined) as they are, except to eventually addedBundle-License andBundle-Vendor headers if not yet present. If an existing bundle is wrapped because a bundle configuration applies to it, the configration from the bundle manifest applies as default configuration.
To other JARs the global default configuration applies, which exports packages with the dependency's version number and imports any identified packages as mandatory import. You can override or extend the global default configuration by adding abnd configuration without giving a dependency notation, for example:
platform { bnd {// make the package junit.framework an optional import// for all JARs that were not bundles already optionalImport'junit.framework' }}
But be careful what you put into the default configuration - setting thesymbolicName orversion here will not be seen as error, but does not make any sense and may lead to unpredicatable behavior (as there can't be two bundles with the same symbolic name and version).
A bundle configuration that is more concrete will always override/extend a more general configuration. A configuration applied to a dependency group takes precedence over the default configuration, while a configuration specified for a combination of group and name in turn takes precedence over a group configuration.If configurations are defined on the same level, the configuration that is defined later in the script will override/extend a previous one.
Please note that in addition, in the combined configurations for a bundle, all assignments (e.g.version = '1.0.0'
) take precedence over the method calls likeinstruction
andoptionalImport
. This allows for instance to both override the version of a bundle in a concrete configuration and making use of it in a more general configuration:
platform { bnd(group:'org.standardout') {// packages with version number (uses the version provided further below) instruction'Export-Package',"org.*;version=$version" } bnd(group:'org.standardout',name:'bnd-platform',version:'0.1') {// override the version version='0.1.0.RELEASE' }}
Starting with version 0.3 it is possible to override all bundle configurations at once. This also applies to dependencies that already are an OSGi bundle. You can use any instructions you would use insidebnd to apply them to all dependencies in a call tooverride:
platform { override {// JUnit optional everywhere - so we can exclude it from products optionalImport'junit.framework.*','org.junit.*' }}
Many third party libraries have optional dependencies being specified by using<optional>true</optional>
in a pom.xml file.
For example theRetrofit library has android as optional dependency:
<dependency> <groupId>com.google.android</groupId> <artifactId>android</artifactId> <optional>true</optional></dependency>
Since it is unlikely to happen that you want an android dependency in your OSGi application it can be marked as optional:
plugin('com.squareup.retrofit2:retrofit:2.4.0'){bnd {optionalImport'android.os'optionalImport'android.net' }}
Unfortunately the information about the<optional>true</optional>
instruction is lost during a Gradle build because Gradle's dependency management does not support it yet. (FYIhttps://docs.gradle.org/4.6/release-notes.html#support-for-optional-dependencies-in-pom-consumption)So the bnd-platform plug-in cannot add the optionalImport instructions automatically, yet.
For theRetrofit library it is fairly easy to write the optionalImport instructions like above, but there are other libraries, which have plenty of these<optional>true</optional>
instruction.
In order to generate the list of potential optional imports apotentialOptionalImports task has been created. This task creates apotentialOptionalImports.txt file, which lists potential optional imports of each and every bundle, which is created during a build.
Even though setting up an extensive platform of OSGi bundles can be done quite fast usingbnd-platform, in many cases additional configuration is necessary. It comes naturally that it should be possible to reuse and share those configurations.
With Gradle you can useapply from: 'someScript.gradle'
to include other build scripts. In those you can define dependencies, bnd configuration or remote repositories like you would do in the main build script.
An alternative is using thegradle-include-plugin which allows you to include specific methods from an external script and thus provide parameters to the include. In context ofbnd-platform it often makes sense to provide a version number as parameter. See thesample project for some nice examples, e.g. thelogging orgeotools platform modules defined there. Using theinclude plugin these modules are applied to the sample build like this:
include {from('modules/logging.groovy') {slf4jAndLogback'1.7.2','1.0.10'// slf4j and logback with given versions}from('modules/geotools.groovy') {geotools()// include geotools with default modules and version}}
We have created a repository on GitHub to collect the platform modules and configurations we use for our projects and you are welcome to fork and contribute:shared-platform. The repository is designed to be used with thegradle-include-plugin and to enable the sharing of configurations without imposing them on you - just include the configuration that makes sense for you and augment it with your own.
You can combine dependencies to an Eclipse feature. The feature will contain the dependencies, as well as their transitive dependencies. You can use the features for more fine-grained control in you target platform or in a feature based Eclipse product. The platform feature contains all features you define. If fetching sources is enabled, a matching source feature will be created for each feature.
A feature includes all plugins (bundles) defined in its context, here an example:
platform {// define a featurefeature(id:'platform.restclient',name:'REST client dependencies',version:'1.0.0') {// define what's in the featureplugin'org.codehaus.groovy.modules.http-builder:http-builder:0.6', {// exclude this transitive dependencyexcludegroup:'net.sourceforge.nekohtml',module:'nekohtml'}plugin'commons-io:commons-io:2.4'// include a feature that is defined elsewhere given its IDincludes<<'platform.geotools'}}
Providing a feature ID is mandatory, butname andversion may be omitted (the version defaults to the platform feature version).plugin
andbundle
can be used synonymously inside thefeature
block, but when usingbundle
you may experience problems when used in inner closures.
The feature may include also other required features. See an example below how to define such dependencies:
platform {// define a feature feature(id:'platform.restclient',name:'REST client dependencies',version:'1.0.0') { requires (featureName:"required.feature.name",version:"1.0.0",match:"greaterOrEqual" )//... the rest of the feature definition }}
You can easily add local JARs to the platform.If the JAR is not an OSGi bundle yet, you have add it on its own and at least providesymbolicName andversion:
platform {bundle file('someLibrary.jar'), {bnd {version='1.0.0'// mandatorysymbolicName='com.example.library.some'// mandatorybundleName='Some Library'instruction'Export-Package',"com.example.library.some.*;version=$version"}}// depends on groovybundle'org.codehaus.groovy:groovy:1.8.5'}
As in the example above, you should make sure to add additional dependencies that might be needed by the JAR. Please note that for a JARfilename.jar
sources provided in afilename-sources.jar
will be wrapped automatically in a corresponding source bundle.
JARs that are already OSGi bundles you can include en masse, and without the need for additional configuration, for example:
platform {// all bundles in a directorybundle fileTree(dir:'lib') {include'*.jar'}// specific bundlesbundle files('someBundle.jar','someOtherBundle.jar')}
When resolving theplatform configuration withbnd-platform,Gradle will only include one version for each dependency. This is intentional as even though OSGi is designed to deal with issues such as multiple versions of a bundle, often it will lead to problems - namely package uses conflicts (please note that you can get package uses conflicts even with only one version of each bundle, as some may conflict with packages provided by the system bundle).
However, if there is the need to have multiple versions of a bundle, these are your options:
- use multiplebnd-platform builds, each will resolve its dependencies independent of the others
- add dependencies to theplatformaux configuration - they will be added in addition to the resolved platform configuration (but w/o their transitive dependencies)
- add the additional versions as local dependencies
Following is an example using theplatformaux configuration:
dependencies {// need old version (pre 4) of asm for some bundles platformaux'asm:asm:3.3.1'}
Sometimes it is necessary to create a bundle out of multiple JARs, most often due to class loading issues. TheGeotools library is a famous example of that. It usesJava SPI as extension mechanism, which does only recognize extensions from the same class loader. One waysuggested to cope with this is creating a monolithic bundle that includes all the needed geotools modules (which I prefer because with Geotools otherwise you have different bundles partly exporting the same packages). Doing this withbnd-platform is quite easy:
platform {def geotoolsVersion='10.4'// define the merged bundlemerge {// the match closure is applied to all dependencies/artifacts encountered// if true, an artifact is included in the bundlematch {// merge all artifacts in org.geotools group, but not gt-opengisit.group=='org.geotools'&& it.name!='gt-opengis'}bnd {symbolicName='org.geotools'bundleName='Geotools'version= geotoolsVersioninstruction'Export-Package',"org.geotools.*;version=$version"instruction'Private-Package','*'}}// add geotools modules as dependenciesbundle"org.geotools:gt-shapefile:$geotoolsVersion"// etc.}
Providing thesymbolicName andversion as part of thebnd configuration is mandatory for merged bundles, as the information from the original JARs' manifests is discarded.Please note that the example above misses the Maven repositories needed to actually retrieve those artifacts, see the sample project for amore complete example.
If you usematch { ... }
to merge bundles, it is called for each artifact. The artifact can be accessed via the variableit. An artifact has the following properties that can be useful to check against in amatch:
- group - the group name of the artifact, e.g.'org.geotools'
- name - the name (artifact ID) of the artifact, e.g.'gt-shapefile'
- version - the version of the artifact
- file - the local or downloaded file of the artifact, as File object
As alternative tomatch or in combination with it you can add bundles to merge viabundle orinclude. The syntax is the same as when adding dependencies. However, usinginclude you just specify an artifact to be included if it is a dependency defined somewhere else, it does not add it as dependency.
platform {merge {bundle'someGroup:someArtifact:1.0.0'// also added as dependencyincludegroup:'someGroup',name:'someOtherArtifact'// not added as dependencybnd {...}}}
You can supply parameters tomerge, currently those are:
- failOnDuplicate - fail if the same file occurs in more than one JAR (not taking into account the manifest) (default:true)
- collectServices - combines files in
META-INF/services
defining extensions via SPI (default:true)
You can specify them as named parameters, e.g.:
platform {merge(failOnDuplicate:false,collectServices:true) {...}}
Via the platform extension there are several settings you can provide:
- fetchSources - if sources for external dependencies should be fetched and source bundles created (default:true)
- updateSiteDir - the directory the generated p2 repository is written to (default:
new File(buildDir, 'updatesite')
) - updateSiteZipFile - the target file for the zipped p2 repository (default:
new File(buildDir, 'updatesite.zip')
) - appendUpdateSite - if any the generated p2 repository should be appended to the one that already exists inupdateSiteDir (default:
false
) - createFeatureVersionFiles - if for the created update site, a version file should be created per feature, e.g.
<feature-id>_versions.json
, that includes information on the versions of the feature available in the p2 repository (default:false
) - eclipseHome - File object pointing to the directory of a local Eclipse installation to be used for generating the p2 repository (default:
null
) - eclipseMirror - Eclipse download URLs to be used when no local installation is provided viaeclipseHome. Since version 3 uses an Eclipse 2023-09 mirror by default.
- downloadsDir - the directory to store the downloaded Eclipse installation on local, this works ifeclipseHome is not specified. (default:
new File(buildDir, 'eclipse-downloads')
) - generatePlatformFeature - States if a general feature should be created. In case custom features are generated you might not want to have an additional "generated platform feature" besides your own features. (default:true)
- featureId - the identifier of the feature including the platform bundles that will be available in the created update site (default:'platform.feature')
- featureName - the name of the feature including the platform bundles that will be available in the created update site (default:'Generated platform feature')
- featureVersion - the version number for the feature including the platform bundles that will be available in the created update site (defaults to the project version)
- featureProvider - the provider name to be used for features (default:'Generated with bnd-platform')
- categoryId - the identifier of the feature's category (default:'platform')
- categoryName - the name of the feature's category (default:'Target platform')
- determineImportVersions - automatically determine package import versions (default:
false
) - importVersionStrategy = global strategy for import versions (default:
MAJOR
) - importIgnorePackages - set of packages to ignore when analyzing packages of dependencies to determine package import versions
- defaultQualifier - the default version qualifier to use for wrapped bundles. If a qualifier is already
- present the default will be appended, separated by a dash. Does by default not apply to file based dependencies (default:'autowrapped')
- useBndHashQualifiers - if a hash calculated from the bnd configuration should be used as version qualifier for wrapped bundles. It replaces the default qualifier where applicable (default:
true
) - useFeatureHashQualifiers - if a hash based on the feature content should be appended as qualifier to feature versions (default:
true
) - hashCalculator - hash calculator for determining the hash qualifier from a bundle's bnd configuration, can be replaced by a custom closure (default:
ADLER32
) - hashQualifierMap - for bundles/features that would have hash based qualifiers, map those to qualifiers that ensure a specific behavior. The default qualifier map is based on version history persisted to a file and date based qualifiers to ensure increasing version qualifiers (can be important for update mechanisms). To use the default qualifier map, simply provide a file or file path for the version history to be stored in (will be stored as Json).
- defaultQualifierMap.prefix - the prefix to use for version qualifiers provided via the default qualifier map (default:
'i'
) - defaultQualifierMap.baseDate - configures the base level for time based qualifiers generated by the default qualifier map. Valid values are
YEAR
,MONTH
,DAY
,MINUTE
,SECOND
,MILLISECOND
(default:MONTH
) - auxVersionedSymbolicNames - states if the symbolic names for bundles created via the platformaux configuration should be adapted to include the version number. This is useful when dealing with systems that have problems when there actually are bundles with the same name but different versions. An example is Eclipse RCP plugin-based products - they can include only one version of a bundle with the same name. (default:
false
) - removeSignaturesFromWrappedBundles - if signatures should be removed from signed jars that are wrapped using bnd (default:
true
) - addBndPlatformManifestHeaders - ifbnd-platform specific manifest headers should be added. Adds information to the manifest that allows reconstructing the original Maven artifact identifiers (default:
false
) - extractPomInformation - if additional configuration information from POM is desired (default:
true
)
For example:
platform {fetchSources=falsefeatureVersion='3.1.0'eclipseHome=newFile('/opt/eclipse')eclipseMirror='http://myeclipsedownload.com/eclipse.tar.gz'}
This software is licensed under theApache License, Version 2.0.
Unless required by applicable law or agreed to in writing, softwaredistributed under the License is distributed on an "AS IS" BASIS,WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.See the License for the specific language governing permissions andlimitations under the License.
About
Build OSGi bundles and Eclipse Update Sites from existing JARs, e.g. from Maven repositories (Plugin for Gradle)