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