1 /** 2 * When compiling on Windows with the Microsoft toolchain, try to detect the Visual Studio setup. 3 * 4 * Copyright: Copyright (C) 1999-2021 by The D Language Foundation, All Rights Reserved 5 * Authors: $(LINK2 http://www.digitalmars.com, Walter Bright) 6 * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 7 * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/link.d, _vsoptions.d) 8 * Documentation: https://dlang.org/phobos/dmd_vsoptions.html 9 * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/vsoptions.d 10 */ 11 12 module dmd.vsoptions; 13 14 version (Windows): 15 import core.stdc.ctype; 16 import core.stdc.stdlib; 17 import core.stdc.string; 18 import core.stdc.wchar_; 19 import core.sys.windows.winbase; 20 import core.sys.windows.windef; 21 import core.sys.windows.winreg; 22 23 import dmd.env; 24 import dmd.root.file; 25 import dmd.root.filename; 26 import dmd.root.outbuffer; 27 import dmd.root.rmem; 28 import dmd.root.string : toDString; 29 30 private immutable supportedPre2017Versions = ["14.0", "12.0", "11.0", "10.0", "9.0"]; 31 32 extern(C++) struct VSOptions 33 { 34 // evaluated once at startup, reflecting the result of vcvarsall.bat 35 // from the current environment or the latest Visual Studio installation 36 const(char)* WindowsSdkDir; 37 const(char)* WindowsSdkVersion; 38 const(char)* UCRTSdkDir; 39 const(char)* UCRTVersion; 40 const(char)* VSInstallDir; 41 const(char)* VCInstallDir; 42 const(char)* VCToolsInstallDir; // used by VS 2017+ 43 44 /** 45 * fill member variables from environment or registry 46 */ 47 void initialize() 48 { 49 detectWindowsSDK(); 50 detectUCRT(); 51 detectVSInstallDir(); 52 detectVCInstallDir(); 53 detectVCToolsInstallDir(); 54 } 55 56 /** 57 * retrieve the name of the default C runtime library 58 * Params: 59 * x64 = target architecture (x86 if false) 60 * Returns: 61 * name of the default C runtime library 62 */ 63 const(char)* defaultRuntimeLibrary(bool x64) 64 { 65 if (VCInstallDir is null) 66 { 67 detectVCInstallDir(); 68 detectVCToolsInstallDir(); 69 } 70 if (getVCLibDir(x64)) 71 return "libcmt"; 72 else 73 return "msvcrt120"; // mingw replacement 74 } 75 76 /** 77 * retrieve options to be passed to the Microsoft linker 78 * Params: 79 * x64 = target architecture (x86 if false) 80 * Returns: 81 * allocated string of options to add to the linker command line 82 */ 83 const(char)* linkOptions(bool x64) 84 { 85 OutBuffer cmdbuf; 86 if (auto vclibdir = getVCLibDir(x64)) 87 { 88 cmdbuf.writestring(" /LIBPATH:\""); 89 cmdbuf.writestring(vclibdir); 90 cmdbuf.writeByte('\"'); 91 92 if (FileName.exists(FileName.combine(vclibdir, "legacy_stdio_definitions.lib"))) 93 { 94 // VS2015 or later use UCRT 95 cmdbuf.writestring(" legacy_stdio_definitions.lib"); 96 if (auto p = getUCRTLibPath(x64)) 97 { 98 cmdbuf.writestring(" /LIBPATH:\""); 99 cmdbuf.writestring(p); 100 cmdbuf.writeByte('\"'); 101 } 102 } 103 } 104 if (auto p = getSDKLibPath(x64)) 105 { 106 cmdbuf.writestring(" /LIBPATH:\""); 107 cmdbuf.writestring(p); 108 cmdbuf.writeByte('\"'); 109 } 110 if (auto p = getenv("DXSDK_DIR"w)) 111 { 112 // support for old DX SDK installations 113 cmdbuf.writestring(" /LIBPATH:\""); 114 cmdbuf.writestring(p); 115 cmdbuf.writestring(x64 ? `\Lib\x64"` : `\Lib\x86"`); 116 mem.xfree(p); 117 } 118 return cmdbuf.extractChars(); 119 } 120 121 /** 122 * retrieve path to the Microsoft linker executable 123 * also modifies PATH environment variable if necessary to find conditionally loaded DLLs 124 * Params: 125 * x64 = target architecture (x86 if false) 126 * Returns: 127 * absolute path to link.exe, just "link.exe" if not found 128 */ 129 const(char)* linkerPath(bool x64) 130 { 131 const(char)* addpath; 132 if (auto p = getVCBinDir(x64, addpath)) 133 { 134 OutBuffer cmdbuf; 135 cmdbuf.writestring(p); 136 cmdbuf.writestring(r"\link.exe"); 137 if (addpath) 138 { 139 // debug info needs DLLs from $(VSInstallDir)\Common7\IDE for most linker versions 140 // so prepend it too the PATH environment variable 141 char* path = getenv("PATH"w); 142 const pathlen = strlen(path); 143 const addpathlen = strlen(addpath); 144 145 const length = addpathlen + 1 + pathlen; 146 char* npath = cast(char*)mem.xmalloc(length); 147 memcpy(npath, addpath, addpathlen); 148 npath[addpathlen] = ';'; 149 memcpy(npath + addpathlen + 1, path, pathlen); 150 if (putenvRestorable("PATH", npath[0 .. length])) 151 assert(0); 152 mem.xfree(npath); 153 mem.xfree(path); 154 } 155 return cmdbuf.extractChars(); 156 } 157 158 // try lld-link.exe alongside dmd.exe 159 char[MAX_PATH + 1] dmdpath = void; 160 const len = GetModuleFileNameA(null, dmdpath.ptr, dmdpath.length); 161 if (len <= MAX_PATH) 162 { 163 auto lldpath = FileName.replaceName(dmdpath[0 .. len], "lld-link.exe"); 164 if (FileName.exists(lldpath)) 165 return lldpath.ptr; 166 } 167 168 // search PATH to avoid createProcess preferring "link.exe" from the dmd folder 169 if (auto p = FileName.searchPath(getenv("PATH"w), "link.exe"[], false)) 170 return p.ptr; 171 return "link.exe"; 172 } 173 174 private: 175 /** 176 * detect WindowsSdkDir and WindowsSDKVersion from environment or registry 177 */ 178 void detectWindowsSDK() 179 { 180 if (WindowsSdkDir is null) 181 WindowsSdkDir = getenv("WindowsSdkDir"w); 182 183 if (WindowsSdkDir is null) 184 { 185 WindowsSdkDir = GetRegistryString(r"Microsoft\Windows Kits\Installed Roots", "KitsRoot10"w); 186 if (WindowsSdkDir && !findLatestSDKDir(FileName.combine(WindowsSdkDir, "Include"), r"um\windows.h")) 187 WindowsSdkDir = null; 188 } 189 if (WindowsSdkDir is null) 190 { 191 WindowsSdkDir = GetRegistryString(r"Microsoft\Microsoft SDKs\Windows\v8.1", "InstallationFolder"w); 192 if (WindowsSdkDir && !FileName.exists(FileName.combine(WindowsSdkDir, "Lib"))) 193 WindowsSdkDir = null; 194 } 195 if (WindowsSdkDir is null) 196 { 197 WindowsSdkDir = GetRegistryString(r"Microsoft\Microsoft SDKs\Windows\v8.0", "InstallationFolder"w); 198 if (WindowsSdkDir && !FileName.exists(FileName.combine(WindowsSdkDir, "Lib"))) 199 WindowsSdkDir = null; 200 } 201 if (WindowsSdkDir is null) 202 { 203 WindowsSdkDir = GetRegistryString(r"Microsoft\Microsoft SDKs\Windows", "CurrentInstallationFolder"w); 204 if (WindowsSdkDir && !FileName.exists(FileName.combine(WindowsSdkDir, "Lib"))) 205 WindowsSdkDir = null; 206 } 207 208 if (WindowsSdkVersion is null) 209 WindowsSdkVersion = getenv("WindowsSdkVersion"w); 210 211 if (WindowsSdkVersion is null && WindowsSdkDir !is null) 212 { 213 const(char)* rootsDir = FileName.combine(WindowsSdkDir, "Include"); 214 WindowsSdkVersion = findLatestSDKDir(rootsDir, r"um\windows.h"); 215 } 216 } 217 218 /** 219 * detect UCRTSdkDir and UCRTVersion from environment or registry 220 */ 221 void detectUCRT() 222 { 223 if (UCRTSdkDir is null) 224 UCRTSdkDir = getenv("UniversalCRTSdkDir"w); 225 226 if (UCRTSdkDir is null) 227 UCRTSdkDir = GetRegistryString(r"Microsoft\Windows Kits\Installed Roots", "KitsRoot10"w); 228 229 if (UCRTVersion is null) 230 UCRTVersion = getenv("UCRTVersion"w); 231 232 if (UCRTVersion is null && UCRTSdkDir !is null) 233 { 234 const(char)* rootsDir = FileName.combine(UCRTSdkDir, "Lib"); 235 UCRTVersion = findLatestSDKDir(rootsDir, r"ucrt\x86\libucrt.lib"); 236 } 237 } 238 239 /** 240 * detect VSInstallDir from environment or registry 241 */ 242 void detectVSInstallDir() 243 { 244 if (VSInstallDir is null) 245 VSInstallDir = getenv("VSINSTALLDIR"w); 246 247 if (VSInstallDir is null) 248 VSInstallDir = detectVSInstallDirViaCOM(); 249 250 if (VSInstallDir is null) 251 VSInstallDir = GetRegistryString(r"Microsoft\VisualStudio\SxS\VS7", "15.0"w); // VS2017 252 253 if (VSInstallDir is null) 254 { 255 wchar[260] buffer = void; 256 // VS Build Tools 2017 (default installation path) 257 const numWritten = ExpandEnvironmentStringsW(r"%ProgramFiles(x86)%\Microsoft Visual Studio\2017\BuildTools"w.ptr, buffer.ptr, buffer.length); 258 if (numWritten <= buffer.length && exists(buffer.ptr)) 259 VSInstallDir = toNarrowStringz(buffer[0 .. numWritten - 1]).ptr; 260 } 261 262 if (VSInstallDir is null) 263 foreach (ver; supportedPre2017Versions) 264 { 265 VSInstallDir = GetRegistryString(FileName.combine(r"Microsoft\VisualStudio", ver), "InstallDir"w); 266 if (VSInstallDir) 267 break; 268 } 269 } 270 271 /** 272 * detect VCInstallDir from environment or registry 273 */ 274 void detectVCInstallDir() 275 { 276 if (VCInstallDir is null) 277 VCInstallDir = getenv("VCINSTALLDIR"w); 278 279 if (VCInstallDir is null) 280 if (VSInstallDir && FileName.exists(FileName.combine(VSInstallDir, "VC"))) 281 VCInstallDir = FileName.combine(VSInstallDir, "VC"); 282 283 // detect from registry (build tools?) 284 if (VCInstallDir is null) 285 foreach (ver; supportedPre2017Versions) 286 { 287 auto regPath = FileName.buildPath(r"Microsoft\VisualStudio", ver, r"Setup\VC"); 288 VCInstallDir = GetRegistryString(regPath, "ProductDir"w); 289 FileName.free(regPath.ptr); 290 if (VCInstallDir) 291 break; 292 } 293 } 294 295 /** 296 * detect VCToolsInstallDir from environment or registry (only used by VC 2017) 297 */ 298 void detectVCToolsInstallDir() 299 { 300 if (VCToolsInstallDir is null) 301 VCToolsInstallDir = getenv("VCTOOLSINSTALLDIR"w); 302 303 if (VCToolsInstallDir is null && VCInstallDir) 304 { 305 const(char)* defverFile = FileName.combine(VCInstallDir, r"Auxiliary\Build\Microsoft.VCToolsVersion.default.txt"); 306 if (!FileName.exists(defverFile)) // file renamed with VS2019 Preview 2 307 defverFile = FileName.combine(VCInstallDir, r"Auxiliary\Build\Microsoft.VCToolsVersion.v142.default.txt"); 308 if (FileName.exists(defverFile)) 309 { 310 // VS 2017 311 auto readResult = File.read(defverFile); // adds sentinel 0 at end of file 312 if (readResult.success) 313 { 314 auto ver = cast(char*)readResult.buffer.data.ptr; 315 // trim version number 316 while (*ver && isspace(*ver)) 317 ver++; 318 auto p = ver; 319 while (*p == '.' || (*p >= '0' && *p <= '9')) 320 p++; 321 *p = 0; 322 323 if (ver && *ver) 324 VCToolsInstallDir = FileName.buildPath(VCInstallDir.toDString, r"Tools\MSVC", ver.toDString).ptr; 325 } 326 } 327 } 328 } 329 330 public: 331 /** 332 * get Visual C bin folder 333 * Params: 334 * x64 = target architecture (x86 if false) 335 * addpath = [out] path that needs to be added to the PATH environment variable 336 * Returns: 337 * folder containing the VC executables 338 * 339 * Selects the binary path according to the host and target OS, but verifies 340 * that link.exe exists in that folder and falls back to 32-bit host/target if 341 * missing 342 * Note: differences for the linker binaries are small, they all 343 * allow cross compilation 344 */ 345 const(char)* getVCBinDir(bool x64, out const(char)* addpath) const 346 { 347 alias linkExists = returnDirIfContainsFile!"link.exe"; 348 349 const bool isHost64 = isWin64Host(); 350 if (VCToolsInstallDir !is null) 351 { 352 const vcToolsInstallDir = VCToolsInstallDir.toDString; 353 if (isHost64) 354 { 355 if (x64) 356 { 357 if (auto p = linkExists(vcToolsInstallDir, r"bin\HostX64\x64")) 358 return p; 359 // in case of missing linker, prefer other host binaries over other target architecture 360 } 361 else 362 { 363 if (auto p = linkExists(vcToolsInstallDir, r"bin\HostX64\x86")) 364 { 365 addpath = FileName.combine(vcToolsInstallDir, r"bin\HostX64\x64").ptr; 366 return p; 367 } 368 } 369 } 370 if (x64) 371 { 372 if (auto p = linkExists(vcToolsInstallDir, r"bin\HostX86\x64")) 373 { 374 addpath = FileName.combine(vcToolsInstallDir, r"bin\HostX86\x86").ptr; 375 return p; 376 } 377 } 378 if (auto p = linkExists(vcToolsInstallDir, r"bin\HostX86\x86")) 379 return p; 380 } 381 if (VCInstallDir !is null) 382 { 383 const vcInstallDir = VCInstallDir.toDString; 384 if (isHost64) 385 { 386 if (x64) 387 { 388 if (auto p = linkExists(vcInstallDir, r"bin\amd64")) 389 return p; 390 // in case of missing linker, prefer other host binaries over other target architecture 391 } 392 else 393 { 394 if (auto p = linkExists(vcInstallDir, r"bin\amd64_x86")) 395 { 396 addpath = FileName.combine(vcInstallDir, r"bin\amd64").ptr; 397 return p; 398 } 399 } 400 } 401 402 if (VSInstallDir) 403 addpath = FileName.combine(VSInstallDir.toDString, r"Common7\IDE").ptr; 404 else 405 addpath = FileName.combine(vcInstallDir, r"bin").ptr; 406 407 if (x64) 408 if (auto p = linkExists(vcInstallDir, r"x86_amd64")) 409 return p; 410 411 if (auto p = linkExists(vcInstallDir, r"bin\HostX86\x86")) 412 return p; 413 } 414 return null; 415 } 416 417 /** 418 * get Visual C Library folder 419 * Params: 420 * x64 = target architecture (x86 if false) 421 * Returns: 422 * folder containing the the VC runtime libraries 423 */ 424 const(char)* getVCLibDir(bool x64) const 425 { 426 if (VCToolsInstallDir !is null) 427 return FileName.combine(VCToolsInstallDir, x64 ? r"lib\x64" : r"lib\x86"); 428 if (VCInstallDir !is null) 429 return FileName.combine(VCInstallDir, x64 ? r"lib\amd64" : "lib"); 430 return null; 431 } 432 433 /** 434 * get the path to the universal CRT libraries 435 * Params: 436 * x64 = target architecture (x86 if false) 437 * Returns: 438 * folder containing the universal CRT libraries 439 */ 440 const(char)* getUCRTLibPath(bool x64) const 441 { 442 if (UCRTSdkDir && UCRTVersion) 443 return FileName.buildPath(UCRTSdkDir.toDString, "Lib", UCRTVersion.toDString, x64 ? r"ucrt\x64" : r"ucrt\x86").ptr; 444 return null; 445 } 446 447 /** 448 * get the path to the Windows SDK CRT libraries 449 * Params: 450 * x64 = target architecture (x86 if false) 451 * Returns: 452 * folder containing the Windows SDK libraries 453 */ 454 const(char)* getSDKLibPath(bool x64) const 455 { 456 if (WindowsSdkDir) 457 { 458 alias kernel32Exists = returnDirIfContainsFile!"kernel32.lib"; 459 460 const arch = x64 ? "x64" : "x86"; 461 const sdk = FileName.combine(WindowsSdkDir.toDString, "lib"); 462 if (WindowsSdkVersion) 463 { 464 if (auto p = kernel32Exists(sdk, WindowsSdkVersion.toDString, "um", arch)) // SDK 10.0 465 return p; 466 } 467 if (auto p = kernel32Exists(sdk, r"win8\um", arch)) // SDK 8.0 468 return p; 469 if (auto p = kernel32Exists(sdk, r"winv6.3\um", arch)) // SDK 8.1 470 return p; 471 if (x64) 472 { 473 if (auto p = kernel32Exists(sdk, arch)) // SDK 7.1 or earlier 474 return p; 475 } 476 else 477 { 478 if (auto p = kernel32Exists(sdk)) // SDK 7.1 or earlier 479 return p; 480 } 481 } 482 483 // try mingw fallback relative to phobos library folder that's part of LIB 484 if (auto p = FileName.searchPath(getenv("LIB"w), r"mingw\kernel32.lib"[], false)) 485 return FileName.path(p).ptr; 486 487 return null; 488 } 489 490 private: 491 extern(D): 492 493 // iterate through subdirectories named by SDK version in baseDir and return the 494 // one with the largest version that also contains the test file 495 static const(char)* findLatestSDKDir(const(char)* baseDir, string testfile) 496 { 497 const(char)* pattern = FileName.combine(baseDir, "*"); 498 wchar* wpattern = toWStringz(pattern.toDString).ptr; 499 scope(exit) mem.xfree(wpattern); 500 FileName.free(pattern); 501 502 WIN32_FIND_DATAW fileinfo; 503 HANDLE h = FindFirstFileW(wpattern, &fileinfo); 504 if (h == INVALID_HANDLE_VALUE) 505 return null; 506 507 const dBaseDir = baseDir.toDString; 508 509 char* res; 510 do 511 { 512 if (fileinfo.cFileName[0] >= '1' && fileinfo.cFileName[0] <= '9') 513 { 514 char[] name = toNarrowStringz(fileinfo.cFileName.ptr.toDString); 515 // FIXME: proper version strings comparison 516 if ((!res || strcmp(res, name.ptr) < 0) && 517 FileName.exists(FileName.buildPath(dBaseDir, name, testfile))) 518 { 519 if (res) 520 mem.xfree(res); 521 res = name.ptr; 522 } 523 else 524 mem.xfree(name.ptr); 525 } 526 } 527 while(FindNextFileW(h, &fileinfo)); 528 529 if (!FindClose(h)) 530 res = null; 531 return res; 532 } 533 534 pragma(lib, "advapi32.lib"); 535 536 /** 537 * read a string from the 32-bit registry 538 * Params: 539 * softwareKeyPath = path below HKLM\SOFTWARE 540 * valueName = name of the value to read 541 * Returns: 542 * the registry value if it exists and has string type 543 */ 544 const(char)* GetRegistryString(const(char)[] softwareKeyPath, wstring valueName) const 545 { 546 enum x64hive = false; // VS registry entries always in 32-bit hive 547 548 version(Win64) 549 enum prefix = x64hive ? r"SOFTWARE\" : r"SOFTWARE\WOW6432Node\"; 550 else 551 enum prefix = r"SOFTWARE\"; 552 553 char[260] regPath = void; 554 assert(prefix.length + softwareKeyPath.length < regPath.length); 555 556 regPath[0 .. prefix.length] = prefix; 557 regPath[prefix.length .. prefix.length + softwareKeyPath.length] = softwareKeyPath; 558 regPath[prefix.length + softwareKeyPath.length] = 0; 559 560 enum KEY_WOW64_64KEY = 0x000100; // not defined in core.sys.windows.winnt due to restrictive version 561 enum KEY_WOW64_32KEY = 0x000200; 562 HKEY key; 563 LONG lRes = RegOpenKeyExA(HKEY_LOCAL_MACHINE, regPath.ptr, (x64hive ? KEY_WOW64_64KEY : KEY_WOW64_32KEY), KEY_READ, &key); 564 if (FAILED(lRes)) 565 return null; 566 scope(exit) RegCloseKey(key); 567 568 wchar[260] buf = void; 569 DWORD size = buf.sizeof; 570 DWORD type; 571 int hr = RegQueryValueExW(key, valueName.ptr, null, &type, cast(ubyte*) buf.ptr, &size); 572 if (type != REG_SZ || size == 0) 573 return null; 574 575 wchar* wideValue = buf.ptr; 576 scope(exit) wideValue != buf.ptr && mem.xfree(wideValue); 577 if (hr == ERROR_MORE_DATA) 578 { 579 wideValue = cast(wchar*) mem.xmalloc_noscan(size); 580 hr = RegQueryValueExW(key, valueName.ptr, null, &type, cast(ubyte*) wideValue, &size); 581 } 582 if (hr != 0) 583 return null; 584 585 auto wideLength = size / wchar.sizeof; 586 if (wideValue[wideLength - 1] == 0) // may or may not be null-terminated 587 --wideLength; 588 return toNarrowStringz(wideValue[0 .. wideLength]).ptr; 589 } 590 591 /*** 592 * get architecture of host OS 593 */ 594 static bool isWin64Host() 595 { 596 version (Win64) 597 { 598 return true; 599 } 600 else 601 { 602 // running as a 32-bit process on a 64-bit host? 603 alias fnIsWow64Process = extern(Windows) BOOL function(HANDLE, PBOOL); 604 __gshared fnIsWow64Process pIsWow64Process; 605 606 if (!pIsWow64Process) 607 { 608 //IsWow64Process is not available on all supported versions of Windows. 609 pIsWow64Process = cast(fnIsWow64Process) GetProcAddress(GetModuleHandleA("kernel32"), "IsWow64Process"); 610 if (!pIsWow64Process) 611 return false; 612 } 613 BOOL bIsWow64 = FALSE; 614 if (!pIsWow64Process(GetCurrentProcess(), &bIsWow64)) 615 return false; 616 617 return bIsWow64 != 0; 618 } 619 } 620 } 621 622 private: 623 624 inout(wchar)[] toDString(inout(wchar)* s) 625 { 626 return s ? s[0 .. wcslen(s)] : null; 627 } 628 629 extern(C) wchar* _wgetenv(const(wchar)* name); 630 631 char* getenv(wstring name) 632 { 633 if (auto wide = _wgetenv(name.ptr)) 634 return toNarrowStringz(wide.toDString).ptr; 635 return null; 636 } 637 638 const(char)* returnDirIfContainsFile(string fileName)(scope const(char)[][] dirs...) 639 { 640 auto dirPath = FileName.buildPath(dirs); 641 642 auto filePath = FileName.combine(dirPath, fileName); 643 scope(exit) FileName.free(filePath.ptr); 644 645 if (FileName.exists(filePath)) 646 return dirPath.ptr; 647 648 FileName.free(dirPath.ptr); 649 return null; 650 } 651 652 extern (C) int _waccess(const(wchar)* _FileName, int _AccessMode); 653 654 bool exists(const(wchar)* path) 655 { 656 return _waccess(path, 0) == 0; 657 } 658 659 /////////////////////////////////////////////////////////////////////// 660 // COM interfaces to find VS2017+ installations 661 import core.sys.windows.com; 662 import core.sys.windows.wtypes : BSTR; 663 import core.sys.windows.oleauto : SysFreeString, SysStringLen; 664 665 pragma(lib, "ole32.lib"); 666 pragma(lib, "oleaut32.lib"); 667 668 interface ISetupInstance : IUnknown 669 { 670 // static const GUID iid = uuid("B41463C3-8866-43B5-BC33-2B0676F7F42E"); 671 static const GUID iid = { 0xB41463C3, 0x8866, 0x43B5, [ 0xBC, 0x33, 0x2B, 0x06, 0x76, 0xF7, 0xF4, 0x2E ] }; 672 673 int GetInstanceId(BSTR* pbstrInstanceId); 674 int GetInstallDate(LPFILETIME pInstallDate); 675 int GetInstallationName(BSTR* pbstrInstallationName); 676 int GetInstallationPath(BSTR* pbstrInstallationPath); 677 int GetInstallationVersion(BSTR* pbstrInstallationVersion); 678 int GetDisplayName(LCID lcid, BSTR* pbstrDisplayName); 679 int GetDescription(LCID lcid, BSTR* pbstrDescription); 680 int ResolvePath(LPCOLESTR pwszRelativePath, BSTR* pbstrAbsolutePath); 681 } 682 683 interface IEnumSetupInstances : IUnknown 684 { 685 // static const GUID iid = uuid("6380BCFF-41D3-4B2E-8B2E-BF8A6810C848"); 686 687 int Next(ULONG celt, ISetupInstance* rgelt, ULONG* pceltFetched); 688 int Skip(ULONG celt); 689 int Reset(); 690 int Clone(IEnumSetupInstances* ppenum); 691 } 692 693 interface ISetupConfiguration : IUnknown 694 { 695 // static const GUID iid = uuid("42843719-DB4C-46C2-8E7C-64F1816EFD5B"); 696 static const GUID iid = { 0x42843719, 0xDB4C, 0x46C2, [ 0x8E, 0x7C, 0x64, 0xF1, 0x81, 0x6E, 0xFD, 0x5B ] }; 697 698 int EnumInstances(IEnumSetupInstances* ppEnumInstances) ; 699 int GetInstanceForCurrentProcess(ISetupInstance* ppInstance); 700 int GetInstanceForPath(LPCWSTR wzPath, ISetupInstance* ppInstance); 701 } 702 703 const GUID iid_SetupConfiguration = { 0x177F0C4A, 0x1CD3, 0x4DE7, [ 0xA3, 0x2C, 0x71, 0xDB, 0xBB, 0x9F, 0xA3, 0x6D ] }; 704 705 const(char)* detectVSInstallDirViaCOM() 706 { 707 CoInitialize(null); 708 scope(exit) CoUninitialize(); 709 710 ISetupConfiguration setup; 711 IEnumSetupInstances instances; 712 ISetupInstance instance; 713 DWORD fetched; 714 715 HRESULT hr = CoCreateInstance(&iid_SetupConfiguration, null, CLSCTX_ALL, &ISetupConfiguration.iid, cast(void**) &setup); 716 if (hr != S_OK || !setup) 717 return null; 718 scope(exit) setup.Release(); 719 720 if (setup.EnumInstances(&instances) != S_OK) 721 return null; 722 scope(exit) instances.Release(); 723 724 static struct WrappedBString 725 { 726 BSTR ptr; 727 this(this) @disable; 728 ~this() { SysFreeString(ptr); } 729 bool opCast(T : bool)() const { return ptr !is null; } 730 size_t length() { return SysStringLen(ptr); } 731 void moveTo(ref WrappedBString other) 732 { 733 SysFreeString(other.ptr); 734 other.ptr = ptr; 735 ptr = null; 736 } 737 } 738 739 WrappedBString versionString, installDir; 740 741 while (instances.Next(1, &instance, &fetched) == S_OK && fetched) 742 { 743 WrappedBString thisVersionString, thisInstallDir; 744 if (instance.GetInstallationVersion(&thisVersionString.ptr) != S_OK || 745 instance.GetInstallationPath(&thisInstallDir.ptr) != S_OK) 746 continue; 747 748 // FIXME: proper version strings comparison 749 // existing versions are 15.0 to 16.5 (May 2020), problems expected in distant future 750 if (versionString && wcscmp(thisVersionString.ptr, versionString.ptr) <= 0) 751 continue; // not a newer version, skip 752 753 const installDirLength = thisInstallDir.length; 754 const vcInstallDirLength = installDirLength + 4; 755 auto vcInstallDir = (cast(wchar*) mem.xmalloc_noscan(vcInstallDirLength * wchar.sizeof))[0 .. vcInstallDirLength]; 756 scope(exit) mem.xfree(vcInstallDir.ptr); 757 vcInstallDir[0 .. installDirLength] = thisInstallDir.ptr[0 .. installDirLength]; 758 vcInstallDir[installDirLength .. $] = "\\VC\0"w; 759 if (!exists(vcInstallDir.ptr)) 760 continue; // Visual C++ not included, skip 761 762 thisVersionString.moveTo(versionString); 763 thisInstallDir.moveTo(installDir); 764 } 765 766 return !installDir ? null : toNarrowStringz(installDir.ptr[0 .. installDir.length]).ptr; 767 }