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 }