Closed hanrw closed 2 years ago
and reproducer project - issue-6.zip
Looks like you already got the root cause of this here. Would've been nice if you had left a clue here as I just spent an hour investigating the issue, summarizing my findings, just to discover you already filed a ticket with ByteBuddy. 😕
And tried to create a new plugin it works, but not working well - code will not generate corectlly. seems still some issue with it.
import net.bytebuddy.ClassFileVersion
import net.bytebuddy.build.EntryPoint
import net.bytebuddy.build.gradle.ByteBuddyTask
import net.bytebuddy.build.gradle.Discovery
import net.bytebuddy.build.gradle.IncrementalResolver
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.file.Directory
import org.gradle.api.tasks.compile.AbstractCompile
import org.jmolecules.bytebuddy.JMoleculesPlugin
class ByteBuddyKotlinPlugin implements Plugin<Project> {
@Override
void apply(Project project) {
println ("ByteBuddyKotlinPlugin.apply()")
project.tasks.matching { it.name in ['compileJava', 'compileKotlin'] }.all {
def compileTask = it as AbstractCompile
project.afterEvaluate {
if (!compileTask.source.empty) {
String instrumentName = compileTask.name.replace('compile', 'instrument')
ByteBuddyTask byteBuddyTask = project.tasks.create(instrumentName, ByteBuddyTask)
byteBuddyTask.group = 'Byte Buddy'
byteBuddyTask.description = "Instruments the classes compiled by ${compileTask.name}"
byteBuddyTask.entryPoint = EntryPoint.Default.REBASE
byteBuddyTask.suffix = ''
byteBuddyTask.failOnLiveInitializer = true
byteBuddyTask.warnOnEmptyTypeSet = true
byteBuddyTask.failFast = false
byteBuddyTask.extendedParsing = false
byteBuddyTask.discovery = Discovery.NONE
byteBuddyTask.threads = 0
byteBuddyTask.classFileVersion = ClassFileVersion.JAVA_V17
byteBuddyTask.incrementalResolver = IncrementalResolver.ForChangedFiles.INSTANCE
// use intermediate 'raw' directory for unprocessed classes
Directory classesDir = compileTask.destinationDirectory.get()
Directory rawClassesDir = classesDir.dir('../raw/')
byteBuddyTask.source = rawClassesDir
byteBuddyTask.target = classesDir
compileTask.destinationDir = rawClassesDir.asFile
byteBuddyTask.classPath.from(project.configurations.compileClasspath + compileTask.destinationDir)
byteBuddyTask.transformation {
it.plugin = JMoleculesPlugin
it.argument({ it.value = byteBuddyTask.classPath.collect({ it.toURI() as String }) })
it.argument({ it.value = byteBuddyTask.target.get().asFile.path }) // must serialize as String
}
// insert task between compile and jar, and before test*
byteBuddyTask.dependsOn(compileTask)
project.tasks.named(project.sourceSets.main.classesTaskName).configure {
dependsOn(byteBuddyTask)
}
}
}
}
}
}
Feel free to add this detection to the official plugin. Gradle is a bit messy in the sense that you need to adjust the build for any JVM language (or more, if there are multiple plugins for it). The JavaPlugin
is part of the official Gradle API, but it should not be much work to add something similar for Kotlin or even Scala. It would need to be added here:
I would do it myself but I am not using Kotlin myself and therefore it's not a priority.
Thanks. will try it first.
https://github.com/raphw/byte-buddy/issues/1284 implies that jmolecules gradle plugin is working with Kotlin by pointing byte buddy to the kotlin output directory. But I still don't get this running. @hanrw could you please provide a working build.gradle.kts? That would be great.
@jason076 I just did some tests for that but not working well Here's some code
import net.bytebuddy.ClassFileVersion
import net.bytebuddy.build.EntryPoint
import net.bytebuddy.build.gradle.ByteBuddyTask
import net.bytebuddy.build.gradle.Discovery
import net.bytebuddy.build.gradle.IncrementalResolver
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.file.Directory
import org.gradle.api.tasks.compile.AbstractCompile
import org.jmolecules.bytebuddy.JMoleculesPlugin
class ByteBuddyKotlinPlugin implements Plugin
@Override
void apply(Project project) {
println ("ByteBuddyKotlinPlugin.apply()")
project.tasks.matching { it.name in ['compileJava', 'compileScala', 'compileKotlin'] }.all {
def compileTask = it as AbstractCompile
project.afterEvaluate {
if (!compileTask.source.empty) {
String instrumentName = compileTask.name.replace('compile', 'instrument')
ByteBuddyTask byteBuddyTask = project.tasks.create(instrumentName, ByteBuddyTask)
byteBuddyTask.group = 'Byte Buddy'
byteBuddyTask.description = "Instruments the classes compiled by ${compileTask.name}"
byteBuddyTask.entryPoint = EntryPoint.Default.REBASE
byteBuddyTask.suffix = ''
byteBuddyTask.failOnLiveInitializer = true
byteBuddyTask.warnOnEmptyTypeSet = true
byteBuddyTask.failFast = false
byteBuddyTask.extendedParsing = false
byteBuddyTask.discovery = Discovery.NONE
byteBuddyTask.threads = 0
byteBuddyTask.classFileVersion = ClassFileVersion.JAVA_V17
byteBuddyTask.incrementalResolver = IncrementalResolver.ForChangedFiles.INSTANCE
// use intermediate 'raw' directory for unprocessed classes
Directory classesDir = compileTask.destinationDirectory.get()
Directory rawClassesDir = classesDir.dir('../raw/')
byteBuddyTask.source = rawClassesDir
byteBuddyTask.target = classesDir
compileTask.destinationDir = rawClassesDir.asFile
byteBuddyTask.classPath.from(project.configurations.compileClasspath + compileTask.destinationDir)
byteBuddyTask.transformation {
it.plugin = JMoleculesPlugin
it.argument({ it.value = byteBuddyTask.classPath.collect({ it.toURI() as String }) })
it.argument({ it.value = byteBuddyTask.target.get().asFile.path }) // must serialize as String
}
// insert task between compile and jar, and before test*
byteBuddyTask.dependsOn(compileTask)
project.tasks.named(project.sourceSets.main.classesTaskName).configure {
dependsOn(byteBuddyTask)
}
}
}
}
}
}
2. create buildSrc/src/main/resources/META-INF/gradle-plugins/ByteBuddyKotlinPlugin.properties
`implementation-class=com.bytebuddy.plugin.ByteBuddyKotlinPlugin`
3. apply plugin - build.gradle.kts
plugins { ByteBuddyKotlinPlugin id("org.jetbrains.kotlin.plugin.allopen") version "1.6.20" id("org.springframework.boot") version "2.7.1" id("io.spring.dependency-management") version "1.0.11.RELEASE" kotlin("jvm") version "1.6.20" id("org.jetbrains.kotlinx.kover") version "0.5.1" kotlin("plugin.spring") version "1.6.21" kotlin("plugin.jpa") version "1.6.21" }
So what the Byte Buddy plugin for Gradle does is that it finds the classes folder of the Java compiler plugin. It then redefines the target directory of the previous plugin. It then depends on the Java compiler plugin, takes the now moved folder as its input and defines the previously defined folder as its output. It then finds all tasks that depend on the Java compiler plugin and makes them depend on itself.
Gradle does not offer a good way to inject a task into the middle of two tasks, that's why this is required, unfortunately. Also, due to incremental compilation, each task needs a clear output folder. This is why - in contrast to Maven - all needs to be implemented tool chain specific. I have never looked into Kotlin, but ideally it would be supported by BB out of the box. BB Gradle already offers discovery for the Android "build flavour" of Gradle and similarly, one could add support for Kotlin. If you wanted to look into this and contribute it to Byte Buddy, I am happy to guide you through.
You would need to start out by adding the KotlinPlugin
where currently, only the JavaPlugin
is discovered.
From there, you would need to configure the plugin similarly to the Java plugin using its convention.
This is already implemented using reflection for Java, as Gradle switched from extensions to conventions and I did not want to break Gradle usage for people who do not run the latest Gradle version, so it should look very similar for Kotlin. Your change could therefor start out with something like:
try {
Class<?> kotlin = Class.forName("some.KotlinPlugin");
project.getPlugins().withType(kotlin, new KotlinPluginConfigurationAction(project));
} catch(ClassNotFoundException exception) {
getLogger().trace("Did not discover Kotlin plugin", exception);
}
If you give me a first draft of this, and ideally some basic unit test, I can do the rest and round it up.
Your attachment point would likely be the Kotlin base plugin class
Just want using gradle kotlin to run the example but not working error logs
build.gradle.kts configs below
kotlin application