1 /** 2 The main entry point for the reggae tool. Its tasks are: 3 $(UL 4 $(LI Verify that a $(D reggafile.d) exists in the selected directory) 5 $(LI Generate a $(D reggaefile.d) for dub projects) 6 $(LI Write out the reggae library files and $(D config.d)) 7 $(LI Compile the build description with the reggae library files to produce $(D buildgen)) 8 $(LI Produce $(D dcompile), a binary to call the D compiler to obtain dependencies during compilation) 9 $(LI Call the produced $(D buildgen) binary) 10 ) 11 */ 12 13 14 module reggae.reggae; 15 16 import std.stdio; 17 import std.process: execute, environment; 18 import std.array: array, join, empty, split; 19 import std.path: absolutePath, buildPath, relativePath; 20 import std.typetuple; 21 import std.file; 22 import std.conv: text; 23 import std.exception: enforce; 24 import std.conv: to; 25 import std.algorithm; 26 27 import reggae.options; 28 import reggae.ctaa; 29 import reggae.types; 30 31 32 version(minimal) { 33 //empty stubs for minimal version of reggae 34 void maybeCreateReggaefile(T...)(T) {} 35 void writeDubConfig(T...)(T) {} 36 } else { 37 import reggae.dub.interop; 38 } 39 40 mixin template reggaeGen(targets...) { 41 mixin buildImpl!targets; 42 mixin ReggaeMain; 43 } 44 45 mixin template ReggaeMain() { 46 import reggae.options: getOptions; 47 import std.stdio: stderr; 48 49 int main(string[] args) { 50 try { 51 run(getOptions(args)); 52 } catch(Exception ex) { 53 stderr.writeln(ex.msg); 54 return 1; 55 } 56 57 return 0; 58 } 59 } 60 61 void run(in Options options) { 62 if(options.help) return; 63 enforce(options.projectPath != "", "A project path must be specified"); 64 65 if(options.reggaeFileLanguage != BuildLanguage.D) { 66 immutable haveToReturn = jsonBuild(options, options.reggaeFileLanguage); 67 if(haveToReturn) return; 68 } 69 70 maybeCreateReggaefile(options); 71 createBuild(options); 72 } 73 74 //get JSON description of the build from a scripting language 75 //return true if no D files are present 76 bool jsonBuild(in Options options, in BuildLanguage language) { 77 enforce(options.backend != Backend.binary, "Binary backend not supported via JSON"); 78 79 immutable jsonOutput = getJsonOutput(options, language); 80 81 import reggae.json_build; 82 import reggae.buildgen; 83 import reggae.rules.common: Language; 84 85 const build = jsonToBuild(options.projectPath, jsonOutput); 86 generateBuild(build, options); 87 88 //true -> exit early 89 return !build.targets.canFind!(a => a.getLanguage == Language.D); 90 } 91 92 private string getJsonOutput(in Options options, in BuildLanguage language) @safe { 93 const args = getJsonOutputArgs(options, language); 94 const nodePaths = environment.get("NODE_PATH", "").split(":"); 95 const luaPaths = environment.get("LUA_PATH", "").split(";"); 96 auto env = ["NODE_PATH": (nodePaths ~ options.projectPath).join(":"), 97 "LUA_PATH": (luaPaths ~ buildPath(options.projectPath, "?.lua")).join(";")]; 98 immutable res = execute(args, env); 99 enforce(res.status == 0, text("Could not execute ", args.join(" "), ":\n", res.output)); 100 return res.output; 101 } 102 103 private string[] getJsonOutputArgs(in Options options, in BuildLanguage language) @safe pure nothrow { 104 final switch(language) { 105 106 case BuildLanguage.D: 107 assert(0, "Cannot obtain JSON build for builds written in D"); 108 109 case BuildLanguage.Python: 110 return ["python", "-m", "reggae.json_build", options.projectPath]; 111 112 case BuildLanguage.Ruby: 113 return ["ruby", "-S", "-I" ~ options.projectPath, "reggae_json_build.rb"]; 114 115 case BuildLanguage.Lua: 116 return ["reggae_json_build.lua"]; 117 118 case BuildLanguage.JavaScript: 119 return ["reggae_json_build.js"]; 120 } 121 } 122 123 enum coreFiles = [ 124 "options.d", 125 "buildgen_main.d", "buildgen.d", 126 "build.d", 127 "backend/package.d", "backend/binary.d", 128 "package.d", "range.d", "reflect.d", 129 "dependencies.d", "types.d", "dcompile.d", 130 "ctaa.d", "sorting.d", 131 "rules/package.d", 132 "rules/common.d", 133 "rules/d.d", 134 "rules/c_and_cpp.d", 135 "core/package.d", "core/rules/package.d", 136 ]; 137 enum otherFiles = [ 138 "backend/ninja.d", "backend/make.d", "backend/tup.d", 139 "dub/info.d", "rules/dub.d", 140 ]; 141 142 //all files that need to be written out and compiled 143 private string[] fileNames() @safe pure nothrow { 144 version(minimal) return coreFiles; 145 else return coreFiles ~ otherFiles; 146 } 147 148 149 private void createBuild(in Options options) { 150 151 enforce(options.reggaeFilePath.exists, text("Could not find ", options.reggaeFilePath)); 152 153 //write out the library source files to be compiled with the user's 154 //build description 155 writeSrcFiles(options); 156 157 //compile the binaries (the build generator and dcompile) 158 immutable buildGenName = compileBinaries(options); 159 160 //binary backend has no build generator, it _is_ the build 161 if(options.backend == Backend.binary) return; 162 163 //only got here to build .dcompile 164 if(options.isScriptBuild) return; 165 166 //actually run the build generator 167 writeln("[Reggae] Running the created binary to generate the build"); 168 immutable retRunBuildgen = execute([buildPath(".", buildGenName)]); 169 enforce(retRunBuildgen.status == 0, 170 text("Couldn't execute the produced ", buildGenName, " binary:\n", retRunBuildgen.output)); 171 172 writeln(retRunBuildgen.output); 173 } 174 175 176 private immutable hiddenDir = ".reggae"; 177 178 private auto compileBinaries(in Options options) { 179 180 immutable buildGenName = getBuildGenName(options); 181 const compileBuildGenCmd = getCompileBuildGenCmd(options); 182 183 immutable dcompileName = buildPath(hiddenDir, "dcompile"); 184 immutable dcompileCmd = ["dmd", 185 "-I.reggae/src", 186 "-of" ~ dcompileName, 187 reggaeSrcFileName("dcompile.d"), 188 reggaeSrcFileName("dependencies.d")]; 189 190 191 static struct Binary { string name; const(string)[] cmd; } 192 193 auto binaries = options.isScriptBuild 194 ? [Binary(dcompileName, dcompileCmd)] 195 : [Binary(buildGenName, compileBuildGenCmd), Binary(dcompileName, dcompileCmd)]; 196 foreach(bin; binaries) writeln("[Reggae] Compiling metabuild binary ", bin.name); 197 198 import std.parallelism; 199 200 foreach(bin; binaries.parallel) { 201 immutable res = execute(bin.cmd); 202 enforce(res.status == 0, text("Couldn't execute ", bin.cmd.join(" "), ":\n", res.output, 203 "\n", "bin.name: ", bin.name, ", bin.cmd: ", bin.cmd.join(" "))); 204 } 205 206 return buildGenName; 207 } 208 209 const (string[]) getCompileBuildGenCmd(in Options options) @safe { 210 const reggaeSrcs = ("config.d" ~ fileNames). 211 filter!(a => a != "dcompile.d"). 212 map!(a => a.reggaeSrcFileName).array; 213 214 immutable buildBinFlags = options.backend == Backend.binary 215 ? ["-O"] 216 : []; 217 immutable commonBefore = ["dmd", 218 "-I" ~ options.projectPath, 219 "-I" ~ buildPath(hiddenDir, "src"), 220 "-g", "-debug", 221 "-of" ~ getBuildGenName(options)]; 222 const commonAfter = buildBinFlags ~ 223 options.reggaeFilePath ~ reggaeSrcs; 224 version(minimal) return commonBefore ~ "-version=minimal" ~ commonAfter; 225 else return commonBefore ~ commonAfter; 226 } 227 228 string getBuildGenName(in Options options) @safe pure nothrow { 229 return options.backend == Backend.binary ? "build" : buildPath(hiddenDir, "buildgen"); 230 } 231 232 immutable reggaeSrcDirName = buildPath(hiddenDir, "src", "reggae"); 233 234 private string filesTupleString() @safe pure nothrow { 235 return "TypeTuple!(" ~ fileNames.map!(a => `"` ~ a ~ `"`).join(",") ~ ")"; 236 } 237 238 template FileNames() { 239 mixin("alias FileNames = " ~ filesTupleString ~ ";"); 240 } 241 242 243 private void writeSrcFiles(in Options options) { 244 writeln("[Reggae] Writing reggae source files"); 245 246 import std.file: mkdirRecurse; 247 if(!reggaeSrcDirName.exists) { 248 mkdirRecurse(reggaeSrcDirName); 249 mkdirRecurse(buildPath(reggaeSrcDirName, "dub")); 250 mkdirRecurse(buildPath(reggaeSrcDirName, "rules")); 251 mkdirRecurse(buildPath(reggaeSrcDirName, "backend")); 252 mkdirRecurse(buildPath(reggaeSrcDirName, "core", "rules")); 253 } 254 255 256 //this foreach has to happen at compile time due 257 //to the string import below. 258 foreach(fileName; FileNames!()) { 259 auto file = File(reggaeSrcFileName(fileName), "w"); 260 file.write(import(fileName)); 261 } 262 263 writeConfig(options); 264 } 265 266 267 private void writeConfig(in Options options) { 268 auto file = File(reggaeSrcFileName("config.d"), "w"); 269 270 file.writeln(q{ 271 module reggae.config; 272 import reggae.ctaa; 273 import reggae.types; 274 import reggae.options; 275 }); 276 277 file.writeln("immutable options = ", options, ";"); 278 279 file.writeln("enum userVars = AssocList!(string, string)(["); 280 foreach(key, value; options.userVars) { 281 file.writeln("assocEntry(`", key, "`, `", value, "`), "); 282 } 283 file.writeln("]);"); 284 285 writeDubConfig(options, file); 286 } 287 288 289 290 private string reggaeSrcFileName(in string fileName) @safe pure nothrow { 291 return buildPath(reggaeSrcDirName, fileName); 292 }