diff --git a/windows/org.eclipse.cdt.msw.build/src/org/eclipse/cdt/internal/msw/build/IVSVersionConstants.java b/windows/org.eclipse.cdt.msw.build/src/org/eclipse/cdt/internal/msw/build/IVSVersionConstants.java new file mode 100644 index 00000000000..1c4ae2f3f9e --- /dev/null +++ b/windows/org.eclipse.cdt.msw.build/src/org/eclipse/cdt/internal/msw/build/IVSVersionConstants.java @@ -0,0 +1,19 @@ +/******************************************************************************* + * Copyright (c) 2020 Marc-Andre Laperle. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package org.eclipse.cdt.internal.msw.build; + +/** + * Constants related to Visual Studio versions. + */ +interface IVSVersionConstants { + VSVersionNumber VS2017_BASE_VER = new VSVersionNumber(15); + VSVersionNumber VS2019_BASE_VER = new VSVersionNumber(16); +} \ No newline at end of file diff --git a/windows/org.eclipse.cdt.msw.build/src/org/eclipse/cdt/internal/msw/build/MSVCToolChainInfo.java b/windows/org.eclipse.cdt.msw.build/src/org/eclipse/cdt/internal/msw/build/MSVCToolChainInfo.java new file mode 100644 index 00000000000..6516b5bd7c2 --- /dev/null +++ b/windows/org.eclipse.cdt.msw.build/src/org/eclipse/cdt/internal/msw/build/MSVCToolChainInfo.java @@ -0,0 +1,49 @@ +/******************************************************************************* + * Copyright (c) 2020 Marc-Andre Laperle. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package org.eclipse.cdt.internal.msw.build; + +/** + * Basic information about a MSVC toolchain as found under a VS installation. + * More information could be added here in order to support users selecting a specific + * toolchain (architecture, etc). + */ +public class MSVCToolChainInfo { + private String fPathEnvVar; + private String fIncludeEnvVar; + private String fLibEnvVar; + + MSVCToolChainInfo(String pathEnvVar, String includeEnvVar, String libEnvVar) { + fPathEnvVar = pathEnvVar; + fIncludeEnvVar = includeEnvVar; + fLibEnvVar = libEnvVar; + } + + /** + * @return The PATH environment variable containing paths needed for this toolchain. Delimited with ';' for multiple paths. + */ + public String getPathEnvVar() { + return fPathEnvVar; + } + + /** + * @return The INCLUDE environment variable containing paths needed for this toolchain. Delimited with ';' for multiple paths. + */ + public String getIncludeEnvVar() { + return fIncludeEnvVar; + } + + /** + * @return The LIB environment variable containing paths needed for this toolchain. Delimited with ';' for multiple paths. + */ + public String getLibEnvVar() { + return fLibEnvVar; + } +} \ No newline at end of file diff --git a/windows/org.eclipse.cdt.msw.build/src/org/eclipse/cdt/internal/msw/build/ProcessOutputUtil.java b/windows/org.eclipse.cdt.msw.build/src/org/eclipse/cdt/internal/msw/build/ProcessOutputUtil.java new file mode 100644 index 00000000000..0cce6f5b3a4 --- /dev/null +++ b/windows/org.eclipse.cdt.msw.build/src/org/eclipse/cdt/internal/msw/build/ProcessOutputUtil.java @@ -0,0 +1,43 @@ +/******************************************************************************* + * Copyright (c) 2020 Marc-Andre Laperle. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package org.eclipse.cdt.internal.msw.build; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.List; + +/** + * Simple util for getting output out of a command. Only meant to be used in a few places to help detect things in the environment. + */ +final class ProcessOutputUtil { + + static String[] getAllOutputFromCommand(String... command) { + try { + Process p = new ProcessBuilder(command).start(); + List lines = new ArrayList<>(); + try (BufferedReader input = new BufferedReader(new InputStreamReader(p.getInputStream()))) { + String line = input.readLine(); + while (line != null) { + lines.add(line); + line = input.readLine(); + } + } + return lines.toArray(new String[0]); + } catch (IOException e) { + // Since this is mostly used to detect the presence of things in the environment, + // if anything goes wrong we just return null and we will fallback to undetected VS/MSVC. + } + + return null; + } +} diff --git a/windows/org.eclipse.cdt.msw.build/src/org/eclipse/cdt/internal/msw/build/VSInstallation.java b/windows/org.eclipse.cdt.msw.build/src/org/eclipse/cdt/internal/msw/build/VSInstallation.java new file mode 100644 index 00000000000..456bfa0c744 --- /dev/null +++ b/windows/org.eclipse.cdt.msw.build/src/org/eclipse/cdt/internal/msw/build/VSInstallation.java @@ -0,0 +1,93 @@ +/******************************************************************************* + * Copyright (c) 2020 Marc-Andre Laperle. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package org.eclipse.cdt.internal.msw.build; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.regex.Pattern; + +/** + * Information about a single Visual Studio installation (paths, toolchains, etc). + */ +public class VSInstallation { + private String fLocation; + private List fToolChains = null; + + VSInstallation(String location) { + super(); + this.fLocation = location; + } + + // Return a non-null String if there is a single non-empty line in the output of the command, null otherwise. + private static String getSingleLineOutputFromCommand(String... command) { + String allOutput[] = ProcessOutputUtil.getAllOutputFromCommand(command); + if (allOutput == null || allOutput.length != 1 || allOutput[0].isEmpty()) { + return null; + } + return allOutput[0]; + } + + private void detectToolchains() { + fToolChains = new ArrayList<>(); + + String vcVarsLocation = fLocation + "\\VC\\Auxiliary\\Build\\vcvarsall.bat"; //$NON-NLS-1$ + //TODO: Support more toolchains/architectures (host and target) when we start giving the choice to the user. + final String arch = "amd64"; //$NON-NLS-1$ + String vcVarsLocationCommands = "\"" + vcVarsLocation + "\" " + arch; //$NON-NLS-1$ //$NON-NLS-2$ + String includeEnvVar = getSingleLineOutputFromCommand("cmd", "/v", "/c", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + "\"" + vcVarsLocationCommands + " > nul && echo !INCLUDE!\""); //$NON-NLS-1$ //$NON-NLS-2$ + if (includeEnvVar == null) { + return; + } + + String libEnvVar = getSingleLineOutputFromCommand("cmd", "/v", "/c", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + "\"" + vcVarsLocationCommands + " > nul && echo !LIB!\""); //$NON-NLS-1$//$NON-NLS-2$ + if (libEnvVar == null) { + return; + } + + // Get the normal PATH variable before calling vcvars then we'll extract the ones added by vcvars. + String normalPathVar = getSingleLineOutputFromCommand("cmd", "/c", "echo %PATH%"); //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$ + if (normalPathVar == null) { + return; + } + + // In case PATH is not defined at all (quite unlikely!) + if (normalPathVar.equals("%PATH%")) { //$NON-NLS-1$ + normalPathVar = ""; //$NON-NLS-1$ + } + + String vcEnvPath = getSingleLineOutputFromCommand("cmd", "/v", "/c", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + "\"" + vcVarsLocationCommands + " > nul && echo !PATH!\""); //$NON-NLS-1$//$NON-NLS-2$ + if (vcEnvPath == null) { + return; + } + + // Vcvars can put the user environment in the middle of its PATH values, not before of after. + String vcEnvPathOnly = vcEnvPath.replaceFirst(Pattern.quote(normalPathVar), ""); //$NON-NLS-1$ + if (vcEnvPathOnly.isEmpty()) + return; + + fToolChains.add(new MSVCToolChainInfo(vcEnvPathOnly, includeEnvVar, libEnvVar)); + } + + /** + * @return Get all toolchains bundled with this installation of VS. + */ + public List getToolchains() { + if (fToolChains == null) { + detectToolchains(); + } + + return Collections.unmodifiableList(fToolChains); + } +} \ No newline at end of file diff --git a/windows/org.eclipse.cdt.msw.build/src/org/eclipse/cdt/internal/msw/build/VSInstallationRegistry.java b/windows/org.eclipse.cdt.msw.build/src/org/eclipse/cdt/internal/msw/build/VSInstallationRegistry.java new file mode 100644 index 00000000000..ae7f4c0957d --- /dev/null +++ b/windows/org.eclipse.cdt.msw.build/src/org/eclipse/cdt/internal/msw/build/VSInstallationRegistry.java @@ -0,0 +1,61 @@ +/******************************************************************************* + * Copyright (c) 2020 Marc-Andre Laperle. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package org.eclipse.cdt.internal.msw.build; + +import java.util.Arrays; +import java.util.Collections; +import java.util.NavigableMap; +import java.util.TreeMap; + +/** + * Stores information about different Visual Studio installations detected on the system (paths, toolchains, etc). + */ +public class VSInstallationRegistry { + private static TreeMap fVsInstallations; + + /** + * @return Get all VS installations, ordered by ascending version numbers. + */ + public static NavigableMap getVsInstallations() { + if (fVsInstallations == null) + detectVSInstallations(); + + return Collections.unmodifiableNavigableMap(fVsInstallations); + } + + private static void detectVSInstallations() { + fVsInstallations = new TreeMap<>(); + // We are opting-in which versions to detect instead of trying to detect even unknown ones in order + // to allow proper testing for a new version before exposing it to users. + Arrays.asList(IVSVersionConstants.VS2017_BASE_VER, IVSVersionConstants.VS2019_BASE_VER).forEach(version -> { + VSInstallation insllation = detectVSInstallation(version); + if (insllation != null) + fVsInstallations.put(version, insllation); + }); + } + + private static VSInstallation detectVSInstallation(VSVersionNumber baseVersion) { + VSVersionNumber upperBound = new VSVersionNumber(baseVersion.get(0) + 1); + // E.g. [15,16) for 15 inclusive to 16 exclusive. + String versionFilterRange = "[" + baseVersion.toString() + "," + upperBound + ")"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + String vsInstallationLocation[] = ProcessOutputUtil.getAllOutputFromCommand("cmd", "/c", //$NON-NLS-1$//$NON-NLS-2$ + "\"\"%ProgramFiles(x86)%\\Microsoft Visual Studio\\Installer\\vswhere.exe\"\"", //$NON-NLS-1$ + "-version", versionFilterRange, "-property", "installationPath"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + if (vsInstallationLocation == null || vsInstallationLocation.length == 0 + || vsInstallationLocation[0].isEmpty()) { + return null; + } + + // We only support one installation per base (major) version for now. + // Supporting multiple is a bit of a niche case left to be determined if useful. + return new VSInstallation(vsInstallationLocation[0]); + } +} diff --git a/windows/org.eclipse.cdt.msw.build/src/org/eclipse/cdt/internal/msw/build/VSVersionNumber.java b/windows/org.eclipse.cdt.msw.build/src/org/eclipse/cdt/internal/msw/build/VSVersionNumber.java new file mode 100644 index 00000000000..84aa13082cf --- /dev/null +++ b/windows/org.eclipse.cdt.msw.build/src/org/eclipse/cdt/internal/msw/build/VSVersionNumber.java @@ -0,0 +1,62 @@ +/******************************************************************************* + * Copyright (c) 2020 Marc-Andre Laperle. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package org.eclipse.cdt.internal.msw.build; + +/** + * A VS version number (Integer tuple). + */ +public class VSVersionNumber implements Comparable { + private Integer[] fIntegers; + + VSVersionNumber(Integer... integers) { + fIntegers = integers; + } + + @Override + public int compareTo(VSVersionNumber o) { + for (int i = 0; i < fIntegers.length; i++) { + if (i >= o.fIntegers.length) { + // All numbers are the same up to now but the other tuple + // has less + return 1; + } + + int compareTo = fIntegers[i].compareTo(o.fIntegers[i]); + if (compareTo != 0) { + return compareTo; + } + } + + // All numbers are the same up to now but this tuple has less than + // the other + if (fIntegers.length < o.fIntegers.length) { + return -1; + } + + return 0; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < fIntegers.length; i++) { + sb.append(fIntegers[i]); + if (i != fIntegers.length - 1) { + sb.append("."); //$NON-NLS-1$ + } + } + return sb.toString(); + } + + Integer get(int index) { + return fIntegers[index]; + } +} \ No newline at end of file diff --git a/windows/org.eclipse.cdt.msw.build/src/org/eclipse/cdt/msw/build/WinEnvironmentVariableSupplier.java b/windows/org.eclipse.cdt.msw.build/src/org/eclipse/cdt/msw/build/WinEnvironmentVariableSupplier.java index 4b0d534cc1e..7ff261cab33 100644 --- a/windows/org.eclipse.cdt.msw.build/src/org/eclipse/cdt/msw/build/WinEnvironmentVariableSupplier.java +++ b/windows/org.eclipse.cdt.msw.build/src/org/eclipse/cdt/msw/build/WinEnvironmentVariableSupplier.java @@ -10,18 +10,23 @@ *******************************************************************************/ package org.eclipse.cdt.msw.build; -import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Map.Entry; +import java.util.stream.Collectors; +import org.eclipse.cdt.internal.msw.build.MSVCToolChainInfo; +import org.eclipse.cdt.internal.msw.build.VSInstallation; +import org.eclipse.cdt.internal.msw.build.VSInstallationRegistry; +import org.eclipse.cdt.internal.msw.build.VSVersionNumber; import org.eclipse.cdt.managedbuilder.core.IConfiguration; import org.eclipse.cdt.managedbuilder.core.IManagedProject; import org.eclipse.cdt.managedbuilder.envvar.IBuildEnvironmentVariable; import org.eclipse.cdt.managedbuilder.envvar.IConfigurationEnvironmentVariableSupplier; import org.eclipse.cdt.managedbuilder.envvar.IEnvironmentVariableProvider; import org.eclipse.cdt.managedbuilder.envvar.IProjectEnvironmentVariableSupplier; -import org.eclipse.cdt.utils.WindowsRegistry; +import org.eclipse.cdt.utils.envvar.EnvVarOperationProcessor; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.Path; @@ -31,10 +36,7 @@ import org.eclipse.core.runtime.Path; */ public class WinEnvironmentVariableSupplier implements IConfigurationEnvironmentVariableSupplier, IProjectEnvironmentVariableSupplier { - private static Map envvars; - private static String sdkDir; - private static String vcDir; private static class WindowsBuildEnvironmentVariable implements IBuildEnvironmentVariable { @@ -97,52 +99,13 @@ public class WinEnvironmentVariableSupplier return envvars.values().toArray(new IBuildEnvironmentVariable[envvars.size()]); } - private static String getSoftwareKey(WindowsRegistry reg, String subkey, String name) { - String value = reg.getLocalMachineValue("SOFTWARE\\" + subkey, name); //$NON-NLS-1$ - // Visual Studio is a 32 bit application so on Windows 64 the keys will be in Wow6432Node - if (value == null) { - value = reg.getLocalMachineValue("SOFTWARE\\Wow6432Node\\" + subkey, name); //$NON-NLS-1$ - } - return value; - } - - // Current support is for Windows SDK 8.0 with Visual C++ 11.0 - // or Windows SDK 7.1 with Visual C++ 10.0 - // or Windows SDK 7.0 with Visual C++ 9.0 - private static String getSDKDir() { - WindowsRegistry reg = WindowsRegistry.getRegistry(); - String sdkDir = getSoftwareKey(reg, "Microsoft\\Microsoft SDKs\\Windows\\v8.0", "InstallationFolder"); //$NON-NLS-1$ //$NON-NLS-2$ - if (sdkDir != null) - return sdkDir; - sdkDir = getSoftwareKey(reg, "Microsoft\\Microsoft SDKs\\Windows\\v7.1", "InstallationFolder"); //$NON-NLS-1$ //$NON-NLS-2$ - if (sdkDir != null) - return sdkDir; - return getSoftwareKey(reg, "Microsoft SDKs\\Windows\\v7.0", "InstallationFolder"); //$NON-NLS-1$ //$NON-NLS-2$ - } - - private static String getVCDir() { - WindowsRegistry reg = WindowsRegistry.getRegistry(); - String vcDir = getSoftwareKey(reg, "Microsoft\\VisualStudio\\SxS\\VC7", "11.0"); //$NON-NLS-1$ //$NON-NLS-2$ - if (vcDir != null) - return vcDir; - vcDir = getSoftwareKey(reg, "Microsoft\\VisualStudio\\SxS\\VC7", "10.0"); //$NON-NLS-1$ //$NON-NLS-2$ - if (vcDir != null) - return vcDir; - return getSoftwareKey(reg, "Microsoft\\VisualStudio\\SxS\\VC7", "9.0"); //$NON-NLS-1$ //$NON-NLS-2$ - } - public static IPath[] getIncludePath() { // Include paths - List includePaths = new ArrayList<>(); - if (sdkDir != null) { - includePaths.add(new Path(sdkDir.concat("Include"))); //$NON-NLS-1$ - includePaths.add(new Path(sdkDir.concat("Include\\gl"))); //$NON-NLS-1$ - } - - if (vcDir != null) { - includePaths.add(new Path(vcDir.concat("Include"))); //$NON-NLS-1$ - } - return includePaths.toArray(new IPath[0]); + IBuildEnvironmentVariable var = envvars.get("INCLUDE"); //$NON-NLS-1$ + if (var == null) + return new IPath[0]; + return EnvVarOperationProcessor.convertToList(var.getValue(), var.getDelimiter()).stream() + .map(val -> Path.fromOSString(val)).collect(Collectors.toList()).toArray(new IPath[0]); } private static void addvar(IBuildEnvironmentVariable var) { @@ -154,46 +117,22 @@ public class WinEnvironmentVariableSupplier return; envvars = new HashMap<>(); - // The SDK Location - sdkDir = getSDKDir(); - vcDir = getVCDir(); - - if (sdkDir == null && vcDir == null) { - return; + Entry vsInstallationEntry = VSInstallationRegistry.getVsInstallations() + .lastEntry(); + if (vsInstallationEntry != null) { + List toolchains = vsInstallationEntry.getValue().getToolchains(); + if (toolchains.size() != 0) { + //TODO: Support more toolchains/architectures (host and target) when we start giving the choice to the user. + MSVCToolChainInfo toolChainInfo = toolchains.get(0); + addvar(new WindowsBuildEnvironmentVariable("INCLUDE", toolChainInfo.getIncludeEnvVar(), //$NON-NLS-1$ + IBuildEnvironmentVariable.ENVVAR_PREPEND)); + addvar(new WindowsBuildEnvironmentVariable("PATH", toolChainInfo.getPathEnvVar(), //$NON-NLS-1$ + IBuildEnvironmentVariable.ENVVAR_PREPEND)); + addvar(new WindowsBuildEnvironmentVariable("LIB", toolChainInfo.getLibEnvVar(), //$NON-NLS-1$ + IBuildEnvironmentVariable.ENVVAR_PREPEND)); + } } - // INCLUDE - StringBuilder buff = new StringBuilder(); - IPath includePaths[] = getIncludePath(); - for (IPath path : includePaths) { - buff.append(path.toOSString()).append(';'); - } - addvar(new WindowsBuildEnvironmentVariable("INCLUDE", buff.toString(), //$NON-NLS-1$ - IBuildEnvironmentVariable.ENVVAR_PREPEND)); - - // LIB - buff = new StringBuilder(); - if (vcDir != null) - buff.append(vcDir).append("Lib;"); //$NON-NLS-1$ - if (sdkDir != null) { - buff.append(sdkDir).append("Lib;"); //$NON-NLS-1$ - buff.append(sdkDir).append("Lib\\win8\\um\\x86;"); //$NON-NLS-1$ - } - - addvar(new WindowsBuildEnvironmentVariable("LIB", buff.toString(), IBuildEnvironmentVariable.ENVVAR_PREPEND)); //$NON-NLS-1$ - - // PATH - buff = new StringBuilder(); - if (vcDir != null) { - buff.append(vcDir).append("..\\Common7\\IDE;"); //$NON-NLS-1$ - buff.append(vcDir).append("..\\Common7\\Tools;"); //$NON-NLS-1$ - buff.append(vcDir).append("Bin;"); //$NON-NLS-1$ - buff.append(vcDir).append("vcpackages;"); //$NON-NLS-1$ - } - if (sdkDir != null) { - buff.append(sdkDir).append("Bin;"); //$NON-NLS-1$ - } - addvar(new WindowsBuildEnvironmentVariable("PATH", buff.toString(), IBuildEnvironmentVariable.ENVVAR_PREPEND)); //$NON-NLS-1$ - }; + } }