Migrate codegen to be a plugin
DELETE
buildSrc/src/main/java/io/opentelemetry/instrumentation/gradle/bytebuddy/ByteBuddyPluginConfigurator.java
-/*
- * Copyright The OpenTelemetry Authors
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package io.opentelemetry.instrumentation.gradle.bytebuddy;
-
-import java.io.File;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Objects;
-import java.util.stream.Collectors;
-import net.bytebuddy.build.gradle.ByteBuddySimpleTask;
-import net.bytebuddy.build.gradle.Transformation;
-import org.gradle.api.Project;
-import org.gradle.api.Task;
-import org.gradle.api.file.FileCollection;
-import org.gradle.api.tasks.SourceSet;
-import org.gradle.api.tasks.TaskProvider;
-import org.gradle.api.tasks.compile.AbstractCompile;
-
-/**
- * Starting from version 1.10.15, ByteBuddy gradle plugin transformation task autoconfiguration is
- * hardcoded to be applied to javaCompile task. This causes the dependencies to be resolved during
- * an afterEvaluate that runs before any afterEvaluate specified in the build script, which in turn
- * makes it impossible to add dependencies in afterEvaluate. Additionally the autoconfiguration will
- * attempt to scan the entire project for tasks which depend on the compile task, to make each task
- * that depends on compile also depend on the transformation task. This is an extremely inefficient
- * operation in this project to the point of causing a stack overflow in some environments.
- *
- * <p>To avoid all the issues with autoconfiguration, this class manually configures the ByteBuddy
- * transformation task. This also allows it to be applied to source languages other than Java. The
- * transformation task is configured to run between the compile and the classes tasks, assuming no
- * other task depends directly on the compile task, but instead other tasks depend on classes task.
- * Contrary to how the ByteBuddy plugin worked in versions up to 1.10.14, this changes the compile
- * task output directory, as starting from 1.10.15, the plugin does not allow the source and target
- * directories to be the same. The transformation task then writes to the original output directory
- * of the compile task.
- */
-public class ByteBuddyPluginConfigurator {
- private static final List<String> LANGUAGES = Arrays.asList("java", "scala", "kotlin");
-
- private final Project project;
- private final SourceSet sourceSet;
- private final String pluginClassName;
- private final FileCollection inputClasspath;
-
- public ByteBuddyPluginConfigurator(
- Project project, SourceSet sourceSet, String pluginClassName, FileCollection inputClasspath) {
- this.project = project;
- this.sourceSet = sourceSet;
- this.pluginClassName = pluginClassName;
-
- // add build resources dir to classpath if it's present
- File resourcesDir = sourceSet.getOutput().getResourcesDir();
- this.inputClasspath =
- resourcesDir == null ? inputClasspath : inputClasspath.plus(project.files(resourcesDir));
- }
-
- public void configure() {
- String taskName = getTaskName();
-
- List<TaskProvider<?>> languageTasks =
- LANGUAGES.stream()
- .map(
- language -> {
- if (project.fileTree("src/" + sourceSet.getName() + "/" + language).isEmpty()) {
- return null;
- }
- String compileTaskName = sourceSet.getCompileTaskName(language);
- if (!project.getTasks().getNames().contains(compileTaskName)) {
- return null;
- }
- TaskProvider<?> compileTask = project.getTasks().named(compileTaskName);
-
- // We also process resources for SPI classes.
- return createLanguageTask(
- compileTask, taskName + language, sourceSet.getProcessResourcesTaskName());
- })
- .filter(Objects::nonNull)
- .collect(Collectors.toList());
-
- TaskProvider<?> byteBuddyTask =
- project.getTasks().register(taskName, task -> task.dependsOn(languageTasks));
-
- project
- .getTasks()
- .named(sourceSet.getClassesTaskName())
- .configure(task -> task.dependsOn(byteBuddyTask));
- }
-
- private TaskProvider<?> createLanguageTask(
- TaskProvider<?> compileTaskProvider, String name, String processResourcesTaskName) {
- return project
- .getTasks()
- .register(
- name,
- ByteBuddySimpleTask.class,
- task -> {
- task.setGroup("Byte Buddy");
- task.getOutputs().cacheIf(unused -> true);
-
- Task maybeCompileTask = compileTaskProvider.get();
- if (maybeCompileTask instanceof AbstractCompile) {
- AbstractCompile compileTask = (AbstractCompile) maybeCompileTask;
- File classesDirectory = compileTask.getDestinationDirectory().getAsFile().get();
- File rawClassesDirectory =
- new File(classesDirectory.getParent(), classesDirectory.getName() + "raw")
- .getAbsoluteFile();
-
- task.dependsOn(compileTask);
- compileTask.getDestinationDirectory().set(rawClassesDirectory);
-
- task.setSource(rawClassesDirectory);
- task.setTarget(classesDirectory);
- task.setClassPath(compileTask.getClasspath());
-
- task.dependsOn(compileTask, processResourcesTaskName);
- }
-
- task.getTransformations().add(createTransformation(inputClasspath, pluginClassName));
- });
- }
-
- private String getTaskName() {
- if (SourceSet.MAIN_SOURCE_SET_NAME.equals(sourceSet.getName())) {
- return "byteBuddy";
- } else {
- return sourceSet.getName() + "ByteBuddy";
- }
- }
-
- private static Transformation createTransformation(
- FileCollection classPath, String pluginClassName) {
- Transformation transformation = new ClasspathTransformation(classPath, pluginClassName);
- transformation.setPlugin(ClasspathByteBuddyPlugin.class);
- return transformation;
- }
-}

