Skip to content

fix(exposition): export internal package for OSGi resolution - #2415

Open
arimu1 wants to merge 2 commits into
prometheus:mainfrom
arimu1:fix/2395-osgi-expositionformats-internal
Open

fix(exposition): export internal package for OSGi resolution#2415
arimu1 wants to merge 2 commits into
prometheus:mainfrom
arimu1:fix/2395-osgi-expositionformats-internal

Conversation

@arimu1

@arimu1 arimu1 commented Aug 24, 2026

Copy link
Copy Markdown

Fixes #2395

Summary

  • Export io.prometheus.metrics.expositionformats.internal from both prometheus-metrics-exposition-formats and prometheus-metrics-exposition-formats-no-protobuf OSGi bundles
  • prometheus-metrics-exposition-textformats imports that package because PrometheusProtobufWriter loads PrometheusProtobufWriterImpl via Class.forName, but bnd kept the implementation package private in the formats bundles, so OSGi resolution failed

Approach

Used bnd _exportcontents to export the otherwise-private internal package while preserving existing generated* exports. This matches the metadata wiring fix suggested in the issue (option 1) without moving implementation classes across bundle boundaries.

Prove-it (before fix on HEAD)

prometheus-metrics-exposition-textformats
  Import-Package: ... io.prometheus.metrics.expositionformats.internal ...

prometheus-metrics-exposition-formats
  Export-Package: io.prometheus.metrics.expositionformats.generated*, ...
  (internal not exported)

After fix

Both formats bundles now export io.prometheus.metrics.expositionformats.internal, satisfying the textformats import.

Test plan

  • mvn -pl prometheus-metrics-exposition-formats,prometheus-metrics-exposition-formats-shaded,prometheus-metrics-exposition-textformats -am test (Java 25)
  • Inspected META-INF/MANIFEST.MF in built JARs to confirm internal package is exported and generated exports remain

Made with Cursor

prometheus-metrics-exposition-textformats imports
io.prometheus.metrics.expositionformats.internal because
PrometheusProtobufWriter loads the protobuf implementation via
Class.forName, but the exposition-formats bundles kept that package
private. Export it via bnd _exportcontents so OSGi can wire the two
bundles together.

Signed-off-by: arimu1 <19286898+arimu1@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes OSGi bundle resolution for prometheus-metrics-exposition-textformats by ensuring the implementation package io.prometheus.metrics.expositionformats.internal is exported from the exposition-formats bundles, matching the wiring expected by PrometheusProtobufWriter’s reflective Class.forName loading approach.

Changes:

  • Adds bnd (maven-bundle-plugin) instructions to export io.prometheus.metrics.expositionformats.internal via _exportcontents for OSGi wiring.
  • Keeps io.prometheus.metrics.expositionformats.generated* exported explicitly to preserve existing generated exports.
  • Applies the same export behavior to both the “formats” and “no-protobuf” bundle-producing modules.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
prometheus-metrics-exposition-formats/pom.xml Exports the internal implementation package for OSGi resolution while preserving generated package exports.
prometheus-metrics-exposition-formats-shaded/pom.xml Mirrors the same OSGi export instructions for the shaded formats bundle so consumers can resolve the internal package consistently.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@jaydeluca jaydeluca left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not super familiar with OSGI, so I would like to see some tests or other evidence that this solves the issue if possible

Comment on lines +70 to +75
<Export-Package>
io.prometheus.metrics.expositionformats.generated*;version="${project.version}"
</Export-Package>
<_exportcontents>
io.prometheus.metrics.expositionformats.internal;version="${project.version}"
</_exportcontents>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

${project.version} is 1.8.1-SNAPSHOT on snapshot builds, which isn't valid OSGi version
syntax, so bnd rewrites it to 1.8.1.SNAPSHOT. That sorts below a released 1.8.1. Built both
branches to confirm the delta:

main:   io.prometheus.metrics.expositionformats.generated;version="1.8.1"
branch: io.prometheus.metrics.expositionformats.generated;version="1.8.1.SNAPSHOT"

