pkasting updated this revision to Diff 403316.
pkasting edited the summary of this revision.
pkasting added a comment.

Functional, but not yet refactored to share code with clang-cl.


Repository:
  rG LLVM Github Monorepo

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D118070/new/

https://reviews.llvm.org/D118070

Files:
  clang/docs/tools/clang-formatted-files.txt
  clang/lib/Driver/ToolChains/MSVC.cpp
  clang/lib/Driver/ToolChains/MSVCSetupApi.h
  lld/COFF/Driver.cpp
  lld/COFF/Driver.h
  lld/COFF/Options.td
  lld/COFF/SymbolTable.cpp
  llvm/include/llvm/Support/MSVCSetupApi.h
  utils/bazel/llvm-project-overlay/clang/BUILD.bazel

Index: utils/bazel/llvm-project-overlay/clang/BUILD.bazel
===================================================================
--- utils/bazel/llvm-project-overlay/clang/BUILD.bazel
+++ utils/bazel/llvm-project-overlay/clang/BUILD.bazel
@@ -1309,9 +1309,6 @@
             "lib/Driver/ToolChains/Arch/*.cpp",
             "lib/Driver/ToolChains/Arch/*.h",
         ],
-        exclude = [
-            "lib/Driver/ToolChains/MSVCSetupApi.h",
-        ],
     ),
     hdrs = glob([
         "include/clang/Driver/*.h",
Index: lld/COFF/SymbolTable.cpp
===================================================================
--- lld/COFF/SymbolTable.cpp
+++ lld/COFF/SymbolTable.cpp
@@ -56,6 +56,7 @@
   MachineTypes mt = file->getMachineType();
   if (config->machine == IMAGE_FILE_MACHINE_UNKNOWN) {
     config->machine = mt;
+    driver->addWinSysRootLibSearchPaths();
   } else if (mt != IMAGE_FILE_MACHINE_UNKNOWN && config->machine != mt) {
     error(toString(file) + ": machine type " + machineToStr(mt) +
           " conflicts with " + machineToStr(config->machine));
Index: lld/COFF/Options.td
===================================================================
--- lld/COFF/Options.td
+++ lld/COFF/Options.td
@@ -41,6 +41,7 @@
     MetaVarName<"[auto,always,never]">;
 def defaultlib : P<"defaultlib", "Add the library to the list of input files">;
 def delayload : P<"delayload", "Delay loaded DLL name">;
+def diasdkdir : P<"diasdkdir", "Set the location of the DIA SDK">;
 def entry   : P<"entry", "Name of entry point symbol">;
 def errorlimit : P<"errorlimit",
     "Maximum number of errors to emit before stopping (0 = no limit)">;
@@ -89,9 +90,16 @@
 def stub    : P<"stub", "Specify DOS stub file">;
 def subsystem : P<"subsystem", "Specify subsystem">;
 def timestamp : P<"timestamp", "Specify the PE header timestamp">;
+def vctoolsdir : P<"vctoolsdir", "Set the location of the VC tools">;
+def vctoolsversion : P<"vctoolsversion",
+    "Specify which VC tools version to use">;
 def version : P<"version", "Specify a version number in the PE header">;
 def wholearchive_file : P<"wholearchive",
     "Include all object files from this library">;
+def winsdkdir : P<"winsdkdir", "Set the location of the Windows SDK">;
+def winsdkversion : P<"winsdkversion", "Specify which SDK version to use">;
+def winsysroot : P<"winsysroot",
+    "Adds several subdirectories to the library search paths">;
 
 def disallowlib : Joined<["/", "-", "/?", "-?"], "disallowlib:">,
     Alias<nodefaultlib>;
Index: lld/COFF/Driver.h
===================================================================
--- lld/COFF/Driver.h
+++ lld/COFF/Driver.h
@@ -36,6 +36,12 @@
 using llvm::COFF::WindowsSubsystem;
 using llvm::Optional;
 
+enum class ToolsetLayout {
+  OlderVS,
+  VS2017OrNewer,
+  DevDivInternal,
+};
+
 class COFFOptTable : public llvm::opt::OptTable {
 public:
   COFFOptTable();
@@ -82,6 +88,10 @@
 
   void linkerMain(llvm::ArrayRef<const char *> args);
 
+  // Adds various search paths based on the sysroot.  Must only be called once
+  // config->machine has been set.
+  void addWinSysRootLibSearchPaths();
+
   // Used by the resolver to parse .drectve section contents.
   void parseDirectives(InputFile *file);
 
@@ -107,11 +117,17 @@
 
   bool findUnderscoreMangle(StringRef sym);
 
+  // Determines the location of the sysroot based on `args`, environment, etc.
+  void detectWinSysRoot(const llvm::opt::InputArgList &args);
+
+  void getUniversalCRTLibraryPath(const llvm::opt::InputArgList &Args);
+  void getWindowsSDKLibraryPath(const llvm::opt::InputArgList &Args);
+
   // Parses LIB environment which contains a list of search paths.
   void addLibSearchPaths();
 
   // Library search path. The first element is always "" (current directory).
-  std::vector<StringRef> searchPaths;
+  std::vector<std::string> searchPaths;
 
   // Convert resource files and potentially merge input resource object
   // trees into one resource tree.
@@ -154,6 +170,14 @@
   llvm::StringSet<> directivesExports;
 
   COFFLinkerContext &ctx;
+
+  ToolsetLayout vsLayout = ToolsetLayout::OlderVS;
+  std::string vcToolChainPath;
+  llvm::SmallString<128> diaPath;
+  bool useWinSysRootLibPath = false;
+  llvm::SmallString<128> universalCRTLibPath;
+  int sdkMajor = 0;
+  llvm::SmallString<128> windowsSdkLibPath;
 };
 
 // Functions below this line are defined in DriverUtils.cpp.
Index: lld/COFF/Driver.cpp
===================================================================
--- lld/COFF/Driver.cpp
+++ lld/COFF/Driver.cpp
@@ -37,6 +37,7 @@
 #include "llvm/Support/BinaryStreamReader.h"
 #include "llvm/Support/CommandLine.h"
 #include "llvm/Support/Debug.h"
+#include "llvm/Support/Host.h"
 #include "llvm/Support/LEB128.h"
 #include "llvm/Support/MathExtras.h"
 #include "llvm/Support/Parallel.h"
@@ -50,6 +51,44 @@
 #include <future>
 #include <memory>
 
+#ifdef _WIN32
+#define WIN32_LEAN_AND_MEAN
+#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>
+
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wnon-virtual-dtor"
+#endif
+#include "llvm/Support/MSVCSetupApi.h"
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
+#include "llvm/Support/COM.h"
+_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));
+
+// TODO(pkasting): Remove
+#undef IMAGE_SUBSYSTEM_UNKNOWN
+#undef IMAGE_SUBSYSTEM_WINDOWS_GUI
+#undef IMAGE_SUBSYSTEM_WINDOWS_CUI
+#endif
+
 using namespace llvm;
 using namespace llvm::object;
 using namespace llvm::COFF;
@@ -150,6 +189,642 @@
   return sym;
 }
 
