diff --git a/otel/src/main/kotlin/com/ryandens/javaagent/otel/JavaagentOTelModificationPlugin.kt b/otel/src/main/kotlin/com/ryandens/javaagent/otel/JavaagentOTelModificationPlugin.kt index 3ea724f5..2a944948 100644 --- a/otel/src/main/kotlin/com/ryandens/javaagent/otel/JavaagentOTelModificationPlugin.kt +++ b/otel/src/main/kotlin/com/ryandens/javaagent/otel/JavaagentOTelModificationPlugin.kt @@ -89,6 +89,11 @@ class JavaagentOTelModificationPlugin : Plugin { it.into("inst") } } - project.dependencies.add("javaagent", extendedAgent.map { project.files(it.outputs.files.singleFile) }) + // Add the extendedAgent output via the task provider (not extendedAgent.outputs.files.singleFile, + // which resolves to a plain File and loses provenance). project.files(TaskProvider) carries the + // producing task as a builtBy dependency, so consumers of the javaagent configuration that track it + // as an input — e.g. the run task in JavaagentApplicationRunPlugin — establish a dependency on + // :extendedAgent instead of failing Gradle's implicit-dependency validation. + project.dependencies.add("javaagent", project.files(extendedAgent)) } } diff --git a/plugin/src/functionalTest/kotlin/com/ryandens/javaagent/JavaagentPluginFunctionalTest.kt b/plugin/src/functionalTest/kotlin/com/ryandens/javaagent/JavaagentPluginFunctionalTest.kt index a7640dc7..ef8adb94 100644 --- a/plugin/src/functionalTest/kotlin/com/ryandens/javaagent/JavaagentPluginFunctionalTest.kt +++ b/plugin/src/functionalTest/kotlin/com/ryandens/javaagent/JavaagentPluginFunctionalTest.kt @@ -74,6 +74,30 @@ class JavaagentPluginFunctionalTest { assertTrue(ccResult.output.contains("Reusing configuration cache.")) } + @Test fun `run task depends on the tasks that build the javaagent artifacts`() { + val dependencies = """ + javaagent project(':simple-agent') + """ + + createJavaagentProject(dependencies) + + // The run task adds the agent jars via a CommandLineArgumentProvider that is not itself a tracked + // input, so the plugin registers the javaagent configuration as a task input to make Gradle schedule + // :simple-agent:jar before :hello-world:run. Without that, the run task can launch with + // -javaagent: before that jar has been produced, failing with "Error opening zip file or JAR + // manifest missing" (the `can attach to application run task` test above reproduces that + // intermittently under parallel scheduling). --dry-run prints the task execution graph in order + // without running anything, so asserting the ordering here catches the regression deterministically, + // independent of scheduling timing. + val result = runBuild(listOf("--dry-run", ":hello-world:run")) + + val agentJarIndex = result.output.indexOf(":simple-agent:jar") + val runIndex = result.output.indexOf(":hello-world:run") + assertTrue(agentJarIndex >= 0, "expected :simple-agent:jar to be scheduled ahead of :hello-world:run") + assertTrue(runIndex >= 0, "expected :hello-world:run to be scheduled") + assertTrue(agentJarIndex < runIndex, "expected :simple-agent:jar to be scheduled before :hello-world:run") + } + @Test fun `can attach to test task`() { // A real third-party javaagent used to verify the plugin attaches it and it logs its version banner. val otelVersion = "2.29.0" diff --git a/plugin/src/main/kotlin/com/ryandens/javaagent/JavaagentApplicationRunPlugin.kt b/plugin/src/main/kotlin/com/ryandens/javaagent/JavaagentApplicationRunPlugin.kt index 0c645702..d446e04c 100644 --- a/plugin/src/main/kotlin/com/ryandens/javaagent/JavaagentApplicationRunPlugin.kt +++ b/plugin/src/main/kotlin/com/ryandens/javaagent/JavaagentApplicationRunPlugin.kt @@ -21,6 +21,19 @@ class JavaagentApplicationRunPlugin : ) { // configure the run task to use the `javaagent` flag pointing to the dependency stored in the local Maven repository project.tasks.named(ApplicationPlugin.TASK_RUN_NAME, JavaExec::class.java).configure { + // The agent jars are passed to the JVM via a CommandLineArgumentProvider (see + // JavaForkOptionsConfigurer), which resolves the configuration's files at execution time but is + // not a tracked task input. Without registering the configuration as an input, Gradle + // establishes no ordering between the run task and the tasks that build the agent jars, so under + // parallel execution the run task can launch with -javaagent: before that jar has been + // produced, failing with "Error opening zip file or JAR manifest missing". + // + // A Configuration is Buildable, so registering it as an input makes the run task depend on the + // tasks that build the agent jars, the same way JavaagentApplicationDistributionPlugin does for + // the start-script task. This requires every artifact added to the javaagent configuration to + // carry its producing-task dependency (builtBy); consumers that synthesize a plain File must + // preserve it (see the otel example's extendedAgent wiring). + it.inputs.files(javaagentConfiguration) JavaForkOptionsConfigurer.configureJavaForkOptions(it, javaagentConfiguration.map { configuration -> configuration.files }) } }