1 module reggae.rules.common;
2 
3 
4 import reggae.build;
5 import reggae.ctaa;
6 import reggae.types;
7 import std.algorithm;
8 import std.path;
9 import std.array: array;
10 import std.traits;
11 import std.typecons;
12 
13 /**
14  This template function exists so as to be referenced in a reggaefile.d
15  at top-level without being called via $(D alias). That way it can be
16  named and used in a further $(D Target) definition without the need to
17  define a function returning $(D Build).
18  This function gets the source files to be compiled at runtime by searching
19  for source files in the given directories, adding files and filtering
20  as appropriate by the parameters given in $(D sources), its first compile-time
21  parameter. The other parameters are self-explanatory.
22 
23  This function returns a list of targets that are the result of compiling
24  source files written in the supported languages. The $(Sources) function
25  can be used to specify source directories and source files, as well as
26  a filter function to select those files that are actually wanted.
27  */
28 Target[] objectFiles(alias sourcesFunc = Sources!(),
29                      Flags flags = Flags(),
30                      ImportPaths includes = ImportPaths(),
31                      StringImportPaths stringImports = StringImportPaths(),
32     )() @trusted {
33 
34     import std.stdio;
35     const srcFiles = sourcesToFileNames!(sourcesFunc);
36     return srcFilesToObjectTargets(srcFiles, flags, includes, stringImports);
37 }
38 
39 @safe:
40 
41 /**
42  An object file, typically from one source file in a certain language
43  (although for D the default is a whole package). The language is determined
44  by the file extension of the file passed in.
45  The $(D projDir) variable is best left alone; right now only the dub targets
46  make use of it (since dub packages are by definition outside of the project
47  source tree).
48 */
49 Target objectFile(in SourceFile srcFile,
50                   in Flags flags = Flags(),
51                   in ImportPaths includePaths = ImportPaths(),
52                   in StringImportPaths stringImportPaths = StringImportPaths(),
53                   in string projDir = "$project") pure {
54 
55     auto cmd = compileCommand(srcFile.value, flags.value, includePaths.value, stringImportPaths.value, projDir);
56     return Target(srcFile.value.objFileName, cmd, [Target(srcFile.value)]);
57 }
58 
59 /**
60  A binary executable. The same as calling objectFiles and link
61  with these parameters.
62  */
63 Target executable(ExeName exeName,
64                   alias sourcesFunc = Sources!(),
65                   Flags compilerFlags = Flags(),
66                   ImportPaths includes = ImportPaths(),
67                   StringImportPaths stringImports = StringImportPaths(),
68                   Flags linkerFlags = Flags())
69     () {
70     auto objs = objectFiles!(sourcesFunc, compilerFlags, includes, stringImports);
71     return link!(exeName, { return objs; }, linkerFlags);
72 }
73 
74 Target executable(in string projectPath,
75                   in string name,
76                   in string[] srcDirs,
77                   in string[] excDirs,
78                   in string[] srcFiles,
79                   in string[] excFiles,
80                   in string compilerFlags,
81                   in string linkerFlags,
82                   in string[] includes,
83                   in string[] stringImports)
84 {
85     auto objs = objectFiles(projectPath, srcDirs, excDirs, srcFiles, excFiles, compilerFlags, includes, stringImports);
86     return link(ExeName(name), objs, Flags(linkerFlags));
87 }
88 
89 
90 
91 /**
92  "Compile-time" link function.
93  Its parameters are compile-time so that it can be aliased and used
94  at global scope in a reggafile.
95  Links an executable from the given dependency targets. The linker used
96  depends on the file extension of the leaf nodes of the passed-in targets.
97  If any D files are found, the linker is the D compiler, and so on with
98  C++ and C. If none of those apply, the D compiler is used.
99  */
100 Target link(ExeName exeName, alias dependenciesFunc, Flags flags = Flags())() @safe {
101     auto dependencies = dependenciesFunc();
102     return link(exeName, dependencies, flags);
103 }
104 
105 /**
106  Regular run-time link function.
107  Links an executable from the given dependency targets. The linker used
108  depends on the file extension of the leaf nodes of the passed-in targets.
109  If any D files are found, the linker is the D compiler, and so on with
110  C++ and C. If none of those apply, the D compiler is used.
111  */
112 Target link(in ExeName exeName, Target[] dependencies, in Flags flags = Flags()) @safe pure {
113     auto command = Command(CommandType.link,
114                            assocList([assocEntry("flags", flags.value.splitter.array)]));
115     return Target(exeName.value, command, dependencies);
116 }
117 
118 
119 /**
120  Convenience rule for creating static libraries
121  */
122 Target[] staticLibrary(string name,
123                        alias sourcesFunc = Sources!(),
124                        Flags flags = Flags(),
125                        ImportPaths includes = ImportPaths(),
126                        StringImportPaths stringImports = StringImportPaths(),
127                        alias dependenciesFunc = () { Target[] ts; return ts; })
128     ()
129 {
130 
131     version(Posix) {}
132     else
133         static assert(false, "Can only create static libraries on Posix");
134 
135     const srcFiles = sourcesToFileNames!(sourcesFunc);
136     return [Target(buildPath("$builddir", name),
137                    "ar rcs $out $in",
138                    objectFiles!(sourcesFunc, flags, includes, stringImports)() ~ dependenciesFunc())];
139 }
140 
141 /**
142  "Compile-time" target creation.
143  Its parameters are compile-time so that it can be aliased and used
144  at global scope in a reggaefile
145  */
146 Target target(alias outputs,
147               alias command = "",
148               alias dependenciesFunc = () { Target[] ts; return ts; },
149               alias implicitsFunc = () { Target[] ts; return ts; })() @trusted {
150 
151     auto depsRes = dependenciesFunc();
152     auto impsRes = implicitsFunc();
153 
154     static if(isArray!(typeof(depsRes)))
155         auto dependencies = depsRes;
156     else
157         auto dependencies = [depsRes];
158 
159     static if(isArray!(typeof(impsRes)))
160         auto implicits = impsRes;
161     else
162         auto implicits = [impsRes];
163 
164 
165     return Target(outputs, command, dependencies, implicits);
166 }
167 
168 
169 /**
170  * Convenience alias for appending targets without calling any runtime function.
171  * This replaces the need to manually define a function to return a `Build` struct
172  * just to concatenate targets
173  */
174 Target[] targetConcat(T...)() {
175     Target[] ret;
176     foreach(target; T) {
177         static if(isCallable!target)
178             ret ~= target();
179         else
180             ret ~= target;
181     }
182     return ret;
183 }
184 
185 /**
186  Compile-time version of Target.phony
187  */
188 Target phony(string name,
189              string shellCommand,
190              alias dependenciesFunc = () { Target[] ts; return ts; },
191              alias implicitsFunc = () { Target[] ts; return ts; })() {
192     return Target.phony(name, shellCommand, arrayify!dependenciesFunc, arrayify!implicitsFunc);
193 }
194 
195 
196 //end of rules
197 
198 private auto arrayify(alias func)() {
199     import std.traits;
200     auto ret = func();
201     static if(isArray!(typeof(ret)))
202         return ret;
203     else
204         return [ret];
205 }
206 
207 auto sourcesToTargets(alias sourcesFunc = Sources!())() {
208     return sourcesToFileNames!sourcesFunc.map!(a => Target(a));
209 }
210 
211 string[] sourcesToFileNames(alias sourcesFunc = Sources!())() @trusted {
212     import std.exception: enforce;
213     import std.file;
214     import std.path: buildNormalizedPath, buildPath;
215     import std.array: array;
216     import std.traits: isCallable;
217     import reggae.config: options;
218 
219     auto srcs = sourcesFunc();
220 
221     DirEntry[] modules;
222     foreach(dir; srcs.dirs.value.map!(a => buildPath(options.projectPath, a))) {
223         enforce(isDir(dir), dir ~ " is not a directory name");
224         auto entries = dirEntries(dir, SpanMode.depth);
225         auto normalised = entries.map!(a => DirEntry(buildNormalizedPath(a)));
226 
227         modules ~= normalised.filter!(a => !a.isDir).array;
228     }
229 
230     foreach(module_; srcs.files.value) {
231         modules ~= DirEntry(buildNormalizedPath(buildPath(options.projectPath, module_)));
232     }
233 
234     return modules.
235         map!(a => a.name.removeProjectPath).
236         filter!(srcs.filterFunc).
237         filter!(a => a != "reggaefile.d").
238         array;
239 }
240 
241 //run-time version
242 string[] sourcesToFileNames(in string projectPath,
243                             in string[] srcDirs,
244                             const(string)[] excDirs,
245                             in string[] srcFiles,
246                             in string[] excFiles) @trusted {
247 
248 
249 
250     import std.exception: enforce;
251     import std.file;
252     import std.path: buildNormalizedPath, buildPath;
253     import std.array: array;
254     import std.traits: isCallable;
255 
256     excDirs = (excDirs ~ ".reggae").map!(a => buildPath(projectPath, a)).array;
257 
258     DirEntry[] files;
259     foreach(dir; srcDirs.map!(a => buildPath(projectPath, a))) {
260         enforce(isDir(dir), dir ~ " is not a directory name");
261 
262         auto entries = dirEntries(dir, SpanMode.depth)
263                 .map!(a => a.buildNormalizedPath)
264                 .filter!(a => !excDirs.canFind!(b => a.dirName.absolutePath.startsWith(b)));
265         files ~= entries.map!(a => DirEntry(a)).array;
266     }
267 
268     foreach(module_; srcFiles) {
269         files ~= DirEntry(buildNormalizedPath(buildPath(projectPath, module_)));
270     }
271 
272     return files.
273         map!(a => removeProjectPath(projectPath, a.name)).
274         filter!(a => !excFiles.canFind(a)).
275         filter!(a => a != "reggaefile.d").
276         array;
277 }
278 
279 
280 //run-time version
281 Target[] objectFiles(in string projectPath,
282                      in string[] srcDirs,
283                      in string[] excDirs,
284                      in string[] srcFiles,
285                      in string[] excFiles,
286                      in string flags = "",
287                      in string[] includes = [],
288                      in string[] stringImports = []) @trusted {
289 
290     return srcFilesToObjectTargets(sourcesToFileNames(projectPath, srcDirs, excDirs, srcFiles, excFiles),
291                                    Flags(flags),
292                                    const ImportPaths(includes),
293                                    const StringImportPaths(stringImports));
294 }
295 
296 //run-time version
297 Target[] staticLibrary(in string projectPath,
298                        in string name,
299                        in string[] srcDirs,
300                        in string[] excDirs,
301                        in string[] srcFiles,
302                        in string[] excFiles,
303                        in string flags,
304                        in string[] includes,
305                        in string[] stringImports) @trusted {
306 
307 
308     version(Posix) {}
309     else
310         static assert(false, "Can only create static libraries on Posix");
311 
312     return [Target([buildPath("$builddir", name)],
313                    "ar rcs $out $in",
314                    objectFiles(projectPath, srcDirs, excDirs, srcFiles, excFiles, flags, includes, stringImports))];
315 }
316 
317 
318 private Target[] srcFilesToObjectTargets(in string[] srcFiles,
319                                          in Flags flags,
320                                          in ImportPaths includes,
321                                          in StringImportPaths stringImports) {
322 
323     const dSrcs = srcFiles.filter!(a => a.getLanguage == Language.D).array;
324     auto otherSrcs = srcFiles.filter!(a => a.getLanguage != Language.D && a.getLanguage != Language.unknown);
325     import reggae.rules.d: dlangPackageObjectFiles;
326     return dlangPackageObjectFiles(dSrcs, flags.value, ["."] ~ includes.value, stringImports.value) ~
327         otherSrcs.map!(a => objectFile(SourceFile(a), flags, includes)).array;
328 
329 }
330 
331 
332 version(Windows) {
333     immutable objExt = ".obj";
334     immutable exeExt = ".exe";
335 } else {
336     immutable objExt = ".o";
337     immutable exeExt = "";
338 }
339 
340 package string objFileName(in string srcFileName) pure {
341     import std.path: stripExtension, defaultExtension, isRooted, stripDrive;
342     import std.array: replace;
343 
344     immutable localFileName = srcFileName.isRooted
345         ? srcFileName.stripDrive[1..$]
346         : srcFileName;
347     return localFileName.stripExtension.defaultExtension(objExt).replace("..", "__");
348 }
349 
350 string removeProjectPath(in string path) {
351     import std.path: relativePath, absolutePath;
352     import reggae.config: options;
353     //relativePath is @system
354     return () @trusted { return path.absolutePath.relativePath(options.projectPath.absolutePath); }();
355 }
356 
357 string removeProjectPath(in string projectPath, in string path) pure {
358     import std.path: relativePath, absolutePath;
359     //relativePath is @system
360     return () @trusted { return path.absolutePath.relativePath(projectPath.absolutePath); }();
361 }
362 
363 
364 
365 Command compileCommand(in string srcFileName,
366                        in string flags = "",
367                        in string[] includePaths = [],
368                        in string[] stringImportPaths = [],
369                        in string projDir = "$project",
370                        Flag!"justCompile" justCompile = Yes.justCompile) pure {
371 
372     string maybeExpand(string path) {
373         return path.startsWith(gBuilddir) ? expandBuildDir(path) : buildPath(projDir, path);
374     }
375 
376     auto includeParams = includePaths.map!(a => "-I" ~ maybeExpand(a)). array;
377     auto flagParams = flags.splitter.array;
378     immutable language = getLanguage(srcFileName);
379 
380     auto params = [assocEntry("includes", includeParams),
381                    assocEntry("flags", flagParams)];
382 
383     if(language == Language.D)
384         params ~= assocEntry("stringImports",
385                              stringImportPaths.map!(a => "-J" ~ maybeExpand(a)).array);
386 
387     params ~= assocEntry("DEPFILE", [srcFileName.objFileName ~ ".dep"]);
388 
389     immutable type = justCompile ? CommandType.compile : CommandType.compileAndLink;
390     return Command(type, assocList(params));
391 }
392 
393 
394 enum Language {
395     C,
396     Cplusplus,
397     D,
398     unknown,
399 }
400 
401 Language getLanguage(in string srcFileName) pure nothrow {
402     switch(srcFileName.extension) with(Language) {
403     case ".d":
404         return D;
405     case ".cpp":
406     case ".CPP":
407     case ".C":
408     case ".cxx":
409     case ".c++":
410     case ".cc":
411         return Cplusplus;
412     case ".c":
413         return C;
414     default:
415         return unknown;
416     }
417 }