Skip to content
This repository has been archived by the owner on Oct 7, 2024. It is now read-only.

Commit

Permalink
Add Windows support (#127)
Browse files Browse the repository at this point in the history
  • Loading branch information
frozenice authored and markelliot committed Aug 23, 2019
1 parent 859bf45 commit 58db239
Show file tree
Hide file tree
Showing 8 changed files with 291 additions and 58 deletions.
76 changes: 73 additions & 3 deletions src/main/java/com/palantir/gradle/graal/BaseGraalCompileTask.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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<String> outputName = getProject().getObjects().property(String.class);
private final ListProperty<String> options = getProject().getObjects().listProperty(String.class);
private final RegularFileProperty outputFile = getProject().getObjects().fileProperty();
Expand All @@ -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());
Expand Down Expand Up @@ -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<String> 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<String> getOutputName() {
return outputName;
Expand Down
28 changes: 22 additions & 6 deletions src/main/java/com/palantir/gradle/graal/DownloadGraalTask.java
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> graalVersion = getProject().getObjects().property(String.class);
private final Property<String> downloadBaseUrl = getProject().getObjects().property(String.class);
Expand All @@ -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
Expand All @@ -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<RegularFile> getTgz() {
public final Provider<RegularFile> getArchive() {
return getProject().getLayout()
.file(getCacheSubdirectory().map(dir -> dir.resolve(render(FILENAME_PATTERN)).toFile()));
}
Expand Down Expand Up @@ -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() {
Expand All @@ -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());
}
Expand All @@ -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");
}
Expand Down
66 changes: 51 additions & 15 deletions src/main/java/com/palantir/gradle/graal/ExtractGraalTask.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<String> 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<String> graalVersion = getProject().getObjects().property(String.class);
private final DirectoryProperty outputDirectory = getProject().getObjects().directoryProperty();
private final Property<Path> 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 ->
Expand All @@ -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);
Expand All @@ -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<RegularFile> getInputTgz() {
return inputTgz;
public final Provider<RegularFile> getInputArchive() {
return inputArchive;
}

public final void setInputTgz(Provider<RegularFile> value) {
this.inputTgz.set(value);
public final void setInputArchive(Provider<RegularFile> value) {
this.inputArchive.set(value);
}

@Input
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
Expand Down
Loading

0 comments on commit 58db239

Please sign in to comment.