1 module reggae.rules;
2 
3 
4 import reggae.build;
5 import reggae.config;
6 import reggae.dependencies;
7 import reggae.types;
8 import reggae.sorting;
9 import std.path : baseName, absolutePath, dirSeparator;
10 import std.algorithm: map, splitter, remove, canFind, startsWith, find;
11 import std.array: array, replace;
12 import std.range: chain;
13 
14 version(Windows) {
15     immutable objExt = ".obj";
16     immutable exeExt = ".exe";
17 } else {
18     immutable objExt = ".o";
19     immutable exeExt = "";
20 }
21 
22 
23 private string objFileName(in string srcFileName) @safe pure nothrow {
24     import std.path: stripExtension, defaultExtension, isRooted, stripDrive;
25     immutable localFileName = srcFileName.isRooted
26         ? srcFileName.stripDrive[1..$]
27         : srcFileName;
28     return localFileName.stripExtension.defaultExtension(objExt);
29 }
30 
31 
32 private string dCompileCommand(in string flags = "",
33                                in string[] importPaths = [], in string[] stringImportPaths = [],
34                                in string projDir = "$project") @safe pure {
35     immutable importParams = importPaths.map!(a => "-I" ~ buildPath(projDir, a)).join(",");
36     immutable stringParams = stringImportPaths.map!(a => "-J" ~ buildPath(projDir, a)).join(",");
37     immutable flagParams = flags.splitter.join(",");
38     return ["_dcompile ", "includes=" ~ importParams, "flags=" ~ flagParams,
39             "stringImports=" ~ stringParams].join(" ");
40 }
41 
42 Target[] dCompileGrouped(in string[] srcFiles, in string flags = "",
43                          in string[] importPaths = [], in string[] stringImportPaths = [],
44                          in string projDir = "$project") @safe {
45     import reggae.config;
46     auto func = perModule ? &dCompilePerModule : &dCompilePerPackage;
47     return func(srcFiles, flags, importPaths, stringImportPaths, projDir);
48 }
49 
50 Target[] dCompilePerPackage(in string[] srcFiles, in string flags = "",
51                             in string[] importPaths = [], in string[] stringImportPaths = [],
52                             in string projDir = "$project") @safe {
53 
54     immutable command = dCompileCommand(flags, importPaths, stringImportPaths, projDir);
55     return srcFiles.byPackage.map!(a => Target(a[0].packagePath.objFileName,
56                                                command,
57                                                a.map!(a => Target(a)).array)).array;
58 }
59 
60 Target[] dCompilePerModule(in string[] srcFiles, in string flags = "",
61                            in string[] importPaths = [], in string[] stringImportPaths = [],
62                            in string projDir = "$project") @safe {
63 
64     immutable command = dCompileCommand(flags, importPaths, stringImportPaths, projDir);
65     return srcFiles.map!(a => dCompile(a, flags, importPaths, stringImportPaths, projDir)).array;
66 }
67 
68 
69 //@trusted because of join
70 Target dCompile(in string srcFileName, in string flags = "",
71                 in string[] importPaths = [], in string[] stringImportPaths = [],
72                 in string projDir = "$project") @trusted pure {
73 
74     immutable command = dCompileCommand(flags, importPaths, stringImportPaths, projDir);
75     return Target(srcFileName.objFileName, command, [Target(srcFileName)]);
76 }
77 
78 
79 Target cppCompile(in string srcFileName, in string flags = "",
80                   in string[] includePaths = []) @safe pure nothrow {
81     immutable includes = includePaths.map!(a => "-I$project/" ~ a).join(",");
82     return Target(srcFileName.objFileName, "_cppcompile includes=" ~ includes ~ " flags=" ~ flags,
83                   [Target(srcFileName)]);
84 }
85 
86 Target cCompile(in string srcFileName, in string flags = "",
87                 in string[] includePaths = []) @safe pure nothrow {
88     return cppCompile(srcFileName, flags, includePaths);
89 }
90 
91 /**
92  * Compile-time function to that returns a list of Target objects
93  * corresponding to D source files from a particular directory
94  */
95 Target[] dObjects(SrcDirs dirs = SrcDirs(),
96                   Flags flags = Flags(),
97                   ImportPaths includes = ImportPaths(),
98                   StringImportPaths stringImports = StringImportPaths(),
99                   SrcFiles srcFiles = SrcFiles(),
100                   ExcludeFiles excludeFiles = ExcludeFiles())
101     () {
102 
103     Target[] dCompileInner(in string[] files) {
104         return dCompileGrouped(files, flags.flags, ["."] ~ includes.paths, stringImports.paths);
105     }
106 
107     return srcObjects!dCompileInner("d", dirs.paths, srcFiles.paths, excludeFiles.paths);
108 }
109 
110 /**
111  * Compile-time function to that returns a list of Target objects
112  * corresponding to C++ source files from a particular directory
113  */
114 auto cppObjects(SrcDirs dirs = SrcDirs(),
115                 Flags flags = Flags(),
116                 ImportPaths includes = ImportPaths(),
117                 SrcFiles srcFiles = SrcFiles(),
118                 ExcludeFiles excludeFiles = ExcludeFiles())
119     () {
120 
121     Target[] cppCompileInner(in string[] files) {
122         return files.map!(a => cppCompile(a, flags.flags, includes.paths)).array;
123     }
124 
125     return srcObjects!cppCompileInner("cpp", dirs.paths, srcFiles.paths, excludeFiles.paths);
126 }
127 
128 
129 /**
130  * Compile-time function to that returns a list of Target objects
131  * corresponding to C source files from a particular directory
132  */
133 auto cObjects(SrcDirs dirs = SrcDirs(),
134               Flags flags = Flags(),
135               ImportPaths includes = ImportPaths(),
136               SrcFiles srcFiles = SrcFiles(),
137               ExcludeFiles excludeFiles = ExcludeFiles())
138     () {
139 
140     Target[] cCompileInner(in string[] files) {
141         return files.map!(a => cCompile(a, flags.flags, includes.paths)).array;
142     }
143 
144 
145     return srcObjects!cCompileInner("c", dirs.paths, srcFiles.paths, excludeFiles.paths);
146 }
147 
148 
149 Target[] srcObjects(alias func)(in string extension,
150                                 string[] dirs, string[] srcFiles, in string[] excludeFiles) {
151     auto files = selectSrcFiles(srcFilesInDirs(extension, dirs), srcFiles, excludeFiles);
152     return func(files);
153 }
154 
155 //The parameters would be "in" except that "remove" doesn't like that...
156 string[] selectSrcFiles(string[] dirFiles,
157                         string[] srcFiles,
158                         in string[] excludeFiles) @safe pure nothrow {
159     return (dirFiles ~ srcFiles).remove!(a => excludeFiles.canFind(a)).array;
160 }
161 
162 private string[] srcFilesInDirs(in string extension, in string[] dirs) {
163     import std.exception: enforce;
164     import std.file;
165     import std.path: buildNormalizedPath;
166 
167     DirEntry[] modules;
168     foreach(dir; dirs.map!(a => buildPath(projectPath, a))) {
169         enforce(isDir(dir), dir ~ " is not a directory name");
170         auto entries = dirEntries(dir, "*." ~ extension, SpanMode.depth);
171         auto normalised = entries.map!(a => DirEntry(buildNormalizedPath(a)));
172         modules ~= array(normalised);
173     }
174 
175     return modules.map!(a => a.name.removeProjectPath).array;
176 }
177 
178 
179 //compile-time verson of dExe, to be used with alias
180 //all paths relative to projectPath
181 Target dExe(App app,
182             Flags flags = Flags(),
183             ImportPaths importPaths = ImportPaths(),
184             StringImportPaths stringImportPaths = StringImportPaths(),
185             alias linkWithFunction = () { return cast(Target[])[];})
186     () {
187     auto linkWith = linkWithFunction();
188     return dExe(app, flags, importPaths, stringImportPaths, linkWith);
189 }
190 
191 
192 //regular runtime version of dExe
193 //all paths relative to projectPath
194 //@trusted because of .array
195 Target dExe(in App app, in Flags flags,
196             in ImportPaths importPaths,
197             in StringImportPaths stringImportPaths,
198             in Target[] linkWith) @trusted {
199 
200     auto mainObj = dCompile(app.srcFileName, flags.flags, importPaths.paths, stringImportPaths.paths);
201     const output = runDCompiler(buildPath(projectPath, app.srcFileName), flags.flags,
202                                 importPaths.paths, stringImportPaths.paths);
203 
204     const files = dMainDepSrcs(output).map!(a => a.removeProjectPath).array;
205     const dependencies = [mainObj] ~ dCompileGrouped(files, flags.flags,
206                                                      importPaths.paths, stringImportPaths.paths);
207 
208     return dLink(app.exeFileName, dependencies ~ linkWith);
209 }
210 
211 
212 Target dLink(in string exeName, in Target[] dependencies, in string flags = "") @safe pure nothrow {
213     auto cmd = "_dlink";
214     if(flags != "") cmd ~= " flags=" ~ flags;
215     return Target(exeName, cmd, dependencies);
216 }
217 
218 
219 //@trusted because of splitter
220 private auto runDCompiler(in string srcFileName, in string flags,
221                           in string[] importPaths, in string[] stringImportPaths) @trusted {
222 
223     import std.process: execute;
224     import std.exception: enforce;
225     import std.conv:text;
226 
227     immutable compiler = "dmd";
228     const compArgs = [compiler] ~ flags.splitter.array ~
229         importPaths.map!(a => "-I" ~ buildPath(projectPath, a)).array ~
230         stringImportPaths.map!(a => "-J" ~ buildPath(projectPath, a)).array ~
231         ["-o-", "-v", "-c", srcFileName];
232     const compRes = execute(compArgs);
233     enforce(compRes.status == 0, text("dExe could not run ", compArgs.join(" "), ":\n", compRes.output));
234     return compRes.output;
235 }
236 
237 string removeProjectPath(in string path) @safe pure {
238     import std.path: relativePath;
239     return path.absolutePath.relativePath(projectPath.absolutePath);
240 }
241 
242 private immutable defaultRules = ["_dcompile", "_ccompile", "_cppcompile", "_dlink"];
243 
244 private bool isDefaultRule(in string command) @safe pure nothrow {
245     return defaultRules.canFind(command);
246 }
247 
248 private string getRule(in string command) @safe pure {
249     return command.splitter.front;
250 }
251 
252 bool isDefaultCommand(in string command) @safe pure {
253     return isDefaultRule(command.getRule);
254 }
255 
256 string getDefaultRule(in string command) @safe pure {
257     immutable rule = command.getRule;
258     if(!isDefaultRule(rule)) {
259         throw new Exception("Cannot get defaultRule from " ~ command);
260     }
261 
262     return rule;
263 }
264 
265 
266 string[] getDefaultRuleParams(in string command, in string key) @safe pure {
267     return getDefaultRuleParams(command, key, false);
268 }
269 
270 
271 string[] getDefaultRuleParams(in string command, in string key, string[] ifNotFound) @safe pure {
272     return getDefaultRuleParams(command, key, true, ifNotFound);
273 }
274 
275 
276 //@trusted because of replace
277 private string[] getDefaultRuleParams(in string command, in string key,
278                                       bool useIfNotFound, string[] ifNotFound = []) @trusted pure {
279     import std.conv: text;
280 
281     auto parts = command.splitter;
282     immutable cmd = parts.front;
283     if(!isDefaultRule(cmd)) {
284         throw new Exception("Cannot get defaultRule from " ~ command);
285     }
286 
287     auto fromParamPart = parts.find!(a => a.startsWith(key ~ "="));
288     if(fromParamPart.empty) {
289         if(useIfNotFound) {
290             return ifNotFound;
291         } else {
292             throw new Exception ("Cannot get default rule from " ~ command);
293         }
294     }
295 
296     auto paramPart = fromParamPart.front;
297     auto removeKey = paramPart.replace(key ~ "=", "");
298 
299     return removeKey.splitter(",").array;
300 }