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 } 68 69 bool isStaticLibrary(in string fileName) @safe pure nothrow { 70 import std.path: extension; 71 return fileName.extension == ".a"; 72 } 73 74 bool isObjectFile(in string fileName) @safe pure nothrow { 75 import reggae.rules.common: objExt; 76 import std.path: extension; 77 return fileName.extension == objExt; 78 } 79 80 string inDubPackagePath(in string packagePath, in string filePath) @safe pure nothrow { 81 import std.path: buildPath; 82 import std.algorithm: startsWith; 83 return filePath.startsWith("$project") 84 ? filePath 85 : buildPath(packagePath, filePath); 86 } 87 88 struct DubInfo { 89 90 DubPackage[] packages; 91 92 Target[] toTargets(Flag!"main" includeMain = Yes.main, 93 in string compilerFlags = "", 94 Flag!"allTogether" allTogether = No.allTogether) @safe const { 95 96 import reggae.config: options; 97 import std.functional: not; 98 99 Target[] targets; 100 101 // -unittest should only apply to the main package 102 string deUnitTest(T)(in T index, in string flags) { 103 import std.string: replace; 104 return index == 0 105 ? flags 106 : flags.replace("-unittest", "").replace("-main", ""); 107 } 108 109 const(string)[] getVersions(T)(in T index) { 110 import std.algorithm: map; 111 import std.array: array; 112 113 const(string)[] ret = index == 0 114 ? packages[index].allOf!(a => a.versions)(packages) 115 : packages[0].versions ~ packages[index].versions; 116 117 return ret.map!(a => "-version=" ~ a).array; 118 } 119 120 foreach(const i, const dubPackage; packages) { 121 const importPaths = allImportPaths(); 122 const stringImportPaths = dubPackage.allOf!(a => a.packagePaths(a.stringImportPaths))(packages); 123 auto versions = getVersions(i); 124 125 //the path must be explicit for the other packages, implicit for the "main" 126 //package 127 const projDir = i == 0 ? "" : dubPackage.path; 128 129 const sharedFlag = targetType == TargetType.dynamicLibrary ? ["-fPIC"] : []; 130 immutable flags = chain(dubPackage.dflags, 131 versions, 132 [options.dflags], 133 sharedFlag, 134 [deUnitTest(i, compilerFlags)]) 135 .join(" "); 136 137 const files = dubPackage.files. 138 filter!(a => includeMain || a != dubPackage.mainSourceFile). 139 filter!(not!isStaticLibrary). 140 filter!(not!isObjectFile). 141 map!(a => buildPath(dubPackage.path, a)) 142 .array; 143 144 auto func = allTogether ? &dlangPackageObjectFilesTogether : &dlangPackageObjectFiles; 145 targets ~= func(files, flags, importPaths, stringImportPaths, projDir); 146 // add any object files that are meant to be linked 147 targets ~= dubPackage 148 .files 149 .filter!isObjectFile 150 .map!(a => Target(inDubPackagePath(dubPackage.path, a))) 151 .array; 152 } 153 154 return targets ~ allStaticLibrarySources; 155 } 156 157 TargetName targetName() @safe const pure nothrow { 158 const fileName = packages[0].targetFileName; 159 switch(targetType) with(TargetType) { 160 default: 161 return TargetName(fileName); 162 case library: 163 return TargetName("lib" ~ fileName ~ ".a"); 164 case dynamicLibrary: 165 return TargetName("lib" ~ fileName ~ ".so"); 166 } 167 } 168 169 TargetType targetType() @safe const pure nothrow { 170 return packages[0].targetType; 171 } 172 173 string[] mainLinkerFlags() @safe pure nothrow const { 174 import std.array: join; 175 176 const pack = packages[0]; 177 return (pack.targetType == TargetType.library || pack.targetType == TargetType.staticLibrary) 178 ? ["-shared"] 179 : []; 180 } 181 182 string[] linkerFlags() @safe const pure nothrow { 183 const allLibs = packages.map!(a => a.libs).join; 184 return 185 allLibs.map!(a => "-L-l" ~ a).array ~ 186 packages.map!(a => a.lflags.map!(b => "-L" ~ b)).join; 187 } 188 189 string[] allImportPaths() @safe nothrow const { 190 import reggae.config: options; 191 192 string[] paths; 193 auto rng = packages.map!(a => a.packagePaths(a.importPaths)); 194 foreach(p; rng) paths ~= p; 195 return paths ~ options.projectPath; 196 } 197 198 // must be at the very end 199 private Target[] allStaticLibrarySources() @trusted nothrow const pure { 200 import std.algorithm: filter, map; 201 import std.array: array, join; 202 return packages. 203 map!(a => cast(string[])a.files.filter!isStaticLibrary.array). 204 join. 205 map!(a => Target(a)). 206 array; 207 } 208 209 // all postBuildCommands in one shell command. Empty if there are none 210 string postBuildCommands() @safe pure nothrow const { 211 import std.string: join; 212 return packages[0].postBuildCommands.join(" && "); 213 } 214 } 215 216 217 private auto packagePaths(in DubPackage dubPackage, in string[] paths) @trusted nothrow { 218 return paths.map!(a => buildPath(dubPackage.path, a)).array; 219 } 220 221 //@trusted because of map.array 222 private string[] allOf(alias F)(in DubPackage pack, in DubPackage[] packages) @trusted nothrow { 223 224 import std.range: chain, only; 225 import std.array: array, front, empty; 226 227 string[] result; 228 229 foreach(dependency; chain(only(pack.name), pack.dependencies)) { 230 auto depPack = packages.find!(a => a.name == dependency); 231 if(!depPack.empty) { 232 result ~= F(depPack.front).array; 233 } 234 } 235 return result; 236 }