1 module reggae.rules.common; 2 3 4 import reggae.build; 5 import reggae.config: options; 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 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 const 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 "Compile-time" link function. 61 Its parameters are compile-time so that it can be aliased and used 62 at global scope in a reggafile. 63 Links an executable from the given dependency targets. The linker used 64 depends on the file extension of the leaf nodes of the passed-in targets. 65 If any D files are found, the linker is the D compiler, and so on with 66 C++ and C. If none of those apply, the D compiler is used. 67 */ 68 Target link(ExeName exeName, alias dependenciesFunc, Flags flags = Flags())() @safe { 69 auto dependencies = dependenciesFunc(); 70 return link(exeName, dependencies, flags); 71 } 72 73 /** 74 Regular run-time link function. 75 Links an executable from the given dependency targets. The linker used 76 depends on the file extension of the leaf nodes of the passed-in targets. 77 If any D files are found, the linker is the D compiler, and so on with 78 C++ and C. If none of those apply, the D compiler is used. 79 */ 80 Target link(in ExeName exeName, in Target[] dependencies, in Flags flags = Flags()) @safe pure { 81 const command = Command(CommandType.link, assocList([assocEntry("flags", flags.value.splitter.array)])); 82 return Target(exeName.value, command, dependencies); 83 } 84 85 86 /** 87 Convenience rule for creating static libraries 88 */ 89 Target[] staticLibrary(string name, 90 alias sourcesFunc = Sources!(), 91 Flags flags = Flags(), 92 ImportPaths includes = ImportPaths(), 93 StringImportPaths stringImports = StringImportPaths()) 94 () { 95 96 version(Posix) {} 97 else 98 static assert(false, "Can only create static libraries on Posix"); 99 100 const srcFiles = sourcesToFileNames!(sourcesFunc); 101 return [Target(buildPath("$builddir", name), "ar rcs $out $in", 102 objectFiles!(sourcesFunc, flags, includes, stringImports)())]; 103 } 104 105 /** 106 "Compile-time" target creation. 107 Its parameters are compile-time so that it can be aliased and used 108 at global scope in a reggaefile 109 */ 110 Target target(alias outputs, 111 alias command = "", 112 alias dependenciesFunc = () { Target[] ts; return ts; }, 113 alias implicitsFunc = () { Target[] ts; return ts; })() @trusted { 114 115 auto depsRes = dependenciesFunc(); 116 auto impsRes = implicitsFunc(); 117 118 static if(isArray!(typeof(depsRes))) 119 auto dependencies = depsRes; 120 else 121 auto dependencies = [depsRes]; 122 123 static if(isArray!(typeof(impsRes))) 124 auto implicits = impsRes; 125 else 126 auto implicits = [impsRes]; 127 128 129 return Target(outputs, command, dependencies, implicits); 130 } 131 132 133 /** 134 * Convenience alias for appending targets without calling any runtime function. 135 * This replaces the need to manually define a function to return a `Build` struct 136 * just to concatenate targets 137 */ 138 Target[] targetConcat(T...)() { 139 Target[] ret; 140 foreach(target; T) { 141 static if(isCallable!target) 142 ret ~= target(); 143 else 144 ret ~= target; 145 } 146 return ret; 147 } 148 149 150 //end of rules 151 152 string[] sourcesToFileNames(alias sourcesFunc = Sources!())() @trusted { 153 154 import std.exception: enforce; 155 import std.file; 156 import std.path: buildNormalizedPath, buildPath; 157 import std.array: array; 158 import std.traits: isCallable; 159 160 auto srcs = sourcesFunc(); 161 162 DirEntry[] modules; 163 foreach(dir; srcs.dirs.value.map!(a => buildPath(options.projectPath, a))) { 164 enforce(isDir(dir), dir ~ " is not a directory name"); 165 auto entries = dirEntries(dir, SpanMode.depth); 166 auto normalised = entries.map!(a => DirEntry(buildNormalizedPath(a))); 167 168 modules ~= normalised.filter!(a => !a.isDir).array; 169 } 170 171 foreach(module_; srcs.files.value) { 172 modules ~= DirEntry(buildNormalizedPath(buildPath(options.projectPath, module_))); 173 } 174 175 return modules. 176 map!(a => a.name.removeProjectPath). 177 filter!(srcs.filterFunc). 178 filter!(a => a != "reggaefile.d"). 179 array; 180 } 181 182 //run-time version 183 string[] sourcesToFileNames(in string projectPath, 184 in string[] srcDirs, 185 in string[] excDirs, 186 in string[] srcFiles, 187 in string[] excFiles) @trusted { 188 189 190 191 import std.exception: enforce; 192 import std.file; 193 import std.path: buildNormalizedPath, buildPath; 194 import std.array: array; 195 import std.traits: isCallable; 196 197 DirEntry[] files; 198 foreach(dir; srcDirs.filter!(a => !excDirs.canFind(a)).map!(a => buildPath(projectPath, a))) { 199 enforce(isDir(dir), dir ~ " is not a directory name"); 200 auto entries = dirEntries(dir, SpanMode.depth); 201 auto normalised = entries.map!(a => DirEntry(buildNormalizedPath(a))); 202 203 files ~= normalised.array; 204 } 205 206 foreach(module_; srcFiles) { 207 files ~= DirEntry(buildNormalizedPath(buildPath(projectPath, module_))); 208 } 209 210 return files. 211 map!(a => removeProjectPath(projectPath, a.name)). 212 filter!(a => !excFiles.canFind(a)). 213 filter!(a => a != "reggaefile.d"). 214 array; 215 } 216 217 218 //run-time version 219 Target[] objectFiles(in string projectPath, 220 in string[] srcDirs, 221 in string[] excDirs, 222 in string[] srcFiles, 223 in string[] excFiles, 224 in string flags, 225 in string[] includes, 226 in string[] stringImports) @trusted { 227 228 return srcFilesToObjectTargets(sourcesToFileNames(projectPath, srcDirs, excDirs, srcFiles, excFiles), 229 Flags(flags), 230 const ImportPaths(includes), 231 const StringImportPaths(stringImports)); 232 } 233 234 //run-time version 235 Target[] staticLibrary(in string projectPath, 236 in string name, 237 in string[] srcDirs, 238 in string[] excDirs, 239 in string[] srcFiles, 240 in string[] excFiles, 241 in string flags, 242 in string[] includes, 243 in string[] stringImports) @trusted { 244 245 246 version(Posix) {} 247 else 248 static assert(false, "Can only create static libraries on Posix"); 249 250 const allFiles = sourcesToFileNames(projectPath, srcDirs, excDirs, srcFiles, excFiles); 251 return [Target([buildPath("$builddir", name)], 252 "ar rcs $out $in", 253 objectFiles(projectPath, srcDirs, excDirs, srcFiles, excFiles, flags, includes, stringImports))]; 254 } 255 256 257 private Target[] srcFilesToObjectTargets(in string[] srcFiles, 258 in Flags flags, 259 in ImportPaths includes, 260 in StringImportPaths stringImports) { 261 262 const dSrcs = srcFiles.filter!(a => a.getLanguage == Language.D).array; 263 auto otherSrcs = srcFiles.filter!(a => a.getLanguage != Language.D && a.getLanguage != Language.unknown); 264 import reggae.rules.d: dlangPackageObjectFiles; 265 return dlangPackageObjectFiles(dSrcs, flags.value, ["."] ~ includes.value, stringImports.value) ~ 266 otherSrcs.map!(a => objectFile(SourceFile(a), flags, includes)).array; 267 268 } 269 270 271 version(Windows) { 272 immutable objExt = ".obj"; 273 immutable exeExt = ".exe"; 274 } else { 275 immutable objExt = ".o"; 276 immutable exeExt = ""; 277 } 278 279 package string objFileName(in string srcFileName) pure { 280 import std.path: stripExtension, defaultExtension, isRooted, stripDrive; 281 immutable localFileName = srcFileName.isRooted 282 ? srcFileName.stripDrive[1..$] 283 : srcFileName; 284 return localFileName.stripExtension.defaultExtension(objExt); 285 } 286 287 string removeProjectPath(in string path) pure { 288 import std.path: relativePath, absolutePath; 289 //relativePath is @system 290 return () @trusted { return path.absolutePath.relativePath(options.projectPath.absolutePath); }(); 291 } 292 293 string removeProjectPath(in string projectPath, in string path) pure { 294 import std.path: relativePath, absolutePath; 295 //relativePath is @system 296 return () @trusted { return path.absolutePath.relativePath(projectPath.absolutePath); }(); 297 } 298 299 300 301 Command compileCommand(in string srcFileName, 302 in string flags = "", 303 in string[] includePaths = [], 304 in string[] stringImportPaths = [], 305 in string projDir = "$project", 306 Flag!"justCompile" justCompile = Yes.justCompile) pure { 307 308 string maybeExpand(string path) { 309 return path.startsWith(gBuilddir) ? expandBuildDir(path) : buildPath(projDir, path); 310 } 311 312 auto includeParams = includePaths.map!(a => "-I" ~ maybeExpand(a)). array; 313 auto flagParams = flags.splitter.array; 314 immutable language = getLanguage(srcFileName); 315 316 auto params = [assocEntry("includes", includeParams), 317 assocEntry("flags", flagParams)]; 318 319 if(language == Language.D) 320 params ~= assocEntry("stringImports", stringImportPaths.map!(a => "-J" ~ maybeExpand(a)).array); 321 322 params ~= assocEntry("DEPFILE", [srcFileName.objFileName ~ ".dep"]); 323 324 immutable type = justCompile ? CommandType.compile : CommandType.compileAndLink; 325 return Command(type, assocList(params)); 326 } 327 328 329 enum Language { 330 C, 331 Cplusplus, 332 D, 333 unknown, 334 } 335 336 Language getLanguage(in string srcFileName) pure nothrow { 337 switch(srcFileName.extension) with(Language) { 338 case ".d": 339 return D; 340 case ".cpp": 341 case ".CPP": 342 case ".C": 343 case ".cxx": 344 case ".c++": 345 case ".cc": 346 return Cplusplus; 347 case ".c": 348 return C; 349 default: 350 return unknown; 351 } 352 }