1 module app;
2 
3 import std.algorithm;
4 import std.array;
5 import std.getopt;
6 import std.json;
7 import std.process;
8 import std.stdio;
9 
10 enum dbdVersion = "1.0.2";
11 
12 struct Pack
13 {
14     string name;
15     string ver;
16     string config;
17     string[] overrideConfig;
18 
19     @property string dubId()
20     {
21         assert(name);
22         if (ver)
23             return name ~ "@" ~ ver;
24         else
25             return name;
26     }
27 }
28 
29 struct DubConfig
30 {
31     string exe;
32     string compiler;
33     string arch;
34     string build;
35 
36     string[] makeDescribeCmd(Pack pack)
37     {
38         return makeComplexCmd("describe", pack);
39     }
40 
41     string[] makeBuildCmd(Pack pack)
42     {
43         return makeComplexCmd("build", pack);
44     }
45 
46     string[] makeFetchCmd(Pack pack)
47     {
48         return [exe, "fetch", pack.dubId];
49     }
50 
51     string[] makeComplexCmd(string cmdName, Pack pack)
52     {
53         auto cmd = [
54             exe, cmdName, pack.dubId
55         ];
56         if (pack.config)
57             cmd ~= ["--config", pack.config];
58 
59         foreach (oc; pack.overrideConfig)
60             cmd ~= ["--override-config", oc];
61 
62         if (compiler)
63             cmd ~= ["--compiler", compiler];
64 
65         if (arch)
66             cmd ~= ["--arch", arch];
67 
68         if (build)
69             cmd ~= ["--build", build];
70 
71         return cmd;
72     }
73 
74 }
75 
76 int usage(string dbdExe, Option[] opts, int code = 0, string errorMsg = null)
77 {
78     auto file = code == 0 ? stdout : stderr;
79 
80     if (errorMsg)
81     {
82         file.writefln("Error: %s", errorMsg);
83         file.writeln("");
84     }
85 
86     if (code == 0)
87     {
88         file.writefln("dub-build-deep v%s - An utility to build DUB sub-dependencies", dbdVersion);
89         file.writefln("");
90     }
91 
92     file.writefln("Usage:");
93     file.writefln("    %s [options] (package name)[@version]", dbdExe);
94     file.writeln();
95     file.writeln("Most options are the same than expected by DUB describe.");
96 
97     defaultGetoptFormatter(file.lockingTextWriter(), null, opts);
98 
99     return code;
100 }
101 
102 int main(string[] args)
103 {
104     Pack pack;
105 
106     DubConfig dub;
107     dub.exe = "dub";
108 
109     auto helpInfo = getopt(args,
110         "config", "Specify a DUB configuration", &pack.config,
111         "override-config", "Specify a DUB configuration for a sub-dependency", &pack.overrideConfig,
112         "dub", "Specify a DUB executable", &dub.exe,
113         "compiler", "D compiler to be used", &dub.compiler,
114         "arch", "Architecture to target", &dub.arch,
115         "build", "The build type (debug, release, ...)", &dub.build,
116     );
117 
118     if (helpInfo.helpWanted)
119     {
120         return usage(args[0], helpInfo.options);
121     }
122 
123     if (args.length < 2)
124     {
125         return usage(args[0], helpInfo.options, 1, "(package name] was not provided");
126     }
127 
128     if (args.length > 2)
129     {
130         return usage(args[0], helpInfo.options, 1, "Too many arguments (only one package accepted)");
131     }
132 
133     const spec = args[1].split("@");
134     if (spec.length > 2)
135     {
136         stderr.writeln("Invalid package specifier: ", args[1]);
137         return 1;
138     }
139     pack.name = spec[0];
140     if (spec.length == 2)
141     {
142         pack.ver = spec[1];
143     }
144 
145     const describeCmd = dub.makeDescribeCmd(pack);
146 
147     writeln("running ", escapeShellCommand(describeCmd));
148     auto describe = execute(describeCmd);
149 
150     if (describe.status != 0 && describe.output.canFind("locally"))
151     {
152         writefln("Warning: %s does not appear to be present locally. Will try to fetch...", pack.dubId);
153         const fetchCmd = dub.makeFetchCmd(pack);
154         writefln("running %s", escapeShellCommand(fetchCmd));
155         auto fetch = execute(fetchCmd);
156         if (fetch.status != 0)
157         {
158             stderr.writefln("Error: `dub fetch` returned %s:", fetch.status);
159             stderr.writeln(fetch.output);
160             return fetch.status;
161         }
162 
163         writeln("running ", escapeShellCommand(describeCmd));
164         describe = execute(describeCmd);
165     }
166 
167 
168     if (describe.status != 0)
169     {
170         stderr.writeln("Error: describe command returned %s:", describe.status);
171         stderr.writeln(describe.output);
172         return describe.status;
173     }
174 
175     auto json = parseJSON(describe.output);
176 
177     foreach (jp; json["packages"].array)
178     {
179         version(GNU)
180         {
181             if (!jp["active"].type == JSON_TYPE.TRUE)
182                 continue;
183         }
184         else
185         {
186             if (!jp["active"].boolean)
187                 continue;
188         }
189 
190         const targetType = jp["targetType"].str;
191         if (targetType == "none" || targetType == "sourceLibrary")
192             continue;
193 
194         Pack p;
195         p.name = jp["name"].str;
196         p.ver = jp["version"].str;
197         p.config = jp["configuration"].str;
198 
199         const res = buildDubPackage(p, dub);
200 
201         if (res)
202             return res;
203     }
204 
205     return 0;
206 }
207 
208 int buildDubPackage(Pack pack, DubConfig dub)
209 {
210     const buildCmd = dub.makeBuildCmd(pack);
211     writeln("running ", escapeShellCommand(buildCmd));
212     auto res = execute(buildCmd);
213     if (res.status != 0)
214     {
215         stderr.writeln(res.output);
216     }
217     return res.status;
218 }