1 /**
2    A module for providing interop between reggae and dub
3 */
4 module reggae.dub.interop;
5 
6 
7 import reggae.from;
8 public import reggae.dub.interop.reggaefile;
9 
10 
11 from!"reggae.dub.info".DubInfo[string] gDubInfos;
12 
13 
14 void writeDubConfig(T)(auto ref T output,
15                        in from!"reggae.options".Options options,
16                        from!"std.stdio".File file) {
17     import reggae.io: log;
18     import reggae.dub.info: TargetType;
19     import reggae.dub.interop.fetch: dubFetch;
20     import reggae.dub.interop.dublib: Dub;
21 
22     output.log("Writing dub configuration");
23     scope(exit) output.log("Finished writing dub configuration");
24 
25     if(!options.isDubProject) {
26         file.writeln("enum isDubProject = false;");
27         return;
28     }
29 
30     // must check for dub.selections.json before creating dub instance
31     const dubSelectionsJson = ensureDubSelectionsJson(output, options);
32 
33     auto dub = Dub(options);
34 
35     dubFetch(output, dub, options, dubSelectionsJson);
36 
37     file.writeln("import reggae.dub.info;");
38     file.writeln("enum isDubProject = true;");
39 
40     output.log("    Getting dub build information");
41     auto dubInfo = getDubInfo(output, dub, options);
42     output.log("    Got     dub build information");
43 
44     const targetType = dubInfo.packages.length
45         ? dubInfo.packages[0].targetType
46         : TargetType.sourceLibrary;
47 
48     file.writeln(`const configToDubInfo = assocList([`);
49 
50     const keys = () @trusted { return gDubInfos.keys; }();
51     foreach(config; keys) {
52         file.writeln(`    assocEntry("`, config, `", `, gDubInfos[config], `),`);
53     }
54     file.writeln(`]);`);
55     file.writeln;
56 }
57 
58 
59 private string ensureDubSelectionsJson
60     (O)
61     (ref O output, in from!"reggae.options".Options options)
62     @safe
63 {
64     import reggae.dub.interop.exec: callDub, dubEnvArgs;
65     import reggae.io: log;
66     import std.path: buildPath;
67     import std.file: exists;
68     import std.exception: enforce;
69 
70     const path = buildPath(options.projectPath, "dub.selections.json");
71 
72     if(!path.exists) {
73         output.log("Creating dub.selections.json");
74         const cmd = ["dub", "upgrade"] ~ dubEnvArgs;
75         callDub(output, options, cmd);
76     }
77 
78     enforce(path.exists, "Could not create dub.selections.json");
79 
80     return path;
81 }
82 
83 
84 
85 private from!"reggae.dub.info".DubInfo getDubInfo
86     (T)
87     (auto ref T output,
88      ref from!"reggae.dub.interop.dublib".Dub dub,
89      in from!"reggae.options".Options options)
90 {
91     import reggae.dub.interop: gDubInfos;
92     import reggae.io: log;
93     import std.array: empty;
94     import std.file: exists;
95     import std.path: buildPath;
96     import std.conv: text;
97     import std.exception: enforce;
98 
99     version(unittest) gDubInfos = null;
100 
101     if("default" !in gDubInfos) {
102 
103         enforce(buildPath(options.projectPath, "dub.selections.json").exists,
104                 "Cannot find dub.selections.json");
105 
106         const configs = dub.getConfigs(options);
107 
108         bool oneConfigOk;
109         Exception dubInfoFailure;
110 
111         if(configs.configurations.empty) {
112             gDubInfos["default"] = dub.configToDubInfo(options, "");
113             oneConfigOk = true;
114         } else {
115             foreach(config; configs.configurations) {
116                 try {
117                     gDubInfos[config] = dub.configToDubInfo(options, config);
118 
119                     // dub adds certain flags to certain configurations automatically but these flags
120                     // don't know up in the output to `dub describe`. Special case them here.
121 
122                     // unittest should only apply to the main package, hence [0].
123                     // This doesn't show up in `dub describe`, it's secret info that dub knows
124                     // so we have to add it manually here.
125                     if(config == "unittest") {
126                         if(config !in gDubInfos)
127                             throw new Exception(
128                                 text("Configuration `", config, "` not found in ",
129                                      () @trusted { return gDubInfos.keys; }()));
130                         if(gDubInfos[config].packages.length == 0)
131                             throw new Exception(
132                                 text("No main package in `", config, "` configuration"));
133                         gDubInfos[config].packages[0].dflags ~= " -unittest";
134                     }
135 
136                     try
137                         callPreBuildCommands(output, options, gDubInfos[config]);
138                     catch(Exception e) {
139                         output.log("Error calling prebuild commands: ", e.msg);
140                         throw e;
141                     }
142 
143                     oneConfigOk = true;
144 
145                 } catch(Exception ex) {
146                     output.log("ERROR: Could not get info for configuration ", config, ": ", ex.msg);
147                     if(dubInfoFailure is null) dubInfoFailure = ex;
148                 }
149             }
150 
151             if(configs.default_ !in gDubInfos)
152                 throw new Exception("Non-existent config info for " ~ configs.default_);
153 
154             gDubInfos["default"] = gDubInfos[configs.default_];
155        }
156 
157         if(!oneConfigOk) {
158             assert(dubInfoFailure !is null,
159                    "Internal error: no configurations worked and no exception to throw");
160             throw dubInfoFailure;
161         }
162     }
163 
164     return gDubInfos["default"];
165 }
166 
167 
168 private void callPreBuildCommands(T)(auto ref T output,
169                                      in from!"reggae.options".Options options,
170                                      in from!"reggae.dub.info".DubInfo dubInfo)
171     @safe
172 {
173     import reggae.io: log;
174     import std.process: executeShell, Config;
175     import std.string: replace;
176     import std.exception: enforce;
177     import std.conv: text;
178 
179     const string[string] env = null;
180     Config config = Config.none;
181     size_t maxOutput = size_t.max;
182     immutable workDir = options.projectPath;
183 
184     if(dubInfo.packages.length == 0) return;
185 
186     foreach(const package_; dubInfo.packages) {
187         foreach(const dubCommandString; package_.preBuildCommands) {
188             auto cmd = dubCommandString.replace("$project", options.projectPath);
189             output.log("Executing pre-build command `", cmd, "`");
190             const ret = executeShell(cmd, env, config, maxOutput, workDir);
191             enforce(ret.status == 0, text("Error calling ", cmd, ":\n", ret.output));
192         }
193     }
194 }