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