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         projectDir.value,
53     );
54 }
55 
56 Target[] dlangObjectsPerModule(
57     alias sourcesFunc = Sources!(),
58     CompilerFlags compilerFlags = CompilerFlags(),
59     ImportPaths importPaths = ImportPaths(),
60     StringImportPaths stringImportPaths = StringImportPaths(),
61     ProjectDir projectDir = ProjectDir(),
62     )
63     ()
64 {
65     return dlangObjectFilesPerModule(
66         sourcesToFileNames!sourcesFunc,
67         compilerFlags.value,
68         importPaths.value,
69         stringImportPaths.value,
70         [], // implicits
71         projectDir.value,
72     );
73 }
74 
75 
76 
77 /**
78    Generate object file(s) for D sources.
79    Depending on command-line options compiles all files together, per package, or per module.
80 */
81 Target[] dlangObjectFiles(in string[] srcFiles,
82                           in string flags = "",
83                           in string[] importPaths = [],
84                           in string[] stringImportPaths = [],
85                           Target[] implicits = [],
86                           in string projDir = "$project")
87     @safe
88 {
89 
90     import reggae.config: options;
91 
92     auto func = options.perModule
93         ? &dlangObjectFilesPerModule
94         : options.allAtOnce
95             ? &dlangObjectFilesTogether
96             : &dlangObjectFilesPerPackage;
97 
98     return func(srcFiles, flags, importPaths, stringImportPaths, implicits, projDir);
99 }
100 
101 /// Generate object files for D sources, compiling the whole package together.
102 Target[] dlangObjectFilesPerPackage(in string[] srcFiles,
103                                     in string flags = "",
104                                     in string[] importPaths = [],
105                                     in string[] stringImportPaths = [],
106                                     Target[] implicits = [],
107                                     in string projDir = "$project")
108     @trusted pure
109 {
110 
111     if(srcFiles.empty) return [];
112     auto command(in string[] files) {
113         return compileCommand(files[0].packagePath ~ ".d",
114                               flags,
115                               importPaths,
116                               stringImportPaths,
117                               projDir);
118     }
119     return srcFiles.byPackage.map!(a => Target(a[0].packagePath.objFileName,
120                                                command(a),
121                                                a.map!(a => Target(a)).array,
122                                                implicits)).array;
123 }
124 
125 /// Generate object files for D sources, compiling each module separately
126 Target[] dlangObjectFilesPerModule(in string[] srcFiles,
127                                    in string flags = "",
128                                    in string[] importPaths = [],
129                                    in string[] stringImportPaths = [],
130                                    Target[] implicits = [],
131                                    in string projDir = "$project")
132     @trusted pure
133 {
134     return srcFiles.map!(a => objectFile(const SourceFile(a),
135                                          const Flags(flags),
136                                          const ImportPaths(importPaths),
137                                          const StringImportPaths(stringImportPaths),
138                                          implicits,
139                                          projDir)).array;
140 }
141 
142 /// Generate object files for D sources, compiling all of them together
143 Target[] dlangObjectFilesTogether(in string[] srcFiles,
144                                   in string flags = "",
145                                   in string[] importPaths = [],
146                                   in string[] stringImportPaths = [],
147                                   Target[] implicits = [],
148                                   in string projDir = "$project")
149     @trusted pure
150 {
151 
152     if(srcFiles.empty) return [];
153     auto command = compileCommand(srcFiles[0], flags, importPaths, stringImportPaths, projDir);
154     return [Target(srcFiles[0].packagePath.objFileName, command, srcFiles.map!(a => Target(a)).array, implicits)];
155 }
156 
157 
158 /**
159  Currently only works for D. This convenience rule builds a D scriptlike, automatically
160  calculating which files must be compiled in a similar way to rdmd.
161  All paths are relative to projectPath.
162  This template function is provided as a wrapper around the regular runtime version
163  below so it can be aliased without trying to call it at runtime. Basically, it's a
164  way to use the runtime scriptlike without having define a function in reggaefile.d,
165  i.e.:
166  $(D
167  alias myApp = scriptlike!(...);
168  mixin build!(myApp);
169  )
170  vs.
171  $(D
172  Build myBuld() { return scriptlike(..); }
173  )
174  */
175 Target scriptlike(App app,
176                   Flags flags = Flags(),
177                   ImportPaths importPaths = ImportPaths(),
178                   StringImportPaths stringImportPaths = StringImportPaths(),
179                   alias linkWithFunction = () { return cast(Target[])[];})
180     () @trusted
181 {
182     auto linkWith = linkWithFunction();
183     import reggae.config: options;
184     return scriptlike(options.projectPath, app, flags, importPaths, stringImportPaths, linkWith);
185 }
186 
187 
188 //regular runtime version of scriptlike
189 //all paths relative to projectPath
190 //@trusted because of .array
191 Target scriptlike(in string projectPath,
192                   in App app, in Flags flags,
193                   in ImportPaths importPaths,
194                   in StringImportPaths stringImportPaths,
195                   Target[] linkWith)
196     @trusted
197 {
198 
199     import std.path;
200 
201     if(getLanguage(app.srcFileName.value) != Language.D)
202         throw new Exception("'scriptlike' rule only works with D files");
203 
204     auto mainObj = objectFile(SourceFile(app.srcFileName.value), flags, importPaths, stringImportPaths);
205     const output = runDCompiler(projectPath, buildPath(projectPath, app.srcFileName.value), flags.value,
206                                 importPaths.value, stringImportPaths.value);
207 
208     const files = dMainDepSrcs(output).map!(a => a.removeProjectPath).array;
209     auto dependencies = [mainObj] ~ dlangObjectFiles(files, flags.value,
210                                                      importPaths.value, stringImportPaths.value);
211 
212     return link(ExeName(app.exeFileName.value), dependencies ~ linkWith);
213 }
214 
215 
216 //@trusted because of splitter
217 private auto runDCompiler(in string projectPath,
218                           in string srcFileName,
219                           in string flags,
220                           in string[] importPaths,
221                           in string[] stringImportPaths) @trusted {
222 
223     import std.process: execute;
224     import std.exception: enforce;
225     import std.conv:text;
226     import std.path: buildPath;
227 
228     immutable compiler = "dmd";
229     const compArgs = [compiler] ~ flags.splitter.array ~
230         importPaths.map!(a => "-I" ~ buildPath(projectPath, a)).array ~
231         stringImportPaths.map!(a => "-J" ~ buildPath(projectPath, a)).array ~
232         ["-o-", "-v", "-c", srcFileName];
233     const compRes = execute(compArgs);
234     enforce(compRes.status == 0,
235             text("scriptlike could not run ", compArgs.join(" "), ":\n", compRes.output));
236     return compRes.output;
237 }