raphw/byte-buddy

net.bytebuddy.byte-buddy-gradle-plugin should support kotlin project as well

Closed this issue · 8 comments

hanrw commented

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)

and the issue seems relate to - Processing class files located in in: /snacks/build/classes/java/raw
which not using kotlin package, and related bytebuddy codes below

ByteBuddyTaskConfiguration

    @Override
    protected void configureDirectories(SourceDirectorySet source, JavaCompile compileTask, ByteBuddyTask byteBuddyTask) {
        try {
            DirectoryProperty directory = (DirectoryProperty) getDestinationDirectory.invoke(source);
            setDestinationDir.invoke(compileTask, directory.dir(RAW_FOLDER).map(ToFileMapper.INSTANCE));
            byteBuddyTask.getSource().set(directory.dir(RAW_FOLDER));
            byteBuddyTask.getTarget().set(directory);
            byteBuddyTask.getClassPath().from(compileTask.getClasspath());
        } catch (Exception exception) {
            throw new GradleException("Could not adjust directories for tasks", exception);
        }
    }
2022-07-18T13:01:24.857+0800 [INFO] [org.gradle.internal.execution.steps.ResolveCachingStateStep] Caching disabled for task ':byteBuddy' because:
  Build cache is disabled
2022-07-18T13:01:24.860+0800 [DEBUG] [org.gradle.internal.execution.steps.SkipUpToDateStep] Determining if task ':byteBuddy' is up-to-date
2022-07-18T13:01:24.860+0800 [INFO] [org.gradle.internal.execution.steps.SkipUpToDateStep] Task ':byteBuddy' is not up-to-date because:
  Output property 'target' file /snacks/build/classes/java/main has been removed.
2022-07-18T13:01:24.861+0800 [DEBUG] [org.gradle.internal.vfs.impl.AbstractVirtualFileSystem] Invalidating VFS paths: [/snacks/build/classes/java/main]
2022-07-18T13:01:24.862+0800 [DEBUG] [org.gradle.internal.execution.steps.CreateOutputsStep] Ensuring directory exists for property target at /snacks/build/classes/java/main
2022-07-18T13:01:24.863+0800 [INFO] [org.gradle.internal.execution.steps.ResolveInputChangesStep] The input changes require a full rebuild for incremental task ':byteBuddy'.
2022-07-18T13:01:24.863+0800 [DEBUG] [org.gradle.internal.file.impl.DefaultDeleter] Deleting /snacks/build/classes/java/main
2022-07-18T13:01:24.863+0800 [DEBUG] [org.gradle.api.internal.tasks.execution.TaskExecution] Executing actions for task ':byteBuddy'.
2022-07-18T13:01:24.868+0800 [DEBUG] [org.gradle.api.Task] Applying non-incremental build
2022-07-18T13:01:24.876+0800 [DEBUG] [org.gradle.api.Task] 1 plugins are being applied via configuration and discovery
2022-07-18T13:01:24.876+0800 [INFO] [org.gradle.api.Task] Resolved plugin: org.jmolecules.bytebuddy.JMoleculesPlugin
2022-07-18T13:01:24.975+0800 [INFO] [org.gradle.api.Task] Processing class files located in in: /snacks/build/classes/java/raw
2022-07-18T13:01:24.976+0800 [DEBUG] [org.gradle.api.Task] Java version was configured: 18
2022-07-18T13:01:24.984+0800 [WARN] [org.gradle.api.Task] No types were transformed during plugin execution
2022-07-18T13:01:24.984+0800 [DEBUG] [org.gradle.internal.operations.DefaultBuildOperationRunner] Build operation 'Execute apply for :byteBuddy' completed

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>
raphw commented

At this point, the Gradle plugin does unfortunately not support Kotlin. Gradle requires a lot of work to inject a task in the workflow, so only Java classes are automatically intercepted. But you can configure your own ByteBuddyTask when using the plugin. You would need to point it to the Kotlin output folder to instrument the classes.

I was however under the impression that Kotlin adds its source sets to the Java source sets. Maybe that's another way of doing it.

hanrw commented

At this point, the Gradle plugin does unfortunately not support Kotlin. Gradle requires a lot of work to inject a task in the workflow, so only Java classes are automatically intercepted. But you can configure your own ByteBuddyTask when using the plugin. You would need to point it to the Kotlin output folder to instrument the classes.

I was however under the impression that Kotlin adds its source sets to the Java source sets. Maybe that's another way of doing it.

Yep, ByteBuddyTask works for kotlin by specific output folder

At this point, the Gradle plugin does unfortunately not support Kotlin. Gradle requires a lot of work to inject a task in the workflow, so only Java classes are automatically intercepted. But you can configure your own ByteBuddyTask when using the plugin. You would need to point it to the Kotlin output folder to instrument the classes.

I was however under the impression that Kotlin adds its source sets to the Java source sets. Maybe that's another way of doing it.

And how do I point the ByteBuddyTask to the Kotlin output folder? I messed arround with gradle sourceSets but I can't get it working. I would appreciate if you have a code example for me.

raphw commented

You would need to get hold of the task and change its target source property: https://github.com/raphw/byte-buddy/blob/master/byte-buddy-gradle-plugin/src/main/java/net/bytebuddy/build/gradle/ByteBuddyTask.java#L66

It's probably possible by reading the task and to call that setter. Easier, you would probably just create new task and set it up in its entirety.

Easier, you would probably just create new task and set it up in its entirety.

What do you mean by that? Should I create a Gradle Task an call the ByteBuddy Java API directly instead of using the ByteBuddy Gradle Plugin? Or is there a way to set up the source directory of the ByteBuddy Plugin in a new task? Unfortunately I have not much experience with Gradle Plugins. It would be great if you could provide me an example code of how to setup the source of the ByteBuddy Plugin.

raphw commented

Unfortunately, I have little experience with Gradle and Kotlin. But you could create your own ByteBuddyTask and hook it into the task chain.

Do you mean that I should create a Gradle Task like that:

task("custom bytebuddy") {

    sourceSets.getByName("main").java.srcDirs("build/kotlin")
    byteBuddy {
        transformation {
            // Needs to be declared explicitly
            plugin = org.jmolecules.bytebuddy.JMoleculesPlugin::class.java
        }
    }

}

Or should I extend the ByteBuddy Java class and then wire this Custom class up in Gradle?
A Groovy / Java example would be perfectly fine to me.

raphw commented

I think you have to specify the task type: ByteBuddyTask and then invoke the appropriate setters. But I would consult a Gradle forum for that, I am not a specialist on Gradle.