diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index b233935..18fe5d9 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -15,7 +15,10 @@ jobs: - uses: gradle/actions/wrapper-validation@v5 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@v6.0.1 - uses: actions/setup-java@v5.1.0 diff --git a/plugin/build.gradle.kts b/plugin/build.gradle.kts index f163834..116d12b 100644 --- a/plugin/build.gradle.kts +++ b/plugin/build.gradle.kts @@ -29,6 +29,7 @@ dependencies { testImplementation("org.jetbrains.kotlin:kotlin-test") testImplementation("org.jetbrains.kotlin:kotlin-test-junit") testImplementation("org.apache.commons:commons-compress:1.28.0") + testImplementation("org.apache.commons:commons-text:1.15.0") } gradlePlugin { diff --git a/plugin/src/functionalTest/kotlin/com/ryandens/javaagent/JavaagentPluginFunctionalTest.kt b/plugin/src/functionalTest/kotlin/com/ryandens/javaagent/JavaagentPluginFunctionalTest.kt index 45dcb45..f8b9825 100644 --- a/plugin/src/functionalTest/kotlin/com/ryandens/javaagent/JavaagentPluginFunctionalTest.kt +++ b/plugin/src/functionalTest/kotlin/com/ryandens/javaagent/JavaagentPluginFunctionalTest.kt @@ -1,10 +1,12 @@ package com.ryandens.javaagent +import org.apache.commons.text.StringEscapeUtils import org.gradle.internal.jvm.Jvm 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 @@ -25,7 +27,7 @@ class JavaagentPluginFunctionalTest { @BeforeTest fun beforeEach() { - functionalTestDir = File("build/functionalTest") + functionalTestDir = File("build", "functionalTest") functionalTestDir.mkdirs() helloWorldDir = File(functionalTestDir, "hello-world") } @@ -120,14 +122,16 @@ 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 = """ DEFAULT_JVM_OPTS="-javaagent:${"$"}APP_HOME/agent-libs/simple-agent.jar -Xmx256m" """ - val applicationDistributionScript = File(functionalTestDir, "hello-world/build/scripts/hello-world") + val applicationDistributionScript = + File(functionalTestDir, "hello-world${File.separator}build${File.separator}scripts${File.separator}hello-world") assertTrue(applicationDistributionScript.readText().contains(expectedDefaultJavaOpts)) /* @@ -140,7 +144,12 @@ DEFAULT_JVM_OPTS="-javaagent:${"$"}APP_HOME/lib/simple-agent.jar -Xmx256m" */ // 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!")) @@ -180,24 +189,32 @@ 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") + val applicationDistributionScript = + File(functionalTestDir, "hello-world${File.separator}build${File.separator}scripts${File.separator}hello-world") assertTrue(applicationDistributionScript.readText().contains("""DEFAULT_JVM_OPTS="-Xmx256m""")) - assertFalse(File(functionalTestDir, "hello-world/build/install/hello-world/agent-libs/").exists()) + 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\")", "", ), ) @@ -210,6 +227,8 @@ DEFAULT_JVM_OPTS="-javaagent:${"$"}APP_HOME/lib/simple-agent.jar -Xmx256m" """, ) + val commandLine = StringEscapeUtils.escapeJava(""".${File.separator}hello-world""") + val javaHome = StringEscapeUtils.escapeJava(Jvm.current().getJavaHome().toString()) helloWorldDir.resolve("build.gradle").writeText( """ plugins { @@ -232,12 +251,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 '$commandLine' + environment JAVA_HOME: "$javaHome" } test { diff --git a/plugin/src/main/java/com/ryandens/javaagent/JavaForkOptionsConfigurer.java b/plugin/src/main/java/com/ryandens/javaagent/JavaForkOptionsConfigurer.java index 5912925..fcc5386 100644 --- a/plugin/src/main/java/com/ryandens/javaagent/JavaForkOptionsConfigurer.java +++ b/plugin/src/main/java/com/ryandens/javaagent/JavaForkOptionsConfigurer.java @@ -8,6 +8,7 @@ import java.util.Set; import java.util.stream.Collectors; import org.gradle.api.provider.Provider; +import org.gradle.internal.os.OperatingSystem; import org.gradle.process.CommandLineArgumentProvider; import org.gradle.process.JavaForkOptions; @@ -42,7 +43,12 @@ public Iterable asArguments() { .map( file -> { try { - return "-javaagent:" + file.getCanonicalPath(); + String path = file.getCanonicalPath(); + if (OperatingSystem.current().isWindows()) { + // Don't let the spaces in the Windows path break the command line + path = '"' + path + '"'; + } + return "-javaagent:" + path; } catch (IOException e) { throw new UncheckedIOException(e); } diff --git a/plugin/src/main/kotlin/com/ryandens/javaagent/JavaagentApplicationDistributionPlugin.kt b/plugin/src/main/kotlin/com/ryandens/javaagent/JavaagentApplicationDistributionPlugin.kt index 3645e5f..f7174a3 100644 --- a/plugin/src/main/kotlin/com/ryandens/javaagent/JavaagentApplicationDistributionPlugin.kt +++ b/plugin/src/main/kotlin/com/ryandens/javaagent/JavaagentApplicationDistributionPlugin.kt @@ -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 @@ -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> = javaagentConfiguration.map { configuration -> configuration.files } + it.unixStartScriptGenerator = JavaagentAwareStartScriptGenerator(agentFiles, Platform.UNIX) + it.windowsStartScriptGenerator = JavaagentAwareStartScriptGenerator(agentFiles, Platform.WINDOWS) } } } diff --git a/plugin/src/main/kotlin/com/ryandens/javaagent/JavaagentAwareStartScriptGenerator.kt b/plugin/src/main/kotlin/com/ryandens/javaagent/JavaagentAwareStartScriptGenerator.kt index 247a0fa..5658eae 100644 --- a/plugin/src/main/kotlin/com/ryandens/javaagent/JavaagentAwareStartScriptGenerator.kt +++ b/plugin/src/main/kotlin/com/ryandens/javaagent/JavaagentAwareStartScriptGenerator.kt @@ -3,7 +3,6 @@ package com.ryandens.javaagent import org.gradle.api.Transformer import org.gradle.api.internal.plugins.DefaultTemplateBasedStartScriptGenerator import org.gradle.api.internal.plugins.StartScriptTemplateBindingFactory -import org.gradle.api.internal.plugins.UnixStartScriptGenerator import org.gradle.api.provider.Provider import org.gradle.jvm.application.scripts.JavaAppStartScriptGenerationDetails import org.gradle.jvm.application.scripts.ScriptGenerator @@ -12,18 +11,19 @@ import java.io.Writer class JavaagentAwareStartScriptGenerator( private val javaagentConfiguration: Provider>, + private val platform: Platform, private val inner: ScriptGenerator = DefaultTemplateBasedStartScriptGenerator( - "\n", - FakeTransformer(StartScriptTemplateBindingFactory.unix()), - UnixStartScriptGenerator().template, + platform.lineSeparator, + FakeTransformer(platform.templateBindingFactory), + platform.template, ), ) : ScriptGenerator { override fun generateScript( details: JavaAppStartScriptGenerationDetails, destination: Writer, ) { - inner.generateScript(details, Fake(destination, javaagentConfiguration)) + inner.generateScript(details, Fake(destination, javaagentConfiguration, platform.pathSeparator)) } private class FakeTransformer( @@ -46,6 +46,7 @@ class JavaagentAwareStartScriptGenerator( private class Fake( private val inner: Writer, private val javaagentFiles: Provider>, + private val pathSeparator: String, ) : Writer() { override fun close() { inner.close() @@ -76,7 +77,9 @@ class JavaagentAwareStartScriptGenerator( } else { str.replace( "-javaagent:COM_RYANDENS_JAVAAGENTS_PLACEHOLDER.jar", - javaagentFiles.get().joinToString(" ") { jar -> "-javaagent:\$APP_HOME/agent-libs/${jar.name}" }, + javaagentFiles.get().joinToString( + " ", + ) { jar -> "-javaagent:\$APP_HOME${pathSeparator}agent-libs${pathSeparator}${jar.name}" }, ) } super.write(replace) diff --git a/plugin/src/main/kotlin/com/ryandens/javaagent/Platform.kt b/plugin/src/main/kotlin/com/ryandens/javaagent/Platform.kt new file mode 100644 index 0000000..3bd7983 --- /dev/null +++ b/plugin/src/main/kotlin/com/ryandens/javaagent/Platform.kt @@ -0,0 +1,16 @@ +package com.ryandens.javaagent + +import org.gradle.api.internal.plugins.StartScriptTemplateBindingFactory +import org.gradle.api.internal.plugins.UnixStartScriptGenerator +import org.gradle.api.internal.plugins.WindowsStartScriptGenerator +import org.gradle.api.resources.TextResource + +enum class Platform( + val lineSeparator: String, + val pathSeparator: String, + val templateBindingFactory: StartScriptTemplateBindingFactory, + val template: TextResource, +) { + UNIX("\n", "/", StartScriptTemplateBindingFactory.unix(), UnixStartScriptGenerator().template), + WINDOWS("\r\n", "\\", StartScriptTemplateBindingFactory.windows(), WindowsStartScriptGenerator().template), +}