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 
40 /**
41  An object file, typically from one source file in a certain language
42  (although for D the default is a whole package). The language is determined
43  by the file extension of the file passed in.
44  The $(D projDir) variable is best left alone; right now only the dub targets
45  make use of it (since dub packages are by definition outside of the project
46  source tree).
47 */
48 Target objectFile(in SourceFile srcFile,
49                   in Flags flags = Flags(),
50                   in ImportPaths includePaths = ImportPaths(),
51                   in StringImportPaths stringImportPaths = StringImportPaths(),
52                   Target[] implicits = [],
53                   in string projDir = "$project") @safe 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)], implicits);
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())() {
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         return staticLibraryTarget(name, objectFiles!(sourcesFunc, flags, includes, stringImports)() ~ dependenciesFunc());
133     else
134         throw new Exception("Can only create static libraries on Posix");
135 }
136 
137 /**
138  "Compile-time" target creation.
139  Its parameters are compile-time so that it can be aliased and used
140  at global scope in a reggaefile
141  */
142 Target target(alias outputs,
143               alias command = "",
144               alias dependenciesFunc = () { Target[] ts; return ts; },
145               alias implicitsFunc = () { Target[] ts; return ts; })() @trusted {
146 
147     auto depsRes = dependenciesFunc();
148     auto impsRes = implicitsFunc();
149 
150     static if(isArray!(typeof(depsRes)))
151         auto dependencies = depsRes;
152     else
153         auto dependencies = [depsRes];
154 
155     static if(isArray!(typeof(impsRes)))
156         auto implicits = impsRes;
157     else
158         auto implicits = [impsRes];
159 
160 
161     return Target(outputs, command, dependencies, implicits);
162 }
163 
164 
165 /**
166  * Convenience alias for appending targets without calling any runtime function.
167  * This replaces the need to manually define a function to return a `Build` struct
168  * just to concatenate targets
169  */
170 Target[] targetConcat(T...)() {
171     Target[] ret;
172     foreach(target; T) {
173         static if(isCallable!target)
174             ret ~= target();
175         else
176             ret ~= target;
177     }
178     return ret;
179 }
180 
181 /**
182  Compile-time version of Target.phony
183  */
184 Target phony(string name,
185              string shellCommand,
186              alias dependenciesFunc = () { Target[] ts; return ts; },
187              alias implicitsFunc = () { Target[] ts; return ts; })() {
188     return Target.phony(name, shellCommand, arrayify!dependenciesFunc, arrayify!implicitsFunc);
189 }
190 
191 
192 //end of rules
193 
194 private auto arrayify(alias func)() {
195     import std.traits;
196     auto ret = func();
197     static if(isArray!(typeof(ret)))
198         return ret;
199     else
200         return [ret];
201 }
202 
203 auto sourcesToTargets(alias sourcesFunc = Sources!())() {
204     return sourcesToFileNames!sourcesFunc.map!(a => Target(a));
205 }
206 
207 string[] sourcesToFileNames(alias sourcesFunc = Sources!())() @trusted {
208     import std.exception: enforce;
209     import std.file;
210     import std.path: buildNormalizedPath, buildPath;
211     import std.array: array;
212     import std.traits: isCallable;
213     import reggae.config: options;
214 
215     auto srcs = sourcesFunc();
216 
217     DirEntry[] modules;
218     foreach(dir; srcs.dirs.value.map!(a => buildPath(options.projectPath, a))) {
219         enforce(isDir(dir), dir ~ " is not a directory name");
220         auto entries = dirEntries(dir, SpanMode.depth);
221         auto normalised = entries.map!(a => DirEntry(buildNormalizedPath(a)));
222 
223         modules ~= normalised.filter!(a => !a.isDir).array;
224     }
225 
226     foreach(module_; srcs.files.value) {
227         modules ~= DirEntry(buildNormalizedPath(buildPath(options.projectPath, module_)));
228     }
229 
230     return modules.
231         map!(a => a.name.removeProjectPath).
232         filter!(srcs.filterFunc).
233         filter!(a => a != "reggaefile.d").
234         array;
235 }
236 
237 //run-time version
238 string[] sourcesToFileNames(in string projectPath,
239                             in string[] srcDirs,
240                             const(string)[] excDirs,
241                             in string[] srcFiles,
242                             in string[] excFiles) @trusted {
243 
244 
245 
246     import std.exception: enforce;
247     import std.file;
248     import std.path: buildNormalizedPath, buildPath;
249     import std.array: array;
250     import std.traits: isCallable;
251 
252     excDirs = (excDirs ~ ".reggae").map!(a => buildPath(projectPath, a)).array;
253 
254     DirEntry[] files;
255     foreach(dir; srcDirs.map!(a => buildPath(projectPath, a))) {
256         enforce(isDir(dir), dir ~ " is not a directory name");
257 
258         auto entries = dirEntries(dir, SpanMode.depth)
259                 .map!(a => a.buildNormalizedPath)
260                 .filter!(a => !excDirs.canFind!(b => a.dirName.absolutePath.startsWith(b)));
261         files ~= entries.map!(a => DirEntry(a)).array;
262     }
263 
264     foreach(module_; srcFiles) {
265         files ~= DirEntry(buildNormalizedPath(buildPath(projectPath, module_)));
266     }
267 
268     return files.
269         map!(a => removeProjectPath(projectPath, a.name)).
270         filter!(a => !excFiles.canFind(a)).
271         filter!(a => a != "reggaefile.d").
272         array;
273 }
274 
275 
276 //run-time version
277 Target[] objectFiles(in string projectPath,
278                      in string[] srcDirs,
279                      in string[] excDirs,
280                      in string[] srcFiles,
281                      in string[] excFiles,
282                      in string flags = "",
283                      in string[] includes = [],
284                      in string[] stringImports = []) @trusted {
285 
286     return srcFilesToObjectTargets(sourcesToFileNames(projectPath, srcDirs, excDirs, srcFiles, excFiles),
287                                    Flags(flags),
288                                    const ImportPaths(includes),
289                                    const StringImportPaths(stringImports));
290 }
291 
292 //run-time version
293 Target[] staticLibrary(in string projectPath,
294                        in string name,
295                        in string[] srcDirs,
296                        in string[] excDirs,
297                        in string[] srcFiles,
298                        in string[] excFiles,
299                        in string flags,
300                        in string[] includes,
301                        in string[] stringImports) @trusted {
302 
303 
304     version(Posix)
305         return staticLibraryTarget(
306             name,
307             objectFiles(projectPath, srcDirs, excDirs, srcFiles, excFiles, flags, includes, stringImports)
308         );
309     else
310         throw new Exception("Can only create static libraries on Posix");
311 }
312 
313 Target[] staticLibraryTarget(in string name, Target[] objects) @safe pure {
314     import std.path: extension;
315     const realName = name.extension == libExt ? name : name ~ libExt;
316     auto target = Target(
317         [buildPath("$builddir", realName)],
318         staticLibraryShellCommand,
319         objects,
320     );
321     return [target];
322 }
323 
324 private enum staticLibraryShellCommand = "ar rcs $out $in";
325 
326 private Target[] srcFilesToObjectTargets(in string[] srcFiles,
327                                          in Flags flags,
328                                          in ImportPaths includes,
329                                          in StringImportPaths stringImports) {
330 
331     const dSrcs = srcFiles.filter!(a => a.getLanguage == Language.D).array;
332     auto otherSrcs = srcFiles.filter!(a => a.getLanguage != Language.D && a.getLanguage != Language.unknown);
333     import reggae.rules.d: dlangObjectFiles;
334     return
335         dlangObjectFiles(dSrcs, flags.value, ["."] ~ includes.value, stringImports.value) ~
336         otherSrcs.map!(a => objectFile(SourceFile(a), flags, includes)).array;
337 }
338 
339 
340 version(Windows) {
341     immutable objExt = ".obj";
342     immutable exeExt = ".exe";
343     immutable libExt = ".lib";
344 } else {
345     immutable objExt = ".o";
346     immutable exeExt = "";
347     immutable libExt = ".a";
348 }
349 
350 string objFileName(in string srcFileName) @safe pure {
351     return extFileName(srcFileName, objExt);
352 }
353 
354 string libFileName(in string srcFileName) @safe pure {
355     return extFileName(srcFileName, libExt);
356 }
357 
358 
359 string extFileName(in string srcFileName, in string extension) @safe pure {
360     import reggae.path: deabsolutePath;
361     import std.path: stripExtension;
362     import std.array: replace;
363 
364     auto tmp = srcFileName
365         .deabsolutePath
366         .stripExtension
367         ;
368 
369     return (tmp ~ extension).replace("..", "__");
370 }
371 
372 
373 string removeProjectPath(in string path) @safe {
374     import std.path: relativePath, absolutePath;
375     import reggae.config: options;
376     //relativePath is @system
377     return () @trusted { return path.absolutePath.relativePath(options.projectPath.absolutePath); }();
378 }
379 
380 string removeProjectPath(in string projectPath, in string path) @safe pure {
381     import std.path: relativePath, absolutePath;
382     //relativePath is @system
383     return () @trusted { return path.absolutePath.relativePath(projectPath.absolutePath); }();
384 }
385 
386 
387 
388 Command compileCommand(in string srcFileName,
389                        in string flags = "",
390                        in string[] includePaths = [],
391                        in string[] stringImportPaths = [],
392                        in string projDir = "$project",
393                        Flag!"justCompile" justCompile = Yes.justCompile)
394     @safe pure
395 {
396 
397     string maybeExpand(string path) {
398         return path.startsWith(gBuilddir)
399             ? expandBuildDir(path)
400             : buildPath(projDir, path);
401     }
402 
403     auto includeParams = includePaths.map!(a => "-I" ~ maybeExpand(a)). array;
404     auto flagParams = flags.splitter.array;
405     immutable language = getLanguage(srcFileName);
406 
407     auto params = [
408         assocEntry("includes", includeParams),
409         assocEntry("flags", flagParams)
410     ];
411 
412     if(language == Language.D)
413         params ~= assocEntry("stringImports",
414                              stringImportPaths.map!(a => "-J" ~ maybeExpand(a)).array);
415 
416     params ~= assocEntry("DEPFILE", [srcFileName.objFileName ~ ".dep"]);
417 
418     immutable type = justCompile ? CommandType.compile : CommandType.compileAndLink;
419     return Command(type, assocList(params));
420 }
421 
422 
423 enum Language {
424     C,
425     Cplusplus,
426     D,
427     unknown,
428 }
429 
430 Language getLanguage(in string srcFileName) @safe pure nothrow {
431     switch(srcFileName.extension) with(Language) {
432     case ".d":
433         return D;
434     case ".cpp":
435     case ".CPP":
436     case ".C":
437     case ".cxx":
438     case ".c++":
439     case ".cc":
440         return Cplusplus;
441     case ".c":
442         return C;
443     default:
444         return unknown;
445     }
446 }