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 const sharedFlag = targetType == TargetType.dynamicLibrary ? ["-fPIC"] : []; 156 157 // -unittest should only apply to the main package 158 const(string)[] deUnitTest(in string[] flags) { 159 return isMainPackage 160 ? flags 161 : flags.filter!(f => f != "-unittest" && f != "-main").array; 162 } 163 164 const flags = chain(dubPackage.dflags, 165 dubPackage.versions.map!(a => "-version=" ~ a), 166 options.dflags.splitter, // TODO: doesn't support quoted args with spaces 167 sharedFlag, 168 deUnitTest(compilerFlags)) 169 .array; 170 171 const files = dubPackage.files 172 .filter!(not!isStaticLibrary) 173 .filter!(not!isObjectFile) 174 .map!(a => buildPath(dubPackage.path, a)) 175 .array; 176 177 auto compileFunc() { 178 final switch(compilationMode) with(CompilationMode) { 179 case all: return &dlangObjectFilesTogether; 180 case module_: return &dlangObjectFilesPerModule; 181 case package_: return &dlangObjectFilesPerPackage; 182 case options: return &dlangObjectFiles; 183 } 184 } 185 186 auto targetsFunc() { 187 import reggae.rules.d: dlangStaticLibraryTogether; 188 import reggae.config: options; 189 190 const isStaticLibDep = 191 dubPackage.targetType == TargetType.staticLibrary && 192 dubPackageIndex != 0 && 193 !options.dubDepObjsInsteadOfStaticLib; 194 195 return isStaticLibDep 196 ? &dlangStaticLibraryTogether 197 : compileFunc; 198 } 199 200 auto packageTargets = targetsFunc()(files, flags, importPaths, stringImportPaths, [], projDir); 201 202 // go through dub dependencies and adjust object file output paths 203 if(!isMainPackage) { 204 // optionally put the object files in dubObjsDir 205 if(dubObjsDir.globalDir != "") { 206 foreach(ref target; packageTargets) { 207 target.rawOutputs[0] = buildPath(dubObjsDir.globalDir, 208 options.projectPath.deabsolutePath, 209 dubObjsDir.targetDir, 210 target.rawOutputs[0]); 211 } 212 } else { 213 const dubPkgRoot = buildPath(dubPackage.path).deabsolutePath.stripRight(dirSeparator); 214 const shortenedRoot = buildPath("__dub__", baseName(dubPackage.path)); 215 foreach(ref target; packageTargets) 216 target.rawOutputs[0] = buildPath(target.rawOutputs[0]).replace(dubPkgRoot, shortenedRoot); 217 } 218 } 219 220 return packageTargets; 221 } 222 223 Target[] packageNameToTargets( 224 in string name, 225 in string[] compilerFlags = [], 226 in CompilationMode compilationMode = CompilationMode.options, 227 in DubObjsDir dubObjsDir = DubObjsDir()) 228 @safe const 229 { 230 foreach(const index, const dubPackage; packages) { 231 if(dubPackage.name == name) 232 return packageIndexToTargets(index, compilerFlags, compilationMode, dubObjsDir); 233 } 234 235 throw new Exception("Couldn't find package '" ~ name ~ "'"); 236 } 237 238 TargetName targetName() @safe const pure nothrow { 239 const fileName = packages[0].targetFileName; 240 return .targetName(targetType, fileName); 241 } 242 243 string targetPath(in Options options) @safe const pure { 244 import std.path: relativePath; 245 246 return options.workingDir == options.projectPath 247 ? packages[0].targetPath.relativePath(options.projectPath) 248 : ""; 249 } 250 251 TargetType targetType() @safe const pure nothrow { 252 return packages[0].targetType; 253 } 254 255 string[] mainLinkerFlags() @safe pure nothrow const { 256 import std.array: join; 257 258 const pack = packages[0]; 259 return (pack.targetType == TargetType.library || pack.targetType == TargetType.staticLibrary) 260 ? ["-shared"] 261 : []; 262 } 263 264 // template due to purity - in the 2nd build with the payload this is pure, 265 // but in the 1st build to generate the reggae executable it's not. 266 // See reggae.config. 267 string[] linkerFlags()() const { 268 import reggae.config: options; 269 270 const allLibs = packages[0].libs; 271 272 static string libFlag(in string lib) { 273 version(Posix) 274 return "-L-l" ~ lib; 275 else 276 return lib ~ ".lib"; 277 } 278 279 return 280 packages[0].libs.map!libFlag.array ~ 281 packages[0].lflags.dup 282 ; 283 } 284 285 string[] allImportPaths() @safe nothrow const { 286 import reggae.config: options; 287 import std.algorithm: sorted = sort, uniq; 288 import std.array: array; 289 290 string[] paths; 291 auto rng = packages.map!(a => a.packagePaths(a.importPaths)); 292 foreach(p; rng) paths ~= p; 293 auto allPaths = paths ~ options.projectPath; 294 return allPaths.sorted.uniq.array; 295 } 296 297 // must be at the very end 298 private Target[] allStaticLibrarySources() @trusted /*join*/ nothrow const pure { 299 import std.algorithm: filter, map; 300 import std.array: array, join; 301 302 return packages 303 .map!(a => cast(string[]) a.files.filter!isStaticLibrary.array) 304 .join 305 .map!(a => Target(a)) 306 .array; 307 } 308 309 private Target[] allObjectFileSources() @trusted nothrow const pure { 310 import std.algorithm.iteration: filter, map, uniq; 311 import std.algorithm.sorting: sort; 312 import std.array: array, join; 313 314 string[] objectFiles = 315 packages 316 .map!(a => cast(string[]) a 317 .files 318 .filter!isObjectFile 319 .map!(b => inDubPackagePath(a.path, b)) 320 .array 321 ) 322 .join 323 .array; 324 sort(objectFiles); 325 326 return objectFiles 327 .uniq 328 .map!(a => Target(a)) 329 .array; 330 } 331 332 333 // all postBuildCommands in one shell command. Empty if there are none 334 string postBuildCommands() @safe pure nothrow const { 335 import std..string: join; 336 return packages[0].postBuildCommands.join(" && "); 337 } 338 } 339 340 341 private auto packagePaths(in DubPackage dubPackage, in string[] paths) @trusted nothrow { 342 return paths.map!(a => buildPath(dubPackage.path, a)).array; 343 } 344 345 //@trusted because of map.array 346 private string[] allOf(alias F)(in DubPackage pack, in DubPackage[] packages) @trusted nothrow { 347 348 import std.range: chain, only; 349 import std.array: array, front, empty; 350 351 string[] result; 352 353 foreach(dependency; chain(only(pack.name), pack.dependencies)) { 354 auto depPack = packages.find!(a => a.name == dependency); 355 if(!depPack.empty) { 356 result ~= F(depPack.front).array; 357 } 358 } 359 return result; 360 } 361 362 363 TargetName targetName(in TargetType targetType, in string fileName) @safe pure nothrow { 364 365 import reggae.rules.common: exeExt; 366 367 switch(targetType) with(TargetType) { 368 default: 369 return TargetName(fileName); 370 371 case executable: 372 return TargetName(fileName ~ exeExt); 373 374 case library: 375 version(Posix) 376 return TargetName("lib" ~ fileName ~ ".a"); 377 else 378 return TargetName(fileName ~ ".lib"); 379 380 case dynamicLibrary: 381 version(Posix) 382 return TargetName("lib" ~ fileName ~ ".so"); 383 else 384 return TargetName(fileName ~ ".dll"); 385 } 386 }