raphw / byte-buddy

Runtime code generation for the Java virtual machine.
https://bytebuddy.net
Apache License 2.0
6.3k stars 808 forks source link

Gradle: Classpath elements not on classpath during plugin execution #1726

Open LarsBodewig opened 2 weeks ago

LarsBodewig commented 2 weeks ago

Multiple byte-buddy gradle tasks annotate an input property as classpath but the input is not actually used as the classpath for execution.

I tried to load some classes in my custom plugin, expecting to find them on the classpath, however they are never available, unless they are also part of an origin or used for discovery.

I understand that byte-buddy does not actually need any other classpath elements that are not part of an origin, so I am not sure if it would be smart to load more, however the use of the annotation makes this a bit misleading.

Would it be possible to load all classes that are configured as classpath? Or idk run the plugins in a custom classloader that queries the ClassFileLocator?

raphw commented 2 weeks ago

You mean this configuration? There are multiple ways to configure the Gradle plugin. You can specify a name and then the plugin will be resolved from within the plugin's class path, or you can configure the plugin and its dependencies directly. How did you approach this?

LarsBodewig commented 2 weeks ago

Judging from the name, I expected the classpath property to allow arbitrary classes to be supplied and accessed in the plugin.

buildscript {
    dependencies {
        classpath 'net.bytebuddy:byte-buddy-gradle-plugin:1.15.0'
        classpath 'net.bytebuddy:byte-buddy:1.15.0'
    }
}

configurations {
    myCustomClasspath
}

dependencies {
    myCustomClasspath 'my:special:jar' // containing my.special.jar.MySpecialClass
}

import net.bytebuddy.build.Plugin
import net.bytebuddy.build.gradle.ByteBuddyJarTask
import net.bytebuddy.description.type.TypeDescription
import net.bytebuddy.dynamic.ClassFileLocator
import net.bytebuddy.dynamic.DynamicType

class SimplePlugin implements Plugin {
    @Override
    void close() throws IOException {
    }

    @Override
    DynamicType.Builder<?> apply(DynamicType.Builder<?> builder, TypeDescription typeDescription, ClassFileLocator classFileLocator) {
        Class.forName("my.special.jar.MySpecialClass") // fails since the myCustomClasspath configuration is not resolved/available
        return builder
    }

    @Override
    boolean matches(TypeDescription typeDefinitions) {
        return true
    }
}

tasks.register("myByteBuddyTask", ByteBuddyJarsTask) {
    source = files("libs")
    target = file("transformedLibs")
    classPath = project.getConfigurations().getByName("myCustomClasspath")
    transformation {
        plugin = SimplePlugin
    }
}

The Class.forName() is just a simple example. I actually want to detect classes from 'my:special:jar' that have a special annotation.

Right now I have to include 'my:special:jar' in the buildscript classpath to be available in the SimplePlugin.

buildscript {
    dependencies {
        classpath 'net.bytebuddy:byte-buddy-gradle-plugin:1.15.0'
        classpath 'net.bytebuddy:byte-buddy:1.15.0'
        classpath 'my:special:jar'
    }
}

However I try to avoid that since those classes are not needed outside of the custom byte-buddy plugin.

As far as I understand, that is exactly the purpose of the classpath property in gradle tasks like JavaExec - using classes during task runtime but not script compilation. Is there a better way to access arbitrary classes from my plugin?

LarsBodewig commented 22 hours ago

I tried creating a custom classloader that reads the byte[] from the class file locator. But I always get java.lang.ClassFormatError: Truncated class file.

Does the class file locator do anything special with the class files or are my classes the issue?

raphw commented 17 hours ago

You can also configure dependencies in the transformation block. I think this is where your additional dependencies need to go. The class path property is mostly used for reading.