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