From 700807c4c2ea69e93f87aab2cbd3b14eb779f90a Mon Sep 17 00:00:00 2001 From: Brice Dutheil Date: Tue, 6 Oct 2026 13:59:46 +0200 Subject: [PATCH 1/5] fix: make muzzle reports configuration-cache compatible --- .../gradle/plugin/muzzle/MuzzlePlugin.kt | 2 +- .../muzzle/tasks/AbstractMuzzleReportTask.kt | 17 ++-- .../muzzle/tasks/MuzzleGenerateReportTask.kt | 2 +- .../muzzle/tasks/MuzzleMergeReportsTask.kt | 51 +++++++---- .../plugin/muzzle/MuzzlePluginTestFixture.kt | 9 ++ .../MuzzleReportConfigurationCacheTest.kt | 91 +++++++++++++++++++ 6 files changed, 146 insertions(+), 26 deletions(-) create mode 100644 buildSrc/src/test/kotlin/datadog/gradle/plugin/muzzle/MuzzleReportConfigurationCacheTest.kt diff --git a/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/MuzzlePlugin.kt b/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/MuzzlePlugin.kt index 0d31c0d6f71..601f6ed2e1e 100644 --- a/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/MuzzlePlugin.kt +++ b/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/MuzzlePlugin.kt @@ -69,7 +69,7 @@ class MuzzlePlugin : Plugin { // compileMuzzle compiles all projects required to run muzzle validation. // Not adding group and description to keep this task from showing in `gradle tasks`. val compileMuzzle = project.tasks.register("compileMuzzle") { - inputs.files(project.providers.provider { project.allMainSourceSet }) + inputs.files(project.allMainSourceSet.map { it.output }) dependsOn(bootstrapProject.tasks.named("compileJava")) dependsOn(bootstrapProject.tasks.named("compileMain_java11Java")) dependsOn(toolingProject.tasks.named("compileJava")) diff --git a/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/tasks/AbstractMuzzleReportTask.kt b/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/tasks/AbstractMuzzleReportTask.kt index 71001aa9747..65ba025e11f 100644 --- a/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/tasks/AbstractMuzzleReportTask.kt +++ b/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/tasks/AbstractMuzzleReportTask.kt @@ -15,22 +15,25 @@ abstract class AbstractMuzzleReportTask : AbstractMuzzleTask() { .file("$MUZZLE_DEPS_RESULTS/${project.pathSlug}.csv") internal fun dumpVersionsToCsv(versions: SortedMap) { - with(project.file(versionsFile)) { - parentFile.mkdirs() - writeText("instrumentation,jarGroupId,jarArtifactId,lowestVersion,highestVersion\n") + val file = versionsFile.get().asFile + file.parentFile.mkdirs() + + file.bufferedWriter().use { writer -> + writer.append("instrumentation,jarGroupId,jarArtifactId,lowestVersion,highestVersion\n") versions.values.forEach { - appendText( + writer.append( listOf( it.instrumentation, it.group, it.module, it.lowVersion.toString(), it.highVersion.toString() - ).joinToString(",") + "\n" - ) + ).joinToString(",") + ).append('\n') } - project.logger.info("Wrote muzzle versions report to\n $this") } + + logger.info("Wrote muzzle versions report to\n $file") } companion object { diff --git a/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/tasks/MuzzleGenerateReportTask.kt b/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/tasks/MuzzleGenerateReportTask.kt index f0aa1d381ae..6448eec5dc2 100644 --- a/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/tasks/MuzzleGenerateReportTask.kt +++ b/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/tasks/MuzzleGenerateReportTask.kt @@ -23,7 +23,7 @@ abstract class MuzzleGenerateReportTask : AbstractMuzzleReportTask() { val map = TreeMap() val versionScheme = GenericVersionScheme() versionReports.forEach { - project.logger.info("Processing muzzle report: $it") + logger.info("Processing muzzle report: $it") it.useLines { lines -> lines.forEachIndexed { idx, line -> if (idx == 0) return@forEachIndexed // skip header diff --git a/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/tasks/MuzzleMergeReportsTask.kt b/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/tasks/MuzzleMergeReportsTask.kt index d443caeae08..1043f0d683c 100644 --- a/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/tasks/MuzzleMergeReportsTask.kt +++ b/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/tasks/MuzzleMergeReportsTask.kt @@ -1,5 +1,6 @@ package datadog.gradle.plugin.muzzle.tasks +import datadog.gradle.plugin.muzzle.MuzzleDirective import datadog.gradle.plugin.muzzle.MuzzleExtension import datadog.gradle.plugin.muzzle.MuzzleMavenRepoUtils import datadog.gradle.plugin.muzzle.MuzzleMavenRepoUtils.highest @@ -9,6 +10,10 @@ import datadog.gradle.plugin.muzzle.TestedArtifact import datadog.gradle.plugin.muzzle.mainSourceSet import org.eclipse.aether.RepositorySystem import org.eclipse.aether.RepositorySystemSession +import org.gradle.api.file.ConfigurableFileCollection +import org.gradle.api.provider.ListProperty +import org.gradle.api.tasks.Classpath +import org.gradle.api.tasks.Input import org.gradle.api.tasks.TaskAction import org.gradle.kotlin.dsl.getByType import java.net.URL @@ -17,8 +22,23 @@ import java.util.TreeMap import java.util.function.BiFunction abstract class MuzzleMergeReportsTask : AbstractMuzzleReportTask() { + @get:Input + abstract val reportDirectives: ListProperty + + @get:Classpath + abstract val instrumentationClasspath: ConfigurableFileCollection + init { description = "Print instrumentation version report" + + val extension = project.extensions.getByType() + val runtimeClasspath = project.mainSourceSet.runtimeClasspath + val directives = project.providers.provider { + extension.directives.filter { !it.isCoreJdk && !it.skipFromReport } + } + + reportDirectives.set(directives) + instrumentationClasspath.from(directives.map { if (it.isEmpty()) emptyList() else runtimeClasspath }) } @TaskAction @@ -26,24 +46,21 @@ abstract class MuzzleMergeReportsTask : AbstractMuzzleReportTask() { val system: RepositorySystem = MuzzleMavenRepoUtils.newRepositorySystem() val session: RepositorySystemSession = MuzzleMavenRepoUtils.newRepositorySystemSession(system) val versions = TreeMap() - project.extensions.getByType().directives - .filter { !it.isCoreJdk && !it.skipFromReport } - .forEach { directive -> - val range = MuzzleMavenRepoUtils.resolveVersionRange(directive, system, session) - val cp = project.files(project.mainSourceSet.runtimeClasspath).map { it.toURI().toURL() }.toTypedArray() - val cl = URLClassLoader(cp, null) - val partials = resolveInstrumentationAndJarVersions(directive, cl, range.lowestVersion, range.highestVersion) - - partials.forEach { (key, value) -> - versions.merge(key, value, BiFunction { x, y -> - TestedArtifact( - x.instrumentation, x.group, x.module, - lowest(x.lowVersion, y.lowVersion), - highest(x.highVersion, y.highVersion) - ) - }) - } + reportDirectives.get().forEach { directive -> + val range = MuzzleMavenRepoUtils.resolveVersionRange(directive, system, session) + val cp = instrumentationClasspath.map { it.toURI().toURL() }.toTypedArray() + val cl = URLClassLoader(cp, null) + val partials = resolveInstrumentationAndJarVersions(directive, cl, range.lowestVersion, range.highestVersion) + partials.forEach { (key, value) -> + versions.merge(key, value, BiFunction { x, y -> + TestedArtifact( + x.instrumentation, x.group, x.module, + lowest(x.lowVersion, y.lowVersion), + highest(x.highVersion, y.highVersion) + ) + }) } + } dumpVersionsToCsv(versions) } } diff --git a/buildSrc/src/test/kotlin/datadog/gradle/plugin/muzzle/MuzzlePluginTestFixture.kt b/buildSrc/src/test/kotlin/datadog/gradle/plugin/muzzle/MuzzlePluginTestFixture.kt index ae286c16fa2..2ba95298c73 100644 --- a/buildSrc/src/test/kotlin/datadog/gradle/plugin/muzzle/MuzzlePluginTestFixture.kt +++ b/buildSrc/src/test/kotlin/datadog/gradle/plugin/muzzle/MuzzlePluginTestFixture.kt @@ -61,9 +61,18 @@ open class MuzzlePluginTestFixture : GradleFixture() { """ package datadog.trace.agent.tooling.muzzle; + import static java.util.Collections.singleton; + + import java.util.Set; + public final class MuzzleVersionScanPlugin { private MuzzleVersionScanPlugin() {} + public static Set listInstrumentationNames( + ClassLoader instrumentationClassLoader, String muzzleDirective) { + return singleton("test-instrumentation"); + } + public static void assertInstrumentationMuzzled( ClassLoader instrumentationClassLoader, ClassLoader testApplicationClassLoader, diff --git a/buildSrc/src/test/kotlin/datadog/gradle/plugin/muzzle/MuzzleReportConfigurationCacheTest.kt b/buildSrc/src/test/kotlin/datadog/gradle/plugin/muzzle/MuzzleReportConfigurationCacheTest.kt new file mode 100644 index 00000000000..e11a9087331 --- /dev/null +++ b/buildSrc/src/test/kotlin/datadog/gradle/plugin/muzzle/MuzzleReportConfigurationCacheTest.kt @@ -0,0 +1,91 @@ +package datadog.gradle.plugin.muzzle + +import org.assertj.core.api.Assertions.assertThat +import org.gradle.testkit.runner.TaskOutcome.SUCCESS +import org.junit.jupiter.api.Test + +class MuzzleReportConfigurationCacheTest : MuzzlePluginTestFixture() { + @Test + fun `dependency report executes with configuration cache reuse`() { + val mavenRepo = createMavenRepoFixture() + mavenRepo.publishVersions("com.example", "demo", listOf("1.0.0")) + + writeProject( + """ + plugins { + id("java") + id("dd-trace-java.muzzle") + } + + dependencies { + runtimeOnly(project(":dd-java-agent:agent-tooling")) + } + + muzzle { + pass { + group = "com.example" + module = "demo" + versions = "1.0.0" + } + } + """ + ) + writeNoopScanPlugin() + writeRootProject("""layout.buildDirectory.set(layout.projectDirectory.dir("relocated/build"))""") + + val args = arrayOf( + ":dd-java-agent:instrumentation:demo:mergeMuzzleReports", + "--configuration-cache", + "--configuration-cache-problems=fail", + "--rerun-tasks", + "--stacktrace" + ) + val env = mapOf("MAVEN_REPOSITORY_PROXY" to mavenRepo.repoUrl) + + val first = run(*args, env = env) + assertThat(first.task(args[0])?.outcome).describedAs(first.output).isEqualTo(SUCCESS) + assertThat(first.output).contains("Configuration cache entry stored") + + val reused = run(*args, env = env) + assertThat(reused.task(args[0])?.outcome).describedAs(reused.output).isEqualTo(SUCCESS) + assertThat(reused.output).contains("Reusing configuration cache") + + assertThat(file("relocated/build/muzzle-deps-results/dd-java-agent_instrumentation_demo.csv").readText()) + .isEqualTo( + "instrumentation,jarGroupId,jarArtifactId,lowestVersion,highestVersion\n" + + "test-instrumentation,com.example,demo,1.0.0,1.0.0\n" + ) + } + + @Test + fun `legacy generation keeps compilation prerequisites and reuses configuration cache`() { + writeProject( + """ + plugins { + id("java") + id("dd-trace-java.muzzle") + } + + sourceSets.create("main_java11") + """ + ) + + val args = arrayOf( + ":dd-java-agent:instrumentation:demo:generateMuzzleReport", + "--configuration-cache", + "--configuration-cache-problems=fail", + "--rerun-tasks", + "--stacktrace" + ) + + val first = run(*args) + assertThat(first.task(args[0])?.outcome).describedAs(first.output).isEqualTo(SUCCESS) + assertThat(first.task(":dd-java-agent:agent-bootstrap:compileMain_java11Java")).isNotNull() + assertThat(first.task(":dd-java-agent:instrumentation:demo:compileMain_java11Java")).isNotNull() + assertThat(first.output).contains("Configuration cache entry stored") + + val reused = run(*args) + assertThat(reused.task(args[0])?.outcome).describedAs(reused.output).isEqualTo(SUCCESS) + assertThat(reused.output).contains("Reusing configuration cache") + } +} From 0578568f35feb9da86b42cc2386c163e246b0c46 Mon Sep 17 00:00:00 2001 From: Brice Dutheil Date: Tue, 6 Oct 2026 15:28:08 +0200 Subject: [PATCH 2/5] fix: restore muzzle report task roles --- .../muzzle/tasks/MuzzleGenerateReportTask.kt | 82 +++++++++++-------- .../muzzle/tasks/MuzzleMergeReportsTask.kt | 82 ++++++++----------- .../MuzzleReportConfigurationCacheTest.kt | 61 +++++++++++++- 3 files changed, 142 insertions(+), 83 deletions(-) diff --git a/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/tasks/MuzzleGenerateReportTask.kt b/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/tasks/MuzzleGenerateReportTask.kt index 6448eec5dc2..97540db4327 100644 --- a/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/tasks/MuzzleGenerateReportTask.kt +++ b/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/tasks/MuzzleGenerateReportTask.kt @@ -1,52 +1,66 @@ package datadog.gradle.plugin.muzzle.tasks +import datadog.gradle.plugin.muzzle.MuzzleDirective +import datadog.gradle.plugin.muzzle.MuzzleExtension import datadog.gradle.plugin.muzzle.MuzzleMavenRepoUtils +import datadog.gradle.plugin.muzzle.MuzzleMavenRepoUtils.highest +import datadog.gradle.plugin.muzzle.MuzzleMavenRepoUtils.lowest +import datadog.gradle.plugin.muzzle.MuzzleMavenRepoUtils.resolveInstrumentationAndJarVersions import datadog.gradle.plugin.muzzle.TestedArtifact -import org.eclipse.aether.util.version.GenericVersionScheme +import datadog.gradle.plugin.muzzle.mainSourceSet +import org.eclipse.aether.RepositorySystem +import org.eclipse.aether.RepositorySystemSession +import org.gradle.api.file.ConfigurableFileCollection +import org.gradle.api.provider.ListProperty +import org.gradle.api.tasks.Classpath +import org.gradle.api.tasks.Input import org.gradle.api.tasks.TaskAction +import org.gradle.kotlin.dsl.getByType +import java.net.URL +import java.net.URLClassLoader import java.util.TreeMap +import java.util.function.BiFunction abstract class MuzzleGenerateReportTask : AbstractMuzzleReportTask() { + @get:Input + abstract val reportDirectives: ListProperty + + @get:Classpath + abstract val instrumentationClasspath: ConfigurableFileCollection + init { - description = "Print instrumentation version report" - } + description = "Generate this instrumentation's dependency version report" + + val extension = project.extensions.getByType() + val runtimeClasspath = project.mainSourceSet.runtimeClasspath + val directives = project.providers.provider { + extension.directives.filter { !it.isCoreJdk && !it.skipFromReport } + } - private val versionReports = project.fileTree(project.rootProject.layout.buildDirectory.dir(MUZZLE_DEPS_RESULTS)) { - include("*.csv") + reportDirectives.set(directives) + instrumentationClasspath.from(directives.map { if (it.isEmpty()) emptyList() else runtimeClasspath }) } - /** - * Merges all muzzle report CSVs in the build directory into a single map and writes the merged results to a CSV. - */ @TaskAction - fun mergeReports() { - val map = TreeMap() - val versionScheme = GenericVersionScheme() - versionReports.forEach { - logger.info("Processing muzzle report: $it") - it.useLines { lines -> - lines.forEachIndexed { idx, line -> - if (idx == 0) return@forEachIndexed // skip header - val split = line.split(",") - val parsed = TestedArtifact( - split[0], - split[1], - split[2], - versionScheme.parseVersion(split[3]), - versionScheme.parseVersion(split[4]) + fun dumpVersionRanges() { + val system: RepositorySystem = MuzzleMavenRepoUtils.newRepositorySystem() + val session: RepositorySystemSession = MuzzleMavenRepoUtils.newRepositorySystemSession(system) + val versions = TreeMap() + reportDirectives.get().forEach { directive -> + val range = MuzzleMavenRepoUtils.resolveVersionRange(directive, system, session) + val cp = instrumentationClasspath.map { it.toURI().toURL() }.toTypedArray() + val cl = URLClassLoader(cp, null) + val partials = resolveInstrumentationAndJarVersions(directive, cl, range.lowestVersion, range.highestVersion) + partials.forEach { (key, value) -> + versions.merge(key, value, BiFunction { x, y -> + TestedArtifact( + x.instrumentation, x.group, x.module, + lowest(x.lowVersion, y.lowVersion), + highest(x.highVersion, y.highVersion) ) - map.merge(parsed.key(), parsed) { x, y -> - TestedArtifact( - x.instrumentation, - x.group, - x.module, - MuzzleMavenRepoUtils.lowest(x.lowVersion, y.lowVersion), - MuzzleMavenRepoUtils.highest(x.highVersion, y.highVersion) - ) - } - } + }) } } - dumpVersionsToCsv(map) + dumpVersionsToCsv(versions) } } diff --git a/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/tasks/MuzzleMergeReportsTask.kt b/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/tasks/MuzzleMergeReportsTask.kt index 1043f0d683c..a5b673ad8c1 100644 --- a/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/tasks/MuzzleMergeReportsTask.kt +++ b/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/tasks/MuzzleMergeReportsTask.kt @@ -1,66 +1,52 @@ package datadog.gradle.plugin.muzzle.tasks -import datadog.gradle.plugin.muzzle.MuzzleDirective -import datadog.gradle.plugin.muzzle.MuzzleExtension import datadog.gradle.plugin.muzzle.MuzzleMavenRepoUtils -import datadog.gradle.plugin.muzzle.MuzzleMavenRepoUtils.highest -import datadog.gradle.plugin.muzzle.MuzzleMavenRepoUtils.lowest -import datadog.gradle.plugin.muzzle.MuzzleMavenRepoUtils.resolveInstrumentationAndJarVersions import datadog.gradle.plugin.muzzle.TestedArtifact -import datadog.gradle.plugin.muzzle.mainSourceSet -import org.eclipse.aether.RepositorySystem -import org.eclipse.aether.RepositorySystemSession -import org.gradle.api.file.ConfigurableFileCollection -import org.gradle.api.provider.ListProperty -import org.gradle.api.tasks.Classpath -import org.gradle.api.tasks.Input +import org.eclipse.aether.util.version.GenericVersionScheme import org.gradle.api.tasks.TaskAction -import org.gradle.kotlin.dsl.getByType -import java.net.URL -import java.net.URLClassLoader import java.util.TreeMap -import java.util.function.BiFunction abstract class MuzzleMergeReportsTask : AbstractMuzzleReportTask() { - @get:Input - abstract val reportDirectives: ListProperty - - @get:Classpath - abstract val instrumentationClasspath: ConfigurableFileCollection - init { - description = "Print instrumentation version report" - - val extension = project.extensions.getByType() - val runtimeClasspath = project.mainSourceSet.runtimeClasspath - val directives = project.providers.provider { - extension.directives.filter { !it.isCoreJdk && !it.skipFromReport } - } + description = "Merge generated dependency version reports into one CSV" + } - reportDirectives.set(directives) - instrumentationClasspath.from(directives.map { if (it.isEmpty()) emptyList() else runtimeClasspath }) + private val versionReports = project.fileTree(project.rootProject.layout.buildDirectory.dir(MUZZLE_DEPS_RESULTS)) { + include("*.csv") } + /** + * Merges all muzzle report CSVs in the build directory into a single map and writes the merged results to a CSV. + */ @TaskAction - fun dumpVersionRanges() { - val system: RepositorySystem = MuzzleMavenRepoUtils.newRepositorySystem() - val session: RepositorySystemSession = MuzzleMavenRepoUtils.newRepositorySystemSession(system) - val versions = TreeMap() - reportDirectives.get().forEach { directive -> - val range = MuzzleMavenRepoUtils.resolveVersionRange(directive, system, session) - val cp = instrumentationClasspath.map { it.toURI().toURL() }.toTypedArray() - val cl = URLClassLoader(cp, null) - val partials = resolveInstrumentationAndJarVersions(directive, cl, range.lowestVersion, range.highestVersion) - partials.forEach { (key, value) -> - versions.merge(key, value, BiFunction { x, y -> - TestedArtifact( - x.instrumentation, x.group, x.module, - lowest(x.lowVersion, y.lowVersion), - highest(x.highVersion, y.highVersion) + fun mergeReports() { + val map = TreeMap() + val versionScheme = GenericVersionScheme() + versionReports.forEach { + logger.info("Processing muzzle report: $it") + it.useLines { lines -> + lines.forEachIndexed { idx, line -> + if (idx == 0) return@forEachIndexed // skip header + val split = line.split(",") + val parsed = TestedArtifact( + split[0], + split[1], + split[2], + versionScheme.parseVersion(split[3]), + versionScheme.parseVersion(split[4]) ) - }) + map.merge(parsed.key(), parsed) { x, y -> + TestedArtifact( + x.instrumentation, + x.group, + x.module, + MuzzleMavenRepoUtils.lowest(x.lowVersion, y.lowVersion), + MuzzleMavenRepoUtils.highest(x.highVersion, y.highVersion) + ) + } + } } } - dumpVersionsToCsv(versions) + dumpVersionsToCsv(map) } } diff --git a/buildSrc/src/test/kotlin/datadog/gradle/plugin/muzzle/MuzzleReportConfigurationCacheTest.kt b/buildSrc/src/test/kotlin/datadog/gradle/plugin/muzzle/MuzzleReportConfigurationCacheTest.kt index e11a9087331..24dc046fd6e 100644 --- a/buildSrc/src/test/kotlin/datadog/gradle/plugin/muzzle/MuzzleReportConfigurationCacheTest.kt +++ b/buildSrc/src/test/kotlin/datadog/gradle/plugin/muzzle/MuzzleReportConfigurationCacheTest.kt @@ -34,7 +34,7 @@ class MuzzleReportConfigurationCacheTest : MuzzlePluginTestFixture() { writeRootProject("""layout.buildDirectory.set(layout.projectDirectory.dir("relocated/build"))""") val args = arrayOf( - ":dd-java-agent:instrumentation:demo:mergeMuzzleReports", + ":dd-java-agent:instrumentation:demo:generateMuzzleReport", "--configuration-cache", "--configuration-cache-problems=fail", "--rerun-tasks", @@ -57,6 +57,65 @@ class MuzzleReportConfigurationCacheTest : MuzzlePluginTestFixture() { ) } + @Test + fun `merge combines generated version ranges with configuration cache reuse`() { + writeProject( + """ + plugins { + id("java") + id("dd-trace-java.muzzle") + } + """ + ) + addSubproject("dd-java-agent:instrumentation", + """ + plugins { + id("java") + id("dd-trace-java.muzzle") + } + """ + ) + writeRootProject("""layout.buildDirectory.set(layout.projectDirectory.dir("relocated/build"))""") + writeFile("relocated/build/muzzle-deps-results/first.csv", + """ + instrumentation,jarGroupId,jarArtifactId,lowestVersion,highestVersion + first-instrumentation,com.example,demo,1.0.0,2.0.0 + """ + ) + writeFile("relocated/build/muzzle-deps-results/second.csv", + """ + instrumentation,jarGroupId,jarArtifactId,lowestVersion,highestVersion + first-instrumentation,com.example,demo,1.5.0,3.0.0 + second-instrumentation,com.example,other,3.0.0,4.0.0 + """ + ) + + val args = arrayOf( + ":dd-java-agent:instrumentation:mergeMuzzleReports", + "--configuration-cache", + "--configuration-cache-problems=fail", + "--rerun-tasks", + "--stacktrace" + ) + + val first = run(*args) + assertThat(first.task(args[0])?.outcome).describedAs(first.output).isEqualTo(SUCCESS) + assertThat(first.task(":dd-java-agent:instrumentation:compileMuzzle")).isNull() + assertThat(first.task(":dd-java-agent:instrumentation:demo:generateMuzzleReport")).isNull() + assertThat(first.output).contains("Configuration cache entry stored") + + val reused = run(*args) + assertThat(reused.task(args[0])?.outcome).describedAs(reused.output).isEqualTo(SUCCESS) + assertThat(reused.output).contains("Reusing configuration cache") + + assertThat(file("relocated/build/muzzle-deps-results/dd-java-agent_instrumentation.csv").readText()) + .isEqualTo( + "instrumentation,jarGroupId,jarArtifactId,lowestVersion,highestVersion\n" + + "first-instrumentation,com.example,demo,1.0.0,3.0.0\n" + + "second-instrumentation,com.example,other,3.0.0,4.0.0\n" + ) + } + @Test fun `legacy generation keeps compilation prerequisites and reuses configuration cache`() { writeProject( From dbce8a269b448a23e5d5d15a3e080331f0219f49 Mon Sep 17 00:00:00 2001 From: Brice Dutheil Date: Tue, 6 Oct 2026 19:32:43 +0200 Subject: [PATCH 3/5] fix: refresh muzzle reports correctly --- .../muzzle/tasks/MuzzleGenerateReportTask.kt | 8 ++- .../muzzle/tasks/MuzzleMergeReportsTask.kt | 9 ++- .../MuzzleReportConfigurationCacheTest.kt | 72 ++++++++++++++++--- 3 files changed, 75 insertions(+), 14 deletions(-) diff --git a/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/tasks/MuzzleGenerateReportTask.kt b/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/tasks/MuzzleGenerateReportTask.kt index 97540db4327..c98992bed07 100644 --- a/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/tasks/MuzzleGenerateReportTask.kt +++ b/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/tasks/MuzzleGenerateReportTask.kt @@ -31,6 +31,9 @@ abstract class MuzzleGenerateReportTask : AbstractMuzzleReportTask() { init { description = "Generate this instrumentation's dependency version report" + // Repository metadata can change without any local task input changing. + outputs.upToDateWhen { false } + val extension = project.extensions.getByType() val runtimeClasspath = project.mainSourceSet.runtimeClasspath val directives = project.providers.provider { @@ -49,8 +52,9 @@ abstract class MuzzleGenerateReportTask : AbstractMuzzleReportTask() { reportDirectives.get().forEach { directive -> val range = MuzzleMavenRepoUtils.resolveVersionRange(directive, system, session) val cp = instrumentationClasspath.map { it.toURI().toURL() }.toTypedArray() - val cl = URLClassLoader(cp, null) - val partials = resolveInstrumentationAndJarVersions(directive, cl, range.lowestVersion, range.highestVersion) + val partials = URLClassLoader(cp, null).use { cl -> + resolveInstrumentationAndJarVersions(directive, cl, range.lowestVersion, range.highestVersion) + } partials.forEach { (key, value) -> versions.merge(key, value, BiFunction { x, y -> TestedArtifact( diff --git a/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/tasks/MuzzleMergeReportsTask.kt b/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/tasks/MuzzleMergeReportsTask.kt index a5b673ad8c1..9934a41c122 100644 --- a/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/tasks/MuzzleMergeReportsTask.kt +++ b/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/tasks/MuzzleMergeReportsTask.kt @@ -3,6 +3,9 @@ package datadog.gradle.plugin.muzzle.tasks import datadog.gradle.plugin.muzzle.MuzzleMavenRepoUtils import datadog.gradle.plugin.muzzle.TestedArtifact import org.eclipse.aether.util.version.GenericVersionScheme +import org.gradle.api.tasks.InputFiles +import org.gradle.api.tasks.PathSensitive +import org.gradle.api.tasks.PathSensitivity import org.gradle.api.tasks.TaskAction import java.util.TreeMap @@ -11,9 +14,11 @@ abstract class MuzzleMergeReportsTask : AbstractMuzzleReportTask() { description = "Merge generated dependency version reports into one CSV" } - private val versionReports = project.fileTree(project.rootProject.layout.buildDirectory.dir(MUZZLE_DEPS_RESULTS)) { + @get:InputFiles + @get:PathSensitive(PathSensitivity.RELATIVE) + val versionReports = project.fileTree(project.rootProject.layout.buildDirectory.dir(MUZZLE_DEPS_RESULTS)) { include("*.csv") - } + }.minus(project.files(versionsFile)) /** * Merges all muzzle report CSVs in the build directory into a single map and writes the merged results to a CSV. diff --git a/buildSrc/src/test/kotlin/datadog/gradle/plugin/muzzle/MuzzleReportConfigurationCacheTest.kt b/buildSrc/src/test/kotlin/datadog/gradle/plugin/muzzle/MuzzleReportConfigurationCacheTest.kt index 24dc046fd6e..833b83fd6ba 100644 --- a/buildSrc/src/test/kotlin/datadog/gradle/plugin/muzzle/MuzzleReportConfigurationCacheTest.kt +++ b/buildSrc/src/test/kotlin/datadog/gradle/plugin/muzzle/MuzzleReportConfigurationCacheTest.kt @@ -2,11 +2,14 @@ package datadog.gradle.plugin.muzzle import org.assertj.core.api.Assertions.assertThat import org.gradle.testkit.runner.TaskOutcome.SUCCESS +import org.gradle.testkit.runner.TaskOutcome.UP_TO_DATE import org.junit.jupiter.api.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ValueSource class MuzzleReportConfigurationCacheTest : MuzzlePluginTestFixture() { @Test - fun `dependency report executes with configuration cache reuse`() { + fun `dependency report refreshes version ranges with configuration cache reuse`() { val mavenRepo = createMavenRepoFixture() mavenRepo.publishVersions("com.example", "demo", listOf("1.0.0")) @@ -25,7 +28,7 @@ class MuzzleReportConfigurationCacheTest : MuzzlePluginTestFixture() { pass { group = "com.example" module = "demo" - versions = "1.0.0" + versions = "[1.0.0,)" } } """ @@ -37,7 +40,6 @@ class MuzzleReportConfigurationCacheTest : MuzzlePluginTestFixture() { ":dd-java-agent:instrumentation:demo:generateMuzzleReport", "--configuration-cache", "--configuration-cache-problems=fail", - "--rerun-tasks", "--stacktrace" ) val env = mapOf("MAVEN_REPOSITORY_PROXY" to mavenRepo.repoUrl) @@ -46,19 +48,28 @@ class MuzzleReportConfigurationCacheTest : MuzzlePluginTestFixture() { assertThat(first.task(args[0])?.outcome).describedAs(first.output).isEqualTo(SUCCESS) assertThat(first.output).contains("Configuration cache entry stored") + val report = file("relocated/build/muzzle-deps-results/dd-java-agent_instrumentation_demo.csv") + assertThat(report.readText()).isEqualTo( + "instrumentation,jarGroupId,jarArtifactId,lowestVersion,highestVersion\n" + + "test-instrumentation,com.example,demo,1.0.0,1.0.0\n" + ) + + mavenRepo.publishVersions("com.example", "demo", listOf("2.0.0")) + val reused = run(*args, env = env) assertThat(reused.task(args[0])?.outcome).describedAs(reused.output).isEqualTo(SUCCESS) assertThat(reused.output).contains("Reusing configuration cache") - assertThat(file("relocated/build/muzzle-deps-results/dd-java-agent_instrumentation_demo.csv").readText()) + assertThat(report.readText()) .isEqualTo( "instrumentation,jarGroupId,jarArtifactId,lowestVersion,highestVersion\n" + - "test-instrumentation,com.example,demo,1.0.0,1.0.0\n" + "test-instrumentation,com.example,demo,1.0.0,2.0.0\n" ) } - @Test - fun `merge combines generated version ranges with configuration cache reuse`() { + @ParameterizedTest + @ValueSource(booleans = [false, true]) + fun `merge reflects changed reports with configuration cache reuse`(rerunTasks: Boolean) { writeProject( """ plugins { @@ -94,7 +105,6 @@ class MuzzleReportConfigurationCacheTest : MuzzlePluginTestFixture() { ":dd-java-agent:instrumentation:mergeMuzzleReports", "--configuration-cache", "--configuration-cache-problems=fail", - "--rerun-tasks", "--stacktrace" ) @@ -105,15 +115,57 @@ class MuzzleReportConfigurationCacheTest : MuzzlePluginTestFixture() { assertThat(first.output).contains("Configuration cache entry stored") val reused = run(*args) - assertThat(reused.task(args[0])?.outcome).describedAs(reused.output).isEqualTo(SUCCESS) + assertThat(reused.task(args[0])?.outcome).describedAs(reused.output).isEqualTo(UP_TO_DATE) assertThat(reused.output).contains("Reusing configuration cache") - assertThat(file("relocated/build/muzzle-deps-results/dd-java-agent_instrumentation.csv").readText()) + val report = file("relocated/build/muzzle-deps-results/dd-java-agent_instrumentation.csv") + assertThat(report.readText()) .isEqualTo( "instrumentation,jarGroupId,jarArtifactId,lowestVersion,highestVersion\n" + "first-instrumentation,com.example,demo,1.0.0,3.0.0\n" + "second-instrumentation,com.example,other,3.0.0,4.0.0\n" ) + + writeFile("relocated/build/muzzle-deps-results/second.csv", + """ + instrumentation,jarGroupId,jarArtifactId,lowestVersion,highestVersion + first-instrumentation,com.example,demo,1.5.0,2.0.0 + """ + ) + + val refreshArgs = if (rerunTasks) args + "--rerun-tasks" else args + val changed = run(*refreshArgs) + assertThat(changed.task(args[0])?.outcome).describedAs(changed.output).isEqualTo(SUCCESS) + assertThat(changed.output).contains("Reusing configuration cache") + assertThat(report.readText()).isEqualTo( + "instrumentation,jarGroupId,jarArtifactId,lowestVersion,highestVersion\n" + + "first-instrumentation,com.example,demo,1.0.0,2.0.0\n" + ) + + writeFile("relocated/build/muzzle-deps-results/third.csv", + """ + instrumentation,jarGroupId,jarArtifactId,lowestVersion,highestVersion + third-instrumentation,com.example,new,4.0.0,5.0.0 + """ + ) + val added = run(*refreshArgs) + assertThat(added.task(args[0])?.outcome).describedAs(added.output).isEqualTo(SUCCESS) + assertThat(added.output).contains("Reusing configuration cache") + assertThat(report.readText()).isEqualTo( + "instrumentation,jarGroupId,jarArtifactId,lowestVersion,highestVersion\n" + + "first-instrumentation,com.example,demo,1.0.0,2.0.0\n" + + "third-instrumentation,com.example,new,4.0.0,5.0.0\n" + ) + + assertThat(file("relocated/build/muzzle-deps-results/first.csv").delete()).isTrue() + assertThat(file("relocated/build/muzzle-deps-results/second.csv").delete()).isTrue() + val removed = run(*refreshArgs) + assertThat(removed.task(args[0])?.outcome).describedAs(removed.output).isEqualTo(SUCCESS) + assertThat(removed.output).contains("Reusing configuration cache") + assertThat(report.readText()).isEqualTo( + "instrumentation,jarGroupId,jarArtifactId,lowestVersion,highestVersion\n" + + "third-instrumentation,com.example,new,4.0.0,5.0.0\n" + ) } @Test From 379fc3e37fc5bdbe3db9c8875e5e44d49b7e0b85 Mon Sep 17 00:00:00 2001 From: Brice Dutheil Date: Tue, 6 Oct 2026 19:48:28 +0200 Subject: [PATCH 4/5] fix: ignore orphaned muzzle reports --- .../muzzle/tasks/MuzzleMergeReportsTask.kt | 10 ++- .../MuzzleReportConfigurationCacheTest.kt | 90 +++++++++++++++++-- 2 files changed, 90 insertions(+), 10 deletions(-) diff --git a/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/tasks/MuzzleMergeReportsTask.kt b/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/tasks/MuzzleMergeReportsTask.kt index 9934a41c122..93ec54e45af 100644 --- a/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/tasks/MuzzleMergeReportsTask.kt +++ b/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/tasks/MuzzleMergeReportsTask.kt @@ -16,12 +16,14 @@ abstract class MuzzleMergeReportsTask : AbstractMuzzleReportTask() { @get:InputFiles @get:PathSensitive(PathSensitivity.RELATIVE) - val versionReports = project.fileTree(project.rootProject.layout.buildDirectory.dir(MUZZLE_DEPS_RESULTS)) { - include("*.csv") - }.minus(project.files(versionsFile)) + val versionReports = project.files( + project.rootProject.allprojects.flatMap { producer -> + producer.tasks.withType(MuzzleGenerateReportTask::class.java).map { it.versionsFile } + } + ).minus(project.files(versionsFile)).asFileTree /** - * Merges all muzzle report CSVs in the build directory into a single map and writes the merged results to a CSV. + * Merges existing CSVs from current report generators and writes the merged results to a CSV. */ @TaskAction fun mergeReports() { diff --git a/buildSrc/src/test/kotlin/datadog/gradle/plugin/muzzle/MuzzleReportConfigurationCacheTest.kt b/buildSrc/src/test/kotlin/datadog/gradle/plugin/muzzle/MuzzleReportConfigurationCacheTest.kt index 833b83fd6ba..6d43d0beedf 100644 --- a/buildSrc/src/test/kotlin/datadog/gradle/plugin/muzzle/MuzzleReportConfigurationCacheTest.kt +++ b/buildSrc/src/test/kotlin/datadog/gradle/plugin/muzzle/MuzzleReportConfigurationCacheTest.kt @@ -86,14 +86,24 @@ class MuzzleReportConfigurationCacheTest : MuzzlePluginTestFixture() { } """ ) + listOf("first", "second", "third").forEach { producer -> + addSubproject("dd-java-agent:instrumentation:$producer", + """ + plugins { + id("java") + id("dd-trace-java.muzzle") + } + """ + ) + } writeRootProject("""layout.buildDirectory.set(layout.projectDirectory.dir("relocated/build"))""") - writeFile("relocated/build/muzzle-deps-results/first.csv", + writeFile("relocated/build/muzzle-deps-results/dd-java-agent_instrumentation_first.csv", """ instrumentation,jarGroupId,jarArtifactId,lowestVersion,highestVersion first-instrumentation,com.example,demo,1.0.0,2.0.0 """ ) - writeFile("relocated/build/muzzle-deps-results/second.csv", + writeFile("relocated/build/muzzle-deps-results/dd-java-agent_instrumentation_second.csv", """ instrumentation,jarGroupId,jarArtifactId,lowestVersion,highestVersion first-instrumentation,com.example,demo,1.5.0,3.0.0 @@ -126,7 +136,7 @@ class MuzzleReportConfigurationCacheTest : MuzzlePluginTestFixture() { "second-instrumentation,com.example,other,3.0.0,4.0.0\n" ) - writeFile("relocated/build/muzzle-deps-results/second.csv", + writeFile("relocated/build/muzzle-deps-results/dd-java-agent_instrumentation_second.csv", """ instrumentation,jarGroupId,jarArtifactId,lowestVersion,highestVersion first-instrumentation,com.example,demo,1.5.0,2.0.0 @@ -142,7 +152,7 @@ class MuzzleReportConfigurationCacheTest : MuzzlePluginTestFixture() { "first-instrumentation,com.example,demo,1.0.0,2.0.0\n" ) - writeFile("relocated/build/muzzle-deps-results/third.csv", + writeFile("relocated/build/muzzle-deps-results/dd-java-agent_instrumentation_third.csv", """ instrumentation,jarGroupId,jarArtifactId,lowestVersion,highestVersion third-instrumentation,com.example,new,4.0.0,5.0.0 @@ -157,8 +167,8 @@ class MuzzleReportConfigurationCacheTest : MuzzlePluginTestFixture() { "third-instrumentation,com.example,new,4.0.0,5.0.0\n" ) - assertThat(file("relocated/build/muzzle-deps-results/first.csv").delete()).isTrue() - assertThat(file("relocated/build/muzzle-deps-results/second.csv").delete()).isTrue() + assertThat(file("relocated/build/muzzle-deps-results/dd-java-agent_instrumentation_first.csv").delete()).isTrue() + assertThat(file("relocated/build/muzzle-deps-results/dd-java-agent_instrumentation_second.csv").delete()).isTrue() val removed = run(*refreshArgs) assertThat(removed.task(args[0])?.outcome).describedAs(removed.output).isEqualTo(SUCCESS) assertThat(removed.output).contains("Reusing configuration cache") @@ -168,6 +178,74 @@ class MuzzleReportConfigurationCacheTest : MuzzlePluginTestFixture() { ) } + @Test + fun `merge ignores reports from removed and renamed producers`() { + val producerScript = """ + plugins { + id("java") + id("dd-trace-java.muzzle") + } + """ + writeProject(producerScript) + addSubproject("dd-java-agent:instrumentation", producerScript) + val currentSettings = file("settings.gradle.kts").readText() + addSubproject("dd-java-agent:instrumentation:deleted", producerScript) + writeRootProject("""layout.buildDirectory.set(layout.projectDirectory.dir("relocated/build"))""") + + val active = writeFile("relocated/build/muzzle-deps-results/dd-java-agent_instrumentation_demo.csv", + """ + instrumentation,jarGroupId,jarArtifactId,lowestVersion,highestVersion + active-instrumentation,com.example,demo,1.0.0,2.0.0 + """ + ) + val orphan = writeFile("relocated/build/muzzle-deps-results/dd-java-agent_instrumentation_deleted.csv", + """ + instrumentation,jarGroupId,jarArtifactId,lowestVersion,highestVersion + deleted-instrumentation,com.example,deleted,3.0.0,4.0.0 + """ + ) + val args = arrayOf( + ":dd-java-agent:instrumentation:mergeMuzzleReports", + "--configuration-cache", + "--configuration-cache-problems=fail", + "--stacktrace" + ) + val report = file("relocated/build/muzzle-deps-results/dd-java-agent_instrumentation.csv") + + val first = run(*args) + assertThat(first.task(args[0])?.outcome).describedAs(first.output).isEqualTo(SUCCESS) + assertThat(report.readText()).isEqualTo( + "instrumentation,jarGroupId,jarArtifactId,lowestVersion,highestVersion\n" + + "active-instrumentation,com.example,demo,1.0.0,2.0.0\n" + + "deleted-instrumentation,com.example,deleted,3.0.0,4.0.0\n" + ) + + writeSettings(currentSettings) + val removed = run(*args) + assertThat(removed.task(args[0])?.outcome).describedAs(removed.output).isEqualTo(SUCCESS) + assertThat(report.readText()).isEqualTo(active.readText()) + assertThat(orphan).exists() + + writeSettings(currentSettings.replace(":instrumentation:demo", ":instrumentation:renamed")) + writeFile("dd-java-agent/instrumentation/renamed/build.gradle.kts", producerScript) + val renamed = writeFile("relocated/build/muzzle-deps-results/dd-java-agent_instrumentation_renamed.csv", + """ + instrumentation,jarGroupId,jarArtifactId,lowestVersion,highestVersion + renamed-instrumentation,com.example,demo,1.5.0,1.5.0 + """ + ) + val refreshed = run(*args) + assertThat(refreshed.task(args[0])?.outcome).describedAs(refreshed.output).isEqualTo(SUCCESS) + assertThat(report.readText()).isEqualTo(renamed.readText()) + assertThat(active).exists() + assertThat(orphan).exists() + + val reused = run(*args) + assertThat(reused.task(args[0])?.outcome).describedAs(reused.output).isEqualTo(UP_TO_DATE) + assertThat(reused.output).contains("Reusing configuration cache") + assertThat(report.readText()).isEqualTo(renamed.readText()) + } + @Test fun `legacy generation keeps compilation prerequisites and reuses configuration cache`() { writeProject( From e0acdbcc47b7ecfb57a1b93271f9358ea65f4ab2 Mon Sep 17 00:00:00 2001 From: Brice Dutheil Date: Wed, 7 Oct 2026 09:44:09 +0200 Subject: [PATCH 5/5] fix: keep Muzzle source-set discovery lazy --- .../datadog/gradle/plugin/muzzle/MuzzlePlugin.kt | 2 +- .../muzzle/MuzzleReportConfigurationCacheTest.kt | 16 ++++++++++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/MuzzlePlugin.kt b/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/MuzzlePlugin.kt index 601f6ed2e1e..3e3217d2f90 100644 --- a/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/MuzzlePlugin.kt +++ b/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/MuzzlePlugin.kt @@ -69,7 +69,7 @@ class MuzzlePlugin : Plugin { // compileMuzzle compiles all projects required to run muzzle validation. // Not adding group and description to keep this task from showing in `gradle tasks`. val compileMuzzle = project.tasks.register("compileMuzzle") { - inputs.files(project.allMainSourceSet.map { it.output }) + inputs.files(project.providers.provider { project.allMainSourceSet.map { it.output } }) dependsOn(bootstrapProject.tasks.named("compileJava")) dependsOn(bootstrapProject.tasks.named("compileMain_java11Java")) dependsOn(toolingProject.tasks.named("compileJava")) diff --git a/buildSrc/src/test/kotlin/datadog/gradle/plugin/muzzle/MuzzleReportConfigurationCacheTest.kt b/buildSrc/src/test/kotlin/datadog/gradle/plugin/muzzle/MuzzleReportConfigurationCacheTest.kt index e11a9087331..04357799b2b 100644 --- a/buildSrc/src/test/kotlin/datadog/gradle/plugin/muzzle/MuzzleReportConfigurationCacheTest.kt +++ b/buildSrc/src/test/kotlin/datadog/gradle/plugin/muzzle/MuzzleReportConfigurationCacheTest.kt @@ -58,7 +58,7 @@ class MuzzleReportConfigurationCacheTest : MuzzlePluginTestFixture() { } @Test - fun `legacy generation keeps compilation prerequisites and reuses configuration cache`() { + fun `legacy generation compiles late source sets and reuses configuration cache`() { writeProject( """ plugins { @@ -66,9 +66,16 @@ class MuzzleReportConfigurationCacheTest : MuzzlePluginTestFixture() { id("dd-trace-java.muzzle") } + tasks.named("compileMuzzle").get() sourceSets.create("main_java11") """ ) + writeJavaSource( + "LateInstrumentation", + "public class LateInstrumentation {}", + sourceSet = "main_java11", + projectPath = "dd-java-agent:instrumentation:demo" + ) val args = arrayOf( ":dd-java-agent:instrumentation:demo:generateMuzzleReport", @@ -81,11 +88,16 @@ class MuzzleReportConfigurationCacheTest : MuzzlePluginTestFixture() { val first = run(*args) assertThat(first.task(args[0])?.outcome).describedAs(first.output).isEqualTo(SUCCESS) assertThat(first.task(":dd-java-agent:agent-bootstrap:compileMain_java11Java")).isNotNull() - assertThat(first.task(":dd-java-agent:instrumentation:demo:compileMain_java11Java")).isNotNull() + assertThat(first.task(":dd-java-agent:instrumentation:demo:compileMain_java11Java")?.outcome) + .describedAs(first.output).isEqualTo(SUCCESS) + assertThat(file("dd-java-agent/instrumentation/demo/build/classes/java/main_java11/LateInstrumentation.class")) + .exists() assertThat(first.output).contains("Configuration cache entry stored") val reused = run(*args) assertThat(reused.task(args[0])?.outcome).describedAs(reused.output).isEqualTo(SUCCESS) + assertThat(reused.task(":dd-java-agent:instrumentation:demo:compileMain_java11Java")?.outcome) + .describedAs(reused.output).isEqualTo(SUCCESS) assertThat(reused.output).contains("Reusing configuration cache") } }