1 module reggae.dub.info; 2 3 import reggae.build; 4 import reggae.rules; 5 import reggae.types; 6 import reggae.sorting; 7 import reggae.options: Options; 8 import reggae.path: buildPath; 9 import std.algorithm: map, filter, find, splitter; 10 import std.array: array, join; 11 import std.range: chain; 12 13 14 enum TargetType { 15 autodetect, 16 none, 17 executable, 18 library, 19 sourceLibrary, 20 dynamicLibrary, 21 staticLibrary, 22 object, 23 } 24 25 26 struct DubPackage { 27 string name; 28 string path; /// path to the dub package 29 string mainSourceFile; 30 string targetFileName; 31 string[] dflags; 32 string[] lflags; 33 string[] importPaths; 34 string[] stringImportPaths; 35 string[] files; 36 TargetType targetType; 37 string[] versions; 38 string[] dependencies; 39 string[] libs; 40 string[] preBuildCommands; 41 string[] postBuildCommands; 42 string targetPath; 43 44 string toString() @safe pure const { 45 import std.string: join; 46 import std.conv: to; 47 import std.traits: Unqual; 48 49 auto ret = `DubPackage(`; 50 string[] lines; 51 52 foreach(ref elt; this.tupleof) { 53 static if(is(Unqual!(typeof(elt)) == TargetType)) 54 lines ~= `TargetType.` ~ elt.to!string; 55 else static if(is(Unqual!(typeof(elt)) == string)) 56 lines ~= "`" ~ elt.to!string ~ "`"; 57 else 58 lines ~= elt.to!string; 59 } 60 ret ~= lines.join(`, `); 61 ret ~= `)`; 62 return ret; 63 } 64 65 DubPackage dup() @safe pure nothrow const { 66 DubPackage ret; 67 foreach(i, member; this.tupleof) { 68 static if(__traits(compiles, member.dup)) 69 ret.tupleof[i] = member.dup; 70 else 71 ret.tupleof[i] = member; 72 } 73 return ret; 74 } 75 } 76 77 bool isStaticLibrary(in string fileName) @safe pure nothrow { 78 import std.path: extension; 79 version(Windows) 80 return fileName.extension == ".lib"; 81 else 82 return fileName.extension == ".a"; 83 } 84 85 bool isObjectFile(in string fileName) @safe pure nothrow { 86 import reggae.rules.common: objExt; 87 import std.path: extension; 88 return fileName.extension == objExt; 89 } 90 91 string inDubPackagePath(in string packagePath, in string filePath) @safe pure nothrow { 92 import std.algorithm: startsWith; 93 return filePath.startsWith("$project") 94 ? buildPath(filePath) 95 : buildPath(packagePath, filePath); 96 } 97 98 struct DubObjsDir { 99 string globalDir; 100 string targetDir; 101 } 102 103 struct DubInfo { 104 105 import reggae.rules.dub: CompilationMode; 106 107 DubPackage[] packages; 108 109 DubInfo dup() @safe pure nothrow const { 110 import std.algorithm: map; 111 import std.array: array; 112 return DubInfo(packages.map!(a => a.dup).array); 113 } 114 115 Target[] toTargets(in string[] compilerFlags = [], 116 in CompilationMode compilationMode = CompilationMode.options, 117 in DubObjsDir dubObjsDir = DubObjsDir(), 118 in size_t startingIndex = 0) 119 @safe const 120 { 121 Target[] targets; 122 123 foreach(i; startingIndex .. packages.length) { 124 targets ~= packageIndexToTargets(i, compilerFlags, compilationMode, dubObjsDir); 125 } 126 127 return targets ~ allObjectFileSources ~ allStaticLibrarySources; 128 } 129 130 // dubPackage[i] -> Target[] 131 private Target[] packageIndexToTargets( 132 in size_t dubPackageIndex, 133 in string[] compilerFlags = [], 134 in CompilationMode compilationMode = CompilationMode.options, 135 in DubObjsDir dubObjsDir = DubObjsDir()) 136 @safe const 137 { 138 import reggae.path: deabsolutePath; 139 import reggae.config: options; 140 import std.range: chain, only; 141 import std.algorithm: filter; 142 import std.array: array, replace; 143 import std.functional: not; 144 import std.path: baseName, dirSeparator; 145 import std.string: stripRight; 146 147 const dubPackage = packages[dubPackageIndex]; 148 const importPaths = allImportPaths(); 149 const stringImportPaths = dubPackage.allOf!(a => a.packagePaths(a.stringImportPaths))(packages); 150 const isMainPackage = dubPackageIndex == 0; 151 //the path must be explicit for the other packages, implicit for the "main" 152 //package 153 const projDir = isMainPackage ? "" : dubPackage.path; 154 155 // -unittest should only apply to the main package 156 const(string)[] deUnitTest(in string[] flags) { 157 return isMainPackage 158 ? flags 159 : flags.filter!(f => f != "-unittest" && f != "-main").array; 160 } 161 162 const flags = chain(dubPackage.dflags, 163 dubPackage.versions.map!(a => "-version=" ~ a), 164 options.dflags.splitter, // TODO: doesn't support quoted args with spaces 165 deUnitTest(compilerFlags)) 166 .array; 167 168 const files = dubPackage.files 169 .filter!(not!isStaticLibrary) 170 .filter!(not!isObjectFile) 171 .map!(a => buildPath(dubPackage.path, a)) 172 .array; 173 174 auto compileFunc() { 175 final switch(compilationMode) with(CompilationMode) { 176 case all: return &dlangObjectFilesTogether; 177 case module_: return &dlangObjectFilesPerModule; 178 case package_: return &dlangObjectFilesPerPackage; 179 case options: return &dlangObjectFiles; 180 } 181 } 182 183 auto targetsFunc() { 184 import reggae.rules.d: dlangStaticLibraryTogether; 185 import reggae.config: options; 186 187 const isStaticLibDep = 188 dubPackage.targetType == TargetType.staticLibrary && 189 dubPackageIndex != 0 && 190 !options.dubDepObjsInsteadOfStaticLib; 191 192 return isStaticLibDep 193 ? &dlangStaticLibraryTogether 194 : compileFunc; 195 } 196 197 auto packageTargets = targetsFunc()(files, flags, importPaths, stringImportPaths, [], projDir); 198 199 // go through dub dependencies and adjust object file output paths 200 if(!isMainPackage) { 201 // optionally put the object files in dubObjsDir 202 if(dubObjsDir.globalDir != "") { 203 foreach(ref target; packageTargets) { 204 target.rawOutputs[0] = buildPath(dubObjsDir.globalDir, 205 options.projectPath.deabsolutePath, 206 dubObjsDir.targetDir, 207 target.rawOutputs[0]); 208 } 209 } else { 210 const dubPkgRoot = buildPath(dubPackage.path).deabsolutePath.stripRight(dirSeparator); 211 const shortenedRoot = buildPath("__dub__", baseName(dubPackage.path)); 212 foreach(ref target; packageTargets) 213 target.rawOutputs[0] = buildPath(target.rawOutputs[0]).replace(dubPkgRoot, shortenedRoot); 214 } 215 } 216 217 return packageTargets; 218 } 219 220 Target[] packageNameToTargets( 221 in string name, 222 in string[] compilerFlags = [], 223 in CompilationMode compilationMode = CompilationMode.options, 224 in DubObjsDir dubObjsDir = DubObjsDir()) 225 @safe const 226 { 227 foreach(const index, const dubPackage; packages) { 228 if(dubPackage.name == name) 229 return packageIndexToTargets(index, compilerFlags, compilationMode, dubObjsDir); 230 } 231 232 throw new Exception("Couldn't find package '" ~ name ~ "'"); 233 } 234 235 TargetName targetName() @safe const pure nothrow { 236 const fileName = packages[0].targetFileName; 237 return .targetName(targetType, fileName); 238 } 239 240 string targetPath(in Options options) @safe const pure { 241 import std.path: relativePath; 242 243 return options.workingDir == options.projectPath 244 ? packages[0].targetPath.relativePath(options.projectPath) 245 : ""; 246 } 247 248 TargetType targetType() @safe const pure nothrow { 249 return packages[0].targetType; 250 } 251 252 string[] mainLinkerFlags() @safe pure nothrow const { 253 import std.array: join; 254 255 const pack = packages[0]; 256 return (pack.targetType == TargetType.library || pack.targetType == TargetType.staticLibrary) 257 ? ["-shared"] 258 : []; 259 } 260 261 // template due to purity - in the 2nd build with the payload this is pure, 262 // but in the 1st build to generate the reggae executable it's not. 263 // See reggae.config. 264 string[] linkerFlags()() const { 265 import reggae.config: options; 266 267 const allLibs = packages[0].libs; 268 269 static string libFlag(in string lib) { 270 version(Posix) 271 return "-L-l" ~ lib; 272 else 273 return lib ~ ".lib"; 274 } 275 276 return 277 packages[0].libs.map!libFlag.array ~ 278 packages[0].lflags.dup 279 ; 280 } 281 282 string[] allImportPaths() @safe nothrow const { 283 import reggae.config: options; 284 import std.algorithm: sorted = sort, uniq; 285 import std.array: array; 286 287 string[] paths; 288 auto rng = packages.map!(a => a.packagePaths(a.importPaths)); 289 foreach(p; rng) paths ~= p; 290 auto allPaths = paths ~ options.projectPath; 291 return allPaths.sorted.uniq.array; 292 } 293 294 // must be at the very end 295 private Target[] allStaticLibrarySources() @trusted /*join*/ nothrow const pure { 296 import std.algorithm: filter, map; 297 import std.array: array, join; 298 299 return packages 300 .map!(a => cast(string[]) a.files.filter!isStaticLibrary.array) 301 .join 302 .map!(a => Target(a)) 303 .array; 304 } 305 306 private Target[] allObjectFileSources() @trusted nothrow const pure { 307 import std.algorithm.iteration: filter, map, uniq; 308 import std.algorithm.sorting: sort; 309 import std.array: array, join; 310 311 string[] objectFiles = 312 packages 313 .map!(a => cast(string[]) a 314 .files 315 .filter!isObjectFile 316 .map!(b => inDubPackagePath(a.path, b)) 317 .array 318 ) 319 .join 320 .array; 321 sort(objectFiles); 322 323 return objectFiles 324 .uniq 325 .map!(a => Target(a)) 326 .array; 327 } 328 329 330 // all postBuildCommands in one shell command. Empty if there are none 331 string postBuildCommands() @safe pure nothrow const { 332 import std.string: join; 333 return packages[0].postBuildCommands.join(" && "); 334 } 335 } 336 337 338 private auto packagePaths(in DubPackage dubPackage, in string[] paths) @trusted nothrow { 339 return paths.map!(a => buildPath(dubPackage.path, a)).array; 340 } 341 342 //@trusted because of map.array 343 private string[] allOf(alias F)(in DubPackage pack, in DubPackage[] packages) @trusted nothrow { 344 345 import std.range: chain, only; 346 import std.array: array, front, empty; 347 348 string[] result; 349 350 foreach(dependency; chain(only(pack.name), pack.dependencies)) { 351 auto depPack = packages.find!(a => a.name == dependency); 352 if(!depPack.empty) { 353 result ~= F(depPack.front).array; 354 } 355 } 356 return result; 357 } 358 359 360 TargetName targetName(in TargetType targetType, in string fileName) @safe pure nothrow { 361 362 import reggae.rules.common: exeExt; 363 364 switch(targetType) with(TargetType) { 365 default: 366 return TargetName(fileName); 367 368 case executable: 369 return TargetName(fileName ~ exeExt); 370 371 case library: 372 version(Posix) 373 return TargetName("lib" ~ fileName ~ ".a"); 374 else 375 return TargetName(fileName ~ ".lib"); 376 377 case dynamicLibrary: 378 version(Posix) 379 return TargetName("lib" ~ fileName ~ ".so"); 380 else 381 return TargetName(fileName ~ ".dll"); 382 } 383 }