Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions all/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ plugins {

description = "OpenTelemetry All"
otelJava.moduleName.set("io.opentelemetry.all")
otelJava.osgiEnabled.set(false)

// Skip ossIndexAudit on test module
tasks.named("ossIndexAudit") {
Expand Down
1 change: 1 addition & 0 deletions animal-sniffer-signature/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ plugins {

description = "Build tool to generate the Animal Sniffer Android signature"
otelJava.moduleName.set("io.opentelemetry.internal.animalsniffer")
otelJava.osgiEnabled.set(false)

val signatureJar = configurations.create("signatureJar") {
isCanBeConsumed = false
Expand Down
4 changes: 4 additions & 0 deletions api/all/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ plugins {
description = "OpenTelemetry API"
otelJava.moduleName.set("io.opentelemetry.api")
base.archivesName.set("opentelemetry-api")
// These packages cannot be compileOnly dependencies (api:incubator depends on api:all, creating a
// circular dependency; sdk:autoconfigure is in a different module family). Declare them as optional
// imports without a version constraint so OSGi wires them if present but does not require them.
otelJava.osgiUnversionedOptionalPackages.set(listOf("io.opentelemetry.sdk.autoconfigure", "io.opentelemetry.api.incubator", "io.opentelemetry.api.incubator.internal"))

dependencies {
api(project(":context"))
Expand Down
1 change: 1 addition & 0 deletions api/incubator/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ plugins {

description = "OpenTelemetry API Incubator"
otelJava.moduleName.set("io.opentelemetry.api.incubator")
otelJava.osgiOptionalPackages.set(listOf("com.fasterxml.jackson.databind"))

dependencies {
api(project(":api:all"))
Expand Down
1 change: 1 addition & 0 deletions api/testing-internal/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ plugins {

description = "OpenTelemetry API Testing (Internal)"
otelJava.moduleName.set("io.opentelemetry.api.testing.internal")
otelJava.osgiEnabled.set(false)

dependencies {
api(project(":api:all"))
Expand Down
1 change: 1 addition & 0 deletions buildSrc/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ repositories {
}

dependencies {
implementation("biz.aQute.bnd:biz.aQute.bnd.gradle:7.2.0")
implementation(enforcedPlatform("com.squareup.wire:wire-bom:6.2.0"))
implementation("com.google.auto.value:auto-value-annotations:1.11.1")
// When updating, update above in plugins too
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,29 @@
package io.opentelemetry.gradle

import org.gradle.api.JavaVersion
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Property

abstract class OtelJavaExtension {
abstract val moduleName: Property<String>

// Set to false for modules that are not OSGi bundles (e.g. test helpers, build tooling,
// aggregators). Skips BND bundle metadata generation entirely.
abstract val osgiEnabled: Property<Boolean>

abstract val osgiOptionalPackages: ListProperty<String>

// Packages that should be optional imports but are not on the compile classpath (e.g. due to
// circular dependencies), so BND cannot resolve version="${@}" for them. Added to Import-Package
// with resolution:=optional but no version constraint.
abstract val osgiUnversionedOptionalPackages: ListProperty<String>

abstract val minJavaVersionSupported: Property<JavaVersion>

init {
minJavaVersionSupported.convention(JavaVersion.VERSION_1_8)
osgiEnabled.convention(true)
osgiOptionalPackages.convention(emptyList<String>())
osgiUnversionedOptionalPackages.convention(emptyList<String>())
}
}
33 changes: 33 additions & 0 deletions buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ plugins {
eclipse
idea

id("biz.aQute.bnd.builder")
id("otel.errorprone-conventions")
id("otel.jacoco-conventions")
id("otel.spotless-conventions")
Expand Down Expand Up @@ -131,6 +132,38 @@ tasks {
}
}

afterEvaluate {
if (otelJava.osgiEnabled.get()) {
named<Jar>("jar") {
// Configure OSGi metadata
bundle {
// Compute import packages.
// Certain packages like javax.annotation.* are always optional.
// Modules may have additional optional packages, typically corresponding to compileOnly dependencies.
// Append wildcard "*" last to import any other referenced packages.
val optionalPackages = mutableListOf("javax.annotation")
optionalPackages.addAll(otelJava.osgiOptionalPackages.get())
val importPackages = optionalPackages.joinToString(",") { "$it.*;resolution:=optional;version=\"\${@}\"" } + ",*"

// Packages not on the compile classpath (e.g. due to circular dependencies) cannot use
// version="${@}" since BND cannot resolve the version. Add them as optional imports without
// a version constraint; they are listed before the wildcard so BND uses our explicit
// instruction rather than auto-detecting them with a version.
val unversionedOptionalPackages = otelJava.osgiUnversionedOptionalPackages.get()
val unversionedImports = unversionedOptionalPackages.joinToString(",") { "$it.*;resolution:=optional" }
val fullImportPackages = if (unversionedImports.isNotEmpty()) "$unversionedImports,$importPackages" else importPackages

bnd(mapOf(
// Exclude shaded internal packages from exports; they are implementation details and
// should not be part of the OSGi bundle's public API surface.
"-exportcontents" to "!io.opentelemetry.internal.shaded.*,io.opentelemetry.*",
"Import-Package" to fullImportPackages
))
}
}
}
}

withType<Jar>().configureEach {
inputs.property("moduleName", otelJava.moduleName)

Expand Down
3 changes: 3 additions & 0 deletions dependencyManagement/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ val DEPENDENCY_BOMS = listOf(
"io.zipkin.brave:brave-bom:6.3.1",
"io.zipkin.reporter2:zipkin-reporter-bom:3.5.3",
"org.assertj:assertj-bom:3.27.7",
"org.osgi:org.osgi.test.bom:1.2.1",
"org.testcontainers:testcontainers-bom:2.0.4",
"org.snakeyaml:snakeyaml-engine:2.10"
)
Expand Down Expand Up @@ -88,11 +89,13 @@ val DEPENDENCIES = listOf(
"io.opentracing:opentracing-noop:0.33.0",
"junit:junit:4.13.2",
"nl.jqno.equalsverifier:equalsverifier:3.19.4",
"org.apache.felix:org.apache.felix.framework:7.0.5",
"org.awaitility:awaitility:4.3.0",
"org.codehaus.mojo:animal-sniffer-annotations:1.27",
"org.jctools:jctools-core:4.0.6",
"org.junit-pioneer:junit-pioneer:1.9.1",
"org.mock-server:mockserver-netty:5.15.0:shaded",
"org.osgi:osgi.core:8.0.0",
"org.skyscreamer:jsonassert:1.5.3",
"com.android.tools:desugar_jdk_libs:2.1.5",
)
Expand Down
1 change: 1 addition & 0 deletions exporters/common/compile-stub/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ plugins {

description = "OpenTelemetry Exporter Compile Stub"
otelJava.moduleName.set("io.opentelemetry.exporter.internal.compile-stub")
otelJava.osgiEnabled.set(false)
1 change: 1 addition & 0 deletions integration-tests/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ plugins {

description = "OpenTelemetry Integration Tests"
otelJava.moduleName.set("io.opentelemetry.integration.tests")
otelJava.osgiEnabled.set(false)

dependencies {
testImplementation(project(":sdk:all"))
Expand Down
1 change: 1 addition & 0 deletions integration-tests/graal-incubating/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ plugins {

description = "OpenTelemetry Graal Integration Tests (Incubating)"
otelJava.moduleName.set("io.opentelemetry.graal.integration.tests.incubating")
otelJava.osgiEnabled.set(false)
otelJava.minJavaVersionSupported.set(JavaVersion.VERSION_17)

sourceSets {
Expand Down
1 change: 1 addition & 0 deletions integration-tests/graal/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ plugins {

description = "OpenTelemetry Graal Integration Tests"
otelJava.moduleName.set("io.opentelemetry.graal.integration.tests")
otelJava.osgiEnabled.set(false)
otelJava.minJavaVersionSupported.set(JavaVersion.VERSION_17)

sourceSets {
Expand Down
111 changes: 111 additions & 0 deletions integration-tests/osgi/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import aQute.bnd.gradle.Bundle
import aQute.bnd.gradle.Resolve
import aQute.bnd.gradle.TestOSGi

plugins {
id("otel.java-conventions")
}

description = "OpenTelemetry OSGi Integration Tests"
otelJava.moduleName.set("io.opentelemetry.integration.tests.osgi")

// For similar test examples see:
// https://github.com/micrometer-metrics/micrometer/tree/main/micrometer-osgi-test
// https://github.com/eclipse-osgi-technology/osgi-test/tree/main/examples/osgi-test-example-gradle

configurations.all {
resolutionStrategy {
// BND not compatible with JUnit 5.13+; see https://github.com/bndtools/bnd/issues/6651
val junitVersion = "5.12.2"
val junitLauncherVersion = "1.12.1"
force("org.junit.jupiter:junit-jupiter:$junitVersion")
force("org.junit.jupiter:junit-jupiter-api:$junitVersion")
force("org.junit.jupiter:junit-jupiter-params:$junitVersion")
force("org.junit.jupiter:junit-jupiter-engine:$junitVersion")
force("org.junit.platform:junit-platform-launcher:$junitLauncherVersion")
}
}

dependencies {
// Testing the "kitchen sink" hides OSGi configuration issues. For example, opentelemetry-api has
// optional dependencies on :sdk-extensions:autoconfigure and :api:incubator. If we only test a
// bundle which includes those, then mask the fact that OSGi fails when using a bundle without those
// until opentelemetry-api OSGi configuration is updated to indicate that they are optional.

// TODO (jack-berg): Add additional test bundles with dependency combinations reflecting popular use cases:
// - with OTLP exporters
// - with autoconfigure
// - with file configuration
testImplementation(project(":sdk:all"))

testImplementation("org.junit.jupiter:junit-jupiter")

testCompileOnly("org.osgi:osgi.core")
testImplementation("org.osgi:org.osgi.test.junit5")
testImplementation("org.osgi:org.osgi.test.assertj.framework")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
testRuntimeOnly("org.apache.felix:org.apache.felix.framework")
}

val testingBundleTask = tasks.register<Bundle>("testingBundle") {
archiveClassifier.set("testing")
from(sourceSets.test.get().output)
bundle {
// The Bundle task uses compileClasspath by default for BND analysis (e.g. resolving the
// @Testable annotation to populate Test-Cases). Without this, testImplementation dependencies
// like junit-jupiter are invisible to BND, causing Test-Cases to be empty and 0 tests to run.
classpath(sourceSets.test.get().runtimeClasspath)
bnd(
"Test-Cases: \${classes;HIERARCHY_INDIRECTLY_ANNOTATED;org.junit.platform.commons.annotation.Testable;CONCRETE}",
"Import-Package: javax.annotation.*;resolution:=optional;version=\"\${@}\",*"
)
}
}

val resolveTask = tasks.register<Resolve>("resolve") {
dependsOn(testingBundleTask)
project.ext.set("osgiRunee", "JavaSE-${java.toolchain.languageVersion.get()}")
description = "Resolve test.bndrun"
group = JavaBasePlugin.VERIFICATION_GROUP
bndrun = file("test.bndrun")
outputBndrun = layout.buildDirectory.file("test.bndrun")
bundles = files(sourceSets.test.get().runtimeClasspath, testingBundleTask.get().archiveFile)
// The generated output embeds an absolute path to the source bndrun, making it unsafe to share
// across machines or worktrees via the build cache.
outputs.cacheIf { false }
}

val testOSGiTask = tasks.register<TestOSGi>("testOSGi") {
description = "OSGi Test test.bndrun"
group = JavaBasePlugin.VERIFICATION_GROUP
bndrun = resolveTask.flatMap { it.outputBndrun }
bundles = files(sourceSets.test.get().runtimeClasspath, testingBundleTask.get().archiveFile)
// BND reports success when zero tests ran (e.g. if bundles failed to start). Fail explicitly.
val testResultsDir = layout.buildDirectory.dir("test-results/testOSGi")
doLast {
check(testResultsDir.get().asFile.listFiles()?.isNotEmpty() == true) {
"No OSGi test results found — bundles may have failed to start. Check the output above."
}
}
}

tasks.named(LifecycleBasePlugin.CHECK_TASK_NAME) {
dependsOn(testOSGiTask)
}

tasks {
jar {
enabled = false
}
test {
// We need to replace junit testing with the testOSGi task, so we clear test actions and add a dependency on testOSGi.
// As a result, running :test runs only :testOSGi.
actions.clear()
dependsOn(testOSGiTask)
}
}

// Skip ossIndexAudit on test module
tasks.named("ossIndexAudit") {
enabled = false
}
Loading
Loading