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 return cmake; 39 } 40 41 version(Windows) 42 CMake withMsvcSetup(int minVer=0, string[] vcvarsOptions=null) 43 { 44 import std.exception : enforce; 45 46 enforce(tryMsvcSetup(minVer, vcvarsOptions), "Could not find suitable MSVC install"); 47 return this; 48 } 49 50 version(Windows) 51 bool tryMsvcSetup(int minVer=0, string[] vcvarsOptions=null) 52 { 53 import dbuild.msvc : detectMsvcInstalls, msvcEnvironment; 54 import dbuild.util : searchExecutable; 55 56 if (searchExecutable("cl")) return true; // TODO: what version 57 58 auto installs = detectMsvcInstalls(); 59 if (!installs.length || installs[0].ver[0] < minVer) { 60 return false; 61 } 62 _env = msvcEnvironment(installs[0].vcvarsBat, vcvarsOptions); 63 _additionalHashFeed = installs[0].vcvarsBat ~ vcvarsOptions; 64 _msvc = true; 65 66 return true; 67 } 68 69 private string findDefaultGenerator() 70 { 71 import dbuild.util : searchExecutable; 72 73 version(Windows) 74 { 75 import dbuild.msvc : dubArchOptions; 76 77 if (tryMsvcSetup(0, dubArchOptions())) { 78 if (hasNinja) { 79 return "Ninja"; 80 } 81 else { 82 return "NMake Makefiles"; 83 } 84 } 85 } 86 if (hasNinja) { 87 return "Ninja"; 88 } 89 version(Windows) { 90 if (hasMingw32Make) { 91 return "MinGW Makefiles"; 92 } 93 else if (hasMake) { // under cygwin? 94 return "MSYS Makefiles"; 95 } 96 } 97 else { 98 if (hasMake) { 99 return "Unix Makefiles"; 100 } 101 } 102 return null; 103 } 104 105 /// get the result BuildSystem 106 BuildSystem buildSystem() 107 { 108 import std.exception : enforce; 109 110 if (!_gen) { 111 _gen = enforce(findDefaultGenerator(), "Could not find suitable CMake generator"); 112 } 113 114 version(Windows) { 115 if (_gen == "Ninja" && _msvc) { 116 _env["CC"] = "cl"; 117 _env["CXX"] = "cl"; 118 } 119 } 120 121 return new CMakeBuildSystem(_gen, _options, _env, _additionalHashFeed); 122 } 123 124 private string _gen; 125 private string[] _options; 126 private string[string] _env; 127 private string[] _additionalHashFeed; 128 version(Windows) private bool _msvc; 129 } 130 131 132 private @property bool hasNinja() 133 { 134 import dbuild.util : searchExecutable; 135 import std.concurrency : initOnce; 136 137 static __gshared bool has; 138 return initOnce!has(searchExecutable("ninja") !is null); 139 } 140 141 private @property bool hasGcc() 142 { 143 import dbuild.util : searchExecutable; 144 import std.concurrency : initOnce; 145 146 static __gshared bool has; 147 return initOnce!has(searchExecutable("gcc") !is null); 148 } 149 150 private @property bool hasClang() 151 { 152 import dbuild.util : searchExecutable; 153 import std.concurrency : initOnce; 154 155 static __gshared bool has; 156 return initOnce!has(searchExecutable("clang") !is null); 157 } 158 159 private @property bool hasMake() 160 { 161 import dbuild.util : searchExecutable; 162 import std.concurrency : initOnce; 163 164 static __gshared bool has; 165 return initOnce!has(searchExecutable("make") !is null); 166 } 167 168 private @property bool hasMingw32Make() 169 { 170 import dbuild.util : searchExecutable; 171 import std.concurrency : initOnce; 172 173 static __gshared bool has; 174 return initOnce!has(searchExecutable("mingw32-make") !is null); 175 } 176 177 178 private class CMakeBuildSystem : BuildSystem 179 { 180 const(string) generator; 181 const(string[]) options; 182 string[string] env; 183 const(string[]) additionalHashFeed; 184 185 this(in string generator, in string[] options, string[string] env, string[] additionalHashFeed) 186 { 187 this.generator = generator; 188 this.options = options; 189 this.env = env; 190 this.additionalHashFeed = additionalHashFeed; 191 } 192 193 override void feedBuildId(ref MD5 digest) 194 { 195 import dbuild.util : feedDigestData; 196 197 feedDigestData(digest, generator); 198 feedDigestData(digest, options); 199 if (additionalHashFeed.length) feedDigestData(digest, additionalHashFeed); 200 } 201 202 override void issueCmds(BuildContext ctx) 203 { 204 import dbuild.util : runCommands; 205 206 const string buildType = ctx.type == BuildType.deb ? "Debug" : "Release"; 207 208 const string[] configCmd = [ 209 "cmake", 210 "-G", generator, 211 "-DCMAKE_BUILD_TYPE="~buildType, 212 "-DCMAKE_INSTALL_PREFIX="~ctx.dirs.installDir 213 ] ~ options ~ [ 214 ctx.dirs.srcDir 215 ]; 216 const string[] buildCmd = [ 217 "cmake", "--build", "." 218 ]; 219 const string[] installCmd = [ 220 "cmake", "--build", ".", "--target", "install" 221 ]; 222 223 runCommands( 224 [ configCmd, buildCmd, installCmd ], ctx.dirs.buildDir, ctx.quiet, env 225 ); 226 } 227 }