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