Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Trying to extract codegen plugin #3521

Merged
merged 17 commits into from
Jul 14, 2021
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions buildSrc/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ dependencies {
implementation(gradleApi())
implementation(localGroovy())

implementation("io.opentelemetry.instrumentation.gradle:codegen:1.4.0-SNAPSHOT")
iNikem marked this conversation as resolved.
Show resolved Hide resolved

implementation("org.eclipse.aether:aether-connector-basic:1.1.0")
implementation("org.eclipse.aether:aether-transport-http:1.1.0")
implementation("org.apache.maven:maven-aether-provider:3.3.9")
Expand Down
22 changes: 22 additions & 0 deletions codegen/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
plugins {
`kotlin-dsl`

id("com.gradle.plugin-publish")

id("otel.java-conventions")
id("otel.publish-conventions")
}

group = "io.opentelemetry.instrumentation.gradle"

val versions: Map<String, String> by project

dependencies {
implementation("net.bytebuddy:byte-buddy-gradle-plugin:${versions["net.bytebuddy"]}")
}

pluginBundle {
website = "https://opentelemetry.io"
vcsUrl = "https://github.com/open-telemetry/opentelemetry-java-instrumentation"
tags = listOf("opentelemetry", "instrumentation")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
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
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* 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()
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* 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)
)
}
}
2 changes: 1 addition & 1 deletion dependencyManagement/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ val DEPENDENCY_SETS = listOf(
"net.bytebuddy",
// When updating, also update buildSrc/build.gradle.kts
"1.11.2",
listOf("byte-buddy", "byte-buddy-agent")
listOf("byte-buddy", "byte-buddy-agent", "byte-buddy-gradle-plugin")
),
DependencySet(
"org.mockito",
Expand Down
3 changes: 3 additions & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ pluginManagement {
plugins {
id("com.github.ben-manes.versions") version "0.39.0"
id("com.github.jk1.dependency-license-report") version "1.16"
id("com.gradle.plugin-publish") version "0.15.0"
id("io.github.gradle-nexus.publish-plugin") version "1.1.0"
id("me.champeau.jmh") version "0.6.5"
id("org.jetbrains.kotlin.jvm") version "1.5.10"
Expand Down Expand Up @@ -49,6 +50,8 @@ buildCache {

rootProject.name = "opentelemetry-java-instrumentation"

include(":codegen")

// agent projects
include(":opentelemetry-api-shaded-for-instrumenting")
include(":opentelemetry-ext-annotations-shaded-for-instrumenting")
Expand Down