1 module reggae.dub.info; 2 3 import reggae.build; 4 import reggae.rules; 5 import reggae.types; 6 import reggae.sorting; 7 8 public import std.typecons: Yes, No; 9 import std.typecons: Flag; 10 import std.algorithm: map, filter, find, splitter; 11 import std.array: array, join; 12 import std.path: buildPath; 13 import std.traits: isCallable; 14 import std.range: chain; 15 16 enum TargetType { 17 autodetect, 18 none, 19 executable, 20 library, 21 sourceLibrary, 22 dynamicLibrary, 23 staticLibrary, 24 object, 25 } 26 27 28 struct DubPackage { 29 string name; 30 string path; 31 string mainSourceFile; 32 string targetFileName; 33 string[] dflags; 34 string[] lflags; 35 string[] importPaths; 36 string[] stringImportPaths; 37 string[] files; 38 TargetType targetType; 39 string[] versions; 40 string[] dependencies; 41 string[] libs; 42 bool active; 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 return fileName.extension == ".a"; 82 } 83 84 bool isObjectFile(in string fileName) @safe pure nothrow { 85 import reggae.rules.common: objExt; 86 import std.path: extension; 87 return fileName.extension == objExt; 88 } 89 90 string inDubPackagePath(in string packagePath, in string filePath) @safe pure nothrow { 91 import std.path: buildPath; 92 import std.algorithm: startsWith; 93 return filePath.startsWith("$project") 94 ? filePath 95 : buildPath(packagePath, filePath); 96 } 97 98 struct DubObjsDir { 99 string globalDir; 100 string targetDir; 101 } 102 103 struct DubInfo { 104 105 DubPackage[] packages; 106 107 DubInfo dup() @safe pure nothrow const { 108 import std.algorithm: map; 109 import std.array: array; 110 return DubInfo(packages.map!(a => a.dup).array); 111 } 112 113 Target[] toTargets(in Flag!"main" includeMain = Yes.main, 114 in string compilerFlags = "", 115 in Flag!"allTogether" allTogether = No.allTogether, 116 in DubObjsDir dubObjsDir = DubObjsDir()) 117 @safe const 118 { 119 120 import reggae.config: options; 121 import reggae.build: targetObjsDir; 122 import std.functional: not; 123 import std.path: buildPath, absolutePath, baseName, dirName; 124 125 Target[] targets; 126 127 // -unittest should only apply to the main package 128 string deUnitTest(T)(in T index, in string flags) { 129 import std.string: replace; 130 return index == 0 131 ? flags 132 : flags.replace("-unittest", "").replace("-main", ""); 133 } 134 135 foreach(const i, const dubPackage; packages) { 136 const importPaths = allImportPaths(); 137 const stringImportPaths = dubPackage.allOf!(a => a.packagePaths(a.stringImportPaths))(packages); 138 const isMainPackage = i == 0; 139 //the path must be explicit for the other packages, implicit for the "main" 140 //package 141 const projDir = isMainPackage ? "" : dubPackage.path; 142 143 const sharedFlag = targetType == TargetType.dynamicLibrary ? ["-fPIC"] : []; 144 immutable flags = chain(dubPackage.dflags, 145 dubPackage.versions.map!(a => "-version=" ~ a).array, 146 [options.dflags], 147 sharedFlag, 148 [deUnitTest(i, compilerFlags)]) 149 .join(" "); 150 151 const files = dubPackage.files. 152 filter!(a => includeMain || a != dubPackage.mainSourceFile). 153 filter!(not!isStaticLibrary). 154 filter!(not!isObjectFile). 155 map!(a => buildPath(dubPackage.path, a)) 156 .array; 157 158 auto func = allTogether ? &dlangPackageObjectFilesTogether : &dlangPackageObjectFiles; 159 auto packageTargets = func(files, flags, importPaths, stringImportPaths, projDir); 160 161 // add any object files that are meant to be linked 162 packageTargets ~= dubPackage 163 .files 164 .filter!isObjectFile 165 .map!(a => Target(inDubPackagePath(dubPackage.path, a))) 166 .array; 167 168 169 // go through dub dependencies and optionally put the object files in dubObjsDir 170 if(!isMainPackage && dubObjsDir.globalDir != "") { 171 foreach(ref target; packageTargets) { 172 target.rawOutputs[0] = buildPath(dubObjsDir.globalDir, 173 target.rawOutputs[0].dirName, 174 dubObjsDir.targetDir, 175 target.rawOutputs[0].baseName); 176 } 177 } 178 179 targets ~= packageTargets; 180 } 181 182 return targets ~ allStaticLibrarySources; 183 } 184 185 TargetName targetName() @safe const pure nothrow { 186 const fileName = packages[0].targetFileName; 187 switch(targetType) with(TargetType) { 188 default: 189 return TargetName(fileName); 190 case library: 191 return TargetName("lib" ~ fileName ~ ".a"); 192 case dynamicLibrary: 193 return TargetName("lib" ~ fileName ~ ".so"); 194 } 195 } 196 197 TargetType targetType() @safe const pure nothrow { 198 return packages[0].targetType; 199 } 200 201 string[] mainLinkerFlags() @safe pure nothrow const { 202 import std.array: join; 203 204 const pack = packages[0]; 205 return (pack.targetType == TargetType.library || pack.targetType == TargetType.staticLibrary) 206 ? ["-shared"] 207 : []; 208 } 209 210 string[] linkerFlags() @safe const pure nothrow { 211 const allLibs = packages.map!(a => a.libs).join; 212 return 213 allLibs.map!(a => "-L-l" ~ a).array ~ 214 packages.map!(a => a.lflags.map!(b => "-L" ~ b)).join; 215 } 216 217 string[] allImportPaths() @safe nothrow const { 218 import reggae.config: options; 219 220 string[] paths; 221 auto rng = packages.map!(a => a.packagePaths(a.importPaths)); 222 foreach(p; rng) paths ~= p; 223 return paths ~ options.projectPath; 224 } 225 226 // must be at the very end 227 private Target[] allStaticLibrarySources() @trusted nothrow const pure { 228 import std.algorithm: filter, map; 229 import std.array: array, join; 230 return packages. 231 map!(a => cast(string[])a.files.filter!isStaticLibrary.array). 232 join. 233 map!(a => Target(a)). 234 array; 235 } 236 237 // all postBuildCommands in one shell command. Empty if there are none 238 string postBuildCommands() @safe pure nothrow const { 239 import std.string: join; 240 return packages[0].postBuildCommands.join(" && "); 241 } 242 } 243 244 245 private auto packagePaths(in DubPackage dubPackage, in string[] paths) @trusted nothrow { 246 return paths.map!(a => buildPath(dubPackage.path, a)).array; 247 } 248 249 //@trusted because of map.array 250 private string[] allOf(alias F)(in DubPackage pack, in DubPackage[] packages) @trusted nothrow { 251 252 import std.range: chain, only; 253 import std.array: array, front, empty; 254 255 string[] result; 256 257 foreach(dependency; chain(only(pack.name), pack.dependencies)) { 258 auto depPack = packages.find!(a => a.name == dependency); 259 if(!depPack.empty) { 260 result ~= F(depPack.front).array; 261 } 262 } 263 return result; 264 }