1 module dbuild.msvc;
2 
3 version(Windows):
4 
5 /// Represent a MSVC installation
6 struct MsvcInstall
7 {
8     /// path to the environment configuration script
9     string vcvarsBat;
10     /// version [major, minor] of the MSVC installation
11     ushort[2] ver;
12 }
13 
14 /// Probe the system for installed MSVC.
15 /// Will return the installs sorted with high versions first.
16 MsvcInstall[] detectMsvcInstalls()
17 {
18     MsvcInstall[] res;
19     MsvcInstall install;
20     if (detectBuildTools2017(install)) {
21         res ~= install;
22     }
23     // TODO: other installs through registry
24     // TODO: sort result, higher versions first
25     return res;
26 }
27 
28 /// Guess options for vcvarsall script from the DUB_ARCH environment variable.
29 /// Returns: the guessed options, or null if the right options could not be guessed.
30 string[] dubArchOptions()
31 {
32     import std.algorithm : canFind;
33     import std.process : environment;
34 
35     const dubArch = environment.get("DUB_ARCH");
36     if (dubArch) {
37         // some times can be "x86 x86_mscoff"
38         if (dubArch.canFind("x86_64")) {
39             return [ "amd64" ];
40         }
41         else if (dubArch.canFind("x86")) {
42             // includes x86_mscoff
43             return [ "x86" ];
44         }
45     }
46     return null;
47 }
48 
49 /// Compute the environment set by the passed vcvarsBat, with options.
50 /// This function actually execute the script and dump the environment to
51 /// the result AA.
52 /// Returns: The environment AA, containing the current environment, and
53 /// the variables set by vcvarsall. (Path for example will also contain the current dirs)
54 string[string] msvcEnvironment(in string vcvarsBat, in string[] options)
55 {
56     import dbuild.util : tempFilePath;
57     import std.algorithm : canFind;
58     import std.exception : enforce;
59     import std.file : remove;
60     import std.process : Config, pipe, spawnShell, wait;
61     import std.stdio : File, stdin, stderr;
62 
63     enum startMark = "__dbuild_start_mark__";
64     enum endMark = "__dbuild_end_mark__";
65 
66     const scriptPath = tempFilePath("vcenv-%s.bat");
67     // TODO: what if no space in vcvarsBat?
68     auto invokeLine = "call \"" ~ vcvarsBat ~ "\"";
69     foreach (o; options) invokeLine ~= " " ~ o;
70 
71     {
72         auto script = File(scriptPath, "w");
73 
74         script.writeln("@echo off");
75         script.writeln(invokeLine);
76         script.writeln("echo " ~ startMark);
77         script.writeln("set"); // dump environment variables
78         script.writeln("echo " ~ endMark);
79     }
80 
81     scope(exit) remove(scriptPath);
82 
83     string[string] env;
84 
85     auto p = pipe();
86     auto childIn = stdin;
87     auto childOut = p.writeEnd;
88     auto childErr = stderr; //File("NUL", "w");
89 	// Do not use retainStdout here as the output reading loop would hang forever.
90     const config = Config.none;
91     auto pid = spawnShell(scriptPath, childIn, childOut, childErr, null, config);
92     bool withinMarks;
93     foreach (l; p.readEnd.byLine) {
94         if (!withinMarks && l.canFind(startMark)) {
95             withinMarks = true;
96         }
97         else if (withinMarks && l.canFind(endMark)) {
98             withinMarks = false;
99         }
100         else if (withinMarks) {
101             import std.algorithm : findSplit;
102             import std.string : strip;
103 
104             auto splt = l.strip().idup.findSplit("=");
105             if (splt) {
106                 env[splt[0]] = splt[2];
107             }
108         }
109     }
110     const exitCode = pid.wait();
111     enforce(exitCode == 0, "detection of MSVC environment failed");
112 
113     return env;
114 }
115 
116 private @property string programFilesDir()
117 {
118     import std.process : environment;
119 
120     version(Win64) {
121         string var = "ProgramFiles(x86)";
122     }
123     else {
124         string var = "ProgramFiles";
125     }
126     return environment[var];
127 }
128 
129 private bool detectBuildTools2017(out MsvcInstall install)
130 {
131     import std.file : exists;
132     import std.path : buildPath;
133 
134     const pfd = programFilesDir;
135     install.vcvarsBat = buildPath(pfd, "Microsoft Visual Studio", "2017", "BuildTools",
136             "VC", "Auxiliary", "Build", "vcvarsall.bat");
137 
138     if (exists(install.vcvarsBat)) {
139         install.ver = [15, 0];
140         return true;
141     }
142     else {
143         return false;
144     }
145 }