1 module reggae.rules.common;
2 
3 
4 import reggae.build;
5 import reggae.config: projectPath;
6 import reggae.ctaa;
7 import reggae.types;
8 import std.algorithm;
9 import std.path;
10 import std.array: array;
11 
12 version(Windows) {
13     immutable objExt = ".obj";
14     immutable exeExt = ".exe";
15 } else {
16     immutable objExt = ".o";
17     immutable exeExt = "";
18 }
19 
20 package string objFileName(in string srcFileName) @safe pure {
21     import std.path: stripExtension, defaultExtension, isRooted, stripDrive;
22     immutable localFileName = srcFileName.isRooted
23         ? srcFileName.stripDrive[1..$]
24         : srcFileName;
25     return localFileName.stripExtension.defaultExtension(objExt);
26 }
27 
28 
29 /**
30  This template function exists so as to be referenced in a reggaefile.d
31  at top-level without being called via $(D alias). That way it can be
32  named and used in a further $(D Target) definition without the need to
33  define a function returning $(D Build).
34  This function gets the source files to be compiled at runtime by searching
35  for source files in the given directories, adding files and filtering
36  as appropriate by the parameters given in $(D sources), its first compile-time
37  parameter. The other parameters are self-explanatory.
38 
39  This function returns a list of targets that are the result of compiling
40  source files written in the supported languages. The $(Sources) function
41  can be used to specify source directories and source files, as well as
42  a filter function to select those files that are actually wanted.
43  */
44 Target[] targetsFromSources(alias sourcesFunc = Sources!(),
45                             Flags flags = Flags(),
46                             ImportPaths includes = ImportPaths(),
47                             StringImportPaths stringImports = StringImportPaths(),
48     )() @trusted {
49 
50     import std.exception: enforce;
51     import std.file;
52     import std.path: buildNormalizedPath, buildPath;
53     import std.array: array;
54     import std.traits: isCallable;
55 
56     auto srcs = sourcesFunc();
57 
58     DirEntry[] modules;
59     foreach(dir; srcs.dirs.value.map!(a => buildPath(projectPath, a))) {
60         enforce(isDir(dir), dir ~ " is not a directory name");
61         auto entries = dirEntries(dir, SpanMode.depth);
62         auto normalised = entries.map!(a => DirEntry(buildNormalizedPath(a)));
63 
64         modules ~= normalised.filter!(a => !a.isDir).array;
65     }
66 
67     foreach(module_; srcs.files.value) {
68         modules ~= DirEntry(buildNormalizedPath(buildPath(projectPath, module_)));
69     }
70 
71     const srcFiles = modules.
72         map!(a => a.name.removeProjectPath).
73         filter!(srcs.filterFunc).
74         array;
75 
76     const dSrcs = srcFiles.filter!(a => a.getLanguage == Language.D).filter!(a => a!= "reggaefile.d").array;
77     auto otherSrcs = srcFiles.filter!(a => a.getLanguage != Language.D && a.getLanguage != Language.unknown);
78     import reggae.rules.d: objectFiles;
79     return objectFiles(dSrcs, flags.value, ["."] ~ includes.value, stringImports.value) ~
80         otherSrcs.map!(a => objectFile(SourceFile(a), flags, includes)).array;
81 }
82 
83 @safe:
84 
85 string removeProjectPath(in string path) pure {
86     import std.path: relativePath, absolutePath;
87     //relativePath is @system
88     return () @trusted { return path.absolutePath.relativePath(projectPath.absolutePath); }();
89 }
90 
91 /**
92  An object file, typically from one source file in a certain language
93  (although for D the default is a whole package. The language is determined
94  by the file extension of the file passed in.
95  The $(D projDir) variable is best left alone; right now only the dub targets
96  make use of it (since dub packages are by definition outside of the project
97  source tree).
98 */
99 Target objectFile(in SourceFile srcFile,
100                   in Flags flags = Flags(),
101                   in ImportPaths includePaths = ImportPaths(),
102                   in StringImportPaths stringImportPaths = StringImportPaths(),
103                   in string projDir = "$project") pure {
104 
105     const cmd = compileCommand(srcFile.value, flags.value, includePaths.value, stringImportPaths.value, projDir);
106     return Target(srcFile.value.objFileName, cmd, [Target(srcFile.value)]);
107 }
108 
109 
110 Command compileCommand(in string srcFileName,
111                        in string flags = "",
112                        in string[] includePaths = [],
113                        in string[] stringImportPaths = [],
114                        in string projDir = "$project") pure {
115     auto includeParams = includePaths.map!(a => "-I" ~ buildPath(projDir, a)).array;
116     auto flagParams = flags.splitter.array;
117     immutable language = getLanguage(srcFileName);
118 
119     auto params = [assocEntry("includes", includeParams),
120                    assocEntry("flags", flagParams)];
121 
122     if(language == Language.D)
123         params ~= assocEntry("stringImports", stringImportPaths.map!(a => "-J" ~ buildPath(projDir, a)).array);
124 
125     params ~= assocEntry("DEPFILE", ["$out.dep"]);
126 
127     return Command(CommandType.compile, assocList(params));
128 }
129 
130 enum Language {
131     C,
132     Cplusplus,
133     D,
134     unknown,
135 }
136 
137 Language getLanguage(in string srcFileName) pure nothrow {
138     switch(srcFileName.extension) with(Language) {
139     case ".d":
140         return D;
141     case ".cpp":
142     case ".CPP":
143     case ".C":
144     case ".cxx":
145     case ".c++":
146     case ".cc":
147         return Cplusplus;
148     case ".c":
149         return C;
150     default:
151         return unknown;
152     }
153 }
154 
155 /**
156  "Compile-time" link function.
157  Its parameters are compile-time so that it can be aliased and used
158  at global scope in a reggafile.
159  Links an executable from the given dependency targets. The linker used
160  depends on the file extension of the leaf nodes of the passed-in targets.
161  If any D files are found, the linker is the D compiler, and so on with
162  C++ and C. If none of those apply, the D compiler is used.
163  */
164 Target link(ExeName exeName, alias dependenciesFunc, Flags flags = Flags())() @safe {
165     auto dependencies = dependenciesFunc();
166     return link(exeName, dependencies, flags);
167 }
168 
169 /**
170  Regular run-time link function.
171  Links an executable from the given dependency targets. The linker used
172  depends on the file extension of the leaf nodes of the passed-in targets.
173  If any D files are found, the linker is the D compiler, and so on with
174  C++ and C. If none of those apply, the D compiler is used.
175  */
176 Target link(in ExeName exeName, in Target[] dependencies, in Flags flags = Flags()) @safe pure {
177     const command = Command(CommandType.link, assocList([assocEntry("flags", flags.value.splitter.array)]));
178     return Target(exeName.value, command, dependencies);
179 }