1 /**
2  * When compiling on Windows with the Microsoft toolchain, try to detect the Visual Studio setup.
3  *
4  * Copyright:   Copyright (C) 1999-2020 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 }