bnd already defaults exported-package versions to the cleaned project version, so on a release
build the attribute produces exactly what you'd get without it — it only changes behavior on
snapshots, and only for the worse. Dropping it restores 1.8.1 (verified locally).

Suggested change
<Export-Package>
io.prometheus.metrics.expositionformats.generated*;version="${project.version}"
</Export-Package>
<_exportcontents>
io.prometheus.metrics.expositionformats.internal;version="${project.version}"
</_exportcontents>
<Export-Package>
io.prometheus.metrics.expositionformats.generated*
</Export-Package>
<_exportcontents>
io.prometheus.metrics.expositionformats.internal
</_exportcontents>

Same applies to prometheus-metrics-exposition-formats-shaded/pom.xml

<_exportcontents>
io.prometheus.metrics.expositionformats.internal;version="${project.version}"
</_exportcontents>
</instructions>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While verifying this fix I hit a second manifest problem in this bundle that's pre-existing, but
it matters for whether #2395 is actually resolved.

maven-bundle-plugin runs before maven-shade-plugin, so bnd computes the manifest against
unrelocated classes. On both main and this branch the shaded bundle declares:

Import-Package: com.google.protobuf;version="[4.36,5)"   ← mandatory

…while the jar contains zero com/google/protobuf/ classes. I decompiled
internal/ProtobufUtil.class out of the built shaded jar and it references
io/prometheus/metrics/shaded/com_google_protobuf_4_36_0/*, as you'd expect. So the shaded
artifact demands a real protobuf-java bundle be installed, which rather defeats the point of
shading.

Why this blocks the original report: micrometer-registry-prometheus depends on
io.prometheus:prometheus-metrics-exposition-formats — the shaded artifact — at runtime scope. So
the bnd resolve in #2395 will get past internal after this fix and then stop at
com.google.protobuf unless protobuf-java happens to be in their resolution repository.

Second-order effect from this PR specifically: the newly exported internal package inherits
uses:="com.google.protobuf,...", propagating the bogus constraint to every consumer that wires
to it.

Adding !com.google.protobuf removes both the phantom import and the bad uses clause. Verified
against a local build:

Suggested change
</instructions>
<Import-Package>!com.google.protobuf,*</Import-Package>
</instructions>

Resulting manifest:

Export-Package: ...internal;version="1.8.1";uses:="io.prometheus.metrics.config,..."
                                              ↑ no com.google.protobuf
Import-Package: io.prometheus.metrics.config;version="[1.8,2)",...
                ↑ com.google.protobuf gone

with all that being said, are there some tests we can include to verify that all of this actually works and fixes the issue? I have limited experience with OSGi, so I defer to you and @anjeongkyun to hopefully weigh in

<artifactId>maven-bundle-plugin</artifactId>
<configuration>
<instructions>
<!-- Required for OSGi: textformats loads protobuf impl via Class.forName -->

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment is the right diagnosis, and it points at the half of the problem that this PR doesn't fix.

createProtobufWriter() is explicitly written to tolerate the impl being absent — it catches, returns null, and isAvailable() reports false (prometheus-metrics-exposition-textformats/.../PrometheusProtobufWriter.java:26-37). But bnd emits the import as mandatory:

Import-Package: ... io.prometheus.metrics.expositionformats.internal   ← no resolution:=optional

So a deployment that installs only prometheus-metrics-exposition-textformats — which is the
entire reason that artifact ships separately from the protobuf-carrying ones — still fails to
resolve after this change. Exporting internal fixes the case where the formats bundle is
present; it doesn't fix the case where it deliberately isn't.

One line in prometheus-metrics-exposition-textformats/pom.xml, in a maven-bundle-plugin
<instructions> block mirroring the ones you've added here:

<Import-Package>io.prometheus.metrics.expositionformats.internal;resolution:=optional,*</Import-Package>

I built this and confirmed the manifest:

Import-Package: io.prometheus.metrics.expositionformats.internal;resolution:=optional,
                io.prometheus.metrics.config;version="[1.8,2)",...

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also, the comment sits above <Export-Package> but explains <_exportcontents>

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.

expositionformats.internal is imported by exposition-textformats but exported by nothing, breaking OSGi resolution

3 participants