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