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.process : environment;
33 
34     const dubArch = environment.get("DUB_ARCH");
35     if (dubArch) {
36         if (dubArch == "x86" || dubArch == "x86_mscoff") {
37             return [ "x86" ];
38         }
39         if (dubArch == "x86_64") {
40             return [ "amd64" ];
41         }
42     }
43     return null;
44 }
45 
46 /// Compute the environment set by the passed vcvarsBat, with options.
47 /// This function actually execute the script and dump the environment to
48 /// the result AA.
49 /// Returns: The environment AA, containing the current environment, and
50 /// the variables set by vcvarsall. (Path for example will also contain the current dirs)
51 string[string] msvcEnvironment(in string vcvarsBat, in string[] options)
52 {
53     import dbuild.util : tempFilePath;
54     import std.algorithm : canFind;
55     import std.exception : enforce;
56     import std.file : remove;
57     import std.process : Config, pipe, spawnShell, wait;
58     import std.stdio : File, stdin, stderr;
59 
60     enum startMark = "__dbuild_start_mark__";
61     enum endMark = "__dbuild_end_mark__";
62 
63     const scriptPath = tempFilePath("vcenv-%s.bat");
64     // TODO: what if no space in vcvarsBat?
65     auto invokeLine = "call \"" ~ vcvarsBat ~ "\"";
66     foreach (o; options) invokeLine ~= " " ~ o;
67 
68     {
69         auto script = File(scriptPath, "w");
70 
71         script.writeln("@echo off");
72         script.writeln(invokeLine);
73         script.writeln("echo " ~ startMark);
74         script.writeln("set"); // dump environment variables
75         script.writeln("echo " ~ endMark);
76     }
77 
78     scope(exit) remove(scriptPath);
79 
80     string[string] env;
81 
82     auto p = pipe();
83     auto childIn = stdin;
84     auto childOut = p.writeEnd;
85     auto childErr = stderr; //File("NUL", "w");
86 	// Do not use retainStdout here as the output reading loop would hang forever.
87     const config = Config.none;
88     auto pid = spawnShell(scriptPath, childIn, childOut, childErr, null, config);
89     bool withinMarks;
90     foreach (l; p.readEnd.byLine) {
91         if (!withinMarks && l.canFind(startMark)) {
92             withinMarks = true;
93         }
94         else if (withinMarks && l.canFind(endMark)) {
95             withinMarks = false;
96         }
97         else if (withinMarks) {
98             import std.algorithm : findSplit;
99             import std..string : strip;
100 
101             auto splt = l.strip().idup.findSplit("=");
102             if (splt) {
103                 env[splt[0]] = splt[2];
104             }
105         }
106     }
107     const exitCode = pid.wait();
108     enforce(exitCode == 0, "detection of MSVC environment failed");
109 
110     return env;
111 }
112 
113 private @property string programFilesDir()
114 {
115     import std.process : environment;
116 
117     version(Win64) {
118         string var = "ProgramFiles(x86)";
119     }
120     else {
121         string var = "ProgramFiles";
122     }
123     return environment[var];
124 }
125 
126 private bool detectBuildTools2017(out MsvcInstall install)
127 {
128     import std.file : exists;
129     import std.path : buildPath;
130 
131     const pfd = programFilesDir;
132     install.vcvarsBat = buildPath(pfd, "Microsoft Visual Studio", "2017", "BuildTools",
133             "VC", "Auxiliary", "Build", "vcvarsall.bat");
134 
135     if (exists(install.vcvarsBat)) {
136         install.ver = [15, 0];
137         return true;
138     }
139     else {
140         return false;
141     }
142 }