Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,17 @@ 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

/**
* 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<Boolean>().convention(true)

val directives: MutableList<MuzzleDirective> = ArrayList()
private val additionalRepositories: MutableList<Triple<String, String, String>> = ArrayList()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,14 +101,17 @@ class MuzzlePlugin : Plugin<Project> {
}
project.publishMuzzleReport(report.flatMap { it.versionsFile })

val hasRelevantTask = project.gradle.startParameter.taskNames.any { taskName ->
val relevantTasks = project.gradle.startParameter.taskNames.filter { taskName ->
val taskProjectPath = taskName.substringBeforeLast(":", "")
val taskNameOnly = taskName.substringAfterLast(":")
val isRelevantForProject = taskProjectPath.isEmpty() || taskProjectPath == project.path
val isAggregate = taskNameOnly.equals("runMuzzle", ignoreCase = true)
val isRelevantForProject = taskProjectPath.isEmpty() || taskProjectPath == project.path ||
(isAggregate && project.path.startsWith("$taskProjectPath:"))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Cover both absolute and relative paths in the nested-aggregate test; dd-java-agent:runMuzzle currently reports success without running the descendant checks.

In MuzzlePluginFunctionalTest, parameterize the existing test:

@ParameterizedTest
@ValueSource(strings = [":dd-java-agent:runMuzzle", "dd-java-agent:runMuzzle"])
fun `nested aggregate plans descendant instrumentation checks`(taskName: String) {

Use run(taskName) and retain the generated check's SUCCESS assertion. Here, the relative taskProjectPath cannot match the absolute project.path. This limitation predates the PR.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI, this block turns out to be useless and removed in #12831, so you can dismiss it.


isRelevantForProject && taskNameOnly.endsWith("muzzle", ignoreCase = true)
isRelevantForProject && !taskNameOnly.equals("compileMuzzle", ignoreCase = true) &&
taskNameOnly.endsWith("muzzle", ignoreCase = true)
}
if (!hasRelevantTask) {
if (relevantTasks.isEmpty()) {
// 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
Expand All @@ -117,10 +120,15 @@ class MuzzlePlugin : Plugin<Project> {
// 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 {
if (relevantTasks.all { it.substringAfterLast(":").equals("runMuzzle", ignoreCase = true) } &&

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: Hmm this still couples muzzle plugin to ci-jobs ("runMuzzle" is registered in ci-jobs).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I stumbled by chance to a feature of Gradle that allows to register and connect task very late, when Gradle assembled the task execution graph, which happens after project evaluation but before execution.

This looks like the exact thing needed. And as a byproduct (that I actually wanted for a while) it removes afterEvaluate. This even allows to get rid of the hasRelevantTasks / relevantTasks.

!extension.includeInAggregate.get()) {
project.logger.info("No muzzle tasks invoked for ${project.path}, skipping muzzle task planification")
return@afterEvaluate
}
val system = MuzzleMavenRepoUtils.newRepositorySystem()
val session = MuzzleMavenRepoUtils.newRepositorySystemSession(system)
val taskPlanner = MuzzleTaskPlanner.from(system, session)
// use runAfter to set up task finalizers in version order
var runAfter: TaskProvider<MuzzleTask> = muzzleTask
val muzzleReportTasks = mutableListOf<TaskProvider<MuzzleTask>>()
Expand Down
13 changes: 10 additions & 3 deletions buildSrc/src/main/kotlin/dd-trace-java.ci-jobs.gradle.kts
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -15,6 +16,12 @@ if (project != rootProject) {
}

allprojects {
pluginManager.withPlugin("dd-trace-java.muzzle") {
extensions.configure<MuzzleExtension> {
includeInAggregate.convention(project.isInSelectedSlot)
}
}

// Enable tests only on the selected slot (if -Pslot=n/t is provided)
tasks.withType<Test>().configureEach {
onlyIf("Project is in selected slot") {
Expand Down Expand Up @@ -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<MuzzleExtension>().includeInAggregate.get()
}
dependsOn(muzzleSubprojects.map { p -> "${p.path}:muzzle" })
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,28 @@ 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)
}

@ParameterizedTest
@ValueSource(strings = ["muzzle", ":dd-java-agent:instrumentation:demo:muzzle", "runMuzzle"])
fun `detects muzzle invocation with various task names`(taskName: String) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,69 @@ 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(
Expand Down
Loading