DELETE
buildSrc/src/main/java/io/opentelemetry/instrumentation/gradle/bytebuddy/ClasspathByteBuddyPlugin.java
-/*
- * Copyright The OpenTelemetry Authors
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package io.opentelemetry.instrumentation.gradle.bytebuddy;
-
-import java.io.File;
-import java.io.IOException;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.net.URLClassLoader;
-import java.util.ArrayList;
-import java.util.List;
-import net.bytebuddy.ByteBuddy;
-import net.bytebuddy.build.Plugin;
-import net.bytebuddy.description.type.TypeDescription;
-import net.bytebuddy.dynamic.ClassFileLocator;
-import net.bytebuddy.dynamic.DynamicType;
-
-/**
- * Starting from version 1.10.15, ByteBuddy gradle plugin transformations require that plugin
- * classes are given as class instances instead of a class name string. To be able to still use a
- * plugin implementation that is not a buildscript dependency, this reimplements the previous logic
- * by taking a delegate class name and class path as arguments and loading the plugin class from the
- * provided classloader when the plugin is instantiated.
- */
-public class ClasspathByteBuddyPlugin implements Plugin {
- private final Plugin delegate;
-
- /**
- * classPath and className argument resolvers are explicitly added by {@link
- * ClasspathTransformation}, sourceDirectory is automatically resolved as by default any {@link
- * File} argument is resolved to source directory.
- */
- public ClasspathByteBuddyPlugin(
- Iterable<File> classPath, File sourceDirectory, String className) {
- this.delegate = pluginFromClassPath(classPath, sourceDirectory, className);
- }
-
- @Override
- public DynamicType.Builder<?> apply(
- DynamicType.Builder<?> builder,
- TypeDescription typeDescription,
- ClassFileLocator classFileLocator) {
-
- return delegate.apply(builder, typeDescription, classFileLocator);
- }
-
- @Override
- public void close() throws IOException {
- delegate.close();
- }
-
- @Override
- public boolean matches(TypeDescription typeDefinitions) {
- return delegate.matches(typeDefinitions);
- }
-
- private static Plugin pluginFromClassPath(
- Iterable<File> classPath, File sourceDirectory, String className) {
- try {
- ClassLoader classLoader = classLoaderFromClassPath(classPath, sourceDirectory);
- Class<?> clazz = Class.forName(className, false, classLoader);
- return (Plugin) clazz.getDeclaredConstructor().newInstance();
- } catch (Exception e) {
- throw new IllegalStateException("Failed to create ByteBuddy plugin instance", e);
- }
- }
-
- private static ClassLoader classLoaderFromClassPath(
- Iterable<File> classPath, File sourceDirectory) {
- List<URL> urls = new ArrayList<>();
- urls.add(fileAsUrl(sourceDirectory));
-
- for (File file : classPath) {
- urls.add(fileAsUrl(file));
- }
-
- return new URLClassLoader(urls.toArray(new URL[0]), ByteBuddy.class.getClassLoader());
- }
-
- private static URL fileAsUrl(File file) {
- try {
- return file.toURI().toURL();
- } catch (MalformedURLException e) {
- throw new IllegalStateException("Cannot resolve " + file + " as URL", e);
- }
- }
-}

