From 25e75fc9a713d0985b33e6ea9bcaeb1c18d2856e Mon Sep 17 00:00:00 2001 From: Brice Dutheil Date: Thu, 4 Sep 2025 20:48:21 +0200 Subject: [PATCH 01/13] chore(build): Rewrite InstrumentPlugin as compile post processing action The current implementation was using bad practices in various ways, `project.afterEvaluate`, task creation, explicit `dependsOn` # Conflicts: # buildSrc/src/main/groovy/InstrumentPlugin.groovy # buildSrc/src/main/groovy/MuzzlePlugin.groovy # dd-java-agent/instrumentation/jetty-9/build.gradle # Conflicts: # buildSrc/src/main/groovy/InstrumentPlugin.groovy # buildSrc/src/test/groovy/InstrumentPluginTest.groovy # dd-java-agent/instrumentation/play/play-2.4/build.gradle # dd-java-agent/instrumentation/play/play-2.6/build.gradle --- .../src/main/groovy/InstrumentPlugin.groovy | 256 +++++++++++------- .../gradle/plugin/muzzle/MuzzlePlugin.kt | 3 +- .../play/play-2.4/build.gradle | 4 +- .../play/play-2.6/build.gradle | 4 +- 4 files changed, 168 insertions(+), 99 deletions(-) diff --git a/buildSrc/src/main/groovy/InstrumentPlugin.groovy b/buildSrc/src/main/groovy/InstrumentPlugin.groovy index ebfe47b3c93..973c32c1213 100644 --- a/buildSrc/src/main/groovy/InstrumentPlugin.groovy +++ b/buildSrc/src/main/groovy/InstrumentPlugin.groovy @@ -1,19 +1,17 @@ -import org.gradle.api.DefaultTask +import org.gradle.api.Action import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.api.file.ConfigurableFileCollection import org.gradle.api.file.Directory import org.gradle.api.file.DirectoryProperty import org.gradle.api.invocation.BuildInvocationDetails +import org.gradle.api.logging.Logger +import org.gradle.api.logging.Logging import org.gradle.api.provider.ListProperty import org.gradle.api.provider.Property -import org.gradle.api.tasks.Classpath -import org.gradle.api.tasks.Input -import org.gradle.api.tasks.InputDirectory -import org.gradle.api.tasks.InputFiles -import org.gradle.api.tasks.Optional -import org.gradle.api.tasks.OutputDirectory -import org.gradle.api.tasks.TaskAction +import org.gradle.api.provider.Provider +import org.gradle.api.tasks.SourceSet +import org.gradle.api.tasks.SourceSetContainer import org.gradle.api.tasks.compile.AbstractCompile import org.gradle.jvm.toolchain.JavaLanguageVersion import org.gradle.jvm.toolchain.JavaToolchainService @@ -22,6 +20,9 @@ import org.gradle.workers.WorkParameters import org.gradle.workers.WorkerExecutor import javax.inject.Inject +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.StandardCopyOption import java.util.concurrent.ConcurrentHashMap import java.util.regex.Matcher @@ -30,18 +31,53 @@ import java.util.regex.Matcher */ @SuppressWarnings('unused') class InstrumentPlugin implements Plugin { + public static final String DEFAULT_JAVA_VERSION = 'default' + private final Logger logger = Logging.getLogger(InstrumentPlugin) + @Override void apply(Project project) { InstrumentExtension extension = project.extensions.create('instrument', InstrumentExtension) - project.tasks.matching { - it.name in ['compileJava', 'compileScala', 'compileGroovy'] || - it.name =~ /compileMain_.+Java/ - }.all { - AbstractCompile compileTask = it as AbstractCompile - Matcher versionMatcher = it.name =~ /compileMain_(.+)Java/ - project.afterEvaluate { - if (!compileTask.source.empty) { + project.pluginManager.withPlugin("java") {configurePostCompilationInstrumentation("java", project, extension) } + project.pluginManager.withPlugin("kotlin") {configurePostCompilationInstrumentation("kotlin", project, extension) } + project.pluginManager.withPlugin("scala") {configurePostCompilationInstrumentation("scala", project, extension) } + project.pluginManager.withPlugin("groovy") {configurePostCompilationInstrumentation("groovy", project, extension) } + } + + private void configurePostCompilationInstrumentation(String language, Project project, InstrumentExtension extension) { + project.extensions.configure(SourceSetContainer) { SourceSetContainer sourceSets -> + // For any "main" source-set configure its compile task + sourceSets.configureEach { SourceSet sourceSet -> + def sourceSetName = sourceSet.name + logger.info("[InstrumentPlugin] source-set: $sourceSetName, language: $language") + + if (!sourceSetName.startsWith(SourceSet.MAIN_SOURCE_SET_NAME)) { + logger.debug("[InstrumentPlugin] Skipping non-main source set {} for language {}", sourceSetName, language) + return + } + + def compileTaskName = sourceSet.getCompileTaskName(language) + logger.info("[InstrumentPlugin] compile task name: " + compileTaskName) + + // For each compile task, append an instrumenting post-processing step + // Examples of compile tasks: + // - compileJava, + // - compileMain_java17Java, + // - compileMain_jetty904Java, + // - compileMain_play25Java, + // - compileEe8TestJava, + // - compileLatestDepTestJava + // - compileKotlin, + // - compileScala, + // - compileGroovy, + project.tasks.named(compileTaskName, AbstractCompile) { + if (it.source.isEmpty()) { + logger.info("[InstrumentPlugin] Skipping $compileTaskName for source set $sourceSetName as it has no source files") + return + } + + // Compute optional Java version + Matcher versionMatcher = compileTaskName =~ /compileMain_(.+)Java/ String sourceSetSuffix = null String javaVersion = null if (versionMatcher.matches()) { @@ -50,63 +86,56 @@ class InstrumentPlugin implements Plugin { javaVersion = sourceSetSuffix[4..-1] } } + javaVersion = javaVersion ?: DEFAULT_JAVA_VERSION // null not accepted + it.inputs.property("javaVersion", javaVersion) - // insert intermediate 'raw' directory for unprocessed classes - Directory classesDir = compileTask.destinationDirectory.get() - Directory rawClassesDir = classesDir.dir("../raw${sourceSetSuffix ? "_$sourceSetSuffix" : ''}/") - compileTask.destinationDirectory.set(rawClassesDir.asFile) - - // insert task between compile and jar, and before test* - String instrumentTaskName = compileTask.name.replace('compile', 'instrument') - def instrumentTask = project.tasks.register(instrumentTaskName, InstrumentTask) { - // Task configuration - it.group = 'Byte Buddy' - it.description = "Instruments the classes compiled by ${compileTask.name}" - it.inputs.dir(compileTask.destinationDirectory) - it.outputs.dir(classesDir) - // Task inputs - it.javaVersion = javaVersion - def instrumenterConfiguration = project.configurations.named('instrumentPluginClasspath') - if (instrumenterConfiguration.present) { - it.pluginClassPath.from(instrumenterConfiguration.get()) - } - it.plugins = extension.plugins - it.instrumentingClassPath.from( - findCompileClassPath(project, it.name) + - rawClassesDir + - findAdditionalClassPath(extension, it.name) - ) - it.sourceDirectory = rawClassesDir - // Task output - it.targetDirectory = classesDir - } - if (javaVersion) { - project.tasks.named(project.sourceSets."main_java${javaVersion}".classesTaskName) { - it.dependsOn(instrumentTask) - } - } else { - project.tasks.named(project.sourceSets.main.classesTaskName) { - it.dependsOn(instrumentTask) - } + def plugins = extension.plugins + it.inputs.property("plugins", plugins) + + def pluginClassPath = project.objects.fileCollection() + def instrumentConfiguration = project.configurations.findByName('instrumentPluginClasspath') + if (instrumentConfiguration != null) { + pluginClassPath.from(instrumentConfiguration) + it.inputs.files(pluginClassPath) } + + def compileTaskClasspath = it.classpath + def rawClassesDir = it.destinationDirectory + + def additionalClassPath = findAdditionalClassPath(extension, it.name) + it.inputs.files(additionalClassPath) + + def instrumentingClassPath = project.objects.fileCollection() + instrumentingClassPath.setFrom( + compileTaskClasspath, + rawClassesDir, + additionalClassPath, + ) + + // This were the post processing happens, i.e. were the instrumentation is applied + it.doLast( + "instrumentClasses", + project.objects.newInstance( + InstrumentPostProcessingAction, + javaVersion, + plugins, + pluginClassPath, + instrumentingClassPath, + rawClassesDir, + ) + ) + logger.info("[InstrumentPlugin] Configured post-compile instrumentation for $compileTaskName for source-set $sourceSetName") } } } } - static findCompileClassPath(Project project, String taskName) { - def matcher = taskName =~ /instrument([A-Z].+)Java/ - def cfgName = matcher.matches() ? "${matcher.group(1).uncapitalize()}CompileClasspath" : 'compileClasspath' - project.configurations.named(cfgName).findAll { - it.name != 'previous-compilation-data.bin' && !it.name.endsWith('.gz') - } - } - - static findAdditionalClassPath(InstrumentExtension extension, String taskName) { - extension.additionalClasspath.getOrDefault(taskName, []).collect { - // insert intermediate 'raw' directory for unprocessed classes - def fileName = it.get().asFile.name - it.get().dir("../${fileName.replaceFirst('^main', 'raw')}") + private static List> findAdditionalClassPath(InstrumentExtension extension, String taskName) { + return extension.additionalClasspath.getOrDefault(taskName, []).collect { dirProperty -> + dirProperty.map { + def fileName = it.asFile.name + it.dir("../${fileName.replaceFirst('^main', 'raw')}") + } } } } @@ -116,21 +145,11 @@ abstract class InstrumentExtension { Map> additionalClasspath = [:] } -abstract class InstrumentTask extends DefaultTask { - @Input @Optional - String javaVersion - @InputFiles @Classpath - abstract ConfigurableFileCollection getPluginClassPath() - @Input - ListProperty plugins - @InputFiles @Classpath - abstract ConfigurableFileCollection getInstrumentingClassPath() - @InputDirectory - Directory sourceDirectory - - @OutputDirectory - Directory targetDirectory +abstract class InstrumentPostProcessingAction implements Action { + private final Logger logger = Logging.getLogger(InstrumentPostProcessingAction) + @Inject + abstract Project getProject() @Inject abstract JavaToolchainService getJavaToolchainService() @Inject @@ -138,20 +157,50 @@ abstract class InstrumentTask extends DefaultTask { @Inject abstract WorkerExecutor getWorkerExecutor() - @TaskAction - instrument() { + final String javaVersion + final ListProperty plugins + final ConfigurableFileCollection pluginClassPath + final ConfigurableFileCollection instrumentingClassPath + final DirectoryProperty rawClassesDirectory + + @Inject + InstrumentPostProcessingAction( + String javaVersion, + ListProperty plugins, + ConfigurableFileCollection pluginClassPath, + ConfigurableFileCollection instrumentingClassPath, + DirectoryProperty rawClassesDir + ) { + this.javaVersion = javaVersion + this.plugins = plugins + this.pluginClassPath = pluginClassPath + this.instrumentingClassPath = instrumentingClassPath + this.rawClassesDirectory = rawClassesDir + } + + @Override + void execute(AbstractCompile task) { + logger.info( + "values: " + + "javaVersion=${javaVersion}, " + + "plugins=${plugins.get()}, " + + "pluginClassPath=${pluginClassPath.files}, " + + "instrumentingClassPath=${instrumentingClassPath.files}, " + + "rawClassesDirectory=${rawClassesDirectory.get().asFile}" + ) + def postCompileAction = this workQueue().submit(InstrumentAction.class, parameters -> { - parameters.buildStartedTime.set(this.invocationDetails.buildStartedTime) - parameters.pluginClassPath.from(this.pluginClassPath) - parameters.plugins.set(this.plugins) - parameters.instrumentingClassPath.setFrom(this.instrumentingClassPath) - parameters.sourceDirectory.set(this.sourceDirectory.asFile) - parameters.targetDirectory.set(this.targetDirectory.asFile) + parameters.buildStartedTime.set(invocationDetails.buildStartedTime) + parameters.pluginClassPath.from(postCompileAction.pluginClassPath) + parameters.plugins.set(postCompileAction.plugins) + parameters.instrumentingClassPath.setFrom(postCompileAction.instrumentingClassPath) + parameters.classesDirectory.set(postCompileAction.rawClassesDirectory) + parameters.tmpDirectory.set(project.layout.buildDirectory.dir("tmp/${task.name}-raw-classes")) }) } private workQueue() { - if (!this.javaVersion) { + if (!this.javaVersion != InstrumentPlugin.DEFAULT_JAVA_VERSION) { this.javaVersion = "8" } def javaLauncher = this.javaToolchainService.launcherFor { spec -> @@ -170,8 +219,8 @@ interface InstrumentWorkParameters extends WorkParameters { ConfigurableFileCollection getPluginClassPath() ListProperty getPlugins() ConfigurableFileCollection getInstrumentingClassPath() - DirectoryProperty getSourceDirectory() - DirectoryProperty getTargetDirectory() + DirectoryProperty getClassesDirectory() + DirectoryProperty getTmpDirectory() } abstract class InstrumentAction implements WorkAction { @@ -197,10 +246,29 @@ abstract class InstrumentAction implements WorkAction } } } - File sourceDirectory = parameters.getSourceDirectory().get().asFile - File targetDirectory = parameters.getTargetDirectory().get().asFile + Path classesDirectory = parameters.classesDirectory.get().asFile.toPath() + Path tmpSourceDir = parameters.tmpDirectory.get().asFile.toPath() + + // Delete tmpSourceDir contents recursively + if (tmpSourceDir.exists()) { + Files.walk(tmpSourceDir) + .sorted(Comparator.reverseOrder()) + .forEach { p -> + if (!p.equals(tmpSourceDir)) { + java.nio.file.Files.deleteIfExists(p) + } + } + } + + Files.move( + classesDirectory, + tmpSourceDir, + StandardCopyOption.REPLACE_EXISTING, + StandardCopyOption.ATOMIC_MOVE, + ) + ClassLoader instrumentingCL = createClassLoader(parameters.instrumentingClassPath, pluginCL) - InstrumentingPlugin.instrumentClasses(plugins, instrumentingCL, sourceDirectory, targetDirectory) + InstrumentingPlugin.instrumentClasses(plugins, instrumentingCL, tmpSourceDir, classesDirectory) } static ClassLoader createClassLoader(cp, parent = InstrumentAction.classLoader) { 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 d72074676c3..76f2316613e 100644 --- a/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/MuzzlePlugin.kt +++ b/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/MuzzlePlugin.kt @@ -15,6 +15,7 @@ import org.gradle.api.Project import org.gradle.api.Task import org.gradle.api.artifacts.Configuration import org.gradle.api.tasks.TaskProvider +import org.gradle.api.tasks.compile.AbstractCompile import org.gradle.kotlin.dsl.create import org.gradle.kotlin.dsl.exclude import org.gradle.kotlin.dsl.getByType @@ -73,7 +74,7 @@ class MuzzlePlugin : Plugin { // Not adding group and description to keep this task from showing in `gradle tasks`. @Suppress("UNCHECKED_CAST") val compileMuzzle = project.tasks.register("compileMuzzle") { - dependsOn(project.tasks.withType(Class.forName("InstrumentTask") as Class)) // kotlin can't see groovy code + dependsOn(project.tasks.matching { it is AbstractCompile && it.name.startsWith("") }) dependsOn(bootstrapProject.tasks.named("compileJava")) dependsOn(bootstrapProject.tasks.named("compileMain_java11Java")) dependsOn(toolingProject.tasks.named("compileJava")) diff --git a/dd-java-agent/instrumentation/play/play-2.4/build.gradle b/dd-java-agent/instrumentation/play/play-2.4/build.gradle index b67968733ed..c786d3409a1 100644 --- a/dd-java-agent/instrumentation/play/play-2.4/build.gradle +++ b/dd-java-agent/instrumentation/play/play-2.4/build.gradle @@ -63,8 +63,8 @@ tasks.named("jar", Jar) { } project.afterEvaluate { - tasks.named('instrumentJava') { dependsOn 'compileMain_play25Java' } - tasks.named('forbiddenApisMain_play25') { dependsOn 'instrumentMain_play25Java' } + //tasks.named('instrumentJava') { dependsOn 'compileMain_play25Java' } + tasks.named('forbiddenApisMain_play25') { dependsOn 'compileMain_play25Java' } } instrument { diff --git a/dd-java-agent/instrumentation/play/play-2.6/build.gradle b/dd-java-agent/instrumentation/play/play-2.6/build.gradle index 08c448d2e4f..4e3629dc140 100644 --- a/dd-java-agent/instrumentation/play/play-2.6/build.gradle +++ b/dd-java-agent/instrumentation/play/play-2.6/build.gradle @@ -82,8 +82,8 @@ tasks.named("compileMain_play27Java", JavaCompile) { } project.afterEvaluate { - tasks.named('instrumentJava') { dependsOn 'compileMain_play27Java' } - tasks.named('forbiddenApisMain_play27') { dependsOn 'instrumentMain_play27Java' } + // tasks.named('instrumentJava') { dependsOn 'compileMain_play27Java' } + tasks.named('forbiddenApisMain_play27') { dependsOn 'compileMain_play27Java' } } instrument { From 82ff47f5377621dfaf2429f92ddad893f3053c68 Mon Sep 17 00:00:00 2001 From: Brice Dutheil Date: Fri, 5 Sep 2025 14:36:53 +0200 Subject: [PATCH 02/13] chore(build): replace dependency on instrument task to source cet output --- buildSrc/src/main/groovy/InstrumentPlugin.groovy | 2 -- .../main/kotlin/datadog/gradle/plugin/muzzle/MuzzlePlugin.kt | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/buildSrc/src/main/groovy/InstrumentPlugin.groovy b/buildSrc/src/main/groovy/InstrumentPlugin.groovy index 973c32c1213..cd7ce6ee097 100644 --- a/buildSrc/src/main/groovy/InstrumentPlugin.groovy +++ b/buildSrc/src/main/groovy/InstrumentPlugin.groovy @@ -65,8 +65,6 @@ class InstrumentPlugin implements Plugin { // - compileMain_java17Java, // - compileMain_jetty904Java, // - compileMain_play25Java, - // - compileEe8TestJava, - // - compileLatestDepTestJava // - compileKotlin, // - compileScala, // - compileGroovy, 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 76f2316613e..bbddbd3288d 100644 --- a/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/MuzzlePlugin.kt +++ b/buildSrc/src/main/kotlin/datadog/gradle/plugin/muzzle/MuzzlePlugin.kt @@ -14,6 +14,7 @@ import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.api.Task import org.gradle.api.artifacts.Configuration +import org.gradle.api.tasks.SourceSetContainer import org.gradle.api.tasks.TaskProvider import org.gradle.api.tasks.compile.AbstractCompile import org.gradle.kotlin.dsl.create @@ -72,9 +73,8 @@ 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`. - @Suppress("UNCHECKED_CAST") val compileMuzzle = project.tasks.register("compileMuzzle") { - dependsOn(project.tasks.matching { it is AbstractCompile && it.name.startsWith("") }) + inputs.files(project.providers.provider { project.allMainSourceSet }) dependsOn(bootstrapProject.tasks.named("compileJava")) dependsOn(bootstrapProject.tasks.named("compileMain_java11Java")) dependsOn(toolingProject.tasks.named("compileJava")) From d7c18dbb32d42f527dd0761c395aa1f44095e62e Mon Sep 17 00:00:00 2001 From: Brice Dutheil Date: Fri, 5 Sep 2025 19:02:03 +0200 Subject: [PATCH 03/13] chore: Properly react on instrumentPluginClasspath configuration --- .../src/main/groovy/InstrumentPlugin.groovy | 75 +++++++++++-------- 1 file changed, 42 insertions(+), 33 deletions(-) diff --git a/buildSrc/src/main/groovy/InstrumentPlugin.groovy b/buildSrc/src/main/groovy/InstrumentPlugin.groovy index cd7ce6ee097..35496481ff5 100644 --- a/buildSrc/src/main/groovy/InstrumentPlugin.groovy +++ b/buildSrc/src/main/groovy/InstrumentPlugin.groovy @@ -4,6 +4,7 @@ import org.gradle.api.Project import org.gradle.api.file.ConfigurableFileCollection import org.gradle.api.file.Directory import org.gradle.api.file.DirectoryProperty +import org.gradle.api.file.FileCollection import org.gradle.api.invocation.BuildInvocationDetails import org.gradle.api.logging.Logger import org.gradle.api.logging.Logging @@ -32,16 +33,17 @@ import java.util.regex.Matcher @SuppressWarnings('unused') class InstrumentPlugin implements Plugin { public static final String DEFAULT_JAVA_VERSION = 'default' + public static final String INSTRUMENT_PLUGIN_CLASSPATH_CONFIGURATION = 'instrumentPluginClasspath' private final Logger logger = Logging.getLogger(InstrumentPlugin) @Override void apply(Project project) { InstrumentExtension extension = project.extensions.create('instrument', InstrumentExtension) - project.pluginManager.withPlugin("java") {configurePostCompilationInstrumentation("java", project, extension) } - project.pluginManager.withPlugin("kotlin") {configurePostCompilationInstrumentation("kotlin", project, extension) } - project.pluginManager.withPlugin("scala") {configurePostCompilationInstrumentation("scala", project, extension) } - project.pluginManager.withPlugin("groovy") {configurePostCompilationInstrumentation("groovy", project, extension) } + project.pluginManager.withPlugin("java") { configurePostCompilationInstrumentation("java", project, extension) } + project.pluginManager.withPlugin("kotlin") { configurePostCompilationInstrumentation("kotlin", project, extension) } + project.pluginManager.withPlugin("scala") { configurePostCompilationInstrumentation("scala", project, extension) } + project.pluginManager.withPlugin("groovy") { configurePostCompilationInstrumentation("groovy", project, extension) } } private void configurePostCompilationInstrumentation(String language, Project project, InstrumentExtension extension) { @@ -68,9 +70,22 @@ class InstrumentPlugin implements Plugin { // - compileKotlin, // - compileScala, // - compileGroovy, - project.tasks.named(compileTaskName, AbstractCompile) { + def compileTasks = project.tasks.withType(AbstractCompile).matching { + it.name == compileTaskName && !it.source.isEmpty() + } + + project.configurations.whenObjectAdded { pluginConfig -> + if (pluginConfig.name == INSTRUMENT_PLUGIN_CLASSPATH_CONFIGURATION) { + logger.info('[InstrumentPlugin] instrumentPluginClasspath configuration was created') + compileTasks.configureEach { + it.inputs.files(pluginConfig) + } + } + } + + compileTasks.configureEach { if (it.source.isEmpty()) { - logger.info("[InstrumentPlugin] Skipping $compileTaskName for source set $sourceSetName as it has no source files") + logger.debug("[InstrumentPlugin] Skipping $compileTaskName for source set $sourceSetName as it has no source files") return } @@ -90,17 +105,10 @@ class InstrumentPlugin implements Plugin { def plugins = extension.plugins it.inputs.property("plugins", plugins) - def pluginClassPath = project.objects.fileCollection() - def instrumentConfiguration = project.configurations.findByName('instrumentPluginClasspath') - if (instrumentConfiguration != null) { - pluginClassPath.from(instrumentConfiguration) - it.inputs.files(pluginClassPath) - } - def compileTaskClasspath = it.classpath def rawClassesDir = it.destinationDirectory - def additionalClassPath = findAdditionalClassPath(extension, it.name) + def additionalClassPath = findAdditionalClassPath(extension) it.inputs.files(additionalClassPath) def instrumentingClassPath = project.objects.fileCollection() @@ -117,7 +125,6 @@ class InstrumentPlugin implements Plugin { InstrumentPostProcessingAction, javaVersion, plugins, - pluginClassPath, instrumentingClassPath, rawClassesDir, ) @@ -128,11 +135,11 @@ class InstrumentPlugin implements Plugin { } } - private static List> findAdditionalClassPath(InstrumentExtension extension, String taskName) { - return extension.additionalClasspath.getOrDefault(taskName, []).collect { dirProperty -> + private static List> findAdditionalClassPath(InstrumentExtension extension) { + return extension.additionalClasspath.getOrDefault('instrumentJava', []).collect { dirProperty -> dirProperty.map { def fileName = it.asFile.name - it.dir("../${fileName.replaceFirst('^main', 'raw')}") + it.dir("../${fileName.replaceFirst('^main', 'raw')}") // TODO there's a hidden contract here } } } @@ -148,30 +155,30 @@ abstract class InstrumentPostProcessingAction implements Action @Inject abstract Project getProject() + @Inject abstract JavaToolchainService getJavaToolchainService() + @Inject abstract BuildInvocationDetails getInvocationDetails() + @Inject abstract WorkerExecutor getWorkerExecutor() final String javaVersion final ListProperty plugins - final ConfigurableFileCollection pluginClassPath - final ConfigurableFileCollection instrumentingClassPath + final FileCollection instrumentingClassPath final DirectoryProperty rawClassesDirectory @Inject InstrumentPostProcessingAction( String javaVersion, ListProperty plugins, - ConfigurableFileCollection pluginClassPath, - ConfigurableFileCollection instrumentingClassPath, + FileCollection instrumentingClassPath, DirectoryProperty rawClassesDir ) { this.javaVersion = javaVersion this.plugins = plugins - this.pluginClassPath = pluginClassPath this.instrumentingClassPath = instrumentingClassPath this.rawClassesDirectory = rawClassesDir } @@ -179,17 +186,19 @@ abstract class InstrumentPostProcessingAction implements Action @Override void execute(AbstractCompile task) { logger.info( - "values: " + - "javaVersion=${javaVersion}, " + - "plugins=${plugins.get()}, " + - "pluginClassPath=${pluginClassPath.files}, " + - "instrumentingClassPath=${instrumentingClassPath.files}, " + - "rawClassesDirectory=${rawClassesDirectory.get().asFile}" + "[InstrumentPostProcessingAction] About to instrument classes \n" + + " javaVersion=${javaVersion}, \n" + + " plugins=${plugins.get()}, \n" + + " instrumentingClassPath=${instrumentingClassPath.files}, \n" + + " rawClassesDirectory=${rawClassesDirectory.get().asFile}" ) def postCompileAction = this workQueue().submit(InstrumentAction.class, parameters -> { parameters.buildStartedTime.set(invocationDetails.buildStartedTime) - parameters.pluginClassPath.from(postCompileAction.pluginClassPath) + parameters.pluginClassPath.from( + project.configurations.findByName(InstrumentPlugin.INSTRUMENT_PLUGIN_CLASSPATH_CONFIGURATION) + ?: project.files() + ) parameters.plugins.set(postCompileAction.plugins) parameters.instrumentingClassPath.setFrom(postCompileAction.instrumentingClassPath) parameters.classesDirectory.set(postCompileAction.rawClassesDirectory) @@ -248,12 +257,12 @@ abstract class InstrumentAction implements WorkAction Path tmpSourceDir = parameters.tmpDirectory.get().asFile.toPath() // Delete tmpSourceDir contents recursively - if (tmpSourceDir.exists()) { + if (Files.exists(tmpSourceDir)) { Files.walk(tmpSourceDir) .sorted(Comparator.reverseOrder()) .forEach { p -> if (!p.equals(tmpSourceDir)) { - java.nio.file.Files.deleteIfExists(p) + Files.deleteIfExists(p) } } } @@ -266,7 +275,7 @@ abstract class InstrumentAction implements WorkAction ) ClassLoader instrumentingCL = createClassLoader(parameters.instrumentingClassPath, pluginCL) - InstrumentingPlugin.instrumentClasses(plugins, instrumentingCL, tmpSourceDir, classesDirectory) + InstrumentingPlugin.instrumentClasses(plugins, instrumentingCL, tmpSourceDir.toFile(), classesDirectory.toFile()) } static ClassLoader createClassLoader(cp, parent = InstrumentAction.classLoader) { From 1b1a8f26f9e9fdae811493a4a9f0d2c02592118a Mon Sep 17 00:00:00 2001 From: Brice Dutheil Date: Mon, 8 Sep 2025 21:17:44 +0200 Subject: [PATCH 04/13] chore: Rework additional classpath and instrumenter classpath contributions to instrument plugin --- .../src/main/groovy/InstrumentPlugin.groovy | 73 +++++++++---------- .../play/play-2.4/build.gradle | 14 ++-- .../play/play-2.6/build.gradle | 16 ++-- 3 files changed, 51 insertions(+), 52 deletions(-) diff --git a/buildSrc/src/main/groovy/InstrumentPlugin.groovy b/buildSrc/src/main/groovy/InstrumentPlugin.groovy index 35496481ff5..8657b749a4e 100644 --- a/buildSrc/src/main/groovy/InstrumentPlugin.groovy +++ b/buildSrc/src/main/groovy/InstrumentPlugin.groovy @@ -2,7 +2,6 @@ import org.gradle.api.Action import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.api.file.ConfigurableFileCollection -import org.gradle.api.file.Directory import org.gradle.api.file.DirectoryProperty import org.gradle.api.file.FileCollection import org.gradle.api.invocation.BuildInvocationDetails @@ -10,7 +9,6 @@ import org.gradle.api.logging.Logger import org.gradle.api.logging.Logging import org.gradle.api.provider.ListProperty import org.gradle.api.provider.Property -import org.gradle.api.provider.Provider import org.gradle.api.tasks.SourceSet import org.gradle.api.tasks.SourceSetContainer import org.gradle.api.tasks.compile.AbstractCompile @@ -102,20 +100,21 @@ class InstrumentPlugin implements Plugin { javaVersion = javaVersion ?: DEFAULT_JAVA_VERSION // null not accepted it.inputs.property("javaVersion", javaVersion) - def plugins = extension.plugins - it.inputs.property("plugins", plugins) + it.inputs.property("plugins", extension.plugins) - def compileTaskClasspath = it.classpath - def rawClassesDir = it.destinationDirectory + it.inputs.files(extension.additionalClasspath) - def additionalClassPath = findAdditionalClassPath(extension) - it.inputs.files(additionalClassPath) + // Temporary location for raw (un-instrumented) classes + DirectoryProperty tmpUninstrumentedClasses = project.objects.directoryProperty().value( + project.layout.buildDirectory.dir("tmp/${it.name}-raw-classes") + ) - def instrumentingClassPath = project.objects.fileCollection() + // Class path to use for instrumentation post-processing + ConfigurableFileCollection instrumentingClassPath = project.objects.fileCollection() instrumentingClassPath.setFrom( - compileTaskClasspath, - rawClassesDir, - additionalClassPath, + it.classpath, + extension.additionalClasspath, + tmpUninstrumentedClasses ) // This were the post processing happens, i.e. were the instrumentation is applied @@ -124,9 +123,10 @@ class InstrumentPlugin implements Plugin { project.objects.newInstance( InstrumentPostProcessingAction, javaVersion, - plugins, + extension.plugins, instrumentingClassPath, - rawClassesDir, + it.destinationDirectory, + tmpUninstrumentedClasses ) ) logger.info("[InstrumentPlugin] Configured post-compile instrumentation for $compileTaskName for source-set $sourceSetName") @@ -134,20 +134,11 @@ class InstrumentPlugin implements Plugin { } } } - - private static List> findAdditionalClassPath(InstrumentExtension extension) { - return extension.additionalClasspath.getOrDefault('instrumentJava', []).collect { dirProperty -> - dirProperty.map { - def fileName = it.asFile.name - it.dir("../${fileName.replaceFirst('^main', 'raw')}") // TODO there's a hidden contract here - } - } - } } abstract class InstrumentExtension { abstract ListProperty getPlugins() - Map> additionalClasspath = [:] + abstract ListProperty getAdditionalClasspath() } abstract class InstrumentPostProcessingAction implements Action { @@ -165,22 +156,26 @@ abstract class InstrumentPostProcessingAction implements Action @Inject abstract WorkerExecutor getWorkerExecutor() + // Those cannot be private other wise Groovy will fail at runtime with a missing property ex final String javaVersion final ListProperty plugins final FileCollection instrumentingClassPath - final DirectoryProperty rawClassesDirectory + final DirectoryProperty compilerOutputDirectory + final DirectoryProperty tmpDirectory @Inject InstrumentPostProcessingAction( String javaVersion, ListProperty plugins, FileCollection instrumentingClassPath, - DirectoryProperty rawClassesDir + DirectoryProperty compilerOutputDirectory, + DirectoryProperty tmpDirectory ) { this.javaVersion = javaVersion this.plugins = plugins this.instrumentingClassPath = instrumentingClassPath - this.rawClassesDirectory = rawClassesDir + this.compilerOutputDirectory = compilerOutputDirectory + this.tmpDirectory = tmpDirectory } @Override @@ -190,7 +185,7 @@ abstract class InstrumentPostProcessingAction implements Action " javaVersion=${javaVersion}, \n" + " plugins=${plugins.get()}, \n" + " instrumentingClassPath=${instrumentingClassPath.files}, \n" + - " rawClassesDirectory=${rawClassesDirectory.get().asFile}" + " rawClassesDirectory=${compilerOutputDirectory.get().asFile}" ) def postCompileAction = this workQueue().submit(InstrumentAction.class, parameters -> { @@ -201,8 +196,8 @@ abstract class InstrumentPostProcessingAction implements Action ) parameters.plugins.set(postCompileAction.plugins) parameters.instrumentingClassPath.setFrom(postCompileAction.instrumentingClassPath) - parameters.classesDirectory.set(postCompileAction.rawClassesDirectory) - parameters.tmpDirectory.set(project.layout.buildDirectory.dir("tmp/${task.name}-raw-classes")) + parameters.compilerOutputDirectory.set(postCompileAction.compilerOutputDirectory) + parameters.tmpDirectory.set(postCompileAction.tmpDirectory) }) } @@ -226,7 +221,7 @@ interface InstrumentWorkParameters extends WorkParameters { ConfigurableFileCollection getPluginClassPath() ListProperty getPlugins() ConfigurableFileCollection getInstrumentingClassPath() - DirectoryProperty getClassesDirectory() + DirectoryProperty getCompilerOutputDirectory() DirectoryProperty getTmpDirectory() } @@ -253,15 +248,15 @@ abstract class InstrumentAction implements WorkAction } } } - Path classesDirectory = parameters.classesDirectory.get().asFile.toPath() - Path tmpSourceDir = parameters.tmpDirectory.get().asFile.toPath() + Path classesDirectory = parameters.compilerOutputDirectory.get().asFile.toPath() + Path tmpUninstrumentedDir = parameters.tmpDirectory.get().asFile.toPath() - // Delete tmpSourceDir contents recursively - if (Files.exists(tmpSourceDir)) { - Files.walk(tmpSourceDir) + // Delete previous tmpSourceDir contents recursively + if (Files.exists(tmpUninstrumentedDir)) { + Files.walk(tmpUninstrumentedDir) .sorted(Comparator.reverseOrder()) .forEach { p -> - if (!p.equals(tmpSourceDir)) { + if (!p.equals(tmpUninstrumentedDir)) { Files.deleteIfExists(p) } } @@ -269,13 +264,13 @@ abstract class InstrumentAction implements WorkAction Files.move( classesDirectory, - tmpSourceDir, + tmpUninstrumentedDir, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE, ) ClassLoader instrumentingCL = createClassLoader(parameters.instrumentingClassPath, pluginCL) - InstrumentingPlugin.instrumentClasses(plugins, instrumentingCL, tmpSourceDir.toFile(), classesDirectory.toFile()) + InstrumentingPlugin.instrumentClasses(plugins, instrumentingCL, tmpUninstrumentedDir.toFile(), classesDirectory.toFile()) } static ClassLoader createClassLoader(cp, parent = InstrumentAction.classLoader) { diff --git a/dd-java-agent/instrumentation/play/play-2.4/build.gradle b/dd-java-agent/instrumentation/play/play-2.4/build.gradle index c786d3409a1..66d89e12e87 100644 --- a/dd-java-agent/instrumentation/play/play-2.4/build.gradle +++ b/dd-java-agent/instrumentation/play/play-2.4/build.gradle @@ -62,15 +62,19 @@ tasks.named("jar", Jar) { from sourceSets.main_play25.output } +tasks.named('compileMain_play25Java') { + dependsOn(tasks.named('compileJava')) +} project.afterEvaluate { - //tasks.named('instrumentJava') { dependsOn 'compileMain_play25Java' } - tasks.named('forbiddenApisMain_play25') { dependsOn 'compileMain_play25Java' } + tasks.named('forbiddenApisMain_play25') { + dependsOn(tasks.named('compileMain_play25Java')) + } } instrument { - additionalClasspath = [ - instrumentJava: compileMain_play25Java.destinationDirectory - ] + additionalClasspath.add( + tasks.named('compileMain_play25Java', AbstractCompile).map { it.destinationDirectory } + ) } dependencies { diff --git a/dd-java-agent/instrumentation/play/play-2.6/build.gradle b/dd-java-agent/instrumentation/play/play-2.6/build.gradle index 4e3629dc140..13bd4a08f08 100644 --- a/dd-java-agent/instrumentation/play/play-2.6/build.gradle +++ b/dd-java-agent/instrumentation/play/play-2.6/build.gradle @@ -77,19 +77,19 @@ tasks.named("jar", Jar) { from sourceSets.main_play27.output } -tasks.named("compileMain_play27Java", JavaCompile) { - dependsOn(compileJava) +tasks.named('compileMain_play27Java') { + dependsOn(tasks.named('compileJava')) } - project.afterEvaluate { - // tasks.named('instrumentJava') { dependsOn 'compileMain_play27Java' } - tasks.named('forbiddenApisMain_play27') { dependsOn 'compileMain_play27Java' } + tasks.named('forbiddenApisMain_play27') { + dependsOn(tasks.named('compileMain_play27Java')) + } } instrument { - additionalClasspath = [ - instrumentJava: compileMain_play27Java.destinationDirectory - ] + additionalClasspath.add( + tasks.named('compileMain_play27Java', AbstractCompile).map { it.destinationDirectory } + ) } dependencies { From 9244887ead81d6474c546ea3b4230824e81118e2 Mon Sep 17 00:00:00 2001 From: Brice Dutheil Date: Wed, 8 Oct 2025 13:15:12 +0200 Subject: [PATCH 05/13] fix: Remove eager whenObjectAdded in InstrumentPlugin --- buildSrc/src/main/groovy/InstrumentPlugin.groovy | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/buildSrc/src/main/groovy/InstrumentPlugin.groovy b/buildSrc/src/main/groovy/InstrumentPlugin.groovy index 8657b749a4e..8f6dfe35208 100644 --- a/buildSrc/src/main/groovy/InstrumentPlugin.groovy +++ b/buildSrc/src/main/groovy/InstrumentPlugin.groovy @@ -72,11 +72,12 @@ class InstrumentPlugin implements Plugin { it.name == compileTaskName && !it.source.isEmpty() } - project.configurations.whenObjectAdded { pluginConfig -> - if (pluginConfig.name == INSTRUMENT_PLUGIN_CLASSPATH_CONFIGURATION) { + // TODO eager config + project.configurations.configureEach { config -> + if (config.name == INSTRUMENT_PLUGIN_CLASSPATH_CONFIGURATION) { logger.info('[InstrumentPlugin] instrumentPluginClasspath configuration was created') compileTasks.configureEach { - it.inputs.files(pluginConfig) + it.inputs.files(config) } } } From f6fd5c0390617a046625a433cfe0a945eda02358 Mon Sep 17 00:00:00 2001 From: Brice Dutheil Date: Wed, 8 Oct 2025 13:15:37 +0200 Subject: [PATCH 06/13] fix: Fix bad java version comparison --- buildSrc/src/main/groovy/InstrumentPlugin.groovy | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/buildSrc/src/main/groovy/InstrumentPlugin.groovy b/buildSrc/src/main/groovy/InstrumentPlugin.groovy index 8f6dfe35208..80fbb5b3e37 100644 --- a/buildSrc/src/main/groovy/InstrumentPlugin.groovy +++ b/buildSrc/src/main/groovy/InstrumentPlugin.groovy @@ -172,7 +172,7 @@ abstract class InstrumentPostProcessingAction implements Action DirectoryProperty compilerOutputDirectory, DirectoryProperty tmpDirectory ) { - this.javaVersion = javaVersion + this.javaVersion = javaVersion != InstrumentPlugin.DEFAULT_JAVA_VERSION ? "8" : javaVersion this.plugins = plugins this.instrumentingClassPath = instrumentingClassPath this.compilerOutputDirectory = compilerOutputDirectory @@ -203,9 +203,6 @@ abstract class InstrumentPostProcessingAction implements Action } private workQueue() { - if (!this.javaVersion != InstrumentPlugin.DEFAULT_JAVA_VERSION) { - this.javaVersion = "8" - } def javaLauncher = this.javaToolchainService.launcherFor { spec -> spec.languageVersion.set(JavaLanguageVersion.of(this.javaVersion)) }.get() From fc4a6bbc9e9ef31c2303495cd4b3a27c74adf091 Mon Sep 17 00:00:00 2001 From: Brice Dutheil Date: Wed, 8 Oct 2025 17:53:48 +0200 Subject: [PATCH 07/13] chore: Rework INSTRUMENT_PLUGIN_CLASSPATH_CONFIGURATION registration and configuration --- .../src/main/groovy/InstrumentPlugin.groovy | 21 ++++++------------- dd-java-agent/instrumentation/build.gradle | 7 +++++-- 2 files changed, 11 insertions(+), 17 deletions(-) diff --git a/buildSrc/src/main/groovy/InstrumentPlugin.groovy b/buildSrc/src/main/groovy/InstrumentPlugin.groovy index 80fbb5b3e37..ab144817feb 100644 --- a/buildSrc/src/main/groovy/InstrumentPlugin.groovy +++ b/buildSrc/src/main/groovy/InstrumentPlugin.groovy @@ -37,6 +37,7 @@ class InstrumentPlugin implements Plugin { @Override void apply(Project project) { InstrumentExtension extension = project.extensions.create('instrument', InstrumentExtension) + project.configurations.register(INSTRUMENT_PLUGIN_CLASSPATH_CONFIGURATION) project.pluginManager.withPlugin("java") { configurePostCompilationInstrumentation("java", project, extension) } project.pluginManager.withPlugin("kotlin") { configurePostCompilationInstrumentation("kotlin", project, extension) } @@ -68,21 +69,12 @@ class InstrumentPlugin implements Plugin { // - compileKotlin, // - compileScala, // - compileGroovy, - def compileTasks = project.tasks.withType(AbstractCompile).matching { + project.tasks.withType(AbstractCompile).matching { it.name == compileTaskName && !it.source.isEmpty() - } - - // TODO eager config - project.configurations.configureEach { config -> - if (config.name == INSTRUMENT_PLUGIN_CLASSPATH_CONFIGURATION) { - logger.info('[InstrumentPlugin] instrumentPluginClasspath configuration was created') - compileTasks.configureEach { - it.inputs.files(config) - } - } - } + }.configureEach { + logger.info('[InstrumentPlugin] Applying instrumentPluginClasspath configuration as compile task input') + it.inputs.files(project.configurations.named(INSTRUMENT_PLUGIN_CLASSPATH_CONFIGURATION)) - compileTasks.configureEach { if (it.source.isEmpty()) { logger.debug("[InstrumentPlugin] Skipping $compileTaskName for source set $sourceSetName as it has no source files") return @@ -192,8 +184,7 @@ abstract class InstrumentPostProcessingAction implements Action workQueue().submit(InstrumentAction.class, parameters -> { parameters.buildStartedTime.set(invocationDetails.buildStartedTime) parameters.pluginClassPath.from( - project.configurations.findByName(InstrumentPlugin.INSTRUMENT_PLUGIN_CLASSPATH_CONFIGURATION) - ?: project.files() + project.configurations.named(InstrumentPlugin.INSTRUMENT_PLUGIN_CLASSPATH_CONFIGURATION) ) parameters.plugins.set(postCompileAction.plugins) parameters.instrumentingClassPath.setFrom(postCompileAction.instrumentingClassPath) diff --git a/dd-java-agent/instrumentation/build.gradle b/dd-java-agent/instrumentation/build.gradle index 96479c0f6d2..b00ad7775b1 100644 --- a/dd-java-agent/instrumentation/build.gradle +++ b/dd-java-agent/instrumentation/build.gradle @@ -23,12 +23,15 @@ subprojects { Project subProj -> ) } - subProj.configurations.register("instrumentPluginClasspath") { + subProj.configurations.named('instrumentPluginClasspath') { it.visible = false it.canBeConsumed = false it.canBeResolved = true - it.dependencies.add(subProj.dependencies.project(path: ':dd-java-agent:agent-tooling', configuration: 'instrumentPluginClasspath')) + it.dependencies.add(subProj.dependencies.project( + path: ':dd-java-agent:agent-tooling', + configuration: 'instrumentPluginClasspath' + )) } } From c4c02e273bacc833fbc8ced533e070fc9bc8756f Mon Sep 17 00:00:00 2001 From: Brice Dutheil Date: Tue, 30 Dec 2025 17:09:46 +0100 Subject: [PATCH 08/13] chore: Remove CallSiteInstrumentationPlugin dependencies on instrumentJava Since instrument plugin do post-processing within compileTask, it not anymore required to depends on instrumentation tasks (as they don't exist anymore) --- .../gradle/plugin/csi/CallSiteInstrumentationPlugin.kt | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/buildSrc/src/main/kotlin/datadog/gradle/plugin/csi/CallSiteInstrumentationPlugin.kt b/buildSrc/src/main/kotlin/datadog/gradle/plugin/csi/CallSiteInstrumentationPlugin.kt index a0591294843..9fff60f498f 100644 --- a/buildSrc/src/main/kotlin/datadog/gradle/plugin/csi/CallSiteInstrumentationPlugin.kt +++ b/buildSrc/src/main/kotlin/datadog/gradle/plugin/csi/CallSiteInstrumentationPlugin.kt @@ -188,14 +188,7 @@ abstract class CallSiteInstrumentationPlugin : Plugin { dependsOn(mainCompileTask) } - // Workaround for instrument plugin modifying compile tasks - project.pluginManager.withPlugin("dd-trace-java.instrument") { - callSiteGeneratorTask.configure { - dependsOn("instrumentJava") - } - } - - // make all sourcesets' class tasks depend on call site generator + // make all sourceSets class tasks depend on call site generator val sourceSets = project.sourceSets sourceSets.named(MAIN_SOURCE_SET_NAME) { project.tasks.named(classesTaskName) { From 6d3817a67ae3b9e67b5ef571dbce70c36e97ea89 Mon Sep 17 00:00:00 2001 From: Brice Dutheil Date: Tue, 30 Dec 2025 17:12:32 +0100 Subject: [PATCH 09/13] chore: Removes useless afterEvaluate for forbiddenApi tasks --- dd-java-agent/instrumentation/play/play-2.4/build.gradle | 6 ++---- dd-java-agent/instrumentation/play/play-2.6/build.gradle | 6 ++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/dd-java-agent/instrumentation/play/play-2.4/build.gradle b/dd-java-agent/instrumentation/play/play-2.4/build.gradle index 66d89e12e87..7c9ca55761f 100644 --- a/dd-java-agent/instrumentation/play/play-2.4/build.gradle +++ b/dd-java-agent/instrumentation/play/play-2.4/build.gradle @@ -65,10 +65,8 @@ tasks.named("jar", Jar) { tasks.named('compileMain_play25Java') { dependsOn(tasks.named('compileJava')) } -project.afterEvaluate { - tasks.named('forbiddenApisMain_play25') { - dependsOn(tasks.named('compileMain_play25Java')) - } +tasks.named('forbiddenApisMain_play25') { + dependsOn(tasks.named('compileMain_play25Java')) } instrument { diff --git a/dd-java-agent/instrumentation/play/play-2.6/build.gradle b/dd-java-agent/instrumentation/play/play-2.6/build.gradle index 13bd4a08f08..24d5c9b96aa 100644 --- a/dd-java-agent/instrumentation/play/play-2.6/build.gradle +++ b/dd-java-agent/instrumentation/play/play-2.6/build.gradle @@ -80,10 +80,8 @@ tasks.named("jar", Jar) { tasks.named('compileMain_play27Java') { dependsOn(tasks.named('compileJava')) } -project.afterEvaluate { - tasks.named('forbiddenApisMain_play27') { - dependsOn(tasks.named('compileMain_play27Java')) - } +tasks.named('forbiddenApisMain_play27') { + dependsOn(tasks.named('compileMain_play27Java')) } instrument { From fccc1f527a7f8e49c5f484ca8eca512351a6e35d Mon Sep 17 00:00:00 2001 From: Brice Dutheil Date: Tue, 30 Dec 2025 18:49:32 +0100 Subject: [PATCH 10/13] chore: Split single plugin file to separate types --- buildSrc/build.gradle.kts | 2 +- .../src/main/groovy/InstrumentPlugin.groovy | 268 ------------------ .../instrument/ByteBuddyInstrumenter.groovy} | 6 +- .../plugin/instrument/InstrumentAction.groovy | 58 ++++ .../instrument/InstrumentExtension.groovy | 9 + .../plugin/instrument/InstrumentPlugin.groovy | 117 ++++++++ .../InstrumentPostProcessingAction.groovy | 86 ++++++ .../InstrumentWorkParameters.groovy | 17 ++ dd-java-agent/instrumentation/build.gradle | 1 + 9 files changed, 293 insertions(+), 271 deletions(-) delete mode 100644 buildSrc/src/main/groovy/InstrumentPlugin.groovy rename buildSrc/src/main/groovy/{InstrumentingPlugin.groovy => datadog/gradle/plugin/instrument/ByteBuddyInstrumenter.groovy} (96%) create mode 100644 buildSrc/src/main/groovy/datadog/gradle/plugin/instrument/InstrumentAction.groovy create mode 100644 buildSrc/src/main/groovy/datadog/gradle/plugin/instrument/InstrumentExtension.groovy create mode 100644 buildSrc/src/main/groovy/datadog/gradle/plugin/instrument/InstrumentPlugin.groovy create mode 100644 buildSrc/src/main/groovy/datadog/gradle/plugin/instrument/InstrumentPostProcessingAction.groovy create mode 100644 buildSrc/src/main/groovy/datadog/gradle/plugin/instrument/InstrumentWorkParameters.groovy diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 92365591b13..2069f36b355 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -16,7 +16,7 @@ gradlePlugin { plugins { create("instrument-plugin") { id = "dd-trace-java.instrument" - implementationClass = "InstrumentPlugin" + implementationClass = "datadog.gradle.plugin.instrument.InstrumentPlugin" } create("muzzle-plugin") { diff --git a/buildSrc/src/main/groovy/InstrumentPlugin.groovy b/buildSrc/src/main/groovy/InstrumentPlugin.groovy deleted file mode 100644 index ab144817feb..00000000000 --- a/buildSrc/src/main/groovy/InstrumentPlugin.groovy +++ /dev/null @@ -1,268 +0,0 @@ -import org.gradle.api.Action -import org.gradle.api.Plugin -import org.gradle.api.Project -import org.gradle.api.file.ConfigurableFileCollection -import org.gradle.api.file.DirectoryProperty -import org.gradle.api.file.FileCollection -import org.gradle.api.invocation.BuildInvocationDetails -import org.gradle.api.logging.Logger -import org.gradle.api.logging.Logging -import org.gradle.api.provider.ListProperty -import org.gradle.api.provider.Property -import org.gradle.api.tasks.SourceSet -import org.gradle.api.tasks.SourceSetContainer -import org.gradle.api.tasks.compile.AbstractCompile -import org.gradle.jvm.toolchain.JavaLanguageVersion -import org.gradle.jvm.toolchain.JavaToolchainService -import org.gradle.workers.WorkAction -import org.gradle.workers.WorkParameters -import org.gradle.workers.WorkerExecutor - -import javax.inject.Inject -import java.nio.file.Files -import java.nio.file.Path -import java.nio.file.StandardCopyOption -import java.util.concurrent.ConcurrentHashMap -import java.util.regex.Matcher - -/** - * instrument task plugin which performs build-time instrumentation of classes. - */ -@SuppressWarnings('unused') -class InstrumentPlugin implements Plugin { - public static final String DEFAULT_JAVA_VERSION = 'default' - public static final String INSTRUMENT_PLUGIN_CLASSPATH_CONFIGURATION = 'instrumentPluginClasspath' - private final Logger logger = Logging.getLogger(InstrumentPlugin) - - @Override - void apply(Project project) { - InstrumentExtension extension = project.extensions.create('instrument', InstrumentExtension) - project.configurations.register(INSTRUMENT_PLUGIN_CLASSPATH_CONFIGURATION) - - project.pluginManager.withPlugin("java") { configurePostCompilationInstrumentation("java", project, extension) } - project.pluginManager.withPlugin("kotlin") { configurePostCompilationInstrumentation("kotlin", project, extension) } - project.pluginManager.withPlugin("scala") { configurePostCompilationInstrumentation("scala", project, extension) } - project.pluginManager.withPlugin("groovy") { configurePostCompilationInstrumentation("groovy", project, extension) } - } - - private void configurePostCompilationInstrumentation(String language, Project project, InstrumentExtension extension) { - project.extensions.configure(SourceSetContainer) { SourceSetContainer sourceSets -> - // For any "main" source-set configure its compile task - sourceSets.configureEach { SourceSet sourceSet -> - def sourceSetName = sourceSet.name - logger.info("[InstrumentPlugin] source-set: $sourceSetName, language: $language") - - if (!sourceSetName.startsWith(SourceSet.MAIN_SOURCE_SET_NAME)) { - logger.debug("[InstrumentPlugin] Skipping non-main source set {} for language {}", sourceSetName, language) - return - } - - def compileTaskName = sourceSet.getCompileTaskName(language) - logger.info("[InstrumentPlugin] compile task name: " + compileTaskName) - - // For each compile task, append an instrumenting post-processing step - // Examples of compile tasks: - // - compileJava, - // - compileMain_java17Java, - // - compileMain_jetty904Java, - // - compileMain_play25Java, - // - compileKotlin, - // - compileScala, - // - compileGroovy, - project.tasks.withType(AbstractCompile).matching { - it.name == compileTaskName && !it.source.isEmpty() - }.configureEach { - logger.info('[InstrumentPlugin] Applying instrumentPluginClasspath configuration as compile task input') - it.inputs.files(project.configurations.named(INSTRUMENT_PLUGIN_CLASSPATH_CONFIGURATION)) - - if (it.source.isEmpty()) { - logger.debug("[InstrumentPlugin] Skipping $compileTaskName for source set $sourceSetName as it has no source files") - return - } - - // Compute optional Java version - Matcher versionMatcher = compileTaskName =~ /compileMain_(.+)Java/ - String sourceSetSuffix = null - String javaVersion = null - if (versionMatcher.matches()) { - sourceSetSuffix = versionMatcher.group(1) - if (sourceSetSuffix ==~ /java\d+/) { - javaVersion = sourceSetSuffix[4..-1] - } - } - javaVersion = javaVersion ?: DEFAULT_JAVA_VERSION // null not accepted - it.inputs.property("javaVersion", javaVersion) - - it.inputs.property("plugins", extension.plugins) - - it.inputs.files(extension.additionalClasspath) - - // Temporary location for raw (un-instrumented) classes - DirectoryProperty tmpUninstrumentedClasses = project.objects.directoryProperty().value( - project.layout.buildDirectory.dir("tmp/${it.name}-raw-classes") - ) - - // Class path to use for instrumentation post-processing - ConfigurableFileCollection instrumentingClassPath = project.objects.fileCollection() - instrumentingClassPath.setFrom( - it.classpath, - extension.additionalClasspath, - tmpUninstrumentedClasses - ) - - // This were the post processing happens, i.e. were the instrumentation is applied - it.doLast( - "instrumentClasses", - project.objects.newInstance( - InstrumentPostProcessingAction, - javaVersion, - extension.plugins, - instrumentingClassPath, - it.destinationDirectory, - tmpUninstrumentedClasses - ) - ) - logger.info("[InstrumentPlugin] Configured post-compile instrumentation for $compileTaskName for source-set $sourceSetName") - } - } - } - } -} - -abstract class InstrumentExtension { - abstract ListProperty getPlugins() - abstract ListProperty getAdditionalClasspath() -} - -abstract class InstrumentPostProcessingAction implements Action { - private final Logger logger = Logging.getLogger(InstrumentPostProcessingAction) - - @Inject - abstract Project getProject() - - @Inject - abstract JavaToolchainService getJavaToolchainService() - - @Inject - abstract BuildInvocationDetails getInvocationDetails() - - @Inject - abstract WorkerExecutor getWorkerExecutor() - - // Those cannot be private other wise Groovy will fail at runtime with a missing property ex - final String javaVersion - final ListProperty plugins - final FileCollection instrumentingClassPath - final DirectoryProperty compilerOutputDirectory - final DirectoryProperty tmpDirectory - - @Inject - InstrumentPostProcessingAction( - String javaVersion, - ListProperty plugins, - FileCollection instrumentingClassPath, - DirectoryProperty compilerOutputDirectory, - DirectoryProperty tmpDirectory - ) { - this.javaVersion = javaVersion != InstrumentPlugin.DEFAULT_JAVA_VERSION ? "8" : javaVersion - this.plugins = plugins - this.instrumentingClassPath = instrumentingClassPath - this.compilerOutputDirectory = compilerOutputDirectory - this.tmpDirectory = tmpDirectory - } - - @Override - void execute(AbstractCompile task) { - logger.info( - "[InstrumentPostProcessingAction] About to instrument classes \n" + - " javaVersion=${javaVersion}, \n" + - " plugins=${plugins.get()}, \n" + - " instrumentingClassPath=${instrumentingClassPath.files}, \n" + - " rawClassesDirectory=${compilerOutputDirectory.get().asFile}" - ) - def postCompileAction = this - workQueue().submit(InstrumentAction.class, parameters -> { - parameters.buildStartedTime.set(invocationDetails.buildStartedTime) - parameters.pluginClassPath.from( - project.configurations.named(InstrumentPlugin.INSTRUMENT_PLUGIN_CLASSPATH_CONFIGURATION) - ) - parameters.plugins.set(postCompileAction.plugins) - parameters.instrumentingClassPath.setFrom(postCompileAction.instrumentingClassPath) - parameters.compilerOutputDirectory.set(postCompileAction.compilerOutputDirectory) - parameters.tmpDirectory.set(postCompileAction.tmpDirectory) - }) - } - - private workQueue() { - def javaLauncher = this.javaToolchainService.launcherFor { spec -> - spec.languageVersion.set(JavaLanguageVersion.of(this.javaVersion)) - }.get() - return this.workerExecutor.processIsolation { spec -> - spec.forkOptions { fork -> - fork.executable = javaLauncher.executablePath - } - } - } -} - -interface InstrumentWorkParameters extends WorkParameters { - Property getBuildStartedTime() - ConfigurableFileCollection getPluginClassPath() - ListProperty getPlugins() - ConfigurableFileCollection getInstrumentingClassPath() - DirectoryProperty getCompilerOutputDirectory() - DirectoryProperty getTmpDirectory() -} - -abstract class InstrumentAction implements WorkAction { - private static final Object lock = new Object() - private static final Map classLoaderCache = new ConcurrentHashMap<>() - private static volatile long lastBuildStamp - - @Override - void execute() { - String[] plugins = parameters.getPlugins().get() as String[] - String classLoaderKey = plugins.join(':') - - // reset shared class-loaders each time a new build starts - long buildStamp = parameters.buildStartedTime.get() - ClassLoader pluginCL = classLoaderCache.get(classLoaderKey) - if (lastBuildStamp < buildStamp || !pluginCL) { - synchronized (lock) { - pluginCL = classLoaderCache.get(classLoaderKey) - if (lastBuildStamp < buildStamp || !pluginCL) { - pluginCL = createClassLoader(parameters.pluginClassPath) - classLoaderCache.put(classLoaderKey, pluginCL) - lastBuildStamp = buildStamp - } - } - } - Path classesDirectory = parameters.compilerOutputDirectory.get().asFile.toPath() - Path tmpUninstrumentedDir = parameters.tmpDirectory.get().asFile.toPath() - - // Delete previous tmpSourceDir contents recursively - if (Files.exists(tmpUninstrumentedDir)) { - Files.walk(tmpUninstrumentedDir) - .sorted(Comparator.reverseOrder()) - .forEach { p -> - if (!p.equals(tmpUninstrumentedDir)) { - Files.deleteIfExists(p) - } - } - } - - Files.move( - classesDirectory, - tmpUninstrumentedDir, - StandardCopyOption.REPLACE_EXISTING, - StandardCopyOption.ATOMIC_MOVE, - ) - - ClassLoader instrumentingCL = createClassLoader(parameters.instrumentingClassPath, pluginCL) - InstrumentingPlugin.instrumentClasses(plugins, instrumentingCL, tmpUninstrumentedDir.toFile(), classesDirectory.toFile()) - } - - static ClassLoader createClassLoader(cp, parent = InstrumentAction.classLoader) { - return new URLClassLoader(cp*.toURI()*.toURL() as URL[], parent as ClassLoader) - } -} diff --git a/buildSrc/src/main/groovy/InstrumentingPlugin.groovy b/buildSrc/src/main/groovy/datadog/gradle/plugin/instrument/ByteBuddyInstrumenter.groovy similarity index 96% rename from buildSrc/src/main/groovy/InstrumentingPlugin.groovy rename to buildSrc/src/main/groovy/datadog/gradle/plugin/instrument/ByteBuddyInstrumenter.groovy index 9a766971487..7b471b8f5dc 100644 --- a/buildSrc/src/main/groovy/InstrumentingPlugin.groovy +++ b/buildSrc/src/main/groovy/datadog/gradle/plugin/instrument/ByteBuddyInstrumenter.groovy @@ -1,3 +1,5 @@ +package datadog.gradle.plugin.instrument + import net.bytebuddy.ClassFileVersion import net.bytebuddy.build.EntryPoint import net.bytebuddy.build.Plugin @@ -12,8 +14,8 @@ import org.slf4j.LoggerFactory * Performs build-time instrumentation of classes, called indirectly from InstrumentPlugin. * (This is the byte-buddy side of the task; InstrumentPlugin contains the Gradle pieces.) */ -class InstrumentingPlugin { - static final Logger log = LoggerFactory.getLogger(InstrumentingPlugin.class) +class ByteBuddyInstrumenter { + static final Logger log = LoggerFactory.getLogger(ByteBuddyInstrumenter.class) static void instrumentClasses( String[] plugins, ClassLoader instrumentingLoader, File sourceDirectory, File targetDirectory) diff --git a/buildSrc/src/main/groovy/datadog/gradle/plugin/instrument/InstrumentAction.groovy b/buildSrc/src/main/groovy/datadog/gradle/plugin/instrument/InstrumentAction.groovy new file mode 100644 index 00000000000..49f3c946412 --- /dev/null +++ b/buildSrc/src/main/groovy/datadog/gradle/plugin/instrument/InstrumentAction.groovy @@ -0,0 +1,58 @@ +package datadog.gradle.plugin.instrument + +import java.nio.file.Path +import java.util.concurrent.ConcurrentHashMap +import javax.inject.Inject +import org.gradle.api.file.FileSystemOperations +import org.gradle.api.model.ObjectFactory +import org.gradle.workers.WorkAction + +abstract class InstrumentAction implements WorkAction { + private static final Object lock = new Object() + private static final Map classLoaderCache = new ConcurrentHashMap<>() + private static volatile long lastBuildStamp + + @Inject + public abstract FileSystemOperations getFileSystemOperations(); + + @Inject + public abstract ObjectFactory getObjects(); + + @Override + void execute() { + String[] plugins = parameters.getPlugins().get() as String[] + String classLoaderKey = plugins.join(':') + + // reset shared class-loaders each time a new build starts + long buildStamp = parameters.buildStartedTime.get() + ClassLoader pluginCL = classLoaderCache.get(classLoaderKey) + if (lastBuildStamp < buildStamp || !pluginCL) { + synchronized (lock) { + pluginCL = classLoaderCache.get(classLoaderKey) + if (lastBuildStamp < buildStamp || !pluginCL) { + pluginCL = createClassLoader(parameters.pluginClassPath) + classLoaderCache.put(classLoaderKey, pluginCL) + lastBuildStamp = buildStamp + } + } + } + Path classesDirectory = parameters.compilerOutputDirectory.get().asFile.toPath() + Path tmpUninstrumentedDir = parameters.tmpDirectory.get().asFile.toPath() + + // Original classes will be replaced by post-processed ones + fileSystemOperations.sync { + from(classesDirectory) + into(tmpUninstrumentedDir) + } + fileSystemOperations.delete { + delete(objects.fileTree().from(classesDirectory)) + } + + ClassLoader instrumentingCL = createClassLoader(parameters.instrumentingClassPath, pluginCL) + ByteBuddyInstrumenter.instrumentClasses(plugins, instrumentingCL, tmpUninstrumentedDir.toFile(), classesDirectory.toFile()) + } + + static ClassLoader createClassLoader(cp, parent = InstrumentAction.classLoader) { + return new URLClassLoader(cp*.toURI()*.toURL() as URL[], parent as ClassLoader) + } +} diff --git a/buildSrc/src/main/groovy/datadog/gradle/plugin/instrument/InstrumentExtension.groovy b/buildSrc/src/main/groovy/datadog/gradle/plugin/instrument/InstrumentExtension.groovy new file mode 100644 index 00000000000..739f648035d --- /dev/null +++ b/buildSrc/src/main/groovy/datadog/gradle/plugin/instrument/InstrumentExtension.groovy @@ -0,0 +1,9 @@ +package datadog.gradle.plugin.instrument + +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.provider.ListProperty + +abstract class InstrumentExtension { + abstract ListProperty getPlugins() + abstract ListProperty getAdditionalClasspath() +} diff --git a/buildSrc/src/main/groovy/datadog/gradle/plugin/instrument/InstrumentPlugin.groovy b/buildSrc/src/main/groovy/datadog/gradle/plugin/instrument/InstrumentPlugin.groovy new file mode 100644 index 00000000000..8f08de41d65 --- /dev/null +++ b/buildSrc/src/main/groovy/datadog/gradle/plugin/instrument/InstrumentPlugin.groovy @@ -0,0 +1,117 @@ +package datadog.gradle.plugin.instrument + +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.api.file.ConfigurableFileCollection +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.logging.Logger +import org.gradle.api.logging.Logging +import org.gradle.api.tasks.SourceSet +import org.gradle.api.tasks.SourceSetContainer +import org.gradle.api.tasks.compile.AbstractCompile + +import java.util.regex.Matcher + +/** + * instrument task plugin which performs build-time instrumentation of classes. + */ +@SuppressWarnings('unused') +class InstrumentPlugin implements Plugin { + public static final String DEFAULT_JAVA_VERSION = 'default' + public static final String INSTRUMENT_PLUGIN_CLASSPATH_CONFIGURATION = 'instrumentPluginClasspath' + private final Logger logger = Logging.getLogger(InstrumentPlugin) + + @Override + void apply(Project project) { + InstrumentExtension extension = project.extensions.create('instrument', InstrumentExtension) + project.configurations.register(INSTRUMENT_PLUGIN_CLASSPATH_CONFIGURATION) + + project.pluginManager.withPlugin("java") { configurePostCompilationInstrumentation("java", project, extension) } + project.pluginManager.withPlugin("kotlin") { configurePostCompilationInstrumentation("kotlin", project, extension) } + project.pluginManager.withPlugin("scala") { configurePostCompilationInstrumentation("scala", project, extension) } + project.pluginManager.withPlugin("groovy") { configurePostCompilationInstrumentation("groovy", project, extension) } + } + + private void configurePostCompilationInstrumentation(String language, Project project, InstrumentExtension extension) { + project.extensions.configure(SourceSetContainer) { SourceSetContainer sourceSets -> + // For any "main" source-set configure its compile task + sourceSets.configureEach { SourceSet sourceSet -> + def sourceSetName = sourceSet.name + logger.info("[InstrumentPlugin] source-set: $sourceSetName, language: $language") + + if (!sourceSetName.startsWith(SourceSet.MAIN_SOURCE_SET_NAME)) { + logger.debug("[InstrumentPlugin] Skipping non-main source set {} for language {}", sourceSetName, language) + return + } + + def compileTaskName = sourceSet.getCompileTaskName(language) + logger.info("[InstrumentPlugin] compile task name: " + compileTaskName) + + // For each compile task, append an instrumenting post-processing step + // Examples of compile tasks: + // - compileJava, + // - compileMain_java17Java, + // - compileMain_jetty904Java, + // - compileMain_play25Java, + // - compileKotlin, + // - compileScala, + // - compileGroovy, + project.tasks.withType(AbstractCompile).matching { + it.name == compileTaskName && !it.source.isEmpty() + }.configureEach { + logger.info('[InstrumentPlugin] Applying instrumentPluginClasspath configuration as compile task input') + it.inputs.files(project.configurations.named(INSTRUMENT_PLUGIN_CLASSPATH_CONFIGURATION)) + + if (it.source.isEmpty()) { + logger.debug("[InstrumentPlugin] Skipping $compileTaskName for source set $sourceSetName as it has no source files") + return + } + + // Compute optional Java version + Matcher versionMatcher = compileTaskName =~ /compileMain_(.+)Java/ + String sourceSetSuffix = null + String javaVersion = null + if (versionMatcher.matches()) { + sourceSetSuffix = versionMatcher.group(1) + if (sourceSetSuffix ==~ /java\d+/) { + javaVersion = sourceSetSuffix[4..-1] + } + } + javaVersion = javaVersion ?: DEFAULT_JAVA_VERSION // null not accepted + it.inputs.property("javaVersion", javaVersion) + + it.inputs.property("plugins", extension.plugins) + + it.inputs.files(extension.additionalClasspath) + + // Temporary location for raw (un-instrumented) classes + DirectoryProperty tmpUninstrumentedClasses = project.objects.directoryProperty().value( + project.layout.buildDirectory.dir("tmp/${it.name}-raw-classes") + ) + + // Class path to use for instrumentation post-processing + ConfigurableFileCollection instrumentingClassPath = project.objects.fileCollection() + instrumentingClassPath.setFrom( + it.classpath, + extension.additionalClasspath, + tmpUninstrumentedClasses + ) + + // This were the post processing happens, i.e. were the instrumentation is applied + it.doLast( + "instrumentClasses", + project.objects.newInstance( + InstrumentPostProcessingAction, + javaVersion, + extension.plugins, + instrumentingClassPath, + it.destinationDirectory, + tmpUninstrumentedClasses + ) + ) + logger.info("[InstrumentPlugin] Configured post-compile instrumentation for $compileTaskName for source-set $sourceSetName") + } + } + } + } +} diff --git a/buildSrc/src/main/groovy/datadog/gradle/plugin/instrument/InstrumentPostProcessingAction.groovy b/buildSrc/src/main/groovy/datadog/gradle/plugin/instrument/InstrumentPostProcessingAction.groovy new file mode 100644 index 00000000000..e9719f53856 --- /dev/null +++ b/buildSrc/src/main/groovy/datadog/gradle/plugin/instrument/InstrumentPostProcessingAction.groovy @@ -0,0 +1,86 @@ +package datadog.gradle.plugin.instrument + +import javax.inject.Inject +import org.gradle.api.Action +import org.gradle.api.Project +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.file.FileCollection +import org.gradle.api.invocation.BuildInvocationDetails +import org.gradle.api.logging.Logger +import org.gradle.api.logging.Logging +import org.gradle.api.provider.ListProperty +import org.gradle.api.tasks.compile.AbstractCompile +import org.gradle.jvm.toolchain.JavaLanguageVersion +import org.gradle.jvm.toolchain.JavaToolchainService +import org.gradle.workers.WorkerExecutor + +abstract class InstrumentPostProcessingAction implements Action { + private final Logger logger = Logging.getLogger(InstrumentPostProcessingAction) + + @Inject + abstract Project getProject() + + @Inject + abstract JavaToolchainService getJavaToolchainService() + + @Inject + abstract BuildInvocationDetails getInvocationDetails() + + @Inject + abstract WorkerExecutor getWorkerExecutor() + + // Those cannot be private other wise Groovy will fail at runtime with a missing property ex + final String javaVersion + final ListProperty plugins + final FileCollection instrumentingClassPath + final DirectoryProperty compilerOutputDirectory + final DirectoryProperty tmpDirectory + + @Inject + InstrumentPostProcessingAction( + String javaVersion, + ListProperty plugins, + FileCollection instrumentingClassPath, + DirectoryProperty compilerOutputDirectory, + DirectoryProperty tmpDirectory + ) { + this.javaVersion = javaVersion != InstrumentPlugin.DEFAULT_JAVA_VERSION ? "8" : javaVersion + this.plugins = plugins + this.instrumentingClassPath = instrumentingClassPath + this.compilerOutputDirectory = compilerOutputDirectory + this.tmpDirectory = tmpDirectory + } + + @Override + void execute(AbstractCompile task) { + logger.info( + "[InstrumentPostProcessingAction] About to instrument classes \n" + + " javaVersion=${javaVersion}, \n" + + " plugins=${plugins.get()}, \n" + + " instrumentingClassPath=${instrumentingClassPath.files}, \n" + + " rawClassesDirectory=${compilerOutputDirectory.get().asFile}" + ) + def postCompileAction = this + workQueue().submit(InstrumentAction.class, parameters -> { + parameters.buildStartedTime.set(invocationDetails.buildStartedTime) + parameters.pluginClassPath.from( + project.configurations.named(InstrumentPlugin.INSTRUMENT_PLUGIN_CLASSPATH_CONFIGURATION) + ) + parameters.plugins.set(postCompileAction.plugins) + parameters.instrumentingClassPath.setFrom(postCompileAction.instrumentingClassPath) + parameters.compilerOutputDirectory.set(postCompileAction.compilerOutputDirectory) + parameters.tmpDirectory.set(postCompileAction.tmpDirectory) + }) + } + + private workQueue() { + def javaLauncher = this.javaToolchainService.launcherFor { spec -> + spec.languageVersion.set(JavaLanguageVersion.of(this.javaVersion)) + }.get() + return this.workerExecutor.processIsolation { spec -> + spec.forkOptions { fork -> + fork.executable = javaLauncher.executablePath + } + } + } +} diff --git a/buildSrc/src/main/groovy/datadog/gradle/plugin/instrument/InstrumentWorkParameters.groovy b/buildSrc/src/main/groovy/datadog/gradle/plugin/instrument/InstrumentWorkParameters.groovy new file mode 100644 index 00000000000..95dfad91916 --- /dev/null +++ b/buildSrc/src/main/groovy/datadog/gradle/plugin/instrument/InstrumentWorkParameters.groovy @@ -0,0 +1,17 @@ +package datadog.gradle.plugin.instrument + + +import org.gradle.api.file.ConfigurableFileCollection +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.provider.ListProperty +import org.gradle.api.provider.Property +import org.gradle.workers.WorkParameters + +interface InstrumentWorkParameters extends WorkParameters { + Property getBuildStartedTime() + ConfigurableFileCollection getPluginClassPath() + ListProperty getPlugins() + ConfigurableFileCollection getInstrumentingClassPath() + DirectoryProperty getCompilerOutputDirectory() + DirectoryProperty getTmpDirectory() +} diff --git a/dd-java-agent/instrumentation/build.gradle b/dd-java-agent/instrumentation/build.gradle index b00ad7775b1..be72e509b3e 100644 --- a/dd-java-agent/instrumentation/build.gradle +++ b/dd-java-agent/instrumentation/build.gradle @@ -2,6 +2,7 @@ import static org.gradle.api.plugins.JavaPlugin.IMPLEMENTATION_CONFIGURATION_NAM import static org.gradle.api.tasks.SourceSet.MAIN_SOURCE_SET_NAME import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar +import datadog.gradle.plugin.instrument.InstrumentExtension plugins { id 'com.gradleup.shadow' From 71a948ce3bae8c879dcb005f5ed6b244457b6ebb Mon Sep 17 00:00:00 2001 From: Brice Dutheil Date: Tue, 30 Dec 2025 19:10:03 +0100 Subject: [PATCH 11/13] chore: Now instrumentPluginClasspath configuration is already registered --- .../gradle/plugin/instrument/InstrumentPluginTest.kt | 6 ------ 1 file changed, 6 deletions(-) diff --git a/buildSrc/src/test/kotlin/datadog/gradle/plugin/instrument/InstrumentPluginTest.kt b/buildSrc/src/test/kotlin/datadog/gradle/plugin/instrument/InstrumentPluginTest.kt index 8c7b0b13f0b..69d36a2263d 100644 --- a/buildSrc/src/test/kotlin/datadog/gradle/plugin/instrument/InstrumentPluginTest.kt +++ b/buildSrc/src/test/kotlin/datadog/gradle/plugin/instrument/InstrumentPluginTest.kt @@ -30,12 +30,6 @@ class InstrumentPluginTest { compileOnly group: 'net.bytebuddy', name: 'byte-buddy', version: '1.18.3' // just to build TestPlugin } - configurations { - instrumentPluginClasspath { - canBeResolved = true - } - } - instrument.plugins = [ 'TestPlugin' ] From 9b5556c63cb1de2ee9763a982672668f46048645 Mon Sep 17 00:00:00 2001 From: Brice Dutheil Date: Tue, 30 Dec 2025 19:10:43 +0100 Subject: [PATCH 12/13] fix: Incorrect comparison --- .../plugin/instrument/InstrumentPostProcessingAction.groovy | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/buildSrc/src/main/groovy/datadog/gradle/plugin/instrument/InstrumentPostProcessingAction.groovy b/buildSrc/src/main/groovy/datadog/gradle/plugin/instrument/InstrumentPostProcessingAction.groovy index e9719f53856..89ef0e9286b 100644 --- a/buildSrc/src/main/groovy/datadog/gradle/plugin/instrument/InstrumentPostProcessingAction.groovy +++ b/buildSrc/src/main/groovy/datadog/gradle/plugin/instrument/InstrumentPostProcessingAction.groovy @@ -30,7 +30,7 @@ abstract class InstrumentPostProcessingAction implements Action abstract WorkerExecutor getWorkerExecutor() // Those cannot be private other wise Groovy will fail at runtime with a missing property ex - final String javaVersion + final JavaLanguageVersion javaVersion final ListProperty plugins final FileCollection instrumentingClassPath final DirectoryProperty compilerOutputDirectory @@ -44,7 +44,7 @@ abstract class InstrumentPostProcessingAction implements Action DirectoryProperty compilerOutputDirectory, DirectoryProperty tmpDirectory ) { - this.javaVersion = javaVersion != InstrumentPlugin.DEFAULT_JAVA_VERSION ? "8" : javaVersion + this.javaVersion = javaVersion == InstrumentPlugin.DEFAULT_JAVA_VERSION ? JavaLanguageVersion.current() : JavaLanguageVersion.of(javaVersion) this.plugins = plugins this.instrumentingClassPath = instrumentingClassPath this.compilerOutputDirectory = compilerOutputDirectory @@ -75,7 +75,7 @@ abstract class InstrumentPostProcessingAction implements Action private workQueue() { def javaLauncher = this.javaToolchainService.launcherFor { spec -> - spec.languageVersion.set(JavaLanguageVersion.of(this.javaVersion)) + spec.languageVersion.set(this.javaVersion) }.get() return this.workerExecutor.processIsolation { spec -> spec.forkOptions { fork -> From 4e77995d80eb0be84005a9feba7c962c5278afa8 Mon Sep 17 00:00:00 2001 From: Brice Dutheil Date: Wed, 31 Dec 2025 10:52:52 +0100 Subject: [PATCH 13/13] style: Make spotless happy --- .../gradle/plugin/instrument/InstrumentPlugin.groovy | 10 ++++++---- dd-java-agent/instrumentation/build.gradle | 6 +++--- .../instrumentation/play/play-2.4/build.gradle | 6 ++++-- .../instrumentation/play/play-2.6/build.gradle | 6 ++++-- 4 files changed, 17 insertions(+), 11 deletions(-) diff --git a/buildSrc/src/main/groovy/datadog/gradle/plugin/instrument/InstrumentPlugin.groovy b/buildSrc/src/main/groovy/datadog/gradle/plugin/instrument/InstrumentPlugin.groovy index 8f08de41d65..e0fb3c6e273 100644 --- a/buildSrc/src/main/groovy/datadog/gradle/plugin/instrument/InstrumentPlugin.groovy +++ b/buildSrc/src/main/groovy/datadog/gradle/plugin/instrument/InstrumentPlugin.groovy @@ -26,10 +26,12 @@ class InstrumentPlugin implements Plugin { InstrumentExtension extension = project.extensions.create('instrument', InstrumentExtension) project.configurations.register(INSTRUMENT_PLUGIN_CLASSPATH_CONFIGURATION) - project.pluginManager.withPlugin("java") { configurePostCompilationInstrumentation("java", project, extension) } - project.pluginManager.withPlugin("kotlin") { configurePostCompilationInstrumentation("kotlin", project, extension) } - project.pluginManager.withPlugin("scala") { configurePostCompilationInstrumentation("scala", project, extension) } - project.pluginManager.withPlugin("groovy") { configurePostCompilationInstrumentation("groovy", project, extension) } + + ['java', 'kotlin', 'scala', 'groovy'].each { langPluginId -> + project.pluginManager.withPlugin(langPluginId) { + configurePostCompilationInstrumentation(langPluginId, project, extension) + } + } } private void configurePostCompilationInstrumentation(String language, Project project, InstrumentExtension extension) { diff --git a/dd-java-agent/instrumentation/build.gradle b/dd-java-agent/instrumentation/build.gradle index be72e509b3e..5dbecbae50f 100644 --- a/dd-java-agent/instrumentation/build.gradle +++ b/dd-java-agent/instrumentation/build.gradle @@ -30,9 +30,9 @@ subprojects { Project subProj -> it.canBeResolved = true it.dependencies.add(subProj.dependencies.project( - path: ':dd-java-agent:agent-tooling', - configuration: 'instrumentPluginClasspath' - )) + path: ':dd-java-agent:agent-tooling', + configuration: 'instrumentPluginClasspath' + )) } } diff --git a/dd-java-agent/instrumentation/play/play-2.4/build.gradle b/dd-java-agent/instrumentation/play/play-2.4/build.gradle index 7c9ca55761f..67f91bc37bd 100644 --- a/dd-java-agent/instrumentation/play/play-2.4/build.gradle +++ b/dd-java-agent/instrumentation/play/play-2.4/build.gradle @@ -71,8 +71,10 @@ tasks.named('forbiddenApisMain_play25') { instrument { additionalClasspath.add( - tasks.named('compileMain_play25Java', AbstractCompile).map { it.destinationDirectory } - ) + tasks.named('compileMain_play25Java', AbstractCompile).map { + it.destinationDirectory + } + ) } dependencies { diff --git a/dd-java-agent/instrumentation/play/play-2.6/build.gradle b/dd-java-agent/instrumentation/play/play-2.6/build.gradle index 24d5c9b96aa..dfd3b279daa 100644 --- a/dd-java-agent/instrumentation/play/play-2.6/build.gradle +++ b/dd-java-agent/instrumentation/play/play-2.6/build.gradle @@ -86,8 +86,10 @@ tasks.named('forbiddenApisMain_play27') { instrument { additionalClasspath.add( - tasks.named('compileMain_play27Java', AbstractCompile).map { it.destinationDirectory } - ) + tasks.named('compileMain_play27Java', AbstractCompile).map { + it.destinationDirectory + } + ) } dependencies {