diff --git a/src/main/java/com/palantir/gradle/graal/BaseGraalCompileTask.java b/src/main/java/com/palantir/gradle/graal/BaseGraalCompileTask.java index 92b40c53..51831cd7 100644 --- a/src/main/java/com/palantir/gradle/graal/BaseGraalCompileTask.java +++ b/src/main/java/com/palantir/gradle/graal/BaseGraalCompileTask.java @@ -18,9 +18,11 @@ import java.io.File; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.ArrayList; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; @@ -29,6 +31,7 @@ import org.gradle.api.artifacts.Configuration; import org.gradle.api.file.RegularFile; import org.gradle.api.file.RegularFileProperty; +import org.gradle.api.logging.LogLevel; import org.gradle.api.provider.ListProperty; import org.gradle.api.provider.Property; import org.gradle.api.provider.Provider; @@ -37,9 +40,10 @@ import org.gradle.api.tasks.InputFile; import org.gradle.api.tasks.InputFiles; import org.gradle.api.tasks.OutputFile; +import org.gradle.process.ExecSpec; -public class BaseGraalCompileTask extends DefaultTask { +public abstract class BaseGraalCompileTask extends DefaultTask { private final Property outputName = getProject().getObjects().property(String.class); private final ListProperty options = getProject().getObjects().listProperty(String.class); private final RegularFileProperty outputFile = getProject().getObjects().fileProperty(); @@ -52,9 +56,11 @@ public BaseGraalCompileTask() { setGroup(GradleGraalPlugin.TASK_GROUP); this.outputFile.set(getProject().getLayout().getBuildDirectory() .dir("graal") - .map(d -> d.file(outputName.get()))); + .map(d -> d.file(outputName.get() + getArchitectureSpecifiedOutputExtension()))); } + protected abstract String getArchitectureSpecifiedOutputExtension(); + protected final File maybeCreateOutputDirectory() throws IOException { File directory = getOutputFile().get().getAsFile().getParentFile(); Files.createDirectories(directory.toPath()); @@ -96,18 +102,82 @@ protected final String generateClasspathArgument() { classpathArgument.addAll(classpath.get().getFiles()); classpathArgument.add(jarFile.getAsFile().get()); - return classpathArgument.stream().map(File::getAbsolutePath).collect(Collectors.joining(":")); + return classpathArgument.stream() + .map(File::getAbsolutePath) + .collect(Collectors.joining(getArchitectureSpecifiedPathSeparator())); } private Path getArchitectureSpecifiedBinaryPath() { switch (Platform.operatingSystem()) { case MAC: return Paths.get("Contents", "Home", "bin", "native-image"); case LINUX: return Paths.get("bin", "native-image"); + case WINDOWS: return Paths.get("bin", "native-image.cmd"); + default: + throw new IllegalStateException("No GraalVM support for " + Platform.operatingSystem()); + } + } + + private String getArchitectureSpecifiedPathSeparator() { + switch (Platform.operatingSystem()) { + case MAC: + case LINUX: + return ":"; + case WINDOWS: + return ";"; default: throw new IllegalStateException("No GraalVM support for " + Platform.operatingSystem()); } } + protected final void configurePlatformSpecifics(ExecSpec spec) { + if (Platform.operatingSystem() == Platform.OperatingSystem.WINDOWS) { + // on Windows the native-image executable needs to be launched from the Windows SDK Command Prompt + // this is mentioned at https://github.com/oracle/graal/tree/master/substratevm#quick-start + // here we create and launch a temporary .cmd file that first calls SetEnv.cmd and then runs Graal + + String outputRedirection = ""; + if (!getLogger().isEnabled(LogLevel.INFO)) { + // hide the output of SetEnv.cmd (an error that can safely be ignored and info messages) + // if Gradle isn't run with e.g. --info + outputRedirection = " >nul 2>&1"; + } + + String argsString = spec.getArgs().stream().collect(Collectors.joining(" ", " ", "\r\n")); + String cmdContent = "@echo off\r\n" + + "call \"C:\\Program Files\\Microsoft SDKs\\Windows\\v7.1\\Bin\\SetEnv.cmd\"" + + outputRedirection + "\r\n" + + "\"" + spec.getExecutable() + "\"" + argsString; + Path buildPath = getProject().getBuildDir().toPath(); + Path startCmd = buildPath.resolve("tmp").resolve("com.palantir.graal").resolve("native-image.cmd"); + try { + if (!Files.exists(startCmd.getParent())) { + Files.createDirectories(startCmd.getParent()); + } + Files.write(startCmd, cmdContent.getBytes(StandardCharsets.UTF_8)); + } catch (IOException e) { + throw new RuntimeException(e); + } + + List cmdArgs = new ArrayList<>(); + // command extensions + cmdArgs.add("/E:ON"); + // delayed environment variable expansion via ! + cmdArgs.add("/V:ON"); + cmdArgs.add("/c"); + cmdArgs.add("\"" + startCmd.toString() + "\""); + spec.setExecutable("cmd.exe"); + spec.setArgs(cmdArgs); + } + } + + protected static long fileSizeMegabytes(RegularFile regularFile) { + try { + return Files.size(regularFile.getAsFile().toPath()) / (1000 * 1000); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + @Input public final Provider getOutputName() { return outputName; diff --git a/src/main/java/com/palantir/gradle/graal/DownloadGraalTask.java b/src/main/java/com/palantir/gradle/graal/DownloadGraalTask.java index 36d8582e..880258d6 100644 --- a/src/main/java/com/palantir/gradle/graal/DownloadGraalTask.java +++ b/src/main/java/com/palantir/gradle/graal/DownloadGraalTask.java @@ -33,12 +33,13 @@ /** Downloads GraalVM binaries. */ public class DownloadGraalTask extends DefaultTask { + // RC versions don't have a windows variant, so no [ext] is needed private static final String ARTIFACT_PATTERN_RC_VERSION = "[url]/vm-[version]/graalvm-ce-[version]-[os]-[arch].tar.gz"; private static final String ARTIFACT_PATTERN_RELEASE_VERSION - = "[url]/vm-[version]/graalvm-ce-[os]-[arch]-[version].tar.gz"; + = "[url]/vm-[version]/graalvm-ce-[os]-[arch]-[version].[ext]"; - private static final String FILENAME_PATTERN = "graalvm-ce-[version]-[arch].tar.gz"; + private static final String FILENAME_PATTERN = "graalvm-ce-[version]-[arch].[ext]"; private final Property graalVersion = getProject().getObjects().property(String.class); private final Property downloadBaseUrl = getProject().getObjects().property(String.class); @@ -48,7 +49,7 @@ public DownloadGraalTask() { setGroup(GradleGraalPlugin.TASK_GROUP); setDescription("Downloads and caches GraalVM binaries."); - onlyIf(task -> !getTgz().get().getAsFile().exists()); + onlyIf(task -> !getArchive().get().getAsFile().exists()); } @TaskAction @@ -60,12 +61,12 @@ public final void downloadGraal() throws IOException { ? ARTIFACT_PATTERN_RC_VERSION : ARTIFACT_PATTERN_RELEASE_VERSION; try (InputStream in = new URL(render(artifactPattern)).openStream()) { - Files.copy(in, getTgz().get().getAsFile().toPath(), StandardCopyOption.REPLACE_EXISTING); + Files.copy(in, getArchive().get().getAsFile().toPath(), StandardCopyOption.REPLACE_EXISTING); } } @OutputFile - public final Provider getTgz() { + public final Provider getArchive() { return getProject().getLayout() .file(getCacheSubdirectory().map(dir -> dir.resolve(render(FILENAME_PATTERN)).toFile())); } @@ -97,7 +98,8 @@ private String render(String pattern) { .replaceAll("\\[url\\]", downloadBaseUrl.get()) .replaceAll("\\[version\\]", graalVersion.get()) .replaceAll("\\[os\\]", getOperatingSystem()) - .replaceAll("\\[arch\\]", getArchitecture()); + .replaceAll("\\[arch\\]", getArchitecture()) + .replaceAll("\\[ext\\]", getArchiveExtension()); } private String getOperatingSystem() { @@ -106,6 +108,8 @@ private String getOperatingSystem() { return isGraalRcVersion() ? "macos" : "darwin"; case LINUX: return "linux"; + case WINDOWS: + return "windows"; default: throw new IllegalStateException("No GraalVM support for " + Platform.operatingSystem()); } @@ -120,6 +124,18 @@ private String getArchitecture() { } } + private String getArchiveExtension() { + switch (Platform.operatingSystem()) { + case MAC: + case LINUX: + return "tar.gz"; + case WINDOWS: + return "zip"; + default: + throw new IllegalStateException("No GraalVM support for " + Platform.operatingSystem()); + } + } + private boolean isGraalRcVersion() { return graalVersion.get().startsWith("1.0.0-rc"); } diff --git a/src/main/java/com/palantir/gradle/graal/ExtractGraalTask.java b/src/main/java/com/palantir/gradle/graal/ExtractGraalTask.java index f5c75091..0e9541f3 100644 --- a/src/main/java/com/palantir/gradle/graal/ExtractGraalTask.java +++ b/src/main/java/com/palantir/gradle/graal/ExtractGraalTask.java @@ -20,7 +20,11 @@ import java.io.File; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; import org.gradle.api.DefaultTask; +import org.gradle.api.Project; import org.gradle.api.file.Directory; import org.gradle.api.file.DirectoryProperty; import org.gradle.api.file.RegularFile; @@ -34,15 +38,21 @@ /** Extracts GraalVM tooling from downloaded tgz archive using the system's tar command. */ public class ExtractGraalTask extends DefaultTask { + /** + * These binaries get .cmd as their filename extension, instead of .cmd (on Windows). + */ + private static final Set WINDOWS_CMD_BINARIES = + new HashSet<>(Arrays.asList("native-image", "native-image-configure", "polyglot")); - private final RegularFileProperty inputTgz = getProject().getObjects().fileProperty(); + private final RegularFileProperty inputArchive = getProject().getObjects().fileProperty(); private final Property graalVersion = getProject().getObjects().property(String.class); private final DirectoryProperty outputDirectory = getProject().getObjects().directoryProperty(); private final Property cacheDir = getProject().getObjects().property(Path.class); public ExtractGraalTask() { setGroup(GradleGraalPlugin.TASK_GROUP); - setDescription("Extracts GraalVM tooling from downloaded tgz archive using the system's tar command."); + setDescription("Extracts GraalVM tooling from downloaded archive using the system's tar command or Gradle's" + + " copy method."); onlyIf(task -> !getOutputDirectory().get().getAsFile().exists()); outputDirectory.set(graalVersion.map(v -> @@ -58,15 +68,27 @@ public final void extractGraal() { throw new SafeIllegalStateException("extract task requires graal.graalVersion to be defined."); } - // ideally this would be a CopyTask, but through Gradle 4.9 CopyTask fails to correctly extract symlinks - getProject().exec(spec -> { - spec.executable("tar"); - spec.args("-xzf", inputTgz.get().getAsFile().getAbsolutePath()); - spec.workingDir(cacheDir.get().resolve(graalVersion.get())); - }); + Project project = getProject(); + File inputArchiveFile = inputArchive.get().getAsFile(); + Path versionedCacheDir = cacheDir.get().resolve(graalVersion.get()); + + if (inputArchiveFile.getName().endsWith(".zip")) { + project.copy(copySpec -> { + copySpec.from(project.zipTree(inputArchiveFile)); + copySpec.into(versionedCacheDir); + }); + } else { + // ideally this would be a CopyTask, but through Gradle 4.9 CopyTask fails to correctly extract symlinks + project.exec(spec -> { + spec.executable("tar"); + spec.args("-xzf", inputArchiveFile.getAbsolutePath()); + spec.workingDir(versionedCacheDir); + }); + } + File nativeImageExecutable = getExecutable("native-image"); if (!nativeImageExecutable.isFile()) { - getProject().exec(spec -> { + project.exec(spec -> { File graalUpdateExecutable = getExecutable("gu"); if (!graalUpdateExecutable.isFile()) { throw new IllegalStateException("Failed to find Graal update binary: " + graalUpdateExecutable); @@ -77,29 +99,43 @@ public final void extractGraal() { } } + // has some overlap with BaseGraalCompileTask#getArchitectureSpecifiedBinaryPath() private File getExecutable(String binaryName) { + String binaryExtension = ""; + + if (Platform.operatingSystem() == Platform.OperatingSystem.WINDOWS) { + // most executables in the GraalVM distribution for Windows have an .exe extension + if (WINDOWS_CMD_BINARIES.contains(binaryName)) { + binaryExtension = ".cmd"; + } else { + binaryExtension = ".exe"; + } + } + return cacheDir.get() .resolve(Paths.get(graalVersion.get(), "graalvm-ce-" + graalVersion.get())) - .resolve(getArchitectureSpecifiedBinaryPath(binaryName)) + .resolve(getArchitectureSpecifiedBinaryPath(binaryName + binaryExtension)) .toFile(); } private Path getArchitectureSpecifiedBinaryPath(String binaryName) { switch (Platform.operatingSystem()) { case MAC: return Paths.get("Contents", "Home", "bin", binaryName); - case LINUX: return Paths.get("bin", binaryName); + case LINUX: + case WINDOWS: + return Paths.get("bin", binaryName); default: throw new IllegalStateException("No GraalVM support for " + Platform.operatingSystem()); } } @InputFile - public final Provider getInputTgz() { - return inputTgz; + public final Provider getInputArchive() { + return inputArchive; } - public final void setInputTgz(Provider value) { - this.inputTgz.set(value); + public final void setInputArchive(Provider value) { + this.inputArchive.set(value); } @Input diff --git a/src/main/java/com/palantir/gradle/graal/GradleGraalPlugin.java b/src/main/java/com/palantir/gradle/graal/GradleGraalPlugin.java index 6fc0db92..0e55b766 100644 --- a/src/main/java/com/palantir/gradle/graal/GradleGraalPlugin.java +++ b/src/main/java/com/palantir/gradle/graal/GradleGraalPlugin.java @@ -71,7 +71,7 @@ public final void apply(Project project) { ExtractGraalTask.class, task -> { task.setGraalVersion(extension.getGraalVersion()); - task.setInputTgz(downloadGraal.get().getTgz()); + task.setInputArchive(downloadGraal.get().getArchive()); task.setCacheDir(cacheDir); task.dependsOn(downloadGraal); }); diff --git a/src/main/java/com/palantir/gradle/graal/NativeImageTask.java b/src/main/java/com/palantir/gradle/graal/NativeImageTask.java index 277c921c..210e1188 100644 --- a/src/main/java/com/palantir/gradle/graal/NativeImageTask.java +++ b/src/main/java/com/palantir/gradle/graal/NativeImageTask.java @@ -17,12 +17,10 @@ package com.palantir.gradle.graal; import java.io.IOException; -import java.nio.file.Files; import java.util.ArrayList; import java.util.List; import org.gradle.api.Action; import org.gradle.api.Task; -import org.gradle.api.file.RegularFile; import org.gradle.api.provider.Property; import org.gradle.api.provider.Provider; import org.gradle.api.tasks.Input; @@ -37,18 +35,39 @@ public class NativeImageTask extends BaseGraalCompileTask { public NativeImageTask() { setDescription("Runs GraalVM's native-image command with configured options and parameters."); + // must use an anonymous inner class instead of a lambda to get Gradle staleness checking doLast(new LogAction()); } + /** + * Returns a platform-dependent file extension for executables. + * + * @return an empty String on {@link Platform.OperatingSystem#MAC MAC} and + * {@link Platform.OperatingSystem#LINUX LINUX}, ".exe" on {@link Platform.OperatingSystem#WINDOWS WINDOWS} + */ + @Override + protected String getArchitectureSpecifiedOutputExtension() { + switch (Platform.operatingSystem()) { + case MAC: + case LINUX: + return ""; + case WINDOWS: + return ".exe"; + default: + throw new IllegalStateException("No GraalVM support for " + Platform.operatingSystem()); + } + } + @TaskAction public final void nativeImage() throws IOException { List args = new ArrayList<>(); configureArgs(args); args.add(mainClass.get()); getProject().exec(spec -> { - spec.executable(getExecutable()); - spec.args(args); + spec.setExecutable(getExecutable()); + spec.setArgs(args); + configurePlatformSpecifics(spec); }); } @@ -61,18 +80,10 @@ public final void setMainClass(Provider provider) { mainClass.set(provider); } - private long fileSizeMegabytes(RegularFile regularFile) { - try { - return Files.size(regularFile.getAsFile().toPath()) / (1000 * 1000); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - private class LogAction implements Action { @Override public void execute(Task task) { - getLogger().warn("native-image available at {} ({}MB)", + getLogger().warn("native image available at {} ({} MB)", getProject().relativePath(getOutputFile().get().getAsFile()), fileSizeMegabytes(getOutputFile().get())); } diff --git a/src/main/java/com/palantir/gradle/graal/SharedLibraryTask.java b/src/main/java/com/palantir/gradle/graal/SharedLibraryTask.java index 7b6ecedd..f3739e2b 100644 --- a/src/main/java/com/palantir/gradle/graal/SharedLibraryTask.java +++ b/src/main/java/com/palantir/gradle/graal/SharedLibraryTask.java @@ -19,6 +19,8 @@ import java.io.IOException; import java.util.ArrayList; import java.util.List; +import org.gradle.api.Action; +import org.gradle.api.Task; import org.gradle.api.tasks.TaskAction; /** @@ -30,6 +32,28 @@ public SharedLibraryTask() { setDescription( "Runs GraalVM's native-image command configured to produce a shared library." ); + // must use an anonymous inner class instead of a lambda to get Gradle staleness checking + doLast(new LogAction()); + } + + /** + * Returns a platform-dependent file extension for libraries. + * + * @return ".dylib" on {@link Platform.OperatingSystem#MAC MAC}, ".so" on + * {@link Platform.OperatingSystem#LINUX LINUX}, ".dll" on {@link Platform.OperatingSystem#WINDOWS WINDOWS} + */ + @Override + protected String getArchitectureSpecifiedOutputExtension() { + switch (Platform.operatingSystem()) { + case MAC: + return ".dylib"; + case LINUX: + return ".so"; + case WINDOWS: + return ".dll"; + default: + throw new IllegalStateException("No GraalVM support for " + Platform.operatingSystem()); + } } @TaskAction @@ -38,9 +62,19 @@ public final void sharedLibrary() throws IOException { args.add("--shared"); configureArgs(args); getProject().exec(spec -> { - spec.executable(getExecutable()); - spec.args(args); + spec.setExecutable(getExecutable()); + spec.setArgs(args); + configurePlatformSpecifics(spec); }); } + private class LogAction implements Action { + @Override + public void execute(Task task) { + getLogger().warn("shared library available at {} ({} MB)", + getProject().relativePath(getOutputFile().get().getAsFile()), + fileSizeMegabytes(getOutputFile().get())); + } + } + } diff --git a/src/test/groovy/com/palantir/gradle/graal/GradleGraalEndToEndSpec.groovy b/src/test/groovy/com/palantir/gradle/graal/GradleGraalEndToEndSpec.groovy index cae1efcf..9f055e8c 100644 --- a/src/test/groovy/com/palantir/gradle/graal/GradleGraalEndToEndSpec.groovy +++ b/src/test/groovy/com/palantir/gradle/graal/GradleGraalEndToEndSpec.groovy @@ -18,8 +18,11 @@ package com.palantir.gradle.graal import nebula.test.IntegrationSpec import nebula.test.functional.ExecutionResult +import spock.lang.IgnoreIf + import static com.palantir.gradle.graal.Platform.OperatingSystem.LINUX import static com.palantir.gradle.graal.Platform.OperatingSystem.MAC +import static com.palantir.gradle.graal.Platform.OperatingSystem.WINDOWS class GradleGraalEndToEndSpec extends IntegrationSpec { @@ -49,11 +52,15 @@ class GradleGraalEndToEndSpec extends IntegrationSpec { ExecutionResult result = runTasksSuccessfully('nativeImage') // note, this accesses your real ~/.gradle cache println "Gradle Standard Out:\n" + result.standardOutput println "Gradle Standard Error:\n" + result.standardError - File output = new File(getProjectDir(), "build/graal/hello-world"); + def outputPath = "build/graal/hello-world" + if (Platform.operatingSystem() == WINDOWS) { + outputPath += ".exe" + } + File output = new File(getProjectDir(), outputPath) then: output.exists() - output.getAbsolutePath().execute().text.equals("hello, world!\n") + output.getAbsolutePath().execute().text.equals("hello, world!" + System.lineSeparator()) when: ExecutionResult result2 = runTasksSuccessfully('nativeImage') @@ -76,9 +83,11 @@ class GradleGraalEndToEndSpec extends IntegrationSpec { then: println result3.standardOutput !result3.wasUpToDate(':nativeImage') - output.getAbsolutePath().execute().text.equals("hello, world (modified)!\n") + output.getAbsolutePath().execute().text.equals("hello, world (modified)!" + System.lineSeparator()) } + // there is no RC version for Windows + @IgnoreIf({ Platform.operatingSystem() == WINDOWS }) def 'test 1.0.0-rc5 nativeImage'() { setup: directory("src/main/java/com/palantir/test") @@ -106,11 +115,15 @@ class GradleGraalEndToEndSpec extends IntegrationSpec { ExecutionResult result = runTasksSuccessfully('nativeImage') // note, this accesses your real ~/.gradle cache println "Gradle Standard Out:\n" + result.standardOutput println "Gradle Standard Error:\n" + result.standardError - File output = new File(getProjectDir(), "build/graal/hello-world"); + def outputPath = "build/graal/hello-world" + if (Platform.operatingSystem() == Platform.OperatingSystem.WINDOWS) { + outputPath += ".exe" + } + File output = new File(getProjectDir(), outputPath) then: output.exists() - output.getAbsolutePath().execute().text.equals("hello, world!\n") + output.getAbsolutePath().execute().text.equals("hello, world!" + System.lineSeparator()) when: ExecutionResult result2 = runTasksSuccessfully('nativeImage') @@ -133,7 +146,7 @@ class GradleGraalEndToEndSpec extends IntegrationSpec { then: println result3.standardOutput !result3.wasUpToDate(':nativeImage') - output.getAbsolutePath().execute().text.equals("hello, world (modified)!\n") + output.getAbsolutePath().execute().text.equals("hello, world (modified)!" + System.lineSeparator()) } def 'allows specifying additional properties'() { @@ -164,7 +177,7 @@ class GradleGraalEndToEndSpec extends IntegrationSpec { graal { mainClass 'com.palantir.test.Main' outputName 'hello-world' - graalVersion '1.0.0-rc5' + graalVersion '19.1.0' // By default, only file:// is supported, see https://github.com/oracle/graal/blob/master/substratevm/URL-PROTOCOLS.md option '-H:EnableURLProtocols=http' } @@ -174,7 +187,11 @@ class GradleGraalEndToEndSpec extends IntegrationSpec { ExecutionResult result = runTasks('nativeImage') // note, this accesses your real ~/.gradle cache println "Gradle Standard Out:\n" + result.standardOutput println "Gradle Standard Error:\n" + result.standardError - File output = new File(getProjectDir(), "build/graal/hello-world"); + def outputPath = "build/graal/hello-world" + if (Platform.operatingSystem() == WINDOWS) { + outputPath += ".exe" + } + File output = new File(getProjectDir(), outputPath) then: output.exists() @@ -203,6 +220,8 @@ class GradleGraalEndToEndSpec extends IntegrationSpec { dylibFile.exists() } + // there is no RC version for Windows + @IgnoreIf({ Platform.operatingSystem() == WINDOWS }) def 'can build shared libraries on 1.0.0-rc5'() { setup: directory("src/main/java/com/palantir/test") @@ -232,6 +251,8 @@ class GradleGraalEndToEndSpec extends IntegrationSpec { return "dylib" case LINUX: return "so" + case WINDOWS: + return "dll" default: throw new IllegalStateException("No GraalVM support for " + Platform.operatingSystem()) } @@ -243,7 +264,7 @@ class GradleGraalEndToEndSpec extends IntegrationSpec { graal { outputName 'hello-world' - graalVersion '1.0.0-rc5' + graalVersion '19.1.0' option '-H:EnableURLProtocols=http' } ''' @@ -264,7 +285,7 @@ class GradleGraalEndToEndSpec extends IntegrationSpec { graal { mainClass 'com.palantir.test.Main' outputName 'hello-world' - graalVersion '1.0.0-rc5' + graalVersion '19.1.0' option '-H:EnableURLProtocols=http' option '-H:Name=foo' } diff --git a/src/test/groovy/com/palantir/gradle/graal/GradleGraalPluginIntegrationSpec.groovy b/src/test/groovy/com/palantir/gradle/graal/GradleGraalPluginIntegrationSpec.groovy index c741d1c2..e5081d64 100644 --- a/src/test/groovy/com/palantir/gradle/graal/GradleGraalPluginIntegrationSpec.groovy +++ b/src/test/groovy/com/palantir/gradle/graal/GradleGraalPluginIntegrationSpec.groovy @@ -21,6 +21,8 @@ import nebula.test.functional.ExecutionResult import okhttp3.mockwebserver.MockResponse import okhttp3.mockwebserver.MockWebServer import org.junit.Rule +import spock.lang.IgnoreIf +import spock.lang.Requires class GradleGraalPluginIntegrationSpec extends IntegrationSpec { @@ -41,9 +43,13 @@ class GradleGraalPluginIntegrationSpec extends IntegrationSpec { } ''' - file('gradle.properties') << "com.palantir.graal.cache.dir=${getProjectDir().toPath().resolve("cacheDir").toAbsolutePath()}" + // on Windows the path contains backslashes, which need to be escaped in .properties files + def cacheDirPath = getProjectDir().toPath().resolve("cacheDir").toAbsolutePath().toString().replace("\\", "\\\\") + file('gradle.properties') << "com.palantir.graal.cache.dir=${cacheDirPath}" } + // there is no RC version for Windows + @IgnoreIf({ Platform.operatingSystem() == Platform.OperatingSystem.WINDOWS }) def 'allows specifying different RC graal version'() { setup: buildFile << """ @@ -54,7 +60,7 @@ class GradleGraalPluginIntegrationSpec extends IntegrationSpec { downloadBaseUrl '${fakeBaseUrl}' } """ - server.enqueue(new MockResponse().setBody('<>')); + server.enqueue(new MockResponse().setBody('<>')) when: ExecutionResult result = runTasksSuccessfully('downloadGraalTooling') @@ -65,13 +71,16 @@ class GradleGraalPluginIntegrationSpec extends IntegrationSpec { !result.wasUpToDate(':downloadGraalTooling') !result.wasSkipped(':downloadGraalTooling') - server.takeRequest().requestUrl.toString() =~ "http://localhost:${server.port}" + + // requestUrl can contain "127.0.0.1" instead of "localhost" + server.takeRequest().requestUrl.toString() =~ "http://(localhost|127\\.0\\.0\\.1):${server.port}" + "/oracle/graal/releases/download//vm-1.0.0-rc3/graalvm-ce-1.0.0-rc3-(macos|linux)-amd64.tar.gz" file("cacheDir/1.0.0-rc3/graalvm-ce-1.0.0-rc3-amd64.tar.gz").text == '<>' } - def 'allows specifying different GA graal version'() { + // for Windows the download is a .zip, this is tested below + @IgnoreIf({ Platform.operatingSystem() == Platform.OperatingSystem.WINDOWS }) + def 'allows specifying different GA graal version (non-windows)'() { setup: buildFile << """ apply plugin: 'com.palantir.graal' @@ -81,7 +90,7 @@ class GradleGraalPluginIntegrationSpec extends IntegrationSpec { downloadBaseUrl '${fakeBaseUrl}' } """ - server.enqueue(new MockResponse().setBody('<>')); + server.enqueue(new MockResponse().setBody('<>')) when: ExecutionResult result = runTasksSuccessfully('downloadGraalTooling') @@ -92,12 +101,48 @@ class GradleGraalPluginIntegrationSpec extends IntegrationSpec { !result.wasUpToDate(':downloadGraalTooling') !result.wasSkipped(':downloadGraalTooling') - server.takeRequest().requestUrl.toString() =~ "http://localhost:${server.port}" + - "/oracle/graal/releases/download//vm-19.0.0/graalvm-ce-(darwin|linux)-amd64-19.0.0.tar.gz" + // `requestUrl` can contain "127.0.0.1" instead of "localhost" + // worse yet, it can contain any hostname that is defined for 127.0.0.1 in the hosts file + // e.g. Docker Desktop puts "127.0.0.1 kubernetes.docker.internal" in there, which ends up in `requestUrl` + // so the comparison is only made for `path` + server.takeRequest().path =~ + "/oracle/graal/releases/download//vm-19.0.0/graalvm-ce-(darwin|linux)-amd64-19.0.0.tar.gz" file("cacheDir/19.0.0/graalvm-ce-19.0.0-amd64.tar.gz").text == '<>' } + @Requires({ Platform.operatingSystem() == Platform.OperatingSystem.WINDOWS }) + def 'allows specifying different GA graal version (windows)'() { + setup: + buildFile << """ + apply plugin: 'com.palantir.graal' + + graal { + graalVersion '19.0.0' + downloadBaseUrl '${fakeBaseUrl}' + } + """ + server.enqueue(new MockResponse().setBody('<>')) + + when: + ExecutionResult result = runTasksSuccessfully('downloadGraalTooling') + + then: + println result.getStandardOutput() + result.wasExecuted(':downloadGraalTooling') + !result.wasUpToDate(':downloadGraalTooling') + !result.wasSkipped(':downloadGraalTooling') + + // `requestUrl` can contain "127.0.0.1" instead of "localhost" + // worse yet, it can contain any hostname that is defined for 127.0.0.1 in the hosts file + // e.g. Docker Desktop puts "127.0.0.1 kubernetes.docker.internal" in there, which ends up in `requestUrl` + // so the comparison is only made for `path` + server.takeRequest().path =~ + "/oracle/graal/releases/download//vm-19.0.0/graalvm-ce-windows-amd64-19.0.0.zip" + + file("cacheDir/19.0.0/graalvm-ce-19.0.0-amd64.zip").text == '<>' + } + def 'downloadGraalTooling behaves incrementally'() { setup: buildFile << """ @@ -107,7 +152,7 @@ class GradleGraalPluginIntegrationSpec extends IntegrationSpec { downloadBaseUrl '${fakeBaseUrl}' } """ - server.enqueue(new MockResponse().setBody('<>')); + server.enqueue(new MockResponse().setBody('<>')) when: ExecutionResult result1 = runTasksSuccessfully('downloadGraalTooling')