-
Notifications
You must be signed in to change notification settings - Fork 323
Rewrite build-time instrumentation as compilation post-processor (using Gradle lazy APIs) #9475
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
25e75fc
82ff47f
d7c18db
1b1a8f2
9244887
f6fd5c0
fc4a6bb
c4c02e2
6d3817a
fccc1f5
71a948c
9b5556c
4e77995
324abc2
5311fd3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| package datadog.gradle.plugin.instrument | ||
|
|
||
| import org.gradle.api.file.DirectoryProperty | ||
| import org.gradle.api.provider.ListProperty | ||
|
|
||
| abstract class BuildTimeInstrumentationExtension { | ||
| abstract ListProperty<String> getPlugins() | ||
| abstract ListProperty<DirectoryProperty> getAdditionalClasspath() | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,160 @@ | ||||||||||||||||||
| 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 | ||||||||||||||||||
|
|
||||||||||||||||||
| /** | ||||||||||||||||||
| * Gradle plugin that applies ByteBuddy plugins to perform build-time bytecode instrumentation. | ||||||||||||||||||
| * | ||||||||||||||||||
| * <p>This plugin appends a post-processing action to existing <strong>main</strong> compilation | ||||||||||||||||||
| * tasks. This plugin allows to apply one or more ByteBuddy {@link net.bytebuddy.build.Plugin} | ||||||||||||||||||
| * implementations. | ||||||||||||||||||
| * | ||||||||||||||||||
| * <h3>Configuration:</h3> | ||||||||||||||||||
| * There are two main configuration points: | ||||||||||||||||||
| * <ul> | ||||||||||||||||||
| * <li>The {@code buildTimeInstrumentation} extension, which allows to specify: | ||||||||||||||||||
| * <ul> | ||||||||||||||||||
| * <li>The list of ByteBuddy plugin class names to apply</li> | ||||||||||||||||||
| * <li>Additional classpath entries required to load the plugins</li> | ||||||||||||||||||
| * </ul> | ||||||||||||||||||
| * </li> | ||||||||||||||||||
| * <li>The {@code buildTimeInstrumentationPlugin} configuration, which allows to specify | ||||||||||||||||||
| * dependencies containing the ByteBuddy plugin implementations</li> | ||||||||||||||||||
| * </ul> | ||||||||||||||||||
| * <p>Example configuration: | ||||||||||||||||||
| * | ||||||||||||||||||
| * <pre> | ||||||||||||||||||
| * buildTimeInstrumentation { | ||||||||||||||||||
| * plugins = ['com.example.MyByteBuddyPlugin', 'com.example.AnotherPlugin'] | ||||||||||||||||||
| * additionalClasspath = [file('path/to/additional/classes')] | ||||||||||||||||||
| * } | ||||||||||||||||||
| * | ||||||||||||||||||
| * dependencies { | ||||||||||||||||||
| * buildTimeInstrumentationPlugin 'com.example:my-bytebuddy-plugin:1.0.0' | ||||||||||||||||||
| * buildTimeInstrumentationPlugin project(path: ':some:project', configuration: 'buildTimeInstrumentationPlugin') | ||||||||||||||||||
| * } | ||||||||||||||||||
| * </pre> | ||||||||||||||||||
| * | ||||||||||||||||||
| * <h3>Requirements for ByteBuddy plugins:</h3> | ||||||||||||||||||
| * <ul> | ||||||||||||||||||
| * <li>Must implement {@link net.bytebuddy.build.Plugin}</li> | ||||||||||||||||||
| * <li>Must have a constructor accepting a {@link File} parameter (the target directory)</li> | ||||||||||||||||||
| * <li>Plugin classes must be available on the {@code buildTimeInstrumentationPlugin} configuration</li> | ||||||||||||||||||
| * </ul> | ||||||||||||||||||
| * | ||||||||||||||||||
| * @see BuildTimeInstrumentationExtension | ||||||||||||||||||
| * @see InstrumentPostProcessingAction | ||||||||||||||||||
| * @see ByteBuddyInstrumenter | ||||||||||||||||||
| */ | ||||||||||||||||||
| @SuppressWarnings('unused') | ||||||||||||||||||
| class BuildTimeInstrumentationPlugin implements Plugin<Project> { | ||||||||||||||||||
| public static final String DEFAULT_JAVA_VERSION = 'default' | ||||||||||||||||||
| public static final String BUILD_TIME_INSTRUMENTATION_PLUGIN_CONFIGURATION = 'buildTimeInstrumentationPlugin' | ||||||||||||||||||
| private final Logger logger = Logging.getLogger(BuildTimeInstrumentationPlugin) | ||||||||||||||||||
|
|
||||||||||||||||||
| @Override | ||||||||||||||||||
| void apply(Project project) { | ||||||||||||||||||
| BuildTimeInstrumentationExtension extension = project.extensions.create('buildTimeInstrumentation', BuildTimeInstrumentationExtension) | ||||||||||||||||||
| project.configurations.register(BUILD_TIME_INSTRUMENTATION_PLUGIN_CONFIGURATION) | ||||||||||||||||||
|
|
||||||||||||||||||
|
|
||||||||||||||||||
| ['java', 'kotlin', 'scala', 'groovy'].each { langPluginId -> | ||||||||||||||||||
| project.pluginManager.withPlugin(langPluginId) { | ||||||||||||||||||
| configurePostCompilationInstrumentation(langPluginId, project, extension) | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| private void configurePostCompilationInstrumentation(String language, Project project, BuildTimeInstrumentationExtension 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("[BuildTimeInstrumentationPlugin] source-set: $sourceSetName, language: $language") | ||||||||||||||||||
|
|
||||||||||||||||||
| if (!sourceSetName.startsWith(SourceSet.MAIN_SOURCE_SET_NAME)) { | ||||||||||||||||||
| logger.debug("[BuildTimeInstrumentationPlugin] Skipping non-main source set {} for language {}", sourceSetName, language) | ||||||||||||||||||
| return | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| def compileTaskName = sourceSet.getCompileTaskName(language) | ||||||||||||||||||
| logger.info("[BuildTimeInstrumentationPlugin] compile task name: " + compileTaskName) | ||||||||||||||||||
|
|
||||||||||||||||||
| // For each _main_ 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('[BuildTimeInstrumentationPlugin] Applying buildTimeInstrumentationPlugin configuration as compile task input') | ||||||||||||||||||
| it.inputs.files(project.configurations.named(BUILD_TIME_INSTRUMENTATION_PLUGIN_CONFIGURATION)) | ||||||||||||||||||
|
|
||||||||||||||||||
| if (it.source.isEmpty()) { | ||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we need this check? if it was already checked on line 103?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Indeed, maybe this can be removed. |
||||||||||||||||||
| logger.debug("[BuildTimeInstrumentationPlugin] 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) | ||||||||||||||||||
|
Comment on lines
+124
to
+128
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Minor - remove empty lines to keep lines logically together.
Suggested change
|
||||||||||||||||||
|
|
||||||||||||||||||
| // 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 | ||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. minor: I would re-phrase this comment a bit, probably make it short:
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||
| it.doLast( | ||||||||||||||||||
| "instrumentClasses", | ||||||||||||||||||
| project.objects.newInstance( | ||||||||||||||||||
| InstrumentPostProcessingAction, | ||||||||||||||||||
| javaVersion, | ||||||||||||||||||
| extension.plugins, | ||||||||||||||||||
| instrumentingClassPath, | ||||||||||||||||||
| it.destinationDirectory, | ||||||||||||||||||
| tmpUninstrumentedClasses | ||||||||||||||||||
| ) | ||||||||||||||||||
| ) | ||||||||||||||||||
| logger.info("[BuildTimeInstrumentationPlugin] Configured post-compile instrumentation for $compileTaskName for source-set $sourceSetName") | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.