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 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, 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.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 return staticLibraryTarget(name, objectFiles!(sourcesFunc, flags, includes, stringImports)() ~ dependenciesFunc()); 133 else 134 throw new Exception("Can only create static libraries on Posix"); 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, buildPath; 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: buildNormalizedPath, buildPath; 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 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 304 version(Posix) 305 return staticLibraryTarget( 306 name, 307 objectFiles(projectPath, srcDirs, excDirs, srcFiles, excFiles, flags, includes, stringImports) 308 ); 309 else 310 throw new Exception("Can only create static libraries on Posix"); 311 } 312 313 Target[] staticLibraryTarget(in string name, Target[] objects) @safe pure { 314 import std.path: extension; 315 const realName = name.extension == libExt ? name : name ~ libExt; 316 auto target = Target( 317 [buildPath("$builddir", realName)], 318 staticLibraryShellCommand, 319 objects, 320 ); 321 return [target]; 322 } 323 324 private enum staticLibraryShellCommand = "ar rcs $out $in"; 325 326 private Target[] srcFilesToObjectTargets(in string[] srcFiles, 327 in Flags flags, 328 in ImportPaths includes, 329 in StringImportPaths stringImports) { 330 331 const dSrcs = srcFiles.filter!(a => a.getLanguage == Language.D).array; 332 auto otherSrcs = srcFiles.filter!(a => a.getLanguage != Language.D && a.getLanguage != Language.unknown); 333 import reggae.rules.d: dlangObjectFiles; 334 return 335 dlangObjectFiles(dSrcs, flags.value, ["."] ~ includes.value, stringImports.value) ~ 336 otherSrcs.map!(a => objectFile(SourceFile(a), flags, includes)).array; 337 } 338 339 340 version(Windows) { 341 immutable objExt = ".obj"; 342 immutable exeExt = ".exe"; 343 immutable libExt = ".lib"; 344 } else { 345 immutable objExt = ".o"; 346 immutable exeExt = ""; 347 immutable libExt = ".a"; 348 } 349 350 string objFileName(in string srcFileName) @safe pure { 351 return extFileName(srcFileName, objExt); 352 } 353 354 string libFileName(in string srcFileName) @safe pure { 355 return extFileName(srcFileName, libExt); 356 } 357 358 359 string extFileName(in string srcFileName, in string extension) @safe pure { 360 import reggae.path: deabsolutePath; 361 import std.path: stripExtension; 362 import std.array: replace; 363 364 auto tmp = srcFileName 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 path.startsWith(gBuilddir) 399 ? expandBuildDir(path) 400 : buildPath(projDir, path); 401 } 402 403 auto includeParams = includePaths.map!(a => "-I" ~ maybeExpand(a)). array; 404 auto flagParams = flags.splitter.array; 405 immutable language = getLanguage(srcFileName); 406 407 auto params = [ 408 assocEntry("includes", includeParams), 409 assocEntry("flags", flagParams) 410 ]; 411 412 if(language == Language.D) 413 params ~= assocEntry("stringImports", 414 stringImportPaths.map!(a => "-J" ~ maybeExpand(a)).array); 415 416 params ~= assocEntry("DEPFILE", [srcFileName.objFileName ~ ".dep"]); 417 418 immutable type = justCompile ? CommandType.compile : CommandType.compileAndLink; 419 return Command(type, assocList(params)); 420 } 421 422 423 enum Language { 424 C, 425 Cplusplus, 426 D, 427 unknown, 428 } 429 430 Language getLanguage(in string srcFileName) @safe pure nothrow { 431 switch(srcFileName.extension) with(Language) { 432 case ".d": 433 return D; 434 case ".cpp": 435 case ".CPP": 436 case ".C": 437 case ".cxx": 438 case ".c++": 439 case ".cc": 440 return Cplusplus; 441 case ".c": 442 return C; 443 default: 444 return unknown; 445 } 446 }