| //===-- MSVCPaths.cpp - MSVC path-parsing helpers -------------------------===// | |
| // | |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | |
| // See https://llvm.org/LICENSE.txt for license information. | |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | |
| // | |
| //===----------------------------------------------------------------------===// | |
| #include "llvm/WindowsDriver/MSVCPaths.h" | |
| #include "llvm/ADT/SmallString.h" | |
| #include "llvm/ADT/SmallVector.h" | |
| #include "llvm/ADT/StringExtras.h" | |
| #include "llvm/ADT/StringRef.h" | |
| #include "llvm/ADT/Twine.h" | |
| #include "llvm/Support/Path.h" | |
| #include "llvm/Support/Process.h" | |
| #include "llvm/Support/Program.h" | |
| #include "llvm/Support/VersionTuple.h" | |
| #include "llvm/Support/VirtualFileSystem.h" | |
| #include "llvm/TargetParser/Host.h" | |
| #include "llvm/TargetParser/Triple.h" | |
| #include <optional> | |
| #include <string> | |
| #ifdef _WIN32 | |
| #include "llvm/Support/ConvertUTF.h" | |
| #endif | |
| #ifdef _WIN32 | |
| #ifndef WIN32_LEAN_AND_MEAN | |
| #define WIN32_LEAN_AND_MEAN | |
| #endif | |
| #define NOGDI | |
| #ifndef NOMINMAX | |
| #define NOMINMAX | |
| #endif | |
| #include <windows.h> | |
| #endif | |
| #ifdef _MSC_VER | |
| // Don't support SetupApi on MinGW. | |
| #define USE_MSVC_SETUP_API | |
| // Make sure this comes before MSVCSetupApi.h | |
| #include <comdef.h> | |
| #include "llvm/Support/COM.h" | |
| #ifdef __clang__ | |
| #pragma clang diagnostic push | |
| #pragma clang diagnostic ignored "-Wnon-virtual-dtor" | |
| #endif | |
| #include "llvm/WindowsDriver/MSVCSetupApi.h" | |
| #ifdef __clang__ | |
| #pragma clang diagnostic pop | |
| #endif | |
| _COM_SMARTPTR_TYPEDEF(ISetupConfiguration, __uuidof(ISetupConfiguration)); | |
| _COM_SMARTPTR_TYPEDEF(ISetupConfiguration2, __uuidof(ISetupConfiguration2)); | |
| _COM_SMARTPTR_TYPEDEF(ISetupHelper, __uuidof(ISetupHelper)); | |
| _COM_SMARTPTR_TYPEDEF(IEnumSetupInstances, __uuidof(IEnumSetupInstances)); | |
| _COM_SMARTPTR_TYPEDEF(ISetupInstance, __uuidof(ISetupInstance)); | |
| _COM_SMARTPTR_TYPEDEF(ISetupInstance2, __uuidof(ISetupInstance2)); | |
| #endif | |
| static std::string | |
| getHighestNumericTupleInDirectory(llvm::vfs::FileSystem &VFS, | |
| llvm::StringRef Directory) { | |
| std::string Highest; | |
| llvm::VersionTuple HighestTuple; | |
| std::error_code EC; | |
| for (llvm::vfs::directory_iterator DirIt = VFS.dir_begin(Directory, EC), | |
| DirEnd; | |
| !EC && DirIt != DirEnd; DirIt.increment(EC)) { | |
| auto Status = VFS.status(DirIt->path()); | |
| if (!Status || !Status->isDirectory()) | |
| continue; | |
| llvm::StringRef CandidateName = llvm::sys::path::filename(DirIt->path()); | |
| llvm::VersionTuple Tuple; | |
| if (Tuple.tryParse(CandidateName)) // tryParse() returns true on error. | |
| continue; | |
| if (Tuple > HighestTuple) { | |
| HighestTuple = Tuple; | |
| Highest = CandidateName.str(); | |
| } | |
| } | |
| return Highest; | |
| } | |
| static bool getWindows10SDKVersionFromPath(llvm::vfs::FileSystem &VFS, | |
| const std::string &SDKPath, | |
| std::string &SDKVersion) { | |
| llvm::SmallString<128> IncludePath(SDKPath); | |
| llvm::sys::path::append(IncludePath, "Include"); | |
| SDKVersion = getHighestNumericTupleInDirectory(VFS, IncludePath); | |
| return !SDKVersion.empty(); | |
| } | |
| static bool getWindowsSDKDirViaCommandLine( | |
| llvm::vfs::FileSystem &VFS, std::optional<llvm::StringRef> WinSdkDir, | |
| std::optional<llvm::StringRef> WinSdkVersion, | |
| std::optional<llvm::StringRef> WinSysRoot, std::string &Path, int &Major, | |
| std::string &Version) { | |
| if (WinSdkDir || WinSysRoot) { | |
| // Don't validate the input; trust the value supplied by the user. | |
| // The motivation is to prevent unnecessary file and registry access. | |
| llvm::VersionTuple SDKVersion; | |
| if (WinSdkVersion) | |
| SDKVersion.tryParse(*WinSdkVersion); | |
| if (WinSysRoot) { | |
| llvm::SmallString<128> SDKPath(*WinSysRoot); | |
| llvm::sys::path::append(SDKPath, "Windows Kits"); | |
| if (!SDKVersion.empty()) | |
| llvm::sys::path::append(SDKPath, llvm::Twine(SDKVersion.getMajor())); | |
| else | |
| llvm::sys::path::append( | |
| SDKPath, getHighestNumericTupleInDirectory(VFS, SDKPath)); | |
| Path = std::string(SDKPath); | |
| } else { | |
| Path = WinSdkDir->str(); | |
| } | |
| if (!SDKVersion.empty()) { | |
| Major = SDKVersion.getMajor(); | |
| Version = SDKVersion.getAsString(); | |
| } else if (getWindows10SDKVersionFromPath(VFS, Path, Version)) { | |
| Major = 10; | |
| } | |
| return true; | |
| } | |
| return false; | |
| } | |
| #ifdef _WIN32 | |
| static bool readFullStringValue(HKEY hkey, const char *valueName, | |
| std::string &value) { | |
| std::wstring WideValueName; | |
| if (!llvm::ConvertUTF8toWide(valueName, WideValueName)) | |
| return false; | |
| DWORD result = 0; | |
| DWORD valueSize = 0; | |
| DWORD type = 0; | |
| // First just query for the required size. | |
| result = RegQueryValueExW(hkey, WideValueName.c_str(), NULL, &type, NULL, | |
| &valueSize); | |
| if (result != ERROR_SUCCESS || type != REG_SZ || !valueSize) | |
| return false; | |
| std::vector<BYTE> buffer(valueSize); | |
| result = RegQueryValueExW(hkey, WideValueName.c_str(), NULL, NULL, &buffer[0], | |
| &valueSize); | |
| if (result == ERROR_SUCCESS) { | |
| std::wstring WideValue(reinterpret_cast<const wchar_t *>(buffer.data()), | |
| valueSize / sizeof(wchar_t)); | |
| if (valueSize && WideValue.back() == L'\0') { | |
| WideValue.pop_back(); | |
| } | |
| // The destination buffer must be empty as an invariant of the conversion | |
| // function; but this function is sometimes called in a loop that passes in | |
| // the same buffer, however. Simply clear it out so we can overwrite it. | |
| value.clear(); | |
| return llvm::convertWideToUTF8(WideValue, value); | |
| } | |
| return false; | |
| } | |
| #endif | |
| /// Read registry string. | |
| /// This also supports a means to look for high-versioned keys by use | |
| /// of a $VERSION placeholder in the key path. | |
| /// $VERSION in the key path is a placeholder for the version number, | |
| /// causing the highest value path to be searched for and used. | |
| /// I.e. "SOFTWARE\\Microsoft\\VisualStudio\\$VERSION". | |
| /// There can be additional characters in the component. Only the numeric | |
| /// characters are compared. This function only searches HKLM. | |
| static bool getSystemRegistryString(const char *keyPath, const char *valueName, | |
| std::string &value, std::string *phValue) { | |
| #ifndef _WIN32 | |
| return false; | |
| #else | |
| HKEY hRootKey = HKEY_LOCAL_MACHINE; | |
| HKEY hKey = NULL; | |
| long lResult; | |
| bool returnValue = false; | |
| const char *placeHolder = strstr(keyPath, "$VERSION"); | |
| std::string bestName; | |
| // If we have a $VERSION placeholder, do the highest-version search. | |
| if (placeHolder) { | |
| const char *keyEnd = placeHolder - 1; | |
| const char *nextKey = placeHolder; | |
| // Find end of previous key. | |
| while ((keyEnd > keyPath) && (*keyEnd != '\\')) | |
| keyEnd--; | |
| // Find end of key containing $VERSION. | |
| while (*nextKey && (*nextKey != '\\')) | |
| nextKey++; | |
| size_t partialKeyLength = keyEnd - keyPath; | |
| char partialKey[256]; | |
| if (partialKeyLength >= sizeof(partialKey)) | |
| partialKeyLength = sizeof(partialKey) - 1; | |
| strncpy(partialKey, keyPath, partialKeyLength); | |
| partialKey[partialKeyLength] = '\0'; | |
| HKEY hTopKey = NULL; | |
| lResult = RegOpenKeyExA(hRootKey, partialKey, 0, KEY_READ | KEY_WOW64_32KEY, | |
| &hTopKey); | |
| if (lResult == ERROR_SUCCESS) { | |
| char keyName[256]; | |
| double bestValue = 0.0; | |
| DWORD index, size = sizeof(keyName) - 1; | |
| for (index = 0; RegEnumKeyExA(hTopKey, index, keyName, &size, NULL, NULL, | |
| NULL, NULL) == ERROR_SUCCESS; | |
| index++) { | |
| const char *sp = keyName; | |
| while (*sp && !llvm::isDigit(*sp)) | |
| sp++; | |
| if (!*sp) | |
| continue; | |
| const char *ep = sp + 1; | |
| while (*ep && (llvm::isDigit(*ep) || (*ep == '.'))) | |
| ep++; | |
| char numBuf[32]; | |
| strncpy(numBuf, sp, sizeof(numBuf) - 1); | |
| numBuf[sizeof(numBuf) - 1] = '\0'; | |
| double dvalue = strtod(numBuf, NULL); | |
| if (dvalue > bestValue) { | |
| // Test that InstallDir is indeed there before keeping this index. | |
| // Open the chosen key path remainder. | |
| bestName = keyName; | |
| // Append rest of key. | |
| bestName.append(nextKey); | |
| lResult = RegOpenKeyExA(hTopKey, bestName.c_str(), 0, | |
| KEY_READ | KEY_WOW64_32KEY, &hKey); | |
| if (lResult == ERROR_SUCCESS) { | |
| if (readFullStringValue(hKey, valueName, value)) { | |
| bestValue = dvalue; | |
| if (phValue) | |
| *phValue = bestName; | |
| returnValue = true; | |
| } | |
| RegCloseKey(hKey); | |
| } | |
| } | |
| size = sizeof(keyName) - 1; | |
| } | |
| RegCloseKey(hTopKey); | |
| } | |
| } else { | |
| lResult = | |
| RegOpenKeyExA(hRootKey, keyPath, 0, KEY_READ | KEY_WOW64_32KEY, &hKey); | |
| if (lResult == ERROR_SUCCESS) { | |
| if (readFullStringValue(hKey, valueName, value)) | |
| returnValue = true; | |
| if (phValue) | |
| phValue->clear(); | |
| RegCloseKey(hKey); | |
| } | |
| } | |
| return returnValue; | |
| #endif // _WIN32 | |
| } | |
| const char *llvm::archToWindowsSDKArch(Triple::ArchType Arch) { | |
| switch (Arch) { | |
| case Triple::ArchType::x86: | |
| return "x86"; | |
| case Triple::ArchType::x86_64: | |
| return "x64"; | |
| case Triple::ArchType::arm: | |
| case Triple::ArchType::thumb: | |
| return "arm"; | |
| case Triple::ArchType::aarch64: | |
| return "arm64"; | |
| default: | |
| return ""; | |
| } | |
| } | |
| const char *llvm::archToLegacyVCArch(Triple::ArchType Arch) { | |
| switch (Arch) { | |
| case Triple::ArchType::x86: | |
| // x86 is default in legacy VC toolchains. | |
| // e.g. x86 libs are directly in /lib as opposed to /lib/x86. | |
| return ""; | |
| case Triple::ArchType::x86_64: | |
| return "amd64"; | |
| case Triple::ArchType::arm: | |
| case Triple::ArchType::thumb: | |
| return "arm"; | |
| case Triple::ArchType::aarch64: | |
| return "arm64"; | |
| default: | |
| return ""; | |
| } | |
| } | |
| const char *llvm::archToDevDivInternalArch(Triple::ArchType Arch) { | |
| switch (Arch) { | |
| case Triple::ArchType::x86: | |
| return "i386"; | |
| case Triple::ArchType::x86_64: | |
| return "amd64"; | |
| case Triple::ArchType::arm: | |
| case Triple::ArchType::thumb: | |
| return "arm"; | |
| case Triple::ArchType::aarch64: | |
| return "arm64"; | |
| default: | |
| return ""; | |
| } | |
| } | |
| bool llvm::appendArchToWindowsSDKLibPath(int SDKMajor, SmallString<128> LibPath, | |
| Triple::ArchType Arch, | |
| std::string &path) { | |
| if (SDKMajor >= 8) { | |
| sys::path::append(LibPath, archToWindowsSDKArch(Arch)); | |
| } else { | |
| switch (Arch) { | |
| // In Windows SDK 7.x, x86 libraries are directly in the Lib folder. | |
| case Triple::x86: | |
| break; | |
| case Triple::x86_64: | |
| sys::path::append(LibPath, "x64"); | |
| break; | |
| case Triple::arm: | |
| case Triple::thumb: | |
| // It is not necessary to link against Windows SDK 7.x when targeting ARM. | |
| return false; | |
| default: | |
| return false; | |
| } | |
| } | |
| path = std::string(LibPath); | |
| return true; | |
| } | |
| std::string llvm::getSubDirectoryPath(SubDirectoryType Type, | |
| ToolsetLayout VSLayout, | |
| const std::string &VCToolChainPath, | |
| Triple::ArchType TargetArch, | |
| StringRef SubdirParent) { | |
| const char *SubdirName; | |
| const char *IncludeName; | |
| switch (VSLayout) { | |
| case ToolsetLayout::OlderVS: | |
| SubdirName = archToLegacyVCArch(TargetArch); | |
| IncludeName = "include"; | |
| break; | |
| case ToolsetLayout::VS2017OrNewer: | |
| SubdirName = archToWindowsSDKArch(TargetArch); | |
| IncludeName = "include"; | |
| break; | |
| case ToolsetLayout::DevDivInternal: | |
| SubdirName = archToDevDivInternalArch(TargetArch); | |
| IncludeName = "inc"; | |
| break; | |
| } | |
| SmallString<256> Path(VCToolChainPath); | |
| if (!SubdirParent.empty()) | |
| sys::path::append(Path, SubdirParent); | |
| switch (Type) { | |
| case SubDirectoryType::Bin: | |
| if (VSLayout == ToolsetLayout::VS2017OrNewer) { | |
| // MSVC ships with two linkers: a 32-bit x86 and 64-bit x86 linker. | |
| // On x86, pick the linker that corresponds to the current process. | |
| // On ARM64, pick the 32-bit x86 linker; the 64-bit one doesn't run | |
| // on Windows 10. | |
| // | |
| // FIXME: Consider using IsWow64GuestMachineSupported to figure out | |
| // if we can invoke the 64-bit linker. It's generally preferable | |
| // because it won't run out of address-space. | |
| const bool HostIsX64 = | |
| Triple(sys::getProcessTriple()).getArch() == Triple::x86_64; | |
| const char *const HostName = HostIsX64 ? "Hostx64" : "Hostx86"; | |
| sys::path::append(Path, "bin", HostName, SubdirName); | |
| } else { // OlderVS or DevDivInternal | |
| sys::path::append(Path, "bin", SubdirName); | |
| } | |
| break; | |
| case SubDirectoryType::Include: | |
| sys::path::append(Path, IncludeName); | |
| break; | |
| case SubDirectoryType::Lib: | |
| sys::path::append(Path, "lib", SubdirName); | |
| break; | |
| } | |
| return std::string(Path); | |
| } | |
| bool llvm::useUniversalCRT(ToolsetLayout VSLayout, | |
| const std::string &VCToolChainPath, | |
| Triple::ArchType TargetArch, vfs::FileSystem &VFS) { | |
| SmallString<128> TestPath(getSubDirectoryPath( | |
| SubDirectoryType::Include, VSLayout, VCToolChainPath, TargetArch)); | |
| sys::path::append(TestPath, "stdlib.h"); | |
| return !VFS.exists(TestPath); | |
| } | |
| bool llvm::getWindowsSDKDir(vfs::FileSystem &VFS, | |
| std::optional<StringRef> WinSdkDir, | |
| std::optional<StringRef> WinSdkVersion, | |
| std::optional<StringRef> WinSysRoot, | |
| std::string &Path, int &Major, | |
| std::string &WindowsSDKIncludeVersion, | |
| std::string &WindowsSDKLibVersion) { | |
| // Trust /winsdkdir and /winsdkversion if present. | |
| if (getWindowsSDKDirViaCommandLine(VFS, WinSdkDir, WinSdkVersion, WinSysRoot, | |
| Path, Major, WindowsSDKIncludeVersion)) { | |
| WindowsSDKLibVersion = WindowsSDKIncludeVersion; | |
| return true; | |
| } | |
| // FIXME: Try env vars (%WindowsSdkDir%, %UCRTVersion%) before going to | |
| // registry. | |
| // Try the Windows registry. | |
| std::string RegistrySDKVersion; | |
| if (!getSystemRegistryString( | |
| "SOFTWARE\\Microsoft\\Microsoft SDKs\\Windows\\$VERSION", | |
| "InstallationFolder", Path, &RegistrySDKVersion)) | |
| return false; | |
| if (Path.empty() || RegistrySDKVersion.empty()) | |
| return false; | |
| WindowsSDKIncludeVersion.clear(); | |
| WindowsSDKLibVersion.clear(); | |
| Major = 0; | |
| std::sscanf(RegistrySDKVersion.c_str(), "v%d.", &Major); | |
| if (Major <= 7) | |
| return true; | |
| if (Major == 8) { | |
| // Windows SDK 8.x installs libraries in a folder whose names depend on the | |
| // version of the OS you're targeting. By default choose the newest, which | |
| // usually corresponds to the version of the OS you've installed the SDK on. | |
| const char *Tests[] = {"winv6.3", "win8", "win7"}; | |
| for (const char *Test : Tests) { | |
| SmallString<128> TestPath(Path); | |
| sys::path::append(TestPath, "Lib", Test); | |
| if (VFS.exists(TestPath)) { | |
| WindowsSDKLibVersion = Test; | |
| break; | |
| } | |
| } | |
| return !WindowsSDKLibVersion.empty(); | |
| } | |
| if (Major == 10) { | |
| if (WinSdkVersion) { | |
| // Use the user-provided version as-is. | |
| WindowsSDKIncludeVersion = WinSdkVersion->str(); | |
| WindowsSDKLibVersion = WindowsSDKIncludeVersion; | |
| return true; | |
| } | |
| if (!getWindows10SDKVersionFromPath(VFS, Path, WindowsSDKIncludeVersion)) | |
| return false; | |
| WindowsSDKLibVersion = WindowsSDKIncludeVersion; | |
| return true; | |
| } | |
| // Unsupported SDK version | |
| return false; | |
| } | |
| bool llvm::getUniversalCRTSdkDir(vfs::FileSystem &VFS, | |
| std::optional<StringRef> WinSdkDir, | |
| std::optional<StringRef> WinSdkVersion, | |
| std::optional<StringRef> WinSysRoot, | |
| std::string &Path, std::string &UCRTVersion) { | |
| // If /winsdkdir is passed, use it as location for the UCRT too. | |
| // FIXME: Should there be a dedicated /ucrtdir to override /winsdkdir? | |
| int Major; | |
| if (getWindowsSDKDirViaCommandLine(VFS, WinSdkDir, WinSdkVersion, WinSysRoot, | |
| Path, Major, UCRTVersion)) | |
| return true; | |
| // FIXME: Try env vars (%UniversalCRTSdkDir%, %UCRTVersion%) before going to | |
| // registry. | |
| // vcvarsqueryregistry.bat for Visual Studio 2015 queries the registry | |
| // for the specific key "KitsRoot10". So do we. | |
| if (!getSystemRegistryString( | |
| "SOFTWARE\\Microsoft\\Windows Kits\\Installed Roots", "KitsRoot10", | |
| Path, nullptr)) | |
| return false; | |
| if (WinSdkVersion) { | |
| // Use the user-provided version as-is. | |
| UCRTVersion = WinSdkVersion->str(); | |
| return true; | |
| } | |
| return getWindows10SDKVersionFromPath(VFS, Path, UCRTVersion); | |
| } | |
| bool llvm::findVCToolChainViaCommandLine( | |
| vfs::FileSystem &VFS, std::optional<StringRef> VCToolsDir, | |
| std::optional<StringRef> VCToolsVersion, | |
| std::optional<StringRef> WinSysRoot, std::string &Path, | |
| ToolsetLayout &VSLayout) { | |
| // Don't validate the input; trust the value supplied by the user. | |
| // The primary motivation is to prevent unnecessary file and registry access. | |
| if (VCToolsDir || WinSysRoot) { | |
| if (WinSysRoot) { | |
| SmallString<128> ToolsPath(*WinSysRoot); | |
| sys::path::append(ToolsPath, "VC", "Tools", "MSVC"); | |
| std::string ToolsVersion; | |
| if (VCToolsVersion) | |
| ToolsVersion = VCToolsVersion->str(); | |
| else | |
| ToolsVersion = getHighestNumericTupleInDirectory(VFS, ToolsPath); | |
| sys::path::append(ToolsPath, ToolsVersion); | |
| Path = std::string(ToolsPath); | |
| } else { | |
| Path = VCToolsDir->str(); | |
| } | |
| VSLayout = ToolsetLayout::VS2017OrNewer; | |
| return true; | |
| } | |
| return false; | |
| } | |
| bool llvm::findVCToolChainViaEnvironment(vfs::FileSystem &VFS, | |
| std::string &Path, | |
| ToolsetLayout &VSLayout) { | |
| // These variables are typically set by vcvarsall.bat | |
| // when launching a developer command prompt. | |
| if (std::optional<std::string> VCToolsInstallDir = | |
| sys::Process::GetEnv("VCToolsInstallDir")) { | |
| // This is only set by newer Visual Studios, and it leads straight to | |
| // the toolchain directory. | |
| Path = std::move(*VCToolsInstallDir); | |
| VSLayout = ToolsetLayout::VS2017OrNewer; | |
| return true; | |
| } | |
| if (std::optional<std::string> VCInstallDir = | |
| sys::Process::GetEnv("VCINSTALLDIR")) { | |
| // If the previous variable isn't set but this one is, then we've found | |
| // an older Visual Studio. This variable is set by newer Visual Studios too, | |
| // so this check has to appear second. | |
| // In older Visual Studios, the VC directory is the toolchain. | |
| Path = std::move(*VCInstallDir); | |
| VSLayout = ToolsetLayout::OlderVS; | |
| return true; | |
| } | |
| // We couldn't find any VC environment variables. Let's walk through PATH and | |
| // see if it leads us to a VC toolchain bin directory. If it does, pick the | |
| // first one that we find. | |
| if (std::optional<std::string> PathEnv = sys::Process::GetEnv("PATH")) { | |
| SmallVector<StringRef, 8> PathEntries; | |
| StringRef(*PathEnv).split(PathEntries, sys::EnvPathSeparator); | |
| for (StringRef PathEntry : PathEntries) { | |
| if (PathEntry.empty()) | |
| continue; | |
| SmallString<256> ExeTestPath; | |
| // If cl.exe doesn't exist, then this definitely isn't a VC toolchain. | |
| ExeTestPath = PathEntry; | |
| sys::path::append(ExeTestPath, "cl.exe"); | |
| if (!VFS.exists(ExeTestPath)) | |
| continue; | |
| // cl.exe existing isn't a conclusive test for a VC toolchain; clang also | |
| // has a cl.exe. So let's check for link.exe too. | |
| ExeTestPath = PathEntry; | |
| sys::path::append(ExeTestPath, "link.exe"); | |
| if (!VFS.exists(ExeTestPath)) | |
| continue; | |
| // whatever/VC/bin --> old toolchain, VC dir is toolchain dir. | |
| StringRef TestPath = PathEntry; | |
| bool IsBin = sys::path::filename(TestPath).equals_insensitive("bin"); | |
| if (!IsBin) { | |
| // Strip any architecture subdir like "amd64". | |
| TestPath = sys::path::parent_path(TestPath); | |
| IsBin = sys::path::filename(TestPath).equals_insensitive("bin"); | |
| } | |
| if (IsBin) { | |
| StringRef ParentPath = sys::path::parent_path(TestPath); | |
| StringRef ParentFilename = sys::path::filename(ParentPath); | |
| if (ParentFilename.equals_insensitive("VC")) { | |
| Path = std::string(ParentPath); | |
| VSLayout = ToolsetLayout::OlderVS; | |
| return true; | |
| } | |
| if (ParentFilename.equals_insensitive("x86ret") || | |
| ParentFilename.equals_insensitive("x86chk") || | |
| ParentFilename.equals_insensitive("amd64ret") || | |
| ParentFilename.equals_insensitive("amd64chk")) { | |
| Path = std::string(ParentPath); | |
| VSLayout = ToolsetLayout::DevDivInternal; | |
| return true; | |
| } | |
| } else { | |
| // This could be a new (>=VS2017) toolchain. If it is, we should find | |
| // path components with these prefixes when walking backwards through | |
| // the path. | |
| // Note: empty strings match anything. | |
| StringRef ExpectedPrefixes[] = {"", "Host", "bin", "", | |
| "MSVC", "Tools", "VC"}; | |
| auto It = sys::path::rbegin(PathEntry); | |
| auto End = sys::path::rend(PathEntry); | |
| for (StringRef Prefix : ExpectedPrefixes) { | |
| if (It == End) | |
| goto NotAToolChain; | |
| if (!It->starts_with_insensitive(Prefix)) | |
| goto NotAToolChain; | |
| ++It; | |
| } | |
| // We've found a new toolchain! | |
| // Back up 3 times (/bin/Host/arch) to get the root path. | |
| StringRef ToolChainPath(PathEntry); | |
| for (int i = 0; i < 3; ++i) | |
| ToolChainPath = sys::path::parent_path(ToolChainPath); | |
| Path = std::string(ToolChainPath); | |
| VSLayout = ToolsetLayout::VS2017OrNewer; | |
| return true; | |
| } | |
| NotAToolChain: | |
| continue; | |
| } | |
| } | |
| return false; | |
| } | |
| bool llvm::findVCToolChainViaSetupConfig( | |
| vfs::FileSystem &VFS, std::optional<StringRef> VCToolsVersion, | |
| std::string &Path, ToolsetLayout &VSLayout) { | |
| #if !defined(USE_MSVC_SETUP_API) | |
| return false; | |
| #else | |
| // FIXME: This really should be done once in the top-level program's main | |
| // function, as it may have already been initialized with a different | |
| // threading model otherwise. | |
| sys::InitializeCOMRAII COM(sys::COMThreadingMode::SingleThreaded); | |
| HRESULT HR; | |
| // _com_ptr_t will throw a _com_error if a COM calls fail. | |
| // The LLVM coding standards forbid exception handling, so we'll have to | |
| // stop them from being thrown in the first place. | |
| // The destructor will put the regular error handler back when we leave | |
| // this scope. | |
| struct SuppressCOMErrorsRAII { | |
| static void __stdcall handler(HRESULT hr, IErrorInfo *perrinfo) {} | |
| SuppressCOMErrorsRAII() { _set_com_error_handler(handler); } | |
| ~SuppressCOMErrorsRAII() { _set_com_error_handler(_com_raise_error); } | |
| } COMErrorSuppressor; | |
| ISetupConfigurationPtr Query; | |
| HR = Query.CreateInstance(__uuidof(SetupConfiguration)); | |
| if (FAILED(HR)) | |
| return false; | |
| IEnumSetupInstancesPtr EnumInstances; | |
| HR = ISetupConfiguration2Ptr(Query)->EnumAllInstances(&EnumInstances); | |
| if (FAILED(HR)) | |
| return false; | |
| ISetupInstancePtr Instance; | |
| HR = EnumInstances->Next(1, &Instance, nullptr); | |
| if (HR != S_OK) | |
| return false; | |
| ISetupInstancePtr NewestInstance; | |
| std::optional<uint64_t> NewestVersionNum; | |
| do { | |
| bstr_t VersionString; | |
| uint64_t VersionNum; | |
| HR = Instance->GetInstallationVersion(VersionString.GetAddress()); | |
| if (FAILED(HR)) | |
| continue; | |
| HR = ISetupHelperPtr(Query)->ParseVersion(VersionString, &VersionNum); | |
| if (FAILED(HR)) | |
| continue; | |
| if (!NewestVersionNum || (VersionNum > NewestVersionNum)) { | |
| NewestInstance = Instance; | |
| NewestVersionNum = VersionNum; | |
| } | |
| } while ((HR = EnumInstances->Next(1, &Instance, nullptr)) == S_OK); | |
| if (!NewestInstance) | |
| return false; | |
| bstr_t VCPathWide; | |
| HR = NewestInstance->ResolvePath(L"VC", VCPathWide.GetAddress()); | |
| if (FAILED(HR)) | |
| return false; | |
| std::string VCRootPath; | |
| convertWideToUTF8(std::wstring(VCPathWide), VCRootPath); | |
| std::string ToolsVersion; | |
| if (VCToolsVersion.has_value()) { | |
| ToolsVersion = *VCToolsVersion; | |
| } else { | |
| SmallString<256> ToolsVersionFilePath(VCRootPath); | |
| sys::path::append(ToolsVersionFilePath, "Auxiliary", "Build", | |
| "Microsoft.VCToolsVersion.default.txt"); | |
| auto ToolsVersionFile = MemoryBuffer::getFile(ToolsVersionFilePath); | |
| if (!ToolsVersionFile) | |
| return false; | |
| ToolsVersion = ToolsVersionFile->get()->getBuffer().rtrim(); | |
| } | |
| SmallString<256> ToolchainPath(VCRootPath); | |
| sys::path::append(ToolchainPath, "Tools", "MSVC", ToolsVersion); | |
| auto Status = VFS.status(ToolchainPath); | |
| if (!Status || !Status->isDirectory()) | |
| return false; | |
| Path = std::string(ToolchainPath.str()); | |
| VSLayout = ToolsetLayout::VS2017OrNewer; | |
| return true; | |
| #endif | |
| } | |
| bool llvm::findVCToolChainViaRegistry(std::string &Path, | |
| ToolsetLayout &VSLayout) { | |
| std::string VSInstallPath; | |
| if (getSystemRegistryString(R"(SOFTWARE\Microsoft\VisualStudio\$VERSION)", | |
| "InstallDir", VSInstallPath, nullptr) || | |
| getSystemRegistryString(R"(SOFTWARE\Microsoft\VCExpress\$VERSION)", | |
| "InstallDir", VSInstallPath, nullptr)) { | |
| if (!VSInstallPath.empty()) { | |
| auto pos = VSInstallPath.find(R"(\Common7\IDE)"); | |
| if (pos == std::string::npos) | |
| return false; | |
| SmallString<256> VCPath(StringRef(VSInstallPath.c_str(), pos)); | |
| sys::path::append(VCPath, "VC"); | |
| Path = std::string(VCPath); | |
| VSLayout = ToolsetLayout::OlderVS; | |
| return true; | |
| } | |
| } | |
| return false; | |
| } |