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(name, 306 objectFiles(projectPath, srcDirs, excDirs, srcFiles, excFiles, flags, includes, stringImports)); 307 else 308 throw new Exception("Can only create static libraries on Posix"); 309 } 310 311 Target[] staticLibraryTarget(in string name, Target[] objects) { 312 import std.path: extension; 313 const realName = name.extension == libExt ? name : name ~ libExt; 314 return [Target([buildPath("$builddir", realName)], 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: dlangObjectFiles; 329 return 330 dlangObjectFiles(dSrcs, flags.value, ["."] ~ includes.value, stringImports.value) ~ 331 otherSrcs.map!(a => objectFile(SourceFile(a), flags, includes)).array; 332 } 333 334 335 version(Windows) { 336 immutable objExt = ".obj"; 337 immutable exeExt = ".exe"; 338 immutable libExt = ".lib"; 339 } else { 340 immutable objExt = ".o"; 341 immutable exeExt = ""; 342 immutable libExt = ".a"; 343 } 344 345 package string objFileName(in string srcFileName) @safe pure { 346 import reggae.path: deabsolutePath; 347 import std.path: stripExtension, defaultExtension, isRooted; 348 import std.array: replace; 349 350 immutable localFileName = srcFileName.deabsolutePath; 351 352 return localFileName.stripExtension.defaultExtension(objExt).replace("..", "__"); 353 } 354 355 string removeProjectPath(in string path) @safe { 356 import std.path: relativePath, absolutePath; 357 import reggae.config: options; 358 //relativePath is @system 359 return () @trusted { return path.absolutePath.relativePath(options.projectPath.absolutePath); }(); 360 } 361 362 string removeProjectPath(in string projectPath, in string path) @safe pure { 363 import std.path: relativePath, absolutePath; 364 //relativePath is @system 365 return () @trusted { return path.absolutePath.relativePath(projectPath.absolutePath); }(); 366 } 367 368 369 370 Command compileCommand(in string srcFileName, 371 in string flags = "", 372 in string[] includePaths = [], 373 in string[] stringImportPaths = [], 374 in string projDir = "$project", 375 Flag!"justCompile" justCompile = Yes.justCompile) @safe pure { 376 377 string maybeExpand(string path) { 378 return path.startsWith(gBuilddir) ? expandBuildDir(path) : buildPath(projDir, path); 379 } 380 381 auto includeParams = includePaths.map!(a => "-I" ~ maybeExpand(a)). array; 382 auto flagParams = flags.splitter.array; 383 immutable language = getLanguage(srcFileName); 384 385 auto params = [assocEntry("includes", includeParams), 386 assocEntry("flags", flagParams)]; 387 388 if(language == Language.D) 389 params ~= assocEntry("stringImports", 390 stringImportPaths.map!(a => "-J" ~ maybeExpand(a)).array); 391 392 params ~= assocEntry("DEPFILE", [srcFileName.objFileName ~ ".dep"]); 393 394 immutable type = justCompile ? CommandType.compile : CommandType.compileAndLink; 395 return Command(type, assocList(params)); 396 } 397 398 399 enum Language { 400 C, 401 Cplusplus, 402 D, 403 unknown, 404 } 405 406 Language getLanguage(in string srcFileName) @safe pure nothrow { 407 switch(srcFileName.extension) with(Language) { 408 case ".d": 409 return D; 410 case ".cpp": 411 case ".CPP": 412 case ".C": 413 case ".cxx": 414 case ".c++": 415 case ".cc": 416 return Cplusplus; 417 case ".c": 418 return C; 419 default: 420 return unknown; 421 } 422 }