+static std::string getHighestNumericTupleInDirectory(StringRef Directory) {
+  std::string Highest;
+  VersionTuple HighestTuple;
+
+  std::error_code EC;
+  for (fs::directory_iterator DirIt(Directory, EC), DirEnd;
+       !EC && DirIt != DirEnd; DirIt.increment(EC)) {
+
+    if (!fs::is_directory(DirIt->path()))
+      continue;
+    StringRef CandidateName = path::filename(DirIt->path());
+    VersionTuple Tuple;
+    if (Tuple.tryParse(CandidateName)) // tryParse() returns true on error.
+      continue;
+    if (Tuple > HighestTuple) {
+      HighestTuple = Tuple;
+      Highest = CandidateName.str();
+    }
+  }
+
+  return Highest;
+}
+
+// Windows SDKs and VC Toolchains group their contents into subdirectories based
+// on the target architecture.
+static const char *getWindowsSDKArch() {
+  switch (config->machine) {
+  case I386:
+    return "x86";
+  case AMD64:
+    return "x64";
+  case ARMNT:
+    return "arm";
+  case ARM64:
+    return "arm64";
+  default:
+    return "";
+  }
+}
+
+// Similar to the above function, but for Visual Studios before VS2017.
+static const char *getLegacyVCArch() {
+  switch (config->machine) {
+  case I386:
+    // x86 is default in legacy VC toolchains.
+    // e.g. x86 libs are directly in /lib as opposed to /lib/x86.
+    return "";
+  case AMD64:
+    return "amd64";
+  case ARMNT:
+    return "arm";
+  case ARM64:
+    return "arm64";
+  default:
+    return "";
+  }
+}
+
+// Similar to the above function, but for DevDiv internal builds.
+static const char *getDevDivInternalArch() {
+  switch (config->machine) {
+  case I386:
+    return "i386";
+  case AMD64:
+    return "amd64";
+  case ARMNT:
+    return "arm";
+  case ARM64:
+    return "arm64";
+  default:
+    return "";
+  }
+}
+
+enum class SubDirectoryType {
+  Bin,
+  Include,
+  Lib,
+};
+
+static std::string getSubDirectoryPath(SubDirectoryType Type,
+                                       ToolsetLayout VSLayout,
+                                       const std::string &VCToolChainPath,
+                                       StringRef SubdirParent = "") {
+  const char *SubdirName;
+  const char *IncludeName;
+  switch (VSLayout) {
+  case ToolsetLayout::OlderVS:
+    SubdirName = getLegacyVCArch();
+    IncludeName = "include";
+    break;
+  case ToolsetLayout::VS2017OrNewer:
+    SubdirName = getWindowsSDKArch();
+    IncludeName = "include";
+    break;
+  case ToolsetLayout::DevDivInternal:
+    SubdirName = getDevDivInternalArch();
+    IncludeName = "inc";
+    break;
+  }
+
+  llvm::SmallString<256> Path(VCToolChainPath);
+  if (!SubdirParent.empty())
+    path::append(Path, SubdirParent);
+
+  switch (Type) {
+  case SubDirectoryType::Bin:
+    if (VSLayout == ToolsetLayout::VS2017OrNewer) {
+      const bool HostIsX64 = llvm::Triple(getProcessTriple()).isArch64Bit();
+      const char *const HostName = HostIsX64 ? "Hostx64" : "Hostx86";
+      path::append(Path, "bin", HostName, SubdirName);
+    } else { // OlderVS or DevDivInternal
+      path::append(Path, "bin", SubdirName);
+    }
+    break;
+  case SubDirectoryType::Include:
+    path::append(Path, IncludeName);
+    break;
+  case SubDirectoryType::Lib:
+    path::append(Path, "lib", SubdirName);
+    break;
+  }
+  return std::string(Path.str());
+}
+
+// Check if the Include path of a specified version of Visual Studio contains
+// specific header files. If not, they are probably shipped with Universal CRT.
+static bool useUniversalCRT(ToolsetLayout VSLayout,
+                            const std::string &VCToolChainPath) {
+  SmallString<128> TestPath(getSubDirectoryPath(SubDirectoryType::Include,
+                                                VSLayout, VCToolChainPath));
+  path::append(TestPath, "stdlib.h");
+  return !fs::exists(TestPath);
+}
+
+#ifdef _WIN32
+static bool readFullStringValue(HKEY hkey, const char *valueName,
+                                std::string &value) {
+  std::wstring WideValueName;
+  if (!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 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 && !isDigit(*sp))
+          sp++;
+        if (!*sp)
+          continue;
+        const char *ep = sp + 1;
+        while (*ep && (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
+}
+
+// Find the most recent version of Universal CRT or Windows 10 SDK.
+// vcvarsqueryregistry.bat from Visual Studio 2015 sorts entries in the include
+// directory by name and uses the last one of the list.
+// So we compare entry names lexicographically to find the greatest one.
+static bool getWindows10SDKVersionFromPath(const std::string &SDKPath,
+                                           std::string &SDKVersion) {
+  llvm::SmallString<128> IncludePath(SDKPath);
+  path::append(IncludePath, "Include");
+  SDKVersion = getHighestNumericTupleInDirectory(IncludePath);
+  return !SDKVersion.empty();
+}
+
+static bool getWindowsSDKDirViaCommandLine(const opt::InputArgList &Args,
+                                           std::string &Path, int &Major,
+                                           std::string &Version) {
+  if (auto *A = Args.getLastArg(OPT_winsdkdir, OPT_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 (auto *A = Args.getLastArg(OPT_winsdkversion))
+      SDKVersion.tryParse(A->getValue());
+
+    if (A->getOption().getID() == OPT_winsysroot) {
+      llvm::SmallString<128> SDKPath(A->getValue());
+      path::append(SDKPath, "Windows Kits");
+      if (!SDKVersion.empty())
+        path::append(SDKPath, Twine(SDKVersion.getMajor()));
+      else
+        path::append(SDKPath, getHighestNumericTupleInDirectory(SDKPath));
+      Path = std::string(SDKPath.str());
+    } else {
+      Path = A->getValue();
+    }
+
+    if (!SDKVersion.empty()) {
+      Major = SDKVersion.getMajor();
+      Version = SDKVersion.getAsString();
+    } else if (getWindows10SDKVersionFromPath(Path, Version)) {
+      Major = 10;
+    }
+    return true;
+  }
+  return false;
+}
+
+static bool getUniversalCRTSdkDir(const opt::InputArgList &Args,
+                                  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(Args, 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(Path, UCRTVersion);
+}
+
+/// Get Windows SDK installation directory.
+static bool getWindowsSDKDir(const opt::InputArgList &Args,
+                             std::string &Path, int &Major,
+                             std::string &WindowsSDKIncludeVersion,
+                             std::string &WindowsSDKLibVersion) {
+  // Trust /winsdkdir and /winsdkversion if present.
+  if (getWindowsSDKDirViaCommandLine(Args, 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) {
+      llvm::SmallString<128> TestPath(Path);
+      path::append(TestPath, "Lib", Test);
+      if (fs::exists(TestPath)) {
+        WindowsSDKLibVersion = Test;
+        break;
+      }
+    }
+    return !WindowsSDKLibVersion.empty();
+  }
+  if (Major == 10) {
+    if (!getWindows10SDKVersionFromPath(Path, WindowsSDKIncludeVersion))
+      return false;
+    WindowsSDKLibVersion = WindowsSDKIncludeVersion;
+    return true;
+  }
+  // Unsupported SDK version
+  return false;
+}
+
+ // Check command line arguments to try and find a toolchain.
+static bool
+findVCToolChainViaCommandLine(const opt::InputArgList &Args, 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 (auto *A = Args.getLastArg(OPT_vctoolsdir, OPT_winsysroot)) {
+    if (A->getOption().getID() == OPT_winsysroot) {
+      SmallString<128> ToolsPath(A->getValue());
+      path::append(ToolsPath, "VC", "Tools", "MSVC");
+      std::string VCToolsVersion;
+      if (auto *A = Args.getLastArg(OPT_vctoolsversion))
+        VCToolsVersion = A->getValue();
+      else
+        VCToolsVersion = getHighestNumericTupleInDirectory(ToolsPath);
+      llvm::sys::path::append(ToolsPath, VCToolsVersion);
+      Path = std::string(ToolsPath.str());
+    } else {
+      Path = A->getValue();
+    }
+    VSLayout = ToolsetLayout::VS2017OrNewer;
+    return true;
+  }
+  return false;
+}
+
+// Check various environment variables to try and find a toolchain.
+static bool
+findVCToolChainViaEnvironment(std::string &Path, ToolsetLayout &VSLayout) {
+  // These variables are typically set by vcvarsall.bat
+  // when launching a developer command prompt.
+  if (Optional<std::string> VCToolsInstallDir =
+          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 (Optional<std::string> VCInstallDir = 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 (Optional<std::string> PathEnv = Process::GetEnv("PATH")) {
+    SmallVector<StringRef, 8> PathEntries;
+    StringRef(*PathEnv).split(PathEntries, 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;
+      path::append(ExeTestPath, "cl.exe");
+      if (!fs::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;
+      path::append(ExeTestPath, "link.exe");
+      if (!fs::exists(ExeTestPath))
+        continue;
+
+      // whatever/VC/bin --> old toolchain, VC dir is toolchain dir.
+      StringRef TestPath = PathEntry;
+      bool IsBin =
+          path::filename(TestPath).equals_insensitive("bin");
+      if (!IsBin) {
+        // Strip any architecture subdir like "amd64".
+        TestPath = path::parent_path(TestPath);
+        IsBin = path::filename(TestPath).equals_insensitive("bin");
+      }
+      if (IsBin) {
+        StringRef ParentPath = path::parent_path(TestPath);
+        StringRef ParentFilename = 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 = path::rbegin(PathEntry);
+        auto End = 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 = path::parent_path(ToolChainPath);
+
+        Path = std::string(ToolChainPath);
+        VSLayout = ToolsetLayout::VS2017OrNewer;
+        return true;
+      }
+
+    NotAToolChain:
+      continue;
+    }
+  }
+  return false;
+}
+
+// Query the Setup Config server for installs, then pick the newest version
+// and find its default VC toolchain.
+// This is the preferred way to discover new Visual Studios, as they're no
+// longer listed in the registry.
+static bool
+findVCToolChainViaSetupConfig(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.
+  InitializeCOMRAII COM(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;
+  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);
+
+  SmallString<256> ToolsVersionFilePath(VCRootPath);
+  path::append(ToolsVersionFilePath, "Auxiliary", "Build",
+               "Microsoft.VCToolsVersion.default.txt");
+
+  auto ToolsVersionFile = MemoryBuffer::getFile(ToolsVersionFilePath);
+  if (!ToolsVersionFile)
+    return false;
+
+  SmallString<256> ToolchainPath(VCRootPath);
+  path::append(ToolchainPath, "Tools", "MSVC",
+               ToolsVersionFile->get()->getBuffer().rtrim());
+  if (!fs::is_directory(ToolchainPath))
+    return false;
+
+  Path = std::string(ToolchainPath.str());
+  VSLayout = ToolsetLayout::VS2017OrNewer;
+  return true;
+#endif
+}
+
+// Look in the registry for Visual Studio installs, and use that to get
+// a toolchain path. VS2017 and newer don't get added to the registry.
+// So if we find something here, we know that it's an older version.
+static 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()) {
+      SmallString<256> VCPath(StringRef(
+          VSInstallPath.c_str(), VSInstallPath.find(R"(\Common7\IDE)")));
+      path::append(VCPath, "VC");
+
+      Path = std::string(VCPath.str());
+      VSLayout = ToolsetLayout::OlderVS;
+      return true;
+    }
+  }
+  return false;
+}
+
 bool LinkerDriver::findUnderscoreMangle(StringRef sym) {
   Symbol *s = ctx.symtab.findMangle(mangle(sym));
   return s && !isa<Undefined>(s);
@@ -422,8 +1097,8 @@
   if (hasPathSep)
     return filename;
   bool hasExt = filename.contains('.');
-  for (StringRef dir : searchPaths) {
-    SmallString<128> path = dir;
+  for (const std::string& dir : searchPaths) {
+    SmallString<128> path(dir);
     sys::path::append(path, filename);
     if (sys::fs::exists(path.str()))
       return saver().save(path.str());
@@ -504,6 +1179,105 @@
   return path;
 }
 
+void LinkerDriver::detectWinSysRoot(const opt::InputArgList &Args) {
+  // Check the command line first, that's the user explicitly telling us what to
+  // use. Check the environment next, in case we're being invoked from a VS
+  // command prompt. Failing that, just try to find the newest Visual Studio
+  // version we can and use its default VC toolchain.
+  if (!findVCToolChainViaCommandLine(Args, vcToolChainPath, vsLayout) &&
+      (Args.hasArg(OPT_lldignoreenv) ||
+       !findVCToolChainViaEnvironment(vcToolChainPath, vsLayout)) &&
+      !findVCToolChainViaSetupConfig(vcToolChainPath, vsLayout) &&
+      !findVCToolChainViaRegistry(vcToolChainPath, vsLayout))
+    return;
+
+  // If the VC environment hasn't been configured (perhaps because the user did
+  // not run vcvarsall), try to build a consistent link environment.  If the
+  // environment variable is set however, assume the user knows what they're
+  // doing. If the user passes /vctoolsdir or /winsdkdir, trust that over env
+  // vars.
+  if (const auto *A = Args.getLastArg(OPT_diasdkdir, OPT_winsysroot)) {
+    diaPath = A->getValue();
+    if (A->getOption().getID() == OPT_winsysroot)
+      path::append(diaPath, "DIA SDK");
+  }
+  useWinSysRootLibPath = Args.hasArg(OPT_lldignoreenv) ||
+                         !Process::GetEnv("LIB") ||
+                         Args.getLastArg(OPT_vctoolsdir, OPT_winsysroot);
+  if (Args.hasArg(OPT_lldignoreenv) || !Process::GetEnv("LIB") ||
+      Args.getLastArg(OPT_winsdkdir, OPT_winsysroot)) {
+    if (useUniversalCRT(vsLayout, vcToolChainPath))
+      getUniversalCRTLibraryPath(Args);
+    getWindowsSDKLibraryPath(Args);
+  }
+}
+
+void LinkerDriver::getUniversalCRTLibraryPath(const opt::InputArgList &Args) {
+  std::string UniversalCRTSdkPath;
+  std::string UCRTVersion;
+
+  if (getUniversalCRTSdkDir(Args, UniversalCRTSdkPath, UCRTVersion)) {
+    universalCRTLibPath = UniversalCRTSdkPath;
+    path::append(universalCRTLibPath, "Lib", UCRTVersion, "ucrt");
+  }
+}
+
+void LinkerDriver::getWindowsSDKLibraryPath(const opt::InputArgList &Args) {
+  std::string sdkPath;
+  std::string windowsSDKIncludeVersion;
+  std::string windowsSDKLibVersion;
+
+  if (getWindowsSDKDir(Args, sdkPath, sdkMajor, windowsSDKIncludeVersion,
+                       windowsSDKLibVersion)) {
+    windowsSdkLibPath = sdkPath;
+    path::append(windowsSdkLibPath, "Lib");
+    if (sdkMajor >= 8)
+      path::append(windowsSdkLibPath, windowsSDKLibVersion, "um");
+  }
+}
+
+void LinkerDriver::addWinSysRootLibSearchPaths() {
+  if (!diaPath.empty()) {
+    // The DIA SDK always uses the legacy vc arch, even in new MSVC versions.
+    path::append(diaPath, "lib", getLegacyVCArch());
+    searchPaths.push_back(diaPath.str().str());
+  }
+  if (useWinSysRootLibPath) {
+    searchPaths.push_back(
+        getSubDirectoryPath(SubDirectoryType::Lib, vsLayout, vcToolChainPath));
+    searchPaths.push_back(getSubDirectoryPath(SubDirectoryType::Lib, vsLayout,
+                                              vcToolChainPath, "atlmfc"));
+  }
+  if (!universalCRTLibPath.empty()) {
+    StringRef ArchName = getWindowsSDKArch();
+    if (!ArchName.empty()) {
+      path::append(universalCRTLibPath, ArchName);
+      searchPaths.push_back(universalCRTLibPath.str().str());
+    }
+  }
+  if (!windowsSdkLibPath.empty()) {
+    if (sdkMajor >= 8) {
+      path::append(windowsSdkLibPath, getWindowsSDKArch());
+    } else {
+      switch (config->machine) {
+      // In Windows SDK 7.x, x86 libraries are directly in the Lib folder.
+      case I386:
+        break;
+      case AMD64:
+        path::append(windowsSdkLibPath, "x64");
+        break;
+      case ARMNT:
+        // It is not necessary to link against Windows SDK 7.x when targeting
+        // ARM.
+        return;
+      default:
+        return;
+      }
+    }
+    searchPaths.push_back(windowsSdkLibPath.str().str());
+  }
+}
+
 // Parses LIB environment which contains a list of search paths.
 void LinkerDriver::addLibSearchPaths() {
   Optional<std::string> envOpt = Process::GetEnv("LIB");
@@ -513,7 +1287,7 @@
   while (!env.empty()) {
     StringRef path;
     std::tie(path, env) = env.split(';');
-    searchPaths.push_back(path);
+    searchPaths.push_back(path.str());
   }
 }
 
@@ -615,7 +1389,7 @@
 // the same command with the same inputs. This is for --reproduce.
 static std::string createResponseFile(const opt::InputArgList &args,
                                       ArrayRef<StringRef> filePaths,
-                                      ArrayRef<StringRef> searchPaths) {
+                                      ArrayRef<std::string> searchPaths) {
   SmallString<0> data;
   raw_svector_ostream os(data);
 
@@ -626,6 +1400,7 @@
     case OPT_INPUT:
     case OPT_defaultlib:
     case OPT_libpath:
+    case OPT_winsysroot:
       break;
     case OPT_call_graph_ordering_file:
     case OPT_deffile:
@@ -658,7 +1433,7 @@
     }
   }
 
-  for (StringRef path : searchPaths) {
+  for (const std::string& path : searchPaths) {
     std::string relPath = relativeToRoot(path);
     os << "/libpath:" << quote(relPath) << "\n";
   }
@@ -1335,7 +2110,8 @@
   searchPaths.push_back("");
   for (auto *arg : args.filtered(OPT_libpath))
     searchPaths.push_back(arg->getValue());
-  if (!args.hasArg(OPT_lldignoreenv))
+  detectWinSysRoot(args);
+  if (!args.hasArg(OPT_winsysroot))
     addLibSearchPaths();
 
   // Handle /ignore
@@ -1475,6 +2251,7 @@
     config->machine = getMachineType(arg->getValue());
     if (config->machine == IMAGE_FILE_MACHINE_UNKNOWN)
       fatal(Twine("unknown /machine argument: ") + arg->getValue());
+    addWinSysRootLibSearchPaths();
   }
 
   // Handle /nodefaultlib:<filename>
@@ -1866,10 +2643,12 @@
   for (auto *arg : args.filtered(OPT_functionpadmin, OPT_functionpadmin_opt))
     parseFunctionPadMin(arg, config->machine);
 
-  if (tar)
-    tar->append("response.txt",
-                createResponseFile(args, filePaths,
-                                   ArrayRef<StringRef>(searchPaths).slice(1)));
+  if (tar) {
+    tar->append(
+        "response.txt",
+        createResponseFile(args, filePaths,
+                           ArrayRef<std::string>(searchPaths).slice(1)));
+  }
 
   // Handle /largeaddressaware
   config->largeAddressAware = args.hasFlag(
Index: clang/lib/Driver/ToolChains/MSVC.cpp
===================================================================
--- clang/lib/Driver/ToolChains/MSVC.cpp
+++ clang/lib/Driver/ToolChains/MSVC.cpp
@@ -51,7 +51,7 @@
 #pragma clang diagnostic push
 #pragma clang diagnostic ignored "-Wnon-virtual-dtor"
 #endif
-#include "MSVCSetupApi.h"
+#include "llvm/Support/MSVCSetupApi.h"
 #ifdef __clang__
 #pragma clang diagnostic pop
 #endif
Index: clang/docs/tools/clang-formatted-files.txt
===================================================================
--- clang/docs/tools/clang-formatted-files.txt
+++ clang/docs/tools/clang-formatted-files.txt
@@ -285,7 +285,6 @@
 clang/lib/Driver/ToolChains/Minix.h
 clang/lib/Driver/ToolChains/MipsLinux.cpp
 clang/lib/Driver/ToolChains/MSP430.h
-clang/lib/Driver/ToolChains/MSVCSetupApi.h
 clang/lib/Driver/ToolChains/PPCFreeBSD.cpp
 clang/lib/Driver/ToolChains/PPCFreeBSD.h
 clang/lib/Driver/ToolChains/PPCLinux.h
@@ -5150,6 +5149,7 @@
 llvm/include/llvm/Support/MSP430AttributeParser.h
 llvm/include/llvm/Support/MSP430Attributes.h
 llvm/include/llvm/Support/MSVCErrorWorkarounds.h
+llvm/include/llvm/Support/MSVCSetupApi.h
 llvm/include/llvm/Support/Parallel.h
 llvm/include/llvm/Support/PGOOptions.h
 llvm/include/llvm/Support/PointerLikeTypeTraits.h
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to