Define class on plugin class loader

For my Byte Buddy plugin, I ask users to supply one or more user plugin classes to the Byte Buddy plugin which is run by my plugin during execution. Up to Gradle 7.* it was possible to define a class inline in the build script and hand it to the Byte Buddy Gradle plugin such as:

plugins {
  id 'java'
  id 'net.bytebuddy.byte-buddy-gradle-plugin'
}
import net.bytebuddy.build.Plugin;
class SamplePlugin implements Plugin { ... }
byteBuddy {
  transformation {",
    plugin = SamplePlugin.class",
  }
}

After updating to Gradle 8.* this is no longer possible, the Byte Buddy Plugin class is no longer visible in the script. To fix this, I tried to move the plugin definition to a buildSrc project. This allows me to define the class after depending on byte-buddy from there. I can now create the plugin, but it seems like the class loader of buildSrc is different to the one of the plugin as the assignment now causes a class cast exception within the plugin’s execution.

So my question is: can I still define a class dynamically to be loaded on the plugin’s class loader? Or has this become impossible.

What makes you think it does not work anymore?
I pasted into an empty 8.4 build script

plugins {
   id 'java'
   id 'net.bytebuddy.byte-buddy-gradle-plugin' version '+'
}
import net.bytebuddy.description.type.TypeDescription
import net.bytebuddy.dynamic.ClassFileLocator
import net.bytebuddy.dynamic.DynamicType
import net.bytebuddy.build.Plugin
class SamplePlugin implements Plugin {
   @Override
   DynamicType.Builder<?> apply(DynamicType.Builder<?> builder, TypeDescription typeDescription, ClassFileLocator classFileLocator) {
      println("FOO")
      return null
   }

   @Override
   void close() throws IOException {
      println("BAR")
   }

   @Override
   boolean matches(TypeDescription target) {
      println("BAZ")
      return false
   }
}
byteBuddy {
   transformation {
      plugin = SamplePlugin
   }
}

and got

> Task :byteBuddy
BAZ
BAZ
BAR
No types were transformed during plugin execution

Btw. yes, if you have that in buildSrc, buildSrc things are put to a class loader that is parent of the build script class loaders.

So if you have in buildSrc (or the dependencies declared there) something that wants to access classes only available in the build script class loader, it cannot find them as they are in a lower class loader in the hierarchy. You would either need to also put that dependency to buildSrc for example as runtimeOnly, or use an included build instead of buildSrc which then is part of the normal build script classpath and thus in the build script class loader.

You are right. I was taking this out of the integration tests where I run:

BuildResult result = GradleRunner.create()
        .withProjectDir(folder)
        .withArguments("build")
        .withPluginClasspath()
        .build();

Here it does not work anymore since the update. Is there any additional setup needed?

I now tried with

package foo

import org.gradle.testkit.runner.GradleRunner
import spock.lang.Specification
import spock.lang.TempDir
import spock.util.io.FileSystemFixture

class FooTest extends Specification {
   @TempDir
   FileSystemFixture rootDir

   def foo() {
      given:
         rootDir.create {
            file('settings.gradle.kts') << '''
               rootProject.name = "functional-testee"
            '''.stripIndent(true)

            file('build.gradle') << '''
               plugins {
                  id 'java'
                  id 'net.bytebuddy.byte-buddy-gradle-plugin'
               }
               import net.bytebuddy.description.type.TypeDescription
               import net.bytebuddy.dynamic.ClassFileLocator
               import net.bytebuddy.dynamic.DynamicType
               import net.bytebuddy.build.Plugin
               class SamplePlugin implements Plugin {
                  @Override
                  DynamicType.Builder<?> apply(DynamicType.Builder<?> builder, TypeDescription typeDescription, ClassFileLocator classFileLocator) {
                     println("FOO")
                     return null
                  }

                  @Override
                  void close() throws IOException {
                     println("BAR")
                  }

                  @Override
                  boolean matches(TypeDescription target) {
                     println("BAZ")
                     return false
                  }
               }
               byteBuddy {
                  transformation {
                     plugin = SamplePlugin
                  }
               }
            '''.stripIndent(true)

            file('src/main/java/Foo.java') << '''
               class Foo {}
            '''.stripIndent(true)
         }

      expect:
         GradleRunner
               .create()
               .forwardOutput()
               .withProjectDir(rootDir.currentPath.toFile())
               .withArguments('build')
               .withPluginClasspath(new File(System.getProperty('bb')).listFiles().toList())
               .withGradleVersion('8.4')
               .build()
   }
}

where the bb system property is a directory with the resolution result of net.bytebuddy.byte-buddy-gradle-plugin:net.bytebuddy.byte-buddy-gradle-plugin.gradle.plugin:+ in it.
Also works just fine:

> Task :byteBuddy
BAZ
BAR
No types were transformed during plugin execution

Can you maybe provide some MCVE, or push your current state to some branch?