1 module reggae.rules.common; 2 3 4 import reggae.build; 5 import reggae.ctaa; 6 import reggae.types; 7 import std.algorithm; 8 import std.path; 9 import std.array: array; 10 import std.traits; 11 import std.typecons; 12 13 /** 14 This template function exists so as to be referenced in a reggaefile.d 15 at top-level without being called via $(D alias). That way it can be 16 named and used in a further $(D Target) definition without the need to 17 define a function returning $(D Build). 18 This function gets the source files to be compiled at runtime by searching 19 for source files in the given directories, adding files and filtering 20 as appropriate by the parameters given in $(D sources), its first compile-time 21 parameter. The other parameters are self-explanatory. 22 23 This function returns a list of targets that are the result of compiling 24 source files written in the supported languages. The $(Sources) function 25 can be used to specify source directories and source files, as well as 26 a filter function to select those files that are actually wanted. 27 */ 28 Target[] objectFiles(alias sourcesFunc = Sources!(), 29 Flags flags = Flags(), 30 ImportPaths includes = ImportPaths(), 31 StringImportPaths stringImports = StringImportPaths(), 32 )() @trusted { 33 34 import std.stdio; 35 const srcFiles = sourcesToFileNames!(sourcesFunc); 36 return srcFilesToObjectTargets(srcFiles, flags, includes, stringImports); 37 } 38 39 @safe: 40 41 /** 42 An object file, typically from one source file in a certain language 43 (although for D the default is a whole package). The language is determined 44 by the file extension of the file passed in. 45 The $(D projDir) variable is best left alone; right now only the dub targets 46 make use of it (since dub packages are by definition outside of the project 47 source tree). 48 */ 49 Target objectFile(in SourceFile srcFile, 50 in Flags flags = Flags(), 51 in ImportPaths includePaths = ImportPaths(), 52 in StringImportPaths stringImportPaths = StringImportPaths(), 53 in string projDir = "$project") pure { 54 55 auto cmd = compileCommand(srcFile.value, flags.value, includePaths.value, stringImportPaths.value, projDir); 56 return Target(srcFile.value.objFileName, cmd, [Target(srcFile.value)]); 57 } 58 59 /** 60 A binary executable. The same as calling objectFiles and link 61 with these parameters. 62 */ 63 Target executable(ExeName exeName, 64 alias sourcesFunc = Sources!(), 65 Flags compilerFlags = Flags(), 66 ImportPaths includes = ImportPaths(), 67 StringImportPaths stringImports = StringImportPaths(), 68 Flags linkerFlags = Flags()) 69 () { 70 auto objs = objectFiles!(sourcesFunc, compilerFlags, includes, stringImports); 71 return link!(exeName, { return objs; }, linkerFlags); 72 } 73 74 Target executable(in string projectPath, 75 in string name, 76 in string[] srcDirs, 77 in string[] excDirs, 78 in string[] srcFiles, 79 in string[] excFiles, 80 in string compilerFlags, 81 in string linkerFlags, 82 in string[] includes, 83 in string[] stringImports) 84 { 85 auto objs = objectFiles(projectPath, srcDirs, excDirs, srcFiles, excFiles, compilerFlags, includes, stringImports); 86 return link(ExeName(name), objs, Flags(linkerFlags)); 87 } 88 89 90 91 /** 92 "Compile-time" link function. 93 Its parameters are compile-time so that it can be aliased and used 94 at global scope in a reggafile. 95 Links an executable from the given dependency targets. The linker used 96 depends on the file extension of the leaf nodes of the passed-in targets. 97 If any D files are found, the linker is the D compiler, and so on with 98 C++ and C. If none of those apply, the D compiler is used. 99 */ 100 Target link(ExeName exeName, alias dependenciesFunc, Flags flags = Flags())() @safe { 101 auto dependencies = dependenciesFunc(); 102 return link(exeName, dependencies, flags); 103 } 104 105 /** 106 Regular run-time link function. 107 Links an executable from the given dependency targets. The linker used 108 depends on the file extension of the leaf nodes of the passed-in targets. 109 If any D files are found, the linker is the D compiler, and so on with 110 C++ and C. If none of those apply, the D compiler is used. 111 */ 112 Target link(in ExeName exeName, Target[] dependencies, in Flags flags = Flags()) @safe pure { 113 auto command = Command(CommandType.link, 114 assocList([assocEntry("flags", flags.value.splitter.array)])); 115 return Target(exeName.value, command, dependencies); 116 } 117 118 119 /** 120 Convenience rule for creating static libraries 121 */ 122 Target[] staticLibrary(string name, 123 alias sourcesFunc = Sources!(), 124 Flags flags = Flags(), 125 ImportPaths includes = ImportPaths(), 126 StringImportPaths stringImports = StringImportPaths(), 127 alias dependenciesFunc = () { Target[] ts; return ts; }) 128 () 129 { 130 131 version(Posix) {} 132 else 133 static assert(false, "Can only create static libraries on Posix"); 134 135 const srcFiles = sourcesToFileNames!(sourcesFunc); 136 return [Target(buildPath("$builddir", name), 137 "ar rcs $out $in", 138 objectFiles!(sourcesFunc, flags, includes, stringImports)() ~ dependenciesFunc())]; 139 } 140 141 /** 142 "Compile-time" target creation. 143 Its parameters are compile-time so that it can be aliased and used 144 at global scope in a reggaefile 145 */ 146 Target target(alias outputs, 147 alias command = "", 148 alias dependenciesFunc = () { Target[] ts; return ts; }, 149 alias implicitsFunc = () { Target[] ts; return ts; })() @trusted { 150 151 auto depsRes = dependenciesFunc(); 152 auto impsRes = implicitsFunc(); 153 154 static if(isArray!(typeof(depsRes))) 155 auto dependencies = depsRes; 156 else 157 auto dependencies = [depsRes]; 158 159 static if(isArray!(typeof(impsRes))) 160 auto implicits = impsRes; 161 else 162 auto implicits = [impsRes]; 163 164 165 return Target(outputs, command, dependencies, implicits); 166 } 167 168 169 /** 170 * Convenience alias for appending targets without calling any runtime function. 171 * This replaces the need to manually define a function to return a `Build` struct 172 * just to concatenate targets 173 */ 174 Target[] targetConcat(T...)() { 175 Target[] ret; 176 foreach(target; T) { 177 static if(isCallable!target) 178 ret ~= target(); 179 else 180 ret ~= target; 181 } 182 return ret; 183 } 184 185 /** 186 Compile-time version of Target.phony 187 */ 188 Target phony(string name, 189 string shellCommand, 190 alias dependenciesFunc = () { Target[] ts; return ts; }, 191 alias implicitsFunc = () { Target[] ts; return ts; })() { 192 return Target.phony(name, shellCommand, arrayify!dependenciesFunc, arrayify!implicitsFunc); 193 } 194 195 196 //end of rules 197 198 private auto arrayify(alias func)() { 199 import std.traits; 200 auto ret = func(); 201 static if(isArray!(typeof(ret))) 202 return ret; 203 else 204 return [ret]; 205 } 206 207 auto sourcesToTargets(alias sourcesFunc = Sources!())() { 208 return sourcesToFileNames!sourcesFunc.map!(a => Target(a)); 209 } 210 211 string[] sourcesToFileNames(alias sourcesFunc = Sources!())() @trusted { 212 import std.exception: enforce; 213 import std.file; 214 import std.path: buildNormalizedPath, buildPath; 215 import std.array: array; 216 import std.traits: isCallable; 217 import reggae.config: options; 218 219 auto srcs = sourcesFunc(); 220 221 DirEntry[] modules; 222 foreach(dir; srcs.dirs.value.map!(a => buildPath(options.projectPath, a))) { 223 enforce(isDir(dir), dir ~ " is not a directory name"); 224 auto entries = dirEntries(dir, SpanMode.depth); 225 auto normalised = entries.map!(a => DirEntry(buildNormalizedPath(a))); 226 227 modules ~= normalised.filter!(a => !a.isDir).array; 228 } 229 230 foreach(module_; srcs.files.value) { 231 modules ~= DirEntry(buildNormalizedPath(buildPath(options.projectPath, module_))); 232 } 233 234 return modules. 235 map!(a => a.name.removeProjectPath). 236 filter!(srcs.filterFunc). 237 filter!(a => a != "reggaefile.d"). 238 array; 239 } 240 241 //run-time version 242 string[] sourcesToFileNames(in string projectPath, 243 in string[] srcDirs, 244 const(string)[] excDirs, 245 in string[] srcFiles, 246 in string[] excFiles) @trusted { 247 248 249 250 import std.exception: enforce; 251 import std.file; 252 import std.path: buildNormalizedPath, buildPath; 253 import std.array: array; 254 import std.traits: isCallable; 255 256 excDirs = (excDirs ~ ".reggae").map!(a => buildPath(projectPath, a)).array; 257 258 DirEntry[] files; 259 foreach(dir; srcDirs.map!(a => buildPath(projectPath, a))) { 260 enforce(isDir(dir), dir ~ " is not a directory name"); 261 262 auto entries = dirEntries(dir, SpanMode.depth) 263 .map!(a => a.buildNormalizedPath) 264 .filter!(a => !excDirs.canFind!(b => a.dirName.absolutePath.startsWith(b))); 265 files ~= entries.map!(a => DirEntry(a)).array; 266 } 267 268 foreach(module_; srcFiles) { 269 files ~= DirEntry(buildNormalizedPath(buildPath(projectPath, module_))); 270 } 271 272 return files. 273 map!(a => removeProjectPath(projectPath, a.name)). 274 filter!(a => !excFiles.canFind(a)). 275 filter!(a => a != "reggaefile.d"). 276 array; 277 } 278 279 280 //run-time version 281 Target[] objectFiles(in string projectPath, 282 in string[] srcDirs, 283 in string[] excDirs, 284 in string[] srcFiles, 285 in string[] excFiles, 286 in string flags = "", 287 in string[] includes = [], 288 in string[] stringImports = []) @trusted { 289 290 return srcFilesToObjectTargets(sourcesToFileNames(projectPath, srcDirs, excDirs, srcFiles, excFiles), 291 Flags(flags), 292 const ImportPaths(includes), 293 const StringImportPaths(stringImports)); 294 } 295 296 //run-time version 297 Target[] staticLibrary(in string projectPath, 298 in string name, 299 in string[] srcDirs, 300 in string[] excDirs, 301 in string[] srcFiles, 302 in string[] excFiles, 303 in string flags, 304 in string[] includes, 305 in string[] stringImports) @trusted { 306 307 308 version(Posix) {} 309 else 310 static assert(false, "Can only create static libraries on Posix"); 311 312 return [Target([buildPath("$builddir", name)], 313 "ar rcs $out $in", 314 objectFiles(projectPath, srcDirs, excDirs, srcFiles, excFiles, flags, includes, stringImports))]; 315 } 316 317 318 private Target[] srcFilesToObjectTargets(in string[] srcFiles, 319 in Flags flags, 320 in ImportPaths includes, 321 in StringImportPaths stringImports) { 322 323 const dSrcs = srcFiles.filter!(a => a.getLanguage == Language.D).array; 324 auto otherSrcs = srcFiles.filter!(a => a.getLanguage != Language.D && a.getLanguage != Language.unknown); 325 import reggae.rules.d: dlangPackageObjectFiles; 326 return dlangPackageObjectFiles(dSrcs, flags.value, ["."] ~ includes.value, stringImports.value) ~ 327 otherSrcs.map!(a => objectFile(SourceFile(a), flags, includes)).array; 328 329 } 330 331 332 version(Windows) { 333 immutable objExt = ".obj"; 334 immutable exeExt = ".exe"; 335 } else { 336 immutable objExt = ".o"; 337 immutable exeExt = ""; 338 } 339 340 package string objFileName(in string srcFileName) pure { 341 import std.path: stripExtension, defaultExtension, isRooted, stripDrive; 342 import std.array: replace; 343 344 immutable localFileName = srcFileName.isRooted 345 ? srcFileName.stripDrive[1..$] 346 : srcFileName; 347 return localFileName.stripExtension.defaultExtension(objExt).replace("..", "__"); 348 } 349 350 string removeProjectPath(in string path) { 351 import std.path: relativePath, absolutePath; 352 import reggae.config: options; 353 //relativePath is @system 354 return () @trusted { return path.absolutePath.relativePath(options.projectPath.absolutePath); }(); 355 } 356 357 string removeProjectPath(in string projectPath, in string path) pure { 358 import std.path: relativePath, absolutePath; 359 //relativePath is @system 360 return () @trusted { return path.absolutePath.relativePath(projectPath.absolutePath); }(); 361 } 362 363 364 365 Command compileCommand(in string srcFileName, 366 in string flags = "", 367 in string[] includePaths = [], 368 in string[] stringImportPaths = [], 369 in string projDir = "$project", 370 Flag!"justCompile" justCompile = Yes.justCompile) pure { 371 372 string maybeExpand(string path) { 373 return path.startsWith(gBuilddir) ? expandBuildDir(path) : buildPath(projDir, path); 374 } 375 376 auto includeParams = includePaths.map!(a => "-I" ~ maybeExpand(a)). array; 377 auto flagParams = flags.splitter.array; 378 immutable language = getLanguage(srcFileName); 379 380 auto params = [assocEntry("includes", includeParams), 381 assocEntry("flags", flagParams)]; 382 383 if(language == Language.D) 384 params ~= assocEntry("stringImports", 385 stringImportPaths.map!(a => "-J" ~ maybeExpand(a)).array); 386 387 params ~= assocEntry("DEPFILE", [srcFileName.objFileName ~ ".dep"]); 388 389 immutable type = justCompile ? CommandType.compile : CommandType.compileAndLink; 390 return Command(type, assocList(params)); 391 } 392 393 394 enum Language { 395 C, 396 Cplusplus, 397 D, 398 unknown, 399 } 400 401 Language getLanguage(in string srcFileName) pure nothrow { 402 switch(srcFileName.extension) with(Language) { 403 case ".d": 404 return D; 405 case ".cpp": 406 case ".CPP": 407 case ".C": 408 case ".cxx": 409 case ".c++": 410 case ".cc": 411 return Cplusplus; 412 case ".c": 413 return C; 414 default: 415 return unknown; 416 } 417 }