1 /** 2 3 This module implements the binary that is used to generate the build 4 in the case of the make, ninja and tup backends, i.e. it translates 5 D code into the respective output. 6 7 For the binary target this module implements the binary that actually 8 performs the build 9 10 */ 11 12 module reggae.buildgen; 13 14 import reggae.build; 15 import reggae.options; 16 import reggae.types; 17 import reggae.backend; 18 import reggae.reflect; 19 20 import std.stdio; 21 import std.file: timeLastModified; 22 23 /** 24 Creates a build generator out of a module and a list of top-level targets. 25 This will define a function with the signature $(D Build buildFunc()) in 26 the calling module and a $(D main) entry point function for a command-line 27 executable. 28 */ 29 mixin template buildGen(string buildModule, targets...) { 30 mixin buildImpl!targets; 31 mixin BuildGenMain!buildModule; 32 } 33 34 mixin template BuildGenMain(string buildModule = "reggaefile") { 35 import std.stdio; 36 37 // args is empty except for the binary backend, 38 // in which case it's used for runtime options 39 int main(string[] args) { 40 try { 41 import reggae.config: options; 42 doBuildFor!(buildModule)(options, args); //the user's build description 43 } catch(Exception ex) { 44 stderr.writeln(ex); 45 return 1; 46 } 47 48 return 0; 49 } 50 } 51 52 void doBuildFor(alias module_ = "reggaefile")(in Options options, string[] args = []) { 53 auto build = getBuildObject!module_(options); 54 if(!options.noCompilationDB) writeCompilationDB(build, options); 55 doBuild(build, options, args); 56 } 57 58 // calls the build function or loads it from the cache and returns 59 // the Build object 60 Build getBuildObject(alias module_)(in Options options) { 61 import std.path; 62 import std.file; 63 64 immutable cacheFileName = buildPath(".reggae", "cache"); 65 if(!options.cacheBuildInfo || 66 !cacheFileName.exists || 67 thisExePath.timeLastModified > cacheFileName.timeLastModified) { 68 const buildFunc = getBuild!(module_); //get the function to call by CT reflection 69 auto build = buildFunc(); //actually call the function to get the build description 70 71 if(options.cacheBuildInfo) { 72 auto file = File(cacheFileName, "w"); 73 file.rawWrite(build.toBytes(options)); 74 } 75 76 return build; 77 } else { 78 auto file = File(cacheFileName); 79 auto buffer = new ubyte[cast(uint)file.size]; 80 return Build.fromBytes(file.rawRead(buffer)); 81 } 82 } 83 84 void doBuild(Build build, in Options options, string[] args = []) { 85 options.export_ ? exportBuild(build, options) : doOneBuild(build, options, args); 86 } 87 88 89 private void doOneBuild(Build build, in Options options, string[] args = []) { 90 final switch(options.backend) with(Backend) { 91 92 version(minimal) { 93 import std.conv; 94 95 case make: 96 case ninja: 97 case tup: 98 throw new Exception(text("Support for ", options.backend, " not compiled in")); 99 } else { 100 101 case make: 102 writeBuild!Makefile(build, options); 103 break; 104 105 case ninja: 106 writeBuild!Ninja(build, options); 107 break; 108 109 case tup: 110 writeBuild!Tup(build, options); 111 break; 112 } 113 114 case binary: 115 Binary(build, options).run(args); 116 break; 117 118 case none: 119 throw new Exception("A backend must be specified with -b/--backend"); 120 } 121 } 122 123 private void exportBuild(Build build, in Options options) { 124 import std.exception; 125 import std.meta; 126 127 enforce(options.backend == Backend.none, "Cannot specify a backend and export at the same time"); 128 129 version(minimal) 130 throw new Exception("export not supported in minimal version"); 131 else 132 foreach(backend; AliasSeq!(Makefile, Ninja, Tup)) 133 writeBuild!backend(build, options); 134 } 135 136 private void writeBuild(T)(Build build, in Options options) { 137 version(minimal) 138 throw new Exception(T.stringof ~ " backend support not compiled in"); 139 else 140 T(build, options).writeBuild; 141 } 142 143 144 void writeCompilationDB(Build build, in Options options) { 145 import std.file; 146 import std.conv; 147 import std.algorithm; 148 import std.string; 149 import std.path; 150 151 auto file = File(buildPath(options.workingDir, "compile_commands.json"), "w"); 152 file.writeln("["); 153 154 immutable cwd = getcwd; 155 string entry(Target target) { 156 auto command = target 157 .shellCommand(options) 158 .replace(`"`, `\"`) 159 .split(" ") 160 .map!(a => a.startsWith("objs/") ? buildPath(options.workingDir, a) : a) 161 .join(" ") 162 ; 163 return 164 " {\n" ~ 165 text(` "directory": "`, cwd, `"`) ~ ",\n" ~ 166 text(` "command": "`, command, `"`) ~ ",\n" ~ 167 text(` "file": "`, target.dependenciesInProjectPath(options.projectPath).join(" "), `"`) ~ "\n" ~ 168 " }"; 169 } 170 171 file.write(build.range.map!(a => entry(a)).join(",\n")); 172 file.writeln; 173 file.writeln("]"); 174 }