Skip to content

Commit 17df995

Browse files
committed
Muzzle improvements
1 parent 77a7371 commit 17df995

14 files changed

Lines changed: 442 additions & 152 deletions

‎buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/MuzzleDirective.kt‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,16 @@ open class MuzzleDirective : Serializable {
8383
}
8484
}
8585

86+
internal fun inverse(version: String): MuzzleDirective = MuzzleDirective().apply {
87+
name = this@MuzzleDirective.name
88+
group = this@MuzzleDirective.group
89+
module = this@MuzzleDirective.module
90+
versions = version
91+
assertPass = !this@MuzzleDirective.assertPass
92+
excludedDependencies = this@MuzzleDirective.excludedDependencies
93+
includeSnapshots = this@MuzzleDirective.includeSnapshots
94+
}
95+
8696
/**
8797
* Slug of directive name.
8898
*

‎buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/MuzzleExtension.kt‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,17 @@ package datadog.gradle.plugin.muzzle
33
import org.gradle.api.Action
44
import org.gradle.api.model.ObjectFactory
55
import org.gradle.kotlin.dsl.newInstance
6+
import org.gradle.kotlin.dsl.property
67
import java.util.Locale
78
import javax.inject.Inject
89

910
/**
1011
* Muzzle extension containing all pass and fail directives.
1112
*/
1213
abstract class MuzzleExtension @Inject constructor(private val objectFactory: ObjectFactory) {
14+
/** Whether aggregate runMuzzle invocations should plan checks for this project. */
15+
val includeInAggregate = objectFactory.property<Boolean>().convention(true)
16+
1317
val directives: MutableList<MuzzleDirective> = ArrayList()
1418
private val additionalRepositories: MutableList<Triple<String, String, String>> = ArrayList()
1519

‎buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/MuzzleMavenRepoUtils.kt‎

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import org.eclipse.aether.version.Version
2222
import org.gradle.api.GradleException
2323
import org.gradle.api.logging.Logging
2424
import java.nio.file.Files
25+
import kotlin.random.Random
2526

2627
internal object MuzzleMavenRepoUtils {
2728
private val log = Logging.getLogger(MuzzleMavenRepoUtils::class.java)
@@ -82,7 +83,8 @@ internal object MuzzleMavenRepoUtils {
8283
muzzleDirective: MuzzleDirective,
8384
system: RepositorySystem,
8485
session: RepositorySystemSession,
85-
defaultRepos: List<RemoteRepository> = defaultMuzzleRepos()
86+
defaultRepos: List<RemoteRepository> = defaultMuzzleRepos(),
87+
random: Random = Random.Default
8688
): Set<MuzzleDirective> {
8789
val allVersionsArtifact = DefaultArtifact(
8890
muzzleDirective.group,
@@ -114,17 +116,10 @@ internal object MuzzleMavenRepoUtils {
114116
return MuzzleVersionUtils.filterAndLimitVersions(
115117
allRangeResult,
116118
muzzleDirective.skipVersions,
117-
muzzleDirective.includeSnapshots
119+
muzzleDirective.includeSnapshots,
120+
random
118121
).map { version ->
119-
MuzzleDirective().apply {
120-
name = muzzleDirective.name
121-
group = muzzleDirective.group
122-
module = muzzleDirective.module
123-
versions = version.toString()
124-
assertPass = !muzzleDirective.assertPass
125-
excludedDependencies = muzzleDirective.excludedDependencies
126-
includeSnapshots = muzzleDirective.includeSnapshots
127-
}
122+
muzzleDirective.inverse(version.toString())
128123
}.toSet()
129124
}
130125

@@ -373,12 +368,14 @@ internal object MuzzleMavenRepoUtils {
373368
*/
374369
fun muzzleDirectiveToArtifacts(
375370
muzzleDirective: MuzzleDirective,
376-
rangeResult: VersionRangeResult
371+
rangeResult: VersionRangeResult,
372+
random: Random = Random.Default
377373
): Set<Artifact> {
378374
val versions = MuzzleVersionUtils.filterAndLimitVersions(
379375
rangeResult,
380376
muzzleDirective.skipVersions,
381-
muzzleDirective.includeSnapshots
377+
muzzleDirective.includeSnapshots,
378+
random
382379
)
383380
val allVersionArtifacts = versions.map { version ->
384381
DefaultArtifact(

‎buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/MuzzlePlugin.kt‎

Lines changed: 92 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
package datadog.gradle.plugin.muzzle
22

3+
import datadog.gradle.plugin.muzzle.planner.MuzzlePlannedVersion
4+
import datadog.gradle.plugin.muzzle.planner.MuzzlePlanningRequest
5+
import datadog.gradle.plugin.muzzle.planner.MuzzlePlansValueSource
36
import datadog.gradle.plugin.muzzle.tasks.MuzzleEndTask
47
import datadog.gradle.plugin.muzzle.tasks.MuzzleGenerateReportTask
58
import datadog.gradle.plugin.muzzle.tasks.MuzzleGetReferencesTask
69
import datadog.gradle.plugin.muzzle.tasks.MuzzleTask
7-
import datadog.gradle.plugin.muzzle.planner.MuzzleTaskPlanner
10+
import kotlin.random.Random
811
import org.eclipse.aether.artifact.Artifact
12+
import org.eclipse.aether.artifact.DefaultArtifact
913
import org.gradle.api.NamedDomainObjectProvider
1014
import org.gradle.api.Plugin
1115
import org.gradle.api.Project
@@ -14,8 +18,10 @@ import org.gradle.api.tasks.TaskProvider
1418
import org.gradle.kotlin.dsl.create
1519
import org.gradle.kotlin.dsl.exclude
1620
import org.gradle.kotlin.dsl.getByType
21+
import org.gradle.kotlin.dsl.named
1722
import org.gradle.kotlin.dsl.project
1823
import org.gradle.kotlin.dsl.register
24+
import org.gradle.kotlin.dsl.withType
1925

2026
/**
2127
* muzzle task plugin which runs muzzle validation against a range of dependencies.
@@ -67,6 +73,7 @@ class MuzzlePlugin : Plugin<Project> {
6773

6874
// compileMuzzle compiles all projects required to run muzzle validation.
6975
// Not adding group and description to keep this task from showing in `gradle tasks`.
76+
val extension = project.extensions.getByType<MuzzleExtension>()
7077
val compileMuzzle = project.tasks.register("compileMuzzle") {
7178
inputs.files(project.providers.provider { project.allMainSourceSet.map { it.output } })
7279
dependsOn(bootstrapProject.tasks.named("compileJava"))
@@ -76,23 +83,29 @@ class MuzzlePlugin : Plugin<Project> {
7683

7784
val muzzleTask = project.tasks.register<MuzzleTask>("muzzle") {
7885
description = "Check instrumentation compatibility against the configured dependency versions"
79-
this.muzzleBootstrap.set(muzzleBootstrap)
80-
this.muzzleTooling.set(muzzleTooling)
86+
this.muzzleBootstrap.from(muzzleBootstrap)
87+
this.muzzleTooling.from(muzzleTooling)
8188
dependsOn(compileMuzzle)
8289
}
8390

91+
project.tasks.withType<MuzzleTask>().configureEach {
92+
agentClassPath.from(project.providers.provider { project.allMainSourceSet.map { it.runtimeClasspath } })
93+
muzzleClassPath.from(project.configurations.named(if (name == "muzzle") "compileClasspath" else name))
94+
}
95+
8496
project.tasks.register<MuzzleGetReferencesTask>("printReferences") {
8597
dependsOn(compileMuzzle)
98+
classpath.from(project.mainSourceSet.runtimeClasspath)
8699
}.also {
100+
val referenceOutput = it.flatMap { task -> task.outputFile }
87101
val printReferencesTask = project.tasks.register("actuallyPrintReferences") {
88102
doLast {
89-
println(it.get().outputFile.get().asFile.readText())
103+
println(referenceOutput.get().asFile.readText())
90104
}
91105
}
92106
it.configure { finalizedBy(printReferencesTask) }
93107
}
94108

95-
val extension = project.extensions.getByType<MuzzleExtension>()
96109
val runtimeClasspath = project.mainSourceSet.runtimeClasspath
97110
val report = project.tasks.register<MuzzleGenerateReportTask>("generateMuzzleReport") {
98111
reportDirectives.convention(project.providers.provider { extension.directives })
@@ -101,54 +114,86 @@ class MuzzlePlugin : Plugin<Project> {
101114
}
102115
project.publishMuzzleReport(report.flatMap { it.versionsFile })
103116

104-
val hasRelevantTask = project.gradle.startParameter.taskNames.any { taskName ->
105-
val taskProjectPath = taskName.substringBeforeLast(":", "")
106-
val taskNameOnly = taskName.substringAfterLast(":")
107-
val isRelevantForProject = taskProjectPath.isEmpty() || taskProjectPath == project.path
117+
registerPlanning(project)
118+
}
108119

109-
isRelevantForProject && taskNameOnly.endsWith("muzzle", ignoreCase = true)
110-
}
111-
if (!hasRelevantTask) {
112-
// Adding muzzle dependencies has a large config overhead. Stop unless muzzle is explicitly run.
113-
project.logger.info("No muzzle tasks invoked for ${project.path}, skipping muzzle task planification")
114-
return
115-
}
120+
private fun hasRelevantTask(project: Project): Boolean = project.gradle.startParameter.taskNames.any { taskName ->
121+
val taskProjectPath = taskName.substringBeforeLast(":", "")
122+
val taskNameOnly = taskName.substringAfterLast(":")
123+
val isAggregate = taskNameOnly.equals("runMuzzle", ignoreCase = true)
124+
val isRelevantForProject = taskProjectPath.isEmpty() || taskProjectPath == project.path ||
125+
(isAggregate && project.path.startsWith("$taskProjectPath:"))
126+
isRelevantForProject && !taskNameOnly.equals("compileMuzzle", ignoreCase = true) &&
127+
taskNameOnly.endsWith("muzzle", ignoreCase = true) &&
128+
(!isAggregate || project.extensions.getByType<MuzzleExtension>().includeInAggregate.get())
129+
}
116130

117-
// We only get here if we are running muzzle, so let's start timing things
118-
val startTime = System.currentTimeMillis()
119-
120-
val system = MuzzleMavenRepoUtils.newRepositorySystem()
121-
val session = MuzzleMavenRepoUtils.newRepositorySystemSession(system)
122-
val taskPlanner = MuzzleTaskPlanner.from(system, session)
123-
project.afterEvaluate {
124-
// use runAfter to set up task finalizers in version order
125-
var runAfter: TaskProvider<MuzzleTask> = muzzleTask
126-
val muzzleReportTasks = mutableListOf<TaskProvider<MuzzleTask>>()
127-
val directives = project.extensions.getByType<MuzzleExtension>().directives
128-
taskPlanner.plan(directives).forEach { plan ->
129-
runAfter = registerMuzzleTask(plan.directive, plan.artifact, project, runAfter, muzzleBootstrap, muzzleTooling)
130-
muzzleReportTasks.add(runAfter)
131-
project.logger.info("configured ${plan.directive}")
131+
private fun registerPlanning(project: Project) {
132+
val root = project.rootProject
133+
val marker = "datadogMuzzlePlanningRegistered"
134+
if (root.extensions.extraProperties.has(marker)) return
135+
root.extensions.extraProperties.set(marker, true)
136+
project.gradle.projectsEvaluated {
137+
val projects = root.allprojects.filter {
138+
it.plugins.hasPlugin("dd-trace-java.muzzle") && it.plugins.hasPlugin("java")
139+
}.filter {
140+
val extension = it.extensions.getByType<MuzzleExtension>()
141+
it.tasks.named<MuzzleTask>("muzzle").configure {
142+
checkCompileTimeDependencies.set(!extension.directives.any { it.assertPass })
143+
}
144+
val relevant = hasRelevantTask(it)
145+
if (!relevant) {
146+
it.logger.info("No muzzle tasks invoked for ${it.path}, skipping muzzle task planification")
147+
}
148+
relevant
132149
}
133-
134-
if (muzzleReportTasks.isEmpty() && !directives.any { it.assertPass }) {
135-
muzzleReportTasks.add(muzzleTask)
150+
if (projects.isEmpty()) return@projectsEvaluated
151+
val requests = projects.map {
152+
MuzzlePlanningRequest(it.path, it.extensions.getByType<MuzzleExtension>().directives.toList())
153+
}
154+
// Revalidate selected coordinates when loading a cached task graph.
155+
val plans = root.providers.of(MuzzlePlansValueSource::class.java) {
156+
parameters.requests.set(requests)
157+
parameters.samplingSeed.set(Random.nextLong())
158+
}.get().groupBy { it.projectPath }
159+
projects.forEachIndexed { index, instrumentation ->
160+
configureMuzzlePlan(instrumentation, requests[index].directives, plans[instrumentation.path].orEmpty())
136161
}
162+
}
163+
}
137164

138-
val timingTask = project.tasks.register<MuzzleEndTask>("muzzle-end") {
139-
startTimeMs.set(startTime)
140-
sourceFile.set(
141-
project.projectDir
142-
.relativeTo(project.rootProject.projectDir)
143-
.invariantSeparatorsPath
144-
)
145-
muzzleResultFiles.from(muzzleReportTasks.map { it.flatMap { task -> task.result } })
165+
private fun configureMuzzlePlan(
166+
project: Project,
167+
directives: List<MuzzleDirective>,
168+
plans: List<MuzzlePlannedVersion>
169+
) {
170+
val muzzleTask = project.tasks.named<MuzzleTask>("muzzle")
171+
val muzzleBootstrap = project.configurations.named("muzzleBootstrap")
172+
val muzzleTooling = project.configurations.named("muzzleTooling")
173+
var runAfter = muzzleTask
174+
val muzzleReportTasks = mutableListOf<TaskProvider<MuzzleTask>>()
175+
plans.forEach { plan ->
176+
val original = directives[plan.directiveIndex]
177+
val directive = if (plan.assertPass == original.assertPass) {
178+
original
179+
} else {
180+
original.inverse(requireNotNull(plan.version))
146181
}
147-
// last muzzle task to run
148-
runAfter.configure {
149-
finalizedBy(timingTask)
182+
val artifact = plan.version?.let {
183+
DefaultArtifact(directive.group, directive.module, directive.classifier ?: "", "jar", it)
150184
}
185+
runAfter = registerMuzzleTask(directive, artifact, project, runAfter, muzzleBootstrap, muzzleTooling)
186+
muzzleReportTasks.add(runAfter)
187+
project.logger.info("configured $directive")
188+
}
189+
if (muzzleReportTasks.isEmpty() && !directives.any { it.assertPass }) {
190+
muzzleReportTasks.add(muzzleTask)
191+
}
192+
val timingTask = project.tasks.register<MuzzleEndTask>("muzzle-end") {
193+
sourceFile.set(project.projectDir.relativeTo(project.rootProject.projectDir).invariantSeparatorsPath)
194+
muzzleResultFiles.from(muzzleReportTasks.map { it.flatMap { task -> task.result } })
151195
}
196+
runAfter.configure { finalizedBy(timingTask) }
152197
}
153198

154199
companion object {
@@ -234,8 +279,8 @@ class MuzzlePlugin : Plugin<Project> {
234279

235280
val muzzleTask = instrumentationProject.tasks.register<MuzzleTask>(muzzleTaskName) {
236281
this.muzzleDirective.set(muzzleDirective)
237-
this.muzzleBootstrap.set(muzzleBootstrap)
238-
this.muzzleTooling.set(muzzleTooling)
282+
this.muzzleBootstrap.from(muzzleBootstrap)
283+
this.muzzleTooling.from(muzzleTooling)
239284
}
240285

241286
runAfterTask.configure {

‎buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/MuzzleVersionUtils.kt‎

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package datadog.gradle.plugin.muzzle
33
import org.eclipse.aether.resolution.VersionRangeResult
44
import org.eclipse.aether.version.Version
55
import java.util.Locale
6+
import kotlin.random.Random
67

78
internal object MuzzleVersionUtils {
89
private val END_NMN_PATTERN = Regex("^.*\\.[0-9]+[mM][0-9]+$")
@@ -19,10 +20,11 @@ internal object MuzzleVersionUtils {
1920
fun filterAndLimitVersions(
2021
result: VersionRangeResult,
2122
skipVersions: Set<String>,
22-
includeSnapshots: Boolean
23+
includeSnapshots: Boolean,
24+
random: Random = Random.Default
2325
): Set<Version> {
2426
val filtered = filterVersion(result.versions.toSet(), skipVersions, includeSnapshots)
25-
return limitLargeRanges(result, filtered, skipVersions)
27+
return limitLargeRanges(result, filtered, skipVersions, random)
2628
}
2729

2830
/**
@@ -81,15 +83,16 @@ internal object MuzzleVersionUtils {
8183
private fun limitLargeRanges(
8284
result: VersionRangeResult,
8385
versions: Set<Version>,
84-
skipVersions: Set<String>
86+
skipVersions: Set<String>,
87+
random: Random
8588
): Set<Version> {
8689
if (versions.size <= 1) return versions
8790
val beforeSize = versions.size
8891
val filteredVersions = versions.toMutableList().apply {
8992
removeAll { skipVersions.contains(it.toString()) }
9093
}
9194
val versionSet = VersionSet(filteredVersions)
92-
val shuffled = versionSet.lowAndHighForMajorMinor.shuffled().toMutableList()
95+
val shuffled = versionSet.lowAndHighForMajorMinor.shuffled(random).toMutableList()
9396
var afterSize = shuffled.size
9497
while (RANGE_COUNT_LIMIT <= afterSize) {
9598
val version = shuffled.removeAt(0)

‎buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/planner/MavenMuzzleResolutionService.kt‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,21 @@ import datadog.gradle.plugin.muzzle.MuzzleMavenRepoUtils
55
import org.eclipse.aether.RepositorySystem
66
import org.eclipse.aether.RepositorySystemSession
77
import org.eclipse.aether.artifact.Artifact
8+
import kotlin.random.Random
89

910
/**
1011
* Default [MuzzleResolutionService] implementation backed by Maven/Aether resolution.
1112
*/
1213
internal class MavenMuzzleResolutionService(
1314
private val system: RepositorySystem,
1415
private val session: RepositorySystemSession,
16+
private val random: Random = Random.Default,
1517
) : MuzzleResolutionService {
1618
override fun resolveArtifacts(directive: MuzzleDirective): Set<Artifact> {
1719
val range = MuzzleMavenRepoUtils.resolveVersionRange(directive, system, session)
18-
return MuzzleMavenRepoUtils.muzzleDirectiveToArtifacts(directive, range)
20+
return MuzzleMavenRepoUtils.muzzleDirectiveToArtifacts(directive, range, random)
1921
}
2022

2123
override fun inverseOf(directive: MuzzleDirective): Set<MuzzleDirective> =
22-
MuzzleMavenRepoUtils.inverseOf(directive, system, session)
24+
MuzzleMavenRepoUtils.inverseOf(directive, system, session, random = random)
2325
}

0 commit comments

Comments
 (0)