jmolecules-kotlin with build.gradle.kts not working
hanrw opened this issue · 8 comments
Just want using gradle kotlin to run the example but not working
error logs
2022-07-16 23:05:52.505 INFO 10203 --- [ main] com.snacks.ApplicationKt : Started ApplicationKt in 2.37 seconds (JVM running for 2.533)
Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.snacks.OrderRepository' available
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:351)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:342)
at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1172)
at com.snacks.ApplicationKt.main(Application.kt:17)
build.gradle.kts configs below
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
buildscript {
dependencies {
classpath(platform("org.jmolecules:jmolecules-bom:2022.2.0"))
classpath("org.jmolecules.integrations:jmolecules-bytebuddy")
classpath("org.jmolecules.integrations:jmolecules-spring")
classpath("org.jmolecules.integrations:jmolecules-jpa")
}
}
plugins {
id("org.springframework.boot") version "2.7.0"
id("io.spring.dependency-management") version "1.0.11.RELEASE"
kotlin("jvm") version "1.6.21"
id("org.jetbrains.kotlinx.kover") version "0.5.1"
kotlin("plugin.spring") version "1.6.21"
kotlin("plugin.jpa") version "1.6.21"
id("net.bytebuddy.byte-buddy-gradle-plugin") version "1.12.12"
idea
}
group = "com.example"
version = "0.0.1-SNAPSHOT"
java.sourceCompatibility = JavaVersion.VERSION_17
repositories {
mavenCentral()
}
dependencies {
implementation(platform("org.jmolecules:jmolecules-bom:2022.2.0"))
implementation("net.bytebuddy:byte-buddy-gradle-plugin:1.12.10")
implementation("org.jmolecules.integrations:jmolecules-bytebuddy")
implementation("org.jmolecules.integrations:jmolecules-spring")
implementation("org.jmolecules.integrations:jmolecules-jpa")
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
implementation("org.springframework.session:spring-session-jdbc")
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
implementation("org.jmolecules:kmolecules-ddd")
implementation("org.springdoc:springdoc-openapi-ui:1.6.9")
implementation("org.springdoc:springdoc-openapi-kotlin:1.6.9")
implementation("com.vladmihalcea:hibernate-types-55:2.16.2")
implementation("org.projectlombok:lombok")
runtimeOnly("mysql:mysql-connector-java:8.0.29")
implementation("com.h2database:h2:2.1.212")
testImplementation("org.mockito.kotlin:mockito-kotlin:4.0.0")
testImplementation("org.springframework.boot:spring-boot-starter-test")
}
tasks.withType<KotlinCompile> {
kotlinOptions {
freeCompilerArgs = listOf("-Xjsr305=strict")
jvmTarget = "17"
}
}
tasks.withType<Test> {
useJUnitPlatform()
}
tasks {
byteBuddy {
transformation {
plugin = org.jmolecules.bytebuddy.JMoleculesPlugin::class.java
}
}
compileKotlin {
kotlinOptions {
freeCompilerArgs = listOf("-Xjsr305=strict")
jvmTarget = "17"
}
}
test {
extensions.configure(kotlinx.kover.api.KoverTaskExtension::class) {
isEnabled = true
binaryReportFile.set(file("$buildDir/kover/test.exec"))
}
useJUnitPlatform()
// testLogging {
// events("passed", "skipped", "failed")
// }
finalizedBy(koverHtmlReport)
}
koverHtmlReport {
isEnabled = true
htmlReportDir.set(layout.buildDirectory.dir("reports/kover/html"))
}
}
kotlin application
@SpringBootApplication
open class Application
fun main(args: Array<String>) {
val context = runApplication<Application>(*args)
val repository = context.getBean(OrderRepository::class.java)
val order = repository.save(Order())
}
@Table(name = "KotlinOrder")
open class Order : AggregateRoot<Order, Order.OrderIdentifier> {
override val id = OrderIdentifier(UUID.randomUUID())
data class OrderIdentifier(val id: UUID) : Identifier
}
interface OrderRepository : CrudRepository<Order, Order.OrderIdentifier>
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.
raphw/byte-buddy#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
- create a plugin under buildSrc/src/main/groovy/com/bytebuddy/plugin/ByteBuddyKotlinPlugin.groovy
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', '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)
}
}
}
}
}
}
- create buildSrc/src/main/resources/META-INF/gradle-plugins/ByteBuddyKotlinPlugin.properties
implementation-class=com.bytebuddy.plugin.ByteBuddyKotlinPlugin
- 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