From 055a0192cc9c599c3af133d96f29f413f0e9e16e Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Thu, 2 Apr 2026 12:22:05 +0200 Subject: [PATCH 01/27] fix(build): remove Spring Boot 2 Gradle plugin for Gradle 9 compatibility The Spring Boot 2.7.x Gradle plugin uses removed Gradle APIs (LenientConfiguration.getFiles()) that are incompatible with Gradle 9. Library modules (sentry-spring, sentry-spring-boot, sentry-spring-boot-starter): - Replace SpringBootPlugin.BOM_COORDINATES with direct BOM reference via version catalog (libs.springboot2.bom) - Remove the 'apply false' plugin declaration entirely Sample apps (spring-boot, webflux, otel, netflix-dgs): - Replace Spring Boot plugin with Shadow plugin for fat JAR creation - Add application plugin for main class configuration - Use platform(libs.springboot2.bom) for dependency version management - Configure shadow JAR to merge Spring metadata files - Replace BootRun task with JavaExec in otel sample --- gradle/libs.versions.toml | 3 +- .../build.gradle.kts | 25 ++++++++++++-- .../build.gradle.kts | 26 +++++++++++++-- .../build.gradle.kts | 33 +++++++++++++++---- .../build.gradle.kts | 25 ++++++++++++-- .../build.gradle.kts | 25 ++++++++++++-- .../sentry-samples-spring/build.gradle.kts | 4 +-- sentry-spring-boot-starter/build.gradle.kts | 4 +-- sentry-spring-boot/build.gradle.kts | 6 ++-- sentry-spring/build.gradle.kts | 4 +-- 10 files changed, 125 insertions(+), 30 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9eb39e5066e..4b1a0595427 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -61,7 +61,7 @@ detekt = { id = "io.gitlab.arturbosch.detekt", version = "1.23.8" } jacoco-android = { id = "com.mxalbert.gradle.jacoco-android", version = "0.2.0" } kover = { id = "org.jetbrains.kotlinx.kover", version = "0.7.3" } vanniktech-maven-publish = { id = "com.vanniktech.maven.publish", version = "0.30.0" } -springboot2 = { id = "org.springframework.boot", version.ref = "springboot2" } +shadow = { id = "com.gradleup.shadow", version = "9.4.1" } springboot3 = { id = "org.springframework.boot", version.ref = "springboot3" } springboot4 = { id = "org.springframework.boot", version.ref = "springboot4" } spring-dependency-management = { id = "io.spring.dependency-management", version = "1.1.7" } @@ -159,6 +159,7 @@ slf4j-api = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" } slf4j-jdk14 = { module = "org.slf4j:slf4j-jdk14", version.ref = "slf4j" } slf4j2-api = { module = "org.slf4j:slf4j-api", version = "2.0.5" } spotlessLib = { module = "com.diffplug.spotless:com.diffplug.spotless.gradle.plugin", version.ref = "spotless"} +springboot2-bom = { module = "org.springframework.boot:spring-boot-dependencies", version.ref = "springboot2" } springboot-starter = { module = "org.springframework.boot:spring-boot-starter", version.ref = "springboot2" } springboot-starter-graphql = { module = "org.springframework.boot:spring-boot-starter-graphql", version.ref = "springboot2" } springboot-starter-quartz = { module = "org.springframework.boot:spring-boot-starter-quartz", version.ref = "springboot2" } diff --git a/sentry-samples/sentry-samples-netflix-dgs/build.gradle.kts b/sentry-samples/sentry-samples-netflix-dgs/build.gradle.kts index ade18a0cbc1..bfe4298e9cb 100644 --- a/sentry-samples/sentry-samples-netflix-dgs/build.gradle.kts +++ b/sentry-samples/sentry-samples-netflix-dgs/build.gradle.kts @@ -2,12 +2,15 @@ import org.jetbrains.kotlin.config.KotlinCompilerVersion import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { - alias(libs.plugins.springboot2) - alias(libs.plugins.spring.dependency.management) + java + application + alias(libs.plugins.shadow) alias(libs.plugins.kotlin.jvm) alias(libs.plugins.kotlin.spring) } +application { mainClass.set("io.sentry.samples.netflix.dgs.NetlixDgsApplication") } + group = "io.sentry.sample.spring-boot" version = "0.0.1-SNAPSHOT" @@ -19,6 +22,7 @@ java.targetCompatibility = JavaVersion.VERSION_1_8 repositories { mavenCentral() } dependencies { + implementation(platform(libs.springboot2.bom)) implementation(libs.springboot.starter.web) implementation(Config.Libs.kotlinReflect) implementation(kotlin(Config.kotlinStdLib, KotlinCompilerVersion.VERSION)) @@ -32,6 +36,23 @@ dependencies { } } +// Configure the Shadow JAR (executable JAR with all dependencies) +tasks.shadowJar { + manifest { attributes["Main-Class"] = "io.sentry.samples.netflix.dgs.NetlixDgsApplication" } + archiveClassifier.set("") + mergeServiceFiles() + append("META-INF/spring.handlers") + append("META-INF/spring.schemas") + append("META-INF/spring.factories") +} + +tasks.jar { + enabled = false + dependsOn(tasks.shadowJar) +} + +tasks.startScripts { dependsOn(tasks.shadowJar) } + tasks.withType().configureEach { useJUnitPlatform() } tasks.withType().configureEach { diff --git a/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts index 07e61c75af8..4aa6f7d9d94 100644 --- a/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts @@ -2,12 +2,15 @@ import org.jetbrains.kotlin.config.KotlinCompilerVersion import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { - alias(libs.plugins.springboot2) - alias(libs.plugins.spring.dependency.management) + java + application + alias(libs.plugins.shadow) alias(libs.plugins.kotlin.jvm) alias(libs.plugins.kotlin.spring) } +application { mainClass.set("io.sentry.samples.spring.boot.SentryDemoApplication") } + group = "io.sentry.sample.spring-boot" version = "0.0.1-SNAPSHOT" @@ -35,6 +38,8 @@ tasks.withType().configureEach { } dependencies { + implementation(platform(libs.springboot2.bom)) + implementation(platform(libs.otel.instrumentation.bom)) implementation(libs.springboot.starter) implementation(libs.springboot.starter.actuator) implementation(libs.springboot.starter.aop) @@ -72,7 +77,22 @@ dependencies { testImplementation("org.apache.httpcomponents:httpclient") } -dependencyManagement { imports { mavenBom(libs.otel.instrumentation.bom.get().toString()) } } +// Configure the Shadow JAR (executable JAR with all dependencies) +tasks.shadowJar { + manifest { attributes["Main-Class"] = "io.sentry.samples.spring.boot.SentryDemoApplication" } + archiveClassifier.set("") + mergeServiceFiles() + append("META-INF/spring.handlers") + append("META-INF/spring.schemas") + append("META-INF/spring.factories") +} + +tasks.jar { + enabled = false + dependsOn(tasks.shadowJar) +} + +tasks.startScripts { dependsOn(tasks.shadowJar) } configure { test { java.srcDir("src/test/java") } } diff --git a/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts index 21a3cf3f7d5..1dea1f6a4ac 100644 --- a/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts @@ -1,14 +1,16 @@ import org.jetbrains.kotlin.config.KotlinCompilerVersion import org.jetbrains.kotlin.gradle.tasks.KotlinCompile -import org.springframework.boot.gradle.tasks.run.BootRun plugins { - alias(libs.plugins.springboot2) - alias(libs.plugins.spring.dependency.management) + java + application + alias(libs.plugins.shadow) alias(libs.plugins.kotlin.jvm) alias(libs.plugins.kotlin.spring) } +application { mainClass.set("io.sentry.samples.spring.boot.SentryDemoApplication") } + group = "io.sentry.sample.spring-boot" version = "0.0.1-SNAPSHOT" @@ -33,6 +35,7 @@ tasks.withType().configureEach { } dependencies { + implementation(platform(libs.springboot2.bom)) implementation(libs.springboot.starter) implementation(libs.springboot.starter.actuator) implementation(libs.springboot.starter.aop) @@ -70,14 +73,30 @@ dependencies { testImplementation("org.apache.httpcomponents:httpclient") } +// Configure the Shadow JAR (executable JAR with all dependencies) +tasks.shadowJar { + manifest { attributes["Main-Class"] = "io.sentry.samples.spring.boot.SentryDemoApplication" } + archiveClassifier.set("") + mergeServiceFiles() + append("META-INF/spring.handlers") + append("META-INF/spring.schemas") + append("META-INF/spring.factories") +} + +tasks.jar { + enabled = false + dependsOn(tasks.shadowJar) +} + +tasks.startScripts { dependsOn(tasks.shadowJar) } + configure { test { java.srcDir("src/test/java") } } -tasks.register("bootRunWithAgent").configure { +tasks.register("bootRunWithAgent").configure { group = "application" - val mainBootRunTask = tasks.getByName("bootRun") - mainClass = mainBootRunTask.mainClass - classpath = mainBootRunTask.classpath + mainClass.set("io.sentry.samples.spring.boot.SentryDemoApplication") + classpath = sourceSets["main"].runtimeClasspath val versionName = project.properties["versionName"] as String val agentJarPath = diff --git a/sentry-samples/sentry-samples-spring-boot-webflux/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-webflux/build.gradle.kts index 3c0a5f8c83e..9fc551fdcd7 100644 --- a/sentry-samples/sentry-samples-spring-boot-webflux/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-webflux/build.gradle.kts @@ -2,12 +2,15 @@ import org.jetbrains.kotlin.config.KotlinCompilerVersion import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { - alias(libs.plugins.springboot2) - alias(libs.plugins.spring.dependency.management) + java + application + alias(libs.plugins.shadow) alias(libs.plugins.kotlin.jvm) alias(libs.plugins.kotlin.spring) } +application { mainClass.set("io.sentry.samples.spring.boot.SentryDemoApplication") } + group = "io.sentry.sample.spring-boot" version = "0.0.1-SNAPSHOT" @@ -19,6 +22,7 @@ java.targetCompatibility = JavaVersion.VERSION_17 repositories { mavenCentral() } dependencies { + implementation(platform(libs.springboot2.bom)) implementation(libs.springboot.starter.actuator) implementation(libs.springboot.starter.graphql) implementation(libs.springboot.starter.webflux) @@ -42,6 +46,23 @@ dependencies { testImplementation("org.apache.httpcomponents:httpclient") } +// Configure the Shadow JAR (executable JAR with all dependencies) +tasks.shadowJar { + manifest { attributes["Main-Class"] = "io.sentry.samples.spring.boot.SentryDemoApplication" } + archiveClassifier.set("") + mergeServiceFiles() + append("META-INF/spring.handlers") + append("META-INF/spring.schemas") + append("META-INF/spring.factories") +} + +tasks.jar { + enabled = false + dependsOn(tasks.shadowJar) +} + +tasks.startScripts { dependsOn(tasks.shadowJar) } + configure { test { java.srcDir("src/test/java") } } tasks.withType().configureEach { diff --git a/sentry-samples/sentry-samples-spring-boot/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot/build.gradle.kts index b6fcd675cf3..a89d3b609fe 100644 --- a/sentry-samples/sentry-samples-spring-boot/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot/build.gradle.kts @@ -2,12 +2,15 @@ import org.jetbrains.kotlin.config.KotlinCompilerVersion import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { - alias(libs.plugins.springboot2) - alias(libs.plugins.spring.dependency.management) + java + application + alias(libs.plugins.shadow) alias(libs.plugins.kotlin.jvm) alias(libs.plugins.kotlin.spring) } +application { mainClass.set("io.sentry.samples.spring.boot.SentryDemoApplication") } + group = "io.sentry.sample.spring-boot" version = "0.0.1-SNAPSHOT" @@ -31,6 +34,7 @@ tasks.withType().configureEach { } dependencies { + implementation(platform(libs.springboot2.bom)) implementation(libs.springboot.starter) implementation(libs.springboot.starter.actuator) implementation(libs.springboot.starter.aop) @@ -69,6 +73,23 @@ dependencies { testImplementation("org.apache.httpcomponents:httpclient") } +// Configure the Shadow JAR (executable JAR with all dependencies) +tasks.shadowJar { + manifest { attributes["Main-Class"] = "io.sentry.samples.spring.boot.SentryDemoApplication" } + archiveClassifier.set("") + mergeServiceFiles() + append("META-INF/spring.handlers") + append("META-INF/spring.schemas") + append("META-INF/spring.factories") +} + +tasks.jar { + enabled = false + dependsOn(tasks.shadowJar) +} + +tasks.startScripts { dependsOn(tasks.shadowJar) } + configure { test { java.srcDir("src/test/java") } } tasks.register("systemTest").configure { diff --git a/sentry-samples/sentry-samples-spring/build.gradle.kts b/sentry-samples/sentry-samples-spring/build.gradle.kts index f6aa0e925ee..acc709a4050 100644 --- a/sentry-samples/sentry-samples-spring/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring/build.gradle.kts @@ -1,9 +1,7 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile -import org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES plugins { application - alias(libs.plugins.springboot2) apply false alias(libs.plugins.spring.dependency.management) alias(libs.plugins.kotlin.jvm) alias(libs.plugins.kotlin.spring) @@ -29,7 +27,7 @@ repositories { mavenCentral() } dependencyManagement { imports { - mavenBom(BOM_COORDINATES) + mavenBom(libs.springboot2.bom.get().toString()) mavenBom(libs.kotlin.bom.get().toString()) mavenBom(libs.jackson.bom.get().toString()) } diff --git a/sentry-spring-boot-starter/build.gradle.kts b/sentry-spring-boot-starter/build.gradle.kts index a8b22a50f09..6b5bcdf5752 100644 --- a/sentry-spring-boot-starter/build.gradle.kts +++ b/sentry-spring-boot-starter/build.gradle.kts @@ -1,6 +1,5 @@ import net.ltgt.gradle.errorprone.errorprone import org.jetbrains.kotlin.gradle.tasks.KotlinCompile -import org.springframework.boot.gradle.plugin.SpringBootPlugin plugins { `java-library` @@ -9,7 +8,6 @@ plugins { jacoco alias(libs.plugins.errorprone) alias(libs.plugins.gradle.versions) - alias(libs.plugins.springboot2) apply false } tasks.withType().configureEach { @@ -22,7 +20,7 @@ dependencies { api(projects.sentrySpringBoot) api(libs.springboot.starter) - annotationProcessor(platform(SpringBootPlugin.BOM_COORDINATES)) + annotationProcessor(platform(libs.springboot2.bom)) annotationProcessor(Config.AnnotationProcessors.springBootAutoConfigure) annotationProcessor(Config.AnnotationProcessors.springBootConfiguration) diff --git a/sentry-spring-boot/build.gradle.kts b/sentry-spring-boot/build.gradle.kts index a81613e5e1e..43150869db5 100644 --- a/sentry-spring-boot/build.gradle.kts +++ b/sentry-spring-boot/build.gradle.kts @@ -1,6 +1,5 @@ import net.ltgt.gradle.errorprone.errorprone import org.jetbrains.kotlin.gradle.tasks.KotlinCompile -import org.springframework.boot.gradle.plugin.SpringBootPlugin plugins { `java-library` @@ -10,7 +9,6 @@ plugins { alias(libs.plugins.errorprone) alias(libs.plugins.gradle.versions) alias(libs.plugins.buildconfig) - alias(libs.plugins.springboot2) apply false } tasks.withType().configureEach { @@ -40,14 +38,14 @@ dependencies { compileOnly(libs.springboot.starter.graphql) compileOnly(libs.springboot.starter.quartz) compileOnly(libs.springboot.starter.security) - compileOnly(platform(SpringBootPlugin.BOM_COORDINATES)) + compileOnly(platform(libs.springboot2.bom)) compileOnly(Config.Libs.springWeb) compileOnly(Config.Libs.springWebflux) compileOnly(projects.sentryOpentelemetry.sentryOpentelemetryCore) compileOnly(projects.sentryGraphql) compileOnly(projects.sentryQuartz) - annotationProcessor(platform(SpringBootPlugin.BOM_COORDINATES)) + annotationProcessor(platform(libs.springboot2.bom)) annotationProcessor(Config.AnnotationProcessors.springBootAutoConfigure) annotationProcessor(Config.AnnotationProcessors.springBootConfiguration) diff --git a/sentry-spring/build.gradle.kts b/sentry-spring/build.gradle.kts index 57c0b9d9f31..b651a9e62b2 100644 --- a/sentry-spring/build.gradle.kts +++ b/sentry-spring/build.gradle.kts @@ -1,6 +1,5 @@ import net.ltgt.gradle.errorprone.errorprone import org.jetbrains.kotlin.gradle.tasks.KotlinCompile -import org.springframework.boot.gradle.plugin.SpringBootPlugin plugins { `java-library` @@ -10,7 +9,6 @@ plugins { alias(libs.plugins.errorprone) alias(libs.plugins.gradle.versions) alias(libs.plugins.buildconfig) - alias(libs.plugins.springboot2) apply false } tasks.withType().configureEach { @@ -22,7 +20,7 @@ tasks.withType().configureEach { dependencies { api(projects.sentry) - compileOnly(platform(SpringBootPlugin.BOM_COORDINATES)) + compileOnly(platform(libs.springboot2.bom)) compileOnly(Config.Libs.springWeb) compileOnly(Config.Libs.springAop) compileOnly(Config.Libs.springSecurityWeb) From 9dd4f2f39a8b253bf5940ee2f8c368bb59f4e61a Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Thu, 2 Apr 2026 12:48:31 +0200 Subject: [PATCH 02/27] fix: set duplicatesStrategy=INCLUDE for shadow JAR spring.factories merge Shadow plugin 9.x defaults to DuplicatesStrategy.EXCLUDE, which drops duplicate META-INF/spring.factories entries before transformers can merge them. Setting INCLUDE allows the AppendingTransformer to see all entries and properly concatenate spring.factories from all JARs. Without this, the shadow JAR only contains spring.factories from a single dependency, causing Spring Boot auto-configuration to fail (e.g. missing RestTemplateBuilder, no embedded web server). --- sentry-samples/sentry-samples-netflix-dgs/build.gradle.kts | 2 ++ .../build.gradle.kts | 2 ++ .../sentry-samples-spring-boot-opentelemetry/build.gradle.kts | 2 ++ .../sentry-samples-spring-boot-webflux/build.gradle.kts | 2 ++ sentry-samples/sentry-samples-spring-boot/build.gradle.kts | 1 + 5 files changed, 9 insertions(+) diff --git a/sentry-samples/sentry-samples-netflix-dgs/build.gradle.kts b/sentry-samples/sentry-samples-netflix-dgs/build.gradle.kts index bfe4298e9cb..6e0818d78b9 100644 --- a/sentry-samples/sentry-samples-netflix-dgs/build.gradle.kts +++ b/sentry-samples/sentry-samples-netflix-dgs/build.gradle.kts @@ -1,3 +1,4 @@ + import org.jetbrains.kotlin.config.KotlinCompilerVersion import org.jetbrains.kotlin.gradle.tasks.KotlinCompile @@ -40,6 +41,7 @@ dependencies { tasks.shadowJar { manifest { attributes["Main-Class"] = "io.sentry.samples.netflix.dgs.NetlixDgsApplication" } archiveClassifier.set("") + duplicatesStrategy = DuplicatesStrategy.INCLUDE mergeServiceFiles() append("META-INF/spring.handlers") append("META-INF/spring.schemas") diff --git a/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts index 4aa6f7d9d94..2804d900cc4 100644 --- a/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts @@ -1,3 +1,4 @@ + import org.jetbrains.kotlin.config.KotlinCompilerVersion import org.jetbrains.kotlin.gradle.tasks.KotlinCompile @@ -81,6 +82,7 @@ dependencies { tasks.shadowJar { manifest { attributes["Main-Class"] = "io.sentry.samples.spring.boot.SentryDemoApplication" } archiveClassifier.set("") + duplicatesStrategy = DuplicatesStrategy.INCLUDE mergeServiceFiles() append("META-INF/spring.handlers") append("META-INF/spring.schemas") diff --git a/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts index 1dea1f6a4ac..6df87736820 100644 --- a/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts @@ -1,3 +1,4 @@ + import org.jetbrains.kotlin.config.KotlinCompilerVersion import org.jetbrains.kotlin.gradle.tasks.KotlinCompile @@ -77,6 +78,7 @@ dependencies { tasks.shadowJar { manifest { attributes["Main-Class"] = "io.sentry.samples.spring.boot.SentryDemoApplication" } archiveClassifier.set("") + duplicatesStrategy = DuplicatesStrategy.INCLUDE mergeServiceFiles() append("META-INF/spring.handlers") append("META-INF/spring.schemas") diff --git a/sentry-samples/sentry-samples-spring-boot-webflux/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-webflux/build.gradle.kts index 9fc551fdcd7..cdbd6fa7549 100644 --- a/sentry-samples/sentry-samples-spring-boot-webflux/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-webflux/build.gradle.kts @@ -1,3 +1,4 @@ + import org.jetbrains.kotlin.config.KotlinCompilerVersion import org.jetbrains.kotlin.gradle.tasks.KotlinCompile @@ -50,6 +51,7 @@ dependencies { tasks.shadowJar { manifest { attributes["Main-Class"] = "io.sentry.samples.spring.boot.SentryDemoApplication" } archiveClassifier.set("") + duplicatesStrategy = DuplicatesStrategy.INCLUDE mergeServiceFiles() append("META-INF/spring.handlers") append("META-INF/spring.schemas") diff --git a/sentry-samples/sentry-samples-spring-boot/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot/build.gradle.kts index a89d3b609fe..6b50d2e3733 100644 --- a/sentry-samples/sentry-samples-spring-boot/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot/build.gradle.kts @@ -77,6 +77,7 @@ dependencies { tasks.shadowJar { manifest { attributes["Main-Class"] = "io.sentry.samples.spring.boot.SentryDemoApplication" } archiveClassifier.set("") + duplicatesStrategy = DuplicatesStrategy.INCLUDE mergeServiceFiles() append("META-INF/spring.handlers") append("META-INF/spring.schemas") From 63aa4585a1b67e46821a3d7ce8fc50f7d9e89c9f Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Thu, 2 Apr 2026 12:50:56 +0200 Subject: [PATCH 03/27] fix: remove duplicate shadow plugin entry in version catalog --- gradle/libs.versions.toml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4b1a0595427..6cc10910315 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -61,14 +61,13 @@ detekt = { id = "io.gitlab.arturbosch.detekt", version = "1.23.8" } jacoco-android = { id = "com.mxalbert.gradle.jacoco-android", version = "0.2.0" } kover = { id = "org.jetbrains.kotlinx.kover", version = "0.7.3" } vanniktech-maven-publish = { id = "com.vanniktech.maven.publish", version = "0.30.0" } -shadow = { id = "com.gradleup.shadow", version = "9.4.1" } springboot3 = { id = "org.springframework.boot", version.ref = "springboot3" } springboot4 = { id = "org.springframework.boot", version.ref = "springboot4" } spring-dependency-management = { id = "io.spring.dependency-management", version = "1.1.7" } gretty = { id = "org.gretty", version = "4.0.0" } animalsniffer = { id = "ru.vyarus.animalsniffer", version = "2.0.1" } sentry = { id = "io.sentry.android.gradle", version = "6.0.0-alpha.6"} -shadow = { id = "com.gradleup.shadow", version = "8.3.6" } +shadow = { id = "com.gradleup.shadow", version = "9.4.1" } [libraries] apache-httpclient = { module = "org.apache.httpcomponents.client5:httpclient5", version = "5.0.4" } From 0e43fc7eae1cdea5b1b02721af6348ccea1cdacc Mon Sep 17 00:00:00 2001 From: Sentry Github Bot Date: Thu, 2 Apr 2026 10:55:27 +0000 Subject: [PATCH 04/27] Format code --- sentry-samples/sentry-samples-netflix-dgs/build.gradle.kts | 1 - .../build.gradle.kts | 1 - .../sentry-samples-spring-boot-opentelemetry/build.gradle.kts | 1 - .../sentry-samples-spring-boot-webflux/build.gradle.kts | 1 - 4 files changed, 4 deletions(-) diff --git a/sentry-samples/sentry-samples-netflix-dgs/build.gradle.kts b/sentry-samples/sentry-samples-netflix-dgs/build.gradle.kts index 6e0818d78b9..008cdc5b6b7 100644 --- a/sentry-samples/sentry-samples-netflix-dgs/build.gradle.kts +++ b/sentry-samples/sentry-samples-netflix-dgs/build.gradle.kts @@ -1,4 +1,3 @@ - import org.jetbrains.kotlin.config.KotlinCompilerVersion import org.jetbrains.kotlin.gradle.tasks.KotlinCompile diff --git a/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts index 2804d900cc4..07cfdd81d51 100644 --- a/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts @@ -1,4 +1,3 @@ - import org.jetbrains.kotlin.config.KotlinCompilerVersion import org.jetbrains.kotlin.gradle.tasks.KotlinCompile diff --git a/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts index 6df87736820..9598ec4357b 100644 --- a/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts @@ -1,4 +1,3 @@ - import org.jetbrains.kotlin.config.KotlinCompilerVersion import org.jetbrains.kotlin.gradle.tasks.KotlinCompile diff --git a/sentry-samples/sentry-samples-spring-boot-webflux/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-webflux/build.gradle.kts index cdbd6fa7549..d7de2e2b4b3 100644 --- a/sentry-samples/sentry-samples-spring-boot-webflux/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-webflux/build.gradle.kts @@ -1,4 +1,3 @@ - import org.jetbrains.kotlin.config.KotlinCompilerVersion import org.jetbrains.kotlin.gradle.tasks.KotlinCompile From da7d4004f109dcf6ab7d25163824409e6b6e9f1e Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Thu, 2 Apr 2026 13:01:16 +0200 Subject: [PATCH 05/27] fix: update system test runner for shadow JAR compatibility - Auto-detect shadowJar vs bootJar build task based on build.gradle.kts - Add fallback HTTP readiness check for shadow JAR apps that lack actuator endpoints (actuator web endpoints don't work in flat JARs) - Append spring-autoconfigure-metadata.properties in shadow JAR config --- .../build.gradle.kts | 1 + .../build.gradle.kts | 1 + .../build.gradle.kts | 1 + .../build.gradle.kts | 1 + .../build.gradle.kts | 1 + test/system-test-runner.py | 35 +++++++++++++++++-- 6 files changed, 38 insertions(+), 2 deletions(-) diff --git a/sentry-samples/sentry-samples-netflix-dgs/build.gradle.kts b/sentry-samples/sentry-samples-netflix-dgs/build.gradle.kts index 008cdc5b6b7..6386a5d1593 100644 --- a/sentry-samples/sentry-samples-netflix-dgs/build.gradle.kts +++ b/sentry-samples/sentry-samples-netflix-dgs/build.gradle.kts @@ -45,6 +45,7 @@ tasks.shadowJar { append("META-INF/spring.handlers") append("META-INF/spring.schemas") append("META-INF/spring.factories") + append("META-INF/spring-autoconfigure-metadata.properties") } tasks.jar { diff --git a/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts index 07cfdd81d51..ef379dcf389 100644 --- a/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts @@ -86,6 +86,7 @@ tasks.shadowJar { append("META-INF/spring.handlers") append("META-INF/spring.schemas") append("META-INF/spring.factories") + append("META-INF/spring-autoconfigure-metadata.properties") } tasks.jar { diff --git a/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts index 9598ec4357b..2cf1cf41d29 100644 --- a/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts @@ -82,6 +82,7 @@ tasks.shadowJar { append("META-INF/spring.handlers") append("META-INF/spring.schemas") append("META-INF/spring.factories") + append("META-INF/spring-autoconfigure-metadata.properties") } tasks.jar { diff --git a/sentry-samples/sentry-samples-spring-boot-webflux/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-webflux/build.gradle.kts index d7de2e2b4b3..f1552bdf2fe 100644 --- a/sentry-samples/sentry-samples-spring-boot-webflux/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-webflux/build.gradle.kts @@ -55,6 +55,7 @@ tasks.shadowJar { append("META-INF/spring.handlers") append("META-INF/spring.schemas") append("META-INF/spring.factories") + append("META-INF/spring-autoconfigure-metadata.properties") } tasks.jar { diff --git a/sentry-samples/sentry-samples-spring-boot/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot/build.gradle.kts index 6b50d2e3733..8828edfbe5e 100644 --- a/sentry-samples/sentry-samples-spring-boot/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot/build.gradle.kts @@ -82,6 +82,7 @@ tasks.shadowJar { append("META-INF/spring.handlers") append("META-INF/spring.schemas") append("META-INF/spring.factories") + append("META-INF/spring-autoconfigure-metadata.properties") } tasks.jar { diff --git a/test/system-test-runner.py b/test/system-test-runner.py index 70489c580a5..6c186f6fe70 100644 --- a/test/system-test-runner.py +++ b/test/system-test-runner.py @@ -418,6 +418,20 @@ def wait_for_spring(self, max_attempts: int = 20) -> bool: except: pass + # Fallback: shadow JAR apps may not have actuator endpoints, + # so also try any HTTP connection to confirm the server is up + try: + response = requests.head( + "http://localhost:8080/", + auth=("user", "password"), + timeout=5 + ) + if response.status_code is not None: + print("Spring application is ready! (actuator not available)") + return True + except: + pass + print(f"Waiting... (attempt {attempt}/{max_attempts})") time.sleep(1) @@ -447,6 +461,18 @@ def get_spring_status(self) -> dict: except: pass + if not status["http_ready"]: + try: + response = requests.head( + "http://localhost:8080/", + auth=("user", "password"), + timeout=2 + ) + if response.status_code is not None: + status["http_ready"] = True + except: + pass + return status def get_sentry_status(self) -> dict: @@ -528,18 +554,23 @@ def stop_spring_server(self) -> None: # Clean up PID file and instance variable cleanup_pid(self.spring_server) - def get_build_task(self, server_type: Optional[ServerType]) -> str: + def get_build_task(self, sample_module: str, server_type: Optional[ServerType]) -> str: """Get the appropriate build task for a module.""" if server_type == ServerType.TOMCAT: return "war" elif server_type == ServerType.SPRING: + # Modules using Shadow plugin (e.g. Spring Boot 2 samples) use shadowJar, + # modules using Spring Boot plugin (SB3/SB4 samples) use bootJar + build_file = Path(f"sentry-samples/{sample_module}/build.gradle.kts") + if build_file.exists() and "shadow" in build_file.read_text(): + return "shadowJar" return "bootJar" return "assemble" def build_module(self, sample_module: str, server_type: Optional[ServerType]) -> int: """Build a sample module using the appropriate task.""" - build_task = self.get_build_task(server_type) + build_task = self.get_build_task(sample_module, server_type) print(f"Building {sample_module} using {build_task} task") return self.run_gradle_task(f":sentry-samples:{sample_module}:{build_task}") From c0acbd7ebd3a5749dc0fcf55f3f5ac48223b2381 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Thu, 2 Apr 2026 13:23:57 +0200 Subject: [PATCH 06/27] fix(otel): use DuplicatesStrategy.INCLUDE for otel agent shadow JAR Shadow 9.x enforces duplicatesStrategy before transformers run, so DuplicatesStrategy.FAIL prevents mergeServiceFiles from merging inst/META-INF/services/ files that exist in both the upstream OTel agent JAR and the isolated distro libs. Switching to INCLUDE lets the transformer see all duplicates and merge them correctly. --- .../sentry-opentelemetry-agent/build.gradle.kts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/sentry-opentelemetry/sentry-opentelemetry-agent/build.gradle.kts b/sentry-opentelemetry/sentry-opentelemetry-agent/build.gradle.kts index 1a57766cc96..09649fbb4a5 100644 --- a/sentry-opentelemetry/sentry-opentelemetry-agent/build.gradle.kts +++ b/sentry-opentelemetry/sentry-opentelemetry-agent/build.gradle.kts @@ -150,7 +150,11 @@ tasks { archiveClassifier.set("") - duplicatesStrategy = DuplicatesStrategy.FAIL + // INCLUDE is required so that mergeServiceFiles can see duplicates from both the + // upstream agent JAR and the isolated distro libs before they are deduplicated. + // Shadow 9.x enforces duplicatesStrategy before transformers run, so FAIL/EXCLUDE + // would prevent service file merging. + duplicatesStrategy = DuplicatesStrategy.INCLUDE mergeServiceFiles { include("inst/META-INF/services/*") } exclude("**/module-info.class") From c3c8c2497755d1ad1c21c514b1ccb6859f5a25f8 Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Tue, 7 Apr 2026 15:35:32 +0200 Subject: [PATCH 07/27] Exclude test-support modules from api validation --- build.gradle.kts | 4 +- .../api/sentry-system-test-support.api | 619 ------------------ .../api/sentry-test-support.api | 75 --- 3 files changed, 3 insertions(+), 695 deletions(-) delete mode 100644 sentry-system-test-support/api/sentry-system-test-support.api delete mode 100644 sentry-test-support/api/sentry-test-support.api diff --git a/build.gradle.kts b/build.gradle.kts index bfa4aef86f3..6656e00e49a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -87,7 +87,9 @@ apiValidation { "test-app-sentry", "test-app-size", "sentry-samples-netflix-dgs", - "sentry-samples-console-otlp" + "sentry-samples-console-otlp", + "sentry-test-support", + "sentry-system-test-support" ) ) } diff --git a/sentry-system-test-support/api/sentry-system-test-support.api b/sentry-system-test-support/api/sentry-system-test-support.api deleted file mode 100644 index 83a9f288d0c..00000000000 --- a/sentry-system-test-support/api/sentry-system-test-support.api +++ /dev/null @@ -1,619 +0,0 @@ -public final class io/sentry/samples/graphql/AddProjectMutation : com/apollographql/apollo3/api/Mutation { - public static final field Companion Lio/sentry/samples/graphql/AddProjectMutation$Companion; - public static final field OPERATION_ID Ljava/lang/String; - public static final field OPERATION_NAME Ljava/lang/String; - public fun (Ljava/lang/String;)V - public fun adapter ()Lcom/apollographql/apollo3/api/Adapter; - public final fun component1 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;)Lio/sentry/samples/graphql/AddProjectMutation; - public static synthetic fun copy$default (Lio/sentry/samples/graphql/AddProjectMutation;Ljava/lang/String;ILjava/lang/Object;)Lio/sentry/samples/graphql/AddProjectMutation; - public fun document ()Ljava/lang/String; - public fun equals (Ljava/lang/Object;)Z - public final fun getSlug ()Ljava/lang/String; - public fun hashCode ()I - public fun id ()Ljava/lang/String; - public fun name ()Ljava/lang/String; - public fun rootField ()Lcom/apollographql/apollo3/api/CompiledField; - public fun serializeVariables (Lcom/apollographql/apollo3/api/json/JsonWriter;Lcom/apollographql/apollo3/api/CustomScalarAdapters;)V - public fun toString ()Ljava/lang/String; -} - -public final class io/sentry/samples/graphql/AddProjectMutation$Companion { - public final fun getOPERATION_DOCUMENT ()Ljava/lang/String; -} - -public final class io/sentry/samples/graphql/AddProjectMutation$Data : com/apollographql/apollo3/api/Mutation$Data { - public fun (Ljava/lang/String;)V - public final fun component1 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;)Lio/sentry/samples/graphql/AddProjectMutation$Data; - public static synthetic fun copy$default (Lio/sentry/samples/graphql/AddProjectMutation$Data;Ljava/lang/String;ILjava/lang/Object;)Lio/sentry/samples/graphql/AddProjectMutation$Data; - public fun equals (Ljava/lang/Object;)Z - public final fun getAddProject ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class io/sentry/samples/graphql/GreetingQuery : com/apollographql/apollo3/api/Query { - public static final field Companion Lio/sentry/samples/graphql/GreetingQuery$Companion; - public static final field OPERATION_ID Ljava/lang/String; - public static final field OPERATION_NAME Ljava/lang/String; - public fun (Ljava/lang/String;)V - public fun adapter ()Lcom/apollographql/apollo3/api/Adapter; - public final fun component1 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;)Lio/sentry/samples/graphql/GreetingQuery; - public static synthetic fun copy$default (Lio/sentry/samples/graphql/GreetingQuery;Ljava/lang/String;ILjava/lang/Object;)Lio/sentry/samples/graphql/GreetingQuery; - public fun document ()Ljava/lang/String; - public fun equals (Ljava/lang/Object;)Z - public final fun getName ()Ljava/lang/String; - public fun hashCode ()I - public fun id ()Ljava/lang/String; - public fun name ()Ljava/lang/String; - public fun rootField ()Lcom/apollographql/apollo3/api/CompiledField; - public fun serializeVariables (Lcom/apollographql/apollo3/api/json/JsonWriter;Lcom/apollographql/apollo3/api/CustomScalarAdapters;)V - public fun toString ()Ljava/lang/String; -} - -public final class io/sentry/samples/graphql/GreetingQuery$Companion { - public final fun getOPERATION_DOCUMENT ()Ljava/lang/String; -} - -public final class io/sentry/samples/graphql/GreetingQuery$Data : com/apollographql/apollo3/api/Query$Data { - public fun (Ljava/lang/String;)V - public final fun component1 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;)Lio/sentry/samples/graphql/GreetingQuery$Data; - public static synthetic fun copy$default (Lio/sentry/samples/graphql/GreetingQuery$Data;Ljava/lang/String;ILjava/lang/Object;)Lio/sentry/samples/graphql/GreetingQuery$Data; - public fun equals (Ljava/lang/Object;)Z - public final fun getGreeting ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class io/sentry/samples/graphql/ProjectQuery : com/apollographql/apollo3/api/Query { - public static final field Companion Lio/sentry/samples/graphql/ProjectQuery$Companion; - public static final field OPERATION_ID Ljava/lang/String; - public static final field OPERATION_NAME Ljava/lang/String; - public fun (Ljava/lang/String;)V - public fun adapter ()Lcom/apollographql/apollo3/api/Adapter; - public final fun component1 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;)Lio/sentry/samples/graphql/ProjectQuery; - public static synthetic fun copy$default (Lio/sentry/samples/graphql/ProjectQuery;Ljava/lang/String;ILjava/lang/Object;)Lio/sentry/samples/graphql/ProjectQuery; - public fun document ()Ljava/lang/String; - public fun equals (Ljava/lang/Object;)Z - public final fun getSlug ()Ljava/lang/String; - public fun hashCode ()I - public fun id ()Ljava/lang/String; - public fun name ()Ljava/lang/String; - public fun rootField ()Lcom/apollographql/apollo3/api/CompiledField; - public fun serializeVariables (Lcom/apollographql/apollo3/api/json/JsonWriter;Lcom/apollographql/apollo3/api/CustomScalarAdapters;)V - public fun toString ()Ljava/lang/String; -} - -public final class io/sentry/samples/graphql/ProjectQuery$Companion { - public final fun getOPERATION_DOCUMENT ()Ljava/lang/String; -} - -public final class io/sentry/samples/graphql/ProjectQuery$Data : com/apollographql/apollo3/api/Query$Data { - public fun (Lio/sentry/samples/graphql/ProjectQuery$Project;)V - public final fun component1 ()Lio/sentry/samples/graphql/ProjectQuery$Project; - public final fun copy (Lio/sentry/samples/graphql/ProjectQuery$Project;)Lio/sentry/samples/graphql/ProjectQuery$Data; - public static synthetic fun copy$default (Lio/sentry/samples/graphql/ProjectQuery$Data;Lio/sentry/samples/graphql/ProjectQuery$Project;ILjava/lang/Object;)Lio/sentry/samples/graphql/ProjectQuery$Data; - public fun equals (Ljava/lang/Object;)Z - public final fun getProject ()Lio/sentry/samples/graphql/ProjectQuery$Project; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class io/sentry/samples/graphql/ProjectQuery$Project { - public fun (Ljava/lang/String;Ljava/lang/String;Lio/sentry/samples/graphql/type/ProjectStatus;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/lang/String; - public final fun component3 ()Lio/sentry/samples/graphql/type/ProjectStatus; - public final fun copy (Ljava/lang/String;Ljava/lang/String;Lio/sentry/samples/graphql/type/ProjectStatus;)Lio/sentry/samples/graphql/ProjectQuery$Project; - public static synthetic fun copy$default (Lio/sentry/samples/graphql/ProjectQuery$Project;Ljava/lang/String;Ljava/lang/String;Lio/sentry/samples/graphql/type/ProjectStatus;ILjava/lang/Object;)Lio/sentry/samples/graphql/ProjectQuery$Project; - public fun equals (Ljava/lang/Object;)Z - public final fun getName ()Ljava/lang/String; - public final fun getSlug ()Ljava/lang/String; - public final fun getStatus ()Lio/sentry/samples/graphql/type/ProjectStatus; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class io/sentry/samples/graphql/TasksAndAssigneesQuery : com/apollographql/apollo3/api/Query { - public static final field Companion Lio/sentry/samples/graphql/TasksAndAssigneesQuery$Companion; - public static final field OPERATION_ID Ljava/lang/String; - public static final field OPERATION_NAME Ljava/lang/String; - public fun (Ljava/lang/String;)V - public fun adapter ()Lcom/apollographql/apollo3/api/Adapter; - public final fun component1 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;)Lio/sentry/samples/graphql/TasksAndAssigneesQuery; - public static synthetic fun copy$default (Lio/sentry/samples/graphql/TasksAndAssigneesQuery;Ljava/lang/String;ILjava/lang/Object;)Lio/sentry/samples/graphql/TasksAndAssigneesQuery; - public fun document ()Ljava/lang/String; - public fun equals (Ljava/lang/Object;)Z - public final fun getSlug ()Ljava/lang/String; - public fun hashCode ()I - public fun id ()Ljava/lang/String; - public fun name ()Ljava/lang/String; - public fun rootField ()Lcom/apollographql/apollo3/api/CompiledField; - public fun serializeVariables (Lcom/apollographql/apollo3/api/json/JsonWriter;Lcom/apollographql/apollo3/api/CustomScalarAdapters;)V - public fun toString ()Ljava/lang/String; -} - -public final class io/sentry/samples/graphql/TasksAndAssigneesQuery$Assignee { - public fun (Ljava/lang/String;Ljava/lang/String;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;Ljava/lang/String;)Lio/sentry/samples/graphql/TasksAndAssigneesQuery$Assignee; - public static synthetic fun copy$default (Lio/sentry/samples/graphql/TasksAndAssigneesQuery$Assignee;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lio/sentry/samples/graphql/TasksAndAssigneesQuery$Assignee; - public fun equals (Ljava/lang/Object;)Z - public final fun getId ()Ljava/lang/String; - public final fun getName ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class io/sentry/samples/graphql/TasksAndAssigneesQuery$Companion { - public final fun getOPERATION_DOCUMENT ()Ljava/lang/String; -} - -public final class io/sentry/samples/graphql/TasksAndAssigneesQuery$Creator { - public fun (Ljava/lang/String;Ljava/lang/String;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;Ljava/lang/String;)Lio/sentry/samples/graphql/TasksAndAssigneesQuery$Creator; - public static synthetic fun copy$default (Lio/sentry/samples/graphql/TasksAndAssigneesQuery$Creator;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lio/sentry/samples/graphql/TasksAndAssigneesQuery$Creator; - public fun equals (Ljava/lang/Object;)Z - public final fun getId ()Ljava/lang/String; - public final fun getName ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class io/sentry/samples/graphql/TasksAndAssigneesQuery$Data : com/apollographql/apollo3/api/Query$Data { - public fun (Ljava/util/List;)V - public final fun component1 ()Ljava/util/List; - public final fun copy (Ljava/util/List;)Lio/sentry/samples/graphql/TasksAndAssigneesQuery$Data; - public static synthetic fun copy$default (Lio/sentry/samples/graphql/TasksAndAssigneesQuery$Data;Ljava/util/List;ILjava/lang/Object;)Lio/sentry/samples/graphql/TasksAndAssigneesQuery$Data; - public fun equals (Ljava/lang/Object;)Z - public final fun getTasks ()Ljava/util/List; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class io/sentry/samples/graphql/TasksAndAssigneesQuery$Task { - public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/sentry/samples/graphql/TasksAndAssigneesQuery$Assignee;Ljava/lang/String;Lio/sentry/samples/graphql/TasksAndAssigneesQuery$Creator;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/lang/String; - public final fun component3 ()Ljava/lang/String; - public final fun component4 ()Lio/sentry/samples/graphql/TasksAndAssigneesQuery$Assignee; - public final fun component5 ()Ljava/lang/String; - public final fun component6 ()Lio/sentry/samples/graphql/TasksAndAssigneesQuery$Creator; - public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/sentry/samples/graphql/TasksAndAssigneesQuery$Assignee;Ljava/lang/String;Lio/sentry/samples/graphql/TasksAndAssigneesQuery$Creator;)Lio/sentry/samples/graphql/TasksAndAssigneesQuery$Task; - public static synthetic fun copy$default (Lio/sentry/samples/graphql/TasksAndAssigneesQuery$Task;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/sentry/samples/graphql/TasksAndAssigneesQuery$Assignee;Ljava/lang/String;Lio/sentry/samples/graphql/TasksAndAssigneesQuery$Creator;ILjava/lang/Object;)Lio/sentry/samples/graphql/TasksAndAssigneesQuery$Task; - public fun equals (Ljava/lang/Object;)Z - public final fun getAssignee ()Lio/sentry/samples/graphql/TasksAndAssigneesQuery$Assignee; - public final fun getAssigneeId ()Ljava/lang/String; - public final fun getCreator ()Lio/sentry/samples/graphql/TasksAndAssigneesQuery$Creator; - public final fun getCreatorId ()Ljava/lang/String; - public final fun getId ()Ljava/lang/String; - public final fun getName ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class io/sentry/samples/graphql/adapter/AddProjectMutation_ResponseAdapter { - public static final field INSTANCE Lio/sentry/samples/graphql/adapter/AddProjectMutation_ResponseAdapter; -} - -public final class io/sentry/samples/graphql/adapter/AddProjectMutation_ResponseAdapter$Data : com/apollographql/apollo3/api/Adapter { - public static final field INSTANCE Lio/sentry/samples/graphql/adapter/AddProjectMutation_ResponseAdapter$Data; - public fun fromJson (Lcom/apollographql/apollo3/api/json/JsonReader;Lcom/apollographql/apollo3/api/CustomScalarAdapters;)Lio/sentry/samples/graphql/AddProjectMutation$Data; - public synthetic fun fromJson (Lcom/apollographql/apollo3/api/json/JsonReader;Lcom/apollographql/apollo3/api/CustomScalarAdapters;)Ljava/lang/Object; - public final fun getRESPONSE_NAMES ()Ljava/util/List; - public fun toJson (Lcom/apollographql/apollo3/api/json/JsonWriter;Lcom/apollographql/apollo3/api/CustomScalarAdapters;Lio/sentry/samples/graphql/AddProjectMutation$Data;)V - public synthetic fun toJson (Lcom/apollographql/apollo3/api/json/JsonWriter;Lcom/apollographql/apollo3/api/CustomScalarAdapters;Ljava/lang/Object;)V -} - -public final class io/sentry/samples/graphql/adapter/AddProjectMutation_VariablesAdapter : com/apollographql/apollo3/api/Adapter { - public static final field INSTANCE Lio/sentry/samples/graphql/adapter/AddProjectMutation_VariablesAdapter; - public fun fromJson (Lcom/apollographql/apollo3/api/json/JsonReader;Lcom/apollographql/apollo3/api/CustomScalarAdapters;)Lio/sentry/samples/graphql/AddProjectMutation; - public synthetic fun fromJson (Lcom/apollographql/apollo3/api/json/JsonReader;Lcom/apollographql/apollo3/api/CustomScalarAdapters;)Ljava/lang/Object; - public fun toJson (Lcom/apollographql/apollo3/api/json/JsonWriter;Lcom/apollographql/apollo3/api/CustomScalarAdapters;Lio/sentry/samples/graphql/AddProjectMutation;)V - public synthetic fun toJson (Lcom/apollographql/apollo3/api/json/JsonWriter;Lcom/apollographql/apollo3/api/CustomScalarAdapters;Ljava/lang/Object;)V -} - -public final class io/sentry/samples/graphql/adapter/GreetingQuery_ResponseAdapter { - public static final field INSTANCE Lio/sentry/samples/graphql/adapter/GreetingQuery_ResponseAdapter; -} - -public final class io/sentry/samples/graphql/adapter/GreetingQuery_ResponseAdapter$Data : com/apollographql/apollo3/api/Adapter { - public static final field INSTANCE Lio/sentry/samples/graphql/adapter/GreetingQuery_ResponseAdapter$Data; - public fun fromJson (Lcom/apollographql/apollo3/api/json/JsonReader;Lcom/apollographql/apollo3/api/CustomScalarAdapters;)Lio/sentry/samples/graphql/GreetingQuery$Data; - public synthetic fun fromJson (Lcom/apollographql/apollo3/api/json/JsonReader;Lcom/apollographql/apollo3/api/CustomScalarAdapters;)Ljava/lang/Object; - public final fun getRESPONSE_NAMES ()Ljava/util/List; - public fun toJson (Lcom/apollographql/apollo3/api/json/JsonWriter;Lcom/apollographql/apollo3/api/CustomScalarAdapters;Lio/sentry/samples/graphql/GreetingQuery$Data;)V - public synthetic fun toJson (Lcom/apollographql/apollo3/api/json/JsonWriter;Lcom/apollographql/apollo3/api/CustomScalarAdapters;Ljava/lang/Object;)V -} - -public final class io/sentry/samples/graphql/adapter/GreetingQuery_VariablesAdapter : com/apollographql/apollo3/api/Adapter { - public static final field INSTANCE Lio/sentry/samples/graphql/adapter/GreetingQuery_VariablesAdapter; - public fun fromJson (Lcom/apollographql/apollo3/api/json/JsonReader;Lcom/apollographql/apollo3/api/CustomScalarAdapters;)Lio/sentry/samples/graphql/GreetingQuery; - public synthetic fun fromJson (Lcom/apollographql/apollo3/api/json/JsonReader;Lcom/apollographql/apollo3/api/CustomScalarAdapters;)Ljava/lang/Object; - public fun toJson (Lcom/apollographql/apollo3/api/json/JsonWriter;Lcom/apollographql/apollo3/api/CustomScalarAdapters;Lio/sentry/samples/graphql/GreetingQuery;)V - public synthetic fun toJson (Lcom/apollographql/apollo3/api/json/JsonWriter;Lcom/apollographql/apollo3/api/CustomScalarAdapters;Ljava/lang/Object;)V -} - -public final class io/sentry/samples/graphql/adapter/ProjectQuery_ResponseAdapter { - public static final field INSTANCE Lio/sentry/samples/graphql/adapter/ProjectQuery_ResponseAdapter; -} - -public final class io/sentry/samples/graphql/adapter/ProjectQuery_ResponseAdapter$Data : com/apollographql/apollo3/api/Adapter { - public static final field INSTANCE Lio/sentry/samples/graphql/adapter/ProjectQuery_ResponseAdapter$Data; - public fun fromJson (Lcom/apollographql/apollo3/api/json/JsonReader;Lcom/apollographql/apollo3/api/CustomScalarAdapters;)Lio/sentry/samples/graphql/ProjectQuery$Data; - public synthetic fun fromJson (Lcom/apollographql/apollo3/api/json/JsonReader;Lcom/apollographql/apollo3/api/CustomScalarAdapters;)Ljava/lang/Object; - public final fun getRESPONSE_NAMES ()Ljava/util/List; - public fun toJson (Lcom/apollographql/apollo3/api/json/JsonWriter;Lcom/apollographql/apollo3/api/CustomScalarAdapters;Lio/sentry/samples/graphql/ProjectQuery$Data;)V - public synthetic fun toJson (Lcom/apollographql/apollo3/api/json/JsonWriter;Lcom/apollographql/apollo3/api/CustomScalarAdapters;Ljava/lang/Object;)V -} - -public final class io/sentry/samples/graphql/adapter/ProjectQuery_ResponseAdapter$Project : com/apollographql/apollo3/api/Adapter { - public static final field INSTANCE Lio/sentry/samples/graphql/adapter/ProjectQuery_ResponseAdapter$Project; - public fun fromJson (Lcom/apollographql/apollo3/api/json/JsonReader;Lcom/apollographql/apollo3/api/CustomScalarAdapters;)Lio/sentry/samples/graphql/ProjectQuery$Project; - public synthetic fun fromJson (Lcom/apollographql/apollo3/api/json/JsonReader;Lcom/apollographql/apollo3/api/CustomScalarAdapters;)Ljava/lang/Object; - public final fun getRESPONSE_NAMES ()Ljava/util/List; - public fun toJson (Lcom/apollographql/apollo3/api/json/JsonWriter;Lcom/apollographql/apollo3/api/CustomScalarAdapters;Lio/sentry/samples/graphql/ProjectQuery$Project;)V - public synthetic fun toJson (Lcom/apollographql/apollo3/api/json/JsonWriter;Lcom/apollographql/apollo3/api/CustomScalarAdapters;Ljava/lang/Object;)V -} - -public final class io/sentry/samples/graphql/adapter/ProjectQuery_VariablesAdapter : com/apollographql/apollo3/api/Adapter { - public static final field INSTANCE Lio/sentry/samples/graphql/adapter/ProjectQuery_VariablesAdapter; - public fun fromJson (Lcom/apollographql/apollo3/api/json/JsonReader;Lcom/apollographql/apollo3/api/CustomScalarAdapters;)Lio/sentry/samples/graphql/ProjectQuery; - public synthetic fun fromJson (Lcom/apollographql/apollo3/api/json/JsonReader;Lcom/apollographql/apollo3/api/CustomScalarAdapters;)Ljava/lang/Object; - public fun toJson (Lcom/apollographql/apollo3/api/json/JsonWriter;Lcom/apollographql/apollo3/api/CustomScalarAdapters;Lio/sentry/samples/graphql/ProjectQuery;)V - public synthetic fun toJson (Lcom/apollographql/apollo3/api/json/JsonWriter;Lcom/apollographql/apollo3/api/CustomScalarAdapters;Ljava/lang/Object;)V -} - -public final class io/sentry/samples/graphql/adapter/TasksAndAssigneesQuery_ResponseAdapter { - public static final field INSTANCE Lio/sentry/samples/graphql/adapter/TasksAndAssigneesQuery_ResponseAdapter; -} - -public final class io/sentry/samples/graphql/adapter/TasksAndAssigneesQuery_ResponseAdapter$Assignee : com/apollographql/apollo3/api/Adapter { - public static final field INSTANCE Lio/sentry/samples/graphql/adapter/TasksAndAssigneesQuery_ResponseAdapter$Assignee; - public fun fromJson (Lcom/apollographql/apollo3/api/json/JsonReader;Lcom/apollographql/apollo3/api/CustomScalarAdapters;)Lio/sentry/samples/graphql/TasksAndAssigneesQuery$Assignee; - public synthetic fun fromJson (Lcom/apollographql/apollo3/api/json/JsonReader;Lcom/apollographql/apollo3/api/CustomScalarAdapters;)Ljava/lang/Object; - public final fun getRESPONSE_NAMES ()Ljava/util/List; - public fun toJson (Lcom/apollographql/apollo3/api/json/JsonWriter;Lcom/apollographql/apollo3/api/CustomScalarAdapters;Lio/sentry/samples/graphql/TasksAndAssigneesQuery$Assignee;)V - public synthetic fun toJson (Lcom/apollographql/apollo3/api/json/JsonWriter;Lcom/apollographql/apollo3/api/CustomScalarAdapters;Ljava/lang/Object;)V -} - -public final class io/sentry/samples/graphql/adapter/TasksAndAssigneesQuery_ResponseAdapter$Creator : com/apollographql/apollo3/api/Adapter { - public static final field INSTANCE Lio/sentry/samples/graphql/adapter/TasksAndAssigneesQuery_ResponseAdapter$Creator; - public fun fromJson (Lcom/apollographql/apollo3/api/json/JsonReader;Lcom/apollographql/apollo3/api/CustomScalarAdapters;)Lio/sentry/samples/graphql/TasksAndAssigneesQuery$Creator; - public synthetic fun fromJson (Lcom/apollographql/apollo3/api/json/JsonReader;Lcom/apollographql/apollo3/api/CustomScalarAdapters;)Ljava/lang/Object; - public final fun getRESPONSE_NAMES ()Ljava/util/List; - public fun toJson (Lcom/apollographql/apollo3/api/json/JsonWriter;Lcom/apollographql/apollo3/api/CustomScalarAdapters;Lio/sentry/samples/graphql/TasksAndAssigneesQuery$Creator;)V - public synthetic fun toJson (Lcom/apollographql/apollo3/api/json/JsonWriter;Lcom/apollographql/apollo3/api/CustomScalarAdapters;Ljava/lang/Object;)V -} - -public final class io/sentry/samples/graphql/adapter/TasksAndAssigneesQuery_ResponseAdapter$Data : com/apollographql/apollo3/api/Adapter { - public static final field INSTANCE Lio/sentry/samples/graphql/adapter/TasksAndAssigneesQuery_ResponseAdapter$Data; - public fun fromJson (Lcom/apollographql/apollo3/api/json/JsonReader;Lcom/apollographql/apollo3/api/CustomScalarAdapters;)Lio/sentry/samples/graphql/TasksAndAssigneesQuery$Data; - public synthetic fun fromJson (Lcom/apollographql/apollo3/api/json/JsonReader;Lcom/apollographql/apollo3/api/CustomScalarAdapters;)Ljava/lang/Object; - public final fun getRESPONSE_NAMES ()Ljava/util/List; - public fun toJson (Lcom/apollographql/apollo3/api/json/JsonWriter;Lcom/apollographql/apollo3/api/CustomScalarAdapters;Lio/sentry/samples/graphql/TasksAndAssigneesQuery$Data;)V - public synthetic fun toJson (Lcom/apollographql/apollo3/api/json/JsonWriter;Lcom/apollographql/apollo3/api/CustomScalarAdapters;Ljava/lang/Object;)V -} - -public final class io/sentry/samples/graphql/adapter/TasksAndAssigneesQuery_ResponseAdapter$Task : com/apollographql/apollo3/api/Adapter { - public static final field INSTANCE Lio/sentry/samples/graphql/adapter/TasksAndAssigneesQuery_ResponseAdapter$Task; - public fun fromJson (Lcom/apollographql/apollo3/api/json/JsonReader;Lcom/apollographql/apollo3/api/CustomScalarAdapters;)Lio/sentry/samples/graphql/TasksAndAssigneesQuery$Task; - public synthetic fun fromJson (Lcom/apollographql/apollo3/api/json/JsonReader;Lcom/apollographql/apollo3/api/CustomScalarAdapters;)Ljava/lang/Object; - public final fun getRESPONSE_NAMES ()Ljava/util/List; - public fun toJson (Lcom/apollographql/apollo3/api/json/JsonWriter;Lcom/apollographql/apollo3/api/CustomScalarAdapters;Lio/sentry/samples/graphql/TasksAndAssigneesQuery$Task;)V - public synthetic fun toJson (Lcom/apollographql/apollo3/api/json/JsonWriter;Lcom/apollographql/apollo3/api/CustomScalarAdapters;Ljava/lang/Object;)V -} - -public final class io/sentry/samples/graphql/adapter/TasksAndAssigneesQuery_VariablesAdapter : com/apollographql/apollo3/api/Adapter { - public static final field INSTANCE Lio/sentry/samples/graphql/adapter/TasksAndAssigneesQuery_VariablesAdapter; - public fun fromJson (Lcom/apollographql/apollo3/api/json/JsonReader;Lcom/apollographql/apollo3/api/CustomScalarAdapters;)Lio/sentry/samples/graphql/TasksAndAssigneesQuery; - public synthetic fun fromJson (Lcom/apollographql/apollo3/api/json/JsonReader;Lcom/apollographql/apollo3/api/CustomScalarAdapters;)Ljava/lang/Object; - public fun toJson (Lcom/apollographql/apollo3/api/json/JsonWriter;Lcom/apollographql/apollo3/api/CustomScalarAdapters;Lio/sentry/samples/graphql/TasksAndAssigneesQuery;)V - public synthetic fun toJson (Lcom/apollographql/apollo3/api/json/JsonWriter;Lcom/apollographql/apollo3/api/CustomScalarAdapters;Ljava/lang/Object;)V -} - -public final class io/sentry/samples/graphql/selections/AddProjectMutationSelections { - public static final field INSTANCE Lio/sentry/samples/graphql/selections/AddProjectMutationSelections; - public final fun get__root ()Ljava/util/List; -} - -public final class io/sentry/samples/graphql/selections/GreetingQuerySelections { - public static final field INSTANCE Lio/sentry/samples/graphql/selections/GreetingQuerySelections; - public final fun get__root ()Ljava/util/List; -} - -public final class io/sentry/samples/graphql/selections/ProjectQuerySelections { - public static final field INSTANCE Lio/sentry/samples/graphql/selections/ProjectQuerySelections; - public final fun get__root ()Ljava/util/List; -} - -public final class io/sentry/samples/graphql/selections/TasksAndAssigneesQuerySelections { - public static final field INSTANCE Lio/sentry/samples/graphql/selections/TasksAndAssigneesQuerySelections; - public final fun get__root ()Ljava/util/List; -} - -public final class io/sentry/samples/graphql/type/Assignee { - public static final field Companion Lio/sentry/samples/graphql/type/Assignee$Companion; - public fun ()V -} - -public final class io/sentry/samples/graphql/type/Assignee$Companion { - public final fun getType ()Lcom/apollographql/apollo3/api/ObjectType; -} - -public final class io/sentry/samples/graphql/type/Creator { - public static final field Companion Lio/sentry/samples/graphql/type/Creator$Companion; - public fun ()V -} - -public final class io/sentry/samples/graphql/type/Creator$Companion { - public final fun getType ()Lcom/apollographql/apollo3/api/ObjectType; -} - -public final class io/sentry/samples/graphql/type/GraphQLBoolean { - public static final field Companion Lio/sentry/samples/graphql/type/GraphQLBoolean$Companion; - public fun ()V -} - -public final class io/sentry/samples/graphql/type/GraphQLBoolean$Companion { - public final fun getType ()Lcom/apollographql/apollo3/api/CustomScalarType; -} - -public final class io/sentry/samples/graphql/type/GraphQLFloat { - public static final field Companion Lio/sentry/samples/graphql/type/GraphQLFloat$Companion; - public fun ()V -} - -public final class io/sentry/samples/graphql/type/GraphQLFloat$Companion { - public final fun getType ()Lcom/apollographql/apollo3/api/CustomScalarType; -} - -public final class io/sentry/samples/graphql/type/GraphQLID { - public static final field Companion Lio/sentry/samples/graphql/type/GraphQLID$Companion; - public fun ()V -} - -public final class io/sentry/samples/graphql/type/GraphQLID$Companion { - public final fun getType ()Lcom/apollographql/apollo3/api/CustomScalarType; -} - -public final class io/sentry/samples/graphql/type/GraphQLInt { - public static final field Companion Lio/sentry/samples/graphql/type/GraphQLInt$Companion; - public fun ()V -} - -public final class io/sentry/samples/graphql/type/GraphQLInt$Companion { - public final fun getType ()Lcom/apollographql/apollo3/api/CustomScalarType; -} - -public final class io/sentry/samples/graphql/type/GraphQLString { - public static final field Companion Lio/sentry/samples/graphql/type/GraphQLString$Companion; - public fun ()V -} - -public final class io/sentry/samples/graphql/type/GraphQLString$Companion { - public final fun getType ()Lcom/apollographql/apollo3/api/CustomScalarType; -} - -public final class io/sentry/samples/graphql/type/Mutation { - public static final field Companion Lio/sentry/samples/graphql/type/Mutation$Companion; - public fun ()V -} - -public final class io/sentry/samples/graphql/type/Mutation$Companion { - public final fun getType ()Lcom/apollographql/apollo3/api/ObjectType; -} - -public final class io/sentry/samples/graphql/type/Project { - public static final field Companion Lio/sentry/samples/graphql/type/Project$Companion; - public fun ()V -} - -public final class io/sentry/samples/graphql/type/Project$Companion { - public final fun getType ()Lcom/apollographql/apollo3/api/ObjectType; -} - -public final class io/sentry/samples/graphql/type/ProjectStatus : java/lang/Enum { - public static final field ACTIVE Lio/sentry/samples/graphql/type/ProjectStatus; - public static final field ATTIC Lio/sentry/samples/graphql/type/ProjectStatus; - public static final field COMMUNITY Lio/sentry/samples/graphql/type/ProjectStatus; - public static final field Companion Lio/sentry/samples/graphql/type/ProjectStatus$Companion; - public static final field EOL Lio/sentry/samples/graphql/type/ProjectStatus; - public static final field INCUBATING Lio/sentry/samples/graphql/type/ProjectStatus; - public static final field UNKNOWN__ Lio/sentry/samples/graphql/type/ProjectStatus; - public static fun getEntries ()Lkotlin/enums/EnumEntries; - public final fun getRawValue ()Ljava/lang/String; - public static fun valueOf (Ljava/lang/String;)Lio/sentry/samples/graphql/type/ProjectStatus; - public static fun values ()[Lio/sentry/samples/graphql/type/ProjectStatus; -} - -public final class io/sentry/samples/graphql/type/ProjectStatus$Companion { - public final fun getType ()Lcom/apollographql/apollo3/api/EnumType; - public final fun knownValues ()[Lio/sentry/samples/graphql/type/ProjectStatus; - public final fun safeValueOf (Ljava/lang/String;)Lio/sentry/samples/graphql/type/ProjectStatus; -} - -public final class io/sentry/samples/graphql/type/Query { - public static final field Companion Lio/sentry/samples/graphql/type/Query$Companion; - public fun ()V -} - -public final class io/sentry/samples/graphql/type/Query$Companion { - public final fun getType ()Lcom/apollographql/apollo3/api/ObjectType; -} - -public final class io/sentry/samples/graphql/type/Task { - public static final field Companion Lio/sentry/samples/graphql/type/Task$Companion; - public fun ()V -} - -public final class io/sentry/samples/graphql/type/Task$Companion { - public final fun getType ()Lcom/apollographql/apollo3/api/ObjectType; -} - -public final class io/sentry/samples/graphql/type/adapter/ProjectStatus_ResponseAdapter : com/apollographql/apollo3/api/Adapter { - public static final field INSTANCE Lio/sentry/samples/graphql/type/adapter/ProjectStatus_ResponseAdapter; - public fun fromJson (Lcom/apollographql/apollo3/api/json/JsonReader;Lcom/apollographql/apollo3/api/CustomScalarAdapters;)Lio/sentry/samples/graphql/type/ProjectStatus; - public synthetic fun fromJson (Lcom/apollographql/apollo3/api/json/JsonReader;Lcom/apollographql/apollo3/api/CustomScalarAdapters;)Ljava/lang/Object; - public fun toJson (Lcom/apollographql/apollo3/api/json/JsonWriter;Lcom/apollographql/apollo3/api/CustomScalarAdapters;Lio/sentry/samples/graphql/type/ProjectStatus;)V - public synthetic fun toJson (Lcom/apollographql/apollo3/api/json/JsonWriter;Lcom/apollographql/apollo3/api/CustomScalarAdapters;Ljava/lang/Object;)V -} - -public final class io/sentry/systemtest/Person { - public fun (Ljava/lang/String;Ljava/lang/String;)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;Ljava/lang/String;)Lio/sentry/systemtest/Person; - public static synthetic fun copy$default (Lio/sentry/systemtest/Person;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lio/sentry/systemtest/Person; - public fun equals (Ljava/lang/Object;)Z - public final fun getFirstName ()Ljava/lang/String; - public final fun getLastName ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class io/sentry/systemtest/Todo { - public fun (JLjava/lang/String;Z)V - public final fun component1 ()J - public final fun component2 ()Ljava/lang/String; - public final fun component3 ()Z - public final fun copy (JLjava/lang/String;Z)Lio/sentry/systemtest/Todo; - public static synthetic fun copy$default (Lio/sentry/systemtest/Todo;JLjava/lang/String;ZILjava/lang/Object;)Lio/sentry/systemtest/Todo; - public fun equals (Ljava/lang/Object;)Z - public final fun getCompleted ()Z - public final fun getId ()J - public final fun getTitle ()Ljava/lang/String; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class io/sentry/systemtest/graphql/GraphqlTestClient { - public fun (Ljava/lang/String;)V - public final fun addProject (Ljava/lang/String;)Lcom/apollographql/apollo3/api/ApolloResponse; - public final fun getApollo ()Lcom/apollographql/apollo3/ApolloClient; - public final fun greet (Ljava/lang/String;)Lcom/apollographql/apollo3/api/ApolloResponse; - public final fun project (Ljava/lang/String;)Lcom/apollographql/apollo3/api/ApolloResponse; - public final fun tasksAndAssignees (Ljava/lang/String;)Lcom/apollographql/apollo3/api/ApolloResponse; -} - -public final class io/sentry/systemtest/util/EnvelopeCounts { - public fun ()V - public final fun getEnvelopes ()Ljava/lang/Long; - public fun toString ()Ljava/lang/String; -} - -public final class io/sentry/systemtest/util/EnvelopesReceived { - public fun ()V - public final fun getEnvelopes ()Ljava/util/List; - public fun toString ()Ljava/lang/String; -} - -public final class io/sentry/systemtest/util/FeatureFlagResponse { - public fun (Ljava/lang/String;Z)V - public final fun component1 ()Ljava/lang/String; - public final fun component2 ()Z - public final fun copy (Ljava/lang/String;Z)Lio/sentry/systemtest/util/FeatureFlagResponse; - public static synthetic fun copy$default (Lio/sentry/systemtest/util/FeatureFlagResponse;Ljava/lang/String;ZILjava/lang/Object;)Lio/sentry/systemtest/util/FeatureFlagResponse; - public fun equals (Ljava/lang/Object;)Z - public final fun getFlagKey ()Ljava/lang/String; - public final fun getValue ()Z - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public class io/sentry/systemtest/util/LoggingInsecureRestClient { - public fun ()V - protected final fun call (Lokhttp3/Request$Builder;ZLjava/util/Map;)Lokhttp3/Response; - public static synthetic fun call$default (Lio/sentry/systemtest/util/LoggingInsecureRestClient;Lokhttp3/Request$Builder;ZLjava/util/Map;ILjava/lang/Object;)Lokhttp3/Response; - protected final fun client ()Lokhttp3/OkHttpClient; - public final fun getLastKnownStatusCode ()Ljava/lang/Integer; - public final fun getLogger ()Lorg/slf4j/Logger; - protected final fun objectMapper ()Lcom/fasterxml/jackson/databind/ObjectMapper; - public final fun setLastKnownStatusCode (Ljava/lang/Integer;)V - protected final fun toRequestBody (Ljava/lang/Object;)Lokhttp3/RequestBody; -} - -public final class io/sentry/systemtest/util/RestTestClient : io/sentry/systemtest/util/LoggingInsecureRestClient { - public fun (Ljava/lang/String;)V - public final fun checkFeatureFlag (Ljava/lang/String;)Lio/sentry/systemtest/util/FeatureFlagResponse; - public final fun createPerson (Lio/sentry/systemtest/Person;Ljava/util/Map;)Lio/sentry/systemtest/Person; - public static synthetic fun createPerson$default (Lio/sentry/systemtest/util/RestTestClient;Lio/sentry/systemtest/Person;Ljava/util/Map;ILjava/lang/Object;)Lio/sentry/systemtest/Person; - public final fun createPersonDistributedTracing (Lio/sentry/systemtest/Person;Ljava/util/Map;)Lio/sentry/systemtest/Person; - public static synthetic fun createPersonDistributedTracing$default (Lio/sentry/systemtest/util/RestTestClient;Lio/sentry/systemtest/Person;Ljava/util/Map;ILjava/lang/Object;)Lio/sentry/systemtest/Person; - public final fun deleteCachedTodo (J)V - public final fun errorWithFeatureFlag (Ljava/lang/String;)Ljava/lang/String; - public final fun getCachedTodo (J)Lio/sentry/systemtest/Todo; - public final fun getCountMetric ()Ljava/lang/String; - public final fun getDistributionMetric (J)Ljava/lang/String; - public final fun getGaugeMetric (J)Ljava/lang/String; - public final fun getPerson (J)Lio/sentry/systemtest/Person; - public final fun getPersonDistributedTracing (JLjava/util/Map;)Lio/sentry/systemtest/Person; - public static synthetic fun getPersonDistributedTracing$default (Lio/sentry/systemtest/util/RestTestClient;JLjava/util/Map;ILjava/lang/Object;)Lio/sentry/systemtest/Person; - public final fun getTodo (J)Lio/sentry/systemtest/Todo; - public final fun getTodoRestClient (J)Lio/sentry/systemtest/Todo; - public final fun getTodoWebclient (J)Lio/sentry/systemtest/Todo; - public final fun saveCachedTodo (Lio/sentry/systemtest/Todo;)Lio/sentry/systemtest/Todo; -} - -public final class io/sentry/systemtest/util/SentryMockServerClient : io/sentry/systemtest/util/LoggingInsecureRestClient { - public fun (Ljava/lang/String;)V - public final fun getEnvelopeCount ()Lio/sentry/systemtest/util/EnvelopeCounts; - public final fun getEnvelopes ()Lio/sentry/systemtest/util/EnvelopesReceived; - public final fun reset ()V -} - -public final class io/sentry/systemtest/util/TestHelper { - public fun (Ljava/lang/String;)V - public final fun doesContainLogWithBody (Lio/sentry/SentryLogEvents;Ljava/lang/String;)Z - public final fun doesContainMetric (Lio/sentry/SentryMetricsEvents;Ljava/lang/String;Ljava/lang/String;DLjava/lang/String;)Z - public static synthetic fun doesContainMetric$default (Lio/sentry/systemtest/util/TestHelper;Lio/sentry/SentryMetricsEvents;Ljava/lang/String;Ljava/lang/String;DLjava/lang/String;ILjava/lang/Object;)Z - public final fun doesEventHaveExceptionMessage (Lio/sentry/SentryEvent;Ljava/lang/String;)Z - public final fun doesEventHaveFlag (Lio/sentry/SentryEvent;Ljava/lang/String;Z)Z - public final fun doesLogWithBodyHaveAttribute (Lio/sentry/SentryLogEvents;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Object;)Z - public final fun doesMetricHaveAttribute (Lio/sentry/SentryMetricsEvents;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Object;)Z - public final fun doesTransactionContainSpanWithDescription (Lio/sentry/protocol/SentryTransaction;Ljava/lang/String;)Z - public final fun doesTransactionContainSpanWithOp (Lio/sentry/protocol/SentryTransaction;Ljava/lang/String;)Z - public final fun doesTransactionContainSpanWithOpAndDescription (Lio/sentry/protocol/SentryTransaction;Ljava/lang/String;Ljava/lang/String;)Z - public final fun doesTransactionHave (Lio/sentry/protocol/SentryTransaction;Ljava/lang/String;Lio/sentry/protocol/FeatureFlag;)Z - public static synthetic fun doesTransactionHave$default (Lio/sentry/systemtest/util/TestHelper;Lio/sentry/protocol/SentryTransaction;Ljava/lang/String;Lio/sentry/protocol/FeatureFlag;ILjava/lang/Object;)Z - public final fun doesTransactionHaveOp (Lio/sentry/protocol/SentryTransaction;Ljava/lang/String;)Z - public final fun doesTransactionHaveSpanWith (Lio/sentry/protocol/SentryTransaction;Ljava/lang/String;Lio/sentry/protocol/FeatureFlag;Z)Z - public static synthetic fun doesTransactionHaveSpanWith$default (Lio/sentry/systemtest/util/TestHelper;Lio/sentry/protocol/SentryTransaction;Ljava/lang/String;Lio/sentry/protocol/FeatureFlag;ZILjava/lang/Object;)Z - public final fun doesTransactionHaveTraceId (Lio/sentry/protocol/SentryTransaction;Ljava/lang/String;)Z - public final fun ensureEnvelopeCountIncreased ()V - public final fun ensureEnvelopeReceived (ILkotlin/jvm/functions/Function1;)V - public static synthetic fun ensureEnvelopeReceived$default (Lio/sentry/systemtest/util/TestHelper;ILkotlin/jvm/functions/Function1;ILjava/lang/Object;)V - public final fun ensureErrorCount (Lcom/apollographql/apollo3/api/ApolloResponse;I)V - public final fun ensureErrorReceived (Lkotlin/jvm/functions/Function1;)V - public final fun ensureLogsReceived (Lkotlin/jvm/functions/Function2;)V - public final fun ensureMetricsReceived (Lkotlin/jvm/functions/Function2;)V - public final fun ensureNoEnvelopeReceived (Lkotlin/jvm/functions/Function1;)V - public final fun ensureNoErrors (Lcom/apollographql/apollo3/api/ApolloResponse;)V - public final fun ensureNoTransactionReceived (Lkotlin/jvm/functions/Function2;)V - public final fun ensureProfileChunkReceived (Lkotlin/jvm/functions/Function2;)V - public final fun ensureTransactionReceived (Lkotlin/jvm/functions/Function2;)V - public final fun ensureTransactionWithSpanReceived (Lkotlin/jvm/functions/Function1;)V - public final fun findJar (Ljava/lang/String;Ljava/lang/String;)Ljava/io/File; - public static synthetic fun findJar$default (Lio/sentry/systemtest/util/TestHelper;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Ljava/io/File; - public final fun getDsn ()Ljava/lang/String; - public final fun getEnvelopeCounts ()Lio/sentry/systemtest/util/EnvelopeCounts; - public final fun getGraphqlClient ()Lio/sentry/systemtest/graphql/GraphqlTestClient; - public final fun getJsonSerializer ()Lio/sentry/JsonSerializer; - public final fun getRestClient ()Lio/sentry/systemtest/util/RestTestClient; - public final fun getSentryClient ()Lio/sentry/systemtest/util/SentryMockServerClient; - public final fun launch (Ljava/io/File;Ljava/util/Map;Z)Ljava/lang/Process; - public static synthetic fun launch$default (Lio/sentry/systemtest/util/TestHelper;Ljava/io/File;Ljava/util/Map;ZILjava/lang/Object;)Ljava/lang/Process; - public final fun logObject (Ljava/lang/Object;)V - public final fun reset ()V - public final fun setEnvelopeCounts (Lio/sentry/systemtest/util/EnvelopeCounts;)V - public final fun snapshotEnvelopeCount ()V -} - diff --git a/sentry-test-support/api/sentry-test-support.api b/sentry-test-support/api/sentry-test-support.api deleted file mode 100644 index 1d8ae671216..00000000000 --- a/sentry-test-support/api/sentry-test-support.api +++ /dev/null @@ -1,75 +0,0 @@ -public final class io/sentry/AssertionsKt { - public static final fun assertEnvelopeEvent (Ljava/util/List;Lio/sentry/ILogger;Lkotlin/jvm/functions/Function2;)Lio/sentry/SentryEvent; - public static synthetic fun assertEnvelopeEvent$default (Ljava/util/List;Lio/sentry/ILogger;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lio/sentry/SentryEvent; - public static final fun assertEnvelopeFeedback (Ljava/util/List;Lio/sentry/ILogger;Lkotlin/jvm/functions/Function2;)Lio/sentry/SentryEvent; - public static synthetic fun assertEnvelopeFeedback$default (Ljava/util/List;Lio/sentry/ILogger;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lio/sentry/SentryEvent; - public static final fun assertEnvelopeProfile (Ljava/util/List;Lio/sentry/ILogger;Lkotlin/jvm/functions/Function2;)Lio/sentry/ProfilingTraceData; - public static synthetic fun assertEnvelopeProfile$default (Ljava/util/List;Lio/sentry/ILogger;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lio/sentry/ProfilingTraceData; - public static final fun assertEnvelopeTransaction (Ljava/util/List;Lio/sentry/ILogger;Lkotlin/jvm/functions/Function2;)Lio/sentry/protocol/SentryTransaction; - public static synthetic fun assertEnvelopeTransaction$default (Ljava/util/List;Lio/sentry/ILogger;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lio/sentry/protocol/SentryTransaction; - public static final fun checkEvent (Lkotlin/jvm/functions/Function1;)Lio/sentry/SentryEnvelope; - public static final fun checkLogs (Lkotlin/jvm/functions/Function1;)Lio/sentry/SentryEnvelope; - public static final fun checkTransaction (Lkotlin/jvm/functions/Function1;)Lio/sentry/SentryEnvelope; - public static final fun getMockServerRequestTimeoutMillis ()J -} - -public final class io/sentry/SkipError : java/lang/Error { - public fun (Ljava/lang/String;)V -} - -public final class io/sentry/test/DeferredExecutorService : io/sentry/ISentryExecutorService { - public fun ()V - public fun close (J)V - public final fun hasScheduledRunnables ()Z - public fun isClosed ()Z - public fun prewarm ()V - public final fun runAll ()V - public fun schedule (Ljava/lang/Runnable;J)Ljava/util/concurrent/Future; - public fun submit (Ljava/lang/Runnable;)Ljava/util/concurrent/Future; - public fun submit (Ljava/util/concurrent/Callable;)Ljava/util/concurrent/Future; -} - -public final class io/sentry/test/ImmediateExecutorService : io/sentry/ISentryExecutorService { - public fun ()V - public fun close (J)V - public fun isClosed ()Z - public fun prewarm ()V - public fun schedule (Ljava/lang/Runnable;J)Ljava/util/concurrent/Future; - public fun submit (Ljava/lang/Runnable;)Ljava/util/concurrent/Future; - public fun submit (Ljava/util/concurrent/Callable;)Ljava/util/concurrent/Future; -} - -public final class io/sentry/test/InitKt { - public static final fun applyTestOptions (Lio/sentry/SentryOptions;)V - public static final fun initForTest ()V - public static final fun initForTest (Lio/sentry/Sentry$OptionsConfiguration;)V - public static final fun initForTest (Lio/sentry/Sentry$OptionsConfiguration;Z)V - public static final fun initForTest (Lio/sentry/SentryOptions;)V - public static final fun initForTest (Ljava/lang/String;)V -} - -public final class io/sentry/test/MocksKt { - public static final fun createSentryClientMock (Z)Lio/sentry/ISentryClient; - public static synthetic fun createSentryClientMock$default (ZILjava/lang/Object;)Lio/sentry/ISentryClient; - public static final fun createTestScopes (Lio/sentry/SentryOptions;ZLio/sentry/IScope;Lio/sentry/IScope;Lio/sentry/IScope;)Lio/sentry/Scopes; - public static synthetic fun createTestScopes$default (Lio/sentry/SentryOptions;ZLio/sentry/IScope;Lio/sentry/IScope;Lio/sentry/IScope;ILjava/lang/Object;)Lio/sentry/Scopes; -} - -public final class io/sentry/test/NonOverridableNoOpSentryExecutorService : io/sentry/ISentryExecutorService { - public fun ()V - public fun close (J)V - public fun isClosed ()Z - public fun prewarm ()V - public fun schedule (Ljava/lang/Runnable;J)Ljava/util/concurrent/Future; - public fun submit (Ljava/lang/Runnable;)Ljava/util/concurrent/Future; - public fun submit (Ljava/util/concurrent/Callable;)Ljava/util/concurrent/Future; -} - -public final class io/sentry/test/ReflectionKt { - public static final fun collectInterfaceHierarchy (Ljava/lang/Class;)Ljava/util/List; - public static final fun containsMethod (Ljava/lang/Class;Ljava/lang/String;Ljava/lang/Class;)Z - public static final fun containsMethod (Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/Class;)Z - public static final fun getCtor (Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Constructor; - public static final fun getDeclaredCtor (Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Constructor; -} - From 76eba1229751329a573466e890d48d657bf58892 Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Tue, 7 Apr 2026 16:40:25 +0200 Subject: [PATCH 08/27] Verbose system test output and wire inputs for them properly --- .../build.gradle.kts | 4 ++++ sentry-samples/sentry-samples-console-otlp/build.gradle.kts | 4 ++++ sentry-samples/sentry-samples-console/build.gradle.kts | 4 ++++ sentry-samples/sentry-samples-jul/build.gradle.kts | 4 ++++ sentry-samples/sentry-samples-log4j2/build.gradle.kts | 4 ++++ sentry-samples/sentry-samples-logback/build.gradle.kts | 4 ++++ sentry-samples/sentry-samples-spring-7/build.gradle.kts | 4 ++++ .../build.gradle.kts | 4 ++++ .../build.gradle.kts | 4 ++++ .../sentry-samples-spring-boot-4-otlp/build.gradle.kts | 4 ++++ .../sentry-samples-spring-boot-4-webflux/build.gradle.kts | 4 ++++ sentry-samples/sentry-samples-spring-boot-4/build.gradle.kts | 4 ++++ .../build.gradle.kts | 4 ++++ .../build.gradle.kts | 4 ++++ .../sentry-samples-spring-boot-jakarta/build.gradle.kts | 4 ++++ .../build.gradle.kts | 4 ++++ .../sentry-samples-spring-boot-opentelemetry/build.gradle.kts | 4 ++++ .../build.gradle.kts | 4 ++++ .../sentry-samples-spring-boot-webflux/build.gradle.kts | 4 ++++ sentry-samples/sentry-samples-spring-boot/build.gradle.kts | 4 ++++ sentry-samples/sentry-samples-spring-jakarta/build.gradle.kts | 4 ++++ sentry-samples/sentry-samples-spring/build.gradle.kts | 4 ++++ test/system-test-runner.py | 4 ++-- 23 files changed, 90 insertions(+), 2 deletions(-) diff --git a/sentry-samples/sentry-samples-console-opentelemetry-noagent/build.gradle.kts b/sentry-samples/sentry-samples-console-opentelemetry-noagent/build.gradle.kts index dfab063591b..04ddecc4ec2 100644 --- a/sentry-samples/sentry-samples-console-opentelemetry-noagent/build.gradle.kts +++ b/sentry-samples/sentry-samples-console-opentelemetry-noagent/build.gradle.kts @@ -66,6 +66,10 @@ tasks.register("systemTest").configure { group = "verification" description = "Runs the System tests" + val test = project.extensions.getByType()["test"] + testClassesDirs = test.output.classesDirs + classpath = test.runtimeClasspath + outputs.upToDateWhen { false } maxParallelForks = 1 diff --git a/sentry-samples/sentry-samples-console-otlp/build.gradle.kts b/sentry-samples/sentry-samples-console-otlp/build.gradle.kts index 08b9315f785..cbb3b7c7a64 100644 --- a/sentry-samples/sentry-samples-console-otlp/build.gradle.kts +++ b/sentry-samples/sentry-samples-console-otlp/build.gradle.kts @@ -69,6 +69,10 @@ tasks.register("systemTest").configure { group = "verification" description = "Runs the System tests" + val test = project.extensions.getByType()["test"] + testClassesDirs = test.output.classesDirs + classpath = test.runtimeClasspath + outputs.upToDateWhen { false } maxParallelForks = 1 diff --git a/sentry-samples/sentry-samples-console/build.gradle.kts b/sentry-samples/sentry-samples-console/build.gradle.kts index 6d0a4fc736b..c27196e96b8 100644 --- a/sentry-samples/sentry-samples-console/build.gradle.kts +++ b/sentry-samples/sentry-samples-console/build.gradle.kts @@ -69,6 +69,10 @@ tasks.register("systemTest").configure { group = "verification" description = "Runs the System tests" + val test = project.extensions.getByType()["test"] + testClassesDirs = test.output.classesDirs + classpath = test.runtimeClasspath + outputs.upToDateWhen { false } maxParallelForks = 1 diff --git a/sentry-samples/sentry-samples-jul/build.gradle.kts b/sentry-samples/sentry-samples-jul/build.gradle.kts index ac1afa03d37..01e6a95f13d 100644 --- a/sentry-samples/sentry-samples-jul/build.gradle.kts +++ b/sentry-samples/sentry-samples-jul/build.gradle.kts @@ -62,6 +62,10 @@ tasks.register("systemTest").configure { group = "verification" description = "Runs the System tests" + val test = project.extensions.getByType()["test"] + testClassesDirs = test.output.classesDirs + classpath = test.runtimeClasspath + outputs.upToDateWhen { false } maxParallelForks = 1 diff --git a/sentry-samples/sentry-samples-log4j2/build.gradle.kts b/sentry-samples/sentry-samples-log4j2/build.gradle.kts index 84e6bb6a0ab..213f009376a 100644 --- a/sentry-samples/sentry-samples-log4j2/build.gradle.kts +++ b/sentry-samples/sentry-samples-log4j2/build.gradle.kts @@ -67,6 +67,10 @@ tasks.register("systemTest").configure { group = "verification" description = "Runs the System tests" + val test = project.extensions.getByType()["test"] + testClassesDirs = test.output.classesDirs + classpath = test.runtimeClasspath + outputs.upToDateWhen { false } maxParallelForks = 1 diff --git a/sentry-samples/sentry-samples-logback/build.gradle.kts b/sentry-samples/sentry-samples-logback/build.gradle.kts index 5cd5a2eb85b..05f96c346a8 100644 --- a/sentry-samples/sentry-samples-logback/build.gradle.kts +++ b/sentry-samples/sentry-samples-logback/build.gradle.kts @@ -62,6 +62,10 @@ tasks.register("systemTest").configure { group = "verification" description = "Runs the System tests" + val test = project.extensions.getByType()["test"] + testClassesDirs = test.output.classesDirs + classpath = test.runtimeClasspath + outputs.upToDateWhen { false } maxParallelForks = 1 diff --git a/sentry-samples/sentry-samples-spring-7/build.gradle.kts b/sentry-samples/sentry-samples-spring-7/build.gradle.kts index a8f2dc4da7c..e3300cd2841 100644 --- a/sentry-samples/sentry-samples-spring-7/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-7/build.gradle.kts @@ -73,6 +73,10 @@ tasks.register("systemTest").configure { group = "verification" description = "Runs the System tests" + val test = project.extensions.getByType()["test"] + testClassesDirs = test.output.classesDirs + classpath = test.runtimeClasspath + outputs.upToDateWhen { false } maxParallelForks = 1 diff --git a/sentry-samples/sentry-samples-spring-boot-4-opentelemetry-noagent/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-4-opentelemetry-noagent/build.gradle.kts index 71ff985d67c..a7b2d939cdc 100644 --- a/sentry-samples/sentry-samples-spring-boot-4-opentelemetry-noagent/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-4-opentelemetry-noagent/build.gradle.kts @@ -82,6 +82,10 @@ tasks.register("systemTest").configure { group = "verification" description = "Runs the System tests" + val test = project.extensions.getByType()["test"] + testClassesDirs = test.output.classesDirs + classpath = test.runtimeClasspath + outputs.upToDateWhen { false } maxParallelForks = 1 diff --git a/sentry-samples/sentry-samples-spring-boot-4-opentelemetry/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-4-opentelemetry/build.gradle.kts index c3e8ba06fae..d43a628eb9a 100644 --- a/sentry-samples/sentry-samples-spring-boot-4-opentelemetry/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-4-opentelemetry/build.gradle.kts @@ -110,6 +110,10 @@ tasks.register("systemTest").configure { group = "verification" description = "Runs the System tests" + val test = project.extensions.getByType()["test"] + testClassesDirs = test.output.classesDirs + classpath = test.runtimeClasspath + outputs.upToDateWhen { false } maxParallelForks = 1 diff --git a/sentry-samples/sentry-samples-spring-boot-4-otlp/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-4-otlp/build.gradle.kts index 01e07fc2526..7329d5cc0ea 100644 --- a/sentry-samples/sentry-samples-spring-boot-4-otlp/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-4-otlp/build.gradle.kts @@ -87,6 +87,10 @@ tasks.register("systemTest").configure { group = "verification" description = "Runs the System tests" + val test = project.extensions.getByType()["test"] + testClassesDirs = test.output.classesDirs + classpath = test.runtimeClasspath + outputs.upToDateWhen { false } maxParallelForks = 1 diff --git a/sentry-samples/sentry-samples-spring-boot-4-webflux/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-4-webflux/build.gradle.kts index cdcf65711a8..a311b8a972e 100644 --- a/sentry-samples/sentry-samples-spring-boot-4-webflux/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-4-webflux/build.gradle.kts @@ -66,6 +66,10 @@ tasks.register("systemTest").configure { group = "verification" description = "Runs the System tests" + val test = project.extensions.getByType()["test"] + testClassesDirs = test.output.classesDirs + classpath = test.runtimeClasspath + outputs.upToDateWhen { false } maxParallelForks = 1 diff --git a/sentry-samples/sentry-samples-spring-boot-4/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-4/build.gradle.kts index f43cc47cc6d..d96e5602483 100644 --- a/sentry-samples/sentry-samples-spring-boot-4/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-4/build.gradle.kts @@ -84,6 +84,10 @@ tasks.register("systemTest").configure { group = "verification" description = "Runs the System tests" + val test = project.extensions.getByType()["test"] + testClassesDirs = test.output.classesDirs + classpath = test.runtimeClasspath + outputs.upToDateWhen { false } maxParallelForks = 1 diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/build.gradle.kts index 86914467a6d..c2f180c7f25 100644 --- a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/build.gradle.kts @@ -80,6 +80,10 @@ tasks.register("systemTest").configure { group = "verification" description = "Runs the System tests" + val test = project.extensions.getByType()["test"] + testClassesDirs = test.output.classesDirs + classpath = test.runtimeClasspath + outputs.upToDateWhen { false } maxParallelForks = 1 diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/build.gradle.kts index 37d7a94eec0..4e99209e3c7 100644 --- a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/build.gradle.kts @@ -114,6 +114,10 @@ tasks.register("systemTest").configure { group = "verification" description = "Runs the System tests" + val test = project.extensions.getByType()["test"] + testClassesDirs = test.output.classesDirs + classpath = test.runtimeClasspath + outputs.upToDateWhen { false } maxParallelForks = 1 diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-jakarta/build.gradle.kts index a945b87109a..f5986e683a1 100644 --- a/sentry-samples/sentry-samples-spring-boot-jakarta/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-jakarta/build.gradle.kts @@ -85,6 +85,10 @@ tasks.register("systemTest").configure { group = "verification" description = "Runs the System tests" + val test = project.extensions.getByType()["test"] + testClassesDirs = test.output.classesDirs + classpath = test.runtimeClasspath + outputs.upToDateWhen { false } maxParallelForks = 1 diff --git a/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts index ef379dcf389..f5791ee5ed1 100644 --- a/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts @@ -102,6 +102,10 @@ tasks.register("systemTest").configure { group = "verification" description = "Runs the System tests" + val test = project.extensions.getByType()["test"] + testClassesDirs = test.output.classesDirs + classpath = test.runtimeClasspath + outputs.upToDateWhen { false } maxParallelForks = 1 diff --git a/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts index 2cf1cf41d29..4542215792b 100644 --- a/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts @@ -122,6 +122,10 @@ tasks.register("systemTest").configure { group = "verification" description = "Runs the System tests" + val test = project.extensions.getByType()["test"] + testClassesDirs = test.output.classesDirs + classpath = test.runtimeClasspath + outputs.upToDateWhen { false } maxParallelForks = 1 diff --git a/sentry-samples/sentry-samples-spring-boot-webflux-jakarta/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-webflux-jakarta/build.gradle.kts index a45249830f4..4ed91454122 100644 --- a/sentry-samples/sentry-samples-spring-boot-webflux-jakarta/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-webflux-jakarta/build.gradle.kts @@ -60,6 +60,10 @@ tasks.register("systemTest").configure { group = "verification" description = "Runs the System tests" + val test = project.extensions.getByType()["test"] + testClassesDirs = test.output.classesDirs + classpath = test.runtimeClasspath + outputs.upToDateWhen { false } maxParallelForks = 1 diff --git a/sentry-samples/sentry-samples-spring-boot-webflux/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-webflux/build.gradle.kts index f1552bdf2fe..c4ff81afb8b 100644 --- a/sentry-samples/sentry-samples-spring-boot-webflux/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-webflux/build.gradle.kts @@ -78,6 +78,10 @@ tasks.register("systemTest").configure { group = "verification" description = "Runs the System tests" + val test = project.extensions.getByType()["test"] + testClassesDirs = test.output.classesDirs + classpath = test.runtimeClasspath + outputs.upToDateWhen { false } maxParallelForks = 1 diff --git a/sentry-samples/sentry-samples-spring-boot/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot/build.gradle.kts index 8828edfbe5e..28baa6830cd 100644 --- a/sentry-samples/sentry-samples-spring-boot/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot/build.gradle.kts @@ -98,6 +98,10 @@ tasks.register("systemTest").configure { group = "verification" description = "Runs the System tests" + val test = project.extensions.getByType()["test"] + testClassesDirs = test.output.classesDirs + classpath = test.runtimeClasspath + outputs.upToDateWhen { false } maxParallelForks = 1 diff --git a/sentry-samples/sentry-samples-spring-jakarta/build.gradle.kts b/sentry-samples/sentry-samples-spring-jakarta/build.gradle.kts index 8e450865659..55e350d66c0 100644 --- a/sentry-samples/sentry-samples-spring-jakarta/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-jakarta/build.gradle.kts @@ -72,6 +72,10 @@ tasks.register("systemTest").configure { group = "verification" description = "Runs the System tests" + val test = project.extensions.getByType()["test"] + testClassesDirs = test.output.classesDirs + classpath = test.runtimeClasspath + outputs.upToDateWhen { false } maxParallelForks = 1 diff --git a/sentry-samples/sentry-samples-spring/build.gradle.kts b/sentry-samples/sentry-samples-spring/build.gradle.kts index acc709a4050..fa3de84c25a 100644 --- a/sentry-samples/sentry-samples-spring/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring/build.gradle.kts @@ -71,6 +71,10 @@ tasks.register("systemTest").configure { group = "verification" description = "Runs the System tests" + val test = project.extensions.getByType()["test"] + testClassesDirs = test.output.classesDirs + classpath = test.runtimeClasspath + outputs.upToDateWhen { false } maxParallelForks = 1 diff --git a/test/system-test-runner.py b/test/system-test-runner.py index 6c186f6fe70..64670b06212 100644 --- a/test/system-test-runner.py +++ b/test/system-test-runner.py @@ -326,7 +326,7 @@ def start_tomcat_server(self, sample_module: str) -> None: # Start the Tomcat server with open("tomcat-server.txt", "w") as log_file: self.tomcat_server.process = subprocess.Popen( - ["./gradlew", f"sentry-samples:{sample_module}:run"], + ["./gradlew", f"sentry-samples:{sample_module}:run", "--console=plain"], env=env, stdout=log_file, stderr=subprocess.STDOUT @@ -578,7 +578,7 @@ def run_gradle_task(self, task: str) -> int: """Run a Gradle task and return the exit code.""" print(f"Running: ./gradlew {task}") try: - result = subprocess.run(["./gradlew", task], check=False) + result = subprocess.run(["./gradlew", task, "--console=plain"], check=False) return result.returncode except Exception as e: print(f"Failed to run Gradle task: {e}") From 5bfc096c74a15ee7debbb79088597eaa51e0727c Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Tue, 7 Apr 2026 17:52:31 +0200 Subject: [PATCH 09/27] align coroutines version to 1.9.0 for system tests --- .../build.gradle.kts | 3 +++ .../build.gradle.kts | 3 +++ .../sentry-samples-spring-boot-jakarta/build.gradle.kts | 4 +++- .../build.gradle.kts | 3 +++ sentry-samples/sentry-samples-spring-jakarta/build.gradle.kts | 3 +++ sentry-samples/sentry-samples-spring/build.gradle.kts | 3 +++ 6 files changed, 18 insertions(+), 1 deletion(-) diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/build.gradle.kts index c2f180c7f25..c7fc0106131 100644 --- a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/build.gradle.kts @@ -18,6 +18,9 @@ java.targetCompatibility = JavaVersion.VERSION_17 repositories { mavenCentral() } +// Apollo 4.x requires coroutines 1.9.0+, override Spring Boot's managed version +extra["kotlin-coroutines.version"] = "1.9.0" + configure { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/build.gradle.kts index 4e99209e3c7..767208a6082 100644 --- a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/build.gradle.kts @@ -19,6 +19,9 @@ java.targetCompatibility = JavaVersion.VERSION_17 repositories { mavenCentral() } +// Apollo 4.x requires coroutines 1.9.0+, override Spring Boot's managed version +extra["kotlin-coroutines.version"] = "1.9.0" + configure { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-jakarta/build.gradle.kts index f5986e683a1..98f7ba434ff 100644 --- a/sentry-samples/sentry-samples-spring-boot-jakarta/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-jakarta/build.gradle.kts @@ -18,6 +18,9 @@ java.targetCompatibility = JavaVersion.VERSION_17 repositories { mavenCentral() } +// Apollo 4.x requires coroutines 1.9.0+, override Spring Boot's managed version +extra["kotlin-coroutines.version"] = "1.9.0" + configure { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 @@ -69,7 +72,6 @@ dependencies { testImplementation(kotlin(Config.kotlinStdLib)) testImplementation(projects.sentry) testImplementation(projects.sentrySystemTestSupport) - testImplementation(libs.apollo3.kotlin) testImplementation(libs.kotlin.test.junit) testImplementation(libs.slf4j2.api) testImplementation(libs.springboot3.starter.test) { diff --git a/sentry-samples/sentry-samples-spring-boot-webflux-jakarta/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-webflux-jakarta/build.gradle.kts index 4ed91454122..d5b04543576 100644 --- a/sentry-samples/sentry-samples-spring-boot-webflux-jakarta/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-webflux-jakarta/build.gradle.kts @@ -18,6 +18,9 @@ java.targetCompatibility = JavaVersion.VERSION_17 repositories { mavenCentral() } +// Apollo 4.x requires coroutines 1.9.0+, override Spring Boot's managed version +extra["kotlin-coroutines.version"] = "1.9.0" + dependencies { implementation(Config.Libs.kotlinReflect) implementation(kotlin(Config.kotlinStdLib, KotlinCompilerVersion.VERSION)) diff --git a/sentry-samples/sentry-samples-spring-jakarta/build.gradle.kts b/sentry-samples/sentry-samples-spring-jakarta/build.gradle.kts index 55e350d66c0..319431e71d2 100644 --- a/sentry-samples/sentry-samples-spring-jakarta/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-jakarta/build.gradle.kts @@ -26,6 +26,9 @@ java.targetCompatibility = JavaVersion.VERSION_17 repositories { mavenCentral() } +// Apollo 4.x requires coroutines 1.9.0+, override Spring Boot's managed version +extra["kotlin-coroutines.version"] = "1.9.0" + dependencyManagement { imports { mavenBom(SpringBootPlugin.BOM_COORDINATES) diff --git a/sentry-samples/sentry-samples-spring/build.gradle.kts b/sentry-samples/sentry-samples-spring/build.gradle.kts index fa3de84c25a..446baf3a696 100644 --- a/sentry-samples/sentry-samples-spring/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring/build.gradle.kts @@ -25,6 +25,9 @@ java { repositories { mavenCentral() } +// Apollo 4.x requires coroutines 1.9.0+, override Spring Boot's managed version +extra["kotlin-coroutines.version"] = "1.9.0" + dependencyManagement { imports { mavenBom(libs.springboot2.bom.get().toString()) From 5a29bc63b207a5bf4a63e4c00c6412ad1c7e3aa2 Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Fri, 10 Apr 2026 14:24:21 +0200 Subject: [PATCH 10/27] fix(otel): use mergeServiceFiles path instead of include for Shadow 9.x Shadow 9.x's ServiceFileTransformer strips the `inst/` prefix when using `include("inst/META-INF/services/*")`, placing merged service files under `META-INF/services/` instead of `inst/META-INF/services/`. This breaks the OTel agent's classloader which expects isolated services under `inst/`. Using `path = "inst/META-INF/services"` preserves the correct output path. Also add missing `duplicatesStrategy = DuplicatesStrategy.INCLUDE` to console-otlp, log4j2, and console-opentelemetry-noagent shadow JARs so that mergeServiceFiles and Log4j2 transformers can see duplicates before they are deduplicated. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../sentry-opentelemetry-agent/build.gradle.kts | 6 +++++- .../build.gradle.kts | 1 + sentry-samples/sentry-samples-console-otlp/build.gradle.kts | 1 + sentry-samples/sentry-samples-log4j2/build.gradle.kts | 1 + 4 files changed, 8 insertions(+), 1 deletion(-) diff --git a/sentry-opentelemetry/sentry-opentelemetry-agent/build.gradle.kts b/sentry-opentelemetry/sentry-opentelemetry-agent/build.gradle.kts index 09649fbb4a5..05f315943e7 100644 --- a/sentry-opentelemetry/sentry-opentelemetry-agent/build.gradle.kts +++ b/sentry-opentelemetry/sentry-opentelemetry-agent/build.gradle.kts @@ -156,7 +156,11 @@ tasks { // would prevent service file merging. duplicatesStrategy = DuplicatesStrategy.INCLUDE - mergeServiceFiles { include("inst/META-INF/services/*") } + // Use `path` instead of `include` filter so that the merged service files are + // written back to `inst/META-INF/services/` (the agent classloader's isolated path). + // Using `include("inst/META-INF/services/*")` with Shadow 9.x strips the `inst/` + // prefix and writes to `META-INF/services/`, which breaks the agent's class loading. + mergeServiceFiles { path = "inst/META-INF/services" } exclude("**/module-info.class") relocatePackages(this) diff --git a/sentry-samples/sentry-samples-console-opentelemetry-noagent/build.gradle.kts b/sentry-samples/sentry-samples-console-opentelemetry-noagent/build.gradle.kts index 04ddecc4ec2..f5d14dc2c38 100644 --- a/sentry-samples/sentry-samples-console-opentelemetry-noagent/build.gradle.kts +++ b/sentry-samples/sentry-samples-console-opentelemetry-noagent/build.gradle.kts @@ -48,6 +48,7 @@ dependencies { tasks.shadowJar { manifest { attributes["Main-Class"] = "io.sentry.samples.console.Main" } archiveClassifier.set("") // Remove the classifier so it replaces the regular JAR + duplicatesStrategy = DuplicatesStrategy.INCLUDE mergeServiceFiles() } diff --git a/sentry-samples/sentry-samples-console-otlp/build.gradle.kts b/sentry-samples/sentry-samples-console-otlp/build.gradle.kts index cbb3b7c7a64..483f6bea799 100644 --- a/sentry-samples/sentry-samples-console-otlp/build.gradle.kts +++ b/sentry-samples/sentry-samples-console-otlp/build.gradle.kts @@ -51,6 +51,7 @@ dependencies { tasks.shadowJar { manifest { attributes["Main-Class"] = "io.sentry.samples.console.Main" } archiveClassifier.set("") // Remove the classifier so it replaces the regular JAR + duplicatesStrategy = DuplicatesStrategy.INCLUDE mergeServiceFiles() } diff --git a/sentry-samples/sentry-samples-log4j2/build.gradle.kts b/sentry-samples/sentry-samples-log4j2/build.gradle.kts index 213f009376a..005e1116528 100644 --- a/sentry-samples/sentry-samples-log4j2/build.gradle.kts +++ b/sentry-samples/sentry-samples-log4j2/build.gradle.kts @@ -45,6 +45,7 @@ dependencies { tasks.shadowJar { manifest { attributes["Main-Class"] = "io.sentry.samples.log4j2.Main" } archiveClassifier.set("") // Remove the classifier so it replaces the regular JAR + duplicatesStrategy = DuplicatesStrategy.INCLUDE mergeServiceFiles() // Use Log4j2 cache transformer to properly handle plugin files transform( From f47be74722b5e762bc477c98baf15032a7a4fe0e Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Fri, 10 Apr 2026 15:25:08 +0200 Subject: [PATCH 11/27] fix(otel): add default mergeServiceFiles for bootstrap service relocation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Shadow 9.x only applies package relocations to service files that are claimed by a ServiceFileTransformer. The ContextStorageProvider service file at META-INF/services/ was not being relocated because it wasn't handled by any transformer — only the inst/META-INF/services/ files were. Adding a default mergeServiceFiles() call ensures bootstrap service files (like ContextStorageProvider) go through the transformer and get properly relocated to their shaded paths. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../sentry-opentelemetry-agent/build.gradle.kts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/sentry-opentelemetry/sentry-opentelemetry-agent/build.gradle.kts b/sentry-opentelemetry/sentry-opentelemetry-agent/build.gradle.kts index 05f315943e7..7c0e9097934 100644 --- a/sentry-opentelemetry/sentry-opentelemetry-agent/build.gradle.kts +++ b/sentry-opentelemetry/sentry-opentelemetry-agent/build.gradle.kts @@ -156,10 +156,15 @@ tasks { // would prevent service file merging. duplicatesStrategy = DuplicatesStrategy.INCLUDE - // Use `path` instead of `include` filter so that the merged service files are - // written back to `inst/META-INF/services/` (the agent classloader's isolated path). - // Using `include("inst/META-INF/services/*")` with Shadow 9.x strips the `inst/` - // prefix and writes to `META-INF/services/`, which breaks the agent's class loading. + // Shadow 9.x only applies relocations to service files handled by a ServiceFileTransformer. + // We need two mergeServiceFiles calls: + // 1. Default path (META-INF/services) — ensures bootstrap service files get relocated + // (e.g., ContextStorageProvider → shaded path). Without this, Shadow 9.x skips + // relocation for service file names/contents not claimed by a transformer. + // 2. inst/ path — merges isolated agent service files from both the upstream agent + // and the distro libs. Uses `path` instead of `include` filter because Shadow 9.x's + // include() strips the `inst/` prefix on output. + mergeServiceFiles() mergeServiceFiles { path = "inst/META-INF/services" } exclude("**/module-info.class") relocatePackages(this) From 00c8173a0c2d32c8636b0941843ec65f6f3de2e9 Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Fri, 10 Apr 2026 17:35:28 +0200 Subject: [PATCH 12/27] fix(spring-boot2): pre-merge Spring metadata for Shadow 9.x compatibility Shadow 9.x enforces DuplicatesStrategy before transformers run, which breaks the `append` transformer for spring.factories and other Spring metadata files. Only the last copy survives instead of being concatenated. Replace `append` calls with a pre-merge task that manually concatenates Spring metadata files (spring.factories, spring.handlers, spring.schemas, spring-autoconfigure-metadata.properties) from the runtime classpath before the shadow JAR is built. The merged files are included first in the shadow JAR so they take precedence over duplicates from dependency JARs. This fixes the PersonSystemTest failure where @SentrySpan AOP and JDBC instrumentation weren't working because SentryAutoConfiguration wasn't properly registered in the merged spring.factories. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../build.gradle.kts | 40 ++++++++++++++--- .../build.gradle.kts | 40 ++++++++++++++--- .../build.gradle.kts | 40 ++++++++++++++--- .../build.gradle.kts | 40 ++++++++++++++--- .../build.gradle.kts | 45 ++++++++++++++++--- 5 files changed, 180 insertions(+), 25 deletions(-) diff --git a/sentry-samples/sentry-samples-netflix-dgs/build.gradle.kts b/sentry-samples/sentry-samples-netflix-dgs/build.gradle.kts index 6386a5d1593..142c838826a 100644 --- a/sentry-samples/sentry-samples-netflix-dgs/build.gradle.kts +++ b/sentry-samples/sentry-samples-netflix-dgs/build.gradle.kts @@ -1,3 +1,4 @@ +import java.util.zip.ZipFile import org.jetbrains.kotlin.config.KotlinCompilerVersion import org.jetbrains.kotlin.gradle.tasks.KotlinCompile @@ -36,16 +37,45 @@ dependencies { } } +// Shadow 9.x enforces DuplicatesStrategy before transformers run, so the `append` +// transformer only sees one copy of each file. We pre-merge Spring metadata files +// from the runtime classpath and include the merged result in the shadow JAR. +val mergeSpringMetadata by tasks.registering { + val outputDir = project.layout.buildDirectory.dir("merged-spring-metadata/META-INF") + val filesToMerge = + listOf("spring.factories", "spring.handlers", "spring.schemas", "spring-autoconfigure-metadata.properties") + outputs.dir(outputDir) + inputs.files(configurations.runtimeClasspath) + doLast { + val out = outputDir.get().asFile + out.mkdirs() + filesToMerge.forEach { fileName -> + val merged = StringBuilder() + configurations.runtimeClasspath.get().filter { it.name.endsWith(".jar") }.forEach { jar -> + try { + val zip = ZipFile(jar) + val entry = zip.getEntry("META-INF/$fileName") + if (entry != null) { + merged.append(zip.getInputStream(entry).bufferedReader().readText()) + if (!merged.endsWith("\n")) merged.append("\n") + } + zip.close() + } catch (e: Exception) { /* skip non-zip files */ } + } + if (merged.isNotEmpty()) { File(out, fileName).writeText(merged.toString()) } + } + } +} + // Configure the Shadow JAR (executable JAR with all dependencies) tasks.shadowJar { + dependsOn(mergeSpringMetadata) manifest { attributes["Main-Class"] = "io.sentry.samples.netflix.dgs.NetlixDgsApplication" } archiveClassifier.set("") - duplicatesStrategy = DuplicatesStrategy.INCLUDE + from(mergeSpringMetadata.map { project.layout.buildDirectory.dir("merged-spring-metadata") }) { + duplicatesStrategy = DuplicatesStrategy.INCLUDE + } mergeServiceFiles() - append("META-INF/spring.handlers") - append("META-INF/spring.schemas") - append("META-INF/spring.factories") - append("META-INF/spring-autoconfigure-metadata.properties") } tasks.jar { diff --git a/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts index f5791ee5ed1..0e9a6d785bc 100644 --- a/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts @@ -1,3 +1,4 @@ +import java.util.zip.ZipFile import org.jetbrains.kotlin.config.KotlinCompilerVersion import org.jetbrains.kotlin.gradle.tasks.KotlinCompile @@ -77,16 +78,45 @@ dependencies { testImplementation("org.apache.httpcomponents:httpclient") } +// Shadow 9.x enforces DuplicatesStrategy before transformers run, so the `append` +// transformer only sees one copy of each file. We pre-merge Spring metadata files +// from the runtime classpath and include the merged result in the shadow JAR. +val mergeSpringMetadata by tasks.registering { + val outputDir = project.layout.buildDirectory.dir("merged-spring-metadata/META-INF") + val filesToMerge = + listOf("spring.factories", "spring.handlers", "spring.schemas", "spring-autoconfigure-metadata.properties") + outputs.dir(outputDir) + inputs.files(configurations.runtimeClasspath) + doLast { + val out = outputDir.get().asFile + out.mkdirs() + filesToMerge.forEach { fileName -> + val merged = StringBuilder() + configurations.runtimeClasspath.get().filter { it.name.endsWith(".jar") }.forEach { jar -> + try { + val zip = ZipFile(jar) + val entry = zip.getEntry("META-INF/$fileName") + if (entry != null) { + merged.append(zip.getInputStream(entry).bufferedReader().readText()) + if (!merged.endsWith("\n")) merged.append("\n") + } + zip.close() + } catch (e: Exception) { /* skip non-zip files */ } + } + if (merged.isNotEmpty()) { File(out, fileName).writeText(merged.toString()) } + } + } +} + // Configure the Shadow JAR (executable JAR with all dependencies) tasks.shadowJar { + dependsOn(mergeSpringMetadata) manifest { attributes["Main-Class"] = "io.sentry.samples.spring.boot.SentryDemoApplication" } archiveClassifier.set("") - duplicatesStrategy = DuplicatesStrategy.INCLUDE + from(mergeSpringMetadata.map { project.layout.buildDirectory.dir("merged-spring-metadata") }) { + duplicatesStrategy = DuplicatesStrategy.INCLUDE + } mergeServiceFiles() - append("META-INF/spring.handlers") - append("META-INF/spring.schemas") - append("META-INF/spring.factories") - append("META-INF/spring-autoconfigure-metadata.properties") } tasks.jar { diff --git a/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts index 4542215792b..67f1b255012 100644 --- a/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts @@ -1,3 +1,4 @@ +import java.util.zip.ZipFile import org.jetbrains.kotlin.config.KotlinCompilerVersion import org.jetbrains.kotlin.gradle.tasks.KotlinCompile @@ -73,16 +74,45 @@ dependencies { testImplementation("org.apache.httpcomponents:httpclient") } +// Shadow 9.x enforces DuplicatesStrategy before transformers run, so the `append` +// transformer only sees one copy of each file. We pre-merge Spring metadata files +// from the runtime classpath and include the merged result in the shadow JAR. +val mergeSpringMetadata by tasks.registering { + val outputDir = project.layout.buildDirectory.dir("merged-spring-metadata/META-INF") + val filesToMerge = + listOf("spring.factories", "spring.handlers", "spring.schemas", "spring-autoconfigure-metadata.properties") + outputs.dir(outputDir) + inputs.files(configurations.runtimeClasspath) + doLast { + val out = outputDir.get().asFile + out.mkdirs() + filesToMerge.forEach { fileName -> + val merged = StringBuilder() + configurations.runtimeClasspath.get().filter { it.name.endsWith(".jar") }.forEach { jar -> + try { + val zip = ZipFile(jar) + val entry = zip.getEntry("META-INF/$fileName") + if (entry != null) { + merged.append(zip.getInputStream(entry).bufferedReader().readText()) + if (!merged.endsWith("\n")) merged.append("\n") + } + zip.close() + } catch (e: Exception) { /* skip non-zip files */ } + } + if (merged.isNotEmpty()) { File(out, fileName).writeText(merged.toString()) } + } + } +} + // Configure the Shadow JAR (executable JAR with all dependencies) tasks.shadowJar { + dependsOn(mergeSpringMetadata) manifest { attributes["Main-Class"] = "io.sentry.samples.spring.boot.SentryDemoApplication" } archiveClassifier.set("") - duplicatesStrategy = DuplicatesStrategy.INCLUDE + from(mergeSpringMetadata.map { project.layout.buildDirectory.dir("merged-spring-metadata") }) { + duplicatesStrategy = DuplicatesStrategy.INCLUDE + } mergeServiceFiles() - append("META-INF/spring.handlers") - append("META-INF/spring.schemas") - append("META-INF/spring.factories") - append("META-INF/spring-autoconfigure-metadata.properties") } tasks.jar { diff --git a/sentry-samples/sentry-samples-spring-boot-webflux/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-webflux/build.gradle.kts index c4ff81afb8b..1895d3bc9ae 100644 --- a/sentry-samples/sentry-samples-spring-boot-webflux/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-webflux/build.gradle.kts @@ -1,3 +1,4 @@ +import java.util.zip.ZipFile import org.jetbrains.kotlin.config.KotlinCompilerVersion import org.jetbrains.kotlin.gradle.tasks.KotlinCompile @@ -46,16 +47,45 @@ dependencies { testImplementation("org.apache.httpcomponents:httpclient") } +// Shadow 9.x enforces DuplicatesStrategy before transformers run, so the `append` +// transformer only sees one copy of each file. We pre-merge Spring metadata files +// from the runtime classpath and include the merged result in the shadow JAR. +val mergeSpringMetadata by tasks.registering { + val outputDir = project.layout.buildDirectory.dir("merged-spring-metadata/META-INF") + val filesToMerge = + listOf("spring.factories", "spring.handlers", "spring.schemas", "spring-autoconfigure-metadata.properties") + outputs.dir(outputDir) + inputs.files(configurations.runtimeClasspath) + doLast { + val out = outputDir.get().asFile + out.mkdirs() + filesToMerge.forEach { fileName -> + val merged = StringBuilder() + configurations.runtimeClasspath.get().filter { it.name.endsWith(".jar") }.forEach { jar -> + try { + val zip = ZipFile(jar) + val entry = zip.getEntry("META-INF/$fileName") + if (entry != null) { + merged.append(zip.getInputStream(entry).bufferedReader().readText()) + if (!merged.endsWith("\n")) merged.append("\n") + } + zip.close() + } catch (e: Exception) { /* skip non-zip files */ } + } + if (merged.isNotEmpty()) { File(out, fileName).writeText(merged.toString()) } + } + } +} + // Configure the Shadow JAR (executable JAR with all dependencies) tasks.shadowJar { + dependsOn(mergeSpringMetadata) manifest { attributes["Main-Class"] = "io.sentry.samples.spring.boot.SentryDemoApplication" } archiveClassifier.set("") - duplicatesStrategy = DuplicatesStrategy.INCLUDE + from(mergeSpringMetadata.map { project.layout.buildDirectory.dir("merged-spring-metadata") }) { + duplicatesStrategy = DuplicatesStrategy.INCLUDE + } mergeServiceFiles() - append("META-INF/spring.handlers") - append("META-INF/spring.schemas") - append("META-INF/spring.factories") - append("META-INF/spring-autoconfigure-metadata.properties") } tasks.jar { diff --git a/sentry-samples/sentry-samples-spring-boot/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot/build.gradle.kts index 28baa6830cd..9eb8b1876c0 100644 --- a/sentry-samples/sentry-samples-spring-boot/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot/build.gradle.kts @@ -1,5 +1,6 @@ import org.jetbrains.kotlin.config.KotlinCompilerVersion import org.jetbrains.kotlin.gradle.tasks.KotlinCompile +import java.util.zip.ZipFile plugins { java @@ -73,16 +74,50 @@ dependencies { testImplementation("org.apache.httpcomponents:httpclient") } +// Shadow 9.x enforces DuplicatesStrategy before transformers run, so the `append` +// transformer only sees one copy of each file. We pre-merge Spring metadata files +// from the runtime classpath and include the merged result in the shadow JAR. +val mergeSpringMetadata by tasks.registering { + val outputDir = project.layout.buildDirectory.dir("merged-spring-metadata/META-INF") + val filesToMerge = + listOf("spring.factories", "spring.handlers", "spring.schemas", "spring-autoconfigure-metadata.properties") + + outputs.dir(outputDir) + inputs.files(configurations.runtimeClasspath) + + doLast { + val out = outputDir.get().asFile + out.mkdirs() + filesToMerge.forEach { fileName -> + val merged = StringBuilder() + configurations.runtimeClasspath.get().filter { it.name.endsWith(".jar") }.forEach { jar -> + try { + val zip = ZipFile(jar) + val entry = zip.getEntry("META-INF/$fileName") + if (entry != null) { + merged.append(zip.getInputStream(entry).bufferedReader().readText()) + if (!merged.endsWith("\n")) merged.append("\n") + } + zip.close() + } catch (e: Exception) { /* skip non-zip files */ } + } + if (merged.isNotEmpty()) { + File(out, fileName).writeText(merged.toString()) + } + } + } +} + // Configure the Shadow JAR (executable JAR with all dependencies) tasks.shadowJar { + dependsOn(mergeSpringMetadata) manifest { attributes["Main-Class"] = "io.sentry.samples.spring.boot.SentryDemoApplication" } archiveClassifier.set("") - duplicatesStrategy = DuplicatesStrategy.INCLUDE + // Pre-merged Spring metadata files must come first so they win over duplicates from JARs + from(mergeSpringMetadata.map { project.layout.buildDirectory.dir("merged-spring-metadata") }) { + duplicatesStrategy = DuplicatesStrategy.INCLUDE + } mergeServiceFiles() - append("META-INF/spring.handlers") - append("META-INF/spring.schemas") - append("META-INF/spring.factories") - append("META-INF/spring-autoconfigure-metadata.properties") } tasks.jar { From 3bfe40d78f08959c2317c3274a63e1bbd62ce2ba Mon Sep 17 00:00:00 2001 From: Sentry Github Bot Date: Fri, 10 Apr 2026 15:40:08 +0000 Subject: [PATCH 13/27] Format code --- .../build.gradle.kts | 57 +++++++++------- .../build.gradle.kts | 57 +++++++++------- .../build.gradle.kts | 57 +++++++++------- .../build.gradle.kts | 57 +++++++++------- .../build.gradle.kts | 65 +++++++++++-------- 5 files changed, 178 insertions(+), 115 deletions(-) diff --git a/sentry-samples/sentry-samples-netflix-dgs/build.gradle.kts b/sentry-samples/sentry-samples-netflix-dgs/build.gradle.kts index 142c838826a..b66d5b276a0 100644 --- a/sentry-samples/sentry-samples-netflix-dgs/build.gradle.kts +++ b/sentry-samples/sentry-samples-netflix-dgs/build.gradle.kts @@ -40,32 +40,45 @@ dependencies { // Shadow 9.x enforces DuplicatesStrategy before transformers run, so the `append` // transformer only sees one copy of each file. We pre-merge Spring metadata files // from the runtime classpath and include the merged result in the shadow JAR. -val mergeSpringMetadata by tasks.registering { - val outputDir = project.layout.buildDirectory.dir("merged-spring-metadata/META-INF") - val filesToMerge = - listOf("spring.factories", "spring.handlers", "spring.schemas", "spring-autoconfigure-metadata.properties") - outputs.dir(outputDir) - inputs.files(configurations.runtimeClasspath) - doLast { - val out = outputDir.get().asFile - out.mkdirs() - filesToMerge.forEach { fileName -> - val merged = StringBuilder() - configurations.runtimeClasspath.get().filter { it.name.endsWith(".jar") }.forEach { jar -> - try { - val zip = ZipFile(jar) - val entry = zip.getEntry("META-INF/$fileName") - if (entry != null) { - merged.append(zip.getInputStream(entry).bufferedReader().readText()) - if (!merged.endsWith("\n")) merged.append("\n") +val mergeSpringMetadata by + tasks.registering { + val outputDir = project.layout.buildDirectory.dir("merged-spring-metadata/META-INF") + val filesToMerge = + listOf( + "spring.factories", + "spring.handlers", + "spring.schemas", + "spring-autoconfigure-metadata.properties", + ) + outputs.dir(outputDir) + inputs.files(configurations.runtimeClasspath) + doLast { + val out = outputDir.get().asFile + out.mkdirs() + filesToMerge.forEach { fileName -> + val merged = StringBuilder() + configurations.runtimeClasspath + .get() + .filter { it.name.endsWith(".jar") } + .forEach { jar -> + try { + val zip = ZipFile(jar) + val entry = zip.getEntry("META-INF/$fileName") + if (entry != null) { + merged.append(zip.getInputStream(entry).bufferedReader().readText()) + if (!merged.endsWith("\n")) merged.append("\n") + } + zip.close() + } catch (e: Exception) { + /* skip non-zip files */ + } } - zip.close() - } catch (e: Exception) { /* skip non-zip files */ } + if (merged.isNotEmpty()) { + File(out, fileName).writeText(merged.toString()) + } } - if (merged.isNotEmpty()) { File(out, fileName).writeText(merged.toString()) } } } -} // Configure the Shadow JAR (executable JAR with all dependencies) tasks.shadowJar { diff --git a/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts index 0e9a6d785bc..5fa83a21637 100644 --- a/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts @@ -81,32 +81,45 @@ dependencies { // Shadow 9.x enforces DuplicatesStrategy before transformers run, so the `append` // transformer only sees one copy of each file. We pre-merge Spring metadata files // from the runtime classpath and include the merged result in the shadow JAR. -val mergeSpringMetadata by tasks.registering { - val outputDir = project.layout.buildDirectory.dir("merged-spring-metadata/META-INF") - val filesToMerge = - listOf("spring.factories", "spring.handlers", "spring.schemas", "spring-autoconfigure-metadata.properties") - outputs.dir(outputDir) - inputs.files(configurations.runtimeClasspath) - doLast { - val out = outputDir.get().asFile - out.mkdirs() - filesToMerge.forEach { fileName -> - val merged = StringBuilder() - configurations.runtimeClasspath.get().filter { it.name.endsWith(".jar") }.forEach { jar -> - try { - val zip = ZipFile(jar) - val entry = zip.getEntry("META-INF/$fileName") - if (entry != null) { - merged.append(zip.getInputStream(entry).bufferedReader().readText()) - if (!merged.endsWith("\n")) merged.append("\n") +val mergeSpringMetadata by + tasks.registering { + val outputDir = project.layout.buildDirectory.dir("merged-spring-metadata/META-INF") + val filesToMerge = + listOf( + "spring.factories", + "spring.handlers", + "spring.schemas", + "spring-autoconfigure-metadata.properties", + ) + outputs.dir(outputDir) + inputs.files(configurations.runtimeClasspath) + doLast { + val out = outputDir.get().asFile + out.mkdirs() + filesToMerge.forEach { fileName -> + val merged = StringBuilder() + configurations.runtimeClasspath + .get() + .filter { it.name.endsWith(".jar") } + .forEach { jar -> + try { + val zip = ZipFile(jar) + val entry = zip.getEntry("META-INF/$fileName") + if (entry != null) { + merged.append(zip.getInputStream(entry).bufferedReader().readText()) + if (!merged.endsWith("\n")) merged.append("\n") + } + zip.close() + } catch (e: Exception) { + /* skip non-zip files */ + } } - zip.close() - } catch (e: Exception) { /* skip non-zip files */ } + if (merged.isNotEmpty()) { + File(out, fileName).writeText(merged.toString()) + } } - if (merged.isNotEmpty()) { File(out, fileName).writeText(merged.toString()) } } } -} // Configure the Shadow JAR (executable JAR with all dependencies) tasks.shadowJar { diff --git a/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts index 67f1b255012..1eca8b94366 100644 --- a/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts @@ -77,32 +77,45 @@ dependencies { // Shadow 9.x enforces DuplicatesStrategy before transformers run, so the `append` // transformer only sees one copy of each file. We pre-merge Spring metadata files // from the runtime classpath and include the merged result in the shadow JAR. -val mergeSpringMetadata by tasks.registering { - val outputDir = project.layout.buildDirectory.dir("merged-spring-metadata/META-INF") - val filesToMerge = - listOf("spring.factories", "spring.handlers", "spring.schemas", "spring-autoconfigure-metadata.properties") - outputs.dir(outputDir) - inputs.files(configurations.runtimeClasspath) - doLast { - val out = outputDir.get().asFile - out.mkdirs() - filesToMerge.forEach { fileName -> - val merged = StringBuilder() - configurations.runtimeClasspath.get().filter { it.name.endsWith(".jar") }.forEach { jar -> - try { - val zip = ZipFile(jar) - val entry = zip.getEntry("META-INF/$fileName") - if (entry != null) { - merged.append(zip.getInputStream(entry).bufferedReader().readText()) - if (!merged.endsWith("\n")) merged.append("\n") +val mergeSpringMetadata by + tasks.registering { + val outputDir = project.layout.buildDirectory.dir("merged-spring-metadata/META-INF") + val filesToMerge = + listOf( + "spring.factories", + "spring.handlers", + "spring.schemas", + "spring-autoconfigure-metadata.properties", + ) + outputs.dir(outputDir) + inputs.files(configurations.runtimeClasspath) + doLast { + val out = outputDir.get().asFile + out.mkdirs() + filesToMerge.forEach { fileName -> + val merged = StringBuilder() + configurations.runtimeClasspath + .get() + .filter { it.name.endsWith(".jar") } + .forEach { jar -> + try { + val zip = ZipFile(jar) + val entry = zip.getEntry("META-INF/$fileName") + if (entry != null) { + merged.append(zip.getInputStream(entry).bufferedReader().readText()) + if (!merged.endsWith("\n")) merged.append("\n") + } + zip.close() + } catch (e: Exception) { + /* skip non-zip files */ + } } - zip.close() - } catch (e: Exception) { /* skip non-zip files */ } + if (merged.isNotEmpty()) { + File(out, fileName).writeText(merged.toString()) + } } - if (merged.isNotEmpty()) { File(out, fileName).writeText(merged.toString()) } } } -} // Configure the Shadow JAR (executable JAR with all dependencies) tasks.shadowJar { diff --git a/sentry-samples/sentry-samples-spring-boot-webflux/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-webflux/build.gradle.kts index 1895d3bc9ae..7c4a4655688 100644 --- a/sentry-samples/sentry-samples-spring-boot-webflux/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-webflux/build.gradle.kts @@ -50,32 +50,45 @@ dependencies { // Shadow 9.x enforces DuplicatesStrategy before transformers run, so the `append` // transformer only sees one copy of each file. We pre-merge Spring metadata files // from the runtime classpath and include the merged result in the shadow JAR. -val mergeSpringMetadata by tasks.registering { - val outputDir = project.layout.buildDirectory.dir("merged-spring-metadata/META-INF") - val filesToMerge = - listOf("spring.factories", "spring.handlers", "spring.schemas", "spring-autoconfigure-metadata.properties") - outputs.dir(outputDir) - inputs.files(configurations.runtimeClasspath) - doLast { - val out = outputDir.get().asFile - out.mkdirs() - filesToMerge.forEach { fileName -> - val merged = StringBuilder() - configurations.runtimeClasspath.get().filter { it.name.endsWith(".jar") }.forEach { jar -> - try { - val zip = ZipFile(jar) - val entry = zip.getEntry("META-INF/$fileName") - if (entry != null) { - merged.append(zip.getInputStream(entry).bufferedReader().readText()) - if (!merged.endsWith("\n")) merged.append("\n") +val mergeSpringMetadata by + tasks.registering { + val outputDir = project.layout.buildDirectory.dir("merged-spring-metadata/META-INF") + val filesToMerge = + listOf( + "spring.factories", + "spring.handlers", + "spring.schemas", + "spring-autoconfigure-metadata.properties", + ) + outputs.dir(outputDir) + inputs.files(configurations.runtimeClasspath) + doLast { + val out = outputDir.get().asFile + out.mkdirs() + filesToMerge.forEach { fileName -> + val merged = StringBuilder() + configurations.runtimeClasspath + .get() + .filter { it.name.endsWith(".jar") } + .forEach { jar -> + try { + val zip = ZipFile(jar) + val entry = zip.getEntry("META-INF/$fileName") + if (entry != null) { + merged.append(zip.getInputStream(entry).bufferedReader().readText()) + if (!merged.endsWith("\n")) merged.append("\n") + } + zip.close() + } catch (e: Exception) { + /* skip non-zip files */ + } } - zip.close() - } catch (e: Exception) { /* skip non-zip files */ } + if (merged.isNotEmpty()) { + File(out, fileName).writeText(merged.toString()) + } } - if (merged.isNotEmpty()) { File(out, fileName).writeText(merged.toString()) } } } -} // Configure the Shadow JAR (executable JAR with all dependencies) tasks.shadowJar { diff --git a/sentry-samples/sentry-samples-spring-boot/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot/build.gradle.kts index 9eb8b1876c0..701898e91db 100644 --- a/sentry-samples/sentry-samples-spring-boot/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot/build.gradle.kts @@ -1,6 +1,6 @@ +import java.util.zip.ZipFile import org.jetbrains.kotlin.config.KotlinCompilerVersion import org.jetbrains.kotlin.gradle.tasks.KotlinCompile -import java.util.zip.ZipFile plugins { java @@ -77,36 +77,47 @@ dependencies { // Shadow 9.x enforces DuplicatesStrategy before transformers run, so the `append` // transformer only sees one copy of each file. We pre-merge Spring metadata files // from the runtime classpath and include the merged result in the shadow JAR. -val mergeSpringMetadata by tasks.registering { - val outputDir = project.layout.buildDirectory.dir("merged-spring-metadata/META-INF") - val filesToMerge = - listOf("spring.factories", "spring.handlers", "spring.schemas", "spring-autoconfigure-metadata.properties") - - outputs.dir(outputDir) - inputs.files(configurations.runtimeClasspath) - - doLast { - val out = outputDir.get().asFile - out.mkdirs() - filesToMerge.forEach { fileName -> - val merged = StringBuilder() - configurations.runtimeClasspath.get().filter { it.name.endsWith(".jar") }.forEach { jar -> - try { - val zip = ZipFile(jar) - val entry = zip.getEntry("META-INF/$fileName") - if (entry != null) { - merged.append(zip.getInputStream(entry).bufferedReader().readText()) - if (!merged.endsWith("\n")) merged.append("\n") +val mergeSpringMetadata by + tasks.registering { + val outputDir = project.layout.buildDirectory.dir("merged-spring-metadata/META-INF") + val filesToMerge = + listOf( + "spring.factories", + "spring.handlers", + "spring.schemas", + "spring-autoconfigure-metadata.properties", + ) + + outputs.dir(outputDir) + inputs.files(configurations.runtimeClasspath) + + doLast { + val out = outputDir.get().asFile + out.mkdirs() + filesToMerge.forEach { fileName -> + val merged = StringBuilder() + configurations.runtimeClasspath + .get() + .filter { it.name.endsWith(".jar") } + .forEach { jar -> + try { + val zip = ZipFile(jar) + val entry = zip.getEntry("META-INF/$fileName") + if (entry != null) { + merged.append(zip.getInputStream(entry).bufferedReader().readText()) + if (!merged.endsWith("\n")) merged.append("\n") + } + zip.close() + } catch (e: Exception) { + /* skip non-zip files */ + } } - zip.close() - } catch (e: Exception) { /* skip non-zip files */ } - } - if (merged.isNotEmpty()) { - File(out, fileName).writeText(merged.toString()) + if (merged.isNotEmpty()) { + File(out, fileName).writeText(merged.toString()) + } } } } -} // Configure the Shadow JAR (executable JAR with all dependencies) tasks.shadowJar { From 0a959988b28421cef1f393999933f84228af7991 Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Fri, 10 Apr 2026 17:40:47 +0200 Subject: [PATCH 14/27] fix(lint): suppress OldTargetApi for uitest-android module Lint 8.13.1 (set via android.experimental.lint.version) expects targetSdk 37 but we target 36. This is a test-only module so suppressing is safe. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../sentry-uitest-android/build.gradle.kts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sentry-android-integration-tests/sentry-uitest-android/build.gradle.kts b/sentry-android-integration-tests/sentry-uitest-android/build.gradle.kts index 0c32cbad941..a4d46405fb8 100644 --- a/sentry-android-integration-tests/sentry-uitest-android/build.gradle.kts +++ b/sentry-android-integration-tests/sentry-uitest-android/build.gradle.kts @@ -74,6 +74,8 @@ android { lint { warningsAsErrors = true checkDependencies = true + // Suppress OldTargetApi: lint 8.13.1 expects API 37 but we target 36 + disable += "OldTargetApi" // We run a full lint analysis as build part in CI, so skip vital checks for assemble tasks. checkReleaseBuilds = false From 9a388afc2bf2e9b38b4803b167f8895aad061c44 Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Fri, 10 Apr 2026 18:15:07 +0200 Subject: [PATCH 15/27] fix(spring-boot2): make mergeSpringMetadata configuration-cache compatible Resolve the runtime classpath at configuration time (not inside doLast) so the task doesn't capture Gradle script object references that can't be serialized by the configuration cache. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../build.gradle.kts | 8 ++---- .../build.gradle.kts | 8 ++---- .../build.gradle.kts | 8 ++---- .../build.gradle.kts | 8 ++---- .../build.gradle.kts | 28 +++++++++---------- 5 files changed, 25 insertions(+), 35 deletions(-) diff --git a/sentry-samples/sentry-samples-netflix-dgs/build.gradle.kts b/sentry-samples/sentry-samples-netflix-dgs/build.gradle.kts index b66d5b276a0..ed1af2ce998 100644 --- a/sentry-samples/sentry-samples-netflix-dgs/build.gradle.kts +++ b/sentry-samples/sentry-samples-netflix-dgs/build.gradle.kts @@ -43,6 +43,7 @@ dependencies { val mergeSpringMetadata by tasks.registering { val outputDir = project.layout.buildDirectory.dir("merged-spring-metadata/META-INF") + val classpathJars = configurations.runtimeClasspath.get().filter { it.name.endsWith(".jar") } val filesToMerge = listOf( "spring.factories", @@ -51,16 +52,13 @@ val mergeSpringMetadata by "spring-autoconfigure-metadata.properties", ) outputs.dir(outputDir) - inputs.files(configurations.runtimeClasspath) + inputs.files(classpathJars) doLast { val out = outputDir.get().asFile out.mkdirs() filesToMerge.forEach { fileName -> val merged = StringBuilder() - configurations.runtimeClasspath - .get() - .filter { it.name.endsWith(".jar") } - .forEach { jar -> + classpathJars.forEach { jar -> try { val zip = ZipFile(jar) val entry = zip.getEntry("META-INF/$fileName") diff --git a/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts index 5fa83a21637..438d0c9036f 100644 --- a/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts @@ -84,6 +84,7 @@ dependencies { val mergeSpringMetadata by tasks.registering { val outputDir = project.layout.buildDirectory.dir("merged-spring-metadata/META-INF") + val classpathJars = configurations.runtimeClasspath.get().filter { it.name.endsWith(".jar") } val filesToMerge = listOf( "spring.factories", @@ -92,16 +93,13 @@ val mergeSpringMetadata by "spring-autoconfigure-metadata.properties", ) outputs.dir(outputDir) - inputs.files(configurations.runtimeClasspath) + inputs.files(classpathJars) doLast { val out = outputDir.get().asFile out.mkdirs() filesToMerge.forEach { fileName -> val merged = StringBuilder() - configurations.runtimeClasspath - .get() - .filter { it.name.endsWith(".jar") } - .forEach { jar -> + classpathJars.forEach { jar -> try { val zip = ZipFile(jar) val entry = zip.getEntry("META-INF/$fileName") diff --git a/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts index 1eca8b94366..bae6597e9ff 100644 --- a/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts @@ -80,6 +80,7 @@ dependencies { val mergeSpringMetadata by tasks.registering { val outputDir = project.layout.buildDirectory.dir("merged-spring-metadata/META-INF") + val classpathJars = configurations.runtimeClasspath.get().filter { it.name.endsWith(".jar") } val filesToMerge = listOf( "spring.factories", @@ -88,16 +89,13 @@ val mergeSpringMetadata by "spring-autoconfigure-metadata.properties", ) outputs.dir(outputDir) - inputs.files(configurations.runtimeClasspath) + inputs.files(classpathJars) doLast { val out = outputDir.get().asFile out.mkdirs() filesToMerge.forEach { fileName -> val merged = StringBuilder() - configurations.runtimeClasspath - .get() - .filter { it.name.endsWith(".jar") } - .forEach { jar -> + classpathJars.forEach { jar -> try { val zip = ZipFile(jar) val entry = zip.getEntry("META-INF/$fileName") diff --git a/sentry-samples/sentry-samples-spring-boot-webflux/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-webflux/build.gradle.kts index 7c4a4655688..8c3c9ab4ab6 100644 --- a/sentry-samples/sentry-samples-spring-boot-webflux/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-webflux/build.gradle.kts @@ -53,6 +53,7 @@ dependencies { val mergeSpringMetadata by tasks.registering { val outputDir = project.layout.buildDirectory.dir("merged-spring-metadata/META-INF") + val classpathJars = configurations.runtimeClasspath.get().filter { it.name.endsWith(".jar") } val filesToMerge = listOf( "spring.factories", @@ -61,16 +62,13 @@ val mergeSpringMetadata by "spring-autoconfigure-metadata.properties", ) outputs.dir(outputDir) - inputs.files(configurations.runtimeClasspath) + inputs.files(classpathJars) doLast { val out = outputDir.get().asFile out.mkdirs() filesToMerge.forEach { fileName -> val merged = StringBuilder() - configurations.runtimeClasspath - .get() - .filter { it.name.endsWith(".jar") } - .forEach { jar -> + classpathJars.forEach { jar -> try { val zip = ZipFile(jar) val entry = zip.getEntry("META-INF/$fileName") diff --git a/sentry-samples/sentry-samples-spring-boot/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot/build.gradle.kts index 701898e91db..ff1a506da0b 100644 --- a/sentry-samples/sentry-samples-spring-boot/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot/build.gradle.kts @@ -80,6 +80,7 @@ dependencies { val mergeSpringMetadata by tasks.registering { val outputDir = project.layout.buildDirectory.dir("merged-spring-metadata/META-INF") + val classpathJars = configurations.runtimeClasspath.get().filter { it.name.endsWith(".jar") } val filesToMerge = listOf( "spring.factories", @@ -89,29 +90,26 @@ val mergeSpringMetadata by ) outputs.dir(outputDir) - inputs.files(configurations.runtimeClasspath) + inputs.files(classpathJars) doLast { val out = outputDir.get().asFile out.mkdirs() filesToMerge.forEach { fileName -> val merged = StringBuilder() - configurations.runtimeClasspath - .get() - .filter { it.name.endsWith(".jar") } - .forEach { jar -> - try { - val zip = ZipFile(jar) - val entry = zip.getEntry("META-INF/$fileName") - if (entry != null) { - merged.append(zip.getInputStream(entry).bufferedReader().readText()) - if (!merged.endsWith("\n")) merged.append("\n") - } - zip.close() - } catch (e: Exception) { - /* skip non-zip files */ + classpathJars.forEach { jar -> + try { + val zip = ZipFile(jar) + val entry = zip.getEntry("META-INF/$fileName") + if (entry != null) { + merged.append(zip.getInputStream(entry).bufferedReader().readText()) + if (!merged.endsWith("\n")) merged.append("\n") } + zip.close() + } catch (e: Exception) { + /* skip non-zip files */ } + } if (merged.isNotEmpty()) { File(out, fileName).writeText(merged.toString()) } From 8ae3c9c1d2d9103ba0785f85d283c0e69272fd06 Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Fri, 10 Apr 2026 18:22:13 +0200 Subject: [PATCH 16/27] formatting --- .../build.gradle.kts | 20 +++++++++---------- .../build.gradle.kts | 20 +++++++++---------- .../build.gradle.kts | 20 +++++++++---------- .../build.gradle.kts | 20 +++++++++---------- 4 files changed, 40 insertions(+), 40 deletions(-) diff --git a/sentry-samples/sentry-samples-netflix-dgs/build.gradle.kts b/sentry-samples/sentry-samples-netflix-dgs/build.gradle.kts index ed1af2ce998..7b1c2ac400a 100644 --- a/sentry-samples/sentry-samples-netflix-dgs/build.gradle.kts +++ b/sentry-samples/sentry-samples-netflix-dgs/build.gradle.kts @@ -59,18 +59,18 @@ val mergeSpringMetadata by filesToMerge.forEach { fileName -> val merged = StringBuilder() classpathJars.forEach { jar -> - try { - val zip = ZipFile(jar) - val entry = zip.getEntry("META-INF/$fileName") - if (entry != null) { - merged.append(zip.getInputStream(entry).bufferedReader().readText()) - if (!merged.endsWith("\n")) merged.append("\n") - } - zip.close() - } catch (e: Exception) { - /* skip non-zip files */ + try { + val zip = ZipFile(jar) + val entry = zip.getEntry("META-INF/$fileName") + if (entry != null) { + merged.append(zip.getInputStream(entry).bufferedReader().readText()) + if (!merged.endsWith("\n")) merged.append("\n") } + zip.close() + } catch (e: Exception) { + /* skip non-zip files */ } + } if (merged.isNotEmpty()) { File(out, fileName).writeText(merged.toString()) } diff --git a/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts index 438d0c9036f..fe9a72dcf5a 100644 --- a/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts @@ -100,18 +100,18 @@ val mergeSpringMetadata by filesToMerge.forEach { fileName -> val merged = StringBuilder() classpathJars.forEach { jar -> - try { - val zip = ZipFile(jar) - val entry = zip.getEntry("META-INF/$fileName") - if (entry != null) { - merged.append(zip.getInputStream(entry).bufferedReader().readText()) - if (!merged.endsWith("\n")) merged.append("\n") - } - zip.close() - } catch (e: Exception) { - /* skip non-zip files */ + try { + val zip = ZipFile(jar) + val entry = zip.getEntry("META-INF/$fileName") + if (entry != null) { + merged.append(zip.getInputStream(entry).bufferedReader().readText()) + if (!merged.endsWith("\n")) merged.append("\n") } + zip.close() + } catch (e: Exception) { + /* skip non-zip files */ } + } if (merged.isNotEmpty()) { File(out, fileName).writeText(merged.toString()) } diff --git a/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts index bae6597e9ff..9496306f49d 100644 --- a/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts @@ -96,18 +96,18 @@ val mergeSpringMetadata by filesToMerge.forEach { fileName -> val merged = StringBuilder() classpathJars.forEach { jar -> - try { - val zip = ZipFile(jar) - val entry = zip.getEntry("META-INF/$fileName") - if (entry != null) { - merged.append(zip.getInputStream(entry).bufferedReader().readText()) - if (!merged.endsWith("\n")) merged.append("\n") - } - zip.close() - } catch (e: Exception) { - /* skip non-zip files */ + try { + val zip = ZipFile(jar) + val entry = zip.getEntry("META-INF/$fileName") + if (entry != null) { + merged.append(zip.getInputStream(entry).bufferedReader().readText()) + if (!merged.endsWith("\n")) merged.append("\n") } + zip.close() + } catch (e: Exception) { + /* skip non-zip files */ } + } if (merged.isNotEmpty()) { File(out, fileName).writeText(merged.toString()) } diff --git a/sentry-samples/sentry-samples-spring-boot-webflux/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-webflux/build.gradle.kts index 8c3c9ab4ab6..465d5e25d3b 100644 --- a/sentry-samples/sentry-samples-spring-boot-webflux/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-webflux/build.gradle.kts @@ -69,18 +69,18 @@ val mergeSpringMetadata by filesToMerge.forEach { fileName -> val merged = StringBuilder() classpathJars.forEach { jar -> - try { - val zip = ZipFile(jar) - val entry = zip.getEntry("META-INF/$fileName") - if (entry != null) { - merged.append(zip.getInputStream(entry).bufferedReader().readText()) - if (!merged.endsWith("\n")) merged.append("\n") - } - zip.close() - } catch (e: Exception) { - /* skip non-zip files */ + try { + val zip = ZipFile(jar) + val entry = zip.getEntry("META-INF/$fileName") + if (entry != null) { + merged.append(zip.getInputStream(entry).bufferedReader().readText()) + if (!merged.endsWith("\n")) merged.append("\n") } + zip.close() + } catch (e: Exception) { + /* skip non-zip files */ } + } if (merged.isNotEmpty()) { File(out, fileName).writeText(merged.toString()) } From 4a48a271250cad08bf51cd84186441a5799b29e5 Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Fri, 10 Apr 2026 18:54:12 +0200 Subject: [PATCH 17/27] fix(spring-boot2): replace from() with doLast JAR patching for spring metadata The from() approach with DuplicatesStrategy.INCLUDE doesn't work because dependency JARs' spring.factories overwrites the pre-merged version. Instead, let the shadow JAR build normally, then use a doLast action to replace the Spring metadata files in the built JAR with the properly merged versions using the NIO ZIP filesystem API. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../build.gradle.kts | 19 ++++++++++-- .../build.gradle.kts | 19 ++++++++++-- .../build.gradle.kts | 19 ++++++++++-- .../build.gradle.kts | 19 ++++++++++-- .../build.gradle.kts | 30 ++++++++++++++++--- 5 files changed, 90 insertions(+), 16 deletions(-) diff --git a/sentry-samples/sentry-samples-netflix-dgs/build.gradle.kts b/sentry-samples/sentry-samples-netflix-dgs/build.gradle.kts index 7b1c2ac400a..adfe893b0e2 100644 --- a/sentry-samples/sentry-samples-netflix-dgs/build.gradle.kts +++ b/sentry-samples/sentry-samples-netflix-dgs/build.gradle.kts @@ -1,3 +1,7 @@ +import java.net.URI +import java.nio.file.FileSystems +import java.nio.file.Files +import java.nio.file.StandardCopyOption import java.util.zip.ZipFile import org.jetbrains.kotlin.config.KotlinCompilerVersion import org.jetbrains.kotlin.gradle.tasks.KotlinCompile @@ -83,10 +87,19 @@ tasks.shadowJar { dependsOn(mergeSpringMetadata) manifest { attributes["Main-Class"] = "io.sentry.samples.netflix.dgs.NetlixDgsApplication" } archiveClassifier.set("") - from(mergeSpringMetadata.map { project.layout.buildDirectory.dir("merged-spring-metadata") }) { - duplicatesStrategy = DuplicatesStrategy.INCLUDE - } mergeServiceFiles() + val metadataDir = project.layout.buildDirectory.dir("merged-spring-metadata/META-INF") + doLast { + val jarFile = archiveFile.get().asFile + val metaDir = metadataDir.get().asFile + if (!metaDir.exists()) return@doLast + val uri = URI.create("jar:${jarFile.toURI()}") + FileSystems.newFileSystem(uri, mapOf("create" to "false")).use { fs -> + metaDir.listFiles()?.forEach { merged -> + Files.copy(merged.toPath(), fs.getPath("META-INF/${merged.name}"), StandardCopyOption.REPLACE_EXISTING) + } + } + } } tasks.jar { diff --git a/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts index fe9a72dcf5a..39f961b94c5 100644 --- a/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts @@ -1,3 +1,7 @@ +import java.net.URI +import java.nio.file.FileSystems +import java.nio.file.Files +import java.nio.file.StandardCopyOption import java.util.zip.ZipFile import org.jetbrains.kotlin.config.KotlinCompilerVersion import org.jetbrains.kotlin.gradle.tasks.KotlinCompile @@ -124,10 +128,19 @@ tasks.shadowJar { dependsOn(mergeSpringMetadata) manifest { attributes["Main-Class"] = "io.sentry.samples.spring.boot.SentryDemoApplication" } archiveClassifier.set("") - from(mergeSpringMetadata.map { project.layout.buildDirectory.dir("merged-spring-metadata") }) { - duplicatesStrategy = DuplicatesStrategy.INCLUDE - } mergeServiceFiles() + val metadataDir = project.layout.buildDirectory.dir("merged-spring-metadata/META-INF") + doLast { + val jarFile = archiveFile.get().asFile + val metaDir = metadataDir.get().asFile + if (!metaDir.exists()) return@doLast + val uri = URI.create("jar:${jarFile.toURI()}") + FileSystems.newFileSystem(uri, mapOf("create" to "false")).use { fs -> + metaDir.listFiles()?.forEach { merged -> + Files.copy(merged.toPath(), fs.getPath("META-INF/${merged.name}"), StandardCopyOption.REPLACE_EXISTING) + } + } + } } tasks.jar { diff --git a/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts index 9496306f49d..ce44406ca50 100644 --- a/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts @@ -1,3 +1,7 @@ +import java.net.URI +import java.nio.file.FileSystems +import java.nio.file.Files +import java.nio.file.StandardCopyOption import java.util.zip.ZipFile import org.jetbrains.kotlin.config.KotlinCompilerVersion import org.jetbrains.kotlin.gradle.tasks.KotlinCompile @@ -120,10 +124,19 @@ tasks.shadowJar { dependsOn(mergeSpringMetadata) manifest { attributes["Main-Class"] = "io.sentry.samples.spring.boot.SentryDemoApplication" } archiveClassifier.set("") - from(mergeSpringMetadata.map { project.layout.buildDirectory.dir("merged-spring-metadata") }) { - duplicatesStrategy = DuplicatesStrategy.INCLUDE - } mergeServiceFiles() + val metadataDir = project.layout.buildDirectory.dir("merged-spring-metadata/META-INF") + doLast { + val jarFile = archiveFile.get().asFile + val metaDir = metadataDir.get().asFile + if (!metaDir.exists()) return@doLast + val uri = URI.create("jar:${jarFile.toURI()}") + FileSystems.newFileSystem(uri, mapOf("create" to "false")).use { fs -> + metaDir.listFiles()?.forEach { merged -> + Files.copy(merged.toPath(), fs.getPath("META-INF/${merged.name}"), StandardCopyOption.REPLACE_EXISTING) + } + } + } } tasks.jar { diff --git a/sentry-samples/sentry-samples-spring-boot-webflux/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-webflux/build.gradle.kts index 465d5e25d3b..64e5d4640fb 100644 --- a/sentry-samples/sentry-samples-spring-boot-webflux/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-webflux/build.gradle.kts @@ -1,3 +1,7 @@ +import java.net.URI +import java.nio.file.FileSystems +import java.nio.file.Files +import java.nio.file.StandardCopyOption import java.util.zip.ZipFile import org.jetbrains.kotlin.config.KotlinCompilerVersion import org.jetbrains.kotlin.gradle.tasks.KotlinCompile @@ -93,10 +97,19 @@ tasks.shadowJar { dependsOn(mergeSpringMetadata) manifest { attributes["Main-Class"] = "io.sentry.samples.spring.boot.SentryDemoApplication" } archiveClassifier.set("") - from(mergeSpringMetadata.map { project.layout.buildDirectory.dir("merged-spring-metadata") }) { - duplicatesStrategy = DuplicatesStrategy.INCLUDE - } mergeServiceFiles() + val metadataDir = project.layout.buildDirectory.dir("merged-spring-metadata/META-INF") + doLast { + val jarFile = archiveFile.get().asFile + val metaDir = metadataDir.get().asFile + if (!metaDir.exists()) return@doLast + val uri = URI.create("jar:${jarFile.toURI()}") + FileSystems.newFileSystem(uri, mapOf("create" to "false")).use { fs -> + metaDir.listFiles()?.forEach { merged -> + Files.copy(merged.toPath(), fs.getPath("META-INF/${merged.name}"), StandardCopyOption.REPLACE_EXISTING) + } + } + } } tasks.jar { diff --git a/sentry-samples/sentry-samples-spring-boot/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot/build.gradle.kts index ff1a506da0b..4d8600fdce2 100644 --- a/sentry-samples/sentry-samples-spring-boot/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot/build.gradle.kts @@ -1,3 +1,7 @@ +import java.net.URI +import java.nio.file.FileSystems +import java.nio.file.Files +import java.nio.file.StandardCopyOption import java.util.zip.ZipFile import org.jetbrains.kotlin.config.KotlinCompilerVersion import org.jetbrains.kotlin.gradle.tasks.KotlinCompile @@ -122,11 +126,29 @@ tasks.shadowJar { dependsOn(mergeSpringMetadata) manifest { attributes["Main-Class"] = "io.sentry.samples.spring.boot.SentryDemoApplication" } archiveClassifier.set("") - // Pre-merged Spring metadata files must come first so they win over duplicates from JARs - from(mergeSpringMetadata.map { project.layout.buildDirectory.dir("merged-spring-metadata") }) { - duplicatesStrategy = DuplicatesStrategy.INCLUDE - } mergeServiceFiles() + + // After the shadow JAR is built, replace Spring metadata files with pre-merged versions. + // Shadow 9.x's `append` transformer doesn't properly merge these files because + // DuplicatesStrategy is enforced before transformers run. + val metadataDir = project.layout.buildDirectory.dir("merged-spring-metadata/META-INF") + doLast { + val jarFile = archiveFile.get().asFile + val metaDir = metadataDir.get().asFile + if (!metaDir.exists()) return@doLast + val uri = URI.create("jar:${jarFile.toURI()}") + val env = mapOf("create" to "false") + FileSystems.newFileSystem(uri, env).use { fs -> + metaDir.listFiles()?.forEach { merged -> + val target = fs.getPath("META-INF/${merged.name}") + Files.copy( + merged.toPath(), + target, + StandardCopyOption.REPLACE_EXISTING, + ) + } + } + } } tasks.jar { From 05c5bac773698d450eda8f75a7ab77415c0e0c59 Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Fri, 10 Apr 2026 19:03:12 +0200 Subject: [PATCH 18/27] formatting --- sentry-samples/sentry-samples-netflix-dgs/build.gradle.kts | 6 +++++- .../build.gradle.kts | 6 +++++- .../build.gradle.kts | 6 +++++- .../sentry-samples-spring-boot-webflux/build.gradle.kts | 6 +++++- sentry-samples/sentry-samples-spring-boot/build.gradle.kts | 6 +----- 5 files changed, 21 insertions(+), 9 deletions(-) diff --git a/sentry-samples/sentry-samples-netflix-dgs/build.gradle.kts b/sentry-samples/sentry-samples-netflix-dgs/build.gradle.kts index adfe893b0e2..95db0a09d89 100644 --- a/sentry-samples/sentry-samples-netflix-dgs/build.gradle.kts +++ b/sentry-samples/sentry-samples-netflix-dgs/build.gradle.kts @@ -96,7 +96,11 @@ tasks.shadowJar { val uri = URI.create("jar:${jarFile.toURI()}") FileSystems.newFileSystem(uri, mapOf("create" to "false")).use { fs -> metaDir.listFiles()?.forEach { merged -> - Files.copy(merged.toPath(), fs.getPath("META-INF/${merged.name}"), StandardCopyOption.REPLACE_EXISTING) + Files.copy( + merged.toPath(), + fs.getPath("META-INF/${merged.name}"), + StandardCopyOption.REPLACE_EXISTING, + ) } } } diff --git a/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts index 39f961b94c5..4a1ebebd07c 100644 --- a/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts @@ -137,7 +137,11 @@ tasks.shadowJar { val uri = URI.create("jar:${jarFile.toURI()}") FileSystems.newFileSystem(uri, mapOf("create" to "false")).use { fs -> metaDir.listFiles()?.forEach { merged -> - Files.copy(merged.toPath(), fs.getPath("META-INF/${merged.name}"), StandardCopyOption.REPLACE_EXISTING) + Files.copy( + merged.toPath(), + fs.getPath("META-INF/${merged.name}"), + StandardCopyOption.REPLACE_EXISTING, + ) } } } diff --git a/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts index ce44406ca50..75052a22209 100644 --- a/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts @@ -133,7 +133,11 @@ tasks.shadowJar { val uri = URI.create("jar:${jarFile.toURI()}") FileSystems.newFileSystem(uri, mapOf("create" to "false")).use { fs -> metaDir.listFiles()?.forEach { merged -> - Files.copy(merged.toPath(), fs.getPath("META-INF/${merged.name}"), StandardCopyOption.REPLACE_EXISTING) + Files.copy( + merged.toPath(), + fs.getPath("META-INF/${merged.name}"), + StandardCopyOption.REPLACE_EXISTING, + ) } } } diff --git a/sentry-samples/sentry-samples-spring-boot-webflux/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-webflux/build.gradle.kts index 64e5d4640fb..e38ff19dbc7 100644 --- a/sentry-samples/sentry-samples-spring-boot-webflux/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-webflux/build.gradle.kts @@ -106,7 +106,11 @@ tasks.shadowJar { val uri = URI.create("jar:${jarFile.toURI()}") FileSystems.newFileSystem(uri, mapOf("create" to "false")).use { fs -> metaDir.listFiles()?.forEach { merged -> - Files.copy(merged.toPath(), fs.getPath("META-INF/${merged.name}"), StandardCopyOption.REPLACE_EXISTING) + Files.copy( + merged.toPath(), + fs.getPath("META-INF/${merged.name}"), + StandardCopyOption.REPLACE_EXISTING, + ) } } } diff --git a/sentry-samples/sentry-samples-spring-boot/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot/build.gradle.kts index 4d8600fdce2..07a93fb0d66 100644 --- a/sentry-samples/sentry-samples-spring-boot/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot/build.gradle.kts @@ -141,11 +141,7 @@ tasks.shadowJar { FileSystems.newFileSystem(uri, env).use { fs -> metaDir.listFiles()?.forEach { merged -> val target = fs.getPath("META-INF/${merged.name}") - Files.copy( - merged.toPath(), - target, - StandardCopyOption.REPLACE_EXISTING, - ) + Files.copy(merged.toPath(), target, StandardCopyOption.REPLACE_EXISTING) } } } From 4bef2eb026e59cac53be7612a48023d87e48ac1a Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Fri, 10 Apr 2026 23:11:46 +0200 Subject: [PATCH 19/27] fix(spring-boot2): merge AutoConfiguration.imports + doLast JAR patching The shadow JAR was missing the embedded web server auto-configuration because AutoConfiguration.imports (used by SB 2.7+) had duplicate entries from multiple dependency JARs, with only the last copy surviving. Add AutoConfiguration.imports to the pre-merge file list and use doLast JAR patching via NIO ZIP filesystem to replace metadata files after the shadow JAR is built, avoiding the DuplicatesStrategy issue entirely. Also suppress OldTargetApi lint for uitest-android-benchmark module. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../build.gradle.kts | 2 ++ .../build.gradle.kts | 22 ++++++------ .../build.gradle.kts | 22 ++++++------ .../build.gradle.kts | 22 ++++++------ .../build.gradle.kts | 22 ++++++------ .../build.gradle.kts | 35 ++++++++++--------- 6 files changed, 69 insertions(+), 56 deletions(-) diff --git a/sentry-android-integration-tests/sentry-uitest-android-benchmark/build.gradle.kts b/sentry-android-integration-tests/sentry-uitest-android-benchmark/build.gradle.kts index e6480d8b37d..4b5993644ee 100644 --- a/sentry-android-integration-tests/sentry-uitest-android-benchmark/build.gradle.kts +++ b/sentry-android-integration-tests/sentry-uitest-android-benchmark/build.gradle.kts @@ -81,6 +81,8 @@ android { lint { warningsAsErrors = true checkDependencies = true + // Suppress OldTargetApi: lint 8.13.1 expects API 37 but we target 36 + disable += "OldTargetApi" // We run a full lint analysis as build part in CI, so skip vital checks for assemble tasks. checkReleaseBuilds = false diff --git a/sentry-samples/sentry-samples-netflix-dgs/build.gradle.kts b/sentry-samples/sentry-samples-netflix-dgs/build.gradle.kts index 95db0a09d89..3460faf6a5f 100644 --- a/sentry-samples/sentry-samples-netflix-dgs/build.gradle.kts +++ b/sentry-samples/sentry-samples-netflix-dgs/build.gradle.kts @@ -46,26 +46,26 @@ dependencies { // from the runtime classpath and include the merged result in the shadow JAR. val mergeSpringMetadata by tasks.registering { - val outputDir = project.layout.buildDirectory.dir("merged-spring-metadata/META-INF") + val outputDir = project.layout.buildDirectory.dir("merged-spring-metadata") val classpathJars = configurations.runtimeClasspath.get().filter { it.name.endsWith(".jar") } val filesToMerge = listOf( - "spring.factories", - "spring.handlers", - "spring.schemas", - "spring-autoconfigure-metadata.properties", + "META-INF/spring.factories", + "META-INF/spring.handlers", + "META-INF/spring.schemas", + "META-INF/spring-autoconfigure-metadata.properties", + "META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports", ) outputs.dir(outputDir) inputs.files(classpathJars) doLast { val out = outputDir.get().asFile - out.mkdirs() - filesToMerge.forEach { fileName -> + filesToMerge.forEach { entryPath -> val merged = StringBuilder() classpathJars.forEach { jar -> try { val zip = ZipFile(jar) - val entry = zip.getEntry("META-INF/$fileName") + val entry = zip.getEntry(entryPath) if (entry != null) { merged.append(zip.getInputStream(entry).bufferedReader().readText()) if (!merged.endsWith("\n")) merged.append("\n") @@ -76,7 +76,9 @@ val mergeSpringMetadata by } } if (merged.isNotEmpty()) { - File(out, fileName).writeText(merged.toString()) + val outFile = File(out, entryPath) + outFile.parentFile.mkdirs() + outFile.writeText(merged.toString()) } } } @@ -88,7 +90,7 @@ tasks.shadowJar { manifest { attributes["Main-Class"] = "io.sentry.samples.netflix.dgs.NetlixDgsApplication" } archiveClassifier.set("") mergeServiceFiles() - val metadataDir = project.layout.buildDirectory.dir("merged-spring-metadata/META-INF") + val metadataDir = project.layout.buildDirectory.dir("merged-spring-metadata") doLast { val jarFile = archiveFile.get().asFile val metaDir = metadataDir.get().asFile diff --git a/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts index 4a1ebebd07c..9ffac96774e 100644 --- a/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts @@ -87,26 +87,26 @@ dependencies { // from the runtime classpath and include the merged result in the shadow JAR. val mergeSpringMetadata by tasks.registering { - val outputDir = project.layout.buildDirectory.dir("merged-spring-metadata/META-INF") + val outputDir = project.layout.buildDirectory.dir("merged-spring-metadata") val classpathJars = configurations.runtimeClasspath.get().filter { it.name.endsWith(".jar") } val filesToMerge = listOf( - "spring.factories", - "spring.handlers", - "spring.schemas", - "spring-autoconfigure-metadata.properties", + "META-INF/spring.factories", + "META-INF/spring.handlers", + "META-INF/spring.schemas", + "META-INF/spring-autoconfigure-metadata.properties", + "META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports", ) outputs.dir(outputDir) inputs.files(classpathJars) doLast { val out = outputDir.get().asFile - out.mkdirs() - filesToMerge.forEach { fileName -> + filesToMerge.forEach { entryPath -> val merged = StringBuilder() classpathJars.forEach { jar -> try { val zip = ZipFile(jar) - val entry = zip.getEntry("META-INF/$fileName") + val entry = zip.getEntry(entryPath) if (entry != null) { merged.append(zip.getInputStream(entry).bufferedReader().readText()) if (!merged.endsWith("\n")) merged.append("\n") @@ -117,7 +117,9 @@ val mergeSpringMetadata by } } if (merged.isNotEmpty()) { - File(out, fileName).writeText(merged.toString()) + val outFile = File(out, entryPath) + outFile.parentFile.mkdirs() + outFile.writeText(merged.toString()) } } } @@ -129,7 +131,7 @@ tasks.shadowJar { manifest { attributes["Main-Class"] = "io.sentry.samples.spring.boot.SentryDemoApplication" } archiveClassifier.set("") mergeServiceFiles() - val metadataDir = project.layout.buildDirectory.dir("merged-spring-metadata/META-INF") + val metadataDir = project.layout.buildDirectory.dir("merged-spring-metadata") doLast { val jarFile = archiveFile.get().asFile val metaDir = metadataDir.get().asFile diff --git a/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts index 75052a22209..380f2cc301c 100644 --- a/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts @@ -83,26 +83,26 @@ dependencies { // from the runtime classpath and include the merged result in the shadow JAR. val mergeSpringMetadata by tasks.registering { - val outputDir = project.layout.buildDirectory.dir("merged-spring-metadata/META-INF") + val outputDir = project.layout.buildDirectory.dir("merged-spring-metadata") val classpathJars = configurations.runtimeClasspath.get().filter { it.name.endsWith(".jar") } val filesToMerge = listOf( - "spring.factories", - "spring.handlers", - "spring.schemas", - "spring-autoconfigure-metadata.properties", + "META-INF/spring.factories", + "META-INF/spring.handlers", + "META-INF/spring.schemas", + "META-INF/spring-autoconfigure-metadata.properties", + "META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports", ) outputs.dir(outputDir) inputs.files(classpathJars) doLast { val out = outputDir.get().asFile - out.mkdirs() - filesToMerge.forEach { fileName -> + filesToMerge.forEach { entryPath -> val merged = StringBuilder() classpathJars.forEach { jar -> try { val zip = ZipFile(jar) - val entry = zip.getEntry("META-INF/$fileName") + val entry = zip.getEntry(entryPath) if (entry != null) { merged.append(zip.getInputStream(entry).bufferedReader().readText()) if (!merged.endsWith("\n")) merged.append("\n") @@ -113,7 +113,9 @@ val mergeSpringMetadata by } } if (merged.isNotEmpty()) { - File(out, fileName).writeText(merged.toString()) + val outFile = File(out, entryPath) + outFile.parentFile.mkdirs() + outFile.writeText(merged.toString()) } } } @@ -125,7 +127,7 @@ tasks.shadowJar { manifest { attributes["Main-Class"] = "io.sentry.samples.spring.boot.SentryDemoApplication" } archiveClassifier.set("") mergeServiceFiles() - val metadataDir = project.layout.buildDirectory.dir("merged-spring-metadata/META-INF") + val metadataDir = project.layout.buildDirectory.dir("merged-spring-metadata") doLast { val jarFile = archiveFile.get().asFile val metaDir = metadataDir.get().asFile diff --git a/sentry-samples/sentry-samples-spring-boot-webflux/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-webflux/build.gradle.kts index e38ff19dbc7..b6eea769e7d 100644 --- a/sentry-samples/sentry-samples-spring-boot-webflux/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-webflux/build.gradle.kts @@ -56,26 +56,26 @@ dependencies { // from the runtime classpath and include the merged result in the shadow JAR. val mergeSpringMetadata by tasks.registering { - val outputDir = project.layout.buildDirectory.dir("merged-spring-metadata/META-INF") + val outputDir = project.layout.buildDirectory.dir("merged-spring-metadata") val classpathJars = configurations.runtimeClasspath.get().filter { it.name.endsWith(".jar") } val filesToMerge = listOf( - "spring.factories", - "spring.handlers", - "spring.schemas", - "spring-autoconfigure-metadata.properties", + "META-INF/spring.factories", + "META-INF/spring.handlers", + "META-INF/spring.schemas", + "META-INF/spring-autoconfigure-metadata.properties", + "META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports", ) outputs.dir(outputDir) inputs.files(classpathJars) doLast { val out = outputDir.get().asFile - out.mkdirs() - filesToMerge.forEach { fileName -> + filesToMerge.forEach { entryPath -> val merged = StringBuilder() classpathJars.forEach { jar -> try { val zip = ZipFile(jar) - val entry = zip.getEntry("META-INF/$fileName") + val entry = zip.getEntry(entryPath) if (entry != null) { merged.append(zip.getInputStream(entry).bufferedReader().readText()) if (!merged.endsWith("\n")) merged.append("\n") @@ -86,7 +86,9 @@ val mergeSpringMetadata by } } if (merged.isNotEmpty()) { - File(out, fileName).writeText(merged.toString()) + val outFile = File(out, entryPath) + outFile.parentFile.mkdirs() + outFile.writeText(merged.toString()) } } } @@ -98,7 +100,7 @@ tasks.shadowJar { manifest { attributes["Main-Class"] = "io.sentry.samples.spring.boot.SentryDemoApplication" } archiveClassifier.set("") mergeServiceFiles() - val metadataDir = project.layout.buildDirectory.dir("merged-spring-metadata/META-INF") + val metadataDir = project.layout.buildDirectory.dir("merged-spring-metadata") doLast { val jarFile = archiveFile.get().asFile val metaDir = metadataDir.get().asFile diff --git a/sentry-samples/sentry-samples-spring-boot/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot/build.gradle.kts index 07a93fb0d66..5989fed4056 100644 --- a/sentry-samples/sentry-samples-spring-boot/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot/build.gradle.kts @@ -83,14 +83,15 @@ dependencies { // from the runtime classpath and include the merged result in the shadow JAR. val mergeSpringMetadata by tasks.registering { - val outputDir = project.layout.buildDirectory.dir("merged-spring-metadata/META-INF") + val outputDir = project.layout.buildDirectory.dir("merged-spring-metadata") val classpathJars = configurations.runtimeClasspath.get().filter { it.name.endsWith(".jar") } val filesToMerge = listOf( - "spring.factories", - "spring.handlers", - "spring.schemas", - "spring-autoconfigure-metadata.properties", + "META-INF/spring.factories", + "META-INF/spring.handlers", + "META-INF/spring.schemas", + "META-INF/spring-autoconfigure-metadata.properties", + "META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports", ) outputs.dir(outputDir) @@ -98,13 +99,12 @@ val mergeSpringMetadata by doLast { val out = outputDir.get().asFile - out.mkdirs() - filesToMerge.forEach { fileName -> + filesToMerge.forEach { entryPath -> val merged = StringBuilder() classpathJars.forEach { jar -> try { val zip = ZipFile(jar) - val entry = zip.getEntry("META-INF/$fileName") + val entry = zip.getEntry(entryPath) if (entry != null) { merged.append(zip.getInputStream(entry).bufferedReader().readText()) if (!merged.endsWith("\n")) merged.append("\n") @@ -115,7 +115,9 @@ val mergeSpringMetadata by } } if (merged.isNotEmpty()) { - File(out, fileName).writeText(merged.toString()) + val outFile = File(out, entryPath) + outFile.parentFile.mkdirs() + outFile.writeText(merged.toString()) } } } @@ -131,16 +133,17 @@ tasks.shadowJar { // After the shadow JAR is built, replace Spring metadata files with pre-merged versions. // Shadow 9.x's `append` transformer doesn't properly merge these files because // DuplicatesStrategy is enforced before transformers run. - val metadataDir = project.layout.buildDirectory.dir("merged-spring-metadata/META-INF") + val metadataDir = project.layout.buildDirectory.dir("merged-spring-metadata") doLast { val jarFile = archiveFile.get().asFile - val metaDir = metadataDir.get().asFile - if (!metaDir.exists()) return@doLast + val baseDir = metadataDir.get().asFile + if (!baseDir.exists()) return@doLast val uri = URI.create("jar:${jarFile.toURI()}") - val env = mapOf("create" to "false") - FileSystems.newFileSystem(uri, env).use { fs -> - metaDir.listFiles()?.forEach { merged -> - val target = fs.getPath("META-INF/${merged.name}") + FileSystems.newFileSystem(uri, mapOf("create" to "false")).use { fs -> + baseDir.walkTopDown().filter { it.isFile }.forEach { merged -> + val relative = merged.relativeTo(baseDir).path + val target = fs.getPath(relative) + if (target.parent != null) Files.createDirectories(target.parent) Files.copy(merged.toPath(), target, StandardCopyOption.REPLACE_EXISTING) } } From 9fa7649e88f557eeadf33a41aca6e3d2115c35c5 Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Fri, 10 Apr 2026 23:15:03 +0200 Subject: [PATCH 20/27] fix(spring-boot2): also merge ManagementContextConfiguration.imports This file has duplicate entries across actuator JARs and needs the same pre-merge treatment as AutoConfiguration.imports. Co-Authored-By: Claude Opus 4.6 (1M context) --- sentry-samples/sentry-samples-netflix-dgs/build.gradle.kts | 1 + .../build.gradle.kts | 1 + .../sentry-samples-spring-boot-opentelemetry/build.gradle.kts | 1 + .../sentry-samples-spring-boot-webflux/build.gradle.kts | 1 + sentry-samples/sentry-samples-spring-boot/build.gradle.kts | 1 + 5 files changed, 5 insertions(+) diff --git a/sentry-samples/sentry-samples-netflix-dgs/build.gradle.kts b/sentry-samples/sentry-samples-netflix-dgs/build.gradle.kts index 3460faf6a5f..17ec8b3d445 100644 --- a/sentry-samples/sentry-samples-netflix-dgs/build.gradle.kts +++ b/sentry-samples/sentry-samples-netflix-dgs/build.gradle.kts @@ -55,6 +55,7 @@ val mergeSpringMetadata by "META-INF/spring.schemas", "META-INF/spring-autoconfigure-metadata.properties", "META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports", + "META-INF/spring/org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration.imports", ) outputs.dir(outputDir) inputs.files(classpathJars) diff --git a/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts index 9ffac96774e..37675f21c5e 100644 --- a/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts @@ -96,6 +96,7 @@ val mergeSpringMetadata by "META-INF/spring.schemas", "META-INF/spring-autoconfigure-metadata.properties", "META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports", + "META-INF/spring/org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration.imports", ) outputs.dir(outputDir) inputs.files(classpathJars) diff --git a/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts index 380f2cc301c..bdcdab1519c 100644 --- a/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts @@ -92,6 +92,7 @@ val mergeSpringMetadata by "META-INF/spring.schemas", "META-INF/spring-autoconfigure-metadata.properties", "META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports", + "META-INF/spring/org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration.imports", ) outputs.dir(outputDir) inputs.files(classpathJars) diff --git a/sentry-samples/sentry-samples-spring-boot-webflux/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-webflux/build.gradle.kts index b6eea769e7d..72a78adfb21 100644 --- a/sentry-samples/sentry-samples-spring-boot-webflux/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-webflux/build.gradle.kts @@ -65,6 +65,7 @@ val mergeSpringMetadata by "META-INF/spring.schemas", "META-INF/spring-autoconfigure-metadata.properties", "META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports", + "META-INF/spring/org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration.imports", ) outputs.dir(outputDir) inputs.files(classpathJars) diff --git a/sentry-samples/sentry-samples-spring-boot/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot/build.gradle.kts index 5989fed4056..9ebb25c9b6a 100644 --- a/sentry-samples/sentry-samples-spring-boot/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot/build.gradle.kts @@ -92,6 +92,7 @@ val mergeSpringMetadata by "META-INF/spring.schemas", "META-INF/spring-autoconfigure-metadata.properties", "META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports", + "META-INF/spring/org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration.imports", ) outputs.dir(outputDir) From 4a277ae6e8d43063c7df680a6328cd0b21e0398b Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Fri, 10 Apr 2026 23:19:52 +0200 Subject: [PATCH 21/27] formatting --- .../sentry-samples-spring-boot/build.gradle.kts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/sentry-samples/sentry-samples-spring-boot/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot/build.gradle.kts index 9ebb25c9b6a..d6703d01b5d 100644 --- a/sentry-samples/sentry-samples-spring-boot/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot/build.gradle.kts @@ -141,12 +141,15 @@ tasks.shadowJar { if (!baseDir.exists()) return@doLast val uri = URI.create("jar:${jarFile.toURI()}") FileSystems.newFileSystem(uri, mapOf("create" to "false")).use { fs -> - baseDir.walkTopDown().filter { it.isFile }.forEach { merged -> - val relative = merged.relativeTo(baseDir).path - val target = fs.getPath(relative) - if (target.parent != null) Files.createDirectories(target.parent) - Files.copy(merged.toPath(), target, StandardCopyOption.REPLACE_EXISTING) - } + baseDir + .walkTopDown() + .filter { it.isFile } + .forEach { merged -> + val relative = merged.relativeTo(baseDir).path + val target = fs.getPath(relative) + if (target.parent != null) Files.createDirectories(target.parent) + Files.copy(merged.toPath(), target, StandardCopyOption.REPLACE_EXISTING) + } } } } From 991221e010f061dc44e67a883125dc76321e10a6 Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Fri, 10 Apr 2026 23:50:28 +0200 Subject: [PATCH 22/27] fix(spring-boot2): use separate patchSpringMetadata task for JAR patching The doLast on shadowJar doesn't run when the task is cached/up-to-date. Move JAR patching to a separate `patchSpringMetadata` task that is finalized by shadowJar, ensuring it always runs. Also use recursive walkTopDown to handle nested directories (e.g. META-INF/spring/). Co-Authored-By: Claude Opus 4.6 (1M context) --- .../build.gradle.kts | 29 +++++++++------ .../build.gradle.kts | 29 +++++++++------ .../build.gradle.kts | 29 +++++++++------ .../build.gradle.kts | 29 +++++++++------ .../build.gradle.kts | 35 +++++++++++-------- 5 files changed, 92 insertions(+), 59 deletions(-) diff --git a/sentry-samples/sentry-samples-netflix-dgs/build.gradle.kts b/sentry-samples/sentry-samples-netflix-dgs/build.gradle.kts index 17ec8b3d445..e1a651a2255 100644 --- a/sentry-samples/sentry-samples-netflix-dgs/build.gradle.kts +++ b/sentry-samples/sentry-samples-netflix-dgs/build.gradle.kts @@ -87,23 +87,30 @@ val mergeSpringMetadata by // Configure the Shadow JAR (executable JAR with all dependencies) tasks.shadowJar { - dependsOn(mergeSpringMetadata) manifest { attributes["Main-Class"] = "io.sentry.samples.netflix.dgs.NetlixDgsApplication" } archiveClassifier.set("") mergeServiceFiles() + outputs.upToDateWhen { false } + finalizedBy("patchSpringMetadata") +} + +tasks.register("patchSpringMetadata") { + dependsOn(mergeSpringMetadata, tasks.shadowJar) val metadataDir = project.layout.buildDirectory.dir("merged-spring-metadata") + val jarFile = tasks.shadowJar.flatMap { it.archiveFile } + inputs.dir(metadataDir) + inputs.file(jarFile) doLast { - val jarFile = archiveFile.get().asFile - val metaDir = metadataDir.get().asFile - if (!metaDir.exists()) return@doLast - val uri = URI.create("jar:${jarFile.toURI()}") + val baseDir = metadataDir.get().asFile + val jar = jarFile.get().asFile + if (!baseDir.exists()) return@doLast + val uri = URI.create("jar:${jar.toURI()}") FileSystems.newFileSystem(uri, mapOf("create" to "false")).use { fs -> - metaDir.listFiles()?.forEach { merged -> - Files.copy( - merged.toPath(), - fs.getPath("META-INF/${merged.name}"), - StandardCopyOption.REPLACE_EXISTING, - ) + baseDir.walkTopDown().filter { it.isFile }.forEach { merged -> + val relative = merged.relativeTo(baseDir).path + val target = fs.getPath(relative) + if (target.parent != null) Files.createDirectories(target.parent) + Files.copy(merged.toPath(), target, StandardCopyOption.REPLACE_EXISTING) } } } diff --git a/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts index 37675f21c5e..968ce65bdc7 100644 --- a/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts @@ -128,23 +128,30 @@ val mergeSpringMetadata by // Configure the Shadow JAR (executable JAR with all dependencies) tasks.shadowJar { - dependsOn(mergeSpringMetadata) manifest { attributes["Main-Class"] = "io.sentry.samples.spring.boot.SentryDemoApplication" } archiveClassifier.set("") mergeServiceFiles() + outputs.upToDateWhen { false } + finalizedBy("patchSpringMetadata") +} + +tasks.register("patchSpringMetadata") { + dependsOn(mergeSpringMetadata, tasks.shadowJar) val metadataDir = project.layout.buildDirectory.dir("merged-spring-metadata") + val jarFile = tasks.shadowJar.flatMap { it.archiveFile } + inputs.dir(metadataDir) + inputs.file(jarFile) doLast { - val jarFile = archiveFile.get().asFile - val metaDir = metadataDir.get().asFile - if (!metaDir.exists()) return@doLast - val uri = URI.create("jar:${jarFile.toURI()}") + val baseDir = metadataDir.get().asFile + val jar = jarFile.get().asFile + if (!baseDir.exists()) return@doLast + val uri = URI.create("jar:${jar.toURI()}") FileSystems.newFileSystem(uri, mapOf("create" to "false")).use { fs -> - metaDir.listFiles()?.forEach { merged -> - Files.copy( - merged.toPath(), - fs.getPath("META-INF/${merged.name}"), - StandardCopyOption.REPLACE_EXISTING, - ) + baseDir.walkTopDown().filter { it.isFile }.forEach { merged -> + val relative = merged.relativeTo(baseDir).path + val target = fs.getPath(relative) + if (target.parent != null) Files.createDirectories(target.parent) + Files.copy(merged.toPath(), target, StandardCopyOption.REPLACE_EXISTING) } } } diff --git a/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts index bdcdab1519c..aa2570165f5 100644 --- a/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts @@ -124,23 +124,30 @@ val mergeSpringMetadata by // Configure the Shadow JAR (executable JAR with all dependencies) tasks.shadowJar { - dependsOn(mergeSpringMetadata) manifest { attributes["Main-Class"] = "io.sentry.samples.spring.boot.SentryDemoApplication" } archiveClassifier.set("") mergeServiceFiles() + outputs.upToDateWhen { false } + finalizedBy("patchSpringMetadata") +} + +tasks.register("patchSpringMetadata") { + dependsOn(mergeSpringMetadata, tasks.shadowJar) val metadataDir = project.layout.buildDirectory.dir("merged-spring-metadata") + val jarFile = tasks.shadowJar.flatMap { it.archiveFile } + inputs.dir(metadataDir) + inputs.file(jarFile) doLast { - val jarFile = archiveFile.get().asFile - val metaDir = metadataDir.get().asFile - if (!metaDir.exists()) return@doLast - val uri = URI.create("jar:${jarFile.toURI()}") + val baseDir = metadataDir.get().asFile + val jar = jarFile.get().asFile + if (!baseDir.exists()) return@doLast + val uri = URI.create("jar:${jar.toURI()}") FileSystems.newFileSystem(uri, mapOf("create" to "false")).use { fs -> - metaDir.listFiles()?.forEach { merged -> - Files.copy( - merged.toPath(), - fs.getPath("META-INF/${merged.name}"), - StandardCopyOption.REPLACE_EXISTING, - ) + baseDir.walkTopDown().filter { it.isFile }.forEach { merged -> + val relative = merged.relativeTo(baseDir).path + val target = fs.getPath(relative) + if (target.parent != null) Files.createDirectories(target.parent) + Files.copy(merged.toPath(), target, StandardCopyOption.REPLACE_EXISTING) } } } diff --git a/sentry-samples/sentry-samples-spring-boot-webflux/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-webflux/build.gradle.kts index 72a78adfb21..b5f27c785ea 100644 --- a/sentry-samples/sentry-samples-spring-boot-webflux/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-webflux/build.gradle.kts @@ -97,23 +97,30 @@ val mergeSpringMetadata by // Configure the Shadow JAR (executable JAR with all dependencies) tasks.shadowJar { - dependsOn(mergeSpringMetadata) manifest { attributes["Main-Class"] = "io.sentry.samples.spring.boot.SentryDemoApplication" } archiveClassifier.set("") mergeServiceFiles() + outputs.upToDateWhen { false } + finalizedBy("patchSpringMetadata") +} + +tasks.register("patchSpringMetadata") { + dependsOn(mergeSpringMetadata, tasks.shadowJar) val metadataDir = project.layout.buildDirectory.dir("merged-spring-metadata") + val jarFile = tasks.shadowJar.flatMap { it.archiveFile } + inputs.dir(metadataDir) + inputs.file(jarFile) doLast { - val jarFile = archiveFile.get().asFile - val metaDir = metadataDir.get().asFile - if (!metaDir.exists()) return@doLast - val uri = URI.create("jar:${jarFile.toURI()}") + val baseDir = metadataDir.get().asFile + val jar = jarFile.get().asFile + if (!baseDir.exists()) return@doLast + val uri = URI.create("jar:${jar.toURI()}") FileSystems.newFileSystem(uri, mapOf("create" to "false")).use { fs -> - metaDir.listFiles()?.forEach { merged -> - Files.copy( - merged.toPath(), - fs.getPath("META-INF/${merged.name}"), - StandardCopyOption.REPLACE_EXISTING, - ) + baseDir.walkTopDown().filter { it.isFile }.forEach { merged -> + val relative = merged.relativeTo(baseDir).path + val target = fs.getPath(relative) + if (target.parent != null) Files.createDirectories(target.parent) + Files.copy(merged.toPath(), target, StandardCopyOption.REPLACE_EXISTING) } } } diff --git a/sentry-samples/sentry-samples-spring-boot/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot/build.gradle.kts index d6703d01b5d..da66913ba70 100644 --- a/sentry-samples/sentry-samples-spring-boot/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot/build.gradle.kts @@ -126,30 +126,35 @@ val mergeSpringMetadata by // Configure the Shadow JAR (executable JAR with all dependencies) tasks.shadowJar { - dependsOn(mergeSpringMetadata) manifest { attributes["Main-Class"] = "io.sentry.samples.spring.boot.SentryDemoApplication" } archiveClassifier.set("") mergeServiceFiles() + // Mark as never up-to-date so doLast always runs to patch Spring metadata + outputs.upToDateWhen { false } + finalizedBy("patchSpringMetadata") +} - // After the shadow JAR is built, replace Spring metadata files with pre-merged versions. - // Shadow 9.x's `append` transformer doesn't properly merge these files because - // DuplicatesStrategy is enforced before transformers run. +// Patch the shadow JAR with pre-merged Spring metadata after it's built. +// Shadow 9.x's `append` transformer doesn't properly merge these files because +// DuplicatesStrategy is enforced before transformers run. +tasks.register("patchSpringMetadata") { + dependsOn(mergeSpringMetadata, tasks.shadowJar) val metadataDir = project.layout.buildDirectory.dir("merged-spring-metadata") + val jarFile = tasks.shadowJar.flatMap { it.archiveFile } + inputs.dir(metadataDir) + inputs.file(jarFile) doLast { - val jarFile = archiveFile.get().asFile val baseDir = metadataDir.get().asFile + val jar = jarFile.get().asFile if (!baseDir.exists()) return@doLast - val uri = URI.create("jar:${jarFile.toURI()}") + val uri = URI.create("jar:${jar.toURI()}") FileSystems.newFileSystem(uri, mapOf("create" to "false")).use { fs -> - baseDir - .walkTopDown() - .filter { it.isFile } - .forEach { merged -> - val relative = merged.relativeTo(baseDir).path - val target = fs.getPath(relative) - if (target.parent != null) Files.createDirectories(target.parent) - Files.copy(merged.toPath(), target, StandardCopyOption.REPLACE_EXISTING) - } + baseDir.walkTopDown().filter { it.isFile }.forEach { merged -> + val relative = merged.relativeTo(baseDir).path + val target = fs.getPath(relative) + if (target.parent != null) Files.createDirectories(target.parent) + Files.copy(merged.toPath(), target, StandardCopyOption.REPLACE_EXISTING) + } } } } From 500f7f18e09286ee67fbcd88e51cd91378f907ff Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Fri, 10 Apr 2026 23:57:45 +0200 Subject: [PATCH 23/27] formatting --- .../sentry-samples-netflix-dgs/build.gradle.kts | 15 +++++++++------ .../build.gradle.kts | 15 +++++++++------ .../build.gradle.kts | 15 +++++++++------ .../build.gradle.kts | 15 +++++++++------ .../sentry-samples-spring-boot/build.gradle.kts | 15 +++++++++------ 5 files changed, 45 insertions(+), 30 deletions(-) diff --git a/sentry-samples/sentry-samples-netflix-dgs/build.gradle.kts b/sentry-samples/sentry-samples-netflix-dgs/build.gradle.kts index e1a651a2255..6d002aad5f0 100644 --- a/sentry-samples/sentry-samples-netflix-dgs/build.gradle.kts +++ b/sentry-samples/sentry-samples-netflix-dgs/build.gradle.kts @@ -106,12 +106,15 @@ tasks.register("patchSpringMetadata") { if (!baseDir.exists()) return@doLast val uri = URI.create("jar:${jar.toURI()}") FileSystems.newFileSystem(uri, mapOf("create" to "false")).use { fs -> - baseDir.walkTopDown().filter { it.isFile }.forEach { merged -> - val relative = merged.relativeTo(baseDir).path - val target = fs.getPath(relative) - if (target.parent != null) Files.createDirectories(target.parent) - Files.copy(merged.toPath(), target, StandardCopyOption.REPLACE_EXISTING) - } + baseDir + .walkTopDown() + .filter { it.isFile } + .forEach { merged -> + val relative = merged.relativeTo(baseDir).path + val target = fs.getPath(relative) + if (target.parent != null) Files.createDirectories(target.parent) + Files.copy(merged.toPath(), target, StandardCopyOption.REPLACE_EXISTING) + } } } } diff --git a/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts index 968ce65bdc7..ec31ea56952 100644 --- a/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts @@ -147,12 +147,15 @@ tasks.register("patchSpringMetadata") { if (!baseDir.exists()) return@doLast val uri = URI.create("jar:${jar.toURI()}") FileSystems.newFileSystem(uri, mapOf("create" to "false")).use { fs -> - baseDir.walkTopDown().filter { it.isFile }.forEach { merged -> - val relative = merged.relativeTo(baseDir).path - val target = fs.getPath(relative) - if (target.parent != null) Files.createDirectories(target.parent) - Files.copy(merged.toPath(), target, StandardCopyOption.REPLACE_EXISTING) - } + baseDir + .walkTopDown() + .filter { it.isFile } + .forEach { merged -> + val relative = merged.relativeTo(baseDir).path + val target = fs.getPath(relative) + if (target.parent != null) Files.createDirectories(target.parent) + Files.copy(merged.toPath(), target, StandardCopyOption.REPLACE_EXISTING) + } } } } diff --git a/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts index aa2570165f5..eb684c79b56 100644 --- a/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts @@ -143,12 +143,15 @@ tasks.register("patchSpringMetadata") { if (!baseDir.exists()) return@doLast val uri = URI.create("jar:${jar.toURI()}") FileSystems.newFileSystem(uri, mapOf("create" to "false")).use { fs -> - baseDir.walkTopDown().filter { it.isFile }.forEach { merged -> - val relative = merged.relativeTo(baseDir).path - val target = fs.getPath(relative) - if (target.parent != null) Files.createDirectories(target.parent) - Files.copy(merged.toPath(), target, StandardCopyOption.REPLACE_EXISTING) - } + baseDir + .walkTopDown() + .filter { it.isFile } + .forEach { merged -> + val relative = merged.relativeTo(baseDir).path + val target = fs.getPath(relative) + if (target.parent != null) Files.createDirectories(target.parent) + Files.copy(merged.toPath(), target, StandardCopyOption.REPLACE_EXISTING) + } } } } diff --git a/sentry-samples/sentry-samples-spring-boot-webflux/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-webflux/build.gradle.kts index b5f27c785ea..c95fb5897af 100644 --- a/sentry-samples/sentry-samples-spring-boot-webflux/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-webflux/build.gradle.kts @@ -116,12 +116,15 @@ tasks.register("patchSpringMetadata") { if (!baseDir.exists()) return@doLast val uri = URI.create("jar:${jar.toURI()}") FileSystems.newFileSystem(uri, mapOf("create" to "false")).use { fs -> - baseDir.walkTopDown().filter { it.isFile }.forEach { merged -> - val relative = merged.relativeTo(baseDir).path - val target = fs.getPath(relative) - if (target.parent != null) Files.createDirectories(target.parent) - Files.copy(merged.toPath(), target, StandardCopyOption.REPLACE_EXISTING) - } + baseDir + .walkTopDown() + .filter { it.isFile } + .forEach { merged -> + val relative = merged.relativeTo(baseDir).path + val target = fs.getPath(relative) + if (target.parent != null) Files.createDirectories(target.parent) + Files.copy(merged.toPath(), target, StandardCopyOption.REPLACE_EXISTING) + } } } } diff --git a/sentry-samples/sentry-samples-spring-boot/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot/build.gradle.kts index da66913ba70..e15c9dd1d14 100644 --- a/sentry-samples/sentry-samples-spring-boot/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot/build.gradle.kts @@ -149,12 +149,15 @@ tasks.register("patchSpringMetadata") { if (!baseDir.exists()) return@doLast val uri = URI.create("jar:${jar.toURI()}") FileSystems.newFileSystem(uri, mapOf("create" to "false")).use { fs -> - baseDir.walkTopDown().filter { it.isFile }.forEach { merged -> - val relative = merged.relativeTo(baseDir).path - val target = fs.getPath(relative) - if (target.parent != null) Files.createDirectories(target.parent) - Files.copy(merged.toPath(), target, StandardCopyOption.REPLACE_EXISTING) - } + baseDir + .walkTopDown() + .filter { it.isFile } + .forEach { merged -> + val relative = merged.relativeTo(baseDir).path + val target = fs.getPath(relative) + if (target.parent != null) Files.createDirectories(target.parent) + Files.copy(merged.toPath(), target, StandardCopyOption.REPLACE_EXISTING) + } } } } From cafc487c49eb484cfe9aa97730d6e5f400f5c484 Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Sat, 11 Apr 2026 09:23:44 +0200 Subject: [PATCH 24/27] fix(spring-boot2): revert to doLast on shadowJar for Spring metadata patching MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The separate patchSpringMetadata task approach caused regressions — the finalizedBy relationship didn't reliably execute the patching in CI. Revert to doLast directly on shadowJar with outputs.upToDateWhen { false } to ensure the patching always runs. Also use walkTopDown for recursive directory traversal (needed for META-INF/spring/ subdirectory). Co-Authored-By: Claude Opus 4.6 (1M context) --- .../build.gradle.kts | 26 +++++---------- .../build.gradle.kts | 26 +++++---------- .../build.gradle.kts | 26 +++++---------- .../build.gradle.kts | 26 +++++---------- .../build.gradle.kts | 33 +++++++------------ 5 files changed, 43 insertions(+), 94 deletions(-) diff --git a/sentry-samples/sentry-samples-netflix-dgs/build.gradle.kts b/sentry-samples/sentry-samples-netflix-dgs/build.gradle.kts index 6d002aad5f0..48f323ed831 100644 --- a/sentry-samples/sentry-samples-netflix-dgs/build.gradle.kts +++ b/sentry-samples/sentry-samples-netflix-dgs/build.gradle.kts @@ -87,34 +87,24 @@ val mergeSpringMetadata by // Configure the Shadow JAR (executable JAR with all dependencies) tasks.shadowJar { + dependsOn(mergeSpringMetadata) manifest { attributes["Main-Class"] = "io.sentry.samples.netflix.dgs.NetlixDgsApplication" } archiveClassifier.set("") mergeServiceFiles() outputs.upToDateWhen { false } - finalizedBy("patchSpringMetadata") -} - -tasks.register("patchSpringMetadata") { - dependsOn(mergeSpringMetadata, tasks.shadowJar) val metadataDir = project.layout.buildDirectory.dir("merged-spring-metadata") - val jarFile = tasks.shadowJar.flatMap { it.archiveFile } - inputs.dir(metadataDir) - inputs.file(jarFile) doLast { val baseDir = metadataDir.get().asFile - val jar = jarFile.get().asFile + val jar = archiveFile.get().asFile if (!baseDir.exists()) return@doLast val uri = URI.create("jar:${jar.toURI()}") FileSystems.newFileSystem(uri, mapOf("create" to "false")).use { fs -> - baseDir - .walkTopDown() - .filter { it.isFile } - .forEach { merged -> - val relative = merged.relativeTo(baseDir).path - val target = fs.getPath(relative) - if (target.parent != null) Files.createDirectories(target.parent) - Files.copy(merged.toPath(), target, StandardCopyOption.REPLACE_EXISTING) - } + baseDir.walkTopDown().filter { it.isFile }.forEach { merged -> + val relative = merged.relativeTo(baseDir).path + val target = fs.getPath(relative) + if (target.parent != null) Files.createDirectories(target.parent) + Files.copy(merged.toPath(), target, StandardCopyOption.REPLACE_EXISTING) + } } } } diff --git a/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts index ec31ea56952..25be0ee86ea 100644 --- a/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts @@ -128,34 +128,24 @@ val mergeSpringMetadata by // Configure the Shadow JAR (executable JAR with all dependencies) tasks.shadowJar { + dependsOn(mergeSpringMetadata) manifest { attributes["Main-Class"] = "io.sentry.samples.spring.boot.SentryDemoApplication" } archiveClassifier.set("") mergeServiceFiles() outputs.upToDateWhen { false } - finalizedBy("patchSpringMetadata") -} - -tasks.register("patchSpringMetadata") { - dependsOn(mergeSpringMetadata, tasks.shadowJar) val metadataDir = project.layout.buildDirectory.dir("merged-spring-metadata") - val jarFile = tasks.shadowJar.flatMap { it.archiveFile } - inputs.dir(metadataDir) - inputs.file(jarFile) doLast { val baseDir = metadataDir.get().asFile - val jar = jarFile.get().asFile + val jar = archiveFile.get().asFile if (!baseDir.exists()) return@doLast val uri = URI.create("jar:${jar.toURI()}") FileSystems.newFileSystem(uri, mapOf("create" to "false")).use { fs -> - baseDir - .walkTopDown() - .filter { it.isFile } - .forEach { merged -> - val relative = merged.relativeTo(baseDir).path - val target = fs.getPath(relative) - if (target.parent != null) Files.createDirectories(target.parent) - Files.copy(merged.toPath(), target, StandardCopyOption.REPLACE_EXISTING) - } + baseDir.walkTopDown().filter { it.isFile }.forEach { merged -> + val relative = merged.relativeTo(baseDir).path + val target = fs.getPath(relative) + if (target.parent != null) Files.createDirectories(target.parent) + Files.copy(merged.toPath(), target, StandardCopyOption.REPLACE_EXISTING) + } } } } diff --git a/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts index eb684c79b56..74850a0f96b 100644 --- a/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts @@ -124,34 +124,24 @@ val mergeSpringMetadata by // Configure the Shadow JAR (executable JAR with all dependencies) tasks.shadowJar { + dependsOn(mergeSpringMetadata) manifest { attributes["Main-Class"] = "io.sentry.samples.spring.boot.SentryDemoApplication" } archiveClassifier.set("") mergeServiceFiles() outputs.upToDateWhen { false } - finalizedBy("patchSpringMetadata") -} - -tasks.register("patchSpringMetadata") { - dependsOn(mergeSpringMetadata, tasks.shadowJar) val metadataDir = project.layout.buildDirectory.dir("merged-spring-metadata") - val jarFile = tasks.shadowJar.flatMap { it.archiveFile } - inputs.dir(metadataDir) - inputs.file(jarFile) doLast { val baseDir = metadataDir.get().asFile - val jar = jarFile.get().asFile + val jar = archiveFile.get().asFile if (!baseDir.exists()) return@doLast val uri = URI.create("jar:${jar.toURI()}") FileSystems.newFileSystem(uri, mapOf("create" to "false")).use { fs -> - baseDir - .walkTopDown() - .filter { it.isFile } - .forEach { merged -> - val relative = merged.relativeTo(baseDir).path - val target = fs.getPath(relative) - if (target.parent != null) Files.createDirectories(target.parent) - Files.copy(merged.toPath(), target, StandardCopyOption.REPLACE_EXISTING) - } + baseDir.walkTopDown().filter { it.isFile }.forEach { merged -> + val relative = merged.relativeTo(baseDir).path + val target = fs.getPath(relative) + if (target.parent != null) Files.createDirectories(target.parent) + Files.copy(merged.toPath(), target, StandardCopyOption.REPLACE_EXISTING) + } } } } diff --git a/sentry-samples/sentry-samples-spring-boot-webflux/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-webflux/build.gradle.kts index c95fb5897af..26f054880db 100644 --- a/sentry-samples/sentry-samples-spring-boot-webflux/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-webflux/build.gradle.kts @@ -97,34 +97,24 @@ val mergeSpringMetadata by // Configure the Shadow JAR (executable JAR with all dependencies) tasks.shadowJar { + dependsOn(mergeSpringMetadata) manifest { attributes["Main-Class"] = "io.sentry.samples.spring.boot.SentryDemoApplication" } archiveClassifier.set("") mergeServiceFiles() outputs.upToDateWhen { false } - finalizedBy("patchSpringMetadata") -} - -tasks.register("patchSpringMetadata") { - dependsOn(mergeSpringMetadata, tasks.shadowJar) val metadataDir = project.layout.buildDirectory.dir("merged-spring-metadata") - val jarFile = tasks.shadowJar.flatMap { it.archiveFile } - inputs.dir(metadataDir) - inputs.file(jarFile) doLast { val baseDir = metadataDir.get().asFile - val jar = jarFile.get().asFile + val jar = archiveFile.get().asFile if (!baseDir.exists()) return@doLast val uri = URI.create("jar:${jar.toURI()}") FileSystems.newFileSystem(uri, mapOf("create" to "false")).use { fs -> - baseDir - .walkTopDown() - .filter { it.isFile } - .forEach { merged -> - val relative = merged.relativeTo(baseDir).path - val target = fs.getPath(relative) - if (target.parent != null) Files.createDirectories(target.parent) - Files.copy(merged.toPath(), target, StandardCopyOption.REPLACE_EXISTING) - } + baseDir.walkTopDown().filter { it.isFile }.forEach { merged -> + val relative = merged.relativeTo(baseDir).path + val target = fs.getPath(relative) + if (target.parent != null) Files.createDirectories(target.parent) + Files.copy(merged.toPath(), target, StandardCopyOption.REPLACE_EXISTING) + } } } } diff --git a/sentry-samples/sentry-samples-spring-boot/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot/build.gradle.kts index e15c9dd1d14..aa0dfb6cd9d 100644 --- a/sentry-samples/sentry-samples-spring-boot/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot/build.gradle.kts @@ -126,38 +126,27 @@ val mergeSpringMetadata by // Configure the Shadow JAR (executable JAR with all dependencies) tasks.shadowJar { + dependsOn(mergeSpringMetadata) manifest { attributes["Main-Class"] = "io.sentry.samples.spring.boot.SentryDemoApplication" } archiveClassifier.set("") mergeServiceFiles() - // Mark as never up-to-date so doLast always runs to patch Spring metadata + // Never up-to-date: ensures doLast always runs to patch Spring metadata. + // Shadow 9.x's `append` transformer doesn't properly merge Spring metadata files + // because DuplicatesStrategy is enforced before transformers run. outputs.upToDateWhen { false } - finalizedBy("patchSpringMetadata") -} - -// Patch the shadow JAR with pre-merged Spring metadata after it's built. -// Shadow 9.x's `append` transformer doesn't properly merge these files because -// DuplicatesStrategy is enforced before transformers run. -tasks.register("patchSpringMetadata") { - dependsOn(mergeSpringMetadata, tasks.shadowJar) val metadataDir = project.layout.buildDirectory.dir("merged-spring-metadata") - val jarFile = tasks.shadowJar.flatMap { it.archiveFile } - inputs.dir(metadataDir) - inputs.file(jarFile) doLast { val baseDir = metadataDir.get().asFile - val jar = jarFile.get().asFile + val jar = archiveFile.get().asFile if (!baseDir.exists()) return@doLast val uri = URI.create("jar:${jar.toURI()}") FileSystems.newFileSystem(uri, mapOf("create" to "false")).use { fs -> - baseDir - .walkTopDown() - .filter { it.isFile } - .forEach { merged -> - val relative = merged.relativeTo(baseDir).path - val target = fs.getPath(relative) - if (target.parent != null) Files.createDirectories(target.parent) - Files.copy(merged.toPath(), target, StandardCopyOption.REPLACE_EXISTING) - } + baseDir.walkTopDown().filter { it.isFile }.forEach { merged -> + val relative = merged.relativeTo(baseDir).path + val target = fs.getPath(relative) + if (target.parent != null) Files.createDirectories(target.parent) + Files.copy(merged.toPath(), target, StandardCopyOption.REPLACE_EXISTING) + } } } } From 702dfd033940ce54a5a1f9215a198d2ac77085dc Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Sat, 11 Apr 2026 09:34:20 +0200 Subject: [PATCH 25/27] formatting --- .../sentry-samples-netflix-dgs/build.gradle.kts | 15 +++++++++------ .../build.gradle.kts | 15 +++++++++------ .../build.gradle.kts | 15 +++++++++------ .../build.gradle.kts | 15 +++++++++------ .../sentry-samples-spring-boot/build.gradle.kts | 15 +++++++++------ 5 files changed, 45 insertions(+), 30 deletions(-) diff --git a/sentry-samples/sentry-samples-netflix-dgs/build.gradle.kts b/sentry-samples/sentry-samples-netflix-dgs/build.gradle.kts index 48f323ed831..291ff1831a4 100644 --- a/sentry-samples/sentry-samples-netflix-dgs/build.gradle.kts +++ b/sentry-samples/sentry-samples-netflix-dgs/build.gradle.kts @@ -99,12 +99,15 @@ tasks.shadowJar { if (!baseDir.exists()) return@doLast val uri = URI.create("jar:${jar.toURI()}") FileSystems.newFileSystem(uri, mapOf("create" to "false")).use { fs -> - baseDir.walkTopDown().filter { it.isFile }.forEach { merged -> - val relative = merged.relativeTo(baseDir).path - val target = fs.getPath(relative) - if (target.parent != null) Files.createDirectories(target.parent) - Files.copy(merged.toPath(), target, StandardCopyOption.REPLACE_EXISTING) - } + baseDir + .walkTopDown() + .filter { it.isFile } + .forEach { merged -> + val relative = merged.relativeTo(baseDir).path + val target = fs.getPath(relative) + if (target.parent != null) Files.createDirectories(target.parent) + Files.copy(merged.toPath(), target, StandardCopyOption.REPLACE_EXISTING) + } } } } diff --git a/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts index 25be0ee86ea..b773e414028 100644 --- a/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts @@ -140,12 +140,15 @@ tasks.shadowJar { if (!baseDir.exists()) return@doLast val uri = URI.create("jar:${jar.toURI()}") FileSystems.newFileSystem(uri, mapOf("create" to "false")).use { fs -> - baseDir.walkTopDown().filter { it.isFile }.forEach { merged -> - val relative = merged.relativeTo(baseDir).path - val target = fs.getPath(relative) - if (target.parent != null) Files.createDirectories(target.parent) - Files.copy(merged.toPath(), target, StandardCopyOption.REPLACE_EXISTING) - } + baseDir + .walkTopDown() + .filter { it.isFile } + .forEach { merged -> + val relative = merged.relativeTo(baseDir).path + val target = fs.getPath(relative) + if (target.parent != null) Files.createDirectories(target.parent) + Files.copy(merged.toPath(), target, StandardCopyOption.REPLACE_EXISTING) + } } } } diff --git a/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts index 74850a0f96b..a8f84c62018 100644 --- a/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts @@ -136,12 +136,15 @@ tasks.shadowJar { if (!baseDir.exists()) return@doLast val uri = URI.create("jar:${jar.toURI()}") FileSystems.newFileSystem(uri, mapOf("create" to "false")).use { fs -> - baseDir.walkTopDown().filter { it.isFile }.forEach { merged -> - val relative = merged.relativeTo(baseDir).path - val target = fs.getPath(relative) - if (target.parent != null) Files.createDirectories(target.parent) - Files.copy(merged.toPath(), target, StandardCopyOption.REPLACE_EXISTING) - } + baseDir + .walkTopDown() + .filter { it.isFile } + .forEach { merged -> + val relative = merged.relativeTo(baseDir).path + val target = fs.getPath(relative) + if (target.parent != null) Files.createDirectories(target.parent) + Files.copy(merged.toPath(), target, StandardCopyOption.REPLACE_EXISTING) + } } } } diff --git a/sentry-samples/sentry-samples-spring-boot-webflux/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-webflux/build.gradle.kts index 26f054880db..3c23d3684fb 100644 --- a/sentry-samples/sentry-samples-spring-boot-webflux/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-webflux/build.gradle.kts @@ -109,12 +109,15 @@ tasks.shadowJar { if (!baseDir.exists()) return@doLast val uri = URI.create("jar:${jar.toURI()}") FileSystems.newFileSystem(uri, mapOf("create" to "false")).use { fs -> - baseDir.walkTopDown().filter { it.isFile }.forEach { merged -> - val relative = merged.relativeTo(baseDir).path - val target = fs.getPath(relative) - if (target.parent != null) Files.createDirectories(target.parent) - Files.copy(merged.toPath(), target, StandardCopyOption.REPLACE_EXISTING) - } + baseDir + .walkTopDown() + .filter { it.isFile } + .forEach { merged -> + val relative = merged.relativeTo(baseDir).path + val target = fs.getPath(relative) + if (target.parent != null) Files.createDirectories(target.parent) + Files.copy(merged.toPath(), target, StandardCopyOption.REPLACE_EXISTING) + } } } } diff --git a/sentry-samples/sentry-samples-spring-boot/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot/build.gradle.kts index aa0dfb6cd9d..84ec6b84312 100644 --- a/sentry-samples/sentry-samples-spring-boot/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot/build.gradle.kts @@ -141,12 +141,15 @@ tasks.shadowJar { if (!baseDir.exists()) return@doLast val uri = URI.create("jar:${jar.toURI()}") FileSystems.newFileSystem(uri, mapOf("create" to "false")).use { fs -> - baseDir.walkTopDown().filter { it.isFile }.forEach { merged -> - val relative = merged.relativeTo(baseDir).path - val target = fs.getPath(relative) - if (target.parent != null) Files.createDirectories(target.parent) - Files.copy(merged.toPath(), target, StandardCopyOption.REPLACE_EXISTING) - } + baseDir + .walkTopDown() + .filter { it.isFile } + .forEach { merged -> + val relative = merged.relativeTo(baseDir).path + val target = fs.getPath(relative) + if (target.parent != null) Files.createDirectories(target.parent) + Files.copy(merged.toPath(), target, StandardCopyOption.REPLACE_EXISTING) + } } } } From 821da53b69bbb50771e3e162cce46fe29019e840 Mon Sep 17 00:00:00 2001 From: Roman Zavarnitsyn Date: Sat, 11 Apr 2026 11:32:52 +0200 Subject: [PATCH 26/27] fix(spring-boot2): inline Spring metadata merge into shadowJar doLast Replace the separate mergeSpringMetadata task with inline doLast on shadowJar that resolves runtimeClasspath at execution time (not configuration time). This ensures all project dependency JARs are built before their spring.factories entries are read and merged. Verified locally: 20/21 system tests pass. Only PersonSystemTest 'create person works' fails due to @SentrySpan AOP limitation in shadow JARs. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../build.gradle.kts | 84 ++++++------------ .../build.gradle.kts | 84 ++++++------------ .../build.gradle.kts | 84 ++++++------------ .../build.gradle.kts | 84 ++++++------------ .../build.gradle.kts | 88 +++++++------------ 5 files changed, 142 insertions(+), 282 deletions(-) diff --git a/sentry-samples/sentry-samples-netflix-dgs/build.gradle.kts b/sentry-samples/sentry-samples-netflix-dgs/build.gradle.kts index 291ff1831a4..c5a9807d564 100644 --- a/sentry-samples/sentry-samples-netflix-dgs/build.gradle.kts +++ b/sentry-samples/sentry-samples-netflix-dgs/build.gradle.kts @@ -1,7 +1,6 @@ import java.net.URI import java.nio.file.FileSystems import java.nio.file.Files -import java.nio.file.StandardCopyOption import java.util.zip.ZipFile import org.jetbrains.kotlin.config.KotlinCompilerVersion import org.jetbrains.kotlin.gradle.tasks.KotlinCompile @@ -41,73 +40,46 @@ dependencies { } } -// Shadow 9.x enforces DuplicatesStrategy before transformers run, so the `append` -// transformer only sees one copy of each file. We pre-merge Spring metadata files -// from the runtime classpath and include the merged result in the shadow JAR. -val mergeSpringMetadata by - tasks.registering { - val outputDir = project.layout.buildDirectory.dir("merged-spring-metadata") - val classpathJars = configurations.runtimeClasspath.get().filter { it.name.endsWith(".jar") } - val filesToMerge = - listOf( - "META-INF/spring.factories", - "META-INF/spring.handlers", - "META-INF/spring.schemas", - "META-INF/spring-autoconfigure-metadata.properties", - "META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports", - "META-INF/spring/org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration.imports", - ) - outputs.dir(outputDir) - inputs.files(classpathJars) - doLast { - val out = outputDir.get().asFile - filesToMerge.forEach { entryPath -> +// Configure the Shadow JAR (executable JAR with all dependencies) +tasks.shadowJar { + manifest { attributes["Main-Class"] = "io.sentry.samples.netflix.dgs.NetlixDgsApplication" } + archiveClassifier.set("") + mergeServiceFiles() + + val springMetadataFiles = + listOf( + "META-INF/spring.factories", + "META-INF/spring.handlers", + "META-INF/spring.schemas", + "META-INF/spring-autoconfigure-metadata.properties", + "META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports", + "META-INF/spring/org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration.imports", + ) + + doLast { + val jar = archiveFile.get().asFile + val runtimeJars = project.configurations.getByName("runtimeClasspath").resolve().filter { it.name.endsWith(".jar") } + val uri = URI.create("jar:${jar.toURI()}") + FileSystems.newFileSystem(uri, mapOf("create" to "false")).use { fs -> + springMetadataFiles.forEach { entryPath -> val merged = StringBuilder() - classpathJars.forEach { jar -> + runtimeJars.forEach { depJar -> try { - val zip = ZipFile(jar) + val zip = ZipFile(depJar) val entry = zip.getEntry(entryPath) if (entry != null) { merged.append(zip.getInputStream(entry).bufferedReader().readText()) if (!merged.endsWith("\n")) merged.append("\n") } zip.close() - } catch (e: Exception) { - /* skip non-zip files */ - } + } catch (e: Exception) { /* skip non-zip files */ } } if (merged.isNotEmpty()) { - val outFile = File(out, entryPath) - outFile.parentFile.mkdirs() - outFile.writeText(merged.toString()) - } - } - } - } - -// Configure the Shadow JAR (executable JAR with all dependencies) -tasks.shadowJar { - dependsOn(mergeSpringMetadata) - manifest { attributes["Main-Class"] = "io.sentry.samples.netflix.dgs.NetlixDgsApplication" } - archiveClassifier.set("") - mergeServiceFiles() - outputs.upToDateWhen { false } - val metadataDir = project.layout.buildDirectory.dir("merged-spring-metadata") - doLast { - val baseDir = metadataDir.get().asFile - val jar = archiveFile.get().asFile - if (!baseDir.exists()) return@doLast - val uri = URI.create("jar:${jar.toURI()}") - FileSystems.newFileSystem(uri, mapOf("create" to "false")).use { fs -> - baseDir - .walkTopDown() - .filter { it.isFile } - .forEach { merged -> - val relative = merged.relativeTo(baseDir).path - val target = fs.getPath(relative) + val target = fs.getPath(entryPath) if (target.parent != null) Files.createDirectories(target.parent) - Files.copy(merged.toPath(), target, StandardCopyOption.REPLACE_EXISTING) + Files.write(target, merged.toString().toByteArray()) } + } } } } diff --git a/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts index b773e414028..3752f7badbb 100644 --- a/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts @@ -1,7 +1,6 @@ import java.net.URI import java.nio.file.FileSystems import java.nio.file.Files -import java.nio.file.StandardCopyOption import java.util.zip.ZipFile import org.jetbrains.kotlin.config.KotlinCompilerVersion import org.jetbrains.kotlin.gradle.tasks.KotlinCompile @@ -82,73 +81,46 @@ dependencies { testImplementation("org.apache.httpcomponents:httpclient") } -// Shadow 9.x enforces DuplicatesStrategy before transformers run, so the `append` -// transformer only sees one copy of each file. We pre-merge Spring metadata files -// from the runtime classpath and include the merged result in the shadow JAR. -val mergeSpringMetadata by - tasks.registering { - val outputDir = project.layout.buildDirectory.dir("merged-spring-metadata") - val classpathJars = configurations.runtimeClasspath.get().filter { it.name.endsWith(".jar") } - val filesToMerge = - listOf( - "META-INF/spring.factories", - "META-INF/spring.handlers", - "META-INF/spring.schemas", - "META-INF/spring-autoconfigure-metadata.properties", - "META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports", - "META-INF/spring/org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration.imports", - ) - outputs.dir(outputDir) - inputs.files(classpathJars) - doLast { - val out = outputDir.get().asFile - filesToMerge.forEach { entryPath -> +// Configure the Shadow JAR (executable JAR with all dependencies) +tasks.shadowJar { + manifest { attributes["Main-Class"] = "io.sentry.samples.spring.boot.SentryDemoApplication" } + archiveClassifier.set("") + mergeServiceFiles() + + val springMetadataFiles = + listOf( + "META-INF/spring.factories", + "META-INF/spring.handlers", + "META-INF/spring.schemas", + "META-INF/spring-autoconfigure-metadata.properties", + "META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports", + "META-INF/spring/org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration.imports", + ) + + doLast { + val jar = archiveFile.get().asFile + val runtimeJars = project.configurations.getByName("runtimeClasspath").resolve().filter { it.name.endsWith(".jar") } + val uri = URI.create("jar:${jar.toURI()}") + FileSystems.newFileSystem(uri, mapOf("create" to "false")).use { fs -> + springMetadataFiles.forEach { entryPath -> val merged = StringBuilder() - classpathJars.forEach { jar -> + runtimeJars.forEach { depJar -> try { - val zip = ZipFile(jar) + val zip = ZipFile(depJar) val entry = zip.getEntry(entryPath) if (entry != null) { merged.append(zip.getInputStream(entry).bufferedReader().readText()) if (!merged.endsWith("\n")) merged.append("\n") } zip.close() - } catch (e: Exception) { - /* skip non-zip files */ - } + } catch (e: Exception) { /* skip non-zip files */ } } if (merged.isNotEmpty()) { - val outFile = File(out, entryPath) - outFile.parentFile.mkdirs() - outFile.writeText(merged.toString()) - } - } - } - } - -// Configure the Shadow JAR (executable JAR with all dependencies) -tasks.shadowJar { - dependsOn(mergeSpringMetadata) - manifest { attributes["Main-Class"] = "io.sentry.samples.spring.boot.SentryDemoApplication" } - archiveClassifier.set("") - mergeServiceFiles() - outputs.upToDateWhen { false } - val metadataDir = project.layout.buildDirectory.dir("merged-spring-metadata") - doLast { - val baseDir = metadataDir.get().asFile - val jar = archiveFile.get().asFile - if (!baseDir.exists()) return@doLast - val uri = URI.create("jar:${jar.toURI()}") - FileSystems.newFileSystem(uri, mapOf("create" to "false")).use { fs -> - baseDir - .walkTopDown() - .filter { it.isFile } - .forEach { merged -> - val relative = merged.relativeTo(baseDir).path - val target = fs.getPath(relative) + val target = fs.getPath(entryPath) if (target.parent != null) Files.createDirectories(target.parent) - Files.copy(merged.toPath(), target, StandardCopyOption.REPLACE_EXISTING) + Files.write(target, merged.toString().toByteArray()) } + } } } } diff --git a/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts index a8f84c62018..94a35a2d28a 100644 --- a/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts @@ -1,7 +1,6 @@ import java.net.URI import java.nio.file.FileSystems import java.nio.file.Files -import java.nio.file.StandardCopyOption import java.util.zip.ZipFile import org.jetbrains.kotlin.config.KotlinCompilerVersion import org.jetbrains.kotlin.gradle.tasks.KotlinCompile @@ -78,73 +77,46 @@ dependencies { testImplementation("org.apache.httpcomponents:httpclient") } -// Shadow 9.x enforces DuplicatesStrategy before transformers run, so the `append` -// transformer only sees one copy of each file. We pre-merge Spring metadata files -// from the runtime classpath and include the merged result in the shadow JAR. -val mergeSpringMetadata by - tasks.registering { - val outputDir = project.layout.buildDirectory.dir("merged-spring-metadata") - val classpathJars = configurations.runtimeClasspath.get().filter { it.name.endsWith(".jar") } - val filesToMerge = - listOf( - "META-INF/spring.factories", - "META-INF/spring.handlers", - "META-INF/spring.schemas", - "META-INF/spring-autoconfigure-metadata.properties", - "META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports", - "META-INF/spring/org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration.imports", - ) - outputs.dir(outputDir) - inputs.files(classpathJars) - doLast { - val out = outputDir.get().asFile - filesToMerge.forEach { entryPath -> +// Configure the Shadow JAR (executable JAR with all dependencies) +tasks.shadowJar { + manifest { attributes["Main-Class"] = "io.sentry.samples.spring.boot.SentryDemoApplication" } + archiveClassifier.set("") + mergeServiceFiles() + + val springMetadataFiles = + listOf( + "META-INF/spring.factories", + "META-INF/spring.handlers", + "META-INF/spring.schemas", + "META-INF/spring-autoconfigure-metadata.properties", + "META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports", + "META-INF/spring/org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration.imports", + ) + + doLast { + val jar = archiveFile.get().asFile + val runtimeJars = project.configurations.getByName("runtimeClasspath").resolve().filter { it.name.endsWith(".jar") } + val uri = URI.create("jar:${jar.toURI()}") + FileSystems.newFileSystem(uri, mapOf("create" to "false")).use { fs -> + springMetadataFiles.forEach { entryPath -> val merged = StringBuilder() - classpathJars.forEach { jar -> + runtimeJars.forEach { depJar -> try { - val zip = ZipFile(jar) + val zip = ZipFile(depJar) val entry = zip.getEntry(entryPath) if (entry != null) { merged.append(zip.getInputStream(entry).bufferedReader().readText()) if (!merged.endsWith("\n")) merged.append("\n") } zip.close() - } catch (e: Exception) { - /* skip non-zip files */ - } + } catch (e: Exception) { /* skip non-zip files */ } } if (merged.isNotEmpty()) { - val outFile = File(out, entryPath) - outFile.parentFile.mkdirs() - outFile.writeText(merged.toString()) - } - } - } - } - -// Configure the Shadow JAR (executable JAR with all dependencies) -tasks.shadowJar { - dependsOn(mergeSpringMetadata) - manifest { attributes["Main-Class"] = "io.sentry.samples.spring.boot.SentryDemoApplication" } - archiveClassifier.set("") - mergeServiceFiles() - outputs.upToDateWhen { false } - val metadataDir = project.layout.buildDirectory.dir("merged-spring-metadata") - doLast { - val baseDir = metadataDir.get().asFile - val jar = archiveFile.get().asFile - if (!baseDir.exists()) return@doLast - val uri = URI.create("jar:${jar.toURI()}") - FileSystems.newFileSystem(uri, mapOf("create" to "false")).use { fs -> - baseDir - .walkTopDown() - .filter { it.isFile } - .forEach { merged -> - val relative = merged.relativeTo(baseDir).path - val target = fs.getPath(relative) + val target = fs.getPath(entryPath) if (target.parent != null) Files.createDirectories(target.parent) - Files.copy(merged.toPath(), target, StandardCopyOption.REPLACE_EXISTING) + Files.write(target, merged.toString().toByteArray()) } + } } } } diff --git a/sentry-samples/sentry-samples-spring-boot-webflux/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-webflux/build.gradle.kts index 3c23d3684fb..a3203c569c6 100644 --- a/sentry-samples/sentry-samples-spring-boot-webflux/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-webflux/build.gradle.kts @@ -1,7 +1,6 @@ import java.net.URI import java.nio.file.FileSystems import java.nio.file.Files -import java.nio.file.StandardCopyOption import java.util.zip.ZipFile import org.jetbrains.kotlin.config.KotlinCompilerVersion import org.jetbrains.kotlin.gradle.tasks.KotlinCompile @@ -51,73 +50,46 @@ dependencies { testImplementation("org.apache.httpcomponents:httpclient") } -// Shadow 9.x enforces DuplicatesStrategy before transformers run, so the `append` -// transformer only sees one copy of each file. We pre-merge Spring metadata files -// from the runtime classpath and include the merged result in the shadow JAR. -val mergeSpringMetadata by - tasks.registering { - val outputDir = project.layout.buildDirectory.dir("merged-spring-metadata") - val classpathJars = configurations.runtimeClasspath.get().filter { it.name.endsWith(".jar") } - val filesToMerge = - listOf( - "META-INF/spring.factories", - "META-INF/spring.handlers", - "META-INF/spring.schemas", - "META-INF/spring-autoconfigure-metadata.properties", - "META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports", - "META-INF/spring/org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration.imports", - ) - outputs.dir(outputDir) - inputs.files(classpathJars) - doLast { - val out = outputDir.get().asFile - filesToMerge.forEach { entryPath -> +// Configure the Shadow JAR (executable JAR with all dependencies) +tasks.shadowJar { + manifest { attributes["Main-Class"] = "io.sentry.samples.spring.boot.SentryDemoApplication" } + archiveClassifier.set("") + mergeServiceFiles() + + val springMetadataFiles = + listOf( + "META-INF/spring.factories", + "META-INF/spring.handlers", + "META-INF/spring.schemas", + "META-INF/spring-autoconfigure-metadata.properties", + "META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports", + "META-INF/spring/org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration.imports", + ) + + doLast { + val jar = archiveFile.get().asFile + val runtimeJars = project.configurations.getByName("runtimeClasspath").resolve().filter { it.name.endsWith(".jar") } + val uri = URI.create("jar:${jar.toURI()}") + FileSystems.newFileSystem(uri, mapOf("create" to "false")).use { fs -> + springMetadataFiles.forEach { entryPath -> val merged = StringBuilder() - classpathJars.forEach { jar -> + runtimeJars.forEach { depJar -> try { - val zip = ZipFile(jar) + val zip = ZipFile(depJar) val entry = zip.getEntry(entryPath) if (entry != null) { merged.append(zip.getInputStream(entry).bufferedReader().readText()) if (!merged.endsWith("\n")) merged.append("\n") } zip.close() - } catch (e: Exception) { - /* skip non-zip files */ - } + } catch (e: Exception) { /* skip non-zip files */ } } if (merged.isNotEmpty()) { - val outFile = File(out, entryPath) - outFile.parentFile.mkdirs() - outFile.writeText(merged.toString()) - } - } - } - } - -// Configure the Shadow JAR (executable JAR with all dependencies) -tasks.shadowJar { - dependsOn(mergeSpringMetadata) - manifest { attributes["Main-Class"] = "io.sentry.samples.spring.boot.SentryDemoApplication" } - archiveClassifier.set("") - mergeServiceFiles() - outputs.upToDateWhen { false } - val metadataDir = project.layout.buildDirectory.dir("merged-spring-metadata") - doLast { - val baseDir = metadataDir.get().asFile - val jar = archiveFile.get().asFile - if (!baseDir.exists()) return@doLast - val uri = URI.create("jar:${jar.toURI()}") - FileSystems.newFileSystem(uri, mapOf("create" to "false")).use { fs -> - baseDir - .walkTopDown() - .filter { it.isFile } - .forEach { merged -> - val relative = merged.relativeTo(baseDir).path - val target = fs.getPath(relative) + val target = fs.getPath(entryPath) if (target.parent != null) Files.createDirectories(target.parent) - Files.copy(merged.toPath(), target, StandardCopyOption.REPLACE_EXISTING) + Files.write(target, merged.toString().toByteArray()) } + } } } } diff --git a/sentry-samples/sentry-samples-spring-boot/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot/build.gradle.kts index 84ec6b84312..30d3003aa73 100644 --- a/sentry-samples/sentry-samples-spring-boot/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot/build.gradle.kts @@ -1,7 +1,6 @@ import java.net.URI import java.nio.file.FileSystems import java.nio.file.Files -import java.nio.file.StandardCopyOption import java.util.zip.ZipFile import org.jetbrains.kotlin.config.KotlinCompilerVersion import org.jetbrains.kotlin.gradle.tasks.KotlinCompile @@ -78,33 +77,35 @@ dependencies { testImplementation("org.apache.httpcomponents:httpclient") } -// Shadow 9.x enforces DuplicatesStrategy before transformers run, so the `append` -// transformer only sees one copy of each file. We pre-merge Spring metadata files -// from the runtime classpath and include the merged result in the shadow JAR. -val mergeSpringMetadata by - tasks.registering { - val outputDir = project.layout.buildDirectory.dir("merged-spring-metadata") - val classpathJars = configurations.runtimeClasspath.get().filter { it.name.endsWith(".jar") } - val filesToMerge = - listOf( - "META-INF/spring.factories", - "META-INF/spring.handlers", - "META-INF/spring.schemas", - "META-INF/spring-autoconfigure-metadata.properties", - "META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports", - "META-INF/spring/org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration.imports", - ) - - outputs.dir(outputDir) - inputs.files(classpathJars) - - doLast { - val out = outputDir.get().asFile - filesToMerge.forEach { entryPath -> +// Configure the Shadow JAR (executable JAR with all dependencies) +tasks.shadowJar { + manifest { attributes["Main-Class"] = "io.sentry.samples.spring.boot.SentryDemoApplication" } + archiveClassifier.set("") + mergeServiceFiles() + + // Shadow 9.x enforces DuplicatesStrategy before transformers run, so `append` + // only sees one copy of each file. We merge Spring metadata from the runtime + // classpath and patch the built JAR in doLast. + val springMetadataFiles = + listOf( + "META-INF/spring.factories", + "META-INF/spring.handlers", + "META-INF/spring.schemas", + "META-INF/spring-autoconfigure-metadata.properties", + "META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports", + "META-INF/spring/org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration.imports", + ) + + doLast { + val jar = archiveFile.get().asFile + val runtimeJars = project.configurations.getByName("runtimeClasspath").resolve().filter { it.name.endsWith(".jar") } + val uri = URI.create("jar:${jar.toURI()}") + FileSystems.newFileSystem(uri, mapOf("create" to "false")).use { fs -> + springMetadataFiles.forEach { entryPath -> val merged = StringBuilder() - classpathJars.forEach { jar -> + runtimeJars.forEach { depJar -> try { - val zip = ZipFile(jar) + val zip = ZipFile(depJar) val entry = zip.getEntry(entryPath) if (entry != null) { merged.append(zip.getInputStream(entry).bufferedReader().readText()) @@ -116,40 +117,11 @@ val mergeSpringMetadata by } } if (merged.isNotEmpty()) { - val outFile = File(out, entryPath) - outFile.parentFile.mkdirs() - outFile.writeText(merged.toString()) - } - } - } - } - -// Configure the Shadow JAR (executable JAR with all dependencies) -tasks.shadowJar { - dependsOn(mergeSpringMetadata) - manifest { attributes["Main-Class"] = "io.sentry.samples.spring.boot.SentryDemoApplication" } - archiveClassifier.set("") - mergeServiceFiles() - // Never up-to-date: ensures doLast always runs to patch Spring metadata. - // Shadow 9.x's `append` transformer doesn't properly merge Spring metadata files - // because DuplicatesStrategy is enforced before transformers run. - outputs.upToDateWhen { false } - val metadataDir = project.layout.buildDirectory.dir("merged-spring-metadata") - doLast { - val baseDir = metadataDir.get().asFile - val jar = archiveFile.get().asFile - if (!baseDir.exists()) return@doLast - val uri = URI.create("jar:${jar.toURI()}") - FileSystems.newFileSystem(uri, mapOf("create" to "false")).use { fs -> - baseDir - .walkTopDown() - .filter { it.isFile } - .forEach { merged -> - val relative = merged.relativeTo(baseDir).path - val target = fs.getPath(relative) + val target = fs.getPath(entryPath) if (target.parent != null) Files.createDirectories(target.parent) - Files.copy(merged.toPath(), target, StandardCopyOption.REPLACE_EXISTING) + Files.write(target, merged.toString().toByteArray()) } + } } } } From b8ec4e1b44ccfa2e8b3f89ab629256c3b2271296 Mon Sep 17 00:00:00 2001 From: Sentry Github Bot Date: Sat, 11 Apr 2026 09:37:14 +0000 Subject: [PATCH 27/27] Format code --- .../sentry-samples-netflix-dgs/build.gradle.kts | 9 +++++++-- .../build.gradle.kts | 9 +++++++-- .../build.gradle.kts | 9 +++++++-- .../sentry-samples-spring-boot-webflux/build.gradle.kts | 9 +++++++-- .../sentry-samples-spring-boot/build.gradle.kts | 5 ++++- 5 files changed, 32 insertions(+), 9 deletions(-) diff --git a/sentry-samples/sentry-samples-netflix-dgs/build.gradle.kts b/sentry-samples/sentry-samples-netflix-dgs/build.gradle.kts index c5a9807d564..964afec9a4b 100644 --- a/sentry-samples/sentry-samples-netflix-dgs/build.gradle.kts +++ b/sentry-samples/sentry-samples-netflix-dgs/build.gradle.kts @@ -58,7 +58,10 @@ tasks.shadowJar { doLast { val jar = archiveFile.get().asFile - val runtimeJars = project.configurations.getByName("runtimeClasspath").resolve().filter { it.name.endsWith(".jar") } + val runtimeJars = + project.configurations.getByName("runtimeClasspath").resolve().filter { + it.name.endsWith(".jar") + } val uri = URI.create("jar:${jar.toURI()}") FileSystems.newFileSystem(uri, mapOf("create" to "false")).use { fs -> springMetadataFiles.forEach { entryPath -> @@ -72,7 +75,9 @@ tasks.shadowJar { if (!merged.endsWith("\n")) merged.append("\n") } zip.close() - } catch (e: Exception) { /* skip non-zip files */ } + } catch (e: Exception) { + /* skip non-zip files */ + } } if (merged.isNotEmpty()) { val target = fs.getPath(entryPath) diff --git a/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts index 3752f7badbb..e9dc99d73ba 100644 --- a/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts @@ -99,7 +99,10 @@ tasks.shadowJar { doLast { val jar = archiveFile.get().asFile - val runtimeJars = project.configurations.getByName("runtimeClasspath").resolve().filter { it.name.endsWith(".jar") } + val runtimeJars = + project.configurations.getByName("runtimeClasspath").resolve().filter { + it.name.endsWith(".jar") + } val uri = URI.create("jar:${jar.toURI()}") FileSystems.newFileSystem(uri, mapOf("create" to "false")).use { fs -> springMetadataFiles.forEach { entryPath -> @@ -113,7 +116,9 @@ tasks.shadowJar { if (!merged.endsWith("\n")) merged.append("\n") } zip.close() - } catch (e: Exception) { /* skip non-zip files */ } + } catch (e: Exception) { + /* skip non-zip files */ + } } if (merged.isNotEmpty()) { val target = fs.getPath(entryPath) diff --git a/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts index 94a35a2d28a..8b0cc557a5f 100644 --- a/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts @@ -95,7 +95,10 @@ tasks.shadowJar { doLast { val jar = archiveFile.get().asFile - val runtimeJars = project.configurations.getByName("runtimeClasspath").resolve().filter { it.name.endsWith(".jar") } + val runtimeJars = + project.configurations.getByName("runtimeClasspath").resolve().filter { + it.name.endsWith(".jar") + } val uri = URI.create("jar:${jar.toURI()}") FileSystems.newFileSystem(uri, mapOf("create" to "false")).use { fs -> springMetadataFiles.forEach { entryPath -> @@ -109,7 +112,9 @@ tasks.shadowJar { if (!merged.endsWith("\n")) merged.append("\n") } zip.close() - } catch (e: Exception) { /* skip non-zip files */ } + } catch (e: Exception) { + /* skip non-zip files */ + } } if (merged.isNotEmpty()) { val target = fs.getPath(entryPath) diff --git a/sentry-samples/sentry-samples-spring-boot-webflux/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-webflux/build.gradle.kts index a3203c569c6..bf58b2b7d2d 100644 --- a/sentry-samples/sentry-samples-spring-boot-webflux/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-webflux/build.gradle.kts @@ -68,7 +68,10 @@ tasks.shadowJar { doLast { val jar = archiveFile.get().asFile - val runtimeJars = project.configurations.getByName("runtimeClasspath").resolve().filter { it.name.endsWith(".jar") } + val runtimeJars = + project.configurations.getByName("runtimeClasspath").resolve().filter { + it.name.endsWith(".jar") + } val uri = URI.create("jar:${jar.toURI()}") FileSystems.newFileSystem(uri, mapOf("create" to "false")).use { fs -> springMetadataFiles.forEach { entryPath -> @@ -82,7 +85,9 @@ tasks.shadowJar { if (!merged.endsWith("\n")) merged.append("\n") } zip.close() - } catch (e: Exception) { /* skip non-zip files */ } + } catch (e: Exception) { + /* skip non-zip files */ + } } if (merged.isNotEmpty()) { val target = fs.getPath(entryPath) diff --git a/sentry-samples/sentry-samples-spring-boot/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot/build.gradle.kts index 30d3003aa73..2340602f1eb 100644 --- a/sentry-samples/sentry-samples-spring-boot/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot/build.gradle.kts @@ -98,7 +98,10 @@ tasks.shadowJar { doLast { val jar = archiveFile.get().asFile - val runtimeJars = project.configurations.getByName("runtimeClasspath").resolve().filter { it.name.endsWith(".jar") } + val runtimeJars = + project.configurations.getByName("runtimeClasspath").resolve().filter { + it.name.endsWith(".jar") + } val uri = URI.create("jar:${jar.toURI()}") FileSystems.newFileSystem(uri, mapOf("create" to "false")).use { fs -> springMetadataFiles.forEach { entryPath ->