1 
2 /**
3 High-level rules for compiling D files. For a D-only application with
4 no dub dependencies, $(D scriptlike) should suffice. If the app depends
5 on dub packages, consult the reggae.rules.dub module instead.
6  */
7 
8 module reggae.rules.d;
9 
10 import reggae.types;
11 import reggae.build;
12 import reggae.sorting;
13 import reggae.rules.common;
14 import std.algorithm;
15 import std.array;
16 
17 
18 Target[] dlangObjects(
19     alias sourcesFunc = Sources!(),
20     CompilerFlags compilerFlags = CompilerFlags(),
21     ImportPaths importPaths = ImportPaths(),
22     StringImportPaths stringImportPaths = StringImportPaths(),
23     ProjectDir projectDir = ProjectDir(),
24     )
25     ()
26 {
27     return dlangObjectFiles(
28         sourcesToFileNames!sourcesFunc,
29         compilerFlags.value,
30         importPaths.value,
31         stringImportPaths.value,
32         [], // implicits
33         projectDir.value,
34     );
35 }
36 
37 
38 Target[] dlangObjectsPerPackage(
39     alias sourcesFunc = Sources!(),
40     CompilerFlags compilerFlags = CompilerFlags(),
41     ImportPaths importPaths = ImportPaths(),
42     StringImportPaths stringImportPaths = StringImportPaths(),
43     ProjectDir projectDir = ProjectDir(),
44     )
45     ()
46 {
47     return dlangObjectFilesPerPackage(
48         sourcesToFileNames!sourcesFunc,
49         compilerFlags.value,
50         importPaths.value,
51         stringImportPaths.value,
52         [], // implicits
53         projectDir.value,
54     );
55 }
56 
57 Target[] dlangObjectsPerModule(
58     alias sourcesFunc = Sources!(),
59     CompilerFlags compilerFlags = CompilerFlags(),
60     ImportPaths importPaths = ImportPaths(),
61     StringImportPaths stringImportPaths = StringImportPaths(),
62     ProjectDir projectDir = ProjectDir(),
63     )
64     ()
65 {
66     return dlangObjectFilesPerModule(
67         sourcesToFileNames!sourcesFunc,
68         compilerFlags.value,
69         importPaths.value,
70         stringImportPaths.value,
71         [], // implicits
72         projectDir.value,
73     );
74 }
75 
76 
77 
78 /**
79    Generate object file(s) for D sources.
80    Depending on command-line options compiles all files together, per package, or per module.
81 */
82 Target[] dlangObjectFiles(in string[] srcFiles,
83                           in string flags = "",
84                           in string[] importPaths = [],
85                           in string[] stringImportPaths = [],
86                           Target[] implicits = [],
87                           in string projDir = "$project")
88     @safe
89 {
90 
91     import reggae.config: options;
92 
93     auto func = options.perModule
94         ? &dlangObjectFilesPerModule
95         : options.allAtOnce
96             ? &dlangObjectFilesTogether
97             : &dlangObjectFilesPerPackage;
98 
99     return func(srcFiles, flags, importPaths, stringImportPaths, implicits, projDir);
100 }
101 
102 /// Generate object files for D sources, compiling the whole package together.
103 Target[] dlangObjectFilesPerPackage(in string[] srcFiles,
104                                     in string flags = "",
105                                     in string[] importPaths = [],
106                                     in string[] stringImportPaths = [],
107                                     Target[] implicits = [],
108                                     in string projDir = "$project")
109     @trusted pure
110 {
111 
112     if(srcFiles.empty) return [];
113 
114     auto command(in string[] files) {
115         return compileCommand(files[0].packagePath ~ ".d",
116                               flags,
117                               importPaths,
118                               stringImportPaths,
119                               projDir);
120     }
121 
122     // the object file for a D package containing pkgFiles
123     static string outputFileName(in string[] pkgFiles) {
124         import std.path: baseName;
125         const path = packagePath(pkgFiles[0]) ~ "_" ~ pkgFiles[0].baseName(".d");
126         return objFileName(path);
127     }
128 
129     return srcFiles
130         .byPackage
131         .map!(a => Target(outputFileName(a),
132                           command(a),
133                           a.map!(a => Target(a)).array,
134                           implicits))
135         .array;
136 }
137 
138 /// Generate object files for D sources, compiling each module separately
139 Target[] dlangObjectFilesPerModule(in string[] srcFiles,
140                                    in string flags = "",
141                                    in string[] importPaths = [],
142                                    in string[] stringImportPaths = [],
143                                    Target[] implicits = [],
144                                    in string projDir = "$project")
145     @trusted pure
146 {
147     return srcFiles
148         .map!(a => objectFile(const SourceFile(a),
149                               const Flags(flags),
150                               const ImportPaths(importPaths),
151                               const StringImportPaths(stringImportPaths),
152                               implicits,
153                               projDir))
154         .array;
155 }
156 
157 /// Generate object files for D sources, compiling all of them together
158 Target[] dlangObjectFilesTogether(in string[] srcFiles,
159                                   in string flags = "",
160                                   in string[] importPaths = [],
161                                   in string[] stringImportPaths = [],
162                                   Target[] implicits = [],
163                                   in string projDir = "$project")
164     @safe pure
165 {
166     import reggae.rules.common: objFileName;
167     return dlangTargetTogether(
168         &objFileName,
169         srcFiles,
170         flags,
171         importPaths,
172         stringImportPaths,
173         implicits,
174         projDir
175     );
176 }
177 
178 
179 /**
180    Generate a static library for D sources, compiling all of them together.
181    With dmd, this results in a different static library than compiling the
182    source into object files then using `ar` to create the .a.
183 */
184 Target[] dlangStaticLibraryTogether(in string[] srcFiles,
185                                     in string flags = "",
186                                     in string[] importPaths = [],
187                                     in string[] stringImportPaths = [],
188                                     Target[] implicits = [],
189                                     in string projDir = "$project")
190     @safe pure
191 {
192     import reggae.rules.common: libFileName;
193     return dlangTargetTogether(
194         &libFileName,
195         srcFiles,
196         "-lib " ~ flags,
197         importPaths,
198         stringImportPaths,
199         implicits,
200         projDir
201     );
202 }
203 
204 
205 private Target[] dlangTargetTogether(
206     string function(in string) @safe pure toFileName,
207     in string[] srcFiles,
208     in string flags = "",
209     in string[] importPaths = [],
210     in string[] stringImportPaths = [],
211     Target[] implicits = [],
212     in string projDir = "$project",
213     )
214     @safe pure
215 {
216     if(srcFiles.empty) return [];
217 
218     // when building a .o or .a for multiple source files, this generates a name
219     // designed to avoid filename clashes (see arsd-official)
220     string outputNameForSrcFiles() @safe pure {
221         import reggae.sorting: packagePath;
222         import std.array: join;
223         import std.path: stripExtension, baseName;
224         import std.range: take;
225 
226         // then number in `take` is arbitrary but larger than 1 to try to get
227         // unique file names without making the file name too long.
228         const name = srcFiles
229             .take(4)
230             .map!baseName
231             .map!stripExtension
232             .join("_")
233 
234             ~ ".d";
235         return packagePath(srcFiles[0]) ~ "_" ~ name;
236     }
237 
238     const outputFileName = toFileName(outputNameForSrcFiles);
239     auto command = compileCommand(srcFiles[0], flags, importPaths, stringImportPaths, projDir);
240 
241     return [Target(outputFileName, command, srcFiles.map!(a => Target(a)).array, implicits)];
242 }
243 
244 
245 
246 
247 
248 
249 /**
250  Currently only works for D. This convenience rule builds a D scriptlike, automatically
251  calculating which files must be compiled in a similar way to rdmd.
252  All paths are relative to projectPath.
253  This template function is provided as a wrapper around the regular runtime version
254  below so it can be aliased without trying to call it at runtime. Basically, it's a
255  way to use the runtime scriptlike without having define a function in reggaefile.d,
256  i.e.:
257  $(D
258  alias myApp = scriptlike!(...);
259  mixin build!(myApp);
260  )
261  vs.
262  $(D
263  Build myBuld() { return scriptlike(..); }
264  )
265  */
266 Target scriptlike(App app,
267                   Flags flags = Flags(),
268                   ImportPaths importPaths = ImportPaths(),
269                   StringImportPaths stringImportPaths = StringImportPaths(),
270                   alias linkWithFunction = () { return cast(Target[])[];})
271     () @trusted
272 {
273     auto linkWith = linkWithFunction();
274     import reggae.config: options;
275     return scriptlike(options.projectPath, app, flags, importPaths, stringImportPaths, linkWith);
276 }
277 
278 
279 //regular runtime version of scriptlike
280 //all paths relative to projectPath
281 //@trusted because of .array
282 Target scriptlike
283     ()
284     (in string projectPath,
285      in App app, in Flags flags,
286      in ImportPaths importPaths,
287      in StringImportPaths stringImportPaths,
288      Target[] linkWith)
289     @trusted
290 {
291 
292     import reggae.dependencies: dMainDepSrcs;
293     import std.path;
294 
295     if(getLanguage(app.srcFileName.value) != Language.D)
296         throw new Exception("'scriptlike' rule only works with D files");
297 
298     auto mainObj = objectFile(SourceFile(app.srcFileName.value), flags, importPaths, stringImportPaths);
299     const output = runDCompiler(projectPath, buildPath(projectPath, app.srcFileName.value), flags.value,
300                                 importPaths.value, stringImportPaths.value);
301 
302     const files = dMainDepSrcs(output).map!(a => a.removeProjectPath).array;
303     auto dependencies = [mainObj] ~ dlangObjectFiles(files, flags.value,
304                                                      importPaths.value, stringImportPaths.value);
305 
306     return link(ExeName(app.exeFileName.value), dependencies ~ linkWith);
307 }
308 
309 
310 //@trusted because of splitter
311 private auto runDCompiler(in string projectPath,
312                           in string srcFileName,
313                           in string flags,
314                           in string[] importPaths,
315                           in string[] stringImportPaths) @trusted {
316 
317     import std.process: execute;
318     import std.exception: enforce;
319     import std.conv:text;
320     import std.path: buildPath;
321 
322     immutable compiler = "dmd";
323     const compArgs = [compiler] ~ flags.splitter.array ~
324         importPaths.map!(a => "-I" ~ buildPath(projectPath, a)).array ~
325         stringImportPaths.map!(a => "-J" ~ buildPath(projectPath, a)).array ~
326         ["-o-", "-v", "-c", srcFileName];
327     const compRes = execute(compArgs);
328     enforce(compRes.status == 0,
329             text("scriptlike could not run ", compArgs.join(" "), ":\n", compRes.output));
330     return compRes.output;
331 }