1 module reggae.rules.common;
2 
3 
4 import reggae.build;
5 import reggae.ctaa;
6 import reggae.path: buildPath;
7 import reggae.types;
8 import std.algorithm;
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, const 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.dup)]));
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 compilerFlags = Flags(),
125                      ImportPaths includes = ImportPaths(),
126                      StringImportPaths stringImports = StringImportPaths(),
127                      alias dependenciesFunc = () { Target[] ts; return ts; })
128     ()
129 {
130 
131     return staticLibraryTarget(
132         name,
133         objectFiles!(sourcesFunc, compilerFlags, includes, stringImports)() ~ dependenciesFunc()
134     );
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;
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: absolutePath, buildNormalizedPath, dirName;
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                                    const 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     return staticLibraryTarget(
304         name,
305         objectFiles(projectPath, srcDirs, excDirs, srcFiles, excFiles, flags, includes, stringImports)
306     );
307 }
308 
309 Target staticLibraryTarget(in string name, Target[] objects) @safe pure {
310     import std.path: extension;
311     const realName = name.extension == libExt ? name : name ~ libExt;
312     auto target = Target(
313         [buildPath("$builddir", realName)],
314         staticLibraryShellCommand,
315         objects,
316     );
317     return target;
318 }
319 
320 version(Windows)
321     private enum staticLibraryShellCommand = "lib.exe /OUT:$out $in";
322 else
323     private enum staticLibraryShellCommand = "ar rcs $out $in";
324 
325 private Target[] srcFilesToObjectTargets(in string[] srcFiles,
326                                          in Flags flags,
327                                          in ImportPaths includes,
328                                          in StringImportPaths stringImports) {
329 
330     const dSrcs = srcFiles.filter!(a => a.getLanguage == Language.D).array;
331     auto otherSrcs = srcFiles.filter!(a => a.getLanguage != Language.D && a.getLanguage != Language.unknown);
332     import reggae.rules.d: dlangObjectFiles;
333     return
334         dlangObjectFiles(dSrcs, flags.value, ["."] ~ includes.value, stringImports.value) ~
335         otherSrcs.map!(a => objectFile(SourceFile(a), flags, includes)).array;
336 }
337 
338 
339 version(Windows) {
340     immutable objExt = ".obj";
341     immutable exeExt = ".exe";
342     immutable libExt = ".lib";
343 } else {
344     immutable objExt = ".o";
345     immutable exeExt = "";
346     immutable libExt = ".a";
347 }
348 
349 string objFileName(in string srcFileName) @safe pure {
350     return extFileName(srcFileName, objExt);
351 }
352 
353 string libFileName(in string srcFileName) @safe pure {
354     return extFileName(srcFileName, libExt);
355 }
356 
357 
358 string extFileName(in string srcFileName, in string extension) @safe pure {
359     import reggae.path: buildPath, deabsolutePath;
360     import std.path: stripExtension;
361     import std.array: replace;
362 
363     auto tmp = srcFileName
364         .buildPath
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 expandOutput(path, projDir, projDir);
399     }
400 
401     auto includeParams = includePaths.map!(a => "-I" ~ maybeExpand(a)). array;
402     immutable language = getLanguage(srcFileName);
403 
404     auto params = [
405         assocEntry("includes", includeParams),
406         assocEntry("flags", flags.dup),
407     ];
408 
409     if(language == Language.D)
410         params ~= assocEntry("stringImports",
411                              stringImportPaths.map!(a => "-J" ~ maybeExpand(a)).array);
412 
413     params ~= assocEntry("DEPFILE", [srcFileName.objFileName ~ ".dep"]);
414 
415     immutable type = justCompile ? CommandType.compile : CommandType.compileAndLink;
416     return Command(type, assocList(params));
417 }
418 
419 
420 enum Language {
421     C,
422     Cplusplus,
423     D,
424     unknown,
425 }
426 
427 Language getLanguage(in string srcFileName) @safe pure nothrow {
428     import std.path: extension;
429 
430     switch(srcFileName.extension) with(Language) {
431     case ".d":
432         return D;
433     case ".cpp":
434     case ".CPP":
435     case ".C":
436     case ".cxx":
437     case ".c++":
438     case ".cc":
439         return Cplusplus;
440     case ".c":
441         return C;
442     default:
443         return unknown;
444     }
445 }