1 module dud.pkgdescription.platformselection; 2 3 import std.algorithm.iteration : each, map, filter; 4 import std.algorithm.searching : canFind, all, any; 5 import std.algorithm.sorting : sort; 6 import std.array : array, empty, front; 7 import std.exception : enforce; 8 import std.format; 9 import std.range : tee; 10 import std.stdio; 11 import std.traits : FieldNameTuple; 12 import std.typecons : Nullable, apply, nullable; 13 14 import dud.pkgdescription.duplicate : ddup = dup; 15 import dud.pkgdescription.exception; 16 import dud.pkgdescription.path; 17 import dud.pkgdescription; 18 import dud.semver.semver; 19 import dud.semver.versionrange; 20 21 PackageDescriptionNoPlatform select()(const(PackageDescription) pkg, 22 const(Platform[]) platform) 23 { 24 return selectImpl(pkg, platform); 25 } 26 27 struct PackageDescriptionNoPlatform { 28 @safe pure: 29 string name; 30 31 SemVer version_; 32 33 string description; 34 35 string homepage; 36 37 string[] authors; 38 39 string copyright; 40 41 string license; 42 43 string systemDependencies; 44 45 DependencyNoPlatform[string] dependencies; 46 47 TargetType targetType; 48 49 UnprocessedPath targetPath; 50 51 string targetName; 52 53 UnprocessedPath workingDirectory; 54 55 UnprocessedPath mainSourceFile; 56 57 string[] dflags; /// Flags passed to the D compiler 58 59 string[] lflags; /// Flags passed to the linker 60 61 string[] libs; /// Librariy names to link against (typically using "-l<name>") 62 63 UnprocessedPath[] copyFiles; /// Files to copy to the target directory 64 65 string[] versions; /// D version identifiers to set 66 67 string[] debugVersions; /// D debug version identifiers to set 68 69 UnprocessedPath[] importPaths; 70 71 UnprocessedPath[] sourcePaths; 72 73 UnprocessedPath[] sourceFiles; 74 75 UnprocessedPath[] excludedSourceFiles; 76 77 UnprocessedPath[] stringImportPaths; 78 79 string[] preGenerateCommands; /// commands executed before creating the description 80 81 string[] postGenerateCommands; /// commands executed after creating the description 82 83 string[] preBuildCommands; /// Commands to execute prior to every build 84 85 string[] postBuildCommands; /// Commands to execute after every build 86 87 string[] preRunCommands; /// Commands to execute prior to every run 88 89 string[] postRunCommands; /// Commands to execute after every run 90 91 string[] ddoxFilterArgs; 92 93 string[] debugVersionFilters; 94 95 string ddoxTool; 96 97 SubPackageNoPlatform[] subPackages; 98 99 BuildRequirement[] buildRequirements; 100 101 string[] versionFilters; 102 103 BuildOption[] buildOptions; 104 105 ToolchainRequirement[Toolchain] toolchainRequirements; 106 107 PackageDescriptionNoPlatform[] configurations; 108 109 string[string] subConfigurations; 110 111 bool opEquals(const(PackageDescriptionNoPlatform) other) const { 112 import dud.pkgdescription.compare : areEqual; 113 return areEqual(this, other); 114 } 115 } 116 117 struct SubPackageNoPlatform { 118 UnprocessedPath path; 119 Nullable!PackageDescriptionNoPlatform inlinePkg; 120 } 121 122 struct DependencyNoPlatform { 123 @safe pure: 124 string name; 125 Nullable!VersionRange version_; 126 UnprocessedPath path; 127 Nullable!bool optional; 128 Nullable!bool default_; 129 } 130 131 PackageDescriptionNoPlatform selectImpl()(const(PackageDescription) pkg, 132 const(Platform[]) platform) 133 { 134 import dud.pkgdescription.helper : isMem; 135 PackageDescriptionNoPlatform ret; 136 137 static foreach(mem; FieldNameTuple!PackageDescription) {{ 138 alias MemType = typeof(__traits(getMember, PackageDescription, mem)); 139 static if(canFind( 140 [ isMem!"name", /*isMem!"version_",*/ isMem!"description" 141 , isMem!"homepage", isMem!"authors", isMem!"copyright" 142 , isMem!"license", isMem!"systemDependencies", isMem!"targetType" 143 , isMem!"ddoxFilterArgs", isMem!"debugVersionFilters" 144 , isMem!"versionFilters", isMem!"toolchainRequirements" 145 , isMem!"workingDirectory", isMem!"mainSourceFile" 146 , isMem!"targetPath", isMem!"targetName" 147 ], mem)) 148 { 149 __traits(getMember, ret, mem) = ddup(__traits(getMember, pkg, mem)); 150 } else static if(canFind( 151 [ isMem!"ddoxTool" 152 , isMem!"preGenerateCommands" 153 , isMem!"postGenerateCommands", isMem!"preBuildCommands" 154 , isMem!"postBuildCommands", isMem!"preRunCommands" 155 , isMem!"postRunCommands", isMem!"dflags", isMem!"lflags" 156 , isMem!"libs", isMem!"versions" 157 , isMem!"sourcePaths", isMem!"importPaths" 158 , isMem!"copyFiles", isMem!"excludedSourceFiles" 159 , isMem!"stringImportPaths", isMem!"sourceFiles" 160 , isMem!"debugVersions", isMem!"subPackages" 161 , isMem!"dependencies", isMem!"buildRequirements" 162 , isMem!"subConfigurations", isMem!"buildOptions" 163 ], mem)) 164 { 165 __traits(getMember, ret, mem) = select( 166 __traits(getMember, pkg, mem), platform); 167 } else static if(canFind( 168 [ isMem!"configurations", isMem!"buildTypes"] 169 , mem)) 170 { 171 enforce(__traits(getMember, pkg, mem).empty, 172 () @trusted { 173 return format("%s %s", mem, __traits(getMember, pkg, mem)); 174 }()); 175 } else static if(canFind([ isMem!"platforms" ] , mem)) { 176 // platforms are ignored 177 } else { 178 assert(false, format("Unhandeld '%s'", mem)); 179 } 180 }} 181 182 return ret; 183 } 184 185 // 186 // BuildOptions 187 // 188 189 BuildOption[] select(const(BuildOptions) buildOptions, 190 const(Platform[]) platform) 191 { 192 auto keys = buildOptions.platforms.byKey() 193 .filter!(key => isSuperSet(key, platform)) 194 .map!(key => ddup(key)) 195 .array 196 .sort!((a, b) => a.length > b.length)(); 197 198 BuildOption[] ret = keys.empty 199 ? BuildOption[].init 200 : buildOptions.platforms[keys.front].ddup; 201 202 buildOptions.unspecifiedPlatform 203 .each!((op) { 204 if(!canFind(ret, op)) { 205 ret ~= op; 206 } 207 }); 208 209 return ret; 210 } 211 // 212 // SubConfigurations 213 // 214 215 string[string] select(const(SubConfigs) subConfs, 216 const(Platform[]) platform) 217 { 218 auto keys = subConfs.configs.byKey() 219 .filter!(key => isSuperSet(key, platform)) 220 .map!(key => ddup(key)) 221 .array 222 .sort!((a, b) => a.length > b.length)(); 223 224 string[string] ret = keys.empty 225 ? string[string].init 226 : subConfs.configs[keys.front].ddup; 227 228 subConfs.unspecifiedPlatform.byKey() 229 .each!((key) { 230 if(key !in ret) { 231 ret[key] = subConfs.unspecifiedPlatform[key].ddup; 232 } 233 }); 234 235 return ret; 236 } 237 238 // 239 // BuildRequirements 240 // 241 242 BuildRequirement[] select(const(BuildRequirements) brs, 243 const(Platform[]) platform) 244 { 245 BuildRequirements brsC = ddup(brs); 246 brsC.platforms.sort!((a, b) => a.platforms.length > b.platforms.length)(); 247 auto f = brsC.platforms.filter!(br => isSuperSet(br.platforms, platform)); 248 return f.empty ? BuildRequirement[].init : f.front.requirements; 249 } 250 251 // 252 // Dependencies 253 // 254 255 DependencyNoPlatform select()(const(Dependency) sp) { 256 DependencyNoPlatform ret; 257 ret.name = sp.name; 258 ret.path = ddup(sp.path); 259 if(!sp.version_.isNull()) { 260 ret.version_ = ddup(sp.version_.get()); 261 } 262 if(!sp.optional.isNull()) { 263 ret.optional = sp.optional.get(); 264 } 265 if(!sp.default_.isNull()) { 266 ret.default_ = sp.default_.get(); 267 } 268 return ret; 269 } 270 271 DependencyNoPlatform[string] select()(const(Dependency[]) deps, 272 const(Platform[]) platform) 273 { 274 Dependency[][string] sorted; 275 deps.filter!(dep => isSuperSet(dep.platforms, platform)) 276 .each!((dep) { 277 auto d = ddup(dep); 278 if(dep.name in sorted) { 279 sorted[dep.name] ~= d; 280 } else { 281 sorted[dep.name] = [d]; 282 } 283 }); 284 285 DependencyNoPlatform[string] ret; 286 foreach(key, ref values; sorted) { 287 values.sort!((a, b) => a.platforms.length > b.platforms.length)(); 288 enforce(!values.empty, "values was unexceptionally empty"); 289 ret[key] = select(values.front); 290 } 291 292 return ret; 293 } 294 295 // 296 // SubPackage(s) 297 // 298 299 SubPackageNoPlatform select()(const(SubPackage) sp, const(Platform[]) platform) { 300 SubPackageNoPlatform ret; 301 if(!sp.inlinePkg.isNull()) { 302 ret.inlinePkg.opAssign(select(sp.inlinePkg.get(), platform)); 303 } else { 304 ret.path = select(sp.path, platform); 305 } 306 return ret; 307 } 308 309 SubPackageNoPlatform[] select()(const(SubPackage[]) sps, const(Platform[]) platform) 310 { 311 return sps.map!(sp => select(sp, platform)).array; 312 313 } 314 315 // 316 // Path(s) 317 // 318 319 UnprocessedPath select(const(Path) path, const(Platform[]) platform) { 320 PathPlatform[] pth = path.platforms.map!(it => ddup(it)).array; 321 auto superSets = selectLargestSuperset(pth, platform); 322 return superSets.empty 323 ? UnprocessedPath.init 324 : superSets.front.path; 325 } 326 327 UnprocessedPath[] select(const(Paths) paths, const(Platform[]) platform) { 328 PathsPlatform[] pths = paths.platforms.map!(it => ddup(it)).array; 329 auto superSets = selectLargestSuperset(pths, platform); 330 return superSets.empty 331 ? [] 332 : superSets.front.paths; 333 } 334 335 // 336 // String(s) 337 // 338 339 string select(const(String) str, const(Platform[]) platform) { 340 StringPlatform[] strs = str.platforms.map!(it => ddup(it)).array; 341 auto superSets = selectLargestSuperset(strs, platform); 342 return superSets.empty 343 ? "" 344 : superSets.front.str; 345 } 346 347 string[] select(const(Strings) strs, const(Platform[]) platform) { 348 StringsPlatform[] strss = strs.platforms.map!(it => ddup(it)).array; 349 auto superSets = selectLargestSuperset(strss, platform); 350 return superSets.empty 351 ? [] 352 : superSets.front.strs; 353 } 354 355 // 356 // Helper 357 // 358 359 auto selectLargestSuperset(T)(ref T ts, 360 const(Platform[]) platform) 361 { 362 ts.sort!((a, b) => a.platforms.length > b.platforms.length)(); 363 auto superSets = ts.filter!(it => isSuperSet(it.platforms, platform)); 364 return superSets; 365 } 366 367 unittest { 368 struct Test { 369 int value; 370 Platform[] platforms; 371 } 372 373 { 374 Test[] tests = 375 [ Test(0, []), Test(1, [Platform.posix]), Test(2, [Platform.windows]) 376 , Test(3, [Platform.posix, Platform.x86_64, Platform.dmd]) 377 , Test(4, [Platform.posix, Platform.x86_64, Platform.gdc]) 378 ]; 379 380 auto a = selectLargestSuperset(tests, []); 381 assert(!a.empty); 382 assert(a.front.value == 0); 383 384 a = selectLargestSuperset(tests, [Platform.posix]); 385 assert(!a.empty); 386 assert(a.front.value == 1); 387 } 388 { 389 Test[] tests = 390 [ Test(1, [Platform.posix]), Test(2, [Platform.windows]) 391 , Test(3, [Platform.posix, Platform.x86_64, Platform.dmd]) 392 , Test(4, [Platform.posix, Platform.x86_64, Platform.gdc]) 393 ]; 394 395 auto a = selectLargestSuperset(tests, [Platform.osx, Platform.x86_64]); 396 assert(a.empty, format("%s", a.front.value)); 397 398 a = selectLargestSuperset(tests, 399 [Platform.posix, Platform.x86_64, Platform.dmd]); 400 assert(!a.empty); 401 assert(a.front.value == 3); 402 403 a = selectLargestSuperset(tests, 404 [Platform.posix, Platform.x86_64, Platform.gdc]); 405 assert(!a.empty); 406 assert(a.front.value == 4); 407 408 a = selectLargestSuperset(tests, 409 [Platform.posix, Platform.x86_64, Platform.gdc, Platform.d_avx2]); 410 assert(!a.empty); 411 assert(a.front.value == 4); 412 } 413 } 414 415 bool isSuperSet(const(Platform[]) a, const(Platform[]) b) { 416 return a.all!(p => canFind(b, p)); 417 } 418 419 unittest { 420 auto a = [ Platform.posix, Platform.x86_64 ]; 421 auto b = [ Platform.posix, Platform.x86_64, Platform.d_avx2 ]; 422 423 assert( isSuperSet(a, b)); 424 assert(!isSuperSet(b, a)); 425 } 426