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