1- import org.gradle.api.DefaultTask
1+ import org.gradle.api.Action
22import org.gradle.api.Plugin
33import org.gradle.api.Project
44import org.gradle.api.file.ConfigurableFileCollection
55import org.gradle.api.file.Directory
66import org.gradle.api.file.DirectoryProperty
77import org.gradle.api.invocation.BuildInvocationDetails
8+ import org.gradle.api.logging.Logger
9+ import org.gradle.api.logging.Logging
810import org.gradle.api.provider.ListProperty
911import org.gradle.api.provider.Property
10- import org.gradle.api.tasks.Classpath
11- import org.gradle.api.tasks.Input
12- import org.gradle.api.tasks.InputDirectory
13- import org.gradle.api.tasks.InputFiles
14- import org.gradle.api.tasks.Optional
15- import org.gradle.api.tasks.OutputDirectory
16- import org.gradle.api.tasks.TaskAction
12+ import org.gradle.api.provider.Provider
13+ import org.gradle.api.tasks.SourceSet
14+ import org.gradle.api.tasks.SourceSetContainer
1715import org.gradle.api.tasks.compile.AbstractCompile
1816import org.gradle.jvm.toolchain.JavaLanguageVersion
1917import org.gradle.jvm.toolchain.JavaToolchainService
@@ -22,6 +20,9 @@ import org.gradle.workers.WorkParameters
2220import org.gradle.workers.WorkerExecutor
2321
2422import javax.inject.Inject
23+ import java.nio.file.Files
24+ import java.nio.file.Path
25+ import java.nio.file.StandardCopyOption
2526import java.util.concurrent.ConcurrentHashMap
2627import java.util.regex.Matcher
2728
@@ -30,18 +31,53 @@ import java.util.regex.Matcher
3031 */
3132@SuppressWarnings (' unused' )
3233class InstrumentPlugin implements Plugin<Project > {
34+ public static final String DEFAULT_JAVA_VERSION = ' default'
35+ private final Logger logger = Logging . getLogger(InstrumentPlugin )
36+
3337 @Override
3438 void apply (Project project ) {
3539 InstrumentExtension extension = project. extensions. create(' instrument' , InstrumentExtension )
3640
37- project. tasks. matching {
38- it. name in [' compileJava' , ' compileScala' , ' compileKotlin' , ' compileGroovy' ] ||
39- it. name =~ / compileMain_.+Java/
40- }. all {
41- AbstractCompile compileTask = it as AbstractCompile
42- Matcher versionMatcher = it. name =~ / compileMain_(.+)Java/
43- project. afterEvaluate {
44- if (! compileTask. source. empty) {
41+ project. pluginManager. withPlugin(" java" ) {configurePostCompilationInstrumentation(" java" , project, extension) }
42+ project. pluginManager. withPlugin(" kotlin" ) {configurePostCompilationInstrumentation(" kotlin" , project, extension) }
43+ project. pluginManager. withPlugin(" scala" ) {configurePostCompilationInstrumentation(" scala" , project, extension) }
44+ project. pluginManager. withPlugin(" groovy" ) {configurePostCompilationInstrumentation(" groovy" , project, extension) }
45+ }
46+
47+ private void configurePostCompilationInstrumentation (String language , Project project , InstrumentExtension extension ) {
48+ project. extensions. configure(SourceSetContainer ) { SourceSetContainer sourceSets ->
49+ // For any "main" source-set configure its compile task
50+ sourceSets. configureEach { SourceSet sourceSet ->
51+ def sourceSetName = sourceSet. name
52+ logger. info(" [InstrumentPlugin] source-set: $sourceSetName , language: $language " )
53+
54+ if (! sourceSetName. startsWith(SourceSet . MAIN_SOURCE_SET_NAME )) {
55+ logger. debug(" [InstrumentPlugin] Skipping non-main source set {} for language {}" , sourceSetName, language)
56+ return
57+ }
58+
59+ def compileTaskName = sourceSet. getCompileTaskName(language)
60+ logger. info(" [InstrumentPlugin] compile task name: " + compileTaskName)
61+
62+ // For each compile task, append an instrumenting post-processing step
63+ // Examples of compile tasks:
64+ // - compileJava,
65+ // - compileMain_java17Java,
66+ // - compileMain_jetty904Java,
67+ // - compileMain_play25Java,
68+ // - compileEe8TestJava,
69+ // - compileLatestDepTestJava
70+ // - compileKotlin,
71+ // - compileScala,
72+ // - compileGroovy,
73+ project. tasks. named(compileTaskName, AbstractCompile ) {
74+ if (it. source. isEmpty()) {
75+ logger. info(" [InstrumentPlugin] Skipping $compileTaskName for source set $sourceSetName as it has no source files" )
76+ return
77+ }
78+
79+ // Compute optional Java version
80+ Matcher versionMatcher = compileTaskName =~ / compileMain_(.+)Java/
4581 String sourceSetSuffix = null
4682 String javaVersion = null
4783 if (versionMatcher. matches()) {
@@ -50,64 +86,56 @@ class InstrumentPlugin implements Plugin<Project> {
5086 javaVersion = sourceSetSuffix[4 .. -1 ]
5187 }
5288 }
89+ javaVersion = javaVersion ?: DEFAULT_JAVA_VERSION // null not accepted
90+ it. inputs. property(" javaVersion" , javaVersion)
5391
54- // insert intermediate 'raw' directory for unprocessed classes
55- Directory classesDir = compileTask. destinationDirectory. get()
56- Directory rawClassesDir = classesDir. dir(
57- " ../raw${ sourceSetSuffix ? "_$sourceSetSuffix" : ''} /" )
58- compileTask. destinationDirectory. set(rawClassesDir. asFile)
59-
60- // insert task between compile and jar, and before test*
61- String instrumentTaskName = compileTask. name. replace(' compile' , ' instrument' )
62- def instrumentTask = project. tasks. register(instrumentTaskName, InstrumentTask ) {
63- // Task configuration
64- it. group = ' Byte Buddy'
65- it. description = " Instruments the classes compiled by ${ compileTask.name} "
66- it. inputs. dir(compileTask. destinationDirectory)
67- it. outputs. dir(classesDir)
68- // Task inputs
69- it. javaVersion = javaVersion
70- def instrumenterConfiguration = project. configurations. named(' instrumentPluginClasspath' )
71- if (instrumenterConfiguration. present) {
72- it. pluginClassPath. from(instrumenterConfiguration. get())
73- }
74- it. plugins = extension. plugins
75- it. instrumentingClassPath. from(
76- findCompileClassPath(project, it. name) +
77- rawClassesDir +
78- findAdditionalClassPath(extension, it. name)
79- )
80- it. sourceDirectory = rawClassesDir
81- // Task output
82- it. targetDirectory = classesDir
83- }
84- if (javaVersion) {
85- project. tasks. named(project. sourceSets. " main_java${ javaVersion} " . classesTaskName) {
86- it. dependsOn(instrumentTask)
87- }
88- } else {
89- project. tasks. named(project. sourceSets. main. classesTaskName) {
90- it. dependsOn(instrumentTask)
91- }
92+ def plugins = extension. plugins
93+ it. inputs. property(" plugins" , plugins)
94+
95+ def pluginClassPath = project. objects. fileCollection()
96+ def instrumentConfiguration = project. configurations. findByName(' instrumentPluginClasspath' )
97+ if (instrumentConfiguration != null ) {
98+ pluginClassPath. from(instrumentConfiguration)
99+ it. inputs. files(pluginClassPath)
92100 }
101+
102+ def compileTaskClasspath = it. classpath
103+ def rawClassesDir = it. destinationDirectory
104+
105+ def additionalClassPath = findAdditionalClassPath(extension, it. name)
106+ it. inputs. files(additionalClassPath)
107+
108+ def instrumentingClassPath = project. objects. fileCollection()
109+ instrumentingClassPath. setFrom(
110+ compileTaskClasspath,
111+ rawClassesDir,
112+ additionalClassPath,
113+ )
114+
115+ // This were the post processing happens, i.e. were the instrumentation is applied
116+ it. doLast(
117+ " instrumentClasses" ,
118+ project. objects. newInstance(
119+ InstrumentPostProcessingAction ,
120+ javaVersion,
121+ plugins,
122+ pluginClassPath,
123+ instrumentingClassPath,
124+ rawClassesDir,
125+ )
126+ )
127+ logger. info(" [InstrumentPlugin] Configured post-compile instrumentation for $compileTaskName for source-set $sourceSetName " )
93128 }
94129 }
95130 }
96131 }
97132
98- static findCompileClassPath (Project project , String taskName ) {
99- def matcher = taskName =~ / instrument([A-Z].+)Java/
100- def cfgName = matcher. matches() ? " ${ matcher.group(1).uncapitalize()} CompileClasspath" : ' compileClasspath'
101- project. configurations. named(cfgName). findAll {
102- it. name != ' previous-compilation-data.bin' && ! it. name. endsWith(' .gz' )
103- }
104- }
105-
106- static findAdditionalClassPath (InstrumentExtension extension , String taskName ) {
107- extension. additionalClasspath. getOrDefault(taskName, []). collect {
108- // insert intermediate 'raw' directory for unprocessed classes
109- def fileName = it. get(). asFile. name
110- it. get(). dir(" ../${ fileName.replaceFirst('^main', 'raw')} " )
133+ private static List<Provider<Directory > > findAdditionalClassPath (InstrumentExtension extension , String taskName ) {
134+ return extension. additionalClasspath. getOrDefault(taskName, []). collect { dirProperty ->
135+ dirProperty. map {
136+ def fileName = it. asFile. name
137+ it. dir(" ../${ fileName.replaceFirst('^main', 'raw')} " )
138+ }
111139 }
112140 }
113141}
@@ -117,42 +145,60 @@ abstract class InstrumentExtension {
117145 Map<String / * taskName */ , List<DirectoryProperty > > additionalClasspath = [:]
118146}
119147
120- abstract class InstrumentTask extends DefaultTask {
121- @Input @Optional
122- String javaVersion
123- @InputFiles @Classpath
124- abstract ConfigurableFileCollection getPluginClassPath ()
125- @Input
126- ListProperty<String > plugins
127- @InputFiles @Classpath
128- abstract ConfigurableFileCollection getInstrumentingClassPath ()
129- @InputDirectory
130- Directory sourceDirectory
131-
132- @OutputDirectory
133- Directory targetDirectory
134-
148+ abstract class InstrumentPostProcessingAction implements Action<AbstractCompile > {
149+ @Inject
150+ abstract Project getProject ()
135151 @Inject
136152 abstract JavaToolchainService getJavaToolchainService ()
137153 @Inject
138154 abstract BuildInvocationDetails getInvocationDetails ()
139155 @Inject
140156 abstract WorkerExecutor getWorkerExecutor ()
141157
142- @TaskAction
143- instrument () {
158+ final String javaVersion
159+ final ListProperty<String > plugins
160+ final ConfigurableFileCollection pluginClassPath
161+ final ConfigurableFileCollection instrumentingClassPath
162+ final DirectoryProperty rawClassesDirectory
163+
164+ @Inject
165+ InstrumentPostProcessingAction (
166+ String javaVersion ,
167+ ListProperty<String > plugins ,
168+ ConfigurableFileCollection pluginClassPath ,
169+ ConfigurableFileCollection instrumentingClassPath ,
170+ DirectoryProperty rawClassesDir
171+ ) {
172+ this . javaVersion = javaVersion
173+ this . plugins = plugins
174+ this . pluginClassPath = pluginClassPath
175+ this . instrumentingClassPath = instrumentingClassPath
176+ this . rawClassesDirectory = rawClassesDir
177+ }
178+
179+ @Override
180+ void execute (AbstractCompile task ) {
181+ logger. info(
182+ " values: " +
183+ " javaVersion=${ javaVersion} , " +
184+ " plugins=${ plugins.get()} , " +
185+ " pluginClassPath=${ pluginClassPath.files} , " +
186+ " instrumentingClassPath=${ instrumentingClassPath.files} , " +
187+ " rawClassesDirectory=${ rawClassesDirectory.get().asFile} "
188+ )
189+ def postCompileAction = this
144190 workQueue(). submit(InstrumentAction . class, parameters -> {
145- parameters. buildStartedTime. set(this . invocationDetails. buildStartedTime)
146- parameters. pluginClassPath. from(this . pluginClassPath)
147- parameters. plugins. set(this . plugins)
148- parameters. instrumentingClassPath. setFrom(this . instrumentingClassPath)
149- parameters. sourceDirectory . set(this . sourceDirectory . asFile )
150- parameters. targetDirectory . set(this . targetDirectory . asFile )
191+ parameters. buildStartedTime. set(invocationDetails. buildStartedTime)
192+ parameters. pluginClassPath. from(postCompileAction . pluginClassPath)
193+ parameters. plugins. set(postCompileAction . plugins)
194+ parameters. instrumentingClassPath. setFrom(postCompileAction . instrumentingClassPath)
195+ parameters. classesDirectory . set(postCompileAction . rawClassesDirectory )
196+ parameters. tmpDirectory . set(project . layout . buildDirectory . dir( " tmp/ ${ task.name } -raw-classes " ) )
151197 })
152198 }
153199
154200 private workQueue () {
155- if (this . javaVersion) {
201+ if (this . javaVersion != InstrumentPlugin . DEFAULT_JAVA_VERSION ) {
156202 def javaLauncher = this . javaToolchainService. launcherFor { spec ->
157203 spec. languageVersion. set(JavaLanguageVersion . of(this . javaVersion))
158204 }. get()
@@ -172,8 +218,8 @@ interface InstrumentWorkParameters extends WorkParameters {
172218 ConfigurableFileCollection getPluginClassPath ()
173219 ListProperty<String > getPlugins ()
174220 ConfigurableFileCollection getInstrumentingClassPath ()
175- DirectoryProperty getSourceDirectory ()
176- DirectoryProperty getTargetDirectory ()
221+ DirectoryProperty getClassesDirectory ()
222+ DirectoryProperty getTmpDirectory ()
177223}
178224
179225abstract class InstrumentAction implements WorkAction<InstrumentWorkParameters > {
@@ -199,10 +245,29 @@ abstract class InstrumentAction implements WorkAction<InstrumentWorkParameters>
199245 }
200246 }
201247 }
202- File sourceDirectory = parameters. getSourceDirectory(). get(). asFile
203- File targetDirectory = parameters. getTargetDirectory(). get(). asFile
248+ Path classesDirectory = parameters. classesDirectory. get(). asFile. toPath()
249+ Path tmpSourceDir = parameters. tmpDirectory. get(). asFile. toPath()
250+
251+ // Delete tmpSourceDir contents recursively
252+ if (tmpSourceDir. exists()) {
253+ Files . walk(tmpSourceDir)
254+ .sorted(Comparator . reverseOrder())
255+ .forEach { p ->
256+ if (! p. equals(tmpSourceDir)) {
257+ java.nio.file.Files . deleteIfExists(p)
258+ }
259+ }
260+ }
261+
262+ Files . move(
263+ classesDirectory,
264+ tmpSourceDir,
265+ StandardCopyOption . REPLACE_EXISTING ,
266+ StandardCopyOption . ATOMIC_MOVE ,
267+ )
268+
204269 ClassLoader instrumentingCL = createClassLoader(parameters. instrumentingClassPath, pluginCL)
205- InstrumentingPlugin . instrumentClasses(plugins, instrumentingCL, sourceDirectory, targetDirectory )
270+ InstrumentingPlugin . instrumentClasses(plugins, instrumentingCL, tmpSourceDir, classesDirectory )
206271 }
207272
208273 static ClassLoader createClassLoader (cp , parent = InstrumentAction . classLoader) {
0 commit comments