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 }