DELETE
buildSrc/src/main/java/io/opentelemetry/instrumentation/gradle/bytebuddy/ClasspathTransformation.java
-/*
- * Copyright The OpenTelemetry Authors
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package io.opentelemetry.instrumentation.gradle.bytebuddy;
-
-import java.io.File;
-import java.util.Arrays;
-import java.util.List;
-import net.bytebuddy.build.Plugin.Factory.UsingReflection.ArgumentResolver;
-import net.bytebuddy.build.gradle.Transformation;
-import org.gradle.api.tasks.Classpath;
-import org.gradle.api.tasks.Input;
-
-/**
- * Special implementation of {@link Transformation} is required as classpath argument must be
- * exposed to Gradle via {@link Classpath} annotation, which cannot be done if it is returned by
- * {@link Transformation#getArguments()}.
- */
-public class ClasspathTransformation extends Transformation {
- private final Iterable<File> classpath;
- private final String pluginClassName;
-
- public ClasspathTransformation(Iterable<File> classpath, String pluginClassName) {
- this.classpath = classpath;
- this.pluginClassName = pluginClassName;
- }
-
- @Classpath
- public Iterable<? extends File> getClasspath() {
- return classpath;
- }
-
- @Input
- public String getPluginClassName() {
- return pluginClassName;
- }
-
- protected List<ArgumentResolver> makeArgumentResolvers() {
- return Arrays.asList(
- new ArgumentResolver.ForIndex(0, classpath),
- new ArgumentResolver.ForIndex(2, pluginClassName));
- }
-}

ADD
buildSrc/src/main/kotlin/io/opentelemetry/instrumentation/gradle/codegen/ClasspathByteBuddyPlugin.kt
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package io.opentelemetry.instrumentation.gradle.codegen
+
+import net.bytebuddy.ByteBuddy
+import net.bytebuddy.build.Plugin
+import net.bytebuddy.description.type.TypeDescription
+import net.bytebuddy.dynamic.ClassFileLocator
+import net.bytebuddy.dynamic.DynamicType
+import java.io.File
+import java.net.URL
+import java.net.URLClassLoader
+
+/**
+ * Starting from version 1.10.15, ByteBuddy gradle plugin transformations require that plugin
+ * classes are given as class instances instead of a class name string. To be able to still use a
+ * plugin implementation that is not a buildscript dependency, this reimplements the previous logic
+ * by taking a delegate class name and class path as arguments and loading the plugin class from the
+ * provided classloader when the plugin is instantiated.
+ */
+class ClasspathByteBuddyPlugin(
+ classPath: Iterable<File>, sourceDirectory: File, className: String
+) : Plugin {
+ private val delegate = pluginFromClassPath(classPath, sourceDirectory, className)
+
+ override fun apply(
+ builder: DynamicType.Builder<*>,
+ typeDescription: TypeDescription,
+ classFileLocator: ClassFileLocator
+ ): DynamicType.Builder<*> {
+ return delegate.apply(builder, typeDescription, classFileLocator)
+ }
+
+ override fun close() {
+ delegate.close()
+ }
+
+ override fun matches(typeDefinitions: TypeDescription): Boolean {
+ return delegate.matches(typeDefinitions)
+ }
+
+ companion object {
+ private fun pluginFromClassPath(
+ classPath: Iterable<File>, sourceDirectory: File, className: String
+ ): Plugin {
+ val classLoader = classLoaderFromClassPath(classPath, sourceDirectory)
+ try {
+ val clazz = Class.forName(className, false, classLoader)
+ return clazz.getDeclaredConstructor().newInstance() as Plugin
+ } catch (e: Exception) {
+ throw IllegalStateException("Failed to create ByteBuddy plugin instance", e)
+ }
+ }
+
+ private fun classLoaderFromClassPath(
+ classPath: Iterable<File>, sourceDirectory: File
+ ): ClassLoader {
+ val urls = mutableListOf<URL>()
+ urls.add(fileAsUrl(sourceDirectory))
+ for (file in classPath) {
+ urls.add(fileAsUrl(file))
+ }
+ return URLClassLoader(urls.toTypedArray(), ByteBuddy::class.java.classLoader)
+ }
+
+ private fun fileAsUrl(file: File): URL {
+ return file.toURI().toURL()
+ }
+ }
+}

