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