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 }