Skip to content

Add OSGi bundle metadata#2310

Open
pavelhoral wants to merge 3 commits intomozilla:masterfrom
pavelhoral:feat-osgi
Open

Add OSGi bundle metadata#2310
pavelhoral wants to merge 3 commits intomozilla:masterfrom
pavelhoral:feat-osgi

Conversation

@pavelhoral
Copy link

This is a different attempt on #1278.

  • I have added metadata for rhino, rhino-engine and rhino-tools as that is what I need. I am not sure if the same should be done for rhino-xml.
  • The imports and exports to some degree mirror what is being declared in module-info.java.
  • I took inspiration from Groovy metadata and Apache ServiceMix bundle for old RhinoJS. However I am not 100 % sure everything is needed (especially the Service Loader stuff for the RegExpLoader).

Disclamer: I got help from Claude Code, but I have verified that the bundles correctly load and work in my OSGi environment. I have tested the OSGi bundles on rhino_1_9_1 branch - would be nice to get this backported as OSGi does not like major version changes).

@aardvark179
Copy link
Contributor

So, if we want to export OSGi meta-data alongside our JPMS module
Info it feels like we should either ensure that the OSGi data is generated from the JPMS data, or that we have a test ensuring the two remain in sync.

I thought OSGi had even added support for JPMS, but I could be mistaken-remembering, it had been a while since I messed with this stuff.

@pavelhoral
Copy link
Author

pavelhoral commented Feb 21, 2026

Trying to generate anything would mean a bigger change in the build scripts, probably switching to https://plugins.gradle.org/plugin/biz.aQute.bnd.builder. I can try doing that, if it has a chance of being potentially approved.

@pavelhoral
Copy link
Author

