//===-- 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/StringRef.h" #include "llvm/ADT/Triple.h" #include "llvm/ADT/Twine.h" #include "llvm/Support/Host.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 #include #ifdef _WIN32 #include "llvm/Support/ConvertUTF.h" #endif #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #define NOGDI #ifndef NOMINMAX #define NOMINMAX #endif #include #endif #ifdef _MSC_VER // Don't support SetupApi on MinGW. #define USE_MSVC_SETUP_API // Make sure this comes before MSVCSetupApi.h #include #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 WinSdkDir, std::optional WinSdkVersion, std::optional 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.str()); } 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 buffer(valueSize); result = RegQueryValueExW(hkey, WideValueName.c_str(), NULL, NULL, &buffer[0], &valueSize); if (result == ERROR_SUCCESS) { std::wstring WideValue(reinterpret_cast(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 } namespace llvm { const char *archToWindowsSDKArch(Triple::ArchType Arch) { switch (Arch) { case Triple::ArchType::x86: return "x86"; case Triple::ArchType::x86_64: return "x64"; case Triple::ArchType::arm: return "arm"; case Triple::ArchType::aarch64: return "arm64"; default: return ""; } } const char *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: return "arm"; case Triple::ArchType::aarch64: return "arm64"; default: return ""; } } const char *archToDevDivInternalArch(Triple::ArchType Arch) { switch (Arch) { case Triple::ArchType::x86: return "i386"; case Triple::ArchType::x86_64: return "amd64"; case Triple::ArchType::arm: return "arm"; case Triple::ArchType::aarch64: return "arm64"; default: return ""; } } bool 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: // It is not necessary to link against Windows SDK 7.x when targeting ARM. return false; default: return false; } } path = std::string(LibPath.str()); return true; } std::string 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.str()); } bool 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 getWindowsSDKDir(vfs::FileSystem &VFS, std::optional WinSdkDir, std::optional WinSdkVersion, std::optional 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 (!getWindows10SDKVersionFromPath(VFS, Path, WindowsSDKIncludeVersion)) return false; WindowsSDKLibVersion = WindowsSDKIncludeVersion; return true; } // Unsupported SDK version return false; } bool getUniversalCRTSdkDir(vfs::FileSystem &VFS, std::optional WinSdkDir, std::optional WinSdkVersion, std::optional 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; return getWindows10SDKVersionFromPath(VFS, Path, UCRTVersion); } bool findVCToolChainViaCommandLine(vfs::FileSystem &VFS, std::optional VCToolsDir, std::optional VCToolsVersion, std::optional 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.str()); } else { Path = VCToolsDir->str(); } VSLayout = ToolsetLayout::VS2017OrNewer; return true; } return false; } bool 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 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 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 PathEnv = sys::Process::GetEnv("PATH")) { SmallVector 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->startswith_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 findVCToolChainViaSetupConfig(vfs::FileSystem &VFS, 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 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); SmallString<256> ToolsVersionFilePath(VCRootPath); sys::path::append(ToolsVersionFilePath, "Auxiliary", "Build", "Microsoft.VCToolsVersion.default.txt"); auto ToolsVersionFile = MemoryBuffer::getFile(ToolsVersionFilePath); if (!ToolsVersionFile) return false; SmallString<256> ToolchainPath(VCRootPath); sys::path::append(ToolchainPath, "Tools", "MSVC", ToolsVersionFile->get()->getBuffer().rtrim()); auto Status = VFS.status(ToolchainPath); if (!Status || !Status->isDirectory()) return false; Path = std::string(ToolchainPath.str()); VSLayout = ToolsetLayout::VS2017OrNewer; return true; #endif } bool 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.str()); VSLayout = ToolsetLayout::OlderVS; return true; } } return false; } } // namespace llvm