From 1334163988cba9c8990b71cfe9e650138caa6a14 Mon Sep 17 00:00:00 2001 From: Sarah Chen Date: Wed, 7 Oct 2026 15:44:12 -0400 Subject: [PATCH 1/2] Muzzle improvements --- .../gradle/plugin/muzzle/MuzzleDirective.kt | 10 ++ .../gradle/plugin/muzzle/MuzzleExtension.kt | 4 + .../plugin/muzzle/MuzzleMavenRepoUtils.kt | 23 ++- .../gradle/plugin/muzzle/MuzzlePlugin.kt | 139 ++++++++++++------ .../plugin/muzzle/MuzzleVersionUtils.kt | 11 +- .../planner/MavenMuzzleResolutionService.kt | 6 +- .../muzzle/planner/MuzzlePlansValueSource.kt | 57 +++++++ .../plugin/muzzle/tasks/MuzzleEndTask.kt | 68 +++++---- .../muzzle/tasks/MuzzleGetReferencesTask.kt | 8 +- .../gradle/plugin/muzzle/tasks/MuzzleTask.kt | 72 +++------ .../kotlin/dd-trace-java.ci-jobs.gradle.kts | 13 +- .../muzzle/MuzzlePluginFunctionalTest.kt | 108 ++++++++++++++ .../muzzle/MuzzlePluginPerformanceTest.kt | 62 ++++++++ .../plugin/muzzle/MuzzleVersionUtilsTest.kt | 13 ++ 14 files changed, 442 insertions(+), 152 deletions(-) create mode 100644 buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/planner/MuzzlePlansValueSource.kt diff --git a/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/MuzzleDirective.kt b/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/MuzzleDirective.kt index 4616363329b..f67a76e5481 100644 --- a/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/MuzzleDirective.kt +++ b/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/MuzzleDirective.kt @@ -83,6 +83,16 @@ open class MuzzleDirective : Serializable { } } + internal fun inverse(version: String): MuzzleDirective = MuzzleDirective().apply { + name = this@MuzzleDirective.name + group = this@MuzzleDirective.group + module = this@MuzzleDirective.module + versions = version + assertPass = !this@MuzzleDirective.assertPass + excludedDependencies = this@MuzzleDirective.excludedDependencies + includeSnapshots = this@MuzzleDirective.includeSnapshots + } + /** * Slug of directive name. * diff --git a/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/MuzzleExtension.kt b/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/MuzzleExtension.kt index 285301e2613..616fb9e430d 100644 --- a/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/MuzzleExtension.kt +++ b/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/MuzzleExtension.kt @@ -3,6 +3,7 @@ package datadog.gradle.plugin.muzzle import org.gradle.api.Action import org.gradle.api.model.ObjectFactory import org.gradle.kotlin.dsl.newInstance +import org.gradle.kotlin.dsl.property import java.util.Locale import javax.inject.Inject @@ -10,6 +11,9 @@ import javax.inject.Inject * Muzzle extension containing all pass and fail directives. */ abstract class MuzzleExtension @Inject constructor(private val objectFactory: ObjectFactory) { + /** Whether aggregate runMuzzle invocations should plan checks for this project. */ + val includeInAggregate = objectFactory.property().convention(true) + val directives: MutableList = ArrayList() private val additionalRepositories: MutableList> = ArrayList() diff --git a/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/MuzzleMavenRepoUtils.kt b/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/MuzzleMavenRepoUtils.kt index 7a8858b1349..938ad0319ff 100644 --- a/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/MuzzleMavenRepoUtils.kt +++ b/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/MuzzleMavenRepoUtils.kt @@ -22,6 +22,7 @@ import org.eclipse.aether.version.Version import org.gradle.api.GradleException import org.gradle.api.logging.Logging import java.nio.file.Files +import kotlin.random.Random internal object MuzzleMavenRepoUtils { private val log = Logging.getLogger(MuzzleMavenRepoUtils::class.java) @@ -82,7 +83,8 @@ internal object MuzzleMavenRepoUtils { muzzleDirective: MuzzleDirective, system: RepositorySystem, session: RepositorySystemSession, - defaultRepos: List = defaultMuzzleRepos() + defaultRepos: List = defaultMuzzleRepos(), + random: Random = Random.Default ): Set { val allVersionsArtifact = DefaultArtifact( muzzleDirective.group, @@ -114,17 +116,10 @@ internal object MuzzleMavenRepoUtils { return MuzzleVersionUtils.filterAndLimitVersions( allRangeResult, muzzleDirective.skipVersions, - muzzleDirective.includeSnapshots + muzzleDirective.includeSnapshots, + random ).map { version -> - MuzzleDirective().apply { - name = muzzleDirective.name - group = muzzleDirective.group - module = muzzleDirective.module - versions = version.toString() - assertPass = !muzzleDirective.assertPass - excludedDependencies = muzzleDirective.excludedDependencies - includeSnapshots = muzzleDirective.includeSnapshots - } + muzzleDirective.inverse(version.toString()) }.toSet() } @@ -373,12 +368,14 @@ internal object MuzzleMavenRepoUtils { */ fun muzzleDirectiveToArtifacts( muzzleDirective: MuzzleDirective, - rangeResult: VersionRangeResult + rangeResult: VersionRangeResult, + random: Random = Random.Default ): Set { val versions = MuzzleVersionUtils.filterAndLimitVersions( rangeResult, muzzleDirective.skipVersions, - muzzleDirective.includeSnapshots + muzzleDirective.includeSnapshots, + random ) val allVersionArtifacts = versions.map { version -> DefaultArtifact( 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 42379795a86..8f293ac4577 100644 --- a/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/MuzzlePlugin.kt +++ b/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/MuzzlePlugin.kt @@ -1,11 +1,15 @@ package datadog.gradle.plugin.muzzle +import datadog.gradle.plugin.muzzle.planner.MuzzlePlannedVersion +import datadog.gradle.plugin.muzzle.planner.MuzzlePlanningRequest +import datadog.gradle.plugin.muzzle.planner.MuzzlePlansValueSource import datadog.gradle.plugin.muzzle.tasks.MuzzleEndTask import datadog.gradle.plugin.muzzle.tasks.MuzzleGenerateReportTask import datadog.gradle.plugin.muzzle.tasks.MuzzleGetReferencesTask import datadog.gradle.plugin.muzzle.tasks.MuzzleTask -import datadog.gradle.plugin.muzzle.planner.MuzzleTaskPlanner +import kotlin.random.Random import org.eclipse.aether.artifact.Artifact +import org.eclipse.aether.artifact.DefaultArtifact import org.gradle.api.NamedDomainObjectProvider import org.gradle.api.Plugin import org.gradle.api.Project @@ -14,8 +18,10 @@ import org.gradle.api.tasks.TaskProvider import org.gradle.kotlin.dsl.create import org.gradle.kotlin.dsl.exclude import org.gradle.kotlin.dsl.getByType +import org.gradle.kotlin.dsl.named import org.gradle.kotlin.dsl.project import org.gradle.kotlin.dsl.register +import org.gradle.kotlin.dsl.withType /** * muzzle task plugin which runs muzzle validation against a range of dependencies. @@ -67,6 +73,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 extension = project.extensions.getByType() val compileMuzzle = project.tasks.register("compileMuzzle") { inputs.files(project.providers.provider { project.allMainSourceSet.map { it.output } }) dependsOn(bootstrapProject.tasks.named("compileJava")) @@ -76,23 +83,29 @@ class MuzzlePlugin : Plugin { val muzzleTask = project.tasks.register("muzzle") { description = "Check instrumentation compatibility against the configured dependency versions" - this.muzzleBootstrap.set(muzzleBootstrap) - this.muzzleTooling.set(muzzleTooling) + this.muzzleBootstrap.from(muzzleBootstrap) + this.muzzleTooling.from(muzzleTooling) dependsOn(compileMuzzle) } + project.tasks.withType().configureEach { + agentClassPath.from(project.providers.provider { project.allMainSourceSet.map { it.runtimeClasspath } }) + muzzleClassPath.from(project.configurations.named(if (name == "muzzle") "compileClasspath" else name)) + } + project.tasks.register("printReferences") { dependsOn(compileMuzzle) + classpath.from(project.mainSourceSet.runtimeClasspath) }.also { + val referenceOutput = it.flatMap { task -> task.outputFile } val printReferencesTask = project.tasks.register("actuallyPrintReferences") { doLast { - println(it.get().outputFile.get().asFile.readText()) + println(referenceOutput.get().asFile.readText()) } } it.configure { finalizedBy(printReferencesTask) } } - val extension = project.extensions.getByType() val runtimeClasspath = project.mainSourceSet.runtimeClasspath val report = project.tasks.register("generateMuzzleReport") { reportDirectives.convention(project.providers.provider { extension.directives }) @@ -101,54 +114,86 @@ class MuzzlePlugin : Plugin { } project.publishMuzzleReport(report.flatMap { it.versionsFile }) - val hasRelevantTask = project.gradle.startParameter.taskNames.any { taskName -> - val taskProjectPath = taskName.substringBeforeLast(":", "") - val taskNameOnly = taskName.substringAfterLast(":") - val isRelevantForProject = taskProjectPath.isEmpty() || taskProjectPath == project.path + registerPlanning(project) + } - isRelevantForProject && taskNameOnly.endsWith("muzzle", ignoreCase = true) - } - if (!hasRelevantTask) { - // Adding muzzle dependencies has a large config overhead. Stop unless muzzle is explicitly run. - project.logger.info("No muzzle tasks invoked for ${project.path}, skipping muzzle task planification") - return - } + private fun hasRelevantTask(project: Project): Boolean = project.gradle.startParameter.taskNames.any { taskName -> + val taskProjectPath = taskName.substringBeforeLast(":", "") + val taskNameOnly = taskName.substringAfterLast(":") + val isAggregate = taskNameOnly.equals("runMuzzle", ignoreCase = true) + val isRelevantForProject = taskProjectPath.isEmpty() || taskProjectPath == project.path || + (isAggregate && project.path.startsWith("$taskProjectPath:")) + isRelevantForProject && !taskNameOnly.equals("compileMuzzle", ignoreCase = true) && + taskNameOnly.endsWith("muzzle", ignoreCase = true) && + (!isAggregate || project.extensions.getByType().includeInAggregate.get()) + } - // We only get here if we are running muzzle, so let's start timing things - val startTime = System.currentTimeMillis() - - val system = MuzzleMavenRepoUtils.newRepositorySystem() - val session = MuzzleMavenRepoUtils.newRepositorySystemSession(system) - val taskPlanner = MuzzleTaskPlanner.from(system, session) - project.afterEvaluate { - // use runAfter to set up task finalizers in version order - var runAfter: TaskProvider = muzzleTask - val muzzleReportTasks = mutableListOf>() - val directives = project.extensions.getByType().directives - taskPlanner.plan(directives).forEach { plan -> - runAfter = registerMuzzleTask(plan.directive, plan.artifact, project, runAfter, muzzleBootstrap, muzzleTooling) - muzzleReportTasks.add(runAfter) - project.logger.info("configured ${plan.directive}") + private fun registerPlanning(project: Project) { + val root = project.rootProject + val marker = "datadogMuzzlePlanningRegistered" + if (root.extensions.extraProperties.has(marker)) return + root.extensions.extraProperties.set(marker, true) + project.gradle.projectsEvaluated { + val projects = root.allprojects.filter { + it.plugins.hasPlugin("dd-trace-java.muzzle") && it.plugins.hasPlugin("java") + }.filter { + val extension = it.extensions.getByType() + it.tasks.named("muzzle").configure { + checkCompileTimeDependencies.set(!extension.directives.any { it.assertPass }) + } + val relevant = hasRelevantTask(it) + if (!relevant) { + it.logger.info("No muzzle tasks invoked for ${it.path}, skipping muzzle task planification") + } + relevant } - - if (muzzleReportTasks.isEmpty() && !directives.any { it.assertPass }) { - muzzleReportTasks.add(muzzleTask) + if (projects.isEmpty()) return@projectsEvaluated + val requests = projects.map { + MuzzlePlanningRequest(it.path, it.extensions.getByType().directives.toList()) + } + // Revalidate selected coordinates when loading a cached task graph. + val plans = root.providers.of(MuzzlePlansValueSource::class.java) { + parameters.requests.set(requests) + parameters.samplingSeed.set(Random.nextLong()) + }.get().groupBy { it.projectPath } + projects.forEachIndexed { index, instrumentation -> + configureMuzzlePlan(instrumentation, requests[index].directives, plans[instrumentation.path].orEmpty()) } + } + } - val timingTask = project.tasks.register("muzzle-end") { - startTimeMs.set(startTime) - sourceFile.set( - project.projectDir - .relativeTo(project.rootProject.projectDir) - .invariantSeparatorsPath - ) - muzzleResultFiles.from(muzzleReportTasks.map { it.flatMap { task -> task.result } }) + private fun configureMuzzlePlan( + project: Project, + directives: List, + plans: List + ) { + val muzzleTask = project.tasks.named("muzzle") + val muzzleBootstrap = project.configurations.named("muzzleBootstrap") + val muzzleTooling = project.configurations.named("muzzleTooling") + var runAfter = muzzleTask + val muzzleReportTasks = mutableListOf>() + plans.forEach { plan -> + val original = directives[plan.directiveIndex] + val directive = if (plan.assertPass == original.assertPass) { + original + } else { + original.inverse(requireNotNull(plan.version)) } - // last muzzle task to run - runAfter.configure { - finalizedBy(timingTask) + val artifact = plan.version?.let { + DefaultArtifact(directive.group, directive.module, directive.classifier ?: "", "jar", it) } + runAfter = registerMuzzleTask(directive, artifact, project, runAfter, muzzleBootstrap, muzzleTooling) + muzzleReportTasks.add(runAfter) + project.logger.info("configured $directive") + } + if (muzzleReportTasks.isEmpty() && !directives.any { it.assertPass }) { + muzzleReportTasks.add(muzzleTask) + } + val timingTask = project.tasks.register("muzzle-end") { + sourceFile.set(project.projectDir.relativeTo(project.rootProject.projectDir).invariantSeparatorsPath) + muzzleResultFiles.from(muzzleReportTasks.map { it.flatMap { task -> task.result } }) } + runAfter.configure { finalizedBy(timingTask) } } companion object { @@ -234,8 +279,8 @@ class MuzzlePlugin : Plugin { val muzzleTask = instrumentationProject.tasks.register(muzzleTaskName) { this.muzzleDirective.set(muzzleDirective) - this.muzzleBootstrap.set(muzzleBootstrap) - this.muzzleTooling.set(muzzleTooling) + this.muzzleBootstrap.from(muzzleBootstrap) + this.muzzleTooling.from(muzzleTooling) } runAfterTask.configure { diff --git a/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/MuzzleVersionUtils.kt b/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/MuzzleVersionUtils.kt index 9a12471775b..eb6dbda957e 100644 --- a/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/MuzzleVersionUtils.kt +++ b/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/MuzzleVersionUtils.kt @@ -3,6 +3,7 @@ package datadog.gradle.plugin.muzzle import org.eclipse.aether.resolution.VersionRangeResult import org.eclipse.aether.version.Version import java.util.Locale +import kotlin.random.Random internal object MuzzleVersionUtils { private val END_NMN_PATTERN = Regex("^.*\\.[0-9]+[mM][0-9]+$") @@ -19,10 +20,11 @@ internal object MuzzleVersionUtils { fun filterAndLimitVersions( result: VersionRangeResult, skipVersions: Set, - includeSnapshots: Boolean + includeSnapshots: Boolean, + random: Random = Random.Default ): Set { val filtered = filterVersion(result.versions.toSet(), skipVersions, includeSnapshots) - return limitLargeRanges(result, filtered, skipVersions) + return limitLargeRanges(result, filtered, skipVersions, random) } /** @@ -81,7 +83,8 @@ internal object MuzzleVersionUtils { private fun limitLargeRanges( result: VersionRangeResult, versions: Set, - skipVersions: Set + skipVersions: Set, + random: Random ): Set { if (versions.size <= 1) return versions val beforeSize = versions.size @@ -89,7 +92,7 @@ internal object MuzzleVersionUtils { removeAll { skipVersions.contains(it.toString()) } } val versionSet = VersionSet(filteredVersions) - val shuffled = versionSet.lowAndHighForMajorMinor.shuffled().toMutableList() + val shuffled = versionSet.lowAndHighForMajorMinor.shuffled(random).toMutableList() var afterSize = shuffled.size while (RANGE_COUNT_LIMIT <= afterSize) { val version = shuffled.removeAt(0) diff --git a/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/planner/MavenMuzzleResolutionService.kt b/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/planner/MavenMuzzleResolutionService.kt index ee9d7c38369..376c0d3f3f6 100644 --- a/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/planner/MavenMuzzleResolutionService.kt +++ b/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/planner/MavenMuzzleResolutionService.kt @@ -5,6 +5,7 @@ import datadog.gradle.plugin.muzzle.MuzzleMavenRepoUtils import org.eclipse.aether.RepositorySystem import org.eclipse.aether.RepositorySystemSession import org.eclipse.aether.artifact.Artifact +import kotlin.random.Random /** * Default [MuzzleResolutionService] implementation backed by Maven/Aether resolution. @@ -12,12 +13,13 @@ import org.eclipse.aether.artifact.Artifact internal class MavenMuzzleResolutionService( private val system: RepositorySystem, private val session: RepositorySystemSession, + private val random: Random = Random.Default, ) : MuzzleResolutionService { override fun resolveArtifacts(directive: MuzzleDirective): Set { val range = MuzzleMavenRepoUtils.resolveVersionRange(directive, system, session) - return MuzzleMavenRepoUtils.muzzleDirectiveToArtifacts(directive, range) + return MuzzleMavenRepoUtils.muzzleDirectiveToArtifacts(directive, range, random) } override fun inverseOf(directive: MuzzleDirective): Set = - MuzzleMavenRepoUtils.inverseOf(directive, system, session) + MuzzleMavenRepoUtils.inverseOf(directive, system, session, random = random) } diff --git a/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/planner/MuzzlePlansValueSource.kt b/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/planner/MuzzlePlansValueSource.kt new file mode 100644 index 00000000000..c024d3baa85 --- /dev/null +++ b/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/planner/MuzzlePlansValueSource.kt @@ -0,0 +1,57 @@ +package datadog.gradle.plugin.muzzle.planner + +import datadog.gradle.plugin.muzzle.MuzzleDirective +import datadog.gradle.plugin.muzzle.MuzzleMavenRepoUtils +import org.gradle.api.Describable +import org.gradle.api.logging.Logging +import org.gradle.api.provider.ListProperty +import org.gradle.api.provider.Property +import org.gradle.api.provider.ValueSource +import org.gradle.api.provider.ValueSourceParameters +import java.io.Serializable +import kotlin.random.Random + +/** Tracks the selected coordinates as configuration inputs. */ +internal abstract class MuzzlePlansValueSource : + ValueSource, MuzzlePlansValueSource.Parameters>, + Describable { + interface Parameters : ValueSourceParameters { + val requests: ListProperty + val samplingSeed: Property + } + + override fun getDisplayName() = "Muzzle coordinates" + + override fun obtain(): List { + val startNanos = System.nanoTime() + val system = MuzzleMavenRepoUtils.newRepositorySystem() + val session = MuzzleMavenRepoUtils.newRepositorySystemSession(system) + val random = Random(parameters.samplingSeed.get()) + val requests = parameters.requests.get() + val plans = requests.flatMap { request -> + val planner = MuzzleTaskPlanner(MavenMuzzleResolutionService(system, session, random = random)) + request.directives.flatMapIndexed { index, directive -> + planner.plan(listOf(directive)).map { plan -> + MuzzlePlannedVersion(request.projectPath, index, plan.artifact?.version, plan.directive.assertPass) + } + } + } + Logging.getLogger(MuzzlePlansValueSource::class.java).info( + "Muzzle planned ${plans.size} checks for ${requests.size} modules in " + + "${(System.nanoTime() - startNanos) / 1_000_000}ms" + ) + return plans + } +} + +internal data class MuzzlePlanningRequest( + val projectPath: String, + val directives: List +) : Serializable + +internal data class MuzzlePlannedVersion( + val projectPath: String, + val directiveIndex: Int, + val version: String?, + val assertPass: Boolean +) : Serializable diff --git a/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/tasks/MuzzleEndTask.kt b/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/tasks/MuzzleEndTask.kt index edc4c133032..a7f4a6f28a7 100644 --- a/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/tasks/MuzzleEndTask.kt +++ b/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/tasks/MuzzleEndTask.kt @@ -2,6 +2,7 @@ package datadog.gradle.plugin.muzzle.tasks import datadog.gradle.plugin.muzzle.pathSlug import org.gradle.api.file.ConfigurableFileCollection +import org.gradle.api.invocation.BuildInvocationDetails import org.gradle.api.provider.Property import org.gradle.api.tasks.Input import org.gradle.api.tasks.InputFiles @@ -11,11 +12,23 @@ import org.gradle.api.tasks.PathSensitivity import org.gradle.api.tasks.TaskAction import java.io.File import java.io.StringWriter +import javax.inject.Inject import javax.xml.stream.XMLOutputFactory abstract class MuzzleEndTask : AbstractMuzzleTask() { + @get:Inject + abstract val invocationDetails: BuildInvocationDetails + + @get:Input + val startTimeMs: Property = project.objects.property(Long::class.java).convention( + project.providers.provider { invocationDetails.buildStartedTime } + ) + + @get:Input + val modulePath: String = project.path + @get:Input - abstract val startTimeMs: Property + val reportClassName: String = "muzzle.${project.pathSlug}" @get:Input abstract val sourceFile: Property @@ -39,8 +52,8 @@ abstract class MuzzleEndTask : AbstractMuzzleTask() { @TaskAction fun generatesResultFile() { val report = buildJUnitReport() - writeReportFile(project.file(resultsFile), renderReportXml(report), "muzzle junit") - writeReportFile(project.file(legacyResultsFile), renderLegacyReportXml(report.durationSeconds), "muzzle legacy") + writeReportFile(resultsFile.get().asFile, renderReportXml(report), "muzzle junit") + writeReportFile(legacyResultsFile.get().asFile, renderLegacyReportXml(report.durationSeconds), "muzzle legacy") } private fun buildJUnitReport(): MuzzleJUnitReport { @@ -49,31 +62,32 @@ abstract class MuzzleEndTask : AbstractMuzzleTask() { val testCases = muzzleResultFiles.files .sortedBy { it.name } .map { resultFile -> - val taskName = resultFile.name.removeSuffix(".txt") - when { - !resultFile.exists() -> { - MuzzleJUnitCase( - name = taskName, - failureMessage = "Muzzle result file missing", - failureText = "Expected ${resultFile.path}" - ) - } + val taskName = resultFile.name.removeSuffix(".txt") + when { + !resultFile.exists() -> { + MuzzleJUnitCase( + name = taskName, + failureMessage = "Muzzle result file missing", + failureText = "Expected ${resultFile.path}" + ) + } + + resultFile.readText() == "PASSING" -> MuzzleJUnitCase(name = taskName) - resultFile.readText() == "PASSING" -> MuzzleJUnitCase(name = taskName) - else -> { - MuzzleJUnitCase( - name = taskName, - failureMessage = "Muzzle validation failed", - failureText = resultFile.readText() - ) + else -> { + MuzzleJUnitCase( + name = taskName, + failureMessage = "Muzzle validation failed", + failureText = resultFile.readText() + ) + } } } - } return MuzzleJUnitReport( - suiteName = project.path, - module = project.path, + suiteName = modulePath, + module = modulePath, sourceFile = sourceFile.get(), - className = "muzzle.${project.pathSlug}", + className = reportClassName, durationSeconds = seconds, testCases = testCases ) @@ -138,17 +152,15 @@ abstract class MuzzleEndTask : AbstractMuzzleTask() { private fun writeReportFile(file: File, xml: String, label: String) { file.parentFile.mkdirs() file.writeText(xml) - project.logger.info("Wrote $label report to\n $file") + logger.info("Wrote $label report to\n $file") } - private fun renderLegacyReportXml(durationSeconds: Double): String { - return """ + private fun renderLegacyReportXml(durationSeconds: Double): String = """ - """.trimIndent() - } + """.trimIndent() private data class MuzzleJUnitReport( val suiteName: String, diff --git a/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/tasks/MuzzleGetReferencesTask.kt b/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/tasks/MuzzleGetReferencesTask.kt index af90609ff6d..31223156232 100644 --- a/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/tasks/MuzzleGetReferencesTask.kt +++ b/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/tasks/MuzzleGetReferencesTask.kt @@ -1,8 +1,7 @@ package datadog.gradle.plugin.muzzle.tasks -import datadog.gradle.plugin.muzzle.mainSourceSet +import org.gradle.api.file.ConfigurableFileCollection import org.gradle.api.model.ObjectFactory -import org.gradle.api.provider.ProviderFactory import org.gradle.api.tasks.CacheableTask import org.gradle.api.tasks.Classpath import org.gradle.api.tasks.InputFiles @@ -17,7 +16,6 @@ import javax.inject.Inject @CacheableTask abstract class MuzzleGetReferencesTask @Inject constructor( - providers: ProviderFactory, objects: ObjectFactory, ) : AbstractMuzzleTask() { @@ -31,7 +29,7 @@ abstract class MuzzleGetReferencesTask @Inject constructor( @get:InputFiles @get:Classpath - val classpath = providers.provider { project.mainSourceSet.runtimeClasspath } + val classpath: ConfigurableFileCollection = objects.fileCollection() // This output is only used to make the task cacheable, this is not exposed @get:OutputFile @@ -41,7 +39,7 @@ abstract class MuzzleGetReferencesTask @Inject constructor( @TaskAction fun printMuzzle() { - val cl = URLClassLoader(classpath.get().map { it.toURI().toURL() }.toTypedArray(), null) + val cl = URLClassLoader(classpath.map { it.toURI().toURL() }.toTypedArray(), null) val printMethod: Method = cl.loadClass("datadog.trace.agent.tooling.muzzle.MuzzleVersionScanPlugin") .getMethod( "printMuzzleReferences", diff --git a/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/tasks/MuzzleTask.kt b/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/tasks/MuzzleTask.kt index 926b8abed43..093d3c13df7 100644 --- a/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/tasks/MuzzleTask.kt +++ b/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/tasks/MuzzleTask.kt @@ -3,12 +3,7 @@ package datadog.gradle.plugin.muzzle.tasks import datadog.gradle.plugin.HostPlatform import datadog.gradle.plugin.muzzle.MuzzleAction import datadog.gradle.plugin.muzzle.MuzzleDirective -import datadog.gradle.plugin.muzzle.MuzzleExtension -import datadog.gradle.plugin.muzzle.allMainSourceSet -import org.gradle.api.Project -import org.gradle.api.artifacts.Configuration import org.gradle.api.file.ConfigurableFileCollection -import org.gradle.api.file.FileCollection import org.gradle.api.file.RegularFileProperty import org.gradle.api.invocation.BuildInvocationDetails import org.gradle.api.model.ObjectFactory @@ -25,7 +20,6 @@ import org.gradle.api.tasks.TaskAction import org.gradle.jvm.toolchain.JavaLanguageVersion import org.gradle.jvm.toolchain.JavaLauncher import org.gradle.jvm.toolchain.JavaToolchainService -import org.gradle.kotlin.dsl.getByType import org.gradle.kotlin.dsl.property import org.gradle.workers.WorkerExecutor import javax.inject.Inject @@ -52,15 +46,15 @@ abstract class MuzzleTask @Inject constructor( @get:InputFiles @get:Classpath - abstract val muzzleBootstrap: Property + abstract val muzzleBootstrap: ConfigurableFileCollection @get:InputFiles @get:Classpath - abstract val muzzleTooling: Property + abstract val muzzleTooling: ConfigurableFileCollection @get:InputFiles @get:Classpath - protected val agentClassPath = providers.provider { createAgentClassPath(project) } + val agentClassPath: ConfigurableFileCollection = objects.fileCollection() @get:InputFiles @get:Classpath @@ -68,7 +62,10 @@ abstract class MuzzleTask @Inject constructor( @get:InputFiles @get:Classpath - protected val muzzleClassPath = providers.provider { createMuzzleClassPath(project, name) } + val muzzleClassPath: ConfigurableFileCollection = objects.fileCollection() + + @get:Input + val checkCompileTimeDependencies: Property = objects.property().convention(true) @get:Input @get:Optional @@ -127,17 +124,18 @@ abstract class MuzzleTask @Inject constructor( @TaskAction fun muzzle() { when { - // Version-specific task: created by MuzzlePlugin for each resolved artifact. - muzzleDirective.isPresent -> { - assertMuzzle(muzzleDirective.get()) - } - // Fallback for the root "muzzle" lifecycle task when no pass{} directives are - // declared. In that case there are no version-specific pass tasks, so we assert - // the instrumentation against its own compile-time classpath as a basic sanity check. - !project.extensions.getByType().directives.any { it.assertPass } -> { - project.logger.info("No muzzle pass directives configured. Asserting pass against instrumentation compile-time dependencies") - assertMuzzle() - } + // Version-specific task: created by MuzzlePlugin for each resolved artifact. + muzzleDirective.isPresent -> { + assertMuzzle(muzzleDirective.get()) + } + + // Fallback for the root "muzzle" lifecycle task when no pass{} directives are + // declared. In that case there are no version-specific pass tasks, so we assert + // the instrumentation against its own compile-time classpath as a basic sanity check. + checkCompileTimeDependencies.get() -> { + logger.info("No muzzle pass directives configured. Asserting pass against instrumentation compile-time dependencies") + assertMuzzle() + } } } @@ -150,7 +148,7 @@ abstract class MuzzleTask @Inject constructor( workerExecutor.processIsolation { forkOptions { // datadog.trace.agent.tooling.muzzle.MuzzleVersionScanPlugin needs reflective access to ClassLoader.findLoadedClass - if(launcher.metadata.languageVersion > JavaLanguageVersion.of(9)) { + if (launcher.metadata.languageVersion > JavaLanguageVersion.of(9)) { jvmArgs("--add-opens=java.base/java.lang=ALL-UNNAMED") } if (HostPlatform.isLinuxArm64()) { @@ -167,8 +165,8 @@ abstract class MuzzleTask @Inject constructor( buildStartedTime.set(invocationDetails.buildStartedTime) bootstrapClassPath.setFrom(muzzleBootstrap) toolingClassPath.setFrom(muzzleTooling) - instrumentationClassPath.setFrom(agentClassPath.get(), extraAgentClasspath) - testApplicationClassPath.setFrom(muzzleClassPath.get()) + instrumentationClassPath.setFrom(agentClassPath, extraAgentClasspath) + testApplicationClassPath.setFrom(muzzleClassPath) if (muzzleDirective != null) { assertPass.set(muzzleDirective.assertPass) this.muzzleDirective.set(muzzleDirective.name ?: muzzleDirective.module) @@ -178,30 +176,4 @@ abstract class MuzzleTask @Inject constructor( resultFile.set(result) } } - - private fun createAgentClassPath(project: Project): FileCollection { - project.logger.info("Creating agent classpath for $project") - val cp = project.files() - cp.from(project.allMainSourceSet.map { it.runtimeClasspath }) - - if (project.logger.isInfoEnabled) { - cp.forEach { project.logger.info("-- $it") } - } - return cp - } - - private fun createMuzzleClassPath(project: Project, muzzleTaskName: String): FileCollection { - project.logger.info("Creating muzzle classpath for $muzzleTaskName") - val cp = project.files() - val config = if (muzzleTaskName == "muzzle") { - project.configurations.named("compileClasspath").get() - } else { - project.configurations.named(muzzleTaskName).get() - } - cp.from(config) - if (project.logger.isInfoEnabled) { - cp.forEach { project.logger.info("-- $it") } - } - return cp - } } diff --git a/buildSrc/src/main/kotlin/dd-trace-java.ci-jobs.gradle.kts b/buildSrc/src/main/kotlin/dd-trace-java.ci-jobs.gradle.kts index 4868f4f769b..8dded4f95ad 100644 --- a/buildSrc/src/main/kotlin/dd-trace-java.ci-jobs.gradle.kts +++ b/buildSrc/src/main/kotlin/dd-trace-java.ci-jobs.gradle.kts @@ -1,4 +1,5 @@ import datadog.gradle.plugin.ci.isInSelectedSlot +import datadog.gradle.plugin.muzzle.MuzzleExtension import org.gradle.api.tasks.testing.Test import java.io.File @@ -15,6 +16,12 @@ if (project != rootProject) { } allprojects { + pluginManager.withPlugin("dd-trace-java.muzzle") { + extensions.configure { + includeInAggregate.convention(project.isInSelectedSlot) + } + } + // Enable tests only on the selected slot (if -Pslot=n/t is provided) tasks.withType().configureEach { onlyIf("Project is in selected slot") { @@ -124,9 +131,9 @@ if (gitBaseRefProvider.isPresent) { tasks.register("runMuzzle") { val muzzleSubprojects = subprojects.filter { p -> - p.isInSelectedSlot.get() - && p.plugins.hasPlugin("java") - && p.plugins.hasPlugin("dd-trace-java.muzzle") + p.plugins.hasPlugin("java") && + p.plugins.hasPlugin("dd-trace-java.muzzle") && + p.extensions.getByType().includeInAggregate.get() } dependsOn(muzzleSubprojects.map { p -> "${p.path}:muzzle" }) } diff --git a/buildSrc/src/test/kotlin/datadog/gradle/plugin/muzzle/MuzzlePluginFunctionalTest.kt b/buildSrc/src/test/kotlin/datadog/gradle/plugin/muzzle/MuzzlePluginFunctionalTest.kt index 2e0e5ebe213..4a20e0185e9 100644 --- a/buildSrc/src/test/kotlin/datadog/gradle/plugin/muzzle/MuzzlePluginFunctionalTest.kt +++ b/buildSrc/src/test/kotlin/datadog/gradle/plugin/muzzle/MuzzlePluginFunctionalTest.kt @@ -8,6 +8,114 @@ import org.junit.jupiter.params.provider.ValueSource import kotlin.io.path.readText class MuzzlePluginFunctionalTest : MuzzlePluginTestFixture() { + @Test + fun `nested aggregate plans descendant instrumentation checks`() { + writeProject( + """ + plugins { + id("java") + id("dd-trace-java.muzzle") + } + muzzle { pass { coreJdk() } } + """ + ) + writeNoopScanPlugin() + writeFile("dd-java-agent/build.gradle.kts", + """ + tasks.register("runMuzzle") { dependsOn(":dd-java-agent:instrumentation:demo:muzzle") } + """ + ) + val result = run(":dd-java-agent:runMuzzle") + assertThat(result.output).contains("BUILD SUCCESSFUL") + assertThat(result.task(":dd-java-agent:instrumentation:demo:muzzle-AssertPass-core-jdk")?.outcome).isEqualTo(SUCCESS) + } + + @Test + fun `planning sees directives configured after project evaluation`() { + writeProject( + """ + plugins { + id("java") + id("dd-trace-java.muzzle") + } + """ + ) + writeNoopScanPlugin() + writeRootProject( + """ + plugins { id("dd-trace-java.muzzle") apply false } + gradle.projectsEvaluated { + project(":dd-java-agent:instrumentation:demo").extensions.configure { + pass { coreJdk() } + } + } + """ + ) + val result = run(":dd-java-agent:instrumentation:demo:muzzle") + assertThat(result.output).contains("BUILD SUCCESSFUL") + assertThat(result.task(":dd-java-agent:instrumentation:demo:muzzle-AssertPass-core-jdk")?.outcome).isEqualTo(SUCCESS) + } + + @Test + fun `cached graph is reused and replanned when a version is published`() { + val repository = createMavenRepoFixture() + repository.publishVersions("com.example.test", "cache-lib", listOf("1.0.0")) + writeProject( + """ + plugins { + id("java") + id("dd-trace-java.muzzle") + } + repositories { maven { url = uri("${repository.repoUrl}") } } + muzzle { + pass { + group = "com.example.test" + module = "cache-lib" + versions = "[1.0.0,2.0.0)" + } + } + """ + ) + writeNoopScanPlugin() + val arguments = arrayOf(":dd-java-agent:instrumentation:demo:muzzle", "--configuration-cache", "--stacktrace") + val environment = mapOf("MAVEN_REPOSITORY_PROXY" to repository.repoUrl) + val first = run(*arguments, env = environment) + assertThat(first.output).contains("BUILD SUCCESSFUL", "Configuration cache entry stored") + val second = run(*arguments, env = environment) + assertThat(second.output).contains("BUILD SUCCESSFUL", "Reusing configuration cache") + repository.publishVersions("com.example.test", "cache-lib", listOf("1.1.0")) + val third = run(*arguments, env = environment) + assertThat(third.output).contains("BUILD SUCCESSFUL").doesNotContain("Reusing configuration cache") + assertThat(third.task(":dd-java-agent:instrumentation:demo:muzzle-AssertPass-com.example.test-cache-lib-1.1.0")?.outcome).isEqualTo(SUCCESS) + } + + @Test + fun `late main source sets are compiled and available to muzzle workers`() { + writeProject( + """ + plugins { + id("java") + id("dd-trace-java.muzzle") + } + muzzle { pass { coreJdk() } } + tasks.named("muzzle").get() + afterEvaluate { sourceSets.create("main_extra") } + """ + ) + writeFile("dd-java-agent/instrumentation/demo/src/main_extra/resources/late.txt", "late source set") + writeScanPlugin( + """ + if (instrumentationClassLoader.getResource("late.txt") == null) { + throw new IllegalStateException("Missing late main source set"); + } + """ + ) + val result = run(":dd-java-agent:instrumentation:demo:muzzle") + assertThat(result.output).contains("BUILD SUCCESSFUL") + assertThat(result.task(":dd-java-agent:instrumentation:demo:processMain_extraResources")?.outcome).isEqualTo(SUCCESS) + assertThat(result.task(":dd-java-agent:instrumentation:demo:muzzle-AssertPass-core-jdk")?.outcome).isEqualTo(SUCCESS) + } + @ParameterizedTest @ValueSource(strings = ["muzzle", ":dd-java-agent:instrumentation:demo:muzzle", "runMuzzle"]) fun `detects muzzle invocation with various task names`(taskName: String) { diff --git a/buildSrc/src/test/kotlin/datadog/gradle/plugin/muzzle/MuzzlePluginPerformanceTest.kt b/buildSrc/src/test/kotlin/datadog/gradle/plugin/muzzle/MuzzlePluginPerformanceTest.kt index 2e43b0b3d38..b23dc815983 100644 --- a/buildSrc/src/test/kotlin/datadog/gradle/plugin/muzzle/MuzzlePluginPerformanceTest.kt +++ b/buildSrc/src/test/kotlin/datadog/gradle/plugin/muzzle/MuzzlePluginPerformanceTest.kt @@ -7,6 +7,68 @@ import org.assertj.core.api.Assertions.assertThat class MuzzlePluginPerformanceTest : MuzzlePluginTestFixture() { + @Test + fun `compileMuzzle does not resolve version directives or schedule checks`() { + writeProject( + """ + plugins { + id("java") + id("dd-trace-java.muzzle") + } + muzzle { + pass { + group = "does.not.exist" + module = "must-not-resolve" + versions = "[1.0,2.0)" + } + } + """ + ) + val result = run(":dd-java-agent:instrumentation:demo:compileMuzzle", "--offline", "--info") + assertThat(result.output).contains("BUILD SUCCESSFUL") + assertThat(result.tasks).noneMatch { it.path.contains("muzzle-Assert") || it.path.endsWith("muzzle-end") } + assertThat(result.task(":dd-java-agent:instrumentation:demo:compileMuzzle")?.outcome).isEqualTo(UP_TO_DATE) + assertThat(result.output).contains("skipping muzzle task planification") + } + + @Test + fun `aggregate skips excluded plans but explicit muzzle still checks them`() { + writeProject( + """ + plugins { + id("java") + id("dd-trace-java.muzzle") + } + muzzle { pass { coreJdk() } } + """ + ) + writeNoopScanPlugin() + addSubproject("dd-java-agent:instrumentation:other", + """ + plugins { + id("java") + id("dd-trace-java.muzzle") + } + muzzle { + includeInAggregate.set(false) + pass { coreJdk() } + } + """ + ) + writeRootProject( + """ + tasks.register("runMuzzle") { dependsOn(":dd-java-agent:instrumentation:demo:muzzle") } + """ + ) + val aggregate = run("runMuzzle", "--info") + assertThat(aggregate.output).contains("BUILD SUCCESSFUL") + assertThat(aggregate.output).contains("No muzzle tasks invoked for :dd-java-agent:instrumentation:other") + assertThat(aggregate.task(":dd-java-agent:instrumentation:demo:muzzle-AssertPass-core-jdk")?.outcome).isEqualTo(SUCCESS) + val explicit = run(":dd-java-agent:instrumentation:other:muzzle") + assertThat(explicit.output).contains("BUILD SUCCESSFUL") + assertThat(explicit.task(":dd-java-agent:instrumentation:other:muzzle-AssertPass-core-jdk")?.outcome).isEqualTo(SUCCESS) + } + @Test fun `task graph does not include muzzle tasks when not requested`() { writeProject( diff --git a/buildSrc/src/test/kotlin/datadog/gradle/plugin/muzzle/MuzzleVersionUtilsTest.kt b/buildSrc/src/test/kotlin/datadog/gradle/plugin/muzzle/MuzzleVersionUtilsTest.kt index 9bc298a40f5..fbf887c4007 100644 --- a/buildSrc/src/test/kotlin/datadog/gradle/plugin/muzzle/MuzzleVersionUtilsTest.kt +++ b/buildSrc/src/test/kotlin/datadog/gradle/plugin/muzzle/MuzzleVersionUtilsTest.kt @@ -11,11 +11,24 @@ import org.junit.jupiter.params.provider.Arguments import org.junit.jupiter.params.provider.MethodSource import org.junit.jupiter.params.provider.ValueSource import org.assertj.core.api.Assertions.assertThat +import kotlin.random.Random class MuzzleVersionUtilsTest { private val versionScheme = GenericVersionScheme() + @Test + fun `sampling seed reproduces large ranges while preserving extrema`() { + val versions = (0..30).flatMap { minor -> listOf("1.$minor.0", "1.$minor.1") } + val result = createVersionRangeResult(*versions.toTypedArray()) + val first = MuzzleVersionUtils.filterAndLimitVersions(result, emptySet(), false, Random(42)) + val replay = MuzzleVersionUtils.filterAndLimitVersions(result, emptySet(), false, Random(42)) + val nextBuild = MuzzleVersionUtils.filterAndLimitVersions(result, emptySet(), false, Random(43)) + assertThat(replay).containsExactlyElementsOf(first) + assertThat(first).contains(result.lowestVersion, result.highestVersion).hasSize(RANGE_COUNT_LIMIT - 1) + assertThat(nextBuild).contains(result.lowestVersion, result.highestVersion).isNotEqualTo(first) + } + @ParameterizedTest(name = "[{index}] filters pre-release: {0}") @ValueSource( strings = From 84c5a27e899436abf5d76bed8bb38b9f91c83f98 Mon Sep 17 00:00:00 2001 From: Sarah Chen Date: Wed, 7 Oct 2026 16:57:10 -0400 Subject: [PATCH 2/2] Simplify --- .../plugin/muzzle/planner/MuzzlePlansValueSource.kt | 2 +- .../gradle/plugin/muzzle/planner/MuzzleTaskPlanner.kt | 7 ------- .../datadog/gradle/plugin/muzzle/tasks/MuzzleEndTask.kt | 9 +++++---- 3 files changed, 6 insertions(+), 12 deletions(-) diff --git a/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/planner/MuzzlePlansValueSource.kt b/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/planner/MuzzlePlansValueSource.kt index c024d3baa85..9c7d2d12a59 100644 --- a/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/planner/MuzzlePlansValueSource.kt +++ b/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/planner/MuzzlePlansValueSource.kt @@ -28,8 +28,8 @@ internal abstract class MuzzlePlansValueSource : val session = MuzzleMavenRepoUtils.newRepositorySystemSession(system) val random = Random(parameters.samplingSeed.get()) val requests = parameters.requests.get() + val planner = MuzzleTaskPlanner(MavenMuzzleResolutionService(system, session, random = random)) val plans = requests.flatMap { request -> - val planner = MuzzleTaskPlanner(MavenMuzzleResolutionService(system, session, random = random)) request.directives.flatMapIndexed { index, directive -> planner.plan(listOf(directive)).map { plan -> MuzzlePlannedVersion(request.projectPath, index, plan.artifact?.version, plan.directive.assertPass) diff --git a/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/planner/MuzzleTaskPlanner.kt b/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/planner/MuzzleTaskPlanner.kt index 107fbf2f2d9..9b08612faef 100644 --- a/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/planner/MuzzleTaskPlanner.kt +++ b/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/planner/MuzzleTaskPlanner.kt @@ -1,8 +1,6 @@ package datadog.gradle.plugin.muzzle.planner import datadog.gradle.plugin.muzzle.MuzzleDirective -import org.eclipse.aether.RepositorySystem -import org.eclipse.aether.RepositorySystemSession /** * Expands configured directives into ordered task plans. @@ -10,11 +8,6 @@ import org.eclipse.aether.RepositorySystemSession internal class MuzzleTaskPlanner( private val resolutionService: MuzzleResolutionService, ) { - companion object { - fun from(system: RepositorySystem, session: RepositorySystemSession): MuzzleTaskPlanner = - MuzzleTaskPlanner(MavenMuzzleResolutionService(system, session)) - } - /** * Expands declared muzzle directives into executable task plans. * diff --git a/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/tasks/MuzzleEndTask.kt b/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/tasks/MuzzleEndTask.kt index a7f4a6f28a7..47015705574 100644 --- a/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/tasks/MuzzleEndTask.kt +++ b/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/tasks/MuzzleEndTask.kt @@ -63,8 +63,9 @@ abstract class MuzzleEndTask : AbstractMuzzleTask() { .sortedBy { it.name } .map { resultFile -> val taskName = resultFile.name.removeSuffix(".txt") - when { - !resultFile.exists() -> { + val result = if (resultFile.exists()) resultFile.readText() else null + when (result) { + null -> { MuzzleJUnitCase( name = taskName, failureMessage = "Muzzle result file missing", @@ -72,13 +73,13 @@ abstract class MuzzleEndTask : AbstractMuzzleTask() { ) } - resultFile.readText() == "PASSING" -> MuzzleJUnitCase(name = taskName) + "PASSING" -> MuzzleJUnitCase(name = taskName) else -> { MuzzleJUnitCase( name = taskName, failureMessage = "Muzzle validation failed", - failureText = resultFile.readText() + failureText = result ) } }