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;
176         import reggae.config: options;
177         import reggae.dub.info: DubObjsDir;
178         import std.array: join;
179         import std.path: buildPath;
180         import std.file: getcwd;
181 
182         const isStaticLibrary =
183             dubInfo.targetType == TargetType.library ||
184             dubInfo.targetType == TargetType.staticLibrary;
185         const sharedFlags = dubInfo.targetType == TargetType.dynamicLibrary
186             ? "-shared"
187             : "";
188         const allLinkerFlags = (linkerFlags ~ dubInfo.linkerFlags ~ sharedFlags).join(" ");
189 
190         auto allObjs = objs(targetName,
191                             dubInfo,
192                             includeMain,
193                             compilerFlags,
194                             compilationMode,
195                             extraObjects,
196                             startingIndex);
197 
198         const name = realName(targetName, dubInfo);
199         auto target = isStaticLibrary
200             ? staticLibraryTarget(name, allObjs)[0]
201             : link(ExeName(name),
202                    allObjs,
203                    Flags(allLinkerFlags));
204 
205         return dubInfo.postBuildCommands == ""
206             ? target
207             : Target.phony("postBuild", dubInfo.postBuildCommands, target);
208     }
209 
210     /**
211        All dub packages object files from the dependencies, but nothing from the
212        main package (the one actually being built).
213      */
214     Target[] dubDependencies(CompilerFlags compilerFlags = CompilerFlags())
215         () // runtime args
216     {
217         return dubDependencies!(Configuration("default"), compilerFlags)();
218     }
219 
220 
221     ///ditto
222     Target[] dubDependencies(Configuration config,
223                              CompilerFlags compilerFlags = CompilerFlags())
224         () // runtime args
225     {
226         const dubInfo = configToDubInfo[config.value];
227         const startingIndex = 1;
228         return objs(dubInfo.targetName,
229                     dubInfo,
230                     No.main,
231                     compilerFlags.value,
232                     CompilationMode.options,
233                     [], // extra objects
234                     startingIndex);
235     }
236 
237 
238 
239     /**
240        All dub object files for a configuration
241      */
242     Target[] dubObjects(Configuration config,
243                         CompilerFlags compilerFlags = CompilerFlags(),
244                         Flag!"main" includeMain = No.main,
245                         CompilationMode compilationMode = CompilationMode.options)
246         ()
247     {
248         const dubInfo = configToDubInfo[config.value];
249         return objs(dubInfo.targetName,
250                     dubInfo,
251                     includeMain,
252                     compilerFlags.value,
253                     compilationMode);
254     }
255 
256     /**
257        Object files from one dub package
258      */
259     Target[] dubPackageObjects(
260         DubPackageName dubPackageName,
261         CompilerFlags compilerFlags = CompilerFlags(),
262         CompilationMode compilationMode = CompilationMode.all,
263         Flag!"main" includeMain = No.main,
264         )
265         ()
266     {
267         return dubPackageObjects!(
268             dubPackageName,
269             Configuration("default"),
270             compilerFlags,
271             compilationMode,
272             includeMain
273         );
274     }
275 
276     /**
277        Object files from one dub package
278      */
279     Target[] dubPackageObjects(
280         DubPackageName dubPackageName,
281         Configuration config = Configuration("default"),
282         CompilerFlags compilerFlags = CompilerFlags(),
283         CompilationMode compilationMode = CompilationMode.all,
284         Flag!"main" includeMain = No.main,
285         )
286         ()
287     {
288         return configToDubInfo[config.value].packageNameToTargets(
289             dubPackageName.value,
290             includeMain,
291             compilerFlags.value,
292             compilationMode,
293         );
294     }
295 
296 
297     ImportPaths dubImportPaths(Configuration config = Configuration("default"))() {
298         return ImportPaths(configToDubInfo[config.value].allImportPaths);
299     }
300 
301     /**
302        Link a target taking into account the dub linker flags
303      */
304     Target dubLink(TargetName targetName,
305                    Configuration config = Configuration("default"),
306                    alias objsFunction = () { Target[] t; return t; },
307                    LinkerFlags linkerFlags = LinkerFlags()
308         )
309         ()
310     {
311         import std.array: join;
312         return link!(
313             ExeName(targetName.value),
314             objsFunction,
315             Flags((linkerFlags.value ~ configToDubInfo[config.value].linkerFlags).join(" "))
316         );
317     }
318 
319     private Target[] objs(in TargetName targetName,
320                           in DubInfo dubInfo,
321                           in Flag!"main" includeMain,
322                           in string compilerFlags,
323                           in CompilationMode compilationMode,
324                           Target[] extraObjects = [],
325                           in size_t startingIndex = 0)
326     {
327 
328         auto dubObjs = dubInfo.toTargets(includeMain,
329                                          compilerFlags,
330                                          compilationMode,
331                                          dubObjsDir(targetName, dubInfo),
332                                          startingIndex);
333         auto allObjs = dubObjs ~ extraObjects;
334 
335         return allObjs;
336     }
337 
338     private string realName(in TargetName targetName, in DubInfo dubInfo) {
339         import std.path: buildPath;
340         // otherwise the target wouldn't be top-level in the presence of
341         // postBuildCommands
342         return dubInfo.postBuildCommands == ""
343             ? targetName.value
344             : buildPath("$project", targetName.value);
345     }
346 
347     private auto dubObjsDir(in TargetName targetName, in DubInfo dubInfo) {
348         import reggae.config: options;
349         import reggae.dub.info: DubObjsDir;
350         return DubObjsDir(options.dubObjsDir, realName(targetName, dubInfo) ~ ".objs");
351     }
352 }