Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
cc17a7d
:white_check_mark: add windows to CI
ryandens Nov 1, 2023
fbbb528
Use Unix line endings for all files
punya Jul 19, 2023
352f517
Add support for Windows
punya Jul 19, 2023
615a342
Use portable path separators
punya Jul 19, 2023
4418803
:bug: get rid of hardcoded file separators in tests
ryandens Nov 1, 2023
e4ba41f
:bug: more path separator cleanup
ryandens Nov 1, 2023
35e885f
:art:
ryandens Nov 1, 2023
98be98a
:bug: use layout for specifying dir where possible
ryandens Nov 1, 2023
337b027
:fire: remove gitattributes
ryandens Dec 18, 2025
9a04631
:art:
ryandens Dec 18, 2025
3fd65d3
:rotating_light:
ryandens Dec 18, 2025
ead01bd
Small fixes to the Windows branch (#298)
neboskreb Dec 22, 2025
0d24570
:art: fix formatting violations
ryandens Dec 22, 2025
9f560a5
Remove manual Windows path quoting in JavaForkOptionsConfigurer.asArg…
macroscopeapp[bot] Jul 6, 2026
f828625
Fix Windows build by using gradlew.bat conditionally based on platform
macroscopeapp[bot] Jul 6, 2026
1b28150
Fix Windows build by adding .\\ prefix to gradlew.bat in workflow
macroscopeapp[bot] Jul 6, 2026
f297e85
Restore Windows path quoting in JavaForkOptionsConfigurer to fix test…
macroscopeapp[bot] Jul 6, 2026
26af4e5
Fix Windows functional tests to use correct script paths and line end…
macroscopeapp[bot] Jul 6, 2026
6cd7d5b
Fix spotless formatting violation in JavaagentJibExtensionFunctionalT…
macroscopeapp[bot] Jul 6, 2026
3a81f20
Fix Windows start script generation to use %APP_HOME% instead of $APP…
macroscopeapp[bot] Jul 6, 2026
10914f3
Run Windows start script through cmd /c in execStartScript functional…
ryandens Jul 6, 2026
7367316
Keep JVM opts as separate quoted tokens in Windows start script
ryandens Jul 6, 2026
f662f09
Fix Windows batch script: use separate quoted tokens for multiple jav…
macroscopeapp[bot] Jul 6, 2026
8dc638c
Strip quoted placeholder token in Windows start script when no agents
ryandens Jul 6, 2026
91b1851
TEMP: enable showStandardStreams to diagnose Windows OTel agent output
ryandens Jul 6, 2026
be076b6
Don't quote javaagent path for forked JVMs (fixes OTel agent on Windows)
ryandens Jul 6, 2026
a8339af
TEMP: re-enable showStandardStreams to re-check Windows OTel output
ryandens Jul 6, 2026
eccb169
Bump OpenTelemetry agent to 1.30.0 in functional tests for Windows
ryandens Jul 6, 2026
67a0d85
Make run task depend on javaagent artifacts to fix build-ordering race
ryandens Jul 6, 2026
884b1bd
Revert run task dependsOn change (fix build-ordering race in a separa…
ryandens Jul 6, 2026
f92cb43
:arrow_up: test with newer version of OTel agent
ryandens Jul 6, 2026
397ecf0
Use absolute (not canonical) javaagent path in JavaForkOptionsConfigurer
ryandens Jul 6, 2026
726ad81
TEMP: print exact -javaagent arg + showStandardStreams to diagnose Wi…
ryandens Jul 6, 2026
5344af8
TEMP: disable fail-fast to let Windows job complete for diagnostics
ryandens Jul 6, 2026
27c290e
Fix Windows MAX_PATH failure for OpenTelemetry agent in run/test tests
ryandens Jul 6, 2026
06728e8
Revert temporary fail-fast:false diagnostic in CI matrix
ryandens Jul 6, 2026
54dca34
Address review feedback on start script generator and test deps
ryandens Jul 6, 2026
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
7 changes: 5 additions & 2 deletions .github/workflows/gradle.yml
Comment thread
macroscopeapp[bot] marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ jobs:
- uses: gradle/actions/wrapper-validation@v6
build:
name: "Javaagent Gradle Plugin Validation"
runs-on: ubuntu-latest
strategy:
matrix:
platform: [ubuntu-latest, windows-latest]
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v7.0.0
- uses: actions/setup-java@v5.4.0
Expand All @@ -29,4 +32,4 @@ jobs:
cache-read-only: false

- name: Execute Gradle build
run: ./gradlew build
run: ${{ matrix.platform == 'windows-latest' && '.\gradlew.bat' || './gradlew' }} build
4 changes: 2 additions & 2 deletions example-projects/buildSrc/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@ dependencies {
implementation("com.ryandens:plugin:0.11.0")
*/
implementation("com.gradleup.shadow:com.gradleup.shadow.gradle.plugin:9.4.3")
implementation("io.opentelemetry.instrumentation.muzzle-generation:io.opentelemetry.instrumentation.muzzle-generation.gradle.plugin:2.27.0-alpha")
implementation("io.opentelemetry.instrumentation.muzzle-check:io.opentelemetry.instrumentation.muzzle-check.gradle.plugin:2.27.0-alpha")
implementation("io.opentelemetry.instrumentation.muzzle-generation:io.opentelemetry.instrumentation.muzzle-generation.gradle.plugin:2.29.0-alpha")
implementation("io.opentelemetry.instrumentation.muzzle-check:io.opentelemetry.instrumentation.muzzle-check.gradle.plugin:2.29.0-alpha")
}
Original file line number Diff line number Diff line change
Expand Up @@ -91,14 +91,16 @@ class JavaagentJibExtensionFunctionalTest {
buildArgs: List<String>,
): BuildResult {
val helloWorldDir = File(functionalTestDir, "hello-world")
File("src/functionalTest/resources/hello-world-project/").copyRecursively(helloWorldDir)
File(
"src${File.separator}functionalTest${File.separator}resources${File.separator}hello-world-project",
).copyRecursively(helloWorldDir)
val simpleAgentTestDir = File(functionalTestDir, "simple-agent")
val simpleAgentBuildScript = simpleAgentTestDir.resolve("build.gradle.kts")
File("../simple-agent/").copyRecursively(simpleAgentTestDir)
File("..${File.separator}simple-agent").copyRecursively(simpleAgentTestDir)
// remove a constant from the above file
simpleAgentBuildScript.writeText(
simpleAgentBuildScript.readText().replace(
"id(\"com.ryandens.java-conventions\")\n",
"id(\"com.ryandens.java-conventions\")",
"",
),
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package com.ryandens.javaagent

import org.gradle.internal.jvm.Jvm
import org.gradle.internal.os.OperatingSystem
import org.gradle.testkit.runner.BuildResult
import org.gradle.testkit.runner.GradleRunner
import org.gradle.testkit.runner.TaskOutcome
import java.io.File
import java.nio.file.Paths
import kotlin.test.AfterTest
import kotlin.test.BeforeTest
import kotlin.test.Test
Expand All @@ -23,9 +25,13 @@ class JavaagentPluginFunctionalTest {

private lateinit var helloWorldDir: File

private val isWindows = OperatingSystem.current().isWindows

private val scriptExtension = if (isWindows) ".bat" else ""

@BeforeTest
fun beforeEach() {
functionalTestDir = File("build/functionalTest")
functionalTestDir = File("build", "functionalTest")
functionalTestDir.mkdirs()
helloWorldDir = File(functionalTestDir, "hello-world")
}
Expand Down Expand Up @@ -69,7 +75,8 @@ class JavaagentPluginFunctionalTest {
}

@Test fun `can attach to test task`() {
val otelVersion = "1.11.1"
// A real third-party javaagent used to verify the plugin attaches it and it logs its version banner.
val otelVersion = "2.29.0"
val dependencies = """
javaagent project(':simple-agent')
testJavaagent 'io.opentelemetry.javaagent:opentelemetry-javaagent:$otelVersion'
Expand All @@ -92,7 +99,8 @@ class JavaagentPluginFunctionalTest {
}

@Test fun `can attach two agents to application run task`() {
val otelVersion = "1.11.1"
// A real third-party javaagent used to verify the plugin attaches it and it logs its version banner.
val otelVersion = "2.29.0"
val dependencies = """
javaagent project(':simple-agent')
javaagent 'io.opentelemetry.javaagent:opentelemetry-javaagent:$otelVersion'
Expand Down Expand Up @@ -120,27 +128,30 @@ class JavaagentPluginFunctionalTest {
val result = runBuild(listOf("--configuration-cache", "build", "installDist", "execStartScript"))

// verify the distribution was created properly
val applicationDistribution = File(functionalTestDir, "hello-world/build/distributions/hello-world.tar")
val applicationDistribution =
File(functionalTestDir, "hello-world${File.separator}build${File.separator}distributions${File.separator}hello-world.tar")
assertTrue(applicationDistribution.exists())

// verify the expected text was injected into the start script
val expectedDefaultJavaOpts = """
val applicationDistributionScript =
File(functionalTestDir, "hello-world${File.separator}build${File.separator}scripts${File.separator}hello-world$scriptExtension")
if (isWindows) {
val expectedWindowsDefaultJvmOpts = """set DEFAULT_JVM_OPTS="-javaagent:%APP_HOME%\agent-libs\simple-agent.jar" "-Xmx256m""""
assertTrue(applicationDistributionScript.readText().contains(expectedWindowsDefaultJvmOpts))
} else {
val expectedDefaultJavaOpts = """
DEFAULT_JVM_OPTS="-javaagent:${"$"}APP_HOME/agent-libs/simple-agent.jar -Xmx256m"
"""
val applicationDistributionScript = File(functionalTestDir, "hello-world/build/scripts/hello-world")
assertTrue(applicationDistributionScript.readText().contains(expectedDefaultJavaOpts))

/*
* TODO add support for windows
* val expectedWindowsDefaultJvmOpts = """
DEFAULT_JVM_OPTS="-javaagent:${"$"}APP_HOME/lib/simple-agent.jar -Xmx256m"
"""
val applicationDistributionScript = File(functionalTestDir, "hello-world/build/scripts/hello-world.bat")
assertTrue(applicationDistributionScript.readText().contains(expectedWindowsDefaultJvmOpts))
*/
assertTrue(applicationDistributionScript.readText().contains(expectedDefaultJavaOpts))
}

// verify the agent was added to the /lib/ dir of the distribution
assertTrue(File(functionalTestDir, "hello-world/build/install/hello-world/agent-libs/simple-agent.jar").exists())
assertTrue(
File(
functionalTestDir,
"hello-world/build/install/hello-world/agent-libs/simple-agent.jar".replace("/", File.separator),
).exists(),
)

// Verify the result
assertTrue(result.output.contains("Hello World!"))
Expand All @@ -154,23 +165,23 @@ DEFAULT_JVM_OPTS="-javaagent:${"$"}APP_HOME/lib/simple-agent.jar -Xmx256m"
@Test fun `can handle upgrade of agent with build cache`() {
createJavaagentProject(
"""
javaagent 'io.opentelemetry.javaagent:opentelemetry-javaagent:1.30.0'
javaagent 'io.opentelemetry.javaagent:opentelemetry-javaagent:2.28.0'
""".trimIndent(),
)

val firstBuild = runBuild(listOf("--build-cache", "build", "installDist", "execStartScript"))

assertTrue(
firstBuild.output.contains("io.opentelemetry.javaagent.tooling.VersionLogger - opentelemetry-javaagent - version: 1.30.0"),
firstBuild.output.contains("io.opentelemetry.javaagent.tooling.VersionLogger - opentelemetry-javaagent - version: 2.28.0"),
)

val buildScript = helloWorldDir.resolve("build.gradle")

// replace the agent version with a newer version
buildScript.writeText(buildScript.readText().replace("1.30.0", "1.31.0"))
buildScript.writeText(buildScript.readText().replace("2.28.0", "2.29.0"))
val secondBuild = runBuild(listOf("--build-cache", "build", "installDist", "execStartScript"))
assertTrue(
secondBuild.output.contains("io.opentelemetry.javaagent.tooling.VersionLogger - opentelemetry-javaagent - version: 1.31.0"),
secondBuild.output.contains("io.opentelemetry.javaagent.tooling.VersionLogger - opentelemetry-javaagent - version: 2.29.0"),
)
}

Expand All @@ -180,24 +191,36 @@ DEFAULT_JVM_OPTS="-javaagent:${"$"}APP_HOME/lib/simple-agent.jar -Xmx256m"
val result = runBuild(listOf("build", "installDist", "execStartScript"))

// verify the distribution was created properly
val applicationDistribution = File(functionalTestDir, "hello-world/build/distributions/hello-world.tar")
val applicationDistribution =
File(functionalTestDir, "hello-world${File.separator}build${File.separator}distributions${File.separator}hello-world.tar")
assertTrue(applicationDistribution.exists())

val applicationDistributionScript = File(functionalTestDir, "hello-world/build/scripts/hello-world")
assertTrue(applicationDistributionScript.readText().contains("""DEFAULT_JVM_OPTS="-Xmx256m"""))

assertFalse(File(functionalTestDir, "hello-world/build/install/hello-world/agent-libs/").exists())
val applicationDistributionScript =
File(functionalTestDir, "hello-world${File.separator}build${File.separator}scripts${File.separator}hello-world$scriptExtension")
if (isWindows) {
assertTrue(applicationDistributionScript.readText().contains("""set DEFAULT_JVM_OPTS="-Xmx256m""""))
} else {
assertTrue(applicationDistributionScript.readText().contains("""DEFAULT_JVM_OPTS="-Xmx256m"""))
}

assertFalse(
File(
functionalTestDir,
"hello-world${File.separator}build${File.separator}install${File.separator}hello-world${File.separator}agent-libs/",
).exists(),
)
assertTrue(result.output.contains("Hello World!"))
}

private fun createJavaagentProject(dependencies: String) {
File("src/functionalTest/resources/hello-world-project/").copyRecursively(helloWorldDir)
val helloWorldDir = File(functionalTestDir, "hello-world")
Paths.get("src", "functionalTest", "resources", "hello-world-project").toFile().copyRecursively(helloWorldDir)
val simpleAgentTestDir = File(functionalTestDir, "simple-agent")
val simpleAgentBuildScript = simpleAgentTestDir.resolve("build.gradle.kts")
File("../simple-agent/").copyRecursively(simpleAgentTestDir)
Paths.get("..", "simple-agent").toFile().copyRecursively(simpleAgentTestDir)
simpleAgentBuildScript.writeText(
simpleAgentBuildScript.readText().replace(
"id(\"com.ryandens.java-conventions\")\n",
"id(\"com.ryandens.java-conventions\")",
"",
),
)
Expand All @@ -210,6 +233,23 @@ DEFAULT_JVM_OPTS="-javaagent:${"$"}APP_HOME/lib/simple-agent.jar -Xmx256m"
""",
)

// On Windows a `.bat` file is not a directly executable image, so it cannot be launched via
// ProcessBuilder/CreateProcess (which is what Gradle's Exec task uses). It must be run through the
// command interpreter instead, e.g. `cmd /c hello-world.bat`.
val execCommandLine =
if (isWindows) {
"'cmd', '/c', 'hello-world.bat'"
} else {
"'./hello-world'"
}
// Escape backslashes and quotes so a Windows JAVA_HOME embeds cleanly in the generated Groovy string literal.
val javaHome =
Jvm
.current()
.getJavaHome()
.toString()
.replace("\\", "\\\\")
.replace("\"", "\\\"")
helloWorldDir.resolve("build.gradle").writeText(
"""
plugins {
Expand All @@ -232,12 +272,11 @@ DEFAULT_JVM_OPTS="-javaagent:${"$"}APP_HOME/lib/simple-agent.jar -Xmx256m"
}

task execStartScript(type: Exec) {
inputs.files(fileTree('${helloWorldDir.canonicalPath}/build/install/') {
builtBy tasks.named('installDist')
})
workingDir '${helloWorldDir.canonicalPath}/build/install/hello-world/bin/'
commandLine './hello-world'
environment JAVA_HOME: "${Jvm.current().getJavaHome()}"
dependsOn('installDist')
inputs.files(layout.buildDirectory.dir('install'))
workingDir(layout.buildDirectory.dir('install').map { it.dir('hello-world').dir('bin') })
commandLine $execCommandLine
environment JAVA_HOME: "$javaHome"
}

test {
Expand All @@ -262,6 +301,20 @@ DEFAULT_JVM_OPTS="-javaagent:${"$"}APP_HOME/lib/simple-agent.jar -Xmx256m"
runner.withPluginClasspath()
runner.withArguments(buildArgs)
runner.withProjectDir(functionalTestDir)
if (isWindows) {
// Keep the TestKit Gradle home short. Its default lives under the deeply nested
// build/tmp/functionalTest/work/.gradle-test-kit, which pushes cached agent jars -- e.g. the
// OpenTelemetry javaagent at caches/modules-2/files-2.1/.../<sha1>/opentelemetry-javaagent-<ver>.jar
// -- past Windows' 260-character MAX_PATH. The JVM's native appendToBootstrapClassLoaderSearch
// then rejects the path with IllegalArgumentException and the agent fails to install. Rooting the
// TestKit home at the drive root keeps the full agent jar path comfortably within the limit.
val driveRoot =
functionalTestDir.absoluteFile
.toPath()
.root
.toFile()
runner.withTestKitDir(File(driveRoot, "gtk"))
}
return runner.build()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ import org.gradle.api.Project
import org.gradle.api.artifacts.Configuration
import org.gradle.api.distribution.DistributionContainer
import org.gradle.api.distribution.plugins.DistributionPlugin
import org.gradle.api.internal.plugins.WindowsStartScriptGenerator
import org.gradle.api.plugins.ApplicationPlugin
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.application.CreateStartScripts
import java.io.File

/**
* Gradle plugin for configuration of the [DistributionPlugin.MAIN_DISTRIBUTION_NAME] after it has been configured by
Expand Down Expand Up @@ -52,14 +53,9 @@ class JavaagentApplicationDistributionPlugin :
.plus(it.defaultJvmOpts ?: listOf())
it.inputs.files(javaagentConfiguration)
// custom start script generator that replaces the placeholder
it.unixStartScriptGenerator =
JavaagentAwareStartScriptGenerator(
javaagentConfiguration.map { configuration ->
configuration.files
},
)
// TODO build support for windows
it.windowsStartScriptGenerator = WindowsStartScriptGenerator()
val agentFiles: Provider<Set<File>> = javaagentConfiguration.map { configuration -> configuration.files }
it.unixStartScriptGenerator = JavaagentAwareStartScriptGenerator(agentFiles, Platform.UNIX)
it.windowsStartScriptGenerator = JavaagentAwareStartScriptGenerator(agentFiles, Platform.WINDOWS)
}
}
}
Loading
Loading