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