1 /**
2  The main entry point for the reggae tool. Its tasks are:
3  $(UL
4    $(LI Verify that a $(D reggafile.d) exists in the selected directory)
5    $(LI Generate a $(D reggaefile.d) for dub projects)
6    $(LI Write out the reggae library files and $(D config.d))
7    $(LI Compile the build description with the reggae library files to produce $(D buildgen))
8    $(LI Produce $(D dcompile), a binary to call the D compiler to obtain dependencies during compilation)
9    $(LI Call the produced $(D buildgen) binary)
10  )
11  */
12 
13 
14 module reggae.reggae;
15 
16 import std.stdio;
17 import std.process: execute;
18 import std.array: array, join, empty;
19 import std.path: absolutePath, buildPath, relativePath;
20 import std.typetuple;
21 import std.file: exists;
22 import std.conv: text;
23 import std.exception: enforce;
24 import std.conv: to;
25 import std.algorithm;
26 
27 import reggae.options;
28 import reggae.ctaa;
29 
30 
31 version(minimal) {
32     //empty stubs for minimal version of reggae
33     void maybeCreateReggaefile(T...)(T) {}
34     void writeDubConfig(T...)(T) {}
35 } else {
36     import reggae.dub.interop;
37 }
38 
39 mixin template reggaeGen(targets...) {
40     mixin buildImpl!targets;
41     mixin ReggaeMain;
42 }
43 
44 mixin template ReggaeMain() {
45     import reggae.options: getOptions;
46     import std.stdio: stderr;
47 
48     int main(string[] args) {
49         try {
50             run(getOptions(args));
51         } catch(Exception ex) {
52             stderr.writeln(ex.msg);
53             return 1;
54         }
55 
56         return 0;
57     }
58 }
59 
60 void run(in Options options) {
61     if(options.help) return;
62     enforce(options.projectPath != "", "A project path must be specified");
63 
64     maybeCreateReggaefile(options);
65     createBuild(options);
66 }
67 
68 
69 enum coreFiles = [
70     "buildgen_main.d", "buildgen.d",
71     "build.d",
72     "backend/binary.d",
73     "package.d", "range.d", "reflect.d",
74     "dependencies.d", "types.d", "dcompile.d",
75     "ctaa.d", "sorting.d",
76     "rules/package.d",
77     "rules/defaults.d", "rules/common.d",
78     "rules/d.d",
79     "core/package.d", "core/rules/package.d",
80     ];
81 enum otherFiles = [
82     "backend/ninja.d", "backend/make.d",
83     "dub/info.d", "rules/dub.d",
84     "rules/cpp.d", "rules/c.d",
85     ];
86 
87 //all files that need to be written out and compiled
88 private string[] fileNames() @safe pure nothrow {
89     version(minimal) return coreFiles;
90     else return coreFiles ~ otherFiles;
91 }
92 
93 
94 private void createBuild(in Options options) {
95 
96     immutable reggaefilePath = getReggaefilePath(options);
97     enforce(reggaefilePath.exists, text("Could not find ", reggaefilePath));
98 
99     //write out the library source files to be compiled with the user's
100     //build description
101     writeSrcFiles(options);
102 
103     //compile the binaries (the build generator and dcompile)
104     immutable buildGenName = compileBinaries(options);
105 
106     //actually run the build generator
107     writeln("[Reggae] Running the created binary to generate the build");
108     immutable retRunBuildgen = execute([buildPath(".", buildGenName)]);
109     enforce(retRunBuildgen.status == 0,
110             text("Couldn't execute the produced ", buildGenName, " binary:\n", retRunBuildgen.output));
111 
112     writeln(retRunBuildgen.output);
113 }
114 
115 private immutable hiddenDir = ".reggae";
116 
117 
118 private auto compileBinaries(in Options options) {
119     immutable buildGenName = getBuildGenName(options);
120     const compileBuildGenCmd = getCompileBuildGenCmd(options);
121 
122     immutable dcompileName = buildPath(hiddenDir, "dcompile");
123     immutable dcompileCmd = ["dmd",
124                              "-I.reggae/src",
125                              "-of" ~ dcompileName,
126                              reggaeSrcFileName("dcompile.d"),
127                              reggaeSrcFileName("dependencies.d")];
128 
129 
130     static struct Binary { string name; const(string)[] cmd; }
131 
132     const binaries = [Binary(buildGenName, compileBuildGenCmd), Binary(dcompileName, dcompileCmd)];
133     import std.parallelism;
134 
135     foreach(bin; binaries.parallel) {
136         writeln("[Reggae] Compiling metabuild binary ", bin.name);
137         immutable res = execute(bin.cmd);
138         enforce(res.status == 0, text("Couldn't execute ", bin.cmd.join(" "), ":\n", res.output,
139                                       "\n", "bin.name: ", bin.name, ", bin.cmd: ", bin.cmd.join(" ")));
140     }
141 
142     return buildGenName;
143 }
144 
145 string[] getCompileBuildGenCmd(in Options options) @safe nothrow {
146     const reggaeSrcs = ("config.d" ~ fileNames).
147         filter!(a => a != "dcompile.d").
148         map!(a => a.reggaeSrcFileName).array;
149 
150     immutable buildBinFlags = options.backend == Backend.binary
151         ? ["-O", "-release", "-inline"]
152         : [];
153     immutable commonBefore = ["dmd",
154                               "-I" ~ options.projectPath,
155                               "-of" ~ getBuildGenName(options)];
156     const commonAfter = buildBinFlags ~ reggaeSrcs ~ getReggaefilePath(options);
157     version(minimal) return commonBefore ~ "-version=minimal" ~ commonAfter;
158     else return commonBefore ~ commonAfter;
159 }
160 
161 string getBuildGenName(in Options options) @safe pure nothrow {
162     return options.backend == Backend.binary ? "build" : buildPath(hiddenDir, "buildgen");
163 }
164 
165 immutable reggaeSrcDirName = buildPath(".reggae", "src", "reggae");
166 
167 private string filesTupleString() @safe pure nothrow {
168     return "TypeTuple!(" ~ fileNames.map!(a => `"` ~ a ~ `"`).join(",") ~ ")";
169 }
170 
171 template FileNames() {
172     mixin("alias FileNames = " ~ filesTupleString ~ ";");
173 }
174 
175 
176 private void writeSrcFiles(in Options options) {
177     import std.file: mkdirRecurse;
178     if(!reggaeSrcDirName.exists) {
179         mkdirRecurse(reggaeSrcDirName);
180         mkdirRecurse(buildPath(reggaeSrcDirName, "dub"));
181         mkdirRecurse(buildPath(reggaeSrcDirName, "rules"));
182         mkdirRecurse(buildPath(reggaeSrcDirName, "backend"));
183         mkdirRecurse(buildPath(reggaeSrcDirName, "core", "rules"));
184     }
185 
186 
187     //this foreach has to happen at compile time due
188     //to the string import below.
189     foreach(fileName; FileNames!()) {
190         auto file = File(reggaeSrcFileName(fileName), "w");
191         file.write(import(fileName));
192     }
193 
194     writeConfig(options);
195 }
196 
197 
198 private void writeConfig(in Options options) {
199     auto file = File(reggaeSrcFileName("config.d"), "w");
200 
201     file.writeln(q{
202         module reggae.config;
203         import reggae.ctaa;
204         import reggae.types: Backend;
205 
206     });
207 
208     file.writeln("enum projectPath = `", options.projectPath, "`;");
209     file.writeln("enum backend = Backend.", options.backend, ";");
210     file.writeln("enum dflags = `", options.dflags, "`;");
211     file.writeln("enum reggaePath = `", options.reggaePath, "`;");
212     file.writeln("enum buildFilePath = `", options.getReggaefilePath.absolutePath, "`;");
213     file.writeln("enum cCompiler = `", options.cCompiler, "`;");
214     file.writeln("enum cppCompiler = `", options.cppCompiler, "`;");
215     file.writeln("enum dCompiler = `", options.dCompiler, "`;");
216     file.writeln("enum perModule = ", options.perModule, ";");
217 
218     file.writeln("enum userVars = AssocList!(string, string)([");
219     foreach(key, value; options.userVars) {
220         file.writeln("assocEntry(`", key, "`, `", value, "`), ");
221     }
222     file.writeln("]);");
223 
224     writeDubConfig(options, file);
225 }
226 
227 
228 
229 private string reggaeSrcFileName(in string fileName) @safe pure nothrow {
230     return buildPath(reggaeSrcDirName, fileName);
231 }
232 
233 private string getReggaefilePath(in Options options) @safe nothrow {
234     immutable regular = projectBuildFile(options);
235     if(regular.exists) return regular;
236     immutable path = options.isDubProject ? "" : options.projectPath;
237     return buildPath(path, "reggaefile.d");
238 }