1 /**
2  High-level rules for building dub projects. The rules in this module
3  only replicate what dub does itself. This allows a reggaefile.d to
4  reuse the information that dub already knows about.
5  */
6 
7 module reggae.rules.dub;
8 
9 import reggae.config;
10 
11 enum CompilationMode {
12     module_,  /// compile per module
13     package_, /// compile per package
14     all,      /// compile all source files
15     options,  /// whatever the command-line option was
16 }
17 
18 struct DubPackageName {
19     string value;
20 }
21 
22 static if(isDubProject) {
23 
24     import reggae.dub.info;
25     import reggae.types;
26     import reggae.build;
27     import reggae.rules.common;
28     import std.traits;
29     import std.typecons;
30 
31     /**
32      Builds the main dub target (equivalent of "dub build")
33     */
34     Target dubDefaultTarget(CompilerFlags compilerFlags = CompilerFlags(),
35                             LinkerFlags linkerFlags = LinkerFlags(),
36                             CompilationMode compilationMode = CompilationMode.options)
37         ()
38     {
39         import std.string: split;
40 
41         enum config = "default";
42         enum dubInfo = configToDubInfo[config];
43         enum targetName = dubInfo.targetName;
44         enum linkerFlags = dubInfo.mainLinkerFlags ~ linkerFlags.value.split(" ");
45         return dubTarget(
46             targetName,
47             dubInfo,
48             compilerFlags.value,
49             linkerFlags,
50             Yes.main,
51             compilationMode,
52         );
53     }
54 
55 
56     /**
57        A target corresponding to `dub test`
58      */
59     Target dubTestTarget(CompilerFlags compilerFlags = CompilerFlags(),
60                          LinkerFlags linkerFlags = LinkerFlags())
61                          ()
62     {
63         import std.typecons: No, Yes;
64 
65 
66         static if (__VERSION__ < 2079 || (__VERSION__ >= 2081 && __VERSION__ < 2084)) {
67             // these dmd versions have a bug pertaining to separate compilation and __traits(getUnitTests),
68             // we default here to compiling all-at-once for the unittest build
69             enum compilationMode = CompilationMode.all;
70         }
71         else
72             enum compilationMode = CompilationMode.options;
73 
74         return dubTestTarget!(compilerFlags, linkerFlags, compilationMode)();
75     }
76 
77     /**
78        A target corresponding to `dub test`
79      */
80     Target dubTestTarget(CompilerFlags compilerFlags = CompilerFlags(),
81                          LinkerFlags linkerFlags = LinkerFlags(),
82                          CompilationMode compilationMode)
83                          ()
84     {
85         import reggae.dub.info: TargetType, targetName;
86         import std.string: split;
87         import std.exception : enforce;
88         import std.conv: text;
89 
90         const config = "unittest" in configToDubInfo ? "unittest" : "default";
91         auto actualCompilerFlags = compilerFlags.value;
92         if("unittest" !in configToDubInfo) actualCompilerFlags ~= " -unittest";
93         const dubInfo = configToDubInfo[config];
94         enforce(dubInfo.packages.length, text("No dub packages found for config '", config, "'"));
95         const hasMain = dubInfo.packages[0].mainSourceFile != "";
96         const string[] emptyStrings;
97         const extraLinkerFlags = hasMain ? emptyStrings : ["-main"];
98         const actualLinkerFlags = extraLinkerFlags ~ linkerFlags.value.split(" ");
99         const defaultTargetHasName = configToDubInfo["default"].packages.length > 0;
100         const sameNameAsDefaultTarget =
101             defaultTargetHasName
102             && dubInfo.targetName == configToDubInfo["default"].targetName;
103         const name = sameNameAsDefaultTarget
104             // don't emit two targets with the same name
105             ? targetName(TargetType.executable, "ut")
106             : dubInfo.targetName;
107 
108         return dubTarget(name,
109                          dubInfo,
110                          actualCompilerFlags,
111                          actualLinkerFlags,
112                          Yes.main,
113                          compilationMode);
114     }
115 
116     /**
117      Builds a particular dub configuration (executable, unittest, etc.)
118      */
119     Target dubConfigurationTarget(Configuration config = Configuration("default"),
120                                   CompilerFlags compilerFlags = CompilerFlags(),
121                                   LinkerFlags linkerFlags = LinkerFlags(),
122                                   Flag!"main" includeMain = Yes.main,
123                                   CompilationMode compilationMode = CompilationMode.options,
124                                   alias objsFunction = () { Target[] t; return t; },
125                                   )
126         () if(isCallable!objsFunction)
127     {
128         import std.string: split;
129 
130         const dubInfo = configToDubInfo[config.value];
131         return dubTarget(dubInfo.targetName,
132                          dubInfo,
133                          compilerFlags.value,
134                          linkerFlags.value.split(" "),
135                          includeMain,
136                          compilationMode,
137                          objsFunction());
138     }
139 
140     Target dubTarget(
141         TargetName targetName,
142         Configuration config,
143         CompilerFlags compilerFlags = CompilerFlags(),
144         LinkerFlags linkerFlags = LinkerFlags(),
145         Flag!"main" includeMain = Yes.main,
146         CompilationMode compilationMode = CompilationMode.options,
147         alias objsFunction = () { Target[] t; return t; },
148      )
149         ()
150     {
151         import std.array: split;
152         return dubTarget(targetName,
153                          configToDubInfo[config.value],
154                          compilerFlags.value,
155                          linkerFlags.value.split(" "),
156                          includeMain,
157                          compilationMode,
158                          objsFunction(),
159             );
160     }
161 
162 
163     Target dubTarget(
164         in TargetName targetName,
165         in DubInfo dubInfo,
166         in string compilerFlags,
167         in string[] linkerFlags = [],
168         in Flag!"main" includeMain = Yes.main,
169         in CompilationMode compilationMode = CompilationMode.options,
170         Target[] extraObjects = [],
171         in size_t startingIndex = 0,
172         )
173     {
174 
175         import reggae.rules.common: staticLibraryTarget, link;
176         import reggae.dub.info: DubObjsDir;
177         import std.array: join;
178         import std.path: buildPath;
179         import std.file: getcwd;
180 
181         const isStaticLibrary =
182             dubInfo.targetType == TargetType.library ||
183             dubInfo.targetType == TargetType.staticLibrary;
184         const sharedFlags = dubInfo.targetType == TargetType.dynamicLibrary
185             ? "-shared"
186             : "";
187         const allLinkerFlags = (linkerFlags ~ dubInfo.linkerFlags ~ sharedFlags).join(" ");
188 
189         auto allObjs = objs(targetName,
190                             dubInfo,
191                             includeMain,
192                             compilerFlags,
193                             compilationMode,
194                             extraObjects,
195                             startingIndex);
196 
197         const name = realName(targetName, dubInfo);
198 
199         auto target = isStaticLibrary
200             ? staticLibraryTarget(name, allObjs)[0]
201             : dubInfo.targetType == TargetType.none
202                 ? Target.phony(name, "", allObjs)
203                 : link(ExeName(name),
204                        allObjs,
205                        Flags(allLinkerFlags));
206 
207         return dubInfo.postBuildCommands == ""
208             ? target
209             : Target.phony("postBuild", dubInfo.postBuildCommands, target);
210     }
211 
212     /**
213        All dub packages object files from the dependencies, but nothing from the
214        main package (the one actually being built).
215      */
216     Target[] dubDependencies(CompilerFlags compilerFlags = CompilerFlags())
217         () // runtime args
218     {
219         return dubDependencies!(Configuration("default"), compilerFlags)();
220     }
221 
222 
223     ///ditto
224     Target[] dubDependencies(Configuration config,
225                              CompilerFlags compilerFlags = CompilerFlags())
226         () // runtime args
227     {
228         const dubInfo = configToDubInfo[config.value];
229         const startingIndex = 1;
230         return objs(dubInfo.targetName,
231                     dubInfo,
232                     No.main,
233                     compilerFlags.value,
234                     CompilationMode.options,
235                     [], // extra objects
236                     startingIndex);
237     }
238 
239 
240 
241     /**
242        All dub object files for a configuration
243      */
244     Target[] dubObjects(Configuration config,
245                         CompilerFlags compilerFlags = CompilerFlags(),
246                         Flag!"main" includeMain = No.main,
247                         CompilationMode compilationMode = CompilationMode.options)
248         ()
249     {
250         const dubInfo = configToDubInfo[config.value];
251         return objs(dubInfo.targetName,
252                     dubInfo,
253                     includeMain,
254                     compilerFlags.value,
255                     compilationMode);
256     }
257 
258     /**
259        Object files from one dub package
260      */
261     Target[] dubPackageObjects(
262         DubPackageName dubPackageName,
263         CompilerFlags compilerFlags = CompilerFlags(),
264         CompilationMode compilationMode = CompilationMode.all,
265         Flag!"main" includeMain = No.main,
266         )
267         ()
268     {
269         return dubPackageObjects!(
270             dubPackageName,
271             Configuration("default"),
272             compilerFlags,
273             compilationMode,
274             includeMain
275         );
276     }
277 
278     /**
279        Object files from one dub package
280      */
281     Target[] dubPackageObjects(
282         DubPackageName dubPackageName,
283         Configuration config = Configuration("default"),
284         CompilerFlags compilerFlags = CompilerFlags(),
285         CompilationMode compilationMode = CompilationMode.all,
286         Flag!"main" includeMain = No.main,
287         )
288         ()
289     {
290         return configToDubInfo[config.value].packageNameToTargets(
291             dubPackageName.value,
292             includeMain,
293             compilerFlags.value,
294             compilationMode,
295         );
296     }
297 
298 
299     ImportPaths dubImportPaths(Configuration config = Configuration("default"))() {
300         return ImportPaths(configToDubInfo[config.value].allImportPaths);
301     }
302 
303     /**
304        Link a target taking into account the dub linker flags
305      */
306     Target dubLink(TargetName targetName,
307                    Configuration config = Configuration("default"),
308                    alias objsFunction = () { Target[] t; return t; },
309                    LinkerFlags linkerFlags = LinkerFlags()
310         )
311         ()
312     {
313         import std.array: join;
314         return link!(
315             ExeName(targetName.value),
316             objsFunction,
317             Flags((linkerFlags.value ~ configToDubInfo[config.value].linkerFlags).join(" "))
318         );
319     }
320 
321     private Target[] objs(in TargetName targetName,
322                           in DubInfo dubInfo,
323                           in Flag!"main" includeMain,
324                           in string compilerFlags,
325                           in CompilationMode compilationMode,
326                           Target[] extraObjects = [],
327                           in size_t startingIndex = 0)
328     {
329 
330         auto dubObjs = dubInfo.toTargets(includeMain,
331                                          compilerFlags,
332                                          compilationMode,
333                                          dubObjsDir(targetName, dubInfo),
334                                          startingIndex);
335         auto allObjs = dubObjs ~ extraObjects;
336 
337         return allObjs;
338     }
339 
340     private string realName(in TargetName targetName, in DubInfo dubInfo) {
341         import std.path: buildPath;
342         // otherwise the target wouldn't be top-level in the presence of
343         // postBuildCommands
344         auto ret =  dubInfo.postBuildCommands == ""
345             ? targetName.value
346             : buildPath("$project", targetName.value);
347         if(ret == "") ret = "placeholder";
348         return ret;
349     }
350 
351     private auto dubObjsDir(in TargetName targetName, in DubInfo dubInfo) {
352         import reggae.config: options;
353         import reggae.dub.info: DubObjsDir;
354         return DubObjsDir(options.dubObjsDir, realName(targetName, dubInfo) ~ ".objs");
355     }
356 }