1 module reggae.backend.binary; 2 3 4 import reggae.build; 5 import reggae.range; 6 import reggae.config; 7 import std.algorithm; 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 executeCommand(target); 86 return true; 87 } 88 } 89 90 return false; 91 } 92 93 //Checks dependencies listed in the .dep file created by the compiler 94 bool checkDeps(in Target target, in string depFileName) const @trusted { 95 auto file = File(depFileName); 96 const dependencies = file.byLine.map!(a => a.to!string).dependenciesFromFile; 97 98 if(dependencies.any!(a => a.newerThan(target.outputsInProjectPath(projectPath)[0]))) { 99 executeCommand(target); 100 return true; 101 } 102 return false; 103 } 104 105 void executeCommand(in Target target) const @trusted { 106 mkDir(target); 107 target.execute(projectPath); 108 } 109 110 } 111 112 bool newerThan(in string a, in string b) nothrow { 113 try { 114 return a.timeLastModified > b.timeLastModified; 115 } catch(Exception) { //file not there, so newer 116 return true; 117 } 118 } 119 120 //@trusted because of mkdirRecurse 121 private void mkDir(in Target target) @trusted { 122 foreach(output; target.outputs) { 123 import std.file: exists, mkdirRecurse; 124 import std.path: dirName; 125 if(!output.dirName.exists) mkdirRecurse(output.dirName); 126 } 127 } 128 129 string[] dependenciesFromFile(R)(R lines) @trusted if(isInputRange!R) { 130 return lines. 131 map!(a => a.replace(" \\", "")). 132 filter!(a => !a.empty). 133 map!(a => a.strip). 134 array[1..$]; 135 }