ADD
buildSrc/src/main/kotlin/io/opentelemetry/instrumentation/gradle/codegen/ClasspathTransformation.kt
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package io.opentelemetry.instrumentation.gradle.codegen
+
+import net.bytebuddy.build.Plugin.Factory.UsingReflection.ArgumentResolver
+import net.bytebuddy.build.gradle.Transformation
+import org.gradle.api.tasks.Classpath
+import org.gradle.api.tasks.Input
+import java.io.File
+
+/**
+ * Special implementation of [Transformation] is required as classpath argument must be
+ * exposed to Gradle via [Classpath] annotation, which cannot be done if it is returned by
+ * [Transformation.getArguments].
+ */
+class ClasspathTransformation(
+ @get:Classpath val classpath: Iterable<File>,
+ @get:Input val pluginClassName: String
+) : Transformation() {
+ override fun makeArgumentResolvers(): List<ArgumentResolver> {
+ return listOf(
+ ArgumentResolver.ForIndex(0, classpath),
+ ArgumentResolver.ForIndex(2, pluginClassName)
+ )
+ }
+}

ADD
buildSrc/src/main/kotlin/otel.javaagent-codegen.gradle.kts
+import io.opentelemetry.instrumentation.gradle.codegen.ClasspathByteBuddyPlugin
+import io.opentelemetry.instrumentation.gradle.codegen.ClasspathTransformation
+import net.bytebuddy.build.gradle.ByteBuddySimpleTask
+import net.bytebuddy.build.gradle.Transformation
+
+plugins {
+ `java-library`
+}
+
+/**
+ * Starting from version 1.10.15, ByteBuddy gradle plugin transformation task autoconfiguration is
+ * hardcoded to be applied to javaCompile task. This causes the dependencies to be resolved during
+ * an afterEvaluate that runs before any afterEvaluate specified in the build script, which in turn
+ * makes it impossible to add dependencies in afterEvaluate. Additionally the autoconfiguration will
+ * attempt to scan the entire project for tasks which depend on the compile task, to make each task
+ * that depends on compile also depend on the transformation task. This is an extremely inefficient
+ * operation in this project to the point of causing a stack overflow in some environments.
+ *
+ * <p>To avoid all the issues with autoconfiguration, this plugin manually configures the ByteBuddy
+ * transformation task. This also allows it to be applied to source languages other than Java. The
+ * transformation task is configured to run between the compile and the classes tasks, assuming no
+ * other task depends directly on the compile task, but instead other tasks depend on classes task.
+ * Contrary to how the ByteBuddy plugin worked in versions up to 1.10.14, this changes the compile
+ * task output directory, as starting from 1.10.15, the plugin does not allow the source and target
+ * directories to be the same. The transformation task then writes to the original output directory
+ * of the compile task.
+ */
+
+val LANGUAGES = listOf("java", "scala", "kotlin")
+val pluginName = "io.opentelemetry.javaagent.tooling.muzzle.collector.MuzzleCodeGenerationPlugin"
+
+val codegen by configurations.creating {
+ isCanBeConsumed = false
+ isCanBeResolved = true
+}
+
+val sourceSet = sourceSets.main.get()
+val inputClasspath = (sourceSet.output.resourcesDir?.let { codegen.plus(project.files(it)) } ?: codegen)
+ .plus(configurations.runtimeClasspath.get())
+
+val languageTasks = LANGUAGES.map { language ->
+ if (fileTree("src/${sourceSet.name}/${language}").isEmpty) {
+ return@map null
+ }
+ val compileTaskName = sourceSet.getCompileTaskName(language)
+ if (!tasks.names.contains(compileTaskName)) {
+ return@map null
+ }
+ val compileTask = tasks.named(compileTaskName)
+ createLanguageTask(compileTask, "byteBuddy${language}")
+}.filterNotNull()
+
+tasks {
+ val byteBuddy by registering {
+ dependsOn(languageTasks)
+ }
+
+ named(sourceSet.classesTaskName) {
+ dependsOn(byteBuddy)
+ }
+}
+
+fun createLanguageTask(
+ compileTaskProvider: TaskProvider<*>, name: String): TaskProvider<*>? {
+ return tasks.register<ByteBuddySimpleTask>(name) {
+ setGroup("Byte Buddy")
+ outputs.cacheIf { true }
+ val compileTask = compileTaskProvider.get()
+ if (compileTask is AbstractCompile) {
+ val classesDirectory = compileTask.destinationDirectory.asFile.get()
+ val rawClassesDirectory: File = File(classesDirectory.parent, "${classesDirectory.name}raw")
+ .absoluteFile
+ dependsOn(compileTask)
+ compileTask.destinationDirectory.set(rawClassesDirectory)
+ source = rawClassesDirectory
+ target = classesDirectory
+ classPath = compileTask.classpath
+ dependsOn(compileTask, sourceSet.processResourcesTaskName)
+ }
+
+ transformations.add(createTransformation(inputClasspath, pluginName))
+ }
+}
+
+fun createTransformation(classPath: FileCollection, pluginClassName: String): Transformation {
+ return ClasspathTransformation(classPath, pluginClassName).apply {
+ plugin = ClasspathByteBuddyPlugin::class.java
+ }
+}

