1 module reggae.rules.common;
2 
3 
4 import reggae.build;
5 import reggae.config: options;
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     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     const 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  "Compile-time" link function.
61  Its parameters are compile-time so that it can be aliased and used
62  at global scope in a reggafile.
63  Links an executable from the given dependency targets. The linker used
64  depends on the file extension of the leaf nodes of the passed-in targets.
65  If any D files are found, the linker is the D compiler, and so on with
66  C++ and C. If none of those apply, the D compiler is used.
67  */
68 Target link(ExeName exeName, alias dependenciesFunc, Flags flags = Flags())() @safe {
69     auto dependencies = dependenciesFunc();
70     return link(exeName, dependencies, flags);
71 }
72 
73 /**
74  Regular run-time link function.
75  Links an executable from the given dependency targets. The linker used
76  depends on the file extension of the leaf nodes of the passed-in targets.
77  If any D files are found, the linker is the D compiler, and so on with
78  C++ and C. If none of those apply, the D compiler is used.
79  */
80 Target link(in ExeName exeName, in Target[] dependencies, in Flags flags = Flags()) @safe pure {
81     const command = Command(CommandType.link, assocList([assocEntry("flags", flags.value.splitter.array)]));
82     return Target(exeName.value, command, dependencies);
83 }
84 
85 
86 /**
87  Convenience rule for creating static libraries
88  */
89 Target[] staticLibrary(string name,
90                        alias sourcesFunc = Sources!(),
91                        Flags flags = Flags(),
92                        ImportPaths includes = ImportPaths(),
93                        StringImportPaths stringImports = StringImportPaths())
94     () {
95 
96     version(Posix) {}
97     else
98         static assert(false, "Can only create static libraries on Posix");
99 
100     const srcFiles = sourcesToFileNames!(sourcesFunc);
101     return [Target(buildPath("$builddir", name), "ar rcs $out $in",
102                    objectFiles!(sourcesFunc, flags, includes, stringImports)())];
103 }
104 
105 /**
106  "Compile-time" target creation.
107  Its parameters are compile-time so that it can be aliased and used
108  at global scope in a reggaefile
109  */
110 Target target(alias outputs,
111               alias command = "",
112               alias dependenciesFunc = () { Target[] ts; return ts; },
113               alias implicitsFunc = () { Target[] ts; return ts; })() @trusted {
114 
115     auto depsRes = dependenciesFunc();
116     auto impsRes = implicitsFunc();
117 
118     static if(isArray!(typeof(depsRes)))
119         auto dependencies = depsRes;
120     else
121         auto dependencies = [depsRes];
122 
123     static if(isArray!(typeof(impsRes)))
124         auto implicits = impsRes;
125     else
126         auto implicits = [impsRes];
127 
128 
129     return Target(outputs, command, dependencies, implicits);
130 }
131 
132 
133 /**
134  * Convenience alias for appending targets without calling any runtime function.
135  * This replaces the need to manually define a function to return a `Build` struct
136  * just to concatenate targets
137  */
138 Target[] targetConcat(T...)() {
139     Target[] ret;
140     foreach(target; T) {
141         static if(isCallable!target)
142             ret ~= target();
143         else
144             ret ~= target;
145     }
146     return ret;
147 }
148 
149 
150 //end of rules
151 
152 string[] sourcesToFileNames(alias sourcesFunc = Sources!())() @trusted {
153 
154     import std.exception: enforce;
155     import std.file;
156     import std.path: buildNormalizedPath, buildPath;
157     import std.array: array;
158     import std.traits: isCallable;
159 
160     auto srcs = sourcesFunc();
161 
162     DirEntry[] modules;
163     foreach(dir; srcs.dirs.value.map!(a => buildPath(options.projectPath, a))) {
164         enforce(isDir(dir), dir ~ " is not a directory name");
165         auto entries = dirEntries(dir, SpanMode.depth);
166         auto normalised = entries.map!(a => DirEntry(buildNormalizedPath(a)));
167 
168         modules ~= normalised.filter!(a => !a.isDir).array;
169     }
170 
171     foreach(module_; srcs.files.value) {
172         modules ~= DirEntry(buildNormalizedPath(buildPath(options.projectPath, module_)));
173     }
174 
175     return modules.
176         map!(a => a.name.removeProjectPath).
177         filter!(srcs.filterFunc).
178         filter!(a => a != "reggaefile.d").
179         array;
180 }
181 
182 //run-time version
183 string[] sourcesToFileNames(in string projectPath,
184                             in string[] srcDirs,
185                             in string[] excDirs,
186                             in string[] srcFiles,
187                             in string[] excFiles) @trusted {
188 
189 
190 
191     import std.exception: enforce;
192     import std.file;
193     import std.path: buildNormalizedPath, buildPath;
194     import std.array: array;
195     import std.traits: isCallable;
196 
197     DirEntry[] files;
198     foreach(dir; srcDirs.filter!(a => !excDirs.canFind(a)).map!(a => buildPath(projectPath, a))) {
199         enforce(isDir(dir), dir ~ " is not a directory name");
200         auto entries = dirEntries(dir, SpanMode.depth);
201         auto normalised = entries.map!(a => DirEntry(buildNormalizedPath(a)));
202 
203         files ~= normalised.array;
204     }
205 
206     foreach(module_; srcFiles) {
207         files ~= DirEntry(buildNormalizedPath(buildPath(projectPath, module_)));
208     }
209 
210     return files.
211         map!(a => removeProjectPath(projectPath, a.name)).
212         filter!(a => !excFiles.canFind(a)).
213         filter!(a => a != "reggaefile.d").
214         array;
215 }
216 
217 
218 //run-time version
219 Target[] objectFiles(in string projectPath,
220                      in string[] srcDirs,
221                      in string[] excDirs,
222                      in string[] srcFiles,
223                      in string[] excFiles,
224                      in string flags,
225                      in string[] includes,
226                      in string[] stringImports) @trusted {
227 
228     return srcFilesToObjectTargets(sourcesToFileNames(projectPath, srcDirs, excDirs, srcFiles, excFiles),
229                                    Flags(flags),
230                                    const ImportPaths(includes),
231                                    const StringImportPaths(stringImports));
232 }
233 
234 //run-time version
235 Target[] staticLibrary(in string projectPath,
236                        in string name,
237                        in string[] srcDirs,
238                        in string[] excDirs,
239                        in string[] srcFiles,
240                        in string[] excFiles,
241                        in string flags,
242                        in string[] includes,
243                        in string[] stringImports) @trusted {
244 
245 
246     version(Posix) {}
247     else
248         static assert(false, "Can only create static libraries on Posix");
249 
250     const allFiles = sourcesToFileNames(projectPath, srcDirs, excDirs, srcFiles, excFiles);
251     return [Target([buildPath("$builddir", name)],
252                    "ar rcs $out $in",
253                    objectFiles(projectPath, srcDirs, excDirs, srcFiles, excFiles, flags, includes, stringImports))];
254 }
255 
256 
257 private Target[] srcFilesToObjectTargets(in string[] srcFiles,
258                                          in Flags flags,
259                                          in ImportPaths includes,
260                                          in StringImportPaths stringImports) {
261 
262     const dSrcs = srcFiles.filter!(a => a.getLanguage == Language.D).array;
263     auto otherSrcs = srcFiles.filter!(a => a.getLanguage != Language.D && a.getLanguage != Language.unknown);
264     import reggae.rules.d: dlangPackageObjectFiles;
265     return dlangPackageObjectFiles(dSrcs, flags.value, ["."] ~ includes.value, stringImports.value) ~
266         otherSrcs.map!(a => objectFile(SourceFile(a), flags, includes)).array;
267 
268 }
269 
270 
271 version(Windows) {
272     immutable objExt = ".obj";
273     immutable exeExt = ".exe";
274 } else {
275     immutable objExt = ".o";
276     immutable exeExt = "";
277 }
278 
279 package string objFileName(in string srcFileName) pure {
280     import std.path: stripExtension, defaultExtension, isRooted, stripDrive;
281     immutable localFileName = srcFileName.isRooted
282         ? srcFileName.stripDrive[1..$]
283         : srcFileName;
284     return localFileName.stripExtension.defaultExtension(objExt);
285 }
286 
287 string removeProjectPath(in string path) pure {
288     import std.path: relativePath, absolutePath;
289     //relativePath is @system
290     return () @trusted { return path.absolutePath.relativePath(options.projectPath.absolutePath); }();
291 }
292 
293 string removeProjectPath(in string projectPath, in string path) pure {
294     import std.path: relativePath, absolutePath;
295     //relativePath is @system
296     return () @trusted { return path.absolutePath.relativePath(projectPath.absolutePath); }();
297 }
298 
299 
300 
301 Command compileCommand(in string srcFileName,
302                        in string flags = "",
303                        in string[] includePaths = [],
304                        in string[] stringImportPaths = [],
305                        in string projDir = "$project",
306                        Flag!"justCompile" justCompile = Yes.justCompile) pure {
307 
308     string maybeExpand(string path) {
309         return path.startsWith(gBuilddir) ? expandBuildDir(path) : buildPath(projDir, path);
310     }
311 
312     auto includeParams = includePaths.map!(a => "-I" ~ maybeExpand(a)). array;
313     auto flagParams = flags.splitter.array;
314     immutable language = getLanguage(srcFileName);
315 
316     auto params = [assocEntry("includes", includeParams),
317                    assocEntry("flags", flagParams)];
318 
319     if(language == Language.D)
320         params ~= assocEntry("stringImports", stringImportPaths.map!(a => "-J" ~ maybeExpand(a)).array);
321 
322     params ~= assocEntry("DEPFILE", [srcFileName.objFileName ~ ".dep"]);
323 
324     immutable type = justCompile ? CommandType.compile : CommandType.compileAndLink;
325     return Command(type, assocList(params));
326 }
327 
328 
329 enum Language {
330     C,
331     Cplusplus,
332     D,
333     unknown,
334 }
335 
336 Language getLanguage(in string srcFileName) pure nothrow {
337     switch(srcFileName.extension) with(Language) {
338     case ".d":
339         return D;
340     case ".cpp":
341     case ".CPP":
342     case ".C":
343     case ".cxx":
344     case ".c++":
345     case ".cc":
346         return Cplusplus;
347     case ".c":
348         return C;
349     default:
350         return unknown;
351     }
352 }