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