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