11package 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
36import datadog.gradle.plugin.muzzle.tasks.MuzzleEndTask
47import datadog.gradle.plugin.muzzle.tasks.MuzzleGenerateReportTask
58import datadog.gradle.plugin.muzzle.tasks.MuzzleGetReferencesTask
69import datadog.gradle.plugin.muzzle.tasks.MuzzleTask
7- import datadog.gradle.plugin.muzzle.planner.MuzzleTaskPlanner
10+ import kotlin.random.Random
811import org.eclipse.aether.artifact.Artifact
12+ import org.eclipse.aether.artifact.DefaultArtifact
913import org.gradle.api.NamedDomainObjectProvider
1014import org.gradle.api.Plugin
1115import org.gradle.api.Project
@@ -14,8 +18,10 @@ import org.gradle.api.tasks.TaskProvider
1418import org.gradle.kotlin.dsl.create
1519import org.gradle.kotlin.dsl.exclude
1620import org.gradle.kotlin.dsl.getByType
21+ import org.gradle.kotlin.dsl.named
1722import org.gradle.kotlin.dsl.project
1823import 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 {
0 commit comments