1 module reggae.backend.binary; 2 3 4 import reggae.build; 5 import reggae.range; 6 import reggae.config; 7 import std.algorithm: all, splitter, cartesianProduct, any, filter; 8 import std.range; 9 import std.file: timeLastModified, thisExePath, exists; 10 import std.process: execute, executeShell; 11 import std.path: absolutePath; 12 import std.typecons: tuple; 13 import std.exception; 14 import std.stdio; 15 import std.parallelism: parallel; 16 import std.conv; 17 import std.array: replace, empty; 18 import std.string: strip; 19 20 @safe: 21 22 struct Binary { 23 Build build; 24 string projectPath; 25 26 this(Build build, string projectPath) pure { 27 this.build = build; 28 this.projectPath = projectPath; 29 } 30 31 void run() const @system { //@system due to parallel 32 bool didAnything = checkReRun(); 33 34 foreach(topTarget; build.targets) { 35 foreach(level; ByDepthLevel(topTarget)) { 36 foreach(target; level.parallel) { 37 38 const outs = target.outputsInProjectPath(projectPath); 39 immutable depFileName = outs[0] ~ ".dep"; 40 if(depFileName.exists) { 41 didAnything = checkDeps(target, depFileName) || didAnything; 42 } 43 44 didAnything = checkTarget(target) || didAnything; 45 } 46 } 47 } 48 if(!didAnything) writeln("[build] Nothing to do"); 49 } 50 51 private: 52 53 bool checkReRun() const { 54 immutable myPath = thisExePath; 55 if(reggaePath.newerThan(myPath) || buildFilePath.newerThan(myPath)) { 56 writeln("[build] " ~ reggaeCmd.join(" ")); 57 immutable reggaeRes = execute(reggaeCmd); 58 enforce(reggaeRes.status == 0, 59 text("Could not run ", reggaeCmd.join(" "), " to regenerate build:\n", 60 reggaeRes.output)); 61 writeln(reggaeRes.output); 62 63 //currently not needed because generating the build also runs it. 64 // immutable buildRes = execute([myPath]); 65 // enforce(buildRes.status == 0, "Could not redo the build:\n", buildRes.output); 66 return true; 67 } 68 69 return false; 70 } 71 72 string[] reggaeCmd() pure nothrow const { 73 immutable _dflags = dflags == "" ? "" : " --dflags='" ~ dflags ~ "'"; 74 auto mutCmd = [reggaePath, "-b", "binary"]; 75 if(_dflags != "") mutCmd ~= _dflags; 76 return mutCmd ~ projectPath; 77 } 78 79 bool checkTarget(in Target target) const { 80 foreach(dep; chain(target.dependencies, target.implicits)) { 81 if(cartesianProduct(dep.outputsInProjectPath(projectPath), 82 target.outputsInProjectPath(projectPath)). 83 any!(a => a[0].newerThan(a[1]))) { 84 85 mkDir(target); 86 immutable cmd = target.shellCommand(projectPath); 87 writeln("[build] " ~ cmd); 88 immutable res = executeShell(cmd); 89 enforce(res.status == 0, "Could not execute " ~ cmd ~ ":\n" ~ res.output); 90 return true; 91 } 92 } 93 94 return false; 95 } 96 97 bool checkDeps(in Target target, in string depFileName) const @trusted { 98 auto file = File(depFileName); 99 const dependencies = file.byLine.map!(a => a.to!string).dependenciesFromFile; 100 101 if(dependencies.any!(a => a.newerThan(target.outputsInProjectPath(projectPath)[0]))) { 102 mkDir(target); 103 immutable cmd = target.shellCommand(projectPath); 104 writeln("[build] " ~ cmd); 105 immutable res = executeShell(cmd); 106 enforce(res.status == 0, "Could not execute " ~ cmd ~ ":\n" ~ res.output); 107 108 return true; 109 } 110 return false; 111 } 112 } 113 114 115 bool newerThan(in string a, in string b) nothrow { 116 try { 117 return a.timeLastModified > b.timeLastModified; 118 } catch(Exception) { //file not there, so newer 119 return true; 120 } 121 } 122 123 //@trusted because of mkdirRecurse 124 private void mkDir(in Target target) @trusted { 125 foreach(output; target.outputs) { 126 import std.file: exists, mkdirRecurse; 127 import std.path: dirName; 128 if(!output.dirName.exists) mkdirRecurse(output.dirName); 129 } 130 } 131 132 string[] dependenciesFromFile(R)(R lines) @trusted if(isInputRange!R) { 133 return lines. 134 map!(a => a.replace(" \\", "")). 135 filter!(a => !a.empty). 136 map!(a => a.strip). 137 array[1..$]; 138 }