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 import reggae.path: buildPath;
20 
21 import std.stdio;
22 import std.file: timeLastModified;
23 
24 /**
25  Creates a build generator out of a module and a list of top-level targets.
26  This will define a function with the signature $(D Build buildFunc()) in
27  the calling module and a $(D main) entry point function for a command-line
28  executable.
29  */
30 mixin template buildGen(string buildModule, targets...) {
31     mixin buildImpl!targets;
32     mixin BuildGenMain!buildModule;
33 }
34 
35 mixin template BuildGenMain(string buildModule = "reggaefile") {
36     import std.stdio;
37 
38     // args is empty except for the binary backend,
39     // in which case it's used for runtime options
40     int main(string[] args) {
41         try {
42             import reggae.config: options;
43             doBuildFor!(buildModule)(options, args); //the user's build description
44         } catch(Exception ex) {
45             stderr.writeln(ex.msg);
46             return 1;
47         }
48 
49         return 0;
50     }
51 }
52 
53 void doBuildFor(alias module_ = "reggaefile")(in Options options, string[] args = []) {
54     auto build = getBuildObject!module_(options);
55     if(!options.noCompilationDB) writeCompilationDB(build, options);
56     doBuild(build, options, args);
57 }
58 
59 // calls the build function or loads it from the cache and returns
60 // the Build object
61 Build getBuildObject(alias module_)(in Options options) {
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: dirSeparator;
150 
151     auto file = File(buildPath(options.workingDir, "compile_commands.json"), "w");
152     file.writeln("[");
153 
154     enum objPathPrefix = "objs" ~ dirSeparator;
155 
156     immutable cwd = getcwd;
157     string entry(Target target) {
158         auto command = target
159             .shellCommand(options)
160             .replace(`"`, `\"`)
161             .split(" ")
162             .map!(a => a.startsWith(objPathPrefix) ? buildPath(options.workingDir, a) : a)
163             .join(" ")
164         ;
165         return
166             "    {\n" ~
167             text(`        "directory": "`, cwd, `"`) ~ ",\n" ~
168             text(`        "command": "`, command, `"`) ~ ",\n" ~
169             text(`        "file": "`, target.dependenciesInProjectPath(options.projectPath).join(" "), `"`) ~ "\n" ~
170             "    }";
171     }
172 
173     file.write(build.range.map!(a => entry(a)).join(",\n"));
174     file.writeln;
175     file.writeln("]");
176 }