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