MODIFY
buildSrc/src/main/kotlin/otel.javaagent-testing.gradle.kts
buildSrc/src/main/kotlin/otel.javaagent-testing.gradle.kts
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
-import io.opentelemetry.instrumentation.gradle.bytebuddy.ByteBuddyPluginConfigurator
plugins {
id("net.bytebuddy.byte-buddy")
id("otel.instrumentation-conventions")
+ id("otel.javaagent-codegen")
id("otel.shadow-conventions")
}
-val toolingRuntime by configurations.creating {
- isCanBeConsumed = false
- isCanBeResolved = true
-}
-
dependencies {
// Integration tests may need to define custom instrumentation modules so we include the standard
// instrumentation infrastructure for testing too.
@@ -39,15 +34,10 @@ dependencies {
testImplementation("org.testcontainers:testcontainers")
- toolingRuntime(project(path = ":javaagent-tooling", configuration = "instrumentationMuzzle"))
- toolingRuntime(project(path = ":javaagent-extension-api", configuration = "instrumentationMuzzle"))
+ add("codegen", project(path = ":javaagent-tooling", configuration = "instrumentationMuzzle"))
+ add("codegen", project(path = ":javaagent-extension-api", configuration = "instrumentationMuzzle"))
}
-val pluginName = "io.opentelemetry.javaagent.tooling.muzzle.collector.MuzzleCodeGenerationPlugin"
-ByteBuddyPluginConfigurator(project, sourceSets.main.get(), pluginName,
- toolingRuntime.plus(configurations.runtimeClasspath.get()))
- .configure()
-
val testInstrumentation by configurations.creating {
isCanBeConsumed = false
isCanBeResolved = true

Migrate codegen to be a plugin (#3401)