Skip to content
Closed
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 @@ -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.
*
Expand Down
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 @@ -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)
Expand Down Expand Up @@ -82,7 +83,8 @@ internal object MuzzleMavenRepoUtils {
muzzleDirective: MuzzleDirective,
system: RepositorySystem,
session: RepositorySystemSession,
defaultRepos: List<RemoteRepository> = defaultMuzzleRepos()
defaultRepos: List<RemoteRepository> = defaultMuzzleRepos(),
random: Random = Random.Default
): Set<MuzzleDirective> {
val allVersionsArtifact = DefaultArtifact(
muzzleDirective.group,
Expand Down Expand Up @@ -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()
}

Expand Down Expand Up @@ -373,12 +368,14 @@ internal object MuzzleMavenRepoUtils {
*/
fun muzzleDirectiveToArtifacts(
muzzleDirective: MuzzleDirective,
rangeResult: VersionRangeResult
rangeResult: VersionRangeResult,
random: Random = Random.Default
): Set<Artifact> {
val versions = MuzzleVersionUtils.filterAndLimitVersions(
rangeResult,
muzzleDirective.skipVersions,
muzzleDirective.includeSnapshots
muzzleDirective.includeSnapshots,
random
)
val allVersionArtifacts = versions.map { version ->
DefaultArtifact(
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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.
Expand Down Expand Up @@ -67,6 +73,7 @@ class MuzzlePlugin : Plugin<Project> {

// 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<MuzzleExtension>()
val compileMuzzle = project.tasks.register("compileMuzzle") {
inputs.files(project.providers.provider { project.allMainSourceSet.map { it.output } })
dependsOn(bootstrapProject.tasks.named("compileJava"))
Expand All @@ -76,23 +83,29 @@ class MuzzlePlugin : Plugin<Project> {

val muzzleTask = project.tasks.register<MuzzleTask>("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<MuzzleTask>().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<MuzzleGetReferencesTask>("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<MuzzleExtension>()
val runtimeClasspath = project.mainSourceSet.runtimeClasspath
val report = project.tasks.register<MuzzleGenerateReportTask>("generateMuzzleReport") {
reportDirectives.convention(project.providers.provider { extension.directives })
Expand All @@ -101,54 +114,86 @@ class MuzzlePlugin : Plugin<Project> {
}
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<MuzzleExtension>().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> = muzzleTask
val muzzleReportTasks = mutableListOf<TaskProvider<MuzzleTask>>()
val directives = project.extensions.getByType<MuzzleExtension>().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<MuzzleExtension>()
it.tasks.named<MuzzleTask>("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<MuzzleExtension>().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<MuzzleEndTask>("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<MuzzleDirective>,
plans: List<MuzzlePlannedVersion>
) {
val muzzleTask = project.tasks.named<MuzzleTask>("muzzle")
val muzzleBootstrap = project.configurations.named("muzzleBootstrap")
val muzzleTooling = project.configurations.named("muzzleTooling")
var runAfter = muzzleTask
val muzzleReportTasks = mutableListOf<TaskProvider<MuzzleTask>>()
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<MuzzleEndTask>("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 {
Expand Down Expand Up @@ -234,8 +279,8 @@ class MuzzlePlugin : Plugin<Project> {

val muzzleTask = instrumentationProject.tasks.register<MuzzleTask>(muzzleTaskName) {
this.muzzleDirective.set(muzzleDirective)
this.muzzleBootstrap.set(muzzleBootstrap)
this.muzzleTooling.set(muzzleTooling)
this.muzzleBootstrap.from(muzzleBootstrap)
this.muzzleTooling.from(muzzleTooling)
}

runAfterTask.configure {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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]+$")
Expand All @@ -19,10 +20,11 @@ internal object MuzzleVersionUtils {
fun filterAndLimitVersions(
result: VersionRangeResult,
skipVersions: Set<String>,
includeSnapshots: Boolean
includeSnapshots: Boolean,
random: Random = Random.Default
): Set<Version> {
val filtered = filterVersion(result.versions.toSet(), skipVersions, includeSnapshots)
return limitLargeRanges(result, filtered, skipVersions)
return limitLargeRanges(result, filtered, skipVersions, random)
}

/**
Expand Down Expand Up @@ -81,15 +83,16 @@ internal object MuzzleVersionUtils {
private fun limitLargeRanges(
result: VersionRangeResult,
versions: Set<Version>,
skipVersions: Set<String>
skipVersions: Set<String>,
random: Random
): Set<Version> {
if (versions.size <= 1) return versions
val beforeSize = versions.size
val filteredVersions = versions.toMutableList().apply {
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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,21 @@ 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.
*/
internal class MavenMuzzleResolutionService(
private val system: RepositorySystem,
private val session: RepositorySystemSession,
private val random: Random = Random.Default,
) : MuzzleResolutionService {
override fun resolveArtifacts(directive: MuzzleDirective): Set<Artifact> {
val range = MuzzleMavenRepoUtils.resolveVersionRange(directive, system, session)
return MuzzleMavenRepoUtils.muzzleDirectiveToArtifacts(directive, range)
return MuzzleMavenRepoUtils.muzzleDirectiveToArtifacts(directive, range, random)
}

override fun inverseOf(directive: MuzzleDirective): Set<MuzzleDirective> =
MuzzleMavenRepoUtils.inverseOf(directive, system, session)
MuzzleMavenRepoUtils.inverseOf(directive, system, session, random = random)
}
Loading
Loading