1 /**
2 
3  This module implements the binary that is used to generate the build
4  in the case of the make, ninja and tup backends, i.e. it translates
5  D code into the respective output.
6 
7  For the binary target this module implements the binary that actually
8  performs the build
9 
10  */
11 
12 module reggae.buildgen;
13 
14 import reggae.build;
15 import reggae.options;
16 import reggae.types;
17 import reggae.backend;
18 import reggae.reflect;
19 
20 import std.stdio;
21 import std.file: timeLastModified;
22 
23 /**
24  Creates a build generator out of a module and a list of top-level targets.
25  This will define a function with the signature $(D Build buildFunc()) in
26  the calling module and a $(D main) entry point function for a command-line
27  executable.
28  */
29 mixin template buildGen(string buildModule, targets...) {
30     mixin buildImpl!targets;
31     mixin BuildGenMain!buildModule;
32 }
33 
34 mixin template BuildGenMain(string buildModule = "reggaefile") {
35     import std.stdio;
36 
37     // args is empty except for the binary backend,
38     // in which case it's used for runtime options
39     int main(string[] args) {
40         try {
41             import reggae.config: options;
42             doBuildFor!(buildModule)(options, args); //the user's build description
43         } catch(Exception ex) {
44             stderr.writeln(ex);
45             return 1;
46         }
47 
48         return 0;
49     }
50 }
51 
52 void doBuildFor(alias module_ = "reggaefile")(in Options options, string[] args = []) {
53     auto build = getBuildObject!module_(options);
54     if(!options.noCompilationDB) writeCompilationDB(build, options);
55     doBuild(build, options, args);
56 }
57 
58 // calls the build function or loads it from the cache and returns
59 // the Build object
60 Build getBuildObject(alias module_)(in Options options) {
61     import std.path;
62     import std.file;
63 
64     immutable cacheFileName = buildPath(".reggae", "cache");
65     if(!options.cacheBuildInfo ||
66        !cacheFileName.exists ||
67         thisExePath.timeLastModified > cacheFileName.timeLastModified) {
68         const buildFunc = getBuild!(module_); //get the function to call by CT reflection
69         auto build = buildFunc(); //actually call the function to get the build description
70 
71         if(options.cacheBuildInfo) {
72             auto file = File(cacheFileName, "w");
73             file.rawWrite(build.toBytes(options));
74         }
75 
76         return build;
77     } else {
78         auto file = File(cacheFileName);
79         auto buffer = new ubyte[cast(uint)file.size];
80         return Build.fromBytes(file.rawRead(buffer));
81     }
82 }
83 
84 void doBuild(Build build, in Options options, string[] args = []) {
85     options.export_ ? exportBuild(build, options) : doOneBuild(build, options, args);
86 }
87 
88 
89 private void doOneBuild(Build build, in Options options, string[] args = []) {
90     final switch(options.backend) with(Backend) {
91 
92         version(minimal) {
93             import std.conv;
94 
95             case make:
96             case ninja:
97             case tup:
98                 throw new Exception(text("Support for ", options.backend, " not compiled in"));
99         } else {
100 
101             case make:
102                 writeBuild!Makefile(build, options);
103                 break;
104 
105             case ninja:
106                 writeBuild!Ninja(build, options);
107                 break;
108 
109             case tup:
110                 writeBuild!Tup(build, options);
111                 break;
112         }
113 
114         case binary:
115             Binary(build, options).run(args);
116             break;
117 
118         case none:
119             throw new Exception("A backend must be specified with -b/--backend");
120         }
121 }
122 
123 private void exportBuild(Build build, in Options options) {
124     import std.exception;
125     import std.meta;
126 
127     enforce(options.backend == Backend.none, "Cannot specify a backend and export at the same time");
128 
129     version(minimal)
130         throw new Exception("export not supported in minimal version");
131     else
132         foreach(backend; AliasSeq!(Makefile, Ninja, Tup))
133             writeBuild!backend(build, options);
134 }
135 
136 private void writeBuild(T)(Build build, in Options options) {
137     version(minimal)
138         throw new Exception(T.stringof ~ " backend support not compiled in");
139     else
140         T(build, options).writeBuild;
141 }
142 
143 
144 void writeCompilationDB(Build build, in Options options) {
145     import std.file;
146     import std.conv;
147     import std.algorithm;
148     import std.string;
149     import std.path;
150 
151     auto file = File(buildPath(options.workingDir, "compile_commands.json"), "w");
152     file.writeln("[");
153 
154     immutable cwd = getcwd;
155     string entry(Target target) {
156         auto command = target
157             .shellCommand(options)
158             .replace(`"`, `\"`)
159             .split(" ")
160             .map!(a => a.startsWith("objs/") ? buildPath(options.workingDir, a) : a)
161             .join(" ")
162         ;
163         return
164             "    {\n" ~
165             text(`        "directory": "`, cwd, `"`) ~ ",\n" ~
166             text(`        "command": "`, command, `"`) ~ ",\n" ~
167             text(`        "file": "`, target.dependenciesInProjectPath(options.projectPath).join(" "), `"`) ~ "\n" ~
168             "    }";
169     }
170 
171     file.write(build.range.map!(a => entry(a)).join(",\n"));
172     file.writeln;
173     file.writeln("]");
174 }