From 2fa4e290d3fa89c5e8e97cb7eb4b2e9316b5b276 Mon Sep 17 00:00:00 2001 From: kalayciburak Date: Tue, 6 Oct 2026 12:46:33 +0300 Subject: [PATCH] fix: mark tracer initializer OSGi import optional prometheus-metrics-core referenced SpanContextSupplier, so bnd required io.prometheus.metrics.tracer.initializer even when Micrometer excludes that artifact. Mark the import optional, the same way exposition-textformats treats its internal package. Fixes #2516 Signed-off-by: kalayciburak --- prometheus-metrics-core/pom.xml | 27 +++++ .../exemplars/OsgiBundleManifestTest.java | 98 +++++++++++++++++++ 2 files changed, 125 insertions(+) create mode 100644 prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/exemplars/OsgiBundleManifestTest.java diff --git a/prometheus-metrics-core/pom.xml b/prometheus-metrics-core/pom.xml index 8d70fb7690..11d11348b3 100644 --- a/prometheus-metrics-core/pom.xml +++ b/prometheus-metrics-core/pom.xml @@ -69,4 +69,31 @@ test + + + + + org.apache.felix + maven-bundle-plugin + + + ${automatic.module.name} + + + io.prometheus.metrics.tracer.initializer;resolution:=optional,* + + + + + + + + manifest + + + + + + diff --git a/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/exemplars/OsgiBundleManifestTest.java b/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/exemplars/OsgiBundleManifestTest.java new file mode 100644 index 0000000000..89d2ea18c7 --- /dev/null +++ b/prometheus-metrics-core/src/test/java/io/prometheus/metrics/core/exemplars/OsgiBundleManifestTest.java @@ -0,0 +1,98 @@ +package io.prometheus.metrics.core.exemplars; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.InputStream; +import java.net.URI; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.CodeSource; +import java.util.ArrayList; +import java.util.List; +import java.util.jar.JarFile; +import java.util.jar.Manifest; +import org.junit.jupiter.api.Test; + +class OsgiBundleManifestTest { + + private static final String TRACER_INITIALIZER = "io.prometheus.metrics.tracer.initializer"; + + @Test + void tracerInitializerImportIsOptional() throws Exception { + Manifest manifest = loadBundleManifest(ExemplarSampler.class); + String header = manifest.getMainAttributes().getValue("Import-Package"); + assertThat(header).as("Import-Package").isNotBlank(); + String clause = requireClause(header, TRACER_INITIALIZER); + assertThat(clause) + .as("OSGi resolution of %s", TRACER_INITIALIZER) + .contains("resolution:=optional"); + } + + private static Manifest loadBundleManifest(Class type) throws Exception { + CodeSource codeSource = type.getProtectionDomain().getCodeSource(); + assertThat(codeSource).as("code source for %s", type.getName()).isNotNull(); + URI location = codeSource.getLocation().toURI(); + Path path = Path.of(location); + if (Files.isDirectory(path)) { + Path manifestFile = path.resolve("META-INF/MANIFEST.MF"); + assertThat(Files.exists(manifestFile)) + .as("bnd MANIFEST.MF for %s at %s", type.getName(), manifestFile) + .isTrue(); + try (InputStream in = Files.newInputStream(manifestFile)) { + return new Manifest(in); + } + } + try (JarFile jar = new JarFile(path.toFile())) { + Manifest manifest = jar.getManifest(); + assertThat(manifest).as("MANIFEST.MF in %s", path).isNotNull(); + return manifest; + } + } + + private static String requireClause(String header, String packageName) { + for (String rawClause : splitRespectingQuotes(header, ',')) { + String name = rawClause.trim(); + int separator = indexOfUnquoted(name, ';'); + if (separator >= 0) { + name = name.substring(0, separator).trim(); + } + if (packageName.equals(name)) { + return rawClause.trim(); + } + } + throw new AssertionError("missing Import-Package clause " + packageName); + } + + private static int indexOfUnquoted(String value, char target) { + boolean inQuote = false; + for (int i = 0; i < value.length(); i++) { + char c = value.charAt(i); + if (c == '"') { + inQuote = !inQuote; + } else if (!inQuote && c == target) { + return i; + } + } + return -1; + } + + private static List splitRespectingQuotes(String value, char separator) { + List parts = new ArrayList<>(); + StringBuilder current = new StringBuilder(); + boolean inQuote = false; + for (int i = 0; i < value.length(); i++) { + char c = value.charAt(i); + if (c == '"') { + inQuote = !inQuote; + current.append(c); + } else if (!inQuote && c == separator) { + parts.add(current.toString()); + current.setLength(0); + } else { + current.append(c); + } + } + parts.add(current.toString()); + return parts; + } +}