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 nothrow {
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     )() {
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     const srcFiles = modules.
71         map!(a => a.name.removeProjectPath).
72         filter!(srcs.filterFunc).
73         array;
74 
75     const dSrcs = srcFiles.filter!(a => a.getLanguage == Language.D).array;
76     auto otherSrcs = srcFiles.filter!(a => a.getLanguage != Language.D);
77 
78     import reggae.rules.d: objectFiles;
79     return objectFiles(dSrcs, flags.value, ["."] ~ includes.value, stringImports.value) ~
80         otherSrcs.map!(a => objectFile(a, flags.value, includes.value)).array;
81 }
82 
83 @safe:
84 
85 string removeProjectPath(in string path) pure {
86     import std.path: relativePath, absolutePath;
87     return path.absolutePath.relativePath(projectPath.absolutePath);
88 }
89 
90 /**
91  An object file, typically from one source file in a certain language
92  (although for D the default is a whole package. The language is determined
93  by the file extension of the file(s) passed in.
94 */
95 Target objectFile(in string srcFileName,
96                   in string flags = "",
97                   in string[] includePaths = [],
98                   in string[] stringImportPaths = [],
99                   in string projDir = "$project") pure {
100 
101     const cmd = compileCommand(srcFileName, flags, includePaths, stringImportPaths, projDir);
102     return Target(srcFileName.objFileName, cmd, [Target(srcFileName)]);
103 }
104 
105 
106 Command compileCommand(in string srcFileName,
107                        in string flags = "",
108                        in string[] includePaths = [],
109                        in string[] stringImportPaths = [],
110                        in string projDir = "$project") pure {
111     auto includeParams = includePaths.map!(a => "-I" ~ buildPath(projDir, a)).array;
112     auto flagParams = flags.splitter.array;
113     immutable language = getLanguage(srcFileName);
114 
115     auto params = [assocEntry("includes", includeParams),
116                    assocEntry("flags", flagParams)];
117 
118     if(language == Language.D)
119         params ~= assocEntry("stringImports", stringImportPaths.map!(a => "-J" ~ buildPath(projDir, a)).array);
120 
121     params ~= assocEntry("DEPFILE", ["$out.dep"]);
122 
123     return Command(CommandType.compile, assocList(params));
124 }
125 
126 enum Language {
127     C,
128     Cplusplus,
129     D,
130     unknown,
131 }
132 
133 Language getLanguage(in string srcFileName) pure nothrow {
134     switch(srcFileName.extension) with(Language) {
135     case ".d":
136         return D;
137     case ".cpp":
138     case ".CPP":
139     case ".C":
140     case ".cxx":
141     case ".c++":
142     case ".cc":
143         return Cplusplus;
144     case ".c":
145         return C;
146     default:
147         return unknown;
148     }
149 }
150 
151 /**
152  Should pull its weight more in the future by automatically figuring out what
153  to do. Right now only works for linking D applications using the configured
154  D compiler
155  */
156 Target link(in string exeName, in Target[] dependencies, in string flags = "") @safe pure {
157     const command = Command(CommandType.link, assocList([assocEntry("flags", flags.splitter.array)]));
158     return Target(exeName, command, dependencies);
159 }