1 module reggae.rules.common; 2 3 4 import reggae.build; 5 import reggae.ctaa; 6 import reggae.path: buildPath; 7 import reggae.types; 8 import std.algorithm; 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 40 /** 41 An object file, typically from one source file in a certain language 42 (although for D the default is a whole package). The language is determined 43 by the file extension of the file passed in. 44 The $(D projDir) variable is best left alone; right now only the dub targets 45 make use of it (since dub packages are by definition outside of the project 46 source tree). 47 */ 48 Target objectFile(in SourceFile srcFile, 49 in Flags flags = Flags(), 50 in ImportPaths includePaths = ImportPaths(), 51 in StringImportPaths stringImportPaths = StringImportPaths(), 52 Target[] implicits = [], 53 in string projDir = "$project") @safe 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)], implicits); 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, const 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())() { 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.dup)])); 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 compilerFlags = Flags(), 125 ImportPaths includes = ImportPaths(), 126 StringImportPaths stringImports = StringImportPaths(), 127 alias dependenciesFunc = () { Target[] ts; return ts; }) 128 () 129 { 130 131 return staticLibraryTarget( 132 name, 133 objectFiles!(sourcesFunc, compilerFlags, includes, stringImports)() ~ dependenciesFunc() 134 ); 135 } 136 137 /** 138 "Compile-time" target creation. 139 Its parameters are compile-time so that it can be aliased and used 140 at global scope in a reggaefile 141 */ 142 Target target(alias outputs, 143 alias command = "", 144 alias dependenciesFunc = () { Target[] ts; return ts; }, 145 alias implicitsFunc = () { Target[] ts; return ts; })() @trusted { 146 147 auto depsRes = dependenciesFunc(); 148 auto impsRes = implicitsFunc(); 149 150 static if(isArray!(typeof(depsRes))) 151 auto dependencies = depsRes; 152 else 153 auto dependencies = [depsRes]; 154 155 static if(isArray!(typeof(impsRes))) 156 auto implicits = impsRes; 157 else 158 auto implicits = [impsRes]; 159 160 161 return Target(outputs, command, dependencies, implicits); 162 } 163 164 165 /** 166 * Convenience alias for appending targets without calling any runtime function. 167 * This replaces the need to manually define a function to return a `Build` struct 168 * just to concatenate targets 169 */ 170 Target[] targetConcat(T...)() { 171 Target[] ret; 172 foreach(target; T) { 173 static if(isCallable!target) 174 ret ~= target(); 175 else 176 ret ~= target; 177 } 178 return ret; 179 } 180 181 /** 182 Compile-time version of Target.phony 183 */ 184 Target phony(string name, 185 string shellCommand, 186 alias dependenciesFunc = () { Target[] ts; return ts; }, 187 alias implicitsFunc = () { Target[] ts; return ts; })() { 188 return Target.phony(name, shellCommand, arrayify!dependenciesFunc, arrayify!implicitsFunc); 189 } 190 191 192 //end of rules 193 194 private auto arrayify(alias func)() { 195 import std.traits; 196 auto ret = func(); 197 static if(isArray!(typeof(ret))) 198 return ret; 199 else 200 return [ret]; 201 } 202 203 auto sourcesToTargets(alias sourcesFunc = Sources!())() { 204 return sourcesToFileNames!sourcesFunc.map!(a => Target(a)); 205 } 206 207 string[] sourcesToFileNames(alias sourcesFunc = Sources!())() @trusted { 208 import std.exception: enforce; 209 import std.file; 210 import std.path: buildNormalizedPath; 211 import std.array: array; 212 import std.traits: isCallable; 213 import reggae.config: options; 214 215 auto srcs = sourcesFunc(); 216 217 DirEntry[] modules; 218 foreach(dir; srcs.dirs.value.map!(a => buildPath(options.projectPath, a))) { 219 enforce(isDir(dir), dir ~ " is not a directory name"); 220 auto entries = dirEntries(dir, SpanMode.depth); 221 auto normalised = entries.map!(a => DirEntry(buildNormalizedPath(a))); 222 223 modules ~= normalised.filter!(a => !a.isDir).array; 224 } 225 226 foreach(module_; srcs.files.value) { 227 modules ~= DirEntry(buildNormalizedPath(buildPath(options.projectPath, module_))); 228 } 229 230 return modules. 231 map!(a => a.name.removeProjectPath). 232 filter!(srcs.filterFunc). 233 filter!(a => a != "reggaefile.d"). 234 array; 235 } 236 237 //run-time version 238 string[] sourcesToFileNames(in string projectPath, 239 in string[] srcDirs, 240 const(string)[] excDirs, 241 in string[] srcFiles, 242 in string[] excFiles) @trusted { 243 244 245 246 import std.exception: enforce; 247 import std.file; 248 import std.path: absolutePath, buildNormalizedPath, dirName; 249 import std.array: array; 250 import std.traits: isCallable; 251 252 excDirs = (excDirs ~ ".reggae").map!(a => buildPath(projectPath, a)).array; 253 254 DirEntry[] files; 255 foreach(dir; srcDirs.map!(a => buildPath(projectPath, a))) { 256 enforce(isDir(dir), dir ~ " is not a directory name"); 257 258 auto entries = dirEntries(dir, SpanMode.depth) 259 .map!(a => a.buildNormalizedPath) 260 .filter!(a => !excDirs.canFind!(b => a.dirName.absolutePath.startsWith(b))); 261 files ~= entries.map!(a => DirEntry(a)).array; 262 } 263 264 foreach(module_; srcFiles) { 265 files ~= DirEntry(buildNormalizedPath(buildPath(projectPath, module_))); 266 } 267 268 return files. 269 map!(a => removeProjectPath(projectPath, a.name)). 270 filter!(a => !excFiles.canFind(a)). 271 filter!(a => a != "reggaefile.d"). 272 array; 273 } 274 275 276 //run-time version 277 Target[] objectFiles(in string projectPath, 278 in string[] srcDirs, 279 in string[] excDirs, 280 in string[] srcFiles, 281 in string[] excFiles, 282 in string[] flags = [], 283 in string[] includes = [], 284 in string[] stringImports = []) @trusted { 285 286 return srcFilesToObjectTargets(sourcesToFileNames(projectPath, srcDirs, excDirs, srcFiles, excFiles), 287 const Flags(flags), 288 const ImportPaths(includes), 289 const StringImportPaths(stringImports)); 290 } 291 292 //run-time version 293 Target staticLibrary(in string projectPath, 294 in string name, 295 in string[] srcDirs, 296 in string[] excDirs, 297 in string[] srcFiles, 298 in string[] excFiles, 299 in string[] flags, 300 in string[] includes, 301 in string[] stringImports) @trusted { 302 303 return staticLibraryTarget( 304 name, 305 objectFiles(projectPath, srcDirs, excDirs, srcFiles, excFiles, flags, includes, stringImports) 306 ); 307 } 308 309 Target staticLibraryTarget(in string name, Target[] objects) @safe pure { 310 import std.path: extension; 311 const realName = name.extension == libExt ? name : name ~ libExt; 312 auto target = Target( 313 [buildPath("$builddir", realName)], 314 staticLibraryShellCommand, 315 objects, 316 ); 317 return target; 318 } 319 320 version(Windows) 321 private enum staticLibraryShellCommand = "lib.exe /OUT:$out $in"; 322 else 323 private enum staticLibraryShellCommand = "ar rcs $out $in"; 324 325 private Target[] srcFilesToObjectTargets(in string[] srcFiles, 326 in Flags flags, 327 in ImportPaths includes, 328 in StringImportPaths stringImports) { 329 330 const dSrcs = srcFiles.filter!(a => a.getLanguage == Language.D).array; 331 auto otherSrcs = srcFiles.filter!(a => a.getLanguage != Language.D && a.getLanguage != Language.unknown); 332 import reggae.rules.d: dlangObjectFiles; 333 return 334 dlangObjectFiles(dSrcs, flags.value, ["."] ~ includes.value, stringImports.value) ~ 335 otherSrcs.map!(a => objectFile(SourceFile(a), flags, includes)).array; 336 } 337 338 339 version(Windows) { 340 immutable objExt = ".obj"; 341 immutable exeExt = ".exe"; 342 immutable libExt = ".lib"; 343 } else { 344 immutable objExt = ".o"; 345 immutable exeExt = ""; 346 immutable libExt = ".a"; 347 } 348 349 string objFileName(in string srcFileName) @safe pure { 350 return extFileName(srcFileName, objExt); 351 } 352 353 string libFileName(in string srcFileName) @safe pure { 354 return extFileName(srcFileName, libExt); 355 } 356 357 358 string extFileName(in string srcFileName, in string extension) @safe pure { 359 import reggae.path: buildPath, deabsolutePath; 360 import std.path: stripExtension; 361 import std.array: replace; 362 363 auto tmp = srcFileName 364 .buildPath 365 .deabsolutePath 366 .stripExtension 367 ; 368 369 return (tmp ~ extension).replace("..", "__"); 370 } 371 372 373 string removeProjectPath(in string path) @safe { 374 import std.path: relativePath, absolutePath; 375 import reggae.config: options; 376 //relativePath is @system 377 return () @trusted { return path.absolutePath.relativePath(options.projectPath.absolutePath); }(); 378 } 379 380 string removeProjectPath(in string projectPath, in string path) @safe pure { 381 import std.path: relativePath, absolutePath; 382 //relativePath is @system 383 return () @trusted { return path.absolutePath.relativePath(projectPath.absolutePath); }(); 384 } 385 386 387 388 Command compileCommand(in string srcFileName, 389 in string[] flags = [], 390 in string[] includePaths = [], 391 in string[] stringImportPaths = [], 392 in string projDir = "$project", 393 Flag!"justCompile" justCompile = Yes.justCompile) 394 @safe pure 395 { 396 397 string maybeExpand(string path) { 398 return expandOutput(path, projDir, projDir); 399 } 400 401 auto includeParams = includePaths.map!(a => "-I" ~ maybeExpand(a)). array; 402 immutable language = getLanguage(srcFileName); 403 404 auto params = [ 405 assocEntry("includes", includeParams), 406 assocEntry("flags", flags.dup), 407 ]; 408 409 if(language == Language.D) 410 params ~= assocEntry("stringImports", 411 stringImportPaths.map!(a => "-J" ~ maybeExpand(a)).array); 412 413 params ~= assocEntry("DEPFILE", [srcFileName.objFileName ~ ".dep"]); 414 415 immutable type = justCompile ? CommandType.compile : CommandType.compileAndLink; 416 return Command(type, assocList(params)); 417 } 418 419 420 enum Language { 421 C, 422 Cplusplus, 423 D, 424 unknown, 425 } 426 427 Language getLanguage(in string srcFileName) @safe pure nothrow { 428 import std.path: extension; 429 430 switch(srcFileName.extension) with(Language) { 431 case ".d": 432 return D; 433 case ".cpp": 434 case ".CPP": 435 case ".C": 436 case ".cxx": 437 case ".c++": 438 case ".cc": 439 return Cplusplus; 440 case ".c": 441 return C; 442 default: 443 return unknown; 444 } 445 }