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