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.dependencies: dMainDepSrcs;
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         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     auto command(in string[] files) {
114         return compileCommand(files[0].packagePath ~ ".d",
115                               flags,
116                               importPaths,
117                               stringImportPaths,
118                               projDir);
119     }
120     return srcFiles.byPackage.map!(a => Target(a[0].packagePath.objFileName,
121                                                command(a),
122                                                a.map!(a => Target(a)).array,
123                                                implicits)).array;
124 }
125 
126 /// Generate object files for D sources, compiling each module separately
127 Target[] dlangObjectFilesPerModule(in string[] srcFiles,
128                                    in string flags = "",
129                                    in string[] importPaths = [],
130                                    in string[] stringImportPaths = [],
131                                    Target[] implicits = [],
132                                    in string projDir = "$project")
133     @trusted pure
134 {
135     return srcFiles.map!(a => objectFile(const SourceFile(a),
136                                          const Flags(flags),
137                                          const ImportPaths(importPaths),
138                                          const StringImportPaths(stringImportPaths),
139                                          implicits,
140                                          projDir)).array;
141 }
142 
143 /// Generate object files for D sources, compiling all of them together
144 Target[] dlangObjectFilesTogether(in string[] srcFiles,
145                                   in string flags = "",
146                                   in string[] importPaths = [],
147                                   in string[] stringImportPaths = [],
148                                   Target[] implicits = [],
149                                   in string projDir = "$project")
150     @trusted pure
151 {
152 
153     if(srcFiles.empty) return [];
154     auto command = compileCommand(srcFiles[0], flags, importPaths, stringImportPaths, projDir);
155     return [Target(srcFiles[0].packagePath.objFileName, command, srcFiles.map!(a => Target(a)).array, implicits)];
156 }
157 
158 
159 /**
160  Currently only works for D. This convenience rule builds a D scriptlike, automatically
161  calculating which files must be compiled in a similar way to rdmd.
162  All paths are relative to projectPath.
163  This template function is provided as a wrapper around the regular runtime version
164  below so it can be aliased without trying to call it at runtime. Basically, it's a
165  way to use the runtime scriptlike without having define a function in reggaefile.d,
166  i.e.:
167  $(D
168  alias myApp = scriptlike!(...);
169  mixin build!(myApp);
170  )
171  vs.
172  $(D
173  Build myBuld() { return scriptlike(..); }
174  )
175  */
176 Target scriptlike(App app,
177                   Flags flags = Flags(),
178                   ImportPaths importPaths = ImportPaths(),
179                   StringImportPaths stringImportPaths = StringImportPaths(),
180                   alias linkWithFunction = () { return cast(Target[])[];})
181     () @trusted
182 {
183     auto linkWith = linkWithFunction();
184     import reggae.config: options;
185     return scriptlike(options.projectPath, app, flags, importPaths, stringImportPaths, linkWith);
186 }
187 
188 
189 //regular runtime version of scriptlike
190 //all paths relative to projectPath
191 //@trusted because of .array
192 Target scriptlike(in string projectPath,
193                   in App app, in Flags flags,
194                   in ImportPaths importPaths,
195                   in StringImportPaths stringImportPaths,
196                   Target[] linkWith)
197     @trusted
198 {
199 
200     import std.path;
201 
202     if(getLanguage(app.srcFileName.value) != Language.D)
203         throw new Exception("'scriptlike' rule only works with D files");
204 
205     auto mainObj = objectFile(SourceFile(app.srcFileName.value), flags, importPaths, stringImportPaths);
206     const output = runDCompiler(projectPath, buildPath(projectPath, app.srcFileName.value), flags.value,
207                                 importPaths.value, stringImportPaths.value);
208 
209     const files = dMainDepSrcs(output).map!(a => a.removeProjectPath).array;
210     auto dependencies = [mainObj] ~ dlangObjectFiles(files, flags.value,
211                                                      importPaths.value, stringImportPaths.value);
212 
213     return link(ExeName(app.exeFileName.value), dependencies ~ linkWith);
214 }
215 
216 
217 //@trusted because of splitter
218 private auto runDCompiler(in string projectPath,
219                           in string srcFileName,
220                           in string flags,
221                           in string[] importPaths,
222                           in string[] stringImportPaths) @trusted {
223 
224     import std.process: execute;
225     import std.exception: enforce;
226     import std.conv:text;
227     import std.path: buildPath;
228 
229     immutable compiler = "dmd";
230     const compArgs = [compiler] ~ flags.splitter.array ~
231         importPaths.map!(a => "-I" ~ buildPath(projectPath, a)).array ~
232         stringImportPaths.map!(a => "-J" ~ buildPath(projectPath, a)).array ~
233         ["-o-", "-v", "-c", srcFileName];
234     const compRes = execute(compArgs);
235     enforce(compRes.status == 0,
236             text("scriptlike could not run ", compArgs.join(" "), ":\n", compRes.output));
237     return compRes.output;
238 }