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 }