1 module reggae.rules.common;
2 
3 
4 import reggae.build;
5 import reggae.config: projectPath;
6 import std.algorithm;
7 import std.path;
8 import std.array: array;
9 
10 version(Windows) {
11     immutable objExt = ".obj";
12     immutable exeExt = ".exe";
13 } else {
14     immutable objExt = ".o";
15     immutable exeExt = "";
16 }
17 
18 
19 package string objFileName(in string srcFileName) @safe pure nothrow {
20     import std.path: stripExtension, defaultExtension, isRooted, stripDrive;
21     immutable localFileName = srcFileName.isRooted
22         ? srcFileName.stripDrive[1..$]
23         : srcFileName;
24     return localFileName.stripExtension.defaultExtension(objExt);
25 }
26 
27 
28 Target[] srcObjects(alias func)(in string extension,
29                                 string[] dirs,
30                                 string[] srcFiles,
31                                 in string[] excludeFiles) {
32     auto files = selectSrcFiles(srcFilesInDirs(extension, dirs), srcFiles, excludeFiles);
33     return func(files);
34 }
35 
36 //The parameters would be "in" except that "remove" doesn't like that...
37 string[] selectSrcFiles(string[] dirFiles,
38                         string[] srcFiles,
39                         in string[] excludeFiles) @safe pure nothrow {
40     return (dirFiles ~ srcFiles).remove!(a => excludeFiles.canFind(a)).array;
41 }
42 
43 private string[] srcFilesInDirs(in string extension, in string[] dirs) {
44     import std.exception: enforce;
45     import std.file;
46     import std.path: buildNormalizedPath, buildPath;
47     import std.array: array;
48 
49     DirEntry[] modules;
50     foreach(dir; dirs.map!(a => buildPath(projectPath, a))) {
51         enforce(isDir(dir), dir ~ " is not a directory name");
52         auto entries = dirEntries(dir, "*." ~ extension, SpanMode.depth);
53         auto normalised = entries.map!(a => DirEntry(buildNormalizedPath(a)));
54         modules ~= array(normalised);
55     }
56 
57     return modules.map!(a => a.name.removeProjectPath).array;
58 }
59 
60 string removeProjectPath(in string path) @safe pure {
61     import std.path: relativePath, absolutePath;
62     return path.absolutePath.relativePath(projectPath.absolutePath);
63 }
64 
65 @safe:
66 /**
67  An object file, typically from one source file in a certain language
68  (although for D the default is a whole package. The language is determined
69  by the file extension of the file(s) passed in.
70 */
71 Target objectFile(in string srcFileName,
72                   in string flags = "",
73                   in string[] includePaths = [],
74                   in string[] stringImportPaths = [],
75                   in string projDir = "$project") pure {
76 
77     immutable cmd = compileCommand(srcFileName, flags, includePaths, stringImportPaths, projDir);
78     return Target(srcFileName.objFileName, cmd, [Target(srcFileName)]);
79 }
80 
81 
82 string compileCommand(in string srcFileName,
83                       in string flags = "",
84                       in string[] includePaths = [],
85                       in string[] stringImportPaths = [],
86                       in string projDir = "$project") pure {
87     immutable includeParams = includePaths.map!(a => "-I" ~ buildPath(projDir, a)).join(",");
88     immutable flagParams = flags.splitter.join(",");
89     immutable ruleName = getBuiltinRule(srcFileName);
90     auto cmd = [ruleName, "includes=" ~ includeParams, "flags=" ~ flagParams];
91     if(ruleName == "_dcompile")
92         cmd ~= "stringImports=" ~ stringImportPaths.map!(a => "-J" ~ buildPath(projDir, a)).join(",");
93 
94     return cmd.join(" ");
95 }
96 
97 enum Language {
98     C,
99     Cplusplus,
100     D,
101 }
102 
103 private Language getLanguage(in string srcFileName) pure {
104     switch(srcFileName.extension) with(Language) {
105     case ".d":
106         return D;
107     case ".cpp":
108     case ".CPP":
109     case ".C":
110     case ".cxx":
111     case ".c++":
112     case ".cc":
113         return Cplusplus;
114     case ".c":
115         return C;
116     default:
117         throw new Exception("Unknown file extension " ~ srcFileName.extension);
118     }
119 
120 }
121 
122 private string getBuiltinRule(in string srcFileName) pure {
123     final switch(getLanguage(srcFileName)) with(Language) {
124         case D:
125             return "_dcompile";
126         case Cplusplus:
127             return "_cppcompile";
128         case C:
129             return "_ccompile";
130     }
131 }
132 
133 
134 /**
135  Should pull its weight more in the future by automatically figuring out what
136  to do. Right now only works for linking D applications using the configured
137  D compiler
138  */
139 Target link(in string exeName, in Target[] dependencies, in string flags = "") @safe pure nothrow {
140     auto cmd = "_link";
141     if(flags != "") cmd ~= " flags=" ~ flags;
142     return Target(exeName, cmd, dependencies);
143 }