Skip to content

Commit

Permalink
[GR-51053] Use vswhere to detect Visual Studio installations.
Browse files Browse the repository at this point in the history
PullRequest: graal/16435
  • Loading branch information
fniephaus committed Mar 8, 2024
2 parents 5c7190b + bc2fbe9 commit 27a9362
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 47 deletions.
1 change: 1 addition & 0 deletions substratevm/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ This changelog summarizes major changes to GraalVM Native Image.
* (GR-52030) Add a stable name for `Proxy` types in Native Image. The name `$Proxy[id]` is replaced by `$Proxy.s[hashCode]` where `hashCode` is computed using the names of the `Proxy` interfaces, the name of the class loader and the name of the module if it is not a dynamic module.
* (GR-47712) Using the `--static` option without the `--libc=musl` option causes the build process to fail (and reports the appropriate error). Static linking is currently only supported with musl.
* (GR-50434) Introduce a `"type"` field in reflection and JNI configuration files to support more than simple named types.
* (GR-51053) Use [`vswhere`](https://github.com/microsoft/vswhere) to find Visual Studio installations more reliably and in non-standard installation locations.

## GraalVM for JDK 22 (Internal Version 24.0.0)
* (GR-48304) Red Hat added support for the JFR event ThreadAllocationStatistics.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
Expand Down Expand Up @@ -30,79 +30,42 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.stream.Stream;

import com.oracle.svm.hosted.c.codegen.CCompilerInvoker;

/**
* This utility helps set up a build environment for Windows users automatically.
*/
class WindowsBuildEnvironmentUtil {
private static final String VSINSTALLDIR_ENV = "VSINSTALLDIR";
private static final Path[] KNOWN_VS_LOCATIONS = {
Paths.get("C:", "Program Files", "Microsoft Visual Studio"),
// prefer x64 location over x86
Paths.get("C:", "Program Files (x86)", "Microsoft Visual Studio")};
private static final String[] KNOWN_VS_EDITIONS = {
// prefer Enterprise over Professional over Community over BuildTools
"Enterprise", "Professional", "Community", "BuildTools"};
private static final String VCVARSALL = "vcvarsall.bat";
private static final Path VCVARSALL_SUBPATH = Paths.get("VC", "Auxiliary", "Build", VCVARSALL);
private static final String OUTPUT_SEPARATOR = "!NEXTCOMMAND!";
// Use another static field for minimum required version because CCompilerInvoker is hosted only
private static final String VISUAL_STUDIO_MINIMUM_REQUIRED_VERSION = CCompilerInvoker.VISUAL_STUDIO_MINIMUM_REQUIRED_VERSION;
private static final String VISUAL_STUDIO_MINIMUM_REQUIRED_VERSION_TEXT = CCompilerInvoker.VISUAL_STUDIO_MINIMUM_REQUIRED_VERSION_TEXT;

static void propagateEnv(Map<String, String> environment) {
if (isCCompilerOnPath()) {
return; // nothing to do, build environment initialized by user
}
Path vcVarsAllLocation = null;
for (Path visualStudioLocation : KNOWN_VS_LOCATIONS) {
if (!Files.isDirectory(visualStudioLocation)) {
continue;
}
List<Path> installationCandidates;
try (Stream<Path> pathStream = Files.list(visualStudioLocation)) {
installationCandidates = pathStream
// only keep sub-directories matching 20\d\d
.filter(p -> p.toFile().getName().matches("20\\d\\d"))
// sort years
.sorted(Comparator.comparing(p -> p.toFile().getName()))
// reverse order to ensure latest year is first
.sorted(Comparator.reverseOrder())
.toList();
} catch (IOException e) {
throw fail("Failed to traverse known Visual Studio locations.", e);
}
for (Path installation : installationCandidates) {
for (String edition : KNOWN_VS_EDITIONS) {
Path possibleLocation = installation.resolve(edition).resolve(VCVARSALL_SUBPATH);
if (Files.isRegularFile(possibleLocation) && Files.isReadable(possibleLocation)) {
vcVarsAllLocation = possibleLocation;
break;
}
}
}
}
Path vcVarsAllLocation = findVCVarsallWithVSWhere();
if (vcVarsAllLocation == null) {
throw fail(String.format("Failed to find '%s' in a Visual Studio installation.", VCVARSALL));
}
Map<String, String> originalEnv = new HashMap<>();
int numSeenOutputSeparators = 0;
String outputSeparator = "!NEXTCOMMAND!";
try {
// call `set`, then `vcvarsall.bat`, and then `set` again with separators in between
String commandSequence = String.format("cmd.exe /c set && echo %s && \"%s\" x64 && echo %s && set",
OUTPUT_SEPARATOR, vcVarsAllLocation, OUTPUT_SEPARATOR);
outputSeparator, vcVarsAllLocation, outputSeparator);
Process p = Runtime.getRuntime().exec(new String[]{"cmd.exe", "/c", commandSequence});
try (BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream()))) {
String line;
while ((line = reader.readLine()) != null) {
if (line.startsWith(OUTPUT_SEPARATOR)) {
if (line.startsWith(outputSeparator)) {
numSeenOutputSeparators++;
} else if (numSeenOutputSeparators == 0) {
// collect environment variables from 1st `set` invocation
Expand All @@ -122,11 +85,44 @@ static void propagateEnv(Map<String, String> environment) {
} catch (IOException | InterruptedException e) {
throw fail("Failed to detect variables of Windows build environment.", e);
}
if (!environment.containsKey(VSINSTALLDIR_ENV)) {
if (!environment.containsKey("VSINSTALLDIR_ENV")) {
throw fail("Failed to automatically set up Windows build environment.");
}
}

private static Path findVCVarsallWithVSWhere() {
String programFilesX86 = System.getenv("ProgramFiles(x86)");
if (programFilesX86 == null) {
return null;
}
/*
* vswhere is included with the installer as of Visual Studio 2017 version 15.2 and later,
* and can be found at the following location: `%ProgramFiles(x86)%\Microsoft Visual
* Studio\Installer\vswhere.exe` (see:
* https://github.com/microsoft/vswhere/blob/2717133/README.md).
*/
Path vsWhereExe = Paths.get(programFilesX86, "Microsoft Visual Studio", "Installer", "vswhere.exe");
if (!Files.exists(vsWhereExe)) {
return null;
}
try {
Process p = Runtime.getRuntime().exec(new String[]{vsWhereExe.toAbsolutePath().toString(),
"-version", VISUAL_STUDIO_MINIMUM_REQUIRED_VERSION,
"-requires", "Microsoft.VisualStudio.Component.VC.Tools.x86.x64",
"-property", "installationPath"});
try (BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream()))) {
Path installationPath = Paths.get(reader.readLine());
Path possibleLocation = installationPath.resolve(VCVARSALL_SUBPATH);
if (isRegularReadableFile(possibleLocation)) {
return possibleLocation;
}
}
} catch (IOException e) {
throw fail("Failed to process vswhere.exe output.", e);
}
throw fail("Failed to find suitable version of Visual Studio with vswhere.exe.");
}

private static boolean isCCompilerOnPath() {
try {
return Runtime.getRuntime().exec(new String[]{"cmd.exe", "/c", "where", "cl.exe"}).waitFor() == 0;
Expand All @@ -135,6 +131,10 @@ private static boolean isCCompilerOnPath() {
}
}

private static boolean isRegularReadableFile(Path location) {
return Files.isRegularFile(location) && Files.isReadable(location);
}

private static void processLineWithKeyValue(String line, BiConsumer<String, String> consumeKeyValue) {
String[] parts = line.split("=");
if (parts.length == 2) {
Expand All @@ -150,6 +150,6 @@ private static Error fail(String reason, Throwable e) {
throw NativeImage.showError(String.format("%s%nPlease make sure that %s or later is installed on your system. " +
"You can download it at https://visualstudio.microsoft.com/downloads/. " +
"If this error persists, please try and run GraalVM Native Image in an x64 Native Tools Command Prompt or file a ticket.",
reason, VISUAL_STUDIO_MINIMUM_REQUIRED_VERSION), e);
reason, VISUAL_STUDIO_MINIMUM_REQUIRED_VERSION_TEXT), e);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@
import jdk.vm.ci.riscv64.RISCV64;

public abstract class CCompilerInvoker {
public static final String VISUAL_STUDIO_MINIMUM_REQUIRED_VERSION = "Visual Studio 2022 version 17.1.0";
public static final String VISUAL_STUDIO_MINIMUM_REQUIRED_VERSION = "17.6";
public static final String VISUAL_STUDIO_MINIMUM_REQUIRED_VERSION_TEXT = "Visual Studio 2022 version %s.0".formatted(VISUAL_STUDIO_MINIMUM_REQUIRED_VERSION);

public final Path tempDirectory;
public final CompilerInfo compilerInfo;
Expand Down Expand Up @@ -193,7 +194,7 @@ protected void verify() {
int minimumMinorVersion = 31;
if (compilerInfo.versionMajor < minimumMajorVersion || compilerInfo.versionMinor0 < minimumMinorVersion) {
UserError.abort("On Windows, GraalVM Native Image for JDK %s requires %s or later (C/C++ Optimizing Compiler Version %s.%s or later).%nCompiler info detected: %s",
JavaVersionUtil.JAVA_SPEC, VISUAL_STUDIO_MINIMUM_REQUIRED_VERSION, minimumMajorVersion, minimumMinorVersion, compilerInfo.getShortDescription());
JavaVersionUtil.JAVA_SPEC, VISUAL_STUDIO_MINIMUM_REQUIRED_VERSION_TEXT, minimumMajorVersion, minimumMinorVersion, compilerInfo.getShortDescription());
}
}

Expand Down

0 comments on commit 27a9362

Please sign in to comment.