1 | //===-- MSVCPaths.cpp - MSVC path-parsing helpers -------------------------===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | |
9 | #include "llvm/WindowsDriver/MSVCPaths.h" |
10 | #include "llvm/ADT/SmallString.h" |
11 | #include "llvm/ADT/SmallVector.h" |
12 | #include "llvm/ADT/StringExtras.h" |
13 | #include "llvm/ADT/StringRef.h" |
14 | #include "llvm/ADT/Twine.h" |
15 | #include "llvm/Support/Path.h" |
16 | #include "llvm/Support/Process.h" |
17 | #include "llvm/Support/Program.h" |
18 | #include "llvm/Support/VersionTuple.h" |
19 | #include "llvm/Support/VirtualFileSystem.h" |
20 | #include "llvm/TargetParser/Host.h" |
21 | #include "llvm/TargetParser/Triple.h" |
22 | #include <optional> |
23 | #include <string> |
24 | |
25 | #ifdef _WIN32 |
26 | #include "llvm/Support/ConvertUTF.h" |
27 | #endif |
28 | |
29 | #ifdef _WIN32 |
30 | #define WIN32_LEAN_AND_MEAN |
31 | #define NOGDI |
32 | #ifndef NOMINMAX |
33 | #define NOMINMAX |
34 | #endif |
35 | #include <windows.h> |
36 | #endif |
37 | |
38 | #ifdef _MSC_VER |
39 | // Don't support SetupApi on MinGW. |
40 | #define USE_MSVC_SETUP_API |
41 | |
42 | // Make sure this comes before MSVCSetupApi.h |
43 | #include <comdef.h> |
44 | |
45 | #include "llvm/Support/COM.h" |
46 | #ifdef __clang__ |
47 | #pragma clang diagnostic push |
48 | #pragma clang diagnostic ignored "-Wnon-virtual-dtor" |
49 | #endif |
50 | #include "llvm/WindowsDriver/MSVCSetupApi.h" |
51 | #ifdef __clang__ |
52 | #pragma clang diagnostic pop |
53 | #endif |
54 | _COM_SMARTPTR_TYPEDEF(ISetupConfiguration, __uuidof(ISetupConfiguration)); |
55 | _COM_SMARTPTR_TYPEDEF(ISetupConfiguration2, __uuidof(ISetupConfiguration2)); |
56 | _COM_SMARTPTR_TYPEDEF(ISetupHelper, __uuidof(ISetupHelper)); |
57 | _COM_SMARTPTR_TYPEDEF(IEnumSetupInstances, __uuidof(IEnumSetupInstances)); |
58 | _COM_SMARTPTR_TYPEDEF(ISetupInstance, __uuidof(ISetupInstance)); |
59 | _COM_SMARTPTR_TYPEDEF(ISetupInstance2, __uuidof(ISetupInstance2)); |
60 | #endif |
61 | |
62 | static std::string |
63 | getHighestNumericTupleInDirectory(llvm::vfs::FileSystem &VFS, |
64 | llvm::StringRef Directory) { |
65 | std::string Highest; |
66 | llvm::VersionTuple HighestTuple; |
67 | |
68 | std::error_code EC; |
69 | for (llvm::vfs::directory_iterator DirIt = VFS.dir_begin(Dir: Directory, EC), |
70 | DirEnd; |
71 | !EC && DirIt != DirEnd; DirIt.increment(EC)) { |
72 | auto Status = VFS.status(Path: DirIt->path()); |
73 | if (!Status || !Status->isDirectory()) |
74 | continue; |
75 | llvm::StringRef CandidateName = llvm::sys::path::filename(path: DirIt->path()); |
76 | llvm::VersionTuple Tuple; |
77 | if (Tuple.tryParse(string: CandidateName)) // tryParse() returns true on error. |
78 | continue; |
79 | if (Tuple > HighestTuple) { |
80 | HighestTuple = Tuple; |
81 | Highest = CandidateName.str(); |
82 | } |
83 | } |
84 | |
85 | return Highest; |
86 | } |
87 | |
88 | static bool getWindows10SDKVersionFromPath(llvm::vfs::FileSystem &VFS, |
89 | const std::string &SDKPath, |
90 | std::string &SDKVersion) { |
91 | llvm::SmallString<128> IncludePath(SDKPath); |
92 | llvm::sys::path::append(path&: IncludePath, a: "Include" ); |
93 | SDKVersion = getHighestNumericTupleInDirectory(VFS, Directory: IncludePath); |
94 | return !SDKVersion.empty(); |
95 | } |
96 | |
97 | static bool getWindowsSDKDirViaCommandLine( |
98 | llvm::vfs::FileSystem &VFS, std::optional<llvm::StringRef> WinSdkDir, |
99 | std::optional<llvm::StringRef> WinSdkVersion, |
100 | std::optional<llvm::StringRef> WinSysRoot, std::string &Path, int &Major, |
101 | std::string &Version) { |
102 | if (WinSdkDir || WinSysRoot) { |
103 | // Don't validate the input; trust the value supplied by the user. |
104 | // The motivation is to prevent unnecessary file and registry access. |
105 | llvm::VersionTuple SDKVersion; |
106 | if (WinSdkVersion) |
107 | SDKVersion.tryParse(string: *WinSdkVersion); |
108 | |
109 | if (WinSysRoot) { |
110 | llvm::SmallString<128> SDKPath(*WinSysRoot); |
111 | llvm::sys::path::append(path&: SDKPath, a: "Windows Kits" ); |
112 | if (!SDKVersion.empty()) |
113 | llvm::sys::path::append(path&: SDKPath, a: llvm::Twine(SDKVersion.getMajor())); |
114 | else |
115 | llvm::sys::path::append( |
116 | path&: SDKPath, a: getHighestNumericTupleInDirectory(VFS, Directory: SDKPath)); |
117 | Path = std::string(SDKPath); |
118 | } else { |
119 | Path = WinSdkDir->str(); |
120 | } |
121 | |
122 | if (!SDKVersion.empty()) { |
123 | Major = SDKVersion.getMajor(); |
124 | Version = SDKVersion.getAsString(); |
125 | } else if (getWindows10SDKVersionFromPath(VFS, SDKPath: Path, SDKVersion&: Version)) { |
126 | Major = 10; |
127 | } |
128 | return true; |
129 | } |
130 | return false; |
131 | } |
132 | |
133 | #ifdef _WIN32 |
134 | static bool readFullStringValue(HKEY hkey, const char *valueName, |
135 | std::string &value) { |
136 | std::wstring WideValueName; |
137 | if (!llvm::ConvertUTF8toWide(valueName, WideValueName)) |
138 | return false; |
139 | |
140 | DWORD result = 0; |
141 | DWORD valueSize = 0; |
142 | DWORD type = 0; |
143 | // First just query for the required size. |
144 | result = RegQueryValueExW(hkey, WideValueName.c_str(), NULL, &type, NULL, |
145 | &valueSize); |
146 | if (result != ERROR_SUCCESS || type != REG_SZ || !valueSize) |
147 | return false; |
148 | std::vector<BYTE> buffer(valueSize); |
149 | result = RegQueryValueExW(hkey, WideValueName.c_str(), NULL, NULL, &buffer[0], |
150 | &valueSize); |
151 | if (result == ERROR_SUCCESS) { |
152 | std::wstring WideValue(reinterpret_cast<const wchar_t *>(buffer.data()), |
153 | valueSize / sizeof(wchar_t)); |
154 | if (valueSize && WideValue.back() == L'\0') { |
155 | WideValue.pop_back(); |
156 | } |
157 | // The destination buffer must be empty as an invariant of the conversion |
158 | // function; but this function is sometimes called in a loop that passes in |
159 | // the same buffer, however. Simply clear it out so we can overwrite it. |
160 | value.clear(); |
161 | return llvm::convertWideToUTF8(WideValue, value); |
162 | } |
163 | return false; |
164 | } |
165 | #endif |
166 | |
167 | /// Read registry string. |
168 | /// This also supports a means to look for high-versioned keys by use |
169 | /// of a $VERSION placeholder in the key path. |
170 | /// $VERSION in the key path is a placeholder for the version number, |
171 | /// causing the highest value path to be searched for and used. |
172 | /// I.e. "SOFTWARE\\Microsoft\\VisualStudio\\$VERSION". |
173 | /// There can be additional characters in the component. Only the numeric |
174 | /// characters are compared. This function only searches HKLM. |
175 | static bool getSystemRegistryString(const char *keyPath, const char *valueName, |
176 | std::string &value, std::string *phValue) { |
177 | #ifndef _WIN32 |
178 | return false; |
179 | #else |
180 | HKEY hRootKey = HKEY_LOCAL_MACHINE; |
181 | HKEY hKey = NULL; |
182 | long lResult; |
183 | bool returnValue = false; |
184 | |
185 | const char *placeHolder = strstr(keyPath, "$VERSION" ); |
186 | std::string bestName; |
187 | // If we have a $VERSION placeholder, do the highest-version search. |
188 | if (placeHolder) { |
189 | const char *keyEnd = placeHolder - 1; |
190 | const char *nextKey = placeHolder; |
191 | // Find end of previous key. |
192 | while ((keyEnd > keyPath) && (*keyEnd != '\\')) |
193 | keyEnd--; |
194 | // Find end of key containing $VERSION. |
195 | while (*nextKey && (*nextKey != '\\')) |
196 | nextKey++; |
197 | size_t partialKeyLength = keyEnd - keyPath; |
198 | char partialKey[256]; |
199 | if (partialKeyLength >= sizeof(partialKey)) |
200 | partialKeyLength = sizeof(partialKey) - 1; |
201 | strncpy(partialKey, keyPath, partialKeyLength); |
202 | partialKey[partialKeyLength] = '\0'; |
203 | HKEY hTopKey = NULL; |
204 | lResult = RegOpenKeyExA(hRootKey, partialKey, 0, KEY_READ | KEY_WOW64_32KEY, |
205 | &hTopKey); |
206 | if (lResult == ERROR_SUCCESS) { |
207 | char keyName[256]; |
208 | double bestValue = 0.0; |
209 | DWORD index, size = sizeof(keyName) - 1; |
210 | for (index = 0; RegEnumKeyExA(hTopKey, index, keyName, &size, NULL, NULL, |
211 | NULL, NULL) == ERROR_SUCCESS; |
212 | index++) { |
213 | const char *sp = keyName; |
214 | while (*sp && !llvm::isDigit(*sp)) |
215 | sp++; |
216 | if (!*sp) |
217 | continue; |
218 | const char *ep = sp + 1; |
219 | while (*ep && (llvm::isDigit(*ep) || (*ep == '.'))) |
220 | ep++; |
221 | char numBuf[32]; |
222 | strncpy(numBuf, sp, sizeof(numBuf) - 1); |
223 | numBuf[sizeof(numBuf) - 1] = '\0'; |
224 | double dvalue = strtod(numBuf, NULL); |
225 | if (dvalue > bestValue) { |
226 | // Test that InstallDir is indeed there before keeping this index. |
227 | // Open the chosen key path remainder. |
228 | bestName = keyName; |
229 | // Append rest of key. |
230 | bestName.append(nextKey); |
231 | lResult = RegOpenKeyExA(hTopKey, bestName.c_str(), 0, |
232 | KEY_READ | KEY_WOW64_32KEY, &hKey); |
233 | if (lResult == ERROR_SUCCESS) { |
234 | if (readFullStringValue(hKey, valueName, value)) { |
235 | bestValue = dvalue; |
236 | if (phValue) |
237 | *phValue = bestName; |
238 | returnValue = true; |
239 | } |
240 | RegCloseKey(hKey); |
241 | } |
242 | } |
243 | size = sizeof(keyName) - 1; |
244 | } |
245 | RegCloseKey(hTopKey); |
246 | } |
247 | } else { |
248 | lResult = |
249 | RegOpenKeyExA(hRootKey, keyPath, 0, KEY_READ | KEY_WOW64_32KEY, &hKey); |
250 | if (lResult == ERROR_SUCCESS) { |
251 | if (readFullStringValue(hKey, valueName, value)) |
252 | returnValue = true; |
253 | if (phValue) |
254 | phValue->clear(); |
255 | RegCloseKey(hKey); |
256 | } |
257 | } |
258 | return returnValue; |
259 | #endif // _WIN32 |
260 | } |
261 | |
262 | namespace llvm { |
263 | |
264 | const char *archToWindowsSDKArch(Triple::ArchType Arch) { |
265 | switch (Arch) { |
266 | case Triple::ArchType::x86: |
267 | return "x86" ; |
268 | case Triple::ArchType::x86_64: |
269 | return "x64" ; |
270 | case Triple::ArchType::arm: |
271 | case Triple::ArchType::thumb: |
272 | return "arm" ; |
273 | case Triple::ArchType::aarch64: |
274 | return "arm64" ; |
275 | default: |
276 | return "" ; |
277 | } |
278 | } |
279 | |
280 | const char *archToLegacyVCArch(Triple::ArchType Arch) { |
281 | switch (Arch) { |
282 | case Triple::ArchType::x86: |
283 | // x86 is default in legacy VC toolchains. |
284 | // e.g. x86 libs are directly in /lib as opposed to /lib/x86. |
285 | return "" ; |
286 | case Triple::ArchType::x86_64: |
287 | return "amd64" ; |
288 | case Triple::ArchType::arm: |
289 | case Triple::ArchType::thumb: |
290 | return "arm" ; |
291 | case Triple::ArchType::aarch64: |
292 | return "arm64" ; |
293 | default: |
294 | return "" ; |
295 | } |
296 | } |
297 | |
298 | const char *archToDevDivInternalArch(Triple::ArchType Arch) { |
299 | switch (Arch) { |
300 | case Triple::ArchType::x86: |
301 | return "i386" ; |
302 | case Triple::ArchType::x86_64: |
303 | return "amd64" ; |
304 | case Triple::ArchType::arm: |
305 | case Triple::ArchType::thumb: |
306 | return "arm" ; |
307 | case Triple::ArchType::aarch64: |
308 | return "arm64" ; |
309 | default: |
310 | return "" ; |
311 | } |
312 | } |
313 | |
314 | bool appendArchToWindowsSDKLibPath(int SDKMajor, SmallString<128> LibPath, |
315 | Triple::ArchType Arch, std::string &path) { |
316 | if (SDKMajor >= 8) { |
317 | sys::path::append(path&: LibPath, a: archToWindowsSDKArch(Arch)); |
318 | } else { |
319 | switch (Arch) { |
320 | // In Windows SDK 7.x, x86 libraries are directly in the Lib folder. |
321 | case Triple::x86: |
322 | break; |
323 | case Triple::x86_64: |
324 | sys::path::append(path&: LibPath, a: "x64" ); |
325 | break; |
326 | case Triple::arm: |
327 | case Triple::thumb: |
328 | // It is not necessary to link against Windows SDK 7.x when targeting ARM. |
329 | return false; |
330 | default: |
331 | return false; |
332 | } |
333 | } |
334 | |
335 | path = std::string(LibPath); |
336 | return true; |
337 | } |
338 | |
339 | std::string getSubDirectoryPath(SubDirectoryType Type, ToolsetLayout VSLayout, |
340 | const std::string &VCToolChainPath, |
341 | Triple::ArchType TargetArch, |
342 | StringRef SubdirParent) { |
343 | const char *SubdirName; |
344 | const char *IncludeName; |
345 | switch (VSLayout) { |
346 | case ToolsetLayout::OlderVS: |
347 | SubdirName = archToLegacyVCArch(Arch: TargetArch); |
348 | IncludeName = "include" ; |
349 | break; |
350 | case ToolsetLayout::VS2017OrNewer: |
351 | SubdirName = archToWindowsSDKArch(Arch: TargetArch); |
352 | IncludeName = "include" ; |
353 | break; |
354 | case ToolsetLayout::DevDivInternal: |
355 | SubdirName = archToDevDivInternalArch(Arch: TargetArch); |
356 | IncludeName = "inc" ; |
357 | break; |
358 | } |
359 | |
360 | SmallString<256> Path(VCToolChainPath); |
361 | if (!SubdirParent.empty()) |
362 | sys::path::append(path&: Path, a: SubdirParent); |
363 | |
364 | switch (Type) { |
365 | case SubDirectoryType::Bin: |
366 | if (VSLayout == ToolsetLayout::VS2017OrNewer) { |
367 | // MSVC ships with two linkers: a 32-bit x86 and 64-bit x86 linker. |
368 | // On x86, pick the linker that corresponds to the current process. |
369 | // On ARM64, pick the 32-bit x86 linker; the 64-bit one doesn't run |
370 | // on Windows 10. |
371 | // |
372 | // FIXME: Consider using IsWow64GuestMachineSupported to figure out |
373 | // if we can invoke the 64-bit linker. It's generally preferable |
374 | // because it won't run out of address-space. |
375 | const bool HostIsX64 = |
376 | Triple(sys::getProcessTriple()).getArch() == Triple::x86_64; |
377 | const char *const HostName = HostIsX64 ? "Hostx64" : "Hostx86" ; |
378 | sys::path::append(path&: Path, a: "bin" , b: HostName, c: SubdirName); |
379 | } else { // OlderVS or DevDivInternal |
380 | sys::path::append(path&: Path, a: "bin" , b: SubdirName); |
381 | } |
382 | break; |
383 | case SubDirectoryType::Include: |
384 | sys::path::append(path&: Path, a: IncludeName); |
385 | break; |
386 | case SubDirectoryType::Lib: |
387 | sys::path::append(path&: Path, a: "lib" , b: SubdirName); |
388 | break; |
389 | } |
390 | return std::string(Path); |
391 | } |
392 | |
393 | bool useUniversalCRT(ToolsetLayout VSLayout, const std::string &VCToolChainPath, |
394 | Triple::ArchType TargetArch, vfs::FileSystem &VFS) { |
395 | SmallString<128> TestPath(getSubDirectoryPath( |
396 | Type: SubDirectoryType::Include, VSLayout, VCToolChainPath, TargetArch)); |
397 | sys::path::append(path&: TestPath, a: "stdlib.h" ); |
398 | return !VFS.exists(Path: TestPath); |
399 | } |
400 | |
401 | bool getWindowsSDKDir(vfs::FileSystem &VFS, std::optional<StringRef> WinSdkDir, |
402 | std::optional<StringRef> WinSdkVersion, |
403 | std::optional<StringRef> WinSysRoot, std::string &Path, |
404 | int &Major, std::string &WindowsSDKIncludeVersion, |
405 | std::string &WindowsSDKLibVersion) { |
406 | // Trust /winsdkdir and /winsdkversion if present. |
407 | if (getWindowsSDKDirViaCommandLine(VFS, WinSdkDir, WinSdkVersion, WinSysRoot, |
408 | Path, Major, Version&: WindowsSDKIncludeVersion)) { |
409 | WindowsSDKLibVersion = WindowsSDKIncludeVersion; |
410 | return true; |
411 | } |
412 | |
413 | // FIXME: Try env vars (%WindowsSdkDir%, %UCRTVersion%) before going to |
414 | // registry. |
415 | |
416 | // Try the Windows registry. |
417 | std::string RegistrySDKVersion; |
418 | if (!getSystemRegistryString( |
419 | keyPath: "SOFTWARE\\Microsoft\\Microsoft SDKs\\Windows\\$VERSION" , |
420 | valueName: "InstallationFolder" , value&: Path, phValue: &RegistrySDKVersion)) |
421 | return false; |
422 | if (Path.empty() || RegistrySDKVersion.empty()) |
423 | return false; |
424 | |
425 | WindowsSDKIncludeVersion.clear(); |
426 | WindowsSDKLibVersion.clear(); |
427 | Major = 0; |
428 | std::sscanf(s: RegistrySDKVersion.c_str(), format: "v%d." , &Major); |
429 | if (Major <= 7) |
430 | return true; |
431 | if (Major == 8) { |
432 | // Windows SDK 8.x installs libraries in a folder whose names depend on the |
433 | // version of the OS you're targeting. By default choose the newest, which |
434 | // usually corresponds to the version of the OS you've installed the SDK on. |
435 | const char *Tests[] = {"winv6.3" , "win8" , "win7" }; |
436 | for (const char *Test : Tests) { |
437 | SmallString<128> TestPath(Path); |
438 | sys::path::append(path&: TestPath, a: "Lib" , b: Test); |
439 | if (VFS.exists(Path: TestPath)) { |
440 | WindowsSDKLibVersion = Test; |
441 | break; |
442 | } |
443 | } |
444 | return !WindowsSDKLibVersion.empty(); |
445 | } |
446 | if (Major == 10) { |
447 | if (!getWindows10SDKVersionFromPath(VFS, SDKPath: Path, SDKVersion&: WindowsSDKIncludeVersion)) |
448 | return false; |
449 | WindowsSDKLibVersion = WindowsSDKIncludeVersion; |
450 | return true; |
451 | } |
452 | // Unsupported SDK version |
453 | return false; |
454 | } |
455 | |
456 | bool getUniversalCRTSdkDir(vfs::FileSystem &VFS, |
457 | std::optional<StringRef> WinSdkDir, |
458 | std::optional<StringRef> WinSdkVersion, |
459 | std::optional<StringRef> WinSysRoot, |
460 | std::string &Path, std::string &UCRTVersion) { |
461 | // If /winsdkdir is passed, use it as location for the UCRT too. |
462 | // FIXME: Should there be a dedicated /ucrtdir to override /winsdkdir? |
463 | int Major; |
464 | if (getWindowsSDKDirViaCommandLine(VFS, WinSdkDir, WinSdkVersion, WinSysRoot, |
465 | Path, Major, Version&: UCRTVersion)) |
466 | return true; |
467 | |
468 | // FIXME: Try env vars (%UniversalCRTSdkDir%, %UCRTVersion%) before going to |
469 | // registry. |
470 | |
471 | // vcvarsqueryregistry.bat for Visual Studio 2015 queries the registry |
472 | // for the specific key "KitsRoot10". So do we. |
473 | if (!getSystemRegistryString( |
474 | keyPath: "SOFTWARE\\Microsoft\\Windows Kits\\Installed Roots" , valueName: "KitsRoot10" , |
475 | value&: Path, phValue: nullptr)) |
476 | return false; |
477 | |
478 | return getWindows10SDKVersionFromPath(VFS, SDKPath: Path, SDKVersion&: UCRTVersion); |
479 | } |
480 | |
481 | bool findVCToolChainViaCommandLine(vfs::FileSystem &VFS, |
482 | std::optional<StringRef> VCToolsDir, |
483 | std::optional<StringRef> VCToolsVersion, |
484 | std::optional<StringRef> WinSysRoot, |
485 | std::string &Path, ToolsetLayout &VSLayout) { |
486 | // Don't validate the input; trust the value supplied by the user. |
487 | // The primary motivation is to prevent unnecessary file and registry access. |
488 | if (VCToolsDir || WinSysRoot) { |
489 | if (WinSysRoot) { |
490 | SmallString<128> ToolsPath(*WinSysRoot); |
491 | sys::path::append(path&: ToolsPath, a: "VC" , b: "Tools" , c: "MSVC" ); |
492 | std::string ToolsVersion; |
493 | if (VCToolsVersion) |
494 | ToolsVersion = VCToolsVersion->str(); |
495 | else |
496 | ToolsVersion = getHighestNumericTupleInDirectory(VFS, Directory: ToolsPath); |
497 | sys::path::append(path&: ToolsPath, a: ToolsVersion); |
498 | Path = std::string(ToolsPath); |
499 | } else { |
500 | Path = VCToolsDir->str(); |
501 | } |
502 | VSLayout = ToolsetLayout::VS2017OrNewer; |
503 | return true; |
504 | } |
505 | return false; |
506 | } |
507 | |
508 | bool findVCToolChainViaEnvironment(vfs::FileSystem &VFS, std::string &Path, |
509 | ToolsetLayout &VSLayout) { |
510 | // These variables are typically set by vcvarsall.bat |
511 | // when launching a developer command prompt. |
512 | if (std::optional<std::string> VCToolsInstallDir = |
513 | sys::Process::GetEnv(name: "VCToolsInstallDir" )) { |
514 | // This is only set by newer Visual Studios, and it leads straight to |
515 | // the toolchain directory. |
516 | Path = std::move(*VCToolsInstallDir); |
517 | VSLayout = ToolsetLayout::VS2017OrNewer; |
518 | return true; |
519 | } |
520 | if (std::optional<std::string> VCInstallDir = |
521 | sys::Process::GetEnv(name: "VCINSTALLDIR" )) { |
522 | // If the previous variable isn't set but this one is, then we've found |
523 | // an older Visual Studio. This variable is set by newer Visual Studios too, |
524 | // so this check has to appear second. |
525 | // In older Visual Studios, the VC directory is the toolchain. |
526 | Path = std::move(*VCInstallDir); |
527 | VSLayout = ToolsetLayout::OlderVS; |
528 | return true; |
529 | } |
530 | |
531 | // We couldn't find any VC environment variables. Let's walk through PATH and |
532 | // see if it leads us to a VC toolchain bin directory. If it does, pick the |
533 | // first one that we find. |
534 | if (std::optional<std::string> PathEnv = sys::Process::GetEnv(name: "PATH" )) { |
535 | SmallVector<StringRef, 8> PathEntries; |
536 | StringRef(*PathEnv).split(A&: PathEntries, Separator: sys::EnvPathSeparator); |
537 | for (StringRef PathEntry : PathEntries) { |
538 | if (PathEntry.empty()) |
539 | continue; |
540 | |
541 | SmallString<256> ExeTestPath; |
542 | |
543 | // If cl.exe doesn't exist, then this definitely isn't a VC toolchain. |
544 | ExeTestPath = PathEntry; |
545 | sys::path::append(path&: ExeTestPath, a: "cl.exe" ); |
546 | if (!VFS.exists(Path: ExeTestPath)) |
547 | continue; |
548 | |
549 | // cl.exe existing isn't a conclusive test for a VC toolchain; clang also |
550 | // has a cl.exe. So let's check for link.exe too. |
551 | ExeTestPath = PathEntry; |
552 | sys::path::append(path&: ExeTestPath, a: "link.exe" ); |
553 | if (!VFS.exists(Path: ExeTestPath)) |
554 | continue; |
555 | |
556 | // whatever/VC/bin --> old toolchain, VC dir is toolchain dir. |
557 | StringRef TestPath = PathEntry; |
558 | bool IsBin = sys::path::filename(path: TestPath).equals_insensitive(RHS: "bin" ); |
559 | if (!IsBin) { |
560 | // Strip any architecture subdir like "amd64". |
561 | TestPath = sys::path::parent_path(path: TestPath); |
562 | IsBin = sys::path::filename(path: TestPath).equals_insensitive(RHS: "bin" ); |
563 | } |
564 | if (IsBin) { |
565 | StringRef ParentPath = sys::path::parent_path(path: TestPath); |
566 | StringRef ParentFilename = sys::path::filename(path: ParentPath); |
567 | if (ParentFilename.equals_insensitive(RHS: "VC" )) { |
568 | Path = std::string(ParentPath); |
569 | VSLayout = ToolsetLayout::OlderVS; |
570 | return true; |
571 | } |
572 | if (ParentFilename.equals_insensitive(RHS: "x86ret" ) || |
573 | ParentFilename.equals_insensitive(RHS: "x86chk" ) || |
574 | ParentFilename.equals_insensitive(RHS: "amd64ret" ) || |
575 | ParentFilename.equals_insensitive(RHS: "amd64chk" )) { |
576 | Path = std::string(ParentPath); |
577 | VSLayout = ToolsetLayout::DevDivInternal; |
578 | return true; |
579 | } |
580 | |
581 | } else { |
582 | // This could be a new (>=VS2017) toolchain. If it is, we should find |
583 | // path components with these prefixes when walking backwards through |
584 | // the path. |
585 | // Note: empty strings match anything. |
586 | StringRef ExpectedPrefixes[] = {"" , "Host" , "bin" , "" , |
587 | "MSVC" , "Tools" , "VC" }; |
588 | |
589 | auto It = sys::path::rbegin(path: PathEntry); |
590 | auto End = sys::path::rend(path: PathEntry); |
591 | for (StringRef Prefix : ExpectedPrefixes) { |
592 | if (It == End) |
593 | goto NotAToolChain; |
594 | if (!It->starts_with_insensitive(Prefix)) |
595 | goto NotAToolChain; |
596 | ++It; |
597 | } |
598 | |
599 | // We've found a new toolchain! |
600 | // Back up 3 times (/bin/Host/arch) to get the root path. |
601 | StringRef ToolChainPath(PathEntry); |
602 | for (int i = 0; i < 3; ++i) |
603 | ToolChainPath = sys::path::parent_path(path: ToolChainPath); |
604 | |
605 | Path = std::string(ToolChainPath); |
606 | VSLayout = ToolsetLayout::VS2017OrNewer; |
607 | return true; |
608 | } |
609 | |
610 | NotAToolChain: |
611 | continue; |
612 | } |
613 | } |
614 | return false; |
615 | } |
616 | |
617 | bool findVCToolChainViaSetupConfig(vfs::FileSystem &VFS, |
618 | std::optional<StringRef> VCToolsVersion, |
619 | std::string &Path, ToolsetLayout &VSLayout) { |
620 | #if !defined(USE_MSVC_SETUP_API) |
621 | return false; |
622 | #else |
623 | // FIXME: This really should be done once in the top-level program's main |
624 | // function, as it may have already been initialized with a different |
625 | // threading model otherwise. |
626 | sys::InitializeCOMRAII COM(sys::COMThreadingMode::SingleThreaded); |
627 | HRESULT HR; |
628 | |
629 | // _com_ptr_t will throw a _com_error if a COM calls fail. |
630 | // The LLVM coding standards forbid exception handling, so we'll have to |
631 | // stop them from being thrown in the first place. |
632 | // The destructor will put the regular error handler back when we leave |
633 | // this scope. |
634 | struct SuppressCOMErrorsRAII { |
635 | static void __stdcall handler(HRESULT hr, IErrorInfo *perrinfo) {} |
636 | |
637 | SuppressCOMErrorsRAII() { _set_com_error_handler(handler); } |
638 | |
639 | ~SuppressCOMErrorsRAII() { _set_com_error_handler(_com_raise_error); } |
640 | |
641 | } COMErrorSuppressor; |
642 | |
643 | ISetupConfigurationPtr Query; |
644 | HR = Query.CreateInstance(__uuidof(SetupConfiguration)); |
645 | if (FAILED(HR)) |
646 | return false; |
647 | |
648 | IEnumSetupInstancesPtr EnumInstances; |
649 | HR = ISetupConfiguration2Ptr(Query)->EnumAllInstances(&EnumInstances); |
650 | if (FAILED(HR)) |
651 | return false; |
652 | |
653 | ISetupInstancePtr Instance; |
654 | HR = EnumInstances->Next(1, &Instance, nullptr); |
655 | if (HR != S_OK) |
656 | return false; |
657 | |
658 | ISetupInstancePtr NewestInstance; |
659 | std::optional<uint64_t> NewestVersionNum; |
660 | do { |
661 | bstr_t VersionString; |
662 | uint64_t VersionNum; |
663 | HR = Instance->GetInstallationVersion(VersionString.GetAddress()); |
664 | if (FAILED(HR)) |
665 | continue; |
666 | HR = ISetupHelperPtr(Query)->ParseVersion(VersionString, &VersionNum); |
667 | if (FAILED(HR)) |
668 | continue; |
669 | if (!NewestVersionNum || (VersionNum > NewestVersionNum)) { |
670 | NewestInstance = Instance; |
671 | NewestVersionNum = VersionNum; |
672 | } |
673 | } while ((HR = EnumInstances->Next(1, &Instance, nullptr)) == S_OK); |
674 | |
675 | if (!NewestInstance) |
676 | return false; |
677 | |
678 | bstr_t VCPathWide; |
679 | HR = NewestInstance->ResolvePath(L"VC" , VCPathWide.GetAddress()); |
680 | if (FAILED(HR)) |
681 | return false; |
682 | |
683 | std::string VCRootPath; |
684 | convertWideToUTF8(std::wstring(VCPathWide), VCRootPath); |
685 | |
686 | std::string ToolsVersion; |
687 | if (VCToolsVersion.has_value()) { |
688 | ToolsVersion = *VCToolsVersion; |
689 | } else { |
690 | SmallString<256> ToolsVersionFilePath(VCRootPath); |
691 | sys::path::append(ToolsVersionFilePath, "Auxiliary" , "Build" , |
692 | "Microsoft.VCToolsVersion.default.txt" ); |
693 | |
694 | auto ToolsVersionFile = MemoryBuffer::getFile(ToolsVersionFilePath); |
695 | if (!ToolsVersionFile) |
696 | return false; |
697 | |
698 | ToolsVersion = ToolsVersionFile->get()->getBuffer().rtrim(); |
699 | } |
700 | |
701 | |
702 | SmallString<256> ToolchainPath(VCRootPath); |
703 | sys::path::append(ToolchainPath, "Tools" , "MSVC" , ToolsVersion); |
704 | auto Status = VFS.status(ToolchainPath); |
705 | if (!Status || !Status->isDirectory()) |
706 | return false; |
707 | |
708 | Path = std::string(ToolchainPath.str()); |
709 | VSLayout = ToolsetLayout::VS2017OrNewer; |
710 | return true; |
711 | #endif |
712 | } |
713 | |
714 | bool findVCToolChainViaRegistry(std::string &Path, ToolsetLayout &VSLayout) { |
715 | std::string VSInstallPath; |
716 | if (getSystemRegistryString(keyPath: R"(SOFTWARE\Microsoft\VisualStudio\$VERSION)" , |
717 | valueName: "InstallDir" , value&: VSInstallPath, phValue: nullptr) || |
718 | getSystemRegistryString(keyPath: R"(SOFTWARE\Microsoft\VCExpress\$VERSION)" , |
719 | valueName: "InstallDir" , value&: VSInstallPath, phValue: nullptr)) { |
720 | if (!VSInstallPath.empty()) { |
721 | auto pos = VSInstallPath.find(s: R"(\Common7\IDE)" ); |
722 | if (pos == std::string::npos) |
723 | return false; |
724 | SmallString<256> VCPath(StringRef(VSInstallPath.c_str(), pos)); |
725 | sys::path::append(path&: VCPath, a: "VC" ); |
726 | |
727 | Path = std::string(VCPath); |
728 | VSLayout = ToolsetLayout::OlderVS; |
729 | return true; |
730 | } |
731 | } |
732 | return false; |
733 | } |
734 | |
735 | } // namespace llvm |
736 | |