diff --git a/substratevm/CHANGELOG.md b/substratevm/CHANGELOG.md index 7c4bc8d6b625..1c018bb7be87 100644 --- a/substratevm/CHANGELOG.md +++ b/substratevm/CHANGELOG.md @@ -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. diff --git a/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/WindowsBuildEnvironmentUtil.java b/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/WindowsBuildEnvironmentUtil.java index 97f569d02d95..34052c72fdb1 100644 --- a/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/WindowsBuildEnvironmentUtil.java +++ b/substratevm/src/com.oracle.svm.driver/src/com/oracle/svm/driver/WindowsBuildEnvironmentUtil.java @@ -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 @@ -30,12 +30,9 @@ 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; @@ -43,66 +40,32 @@ * 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 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 installationCandidates; - try (Stream 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 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 @@ -122,11 +85,44 @@ static void propagateEnv(Map 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; @@ -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 consumeKeyValue) { String[] parts = line.split("="); if (parts.length == 2) { @@ -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); } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/codegen/CCompilerInvoker.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/codegen/CCompilerInvoker.java index 4902e98e8b6e..47954c33a1e7 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/codegen/CCompilerInvoker.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/c/codegen/CCompilerInvoker.java @@ -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; @@ -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()); } }