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 }