diff --git a/gradle-plugins/src/main/kotlin/io.opentelemetry.instrumentation.muzzle-generation.gradle.kts b/gradle-plugins/src/main/kotlin/io.opentelemetry.instrumentation.muzzle-generation.gradle.kts index fc5fa78fb3bc..6925bc8d4063 100644 --- a/gradle-plugins/src/main/kotlin/io.opentelemetry.instrumentation.muzzle-generation.gradle.kts +++ b/gradle-plugins/src/main/kotlin/io.opentelemetry.instrumentation.muzzle-generation.gradle.kts @@ -1,7 +1,6 @@ import io.opentelemetry.javaagent.muzzle.generation.ClasspathByteBuddyPlugin import io.opentelemetry.javaagent.muzzle.generation.ClasspathTransformation -import net.bytebuddy.ClassFileVersion -import net.bytebuddy.build.gradle.ByteBuddySimpleTask +import io.opentelemetry.javaagent.muzzle.generation.ConfigurationCacheFriendlyByteBuddyTask import net.bytebuddy.build.gradle.Transformation plugins { @@ -50,7 +49,7 @@ val languageTasks = LANGUAGES.map { language -> return@map null } val compileTask = tasks.named(compileTaskName) - createLanguageTask(compileTask, "byteBuddy${language.replaceFirstChar(Char::titlecase)}") + createLanguageTask(compileTask, language) }.filterNotNull() tasks { @@ -60,29 +59,43 @@ tasks { } fun createLanguageTask( - compileTaskProvider: TaskProvider<*>, name: String): TaskProvider<*> { - return tasks.register(name) { - setGroup("Byte Buddy") - outputs.cacheIf { true } - classFileVersion = ClassFileVersion.JAVA_V8 - var transformationClassPath = inputClasspath + compileTaskProvider: TaskProvider<*>, language: String): TaskProvider<*> { + val taskName = "byteBuddy${language.replaceFirstChar { it.uppercase() }}" + val mainSourceSet = sourceSets.main.get() + + // Create the input classpath from the existing logic in the main part + val inputClasspath = (mainSourceSet.output.resourcesDir?.let { codegen.plus(project.files(it)) } + ?: codegen) + .plus(mainSourceSet.output.dirs) // needed to support embedding shadowed modules into instrumentation + .plus(configurations.runtimeClasspath.get()) + + val byteBuddyTask = tasks.register(taskName, ConfigurationCacheFriendlyByteBuddyTask::class.java) { + dependsOn(compileTaskProvider, mainSourceSet.processResourcesTaskName) + + transformations.add(createTransformation(inputClasspath, pluginName)) + + // Configure the ByteBuddy task properties directly during task creation val compileTask = compileTaskProvider.get() // this does not work for kotlin as compile task does not extend AbstractCompile if (compileTask is AbstractCompile) { val classesDirectory = compileTask.destinationDirectory.asFile.get() - val rawClassesDirectory: File = File(classesDirectory.parent, "${classesDirectory.name}raw") - .absoluteFile - dependsOn(compileTask) + val rawClassesDirectory = File(classesDirectory.parent, "${classesDirectory.name}raw").absoluteFile + + // Configure the compile task to write to rawClassesDirectory compileTask.destinationDirectory.set(rawClassesDirectory) + + // Configure ByteBuddy task properties source = rawClassesDirectory target = classesDirectory classPath = compileTask.classpath.plus(rawClassesDirectory) - transformationClassPath = transformationClassPath.plus(files(rawClassesDirectory)) - dependsOn(compileTask, sourceSet.processResourcesTaskName) - } - transformations.add(createTransformation(transformationClassPath, pluginName)) + // Clear and set transformations with correct classpath + transformations.clear() + transformations.add(createTransformation(inputClasspath.plus(files(rawClassesDirectory)), pluginName)) + } } + + return byteBuddyTask } fun createTransformation(classPath: FileCollection, pluginClassName: String): Transformation { diff --git a/gradle-plugins/src/main/kotlin/io/opentelemetry/javaagent/muzzle/generation/ConfigurationCacheFriendlyByteBuddyTask.kt b/gradle-plugins/src/main/kotlin/io/opentelemetry/javaagent/muzzle/generation/ConfigurationCacheFriendlyByteBuddyTask.kt new file mode 100644 index 000000000000..f0da72984dce --- /dev/null +++ b/gradle-plugins/src/main/kotlin/io/opentelemetry/javaagent/muzzle/generation/ConfigurationCacheFriendlyByteBuddyTask.kt @@ -0,0 +1,29 @@ +package io.opentelemetry.javaagent.muzzle.generation + +import net.bytebuddy.build.Plugin +import net.bytebuddy.build.gradle.ByteBuddySimpleTask +import org.gradle.api.tasks.TaskAction +import java.io.IOException + +/** + * Byte Buddy task variant that avoids touching Gradle's Project API from the task action so the + * task remains compatible with Gradle configuration cache. + */ +open class ConfigurationCacheFriendlyByteBuddyTask : ByteBuddySimpleTask() { + + @TaskAction + @Throws(IOException::class) + override fun apply() { + val sourceDir = source + val targetDir = target + + if (sourceDir != targetDir && deleteRecursively(targetDir)) { + logger.debug("Deleted all target files in {}", targetDir) + } + + doApply( + Plugin.Engine.Source.ForFolder(sourceDir), + Plugin.Engine.Target.ForFolder(targetDir) + ) + } +} diff --git a/gradle.properties b/gradle.properties index fef595296b18..bc3c93e2600b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,6 @@ org.gradle.parallel=true org.gradle.caching=true +org.gradle.configuration-cache=true org.gradle.priority=low diff --git a/instrumentation-api-incubator/build.gradle.kts b/instrumentation-api-incubator/build.gradle.kts index cf8224b99cdd..d4621a5ea05b 100644 --- a/instrumentation-api-incubator/build.gradle.kts +++ b/instrumentation-api-incubator/build.gradle.kts @@ -1,6 +1,4 @@ plugins { - id("org.xbib.gradle.plugin.jflex") - id("otel.java-conventions") id("otel.animalsniffer-conventions") id("otel.jacoco-conventions") @@ -10,7 +8,17 @@ plugins { group = "io.opentelemetry.instrumentation" +// JFlex configuration - manual integration for configuration cache compatibility +configurations { + val jflex by creating { + isTransitive = true + } +} + dependencies { + "jflex"("de.jflex:jflex:1.9.1") + "jflex"("com.github.vbmacher:java-cup-runtime:11b-20160615") + api("io.opentelemetry.semconv:opentelemetry-semconv") api(project(":instrumentation-api")) api("io.opentelemetry:opentelemetry-api-incubator") @@ -24,6 +32,41 @@ dependencies { testImplementation("io.opentelemetry.semconv:opentelemetry-semconv-incubating") } +// Manual JFlex task - configuration cache compatible +val generateJflex by tasks.registering(JavaExec::class) { + description = "Generate Java code from JFlex files" + group = "build" + + classpath = configurations.getByName("jflex") + mainClass.set("jflex.Main") + + val jflexSourceDir = file("src/main/jflex") + val jflexOutputDir = file("build/generated/sources/jflex") + + inputs.dir(jflexSourceDir) + outputs.dir(jflexOutputDir) + + doFirst { + jflexOutputDir.mkdirs() + } + + args( + "-d", jflexOutputDir, + "--nobak", + "$jflexSourceDir/SqlSanitizer.jflex" + ) +} + +sourceSets { + main { + java.srcDir(generateJflex.map { it.outputs.files.singleFile }) + } +} + +tasks.compileJava { + dependsOn(generateJflex) +} + tasks { // exclude auto-generated code named("checkstyleMain") { @@ -38,7 +81,11 @@ tasks { } sourcesJar { - dependsOn("generateJflex") + dependsOn(generateJflex) + // Avoid configuration cache issue by not capturing task reference + from("src/main/jflex") { + include("**/*.java") + } } val testStableSemconv by registering(Test::class) { diff --git a/settings.gradle.kts b/settings.gradle.kts index 4488c3620b13..7d7486b7e8d9 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -5,7 +5,6 @@ pluginManagement { id("com.gradle.plugin-publish") version "2.0.0" id("io.github.gradle-nexus.publish-plugin") version "2.0.0" id("org.jetbrains.kotlin.jvm") version "2.2.21" - id("org.xbib.gradle.plugin.jflex") version "3.0.2" id("com.github.bjornvester.xjc") version "1.8.2" id("org.graalvm.buildtools.native") version "0.11.2" id("com.google.osdetector") version "1.7.3" diff --git a/testing/agent-for-testing/build.gradle.kts b/testing/agent-for-testing/build.gradle.kts index d8c83986af5b..790d33203593 100644 --- a/testing/agent-for-testing/build.gradle.kts +++ b/testing/agent-for-testing/build.gradle.kts @@ -1,3 +1,5 @@ +import java.io.File + plugins { id("otel.java-conventions") id("otel.publish-conventions") @@ -16,6 +18,14 @@ val extensionLibs by configurations.creating { isCanBeConsumed = false } +val agentJarFile = providers.provider { agent.singleFile } + +val agentManifestFile = agentJarFile.map { jarFile: File -> + zipTree(jarFile).matching { + include("META-INF/MANIFEST.MF") + }.singleFile +} + dependencies { extensionLibs(project(":testing:agent-exporter", configuration = "shadow")) agent(project(":javaagent", configuration = "baseJar")) @@ -27,18 +37,12 @@ dependencies { tasks { jar { dependsOn(agent) - from(zipTree(agent.singleFile)) + from(agentJarFile.map { jarFile: File -> zipTree(jarFile) }) from(extensionLibs) { into("extensions") } - doFirst { - manifest.from( - zipTree(agent.singleFile).matching { - include("META-INF/MANIFEST.MF") - }.singleFile, - ) - } + manifest.from(agentManifestFile) } afterEvaluate {