Push x3
Browse files
+180 −6 src/Main.hx
| @@ -10,7 +10,8 @@ typedef Entry = { | ||
| 10 | 10 | repo: String, |
| 11 | 11 | slot: String, |
| 12 | 12 | deps: Array<String>, |
| 13 | - subslot: String | |
| 13 | + subslot: String, | |
| 14 | + use: String | |
| 14 | 15 | } |
| 15 | 16 | |
| 16 | 17 | typedef Status = { |
| @@ -18,6 +19,12 @@ typedef Status = { | ||
| 18 | 19 | old: Null<String> |
| 19 | 20 | } |
| 20 | 21 | |
| 22 | +typedef Candidate = { | |
| 23 | + ver: Version, | |
| 24 | + name: String, | |
| 25 | + tree: Tree | |
| 26 | +} | |
| 27 | + | |
| 21 | 28 | typedef Suggestion = { |
| 22 | 29 | label: String, |
| 23 | 30 | score: Int |
| @@ -32,6 +39,7 @@ class Main { | ||
| 32 | 39 | static var targetRoot = "/"; |
| 33 | 40 | static var showRepo = false; |
| 34 | 41 | static var update = false; |
| 42 | + static var deep = false; | |
| 35 | 43 | static var quiet = false; |
| 36 | 44 | static var syncFlag = false; |
| 37 | 45 | static var syncOnly: Null<String> = null; |
| @@ -261,15 +269,117 @@ class Main { | ||
| 261 | 269 | return false; |
| 262 | 270 | } |
| 263 | 271 | |
| 272 | + static function rejectedFor(a: Atom): Array<String> { | |
| 273 | + var out: Array<String> = []; | |
| 274 | + for (r in repos.list) { | |
| 275 | + for (v in r.tree.versions(a.cat, a.pkg)) { | |
| 276 | + if (!AtomParser.matches(a, v)) continue; | |
| 277 | + var path = r.tree.ebuildPath(a.cat, a.pkg, v); | |
| 278 | + var si = r.tree.slotInfo(path); | |
| 279 | + if (!AtomParser.slotMatches(a, si.slot, si.subslot)) continue; | |
| 280 | + | |
| 281 | + var licenses = LicenseCheck.unacceptedFor(a.cat, a.pkg, v, r.tree.variable(path, "LICENSE"), UseFlags.forPackage(a.cat, a.pkg, r.tree.iuse(path))); | |
| 282 | + var masked = Mask.isMasked(a.cat, a.pkg, v); | |
| 283 | + var missingKeyword = !Keywords.acceptsWith(r.tree.variable(path, "KEYWORDS"), Mask.extraKeywords(a.cat, a.pkg, v)); | |
| 284 | + if (licenses.length == 0 && !masked && !missingKeyword) continue; | |
| 285 | + | |
| 286 | + var parts: Array<String> = []; | |
| 287 | + if (licenses.length > 0) parts.push(licenses.join(" ") + " license(s)"); | |
| 288 | + if (masked) parts.push("package.mask"); | |
| 289 | + if (missingKeyword) parts.push("missing keyword"); | |
| 290 | + var reason = (licenses.length > 0 || masked) ? "masked by: " + parts.join(", ") : parts.join(", "); | |
| 291 | + out.push(a.cat + "/" + a.pkg + "-" + v.raw + "::" + r.name + " (" + reason + ")"); | |
| 292 | + } | |
| 293 | + } | |
| 294 | + return out; | |
| 295 | + } | |
| 296 | + | |
| 297 | + static function noMatchError(a: Atom, label: String): String { | |
| 298 | + var rejected = rejectedFor(a); | |
| 299 | + if (rejected.length == 0) return 'no ebuild matches "$label"'; | |
| 300 | + | |
| 301 | + var lines: Array<String> = [ | |
| 302 | + '!!! All ebuilds that could satisfy "' + label + '" have been masked.', | |
| 303 | + "!!! One of the following masked packages is required to complete your request:" | |
| 304 | + ]; | |
| 305 | + for (r in rejected) lines.push("- " + r); | |
| 306 | + lines.push(""); | |
| 307 | + lines.push("For more information, see the MASKED PACKAGES section in the emerge"); | |
| 308 | + lines.push("man page or refer to the Gentoo Handbook."); | |
| 309 | + return lines.join("\n"); | |
| 310 | + } | |
| 311 | + | |
| 312 | + static var seenLog: Array<String> = []; | |
| 313 | + static final MAX_ALTERNATIVES = 6; | |
| 314 | + | |
| 315 | + static function rollback(planMark: Int, blockMark: Int, chainMark: Int, seenMark: Int): Void { | |
| 316 | + while (plan.length > planMark) plan.pop(); | |
| 317 | + while (blockerList.length > blockMark) blockerList.pop(); | |
| 318 | + while (chain.length > chainMark) chain.pop(); | |
| 319 | + while (seenLog.length > seenMark) { | |
| 320 | + var k = seenLog.pop(); | |
| 321 | + if (k != null) seen.remove(k); | |
| 322 | + } | |
| 323 | + } | |
| 324 | + | |
| 325 | + static function candidatesFor(a: Atom): Array<Candidate> { | |
| 326 | + var out: Array<Candidate> = []; | |
| 327 | + var taken = new Map<String, Bool>(); | |
| 328 | + for (r in repos.list) { | |
| 329 | + for (v in r.tree.versions(a.cat, a.pkg)) { | |
| 330 | + if (taken.exists(v.raw)) continue; | |
| 331 | + if (!AtomParser.matches(a, v)) continue; | |
| 332 | + var si = r.tree.slotInfo(r.tree.ebuildPath(a.cat, a.pkg, v)); | |
| 333 | + if (!AtomParser.slotMatches(a, si.slot, si.subslot)) continue; | |
| 334 | + var exact = AtomParser.parse("=" + a.cat + "/" + a.pkg + "-" + v.raw); | |
| 335 | + if (exact == null) continue; | |
| 336 | + var pick = repos.best(exact); | |
| 337 | + if (pick == null || pick.ver.compare(v) != 0) continue; | |
| 338 | + taken.set(v.raw, true); | |
| 339 | + out.push({ver: v, name: r.name, tree: r.tree}); | |
| 340 | + } | |
| 341 | + } | |
| 342 | + out.sort(function(x, y) return y.ver.compare(x.ver)); | |
| 343 | + return out; | |
| 344 | + } | |
| 345 | + | |
| 264 | 346 | static function resolve(a: Atom, label: String, top: Bool) { |
| 265 | - if (installedSatisfies(a) && (update ? !upgradeAvailable(a) : !top)) { | |
| 347 | + var planMark = plan.length; | |
| 348 | + var blockMark = blockerList.length; | |
| 349 | + var chainMark = chain.length; | |
| 350 | + var seenMark = seenLog.length; | |
| 351 | + try { | |
| 352 | + resolveWith(a, label, top, null); | |
| 353 | + } catch (first: Dynamic) { | |
| 354 | + rollback(planMark, blockMark, chainMark, seenMark); | |
| 355 | + var primary = repos.best(a); | |
| 356 | + var tried = 0; | |
| 357 | + for (c in candidatesFor(a)) { | |
| 358 | + if (tried >= MAX_ALTERNATIVES) break; | |
| 359 | + if (primary != null && c.ver.compare(primary.ver) == 0 && c.name == primary.repo.name) continue; | |
| 360 | + tried++; | |
| 361 | + try { | |
| 362 | + resolveWith(a, label, top, c); | |
| 363 | + return; | |
| 364 | + } catch (ignored: Dynamic) { | |
| 365 | + rollback(planMark, blockMark, chainMark, seenMark); | |
| 366 | + } | |
| 367 | + } | |
| 368 | + throw first; | |
| 369 | + } | |
| 370 | + } | |
| 371 | + | |
| 372 | + static function resolveWith(a: Atom, label: String, top: Bool, forced: Null<Candidate>) { | |
| 373 | + if (installedSatisfies(a) && (top ? (update && !upgradeAvailable(a)) : !(update && deep && upgradeAvailable(a)))) { | |
| 266 | 374 | log("installed, skipping " + label); |
| 267 | 375 | return; |
| 268 | 376 | } |
| 269 | 377 | |
| 270 | 378 | spin(); |
| 271 | 379 | |
| 272 | - var hit = repos.best(a); | |
| 380 | + var hit: Dynamic = null; | |
| 381 | + if (forced != null) hit = {ver: forced.ver, repo: {name: forced.name, tree: forced.tree}}; | |
| 382 | + else hit = repos.best(a); | |
| 273 | 383 | var inst = vdb.installed(a.cat, a.pkg); |
| 274 | 384 | |
| 275 | 385 | var targetVer: Version = null; |
| @@ -312,7 +422,7 @@ class Main { | ||
| 312 | 422 | } |
| 313 | 423 | |
| 314 | 424 | if (targetVer == null) { |
| 315 | - if (hit == null) throw 'no ebuild matches "$label"'; | |
| 425 | + if (hit == null) throw noMatchError(a, label); | |
| 316 | 426 | targetVer = hit.ver; |
| 317 | 427 | targetRepo = hit.repo.name; |
| 318 | 428 | targetPath = hit.repo.tree.ebuildPath(a.cat, a.pkg, hit.ver); |
| @@ -324,6 +434,7 @@ class Main { | ||
| 324 | 434 | return; |
| 325 | 435 | } |
| 326 | 436 | seen.set(key, true); |
| 437 | + seenLog.push(key); | |
| 327 | 438 | |
| 328 | 439 | if (chain.length > 150) { |
| 329 | 440 | throw "dependency chain too deep: " + chain.slice(-10).join(" -> ") + " -> " + key; |
| @@ -341,6 +452,18 @@ class Main { | ||
| 341 | 452 | var tree = activeRepo != null ? activeRepo.tree : repos.list[0].tree; |
| 342 | 453 | var flags = UseFlags.forPackage(a.cat, a.pkg, tree.iuse(targetPath)); |
| 343 | 454 | |
| 455 | + var requiredSpec = tree.variable(targetPath, "REQUIRED_USE"); | |
| 456 | + var unmetUse = RequiredUse.unmet(requiredSpec, flags); | |
| 457 | + if (unmetUse.length > 0) { | |
| 458 | + var where = a.cat + "/" + a.pkg + "-" + targetVer.raw + "::" + targetRepo + " USE=\"" + useLine(tree.iuse(targetPath), flags) + "\""; | |
| 459 | + throw "The ebuild selected to satisfy \"" + label + "\" has unmet requirements.\n" | |
| 460 | + + " - " + where + "\n" | |
| 461 | + + " The following REQUIRED_USE flag constraints are unsatisfied:\n" | |
| 462 | + + unmetUse.map(function(u) return " " + u).join("\n") + "\n\n" | |
| 463 | + + " The above constraints are a subset of the following complete expression:\n" | |
| 464 | + + " " + RequiredUse.pretty(requiredSpec); | |
| 465 | + } | |
| 466 | + | |
| 344 | 467 | var runtime = Dependencies.eval(Dependencies.parse(tree.runtimeSpec(targetPath)), flags, chooser); |
| 345 | 468 | |
| 346 | 469 | var toResolve = runtime.copy(); |
| @@ -362,7 +485,7 @@ class Main { | ||
| 362 | 485 | |
| 363 | 486 | chain.pop(); |
| 364 | 487 | var si = slotOf(targetRepo, targetPath); |
| 365 | - plan.push({cat: a.cat, pkg: a.pkg, ver: targetVer, path: targetPath, repo: targetRepo, deps: depLabels, slot: si.slot, subslot: si.subslot}); | |
| 488 | + plan.push({cat: a.cat, pkg: a.pkg, ver: targetVer, path: targetPath, repo: targetRepo, deps: depLabels, slot: si.slot, subslot: si.subslot, use: useLine(tree.iuse(targetPath), flags)}); | |
| 366 | 489 | |
| 367 | 490 | var by = a.cat + "/" + a.pkg + "-" + targetVer.raw; |
| 368 | 491 | var blocked = Dependencies.blockers(Dependencies.parse(tree.runtimeSpec(targetPath)), flags) |
| @@ -479,6 +602,16 @@ class Main { | ||
| 479 | 602 | return testing; |
| 480 | 603 | } |
| 481 | 604 | |
| 605 | + static function useLine(iuse: Array<String>, flags: Map<String, Bool>): String { | |
| 606 | + var parts: Array<String> = []; | |
| 607 | + for (tok in iuse) { | |
| 608 | + var name = StringTools.startsWith(tok, "+") || StringTools.startsWith(tok, "-") ? tok.substr(1) : tok; | |
| 609 | + if (name == "") continue; | |
| 610 | + parts.push(flags.get(name) == true ? name : "-" + name); | |
| 611 | + } | |
| 612 | + return parts.join(" "); | |
| 613 | + } | |
| 614 | + | |
| 482 | 615 | static function showPlan(vdb: VDB, verbose: Bool) { |
| 483 | 616 | if (plan.length == 0) { |
| 484 | 617 | Sys.println(c("1;32", "Nothing to merge; all requested packages are installed.")); |
| @@ -511,6 +644,7 @@ class Main { | ||
| 511 | 644 | } |
| 512 | 645 | |
| 513 | 646 | if (verbose) { |
| 647 | + if (e.use != "") line += " " + c("0;33", "USE=\"" + e.use + "\""); | |
| 514 | 648 | var pkgSize = Std.int(getDownloadSize(e) / 1024); |
| 515 | 649 | if (pkgSize > 0) { |
| 516 | 650 | line += " " + formatNum(pkgSize) + " KiB"; |
| @@ -584,6 +718,42 @@ class Main { | ||
| 584 | 718 | return out; |
| 585 | 719 | } |
| 586 | 720 | |
| 721 | + static function worldLine(spec: String): Null<String> { | |
| 722 | + var a = AtomParser.parse(spec); | |
| 723 | + if (a == null) return null; | |
| 724 | + | |
| 725 | + var line = a.cat + "/" + a.pkg; | |
| 726 | + var slot = a.slot; | |
| 727 | + if (slot != null) { | |
| 728 | + var name = slot.split("/")[0]; | |
| 729 | + if (name != "" && name != "*" && name != "=") line += ":" + name; | |
| 730 | + } | |
| 731 | + return line; | |
| 732 | + } | |
| 733 | + | |
| 734 | + static function updateWorld(specs: Array<String>): Void { | |
| 735 | + var base = StringTools.endsWith(targetRoot, "/") ? targetRoot.substr(0, targetRoot.length - 1) : targetRoot; | |
| 736 | + var dir = base + "/var/lib/portage"; | |
| 737 | + var path = dir + "/world"; | |
| 738 | + | |
| 739 | + var lines: Array<String> = []; | |
| 740 | + if (FileSystem.exists(path)) { | |
| 741 | + for (raw in File.getContent(path).split("\n")) { | |
| 742 | + var t = StringTools.trim(raw); | |
| 743 | + if (t != "" && lines.indexOf(t) < 0) lines.push(t); | |
| 744 | + } | |
| 745 | + } | |
| 746 | + | |
| 747 | + for (spec in specs) { | |
| 748 | + var line = worldLine(spec); | |
| 749 | + if (line != null && lines.indexOf(line) < 0) lines.push(line); | |
| 750 | + } | |
| 751 | + | |
| 752 | + lines.sort(function(x, y) return x < y ? -1 : (x > y ? 1 : 0)); | |
| 753 | + if (!FileSystem.exists(dir)) FileSystem.createDirectory(dir); | |
| 754 | + File.saveContent(path, lines.join("\n") + "\n"); | |
| 755 | + } | |
| 756 | + | |
| 587 | 757 | static function readWorld(): Array<String> { |
| 588 | 758 | var base = StringTools.endsWith(targetRoot, "/") ? targetRoot.substr(0, targetRoot.length - 1) : targetRoot; |
| 589 | 759 | var path = base + "/var/lib/portage/world"; |
| @@ -719,6 +889,7 @@ class Main { | ||
| 719 | 889 | case "--ask" | "-a": ask = true; |
| 720 | 890 | case "--verbose" | "-v": verbose = true; |
| 721 | 891 | case "--update" | "-u": update = true; |
| 892 | + case "--deep" | "-D": deep = true; | |
| 722 | 893 | case "--quiet" | "-q": quiet = true; |
| 723 | 894 | case "--sync": |
| 724 | 895 | syncFlag = true; |
| @@ -756,6 +927,7 @@ class Main { | ||
| 756 | 927 | return; |
| 757 | 928 | } |
| 758 | 929 | |
| 930 | + var namedAtoms: Array<String> = [for (x in atoms) if (!StringTools.startsWith(x, "@")) x]; | |
| 759 | 931 | atoms = expandSets(atoms); |
| 760 | 932 | |
| 761 | 933 | if (syncFlag) { |
| @@ -813,7 +985,8 @@ class Main { | ||
| 813 | 985 | resolve(a, spec, true); |
| 814 | 986 | } catch (e: Dynamic) { |
| 815 | 987 | clearProgress(); |
| 816 | - Sys.println('error: $e'); | |
| 988 | + var message = Std.string(e); | |
| 989 | + Sys.println(StringTools.startsWith(message, "!!!") ? message : "error: " + message); | |
| 817 | 990 | Sys.exit(1); |
| 818 | 991 | } |
| 819 | 992 | } |
| @@ -849,5 +1022,6 @@ class Main { | ||
| 849 | 1022 | for (n in 0...total) { |
| 850 | 1023 | if (!mergeOne(plan[n], n + 1, total)) Sys.exit(1); |
| 851 | 1024 | } |
| 1025 | + updateWorld([for (x in namedAtoms) NAME_RE.match(x) ? resolveName(x) : x]); | |
| 852 | 1026 | } |
| 853 | 1027 | } |
| \ No newline at end of file |
+2 −0 src/import.hx
| @@ -2,8 +2,10 @@ import portage.Atom; | ||
| 2 | 2 | import portage.Atom.AtomParser; |
| 3 | 3 | import portage.Dependencies; |
| 4 | 4 | import portage.Keywords; |
| 5 | +import portage.LicenseCheck; | |
| 5 | 6 | import portage.Mask; |
| 6 | 7 | import portage.Repository; |
| 8 | +import portage.RequiredUse; | |
| 7 | 9 | import portage.Search; |
| 8 | 10 | import portage.Slots; |
| 9 | 11 | import portage.Sync; |
+264 −0 src/portage/LicenseCheck.hx new file mode 100644
| @@ -0,0 +1,264 @@ | ||
| 1 | +package portage; | |
| 2 | + | |
| 3 | +import sys.FileSystem; | |
| 4 | +import sys.io.File; | |
| 5 | + | |
| 6 | +typedef LicNode = { | |
| 7 | + kind: String, | |
| 8 | + name: String, | |
| 9 | + neg: Bool, | |
| 10 | + kids: Array<LicNode> | |
| 11 | +} | |
| 12 | + | |
| 13 | +typedef LicenseRule = { | |
| 14 | + atom: Atom, | |
| 15 | + tokens: Array<String> | |
| 16 | +} | |
| 17 | + | |
| 18 | +class LicenseCheck { | |
| 19 | + static var loaded = false; | |
| 20 | + static var enforce = false; | |
| 21 | + static var star = false; | |
| 22 | + static var explicit = new Map<String, Bool>(); | |
| 23 | + static var groups = new Map<String, Array<String>>(); | |
| 24 | + static var rules: Array<LicenseRule> = []; | |
| 25 | + | |
| 26 | + static function load(): Void { | |
| 27 | + if (loaded) return; | |
| 28 | + loaded = true; | |
| 29 | + | |
| 30 | + loadGroups(); | |
| 31 | + loadRules(); | |
| 32 | + | |
| 33 | + var groupCount = 0; | |
| 34 | + for (groupName in groups.keys()) groupCount++; | |
| 35 | + if (groupCount == 0) return; | |
| 36 | + | |
| 37 | + var value = Sys.getEnv("ACCEPT_LICENSE"); | |
| 38 | + if (value == null) value = fromFile("/etc/portage/make.conf"); | |
| 39 | + if (value == null) value = fromFile("/etc/make.conf"); | |
| 40 | + if (value == null) value = "@FREE"; | |
| 41 | + | |
| 42 | + enforce = true; | |
| 43 | + for (t in split(value)) apply(t); | |
| 44 | + } | |
| 45 | + | |
| 46 | + static function apply(t: String): Void { | |
| 47 | + if (t == "*") { | |
| 48 | + star = true; | |
| 49 | + } else if (t == "-*") { | |
| 50 | + star = false; | |
| 51 | + explicit = new Map<String, Bool>(); | |
| 52 | + } else if (StringTools.startsWith(t, "-@")) { | |
| 53 | + setGroup(t.substr(2), false); | |
| 54 | + } else if (StringTools.startsWith(t, "@")) { | |
| 55 | + setGroup(t.substr(1), true); | |
| 56 | + } else if (StringTools.startsWith(t, "-")) { | |
| 57 | + explicit.set(t.substr(1), false); | |
| 58 | + } else { | |
| 59 | + explicit.set(t, true); | |
| 60 | + } | |
| 61 | + } | |
| 62 | + | |
| 63 | + static function setGroup(name: String, value: Bool): Void { | |
| 64 | + for (license in expand(name, [])) explicit.set(license, value); | |
| 65 | + } | |
| 66 | + | |
| 67 | + static function expand(name: String, visited: Array<String>): Array<String> { | |
| 68 | + if (visited.indexOf(name) >= 0) return []; | |
| 69 | + var members = groups.get(name); | |
| 70 | + if (members == null) return []; | |
| 71 | + | |
| 72 | + var out: Array<String> = []; | |
| 73 | + for (m in members) { | |
| 74 | + if (StringTools.startsWith(m, "@")) { | |
| 75 | + for (license in expand(m.substr(1), visited.concat([name]))) out.push(license); | |
| 76 | + } else { | |
| 77 | + out.push(m); | |
| 78 | + } | |
| 79 | + } | |
| 80 | + return out; | |
| 81 | + } | |
| 82 | + | |
| 83 | + static function loadGroups(): Void { | |
| 84 | + for (path in ["/etc/portage/license_groups", "/usr/share/portage/config/license_groups", "/var/db/repos/gentoo/profiles/license_groups"]) { | |
| 85 | + if (!FileSystem.exists(path) || FileSystem.isDirectory(path)) continue; | |
| 86 | + for (raw in File.getContent(path).split("\n")) { | |
| 87 | + var line = StringTools.trim(raw); | |
| 88 | + if (line == "" || StringTools.startsWith(line, "#")) continue; | |
| 89 | + var words = split(line); | |
| 90 | + if (words.length < 2 || groups.exists(words[0])) continue; | |
| 91 | + groups.set(words[0], words.slice(1)); | |
| 92 | + } | |
| 93 | + } | |
| 94 | + } | |
| 95 | + | |
| 96 | + static function loadRules(): Void { | |
| 97 | + for (line in configLines("/etc/portage/package.license")) { | |
| 98 | + var words = split(line); | |
| 99 | + if (words.length < 2) continue; | |
| 100 | + var a = AtomParser.parse(words[0]); | |
| 101 | + if (a == null) continue; | |
| 102 | + rules.push({atom: a, tokens: words.slice(1)}); | |
| 103 | + } | |
| 104 | + } | |
| 105 | + | |
| 106 | + static function configLines(path: String): Array<String> { | |
| 107 | + var out: Array<String> = []; | |
| 108 | + if (!FileSystem.exists(path)) return out; | |
| 109 | + | |
| 110 | + var files: Array<String> = []; | |
| 111 | + if (FileSystem.isDirectory(path)) { | |
| 112 | + var names = FileSystem.readDirectory(path); | |
| 113 | + names.sort(function(x, y) return x < y ? -1 : (x > y ? 1 : 0)); | |
| 114 | + for (n in names) files.push(path + "/" + n); | |
| 115 | + } else { | |
| 116 | + files.push(path); | |
| 117 | + } | |
| 118 | + | |
| 119 | + for (f in files) { | |
| 120 | + if (FileSystem.isDirectory(f)) continue; | |
| 121 | + for (raw in File.getContent(f).split("\n")) { | |
| 122 | + var line = StringTools.trim(raw); | |
| 123 | + if (line == "" || StringTools.startsWith(line, "#")) continue; | |
| 124 | + out.push(line); | |
| 125 | + } | |
| 126 | + } | |
| 127 | + return out; | |
| 128 | + } | |
| 129 | + | |
| 130 | + static function fromFile(path: String): Null<String> { | |
| 131 | + if (!FileSystem.exists(path)) return null; | |
| 132 | + var text = "\n" + File.getContent(path); | |
| 133 | + var re = new EReg("\nACCEPT_LICENSE=[\"']?([^\"'\n]*)[\"']?", ""); | |
| 134 | + var found: Null<String> = null; | |
| 135 | + var rest = text; | |
| 136 | + while (re.match(rest)) { | |
| 137 | + found = re.matched(1); | |
| 138 | + rest = re.matchedRight(); | |
| 139 | + } | |
| 140 | + return found; | |
| 141 | + } | |
| 142 | + | |
| 143 | + static function split(s: String): Array<String> { | |
| 144 | + return [for (t in ~/[ \t\r\n]+/g.split(s)) if (t != "") t]; | |
| 145 | + } | |
| 146 | + | |
| 147 | + static function accepted(name: String): Bool { | |
| 148 | + load(); | |
| 149 | + if (!enforce) return true; | |
| 150 | + if (explicit.exists(name)) return explicit.get(name) == true; | |
| 151 | + return star; | |
| 152 | + } | |
| 153 | + | |
| 154 | + static function extraFor(cat: String, pkg: String, v: Version): Array<String> { | |
| 155 | + var out: Array<String> = []; | |
| 156 | + for (r in rules) { | |
| 157 | + if (r.atom.cat != cat || r.atom.pkg != pkg || !AtomParser.matches(r.atom, v)) continue; | |
| 158 | + for (t in r.tokens) { | |
| 159 | + if (t == "*") { | |
| 160 | + out.push("*"); | |
| 161 | + } else if (StringTools.startsWith(t, "@")) { | |
| 162 | + for (license in expand(t.substr(1), [])) out.push(license); | |
| 163 | + } else if (!StringTools.startsWith(t, "-")) { | |
| 164 | + out.push(t); | |
| 165 | + } | |
| 166 | + } | |
| 167 | + } | |
| 168 | + return out; | |
| 169 | + } | |
| 170 | + | |
| 171 | + public static function unacceptedFor(cat: String, pkg: String, v: Version, spec: String, use: Map<String, Bool>): Array<String> { | |
| 172 | + load(); | |
| 173 | + if (!enforce) return []; | |
| 174 | + var toks = split(spec); | |
| 175 | + if (toks.length == 0) return []; | |
| 176 | + | |
| 177 | + var parser = new LicParser(toks); | |
| 178 | + var top = parser.items(); | |
| 179 | + if (top == null || !parser.atEnd()) return []; | |
| 180 | + | |
| 181 | + var extra = extraFor(cat, pkg, v); | |
| 182 | + var out: Array<String> = []; | |
| 183 | + for (n in top) { | |
| 184 | + for (x in missing(n, use, extra)) if (out.indexOf(x) < 0) out.push(x); | |
| 185 | + } | |
| 186 | + return out; | |
| 187 | + } | |
| 188 | + | |
| 189 | + static function missing(n: LicNode, use: Map<String, Bool>, extra: Array<String>): Array<String> { | |
| 190 | + var out: Array<String> = []; | |
| 191 | + switch n.kind { | |
| 192 | + case "name": | |
| 193 | + if (!accepted(n.name) && extra.indexOf("*") < 0 && extra.indexOf(n.name) < 0) out.push(n.name); | |
| 194 | + case "all": | |
| 195 | + for (k in n.kids) for (x in missing(k, use, extra)) out.push(x); | |
| 196 | + case "cond": | |
| 197 | + var onFlag = use.get(n.name) == true; | |
| 198 | + if (onFlag != n.neg) for (k in n.kids) for (x in missing(k, use, extra)) out.push(x); | |
| 199 | + case "any": | |
| 200 | + var best: Null<Array<String>> = null; | |
| 201 | + for (k in n.kids) { | |
| 202 | + var m = missing(k, use, extra); | |
| 203 | + if (best == null || m.length < best.length) best = m; | |
| 204 | + } | |
| 205 | + if (best != null) for (x in best) out.push(x); | |
| 206 | + default: | |
| 207 | + } | |
| 208 | + return out; | |
| 209 | + } | |
| 210 | +} | |
| 211 | + | |
| 212 | +private class LicParser { | |
| 213 | + final toks: Array<String>; | |
| 214 | + var pos = 0; | |
| 215 | + | |
| 216 | + public function new(toks: Array<String>) { | |
| 217 | + this.toks = toks; | |
| 218 | + } | |
| 219 | + | |
| 220 | + public function atEnd(): Bool { | |
| 221 | + return pos >= toks.length; | |
| 222 | + } | |
| 223 | + | |
| 224 | + public function items(): Null<Array<LicNode>> { | |
| 225 | + var out: Array<LicNode> = []; | |
| 226 | + while (pos < toks.length && toks[pos] != ")") { | |
| 227 | + var n = item(); | |
| 228 | + if (n == null) return null; | |
| 229 | + out.push(n); | |
| 230 | + } | |
| 231 | + return out; | |
| 232 | + } | |
| 233 | + | |
| 234 | + function group(): Null<Array<LicNode>> { | |
| 235 | + if (pos >= toks.length || toks[pos] != "(") return null; | |
| 236 | + pos++; | |
| 237 | + var kids = items(); | |
| 238 | + if (kids == null || pos >= toks.length || toks[pos] != ")") return null; | |
| 239 | + pos++; | |
| 240 | + return kids; | |
| 241 | + } | |
| 242 | + | |
| 243 | + function item(): Null<LicNode> { | |
| 244 | + var t = toks[pos]; | |
| 245 | + if (t == "(") { | |
| 246 | + var kids = group(); | |
| 247 | + return kids == null ? null : {kind: "all", name: "", neg: false, kids: kids}; | |
| 248 | + } | |
| 249 | + if (t == "||") { | |
| 250 | + pos++; | |
| 251 | + var alts = group(); | |
| 252 | + return alts == null ? null : {kind: "any", name: "", neg: false, kids: alts}; | |
| 253 | + } | |
| 254 | + if (StringTools.endsWith(t, "?")) { | |
| 255 | + var neg = StringTools.startsWith(t, "!"); | |
| 256 | + var flag = t.substr(neg ? 1 : 0, t.length - (neg ? 1 : 0) - 1); | |
| 257 | + pos++; | |
| 258 | + var inner = group(); | |
| 259 | + return inner == null ? null : {kind: "cond", name: flag, neg: neg, kids: inner}; | |
| 260 | + } | |
| 261 | + pos++; | |
| 262 | + return {kind: "name", name: t, neg: false, kids: []}; | |
| 263 | + } | |
| 264 | +} |
+154 −0 src/portage/RequiredUse.hx new file mode 100644
| @@ -0,0 +1,154 @@ | ||
| 1 | +package portage; | |
| 2 | + | |
| 3 | +typedef ReqNode = { | |
| 4 | + kind: String, | |
| 5 | + flag: String, | |
| 6 | + neg: Bool, | |
| 7 | + kids: Array<ReqNode>, | |
| 8 | + text: String | |
| 9 | +} | |
| 10 | + | |
| 11 | +class RequiredUse { | |
| 12 | + public static function pretty(spec: String): String { | |
| 13 | + var toks = [for (t in ~/[ \t\r\n]+/g.split(spec)) if (t != "") t]; | |
| 14 | + return [for (t in toks) ReqParser.translate(t)].join(" "); | |
| 15 | + } | |
| 16 | + | |
| 17 | + public static function unmet(spec: String, use: Map<String, Bool>): Array<String> { | |
| 18 | + var toks = [for (t in ~/[ \t\r\n]+/g.split(spec)) if (t != "") t]; | |
| 19 | + if (toks.length == 0) return []; | |
| 20 | + | |
| 21 | + var parser = new ReqParser(toks); | |
| 22 | + var top = parser.items(); | |
| 23 | + if (top == null || !parser.atEnd()) { | |
| 24 | + Sys.println("warning: could not parse REQUIRED_USE: " + spec); | |
| 25 | + return []; | |
| 26 | + } | |
| 27 | + | |
| 28 | + var out: Array<String> = []; | |
| 29 | + for (n in top) holds(n, use, out); | |
| 30 | + return out; | |
| 31 | + } | |
| 32 | + | |
| 33 | + static function holds(n: ReqNode, use: Map<String, Bool>, unmet: Array<String>): Bool { | |
| 34 | + switch n.kind { | |
| 35 | + case "flag": | |
| 36 | + var on = use.get(n.flag) == true; | |
| 37 | + var okFlag = n.neg ? !on : on; | |
| 38 | + if (!okFlag) unmet.push(n.text); | |
| 39 | + return okFlag; | |
| 40 | + | |
| 41 | + case "all": | |
| 42 | + var okAll = true; | |
| 43 | + for (k in n.kids) if (!holds(k, use, unmet)) okAll = false; | |
| 44 | + return okAll; | |
| 45 | + | |
| 46 | + case "cond": | |
| 47 | + var onCond = use.get(n.flag) == true; | |
| 48 | + if (onCond == n.neg) return true; | |
| 49 | + var okCond = true; | |
| 50 | + var inside: Array<String> = []; | |
| 51 | + for (k in n.kids) if (!holds(k, use, inside)) okCond = false; | |
| 52 | + if (!okCond) unmet.push(n.text); | |
| 53 | + return okCond; | |
| 54 | + | |
| 55 | + case "any": | |
| 56 | + var okAny = false; | |
| 57 | + for (k in n.kids) if (holds(k, use, [])) okAny = true; | |
| 58 | + if (!okAny) unmet.push(n.text); | |
| 59 | + return okAny; | |
| 60 | + | |
| 61 | + case "one": | |
| 62 | + var countOne = 0; | |
| 63 | + for (k in n.kids) if (holds(k, use, [])) countOne++; | |
| 64 | + var okOne = countOne == 1; | |
| 65 | + if (!okOne) unmet.push(n.text); | |
| 66 | + return okOne; | |
| 67 | + | |
| 68 | + case "most": | |
| 69 | + var countMost = 0; | |
| 70 | + for (k in n.kids) if (holds(k, use, [])) countMost++; | |
| 71 | + var okMost = countMost <= 1; | |
| 72 | + if (!okMost) unmet.push(n.text); | |
| 73 | + return okMost; | |
| 74 | + | |
| 75 | + default: | |
| 76 | + return true; | |
| 77 | + } | |
| 78 | + } | |
| 79 | +} | |
| 80 | + | |
| 81 | +private class ReqParser { | |
| 82 | + final toks: Array<String>; | |
| 83 | + var pos = 0; | |
| 84 | + | |
| 85 | + public function new(toks: Array<String>) { | |
| 86 | + this.toks = toks; | |
| 87 | + } | |
| 88 | + | |
| 89 | + public static function translate(t: String): String { | |
| 90 | + return switch t { | |
| 91 | + case "||": "any-of"; | |
| 92 | + case "^^": "exactly-one-of"; | |
| 93 | + case "??": "at-most-one-of"; | |
| 94 | + default: t; | |
| 95 | + }; | |
| 96 | + } | |
| 97 | + | |
| 98 | + public function atEnd(): Bool { | |
| 99 | + return pos >= toks.length; | |
| 100 | + } | |
| 101 | + | |
| 102 | + public function items(): Null<Array<ReqNode>> { | |
| 103 | + var out: Array<ReqNode> = []; | |
| 104 | + while (pos < toks.length && toks[pos] != ")") { | |
| 105 | + var n = item(); | |
| 106 | + if (n == null) return null; | |
| 107 | + out.push(n); | |
| 108 | + } | |
| 109 | + return out; | |
| 110 | + } | |
| 111 | + | |
| 112 | + function group(): Null<Array<ReqNode>> { | |
| 113 | + if (pos >= toks.length || toks[pos] != "(") return null; | |
| 114 | + pos++; | |
| 115 | + var kids = items(); | |
| 116 | + if (kids == null || pos >= toks.length || toks[pos] != ")") return null; | |
| 117 | + pos++; | |
| 118 | + return kids; | |
| 119 | + } | |
| 120 | + | |
| 121 | + function item(): Null<ReqNode> { | |
| 122 | + var start = pos; | |
| 123 | + var t = toks[pos]; | |
| 124 | + var kind = "flag"; | |
| 125 | + var flag = ""; | |
| 126 | + var neg = false; | |
| 127 | + var kids: Null<Array<ReqNode>> = null; | |
| 128 | + | |
| 129 | + if (t == "(") { | |
| 130 | + kids = group(); | |
| 131 | + kind = "all"; | |
| 132 | + } else if (t == "||" || t == "^^" || t == "??") { | |
| 133 | + pos++; | |
| 134 | + kids = group(); | |
| 135 | + kind = t == "||" ? "any" : (t == "^^" ? "one" : "most"); | |
| 136 | + } else if (StringTools.endsWith(t, "?")) { | |
| 137 | + neg = StringTools.startsWith(t, "!"); | |
| 138 | + var begin = neg ? 1 : 0; | |
| 139 | + flag = t.substr(begin, t.length - begin - 1); | |
| 140 | + kind = "cond"; | |
| 141 | + pos++; | |
| 142 | + kids = group(); | |
| 143 | + } else { | |
| 144 | + neg = StringTools.startsWith(t, "!"); | |
| 145 | + flag = neg ? t.substr(1) : t; | |
| 146 | + kind = "flag"; | |
| 147 | + pos++; | |
| 148 | + } | |
| 149 | + | |
| 150 | + if (kind != "flag" && kids == null) return null; | |
| 151 | + var text = [for (t in toks.slice(start, pos)) ReqParser.translate(t)].join(" "); | |
| 152 | + return {kind: kind, flag: flag, neg: neg, kids: kids == null ? [] : kids, text: text}; | |
| 153 | + } | |
| 154 | +} |
+16 −10 src/portage/Tree.hx
| @@ -34,16 +34,31 @@ class Tree { | ||
| 34 | 34 | for (v in versions(a.cat, a.pkg)) { |
| 35 | 35 | if (StringTools.contains(v.raw, "9999")) continue; |
| 36 | 36 | if (!AtomParser.matches(a, v)) continue; |
| 37 | + | |
| 38 | + var path = ebuildPath(a.cat, a.pkg, v); | |
| 39 | + var slot = slotInfo(path); | |
| 40 | + if (!AtomParser.slotMatches(a, slot.slot, slot.subslot)) continue; | |
| 41 | + | |
| 37 | 42 | if (Mask.isMasked(a.cat, a.pkg, v)) continue; |
| 38 | 43 | |
| 39 | - var keywords = variable(ebuildPath(a.cat, a.pkg, v), "KEYWORDS"); | |
| 44 | + var keywords = variable(path, "KEYWORDS"); | |
| 40 | 45 | if (!Keywords.acceptsWith(keywords, Mask.extraKeywords(a.cat, a.pkg, v))) continue; |
| 41 | 46 | |
| 47 | + if (LicenseCheck.unacceptedFor(a.cat, a.pkg, v, variable(path, "LICENSE"), UseFlags.forPackage(a.cat, a.pkg, iuse(path))).length > 0) continue; | |
| 48 | + | |
| 42 | 49 | if (found == null || v.compare(found) > 0) found = v; |
| 43 | 50 | } |
| 44 | 51 | return found; |
| 45 | 52 | } |
| 46 | 53 | |
| 54 | + public function slotInfo(path: String): {slot: String, subslot: String} { | |
| 55 | + var raw = StringTools.trim(variable(path, "SLOT")); | |
| 56 | + var parts = raw.split("/"); | |
| 57 | + var slot = parts[0] == "" ? "0" : parts[0]; | |
| 58 | + var subslot = (parts.length > 1 && parts[1] != "") ? parts[1] : slot; | |
| 59 | + return {slot: slot, subslot: subslot}; | |
| 60 | + } | |
| 61 | + | |
| 47 | 62 | public function ebuildPath(cat: String, pkg: String, v: Version): String { |
| 48 | 63 | return root + "/" + cat + "/" + pkg + "/" + pkg + "-" + v.raw + ".ebuild"; |
| 49 | 64 | } |
| @@ -111,15 +126,6 @@ class Tree { | ||
| 111 | 126 | }); |
| 112 | 127 | } |
| 113 | 128 | |
| 114 | - public function slotInfo(path: String): {slot: String, subslot: String} { | |
| 115 | - var raw = variable(path, "SLOT"); | |
| 116 | - if (raw == "") raw = "0"; | |
| 117 | - var parts = raw.split("/"); | |
| 118 | - var slot = parts[0]; | |
| 119 | - var subslot = parts.length > 1 ? parts[1] : slot; | |
| 120 | - return {slot: slot, subslot: subslot}; | |
| 121 | - } | |
| 122 | - | |
| 123 | 129 | public function depSpec(path: String): String { |
| 124 | 130 | return variable(path, "DEPEND") + " " + variable(path, "RDEPEND") + " " + variable(path, "BDEPEND"); |
| 125 | 131 | } |