1 module dbuild.buildsystem; 2 3 import dbuild.build; 4 import std.digest.md : MD5; 5 6 /// context of a build 7 struct BuildContext 8 { 9 BuildDirs dirs; 10 BuildType type; 11 bool quiet; 12 } 13 14 /// simple interface to a build system (such as CMake, or Autotools) 15 interface BuildSystem 16 { 17 /// Feeds the digest in a way that makes a unique build identifier. 18 void feedBuildId(ref MD5 digest); 19 /// Issue the commands to build the source code to the requested binaries. 20 void issueCmds(BuildContext ctx); 21 } 22 23 /// Helper to create a CMAKE build system, with generator and options. 24 /// Option should not deal with install dir or build type, which 25 /// are set from the build context. 26 /// The build system will issue 3 commands: configure, build and install. 27 struct CMake 28 { 29 static CMake create(string generator=null, string[] options=null) 30 { 31 import dbuild.util : searchExecutable; 32 import std.exception : enforce; 33 34 enforce(searchExecutable("cmake"), "Could not find CMake!"); 35 CMake cmake; 36 cmake._gen = generator; 37 cmake._options = options; 38 if (generator == "Ninja") { 39 cmake._env["NINJA_STATUS"] = "[%r %f/%t %p %o/s] "; 40 } 41 return cmake; 42 } 43 44 CMake withEnv(in string[string] env) 45 { 46 foreach(k, v; env) { 47 _env[k] = v; 48 } 49 return this; 50 } 51 52 version(Windows) 53 CMake withMsvcSetup(int minVer=0, string[] vcvarsOptions=null) 54 { 55 import std.exception : enforce; 56 57 enforce(tryMsvcSetup(minVer, vcvarsOptions), "Could not find suitable MSVC install"); 58 return this; 59 } 60 61 version(Windows) 62 bool tryMsvcSetup(int minVer=0, string[] vcvarsOptions=null) 63 { 64 import dbuild.msvc : detectMsvcInstalls, msvcEnvironment; 65 import dbuild.util : searchExecutable; 66 67 if (searchExecutable("cl")) return true; // TODO: what version 68 69 auto installs = detectMsvcInstalls(); 70 if (!installs.length || installs[0].ver[0] < minVer) { 71 return false; 72 } 73 auto oldEnv = _env; 74 _env = msvcEnvironment(installs[0].vcvarsBat, vcvarsOptions); 75 foreach(k, v; oldEnv) { 76 _env[k] = v; 77 } 78 _additionalHashFeed = installs[0].vcvarsBat ~ vcvarsOptions; 79 _msvc = true; 80 81 return true; 82 } 83 84 private string findDefaultGenerator() 85 { 86 import dbuild.util : searchExecutable; 87 88 version(Windows) 89 { 90 import dbuild.msvc : dubArchOptions; 91 92 if (tryMsvcSetup(0, dubArchOptions())) { 93 if (hasNinja) { 94 return "Ninja"; 95 } 96 else { 97 return "NMake Makefiles"; 98 } 99 } 100 } 101 if (hasNinja) { 102 return "Ninja"; 103 } 104 version(Windows) { 105 if (hasMingw32Make) { 106 return "MinGW Makefiles"; 107 } 108 else if (hasMake) { // under cygwin? 109 return "MSYS Makefiles"; 110 } 111 } 112 else { 113 if (hasMake) { 114 return "Unix Makefiles"; 115 } 116 } 117 return null; 118 } 119 120 /// get the result BuildSystem 121 BuildSystem buildSystem() 122 { 123 import std.exception : enforce; 124 125 if (!_gen) { 126 _gen = enforce(findDefaultGenerator(), "Could not find suitable CMake generator"); 127 } 128 129 version(Windows) { 130 if (_gen == "Ninja" && _msvc) { 131 _env["CC"] = "cl"; 132 _env["CXX"] = "cl"; 133 } 134 } 135 136 return new CMakeBuildSystem(_gen, _options, _env, _additionalHashFeed); 137 } 138 139 private string _gen; 140 private string[] _options; 141 private string[string] _env; 142 private string[] _additionalHashFeed; 143 version(Windows) private bool _msvc; 144 } 145 146 147 private @property bool hasNinja() 148 { 149 import dbuild.util : searchExecutable; 150 import std.concurrency : initOnce; 151 152 static __gshared bool has; 153 return initOnce!has(searchExecutable("ninja") !is null); 154 } 155 156 private @property bool hasGcc() 157 { 158 import dbuild.util : searchExecutable; 159 import std.concurrency : initOnce; 160 161 static __gshared bool has; 162 return initOnce!has(searchExecutable("gcc") !is null); 163 } 164 165 private @property bool hasClang() 166 { 167 import dbuild.util : searchExecutable; 168 import std.concurrency : initOnce; 169 170 static __gshared bool has; 171 return initOnce!has(searchExecutable("clang") !is null); 172 } 173 174 private @property bool hasMake() 175 { 176 import dbuild.util : searchExecutable; 177 import std.concurrency : initOnce; 178 179 static __gshared bool has; 180 return initOnce!has(searchExecutable("make") !is null); 181 } 182 183 private @property bool hasMingw32Make() 184 { 185 import dbuild.util : searchExecutable; 186 import std.concurrency : initOnce; 187 188 static __gshared bool has; 189 return initOnce!has(searchExecutable("mingw32-make") !is null); 190 } 191 192 193 private class CMakeBuildSystem : BuildSystem 194 { 195 const(string) generator; 196 const(string[]) options; 197 string[string] env; 198 const(string[]) additionalHashFeed; 199 200 this(in string generator, in string[] options, string[string] env, string[] additionalHashFeed) 201 { 202 this.generator = generator; 203 this.options = options; 204 this.env = env; 205 this.additionalHashFeed = additionalHashFeed; 206 } 207 208 override void feedBuildId(ref MD5 digest) 209 { 210 import dbuild.util : feedDigestData; 211 212 feedDigestData(digest, generator); 213 feedDigestData(digest, options); 214 if (additionalHashFeed.length) feedDigestData(digest, additionalHashFeed); 215 } 216 217 override void issueCmds(BuildContext ctx) 218 { 219 import dbuild.util : runCommands; 220 221 const string buildType = ctx.type == BuildType.deb ? "Debug" : "Release"; 222 223 const string[] configCmd = [ 224 "cmake", 225 "-G", generator, 226 "-DCMAKE_BUILD_TYPE="~buildType, 227 "-DCMAKE_INSTALL_PREFIX="~ctx.dirs.installDir 228 ] ~ options ~ [ 229 ctx.dirs.srcDir 230 ]; 231 const string[] buildCmd = [ 232 "cmake", "--build", "." 233 ]; 234 const string[] installCmd = [ 235 "cmake", "--build", ".", "--target", "install" 236 ]; 237 238 runCommands( 239 [ configCmd, buildCmd, installCmd ], ctx.dirs.buildDir, ctx.quiet, env 240 ); 241 } 242 }