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 }