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 import reggae.path: buildPath; 20 21 import std.stdio; 22 import std.file: timeLastModified; 23 24 /** 25 Creates a build generator out of a module and a list of top-level targets. 26 This will define a function with the signature $(D Build buildFunc()) in 27 the calling module and a $(D main) entry point function for a command-line 28 executable. 29 */ 30 mixin template buildGen(string buildModule, targets...) { 31 mixin buildImpl!targets; 32 mixin BuildGenMain!buildModule; 33 } 34 35 mixin template BuildGenMain(string buildModule = "reggaefile") { 36 import std.stdio; 37 38 // args is empty except for the binary backend, 39 // in which case it's used for runtime options 40 int main(string[] args) { 41 try { 42 import reggae.config: options; 43 doBuildFor!(buildModule)(options, args); //the user's build description 44 } catch(Exception ex) { 45 stderr.writeln(ex.msg); 46 return 1; 47 } 48 49 return 0; 50 } 51 } 52 53 void doBuildFor(alias module_ = "reggaefile")(in Options options, string[] args = []) { 54 auto build = getBuildObject!module_(options); 55 if(!options.noCompilationDB) writeCompilationDB(build, options); 56 doBuild(build, options, args); 57 } 58 59 // calls the build function or loads it from the cache and returns 60 // the Build object 61 Build getBuildObject(alias module_)(in Options options) { 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[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 Makefile(build, options).writeBuild; 103 break; 104 105 case ninja: 106 Ninja(build, options).writeBuild; 107 break; 108 109 case tup: 110 Tup(build, options).writeBuild; 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(B; AliasSeq!(Makefile, Ninja, Tup)) 133 B(build, options).writeBuild; 134 } 135 136 137 void writeCompilationDB(Build build, in Options options) { 138 import std.file; 139 import std.conv; 140 import std.algorithm; 141 import std.string; 142 import std.path: dirSeparator; 143 144 auto file = File(buildPath(options.workingDir, "compile_commands.json"), "w"); 145 file.writeln("["); 146 147 enum objPathPrefix = "objs" ~ dirSeparator; 148 149 immutable cwd = getcwd; 150 string entry(Target target) { 151 auto command = target 152 .shellCommand(options) 153 .replace(`"`, `\"`) 154 .split(" ") 155 .map!(a => a.startsWith(objPathPrefix) ? buildPath(options.workingDir, a) : a) 156 .join(" ") 157 ; 158 return 159 " {\n" ~ 160 text(` "directory": "`, cwd, `"`) ~ ",\n" ~ 161 text(` "command": "`, command, `"`) ~ ",\n" ~ 162 text(` "file": "`, target.dependenciesInProjectPath(options.projectPath).join(" "), `"`) ~ "\n" ~ 163 " }"; 164 } 165 166 file.write(build.range.map!(a => entry(a)).join(",\n")); 167 file.writeln; 168 file.writeln("]"); 169 }