Skip to content

Multi release JAR support design discussion

BJ Hargrave edited this page Sep 6, 2022 · 8 revisions

Introduction

Bnd needs updates to support building of Multi-Release JARs.

Currently Bnd is unaware of MRJARs and complains about classes under META-INF/versions/. A related issue is that Bnd will include the module-info.class in the root of the bundle in the EE calculation when it really should not.

Background

Java 9 introduced the concept of multi-release jars to support changes in the Java class libraries as APIs are removed (e.g. to Jakarta EE) or changed (in support of better encapsulation.) So the goal of multi-release jars is not to support adding incremental API for use in later Java releases, but to support class implementations having to vary to support multiple versions of Java in the face of Java class library API changes. The same class may thus have multiple implementations where the proper variation is selected at runtime based upon the version of Java in use.

The public API exported by the classes in a multi-release JAR file must be exactly the same across versions

A bundle can contain resource in the main jar as well as in directories and embedded jars along the Bundle-Classpath. The OSGi specification calls these "containers". The main jar is a container and each entry of the Bundle-Classpath also a container. (Only the main jar supports Bundle-Classpath. Other containers on the Bundle-Classpath are not treated as OSGi bundles and thus not themselves processed for Bundle-Classpath.)

The OSGi specification allows for each container to be multi-release. Being a multi-release container is declared by the presence of Multi-Release: true in the manifest (META-INF/MANIFEST.MF) of the container. Each container in the bundle can be multi-release or not multi-released. So there can be a mixture.

When the main container is multi-release, the OSGi specification supports supplemental manifest information in the META-INF/versions/N/OSGI-INF/MANIFEST.MF resources. This allows the specification of different Import-Package and Require-Capability headers for the class implementation variations of a version. For example, a versioned variation can use some imported package or require some capability which is not needed by the variation in the base.

Changes

Bnd needs to be able to assemble the contents of a bundle and also calculate the main manifest and any necessary supplemental manifest. So Bnd must understand that the main jar and other Bundle-Classpath entries may be multi-release. If there is at least one multi-release container, the manifest calculation needs to take into account that the class variations may require a supplemental manifest in the main jar for a Java version. This would then cause the main jar to become multi-release even if it did not itself contains class variations or specify Multi-Release: true.

Bundle content

When assembling the content of a bundle, the Builder can be configured with a classpath from which to locate included classes. Classes can be included by using content instructions like -includepackage, -privatepackage, or Export-Package. Classes from an included package can be split across entries in the Builder's classpath. Bnd can then handle that different classpath entries can be for a different versions of Java and then place the different classpath entry contents in different META-INF/versions folders when Multi-Release: true is specified for the bundle. Bnd could also define a path entry directive to tell Bnd to place any packaged included from the path into a specified META-INF/versions folder. For example: for-java11.jar;multi-release=11.

The user can also manually place classes in the META-INF/versions folders using -includeresource with target folders.

Calculating Manifest

If there is at least one multi-release container in the bundle, then Bnd must compute both the main manifest as well as any necessary supplemental manifests for each META-INF/versions folder. So if the main bundle is for Java 8, and there are META-INF/versions/11 and META-INF/versions/17 folders found in a multi-release container, in additional to calculating the main manifest, Bnd will also need to calculate the supplemental manifests for 11 and 17 for inclusion in the bundle.

When calculating the main manifest, Bnd must ignore any META-INF/versions folders for Java versions higher than the Java version of the main jar. So if the main jar is for Java 11, then META-INF/versions folders for 11 or lower are to be included in the calculation but META-INF/versions folders for greater 11 are to be ignored.

Similarly, when calculating the supplemental manifest for a Java version, META-INF/versions folders greater than the target Java version are to be ignored.

When calculating the osgi.ee version for a bundle, module-info.class resources must not be considered. OSGi (consumer of osgi.ee) and JPMS (consumer of module-info.class) are mutually exclusive. While a jar can be built which is both a proper JPMS module and a proper OSGi bundle, the jar cannot be simultaneously loaded by both OSGi and JPMS. In the OSGi case, module-info.class is ignored by OSGi bundle class loaders. So the class version of module-info.class is not material to OSGi. In the JPMS case, osgi.ee is ignored by JPMS. So the osgi.ee version is not material to JPMS.

Complications

Plugins

Bnd supports AnalyzerPlugins and VerifierPlugins. These are used to generate things like DS xml, CDI integration, JPMS module-info.class, and even transforming classes (Eclipse Transformer). These plugins are called by the Analyzer when building the bundle.

In particular, the DS xml plugins are a complication. The DS plugin generates XML based upon inspection of the bundle's classes. When there is at least one multi-release container in the bundle, the DS plugin must be run over each of the known Java versions and the xml resources placed in the proper META-INF/versions folder (or the main jar for the base Java version).

After discussion, it was agreed that the DS plugin would run only on the main jar contents. This means that component implementation classes should not be versioned. If a component implementation needs access to a versioned implementation, that versioned implementation should be factored into a separate class which is versioned.

So, at this time, plugins will only be run on the main jar and not the versioned content.

Clone this wiki locally