Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,11 @@ class JavaagentOTelModificationPlugin : Plugin<Project> {
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))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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:<jar> 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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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:<jar> 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 })
}
}
Expand Down
Loading