Played with BND a bit and I am pretty convinced that the only way to keep module-info.java and OSGi metadata in sync is to actually let BND generate both and ideally using its annotation system (https://bnd.bndtools.org/chapters/230-manifest-annotations.html).

@pavelhoral pavelhoral force-pushed the feat-osgi branch 2 times, most recently from 7ce4855 to 1e7bc14 Compare February 23, 2026 00:07
@pavelhoral
Copy link
Author

I have added metadata for rhino-xml and fixed cardinality and resolution attributes.

I have also wrote unit test that checks what can be checked. Ideally this should be accompanied with bnd verify to make sure the imports are complete, but that is a task for another day (and maybe you will find more convenient to create the metadata via bnd).

@pavelhoral
Copy link
Author

I have prepared feature branch for backporting this to 1.9 - https://github.com/pavelhoral/rhino/tree/feat-osgi-1_9.

@gbrail
Copy link
Collaborator

gbrail commented Feb 28, 2026

Does anyone else have experience with BND? If it's a straightforward Gradle plugin, and it lets us generate both Java module config and Osgi metadata from a single source, I'm all for it if you're willing to put together a PR.

@pavelhoral
Copy link
Author

pavelhoral commented Mar 6, 2026

I have (with help from Claude Code and a lot of double-checking) switched OSGi metadata and JPMS module info build to BND plugin.

Generated change summary:

This commit replaces hand-written OSGi manifest attributes and module-info.java files with the bnd Gradle plugin (biz.aQute.bnd.builder), which generates both OSGi and JPMS metadata from a single source of truth in each module's build.gradle.

What changed

Added bnd plugin (biz.aQute.bnd.gradle:7.2.1) to buildSrc/build.gradle and applied biz.aQute.bnd.builder to all 5 published modules: rhino, rhino-engine, rhino-tools, rhino-xml, rhino-kotlin.

Deleted all module-info.java files (8 files total -- main and test source sets):

  • rhino/src/main/java/module-info.java
  • rhino-engine/src/main/java/module-info.java + test
  • rhino-tools/src/main/java/module-info.java + test
  • rhino-xml/src/main/java/module-info.java + test
  • rhino-kotlin/src/main/java/module-info.java
  • testutils/src/main/java/module-info.java

Deleted OsgiMetadataTest.java -- no longer needed since bnd generates correct metadata by construction.

Removed osgiVersion helper and javaModuleVersion from rhino.library-conventions.gradle -- bnd derives Bundle-Version automatically from the project version.

How bnd generates metadata

Each module's build.gradle now contains a jar { bundle { bnd(...) } } block with:

  • OSGi headers: Bundle-SymbolicName, -exportcontents, Provide-Capability, Require-Capability, etc.
  • JPMS instructions: -jpms-module-info, -jpms-module-info-options, -jpms-module-info-ee -- bnd uses these plus the OSGi capability model to generate module-info.class directly in the JAR.
  • Import-Package is auto-calculated by bnd from bytecode analysis, with explicit overrides only where needed (e.g., resolution:=optional for android.os, java.beans, jdk.dynalink*, javax.swing*, java.awt*, org.jline.*).

Key design decisions

  • -exportcontents (not Export-Package) is used so bnd doesn't filter jar contents -- it only sets the header.
  • access=0 prevents bnd from generating open modules (which it defaults to when osgi.extender capabilities are present).
  • register:= directives on Provide-Capability tell bnd which implementation class provides each service, enabling provides ... with ... in the generated module-info.
  • Attribute-based Require-Capability format (e.g., osgi.serviceloader;osgi.serviceloader=X) instead of filter-based format -- required by bnd's JPMS plugin to generate correct uses directives.

Improvements over the previous approach

  • Single source of truth: OSGi and JPMS metadata are derived from the same bnd instructions, eliminating drift between manifest and module-info.
  • Correct Import-Package: bnd calculates imports from bytecode, catching packages the hand-written manifests missed or got wrong.
  • Optional imports: android.os, java.beans, jdk.dynalink* (rhino), javax.swing*, java.awt* (rhino-tools) are now correctly marked resolution:=optional in OSGi and requires static in JPMS, improving support for headless/minimal JREs.
  • rhino-kotlin OSGi support: Previously had no OSGi metadata; now fully configured with bnd.
  • Bug fix: rhino-xml had Bundle-SymbolicName: org.mozilla.rhino.engine (copy-paste error) -- fixed to org.mozilla.rhino.xml.

Known differences from previous metadata

  • requires java.base no longer shows mandated flag -- this is cosmetic; bnd cannot set this flag, but java.base is always implicitly required regardless.
  • contains directives appear in JPMS output for non-exported packages -- bnd lists all packages, while javac only listed compiled ones. These are informational only and have no runtime effect.
  • java.desktop changed from transitive to static in both rhino and rhino-tools -- this makes java.desktop optional at runtime, allowing Rhino to load on minimal JREs without the desktop module (as long as no AWT/Swing code paths are hit).

Verification

Helper scripts are included in scripts/ for comparing metadata before and after:

  • scripts/dump-jpms.sh -- dumps JPMS module descriptors via jar --describe-module
  • scripts/dump-osgi.sh -- dumps OSGi headers via bnd print

I have intentionally added this change as a separate commit from my previous changes that only added OSGi metadata and did not touch module-info.java. That way you can compare the approaches and decide what you like more.

Generated script for extracting JPMS and OSGi metadata:

JPMS differences
diff --git a/jpms-before.txt b/jpms-after.txt
index 0622aa2..dd50cca 100644
--- a/jpms-before.txt
+++ b/jpms-after.txt
@@ -1,5 +1,5 @@
 === rhino ===
-org.mozilla.rhino
+org.mozilla.rhino@2.0.0.SNAPSHOT
 exports org.mozilla.classfile
 exports org.mozilla.javascript
 exports org.mozilla.javascript.annotations
@@ -13,52 +13,62 @@ exports org.mozilla.javascript.optimizer
 exports org.mozilla.javascript.serialize
 exports org.mozilla.javascript.typedarrays
 exports org.mozilla.javascript.xml
-requires java.base mandated
+requires java.base
 requires java.compiler
-requires java.desktop transitive
-requires jdk.dynalink
+requires java.desktop static
+requires jdk.dynalink static
 uses org.mozilla.javascript.NullabilityDetector
 uses org.mozilla.javascript.RegExpLoader
 uses org.mozilla.javascript.config.RhinoPropertiesLoader
 uses org.mozilla.javascript.xml.XMLLoader
 provides org.mozilla.javascript.RegExpLoader with org.mozilla.javascript.regexp.RegExpLoaderImpl
+contains org.mozilla.javascript.dtoa
+contains org.mozilla.javascript.json
+contains org.mozilla.javascript.lc
+contains org.mozilla.javascript.lc.member
+contains org.mozilla.javascript.lc.type.impl
+contains org.mozilla.javascript.lc.type.impl.factory
 contains org.mozilla.javascript.regexp
+contains org.mozilla.javascript.resources
+contains org.mozilla.javascript.v8dtoa
 
 === rhino-engine ===
-org.mozilla.rhino.engine@2.0.0-SNAPSHOT
+org.mozilla.rhino.engine@2.0.0.SNAPSHOT
 exports org.mozilla.javascript.engine
-requires java.base mandated
+requires java.base
 requires java.scripting transitive
 requires org.mozilla.rhino transitive
 provides javax.script.ScriptEngineFactory with org.mozilla.javascript.engine.RhinoScriptEngineFactory
 
 === rhino-tools ===
-org.mozilla.rhino.tools@2.0.0-SNAPSHOT
+org.mozilla.rhino.tools@2.0.0.SNAPSHOT
 exports org.mozilla.javascript.tools
 exports org.mozilla.javascript.tools.debugger
 exports org.mozilla.javascript.tools.jsc
 exports org.mozilla.javascript.tools.shell
-requires java.base mandated
-requires java.desktop transitive
+requires java.base
+requires java.desktop static
 requires org.jline.reader static
 requires org.jline.terminal static
 requires org.mozilla.rhino transitive
 uses org.mozilla.javascript.tools.ConsoleProvider
 provides org.mozilla.javascript.tools.ConsoleProvider with org.mozilla.javascript.tools.shell.BasicConsoleProvider org.mozilla.javascript.tools.shell.JLineConsoleProvider
+contains org.mozilla.javascript.tools.debugger.treetable
+contains org.mozilla.javascript.tools.resources
 main-class org.mozilla.javascript.tools.shell.Main
 
 === rhino-xml ===
-org.mozilla.javascript.xml@2.0.0-SNAPSHOT
+org.mozilla.javascript.xml@2.0.0.SNAPSHOT
 exports org.mozilla.javascript.xmlimpl
-requires java.base mandated
+requires java.base
 requires java.xml transitive
 requires org.mozilla.rhino transitive
 provides org.mozilla.javascript.xml.XMLLoader with org.mozilla.javascript.xmlimpl.XMLLoaderImpl
 
 === rhino-kotlin ===
-org.mozilla.rhino.kotlin@2.0.0-SNAPSHOT
+org.mozilla.rhino.kotlin@2.0.0.SNAPSHOT
 exports org.mozilla.kotlin
-requires java.base mandated
+requires java.base
 requires kotlin.metadata.jvm
 requires kotlin.stdlib
 requires org.mozilla.rhino
OSGi differences (from what my previous commit was generating)
diff --git a/osgi-before.txt b/osgi-after.txt
index 9f5e77b..4a72443 100644
--- a/osgi-before.txt
+++ b/osgi-after.txt
@@ -1,7 +1,36 @@
 === rhino ===
 Bundle-SymbolicName                     org.mozilla.rhino
-Bundle-Version                          2.0.0
+Bundle-Version                          2.0.0.SNAPSHOT
 DynamicImport-Package                   *
+Import-Package
+  android.os                             {resolution:=optional}
+  java.beans                             {resolution:=optional}
+  java.io                                
+  java.lang                              
+  java.lang.annotation                   
+  java.lang.invoke                       
+  java.lang.ref                          
+  java.lang.reflect                      
+  java.math                              
+  java.net                               
+  java.nio.charset                       
+  java.security                          
+  java.text                              
+  java.time                              
+  java.time.format                       
+  java.time.temporal                     
+  java.util                              
+  java.util.concurrent                   
+  java.util.concurrent.atomic            
+  java.util.concurrent.locks             
+  java.util.function                     
+  java.util.jar                          
+  java.util.regex                        
+  java.util.stream                       
+  jdk.dynalink                           {resolution:=optional}
+  jdk.dynalink.linker                    {resolution:=optional}
+  jdk.dynalink.linker.support            {resolution:=optional}
+  jdk.dynalink.support                   {resolution:=optional}
 Export-Package
   org.mozilla.classfile                  {version=2.0.0}
   org.mozilla.javascript                 {version=2.0.0}
@@ -17,70 +46,130 @@ Export-Package
   org.mozilla.javascript.typedarrays     {version=2.0.0}
   org.mozilla.javascript.xml             {version=2.0.0}
 Provide-Capability
-  osgi.serviceloader                     {osgi.serviceloader=org.mozilla.javascript.RegExpLoader}
+  osgi.serviceloader                     {osgi.serviceloader=org.mozilla.javascript.RegExpLoader, register:=org.mozilla.javascript.regexp.RegExpLoaderImpl}
 Require-Capability
   osgi.ee                                {filter:=(&(osgi.ee=JavaSE)(version>=11))}
   osgi.extender                          {filter:=(osgi.extender=osgi.serviceloader.processor)}
   osgi.extender                          {filter:=(osgi.extender=osgi.serviceloader.registrar)}
-  osgi.serviceloader                     {filter:=(osgi.serviceloader=org.mozilla.javascript.RegExpLoader)}
-  osgi.serviceloader                     {filter:=(osgi.serviceloader=org.mozilla.javascript.NullabilityDetector), resolution:=optional}
-  osgi.serviceloader                     {filter:=(osgi.serviceloader=org.mozilla.javascript.config.RhinoPropertiesLoader), resolution:=optional}
-  osgi.serviceloader                     {filter:=(osgi.serviceloader=org.mozilla.javascript.xml.XMLLoader), resolution:=optional}
+  osgi.serviceloader                     {osgi.serviceloader=org.mozilla.javascript.RegExpLoader}
+  osgi.serviceloader                     {osgi.serviceloader=org.mozilla.javascript.NullabilityDetector, resolution:=optional}
+  osgi.serviceloader                     {osgi.serviceloader=org.mozilla.javascript.config.RhinoPropertiesLoader, resolution:=optional}
+  osgi.serviceloader                     {osgi.serviceloader=org.mozilla.javascript.xml.XMLLoader, resolution:=optional}
 
 === rhino-engine ===
 Bundle-SymbolicName                     org.mozilla.rhino.engine
-Bundle-Version                          2.0.0
+Bundle-Version                          2.0.0.SNAPSHOT
 Import-Package
+  java.io                                
+  java.lang                              
+  java.lang.invoke                       
+  java.lang.reflect                      
+  java.nio.charset                       
+  java.util                              
   javax.script                           
-  org.mozilla.javascript                 {version=2.0.0}
+  org.mozilla.javascript                 {version=[2.0,3)}
 Export-Package
   org.mozilla.javascript.engine          {version=2.0.0}
 Provide-Capability
-  osgi.serviceloader                     {osgi.serviceloader=javax.script.ScriptEngineFactory}
+  osgi.serviceloader                     {osgi.serviceloader=javax.script.ScriptEngineFactory, register:=org.mozilla.javascript.engine.RhinoScriptEngineFactory}
 Require-Capability
   osgi.ee                                {filter:=(&(osgi.ee=JavaSE)(version>=11))}
   osgi.extender                          {filter:=(osgi.extender=osgi.serviceloader.registrar)}
 
 === rhino-tools ===
 Bundle-SymbolicName                     org.mozilla.rhino.tools
-Bundle-Version                          2.0.0
+Bundle-Version                          2.0.0.SNAPSHOT
 Import-Package
-  org.jline.reader                       {resolution:=optional}
-  org.jline.terminal                     {resolution:=optional}
-  org.mozilla.javascript                 {version=2.0.0}
-  org.mozilla.javascript.commonjs.module {version=2.0.0}
-  org.mozilla.javascript.commonjs.module.provider {version=2.0.0}
-  org.mozilla.javascript.config          {version=2.0.0}
-  org.mozilla.javascript.debug           {version=2.0.0}
-  org.mozilla.javascript.optimizer       {version=2.0.0}
-  org.mozilla.javascript.serialize       {version=2.0.0}
+  java.awt                               {resolution:=optional}
+  java.awt.event                         {resolution:=optional}
+  java.awt.geom                          {resolution:=optional}
+  java.io                                
+  java.lang                              
+  java.lang.invoke                       
+  java.lang.ref                          
+  java.lang.reflect                      
+  java.net                               
+  java.nio.charset                       
+  java.security                          
+  java.security.cert                     
+  java.text                              
+  java.util                              
+  java.util.regex                        
+  javax.swing                            {resolution:=optional}
+  javax.swing.border                     {resolution:=optional}
+  javax.swing.event                      {resolution:=optional}
+  javax.swing.filechooser                {resolution:=optional}
+  javax.swing.table                      {resolution:=optional}
+  javax.swing.text                       {resolution:=optional}
+  javax.swing.tree                       {resolution:=optional}
+  org.jline.reader                       {version=[3.30,4), resolution:=optional}
+  org.jline.terminal                     {version=[3.30,4), resolution:=optional}
+  org.mozilla.javascript                 {version=[2.0,3)}
+  org.mozilla.javascript.commonjs.module {version=[2.0,3)}
+  org.mozilla.javascript.commonjs.module.provider {version=[2.0,3)}
+  org.mozilla.javascript.config          {version=[2.0,3)}
+  org.mozilla.javascript.debug           {version=[2.0,3)}
+  org.mozilla.javascript.optimizer       {version=[2.0,3)}
+  org.mozilla.javascript.serialize       {version=[2.0,3)}
 Export-Package
   org.mozilla.javascript.tools           {version=2.0.0}
   org.mozilla.javascript.tools.debugger  {version=2.0.0}
   org.mozilla.javascript.tools.jsc       {version=2.0.0}
   org.mozilla.javascript.tools.shell     {version=2.0.0}
 Provide-Capability
-  osgi.serviceloader                     {osgi.serviceloader=org.mozilla.javascript.tools.ConsoleProvider}
+  osgi.serviceloader                     {osgi.serviceloader=org.mozilla.javascript.tools.ConsoleProvider, register:=org.mozilla.javascript.tools.shell.BasicConsoleProvider}
+  osgi.serviceloader                     {osgi.serviceloader=org.mozilla.javascript.tools.ConsoleProvider, register:=org.mozilla.javascript.tools.shell.JLineConsoleProvider}
 Require-Capability
   osgi.ee                                {filter:=(&(osgi.ee=JavaSE)(version>=11))}
   osgi.extender                          {filter:=(osgi.extender=osgi.serviceloader.processor)}
   osgi.extender                          {filter:=(osgi.extender=osgi.serviceloader.registrar)}
-  osgi.serviceloader                     {filter:=(osgi.serviceloader=org.mozilla.javascript.tools.ConsoleProvider), cardinality:=multiple}
+  osgi.serviceloader                     {osgi.serviceloader=org.mozilla.javascript.tools.ConsoleProvider, cardinality:=multiple}
 
 === rhino-xml ===
-Bundle-SymbolicName                     org.mozilla.rhino.engine
-Bundle-Version                          2.0.0
+Bundle-SymbolicName                     org.mozilla.rhino.xml
+Bundle-Version                          2.0.0.SNAPSHOT
 Import-Package
-  javax.script                           
-  org.mozilla.javascript                 {version=2.0.0}
-  org.mozilla.javascript.xml             {version=2.0.0}
+  java.io                                
+  java.lang                              
+  java.lang.invoke                       
+  java.util                              
+  java.util.concurrent                   
+  javax.xml.parsers                      
+  javax.xml.transform                    
+  javax.xml.transform.dom                
+  javax.xml.transform.stream             
+  org.mozilla.javascript                 {version=[2.0,3)}
+  org.mozilla.javascript.xml             {version=[2.0,3)}
+  org.w3c.dom                            
+  org.xml.sax                            
 Export-Package
   org.mozilla.javascript.xmlimpl         {version=2.0.0}
 Provide-Capability
-  osgi.serviceloader                     {osgi.serviceloader=org.mozilla.javascript.xml.XMLLoader}
+  osgi.serviceloader                     {osgi.serviceloader=org.mozilla.javascript.xml.XMLLoader, register:=org.mozilla.javascript.xmlimpl.XMLLoaderImpl}
 Require-Capability
   osgi.ee                                {filter:=(&(osgi.ee=JavaSE)(version>=11))}
   osgi.extender                          {filter:=(osgi.extender=osgi.serviceloader.registrar)}
 
 === rhino-kotlin ===
+Bundle-SymbolicName                     org.mozilla.rhino.kotlin
+Bundle-Version                          2.0.0.SNAPSHOT
+Import-Package
+  java.lang                              
+  java.lang.annotation                   
+  java.lang.invoke                       
+  java.lang.reflect                      
+  java.util                              
+  java.util.function                     
+  java.util.stream                       
+  kotlin                                 
+  kotlin.metadata                        
+  kotlin.metadata.jvm                    
+  org.mozilla.javascript                 {version=[2.0,3)}
+Export-Package
+  org.mozilla.kotlin                     {version=2.0.0}
+Provide-Capability
+  osgi.serviceloader                     {osgi.serviceloader=org.mozilla.javascript.NullabilityDetector, register:=org.mozilla.kotlin.KotlinNullabilityDetector}
+Require-Capability
+  osgi.ee                                {filter:=(&(osgi.ee=JavaSE)(version>=11))}
+  osgi.extender                          {filter:=(osgi.extender=osgi.serviceloader.registrar)}

@gbrail
Copy link
Collaborator

gbrail commented Mar 7, 2026

Thanks! Two things:

First of all, my AI found two regressions in the module file when I asked it to compare before and after and I think it's right:

diff --git a/rhino-tools/build.gradle b/rhino-tools/build.gradle
index 4126ce71d..385029e6e 100644
--- a/rhino-tools/build.gradle
+++ b/rhino-tools/build.gradle
@@ -57,7 +57,7 @@ jar {
             '-jpms-module-info': 'org.mozilla.rhino.tools;access=0',
             '-jpms-module-info-options': [
                 'org.mozilla.rhino;transitive=true;static=false',
-                'java.desktop;transitive=false;static=true',
+                'java.desktop;transitive=true;static=false',
                 'org.jline.terminal;static=true',
                 'org.jline.reader;static=true',
                 'java.base;static=false',
diff --git a/rhino/build.gradle b/rhino/build.gradle
index 2efdfd1d8..6b0189437 100644
--- a/rhino/build.gradle
+++ b/rhino/build.gradle
@@ -47,8 +47,8 @@ jar {
             ].join(','),
             '-jpms-module-info': 'org.mozilla.rhino;access=0;modules="java.compiler,jdk.dynalink"',
             '-jpms-module-info-options': [
-                'java.desktop;transitive=false;static=true',
-                'jdk.dynalink;static=true;transitive=false',
+                'java.desktop;transitive=true;static=false',
+                'jdk.dynalink;static=false;transitive=false',
                 'java.compiler;static=false',
                 'java.base;static=false',
             ].join(','),

Second, how do we verify that the OSGi manifests are right?

The way we have it now, if someone makes a mistake in a module specification then tests will fail (because we run many of the tests in a module themselves). If someone makes a mistake in the OSGi bit in the future, how do we catch it? I think you had a previous version of this PR that had some tests.

@pavelhoral
Copy link
Author

pavelhoral commented Mar 8, 2026

Thanks! Two things:

First of all, my AI found two regressions in the module file when I asked it to compare before and after and I think it's right:

This is intentional as described in the summary I posted. I wanted the java.desktop and java.dynalink to be optional in OSGi (to be precise I don't think that packages from those modules should be required -> i.e. OSGi bundle won't start unless they are available). Claude warns me that the transitivity/non-static requirement would make the JPMS definition inconsistent with the OSGi metadata... so I would rather ask the opposite question: Is it really really necessary to have those module defined as static=false/transitive=true in JPMS?

Second, how do we verify that the OSGi manifests are right?

Properly testing would require testing in OSGi environment. I can try to create some Apache Felix test if that is required, but it probably wouldn't be that lightweight.

The way we have it now, if someone makes a mistake in a module specification then tests will fail (because we run many of the tests in a module themselves).

Here be careful, because my change removed the modules from tests AFAIK! There was a intermediate step, where I had both module-info.java and bnd generating the final module-info which felt very strange... I have asked why is that needed and the reason was "for tests as bnd is active only during packaging phase, but those tests can run in classpath mode perfectly fine". So if it was really a hard requirement to have modules in unit tests, I need to change this (I am just not sure if it would be possible to have bnd act in a way that we have no duplicate module definition).

Question here is whether you really want to run all unit tests under JPMS, or if you just want some lightweight integration smoke test that will verify modules work together.

@pavelhoral
Copy link
Author

pavelhoral commented Mar 8, 2026

Playing with it a bit more -> when the tests are run with JPMS, they fail as the jdk.dynalink module was not available. But that is expected if the test explicitly tests the optimization while not adding jdk.dynalink to module path. If only a specific feature needs that module it can remain optional (static). I would say this is a bug in the original module definition.

@pavelhoral
Copy link
Author

pavelhoral commented Mar 8, 2026

I have pushed one additional commit that adds integration tests for JPMS and OSGi. These tests are intentionally separate from the pre-existing unit tests as they depend on the built JAR files.

This setup makes much more sense to me (intentional testing of JPMS and OSGi) than what was done previously -> running all unit tests as JPMS modules.

Summary generated by Claude

JPMS and OSGi integration tests

Dedicated integration tests verify that Rhino's BND-generated module metadata works correctly in real JPMS and OSGi environments. These tests run against the built JARs (not classes directories) and are separated from regular unit tests so that ./gradlew test does not require JARs to be built first.

Running

./gradlew integrationTest

This is also included in ./gradlew check.

Build setup

A dedicated integrationTest source set in tests/build.gradle keeps integration tests separate from unit tests. The integrationTest task depends on all module JARs being built and passes their paths as system properties (e.g. rhino.jar, rhino-engine.jar).

Apache Felix (org.apache.felix.framework:7.0.5) is added as a dependency for OSGi testing.

JpmsModuleTest

Tests JPMS module behavior in-process using the ModuleLayer API. Each test creates a module layer from the built JARs via ModuleFinder.of() and Configuration.resolve(), then loads Rhino classes from the layer via reflection. The layer's classloader is parented by the platform classloader (not the system classloader) to isolate it from test classpath classes.

Tests:

  • rhinoExecutesJavaScriptInModuleLayer -- Rhino evaluates JavaScript within a JPMS module layer
  • scriptEngineDiscoveredViaServiceLoader -- ScriptEngineFactory is discovered via JPMS provides/uses service binding
  • xmlModuleWorksInModuleLayer -- typeof XML is "function" when rhino-xml is in the module layer
  • xmlModuleNotAvailableWithoutXmlInLayer -- typeof XML is "undefined" without rhino-xml

OsgiModuleTest

Tests OSGi bundle behavior by embedding an Apache Felix framework, installing the built JARs as bundles, and verifying resolution, activation, and cross-bundle wiring. The jdk.dynalink package is boot-delegated so bundles can access it without an explicit import. Framework-level osgi.serviceloader capabilities are provided so bundles resolve without requiring SPI Fly.

Tests:

  • rhinoBundleResolvesAndStarts -- bundle resolution and activation
  • rhinoExecutesJavaScriptInOsgi -- JavaScript execution within the bundle
  • rhinoEngineBundleResolvesAndStarts -- rhino-engine resolves with rhino
  • rhinoToolsBundleResolvesAndStarts -- rhino-tools resolves with rhino
  • rhinoXmlBundleResolvesAndStarts -- rhino-xml resolves with rhino
  • allBundlesResolveAndStartTogether -- all 4 bundles active simultaneously
  • xmlBundleExportsAreAccessible -- cross-bundle wiring works (XMLLoaderImpl implements XMLLoader across bundle boundaries)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants