Push x4
Browse files
+166 −4 src/Main.hx
| @@ -40,6 +40,8 @@ class Main { | ||
| 40 | 40 | static var showRepo = false; |
| 41 | 41 | static var update = false; |
| 42 | 42 | static var deep = false; |
| 43 | + static var unmergeMode = false; | |
| 44 | + static var depclean = false; | |
| 43 | 45 | static var quiet = false; |
| 44 | 46 | static var syncFlag = false; |
| 45 | 47 | static var syncOnly: Null<String> = null; |
| @@ -206,9 +208,12 @@ class Main { | ||
| 206 | 208 | |
| 207 | 209 | static function installedSatisfies(a: Atom): Bool { |
| 208 | 210 | for (i in vdb.installed(a.cat, a.pkg)) { |
| 209 | - var info = Slots.installedSlot(vdb.root, a.cat, a.pkg + "-" + i.ver.raw); | |
| 210 | - if (AtomParser.matches(a, i.ver) && AtomParser.slotMatches(a, info.slot, info.subslot)) return true; | |
| 211 | - if (AtomParser.matches(a, i.ver)) return true; | |
| 211 | + var pkgVer = a.pkg + "-" + i.ver.raw; | |
| 212 | + var info = Slots.installedSlot(vdb.root, a.cat, pkgVer); | |
| 213 | + if (!AtomParser.matches(a, i.ver)) continue; | |
| 214 | + if (!AtomParser.slotMatches(a, info.slot, info.subslot)) continue; | |
| 215 | + if (!AtomParser.useMatches(a, Slots.installedUse(vdb.root, a.cat, pkgVer))) continue; | |
| 216 | + return true; | |
| 212 | 217 | } |
| 213 | 218 | return false; |
| 214 | 219 | } |
| @@ -264,7 +269,10 @@ class Main { | ||
| 264 | 269 | |
| 265 | 270 | static function hostSatisfies(a: Atom): Bool { |
| 266 | 271 | for (i in hostVdb.installed(a.cat, a.pkg)) { |
| 267 | - if (AtomParser.matches(a, i.ver)) return true; | |
| 272 | + var pkgVer = a.pkg + "-" + i.ver.raw; | |
| 273 | + if (!AtomParser.matches(a, i.ver)) continue; | |
| 274 | + if (!AtomParser.useMatches(a, Slots.installedUse(hostVdb.root, a.cat, pkgVer))) continue; | |
| 275 | + return true; | |
| 268 | 276 | } |
| 269 | 277 | return false; |
| 270 | 278 | } |
| @@ -331,6 +339,7 @@ class Main { | ||
| 331 | 339 | if (!AtomParser.matches(a, v)) continue; |
| 332 | 340 | var si = r.tree.slotInfo(r.tree.ebuildPath(a.cat, a.pkg, v)); |
| 333 | 341 | if (!AtomParser.slotMatches(a, si.slot, si.subslot)) continue; |
| 342 | + if (a.useDeps.length > 0 && !AtomParser.useMatches(a, UseFlags.forPackage(a.cat, a.pkg, r.tree.iuse(r.tree.ebuildPath(a.cat, a.pkg, v))))) continue; | |
| 334 | 343 | var exact = AtomParser.parse("=" + a.cat + "/" + a.pkg + "-" + v.raw); |
| 335 | 344 | if (exact == null) continue; |
| 336 | 345 | var pick = repos.best(exact); |
| @@ -775,6 +784,142 @@ class Main { | ||
| 775 | 784 | return out; |
| 776 | 785 | } |
| 777 | 786 | |
| 787 | + static function ebuildFile(cat: String, pkgVer: String): Null<String> { | |
| 788 | + var pn = Unmerge.pnFromDir(pkgVer); | |
| 789 | + for (r in repos.list) { | |
| 790 | + var path = r.tree.root + "/" + cat + "/" + pn + "/" + pkgVer + ".ebuild"; | |
| 791 | + if (FileSystem.exists(path)) return path; | |
| 792 | + } | |
| 793 | + return null; | |
| 794 | + } | |
| 795 | + | |
| 796 | + static function removeInstalled(cat: String, pkgVer: String): Void { | |
| 797 | + var ebuild = ebuildFile(cat, pkgVer); | |
| 798 | + Sys.putEnv("ROOT", targetRoot); | |
| 799 | + | |
| 800 | + if (ebuild == null) { | |
| 801 | + Sys.println(c("1;33", ">>> " + cat + "/" + pkgVer + ": no ebuild in any repo, pkg_prerm and pkg_postrm skipped")); | |
| 802 | + } else if (Sys.command("ebuild", [ebuild, "prerm"]) != 0) { | |
| 803 | + Sys.println(c("1;31", ">>> pkg_prerm failed for " + cat + "/" + pkgVer + "; not removed")); | |
| 804 | + Sys.exit(1); | |
| 805 | + } | |
| 806 | + | |
| 807 | + Unmerge.remove(targetRoot, cat, pkgVer); | |
| 808 | + | |
| 809 | + if (ebuild != null && Sys.command("ebuild", [ebuild, "postrm"]) != 0) { | |
| 810 | + Sys.println(c("1;33", ">>> pkg_postrm failed for " + cat + "/" + pkgVer)); | |
| 811 | + } | |
| 812 | + } | |
| 813 | + | |
| 814 | + static function runUnmerge(specs: Array<String>, pretend: Bool, ask: Bool): Void { | |
| 815 | + if (specs.length == 0) { | |
| 816 | + Sys.println("usage: emerge-hx -C [-p] [--root DIR] <atom> [atom...]"); | |
| 817 | + Sys.exit(1); | |
| 818 | + } | |
| 819 | + | |
| 820 | + var targets = []; | |
| 821 | + for (x in specs) { | |
| 822 | + if (StringTools.startsWith(x, "@")) { | |
| 823 | + Sys.println("sets can't be unmerged: " + x); | |
| 824 | + Sys.exit(1); | |
| 825 | + } | |
| 826 | + var found = Unmerge.find(targetRoot, x); | |
| 827 | + if (found.length == 0) { | |
| 828 | + Sys.println('no installed package matches "' + x + '"'); | |
| 829 | + Sys.exit(1); | |
| 830 | + } | |
| 831 | + for (f in found) targets.push(f); | |
| 832 | + } | |
| 833 | + | |
| 834 | + var blocked = false; | |
| 835 | + for (t in targets) { | |
| 836 | + for (dep in Unmerge.dependents(targetRoot, t.cat, t.pkg)) { | |
| 837 | + var removing = false; | |
| 838 | + for (other in targets) if (other.cat + "/" + other.pkg == dep) removing = true; | |
| 839 | + if (removing) continue; | |
| 840 | + Sys.println(c("1;31", "!!! " + dep + " depends on " + t.label + "; unmerge " + dep + " first")); | |
| 841 | + blocked = true; | |
| 842 | + } | |
| 843 | + } | |
| 844 | + if (blocked) Sys.exit(1); | |
| 845 | + | |
| 846 | + Sys.println(c("1;32", "These packages would be unmerged:")); | |
| 847 | + Sys.println(""); | |
| 848 | + for (t in targets) Sys.println(c("1;37", " " + t.label)); | |
| 849 | + Sys.println(""); | |
| 850 | + | |
| 851 | + if (pretend) return; | |
| 852 | + | |
| 853 | + if (!isRoot()) { | |
| 854 | + Sys.println("emerge-hx: superuser access is required to unmerge"); | |
| 855 | + Sys.exit(1); | |
| 856 | + } | |
| 857 | + | |
| 858 | + { | |
| 859 | + Sys.print("\nWould you like to unmerge these packages? [Yes/No] "); | |
| 860 | + var answer = ""; | |
| 861 | + try { | |
| 862 | + answer = Sys.stdin().readLine().toLowerCase(); | |
| 863 | + } catch (e: Dynamic) { | |
| 864 | + answer = ""; | |
| 865 | + } | |
| 866 | + if (answer != "y" && answer != "yes") { | |
| 867 | + Sys.println("Aborted."); | |
| 868 | + return; | |
| 869 | + } | |
| 870 | + } | |
| 871 | + | |
| 872 | + for (n in 0...targets.length) { | |
| 873 | + var t = targets[n]; | |
| 874 | + var pos = "(" + (n + 1) + " of " + targets.length + ") "; | |
| 875 | + Sys.println(c("1;32", ">>> Unmerging " + pos) + c("1;37", t.label)); | |
| 876 | + removeInstalled(t.cat, t.pkgVer); | |
| 877 | + Sys.println(c("1;32", ">>> Completed " + pos) + c("1;37", t.label)); | |
| 878 | + } | |
| 879 | + Sys.println(c("1;32", "Done.")); | |
| 880 | + } | |
| 881 | + | |
| 882 | + static function runDepclean(pretend: Bool): Void { | |
| 883 | + var orphans = Unmerge.depcleanCandidates(targetRoot); | |
| 884 | + if (orphans.length == 0) { | |
| 885 | + Sys.println(c("1;32", "Nothing to clean.")); | |
| 886 | + return; | |
| 887 | + } | |
| 888 | + | |
| 889 | + Sys.println(c("1;32", "These packages are not needed by the world file or the system profile:")); | |
| 890 | + Sys.println(""); | |
| 891 | + for (o in orphans) Sys.println(c("1;37", " " + o.label)); | |
| 892 | + Sys.println(""); | |
| 893 | + | |
| 894 | + if (pretend) return; | |
| 895 | + | |
| 896 | + if (!isRoot()) { | |
| 897 | + Sys.println("emerge-hx: superuser access is required to clean"); | |
| 898 | + Sys.exit(1); | |
| 899 | + } | |
| 900 | + | |
| 901 | + Sys.print("\nWould you like to unmerge these packages? [Yes/No] "); | |
| 902 | + var answer = ""; | |
| 903 | + try { | |
| 904 | + answer = Sys.stdin().readLine().toLowerCase(); | |
| 905 | + } catch (e: Dynamic) { | |
| 906 | + answer = ""; | |
| 907 | + } | |
| 908 | + if (answer != "y" && answer != "yes") { | |
| 909 | + Sys.println("Aborted."); | |
| 910 | + return; | |
| 911 | + } | |
| 912 | + | |
| 913 | + for (n in 0...orphans.length) { | |
| 914 | + var o = orphans[n]; | |
| 915 | + var pos = "(" + (n + 1) + " of " + orphans.length + ") "; | |
| 916 | + Sys.println(c("1;32", ">>> Unmerging " + pos) + c("1;37", o.label)); | |
| 917 | + removeInstalled(o.cat, o.pkgVer); | |
| 918 | + Sys.println(c("1;32", ">>> Completed " + pos) + c("1;37", o.label)); | |
| 919 | + } | |
| 920 | + Sys.println(c("1;32", "Done.")); | |
| 921 | + } | |
| 922 | + | |
| 778 | 923 | static function fullLabel(e: Entry): String { |
| 779 | 924 | var base = e.cat + "/" + e.pkg + "-" + e.ver.raw; |
| 780 | 925 | return showRepo ? base + "::" + e.repo : base; |
| @@ -890,6 +1035,8 @@ class Main { | ||
| 890 | 1035 | case "--verbose" | "-v": verbose = true; |
| 891 | 1036 | case "--update" | "-u": update = true; |
| 892 | 1037 | case "--deep" | "-D": deep = true; |
| 1038 | + case "--unmerge" | "-C": unmergeMode = true; | |
| 1039 | + case "--depclean" | "-d": depclean = true; | |
| 893 | 1040 | case "--quiet" | "-q": quiet = true; |
| 894 | 1041 | case "--sync": |
| 895 | 1042 | syncFlag = true; |
| @@ -927,6 +1074,21 @@ class Main { | ||
| 927 | 1074 | return; |
| 928 | 1075 | } |
| 929 | 1076 | |
| 1077 | + if (depclean && atoms.length > 0) { | |
| 1078 | + Sys.println("--depclean takes no package arguments; use -C to unmerge specific packages"); | |
| 1079 | + Sys.exit(1); | |
| 1080 | + } | |
| 1081 | + | |
| 1082 | + if (depclean) { | |
| 1083 | + runDepclean(pretend); | |
| 1084 | + return; | |
| 1085 | + } | |
| 1086 | + | |
| 1087 | + if (unmergeMode) { | |
| 1088 | + runUnmerge(atoms, pretend, ask); | |
| 1089 | + return; | |
| 1090 | + } | |
| 1091 | + | |
| 930 | 1092 | var namedAtoms: Array<String> = [for (x in atoms) if (!StringTools.startsWith(x, "@")) x]; |
| 931 | 1093 | atoms = expandSets(atoms); |
| 932 | 1094 |
+1 −0 src/import.hx
| @@ -10,6 +10,7 @@ import portage.Search; | ||
| 10 | 10 | import portage.Slots; |
| 11 | 11 | import portage.Sync; |
| 12 | 12 | import portage.Tree; |
| 13 | +import portage.Unmerge; | |
| 13 | 14 | import portage.UseFlags; |
| 14 | 15 | import portage.VDB; |
| 15 | 16 | import portage.Version; |
+91 −16 src/portage/Atom.hx
| @@ -6,7 +6,10 @@ typedef Atom = { | ||
| 6 | 6 | pkg: String, |
| 7 | 7 | ver: Null<Version>, |
| 8 | 8 | slot: Null<String>, |
| 9 | - glob: Bool | |
| 9 | + slotName: Null<String>, | |
| 10 | + subslot: Null<String>, | |
| 11 | + glob: Bool, | |
| 12 | + useDeps: Array<String> | |
| 10 | 13 | } |
| 11 | 14 | |
| 12 | 15 | class AtomParser { |
| @@ -15,6 +18,7 @@ class AtomParser { | ||
| 15 | 18 | static final OPS = ["", "=", ">=", "<=", ">", "<", "~"]; |
| 16 | 19 | |
| 17 | 20 | public static function parse(s: String): Null<Atom> { |
| 21 | + var useDeps = parseUseDeps(s); | |
| 18 | 22 | var stripped = USE_DEP.replace(s, ""); |
| 19 | 23 | if (!RE.match(stripped)) return null; |
| 20 | 24 | |
| @@ -36,31 +40,96 @@ class AtomParser { | ||
| 36 | 40 | } |
| 37 | 41 | if (op != "" && ver == null) return null; |
| 38 | 42 | |
| 43 | + var rawSlot: Null<String> = RE.matched(5); | |
| 44 | + var slotName: Null<String> = null; | |
| 45 | + var subslot: Null<String> = null; | |
| 46 | + if (rawSlot != null) { | |
| 47 | + var s = StringTools.endsWith(rawSlot, "=") ? rawSlot.substr(0, rawSlot.length - 1) : rawSlot; | |
| 48 | + if (s != "" && s != "*") { | |
| 49 | + var parts = s.split("/"); | |
| 50 | + slotName = parts[0]; | |
| 51 | + if (parts.length > 1 && parts[1] != "") subslot = parts[1]; | |
| 52 | + } | |
| 53 | + } | |
| 54 | + | |
| 39 | 55 | return { |
| 40 | 56 | op: op, |
| 41 | 57 | cat: RE.matched(2), |
| 42 | 58 | pkg: RE.matched(3), |
| 43 | 59 | ver: ver, |
| 44 | - slot: RE.matched(5), | |
| 45 | - glob: glob | |
| 60 | + slot: rawSlot, | |
| 61 | + slotName: slotName, | |
| 62 | + subslot: subslot, | |
| 63 | + glob: glob, | |
| 64 | + useDeps: useDeps | |
| 46 | 65 | }; |
| 47 | 66 | } |
| 48 | 67 | |
| 49 | - public static function slotMatches(a: Atom, slot: String, subslot: String): Bool { | |
| 50 | - if (a.slot == null || a.slot == "*") return true; | |
| 51 | - var req = a.slot; | |
| 52 | - if (StringTools.endsWith(req, "=")) { | |
| 53 | - req = req.substr(0, req.length - 1); | |
| 68 | + public static function resolveConditional(a: Atom, parent: Map<String, Bool>): Atom { | |
| 69 | + if (a.useDeps.length == 0) return a; | |
| 70 | + | |
| 71 | + var out: Array<String> = []; | |
| 72 | + for (t in a.useDeps) { | |
| 73 | + if (StringTools.startsWith(t, "-")) { | |
| 74 | + out.push(t); | |
| 75 | + continue; | |
| 76 | + } | |
| 77 | + | |
| 78 | + var name = t; | |
| 79 | + var inverse = StringTools.startsWith(name, "!"); | |
| 80 | + if (inverse) name = name.substr(1); | |
| 81 | + | |
| 82 | + var isCond = StringTools.endsWith(name, "?"); | |
| 83 | + var isEq = StringTools.endsWith(name, "="); | |
| 84 | + if (!isCond && !isEq) { | |
| 85 | + out.push(t); | |
| 86 | + continue; | |
| 87 | + } | |
| 88 | + name = name.substr(0, name.length - 1).split("(")[0]; | |
| 89 | + | |
| 90 | + var parentOn = parent.get(name) == true; | |
| 91 | + var required: Null<Bool> = null; | |
| 92 | + if (isCond) { | |
| 93 | + var holds = inverse ? !parentOn : parentOn; | |
| 94 | + if (holds) required = true; | |
| 95 | + } else { | |
| 96 | + required = inverse ? !parentOn : parentOn; | |
| 97 | + } | |
| 98 | + | |
| 99 | + if (required == null) continue; | |
| 100 | + out.push(required ? name : "-" + name); | |
| 54 | 101 | } |
| 55 | - var slash = req.indexOf("/"); | |
| 56 | - if (slash >= 0) { | |
| 57 | - var rSlot = req.substr(0, slash); | |
| 58 | - var rSub = req.substr(slash + 1); | |
| 59 | - if (rSlot != "" && rSlot != slot) return false; | |
| 60 | - if (rSub != "" && rSub != subslot) return false; | |
| 61 | - return true; | |
| 102 | + | |
| 103 | + return { | |
| 104 | + op: a.op, | |
| 105 | + cat: a.cat, | |
| 106 | + pkg: a.pkg, | |
| 107 | + ver: a.ver, | |
| 108 | + slot: a.slot, | |
| 109 | + slotName: a.slotName, | |
| 110 | + subslot: a.subslot, | |
| 111 | + glob: a.glob, | |
| 112 | + useDeps: out | |
| 113 | + }; | |
| 114 | + } | |
| 115 | + | |
| 116 | + static function parseUseDeps(s: String): Array<String> { | |
| 117 | + if (!USE_DEP.match(s)) return []; | |
| 118 | + var inner = USE_DEP.matched(0); | |
| 119 | + inner = inner.substr(1, inner.length - 2); | |
| 120 | + return [for (t in inner.split(",")) if (StringTools.trim(t) != "") StringTools.trim(t)]; | |
| 121 | + } | |
| 122 | + | |
| 123 | + public static function useMatches(a: Atom, flags: Map<String, Bool>): Bool { | |
| 124 | + for (t in a.useDeps) { | |
| 125 | + var neg = StringTools.startsWith(t, "-"); | |
| 126 | + var name = neg ? t.substr(1) : t; | |
| 127 | + if (name.indexOf("?") >= 0 || name.indexOf("=") >= 0 || StringTools.startsWith(name, "!")) continue; | |
| 128 | + | |
| 129 | + var flag = name.split("(")[0]; | |
| 130 | + var on = flags.get(flag) == true; | |
| 131 | + if (on == neg) return false; | |
| 62 | 132 | } |
| 63 | - if (req != "" && req != slot) return false; | |
| 64 | 133 | return true; |
| 65 | 134 | } |
| 66 | 135 | |
| @@ -88,4 +157,10 @@ class AtomParser { | ||
| 88 | 157 | default: false; |
| 89 | 158 | }; |
| 90 | 159 | } |
| 160 | + | |
| 161 | + public static function slotMatches(a: Atom, slot: String, subslot: String): Bool { | |
| 162 | + if (a.slotName != null && a.slotName != slot) return false; | |
| 163 | + if (a.subslot != null && a.subslot != subslot) return false; | |
| 164 | + return true; | |
| 165 | + } | |
| 91 | 166 | } |
+4 −6 src/portage/Dependencies.hx
| @@ -3,9 +3,9 @@ package portage; | ||
| 3 | 3 | enum Dep { |
| 4 | 4 | Leaf(a: Atom); |
| 5 | 5 | AllOf(kids: Array<Dep>); |
| 6 | - AnyOf(alts: Array<Dep>); | |
| 7 | - Cond(flag: String, negate: Bool, kids: Array<Dep>); | |
| 8 | - Block(a: Atom, strong: Bool); | |
| 6 | + AnyOf(alts: Array<Dep>); // || ( ... ): pick one alternative | |
| 7 | + Cond(flag: String, negate: Bool, kids: Array<Dep>); // flag? ( ... ) / !flag? ( ... ) | |
| 8 | + Block(a: Atom, strong: Bool); // !atom (weak) / !!atom (strong): must not be installed | |
| 9 | 9 | } |
| 10 | 10 | |
| 11 | 11 | typedef Chooser = Array<Array<Atom>> -> Int; |
| @@ -82,7 +82,6 @@ class Dependencies { | ||
| 82 | 82 | static function collectBlockers(d: Dep, use: Map<String, Bool>, out: Array<{atom: Atom, strong: Bool}>): Void { |
| 83 | 83 | switch d { |
| 84 | 84 | case Leaf(_): |
| 85 | - | |
| 86 | 85 | case Block(a, strong): |
| 87 | 86 | out.push({atom: a, strong: strong}); |
| 88 | 87 | case AllOf(kids): |
| @@ -104,11 +103,10 @@ class Dependencies { | ||
| 104 | 103 | static function evalOne(d: Dep, use: Map<String, Bool>, pick: Chooser, out: Array<Atom>): Void { |
| 105 | 104 | switch d { |
| 106 | 105 | case Leaf(a): |
| 107 | - out.push(a); | |
| 106 | + out.push(AtomParser.resolveConditional(a, use)); | |
| 108 | 107 | case AllOf(kids): |
| 109 | 108 | for (k in kids) evalOne(k, use, pick, out); |
| 110 | 109 | case Block(_, _): |
| 111 | - | |
| 112 | 110 | case Cond(flag, negate, kids): |
| 113 | 111 | var on = use.get(flag) == true; |
| 114 | 112 | if (on != negate) for (k in kids) evalOne(k, use, pick, out); |
+9 −1 src/portage/Slots.hx
| @@ -28,7 +28,7 @@ class Slots { | ||
| 28 | 28 | var dir = catDir + "/" + pkgVer; |
| 29 | 29 | if (!FileSystem.isDirectory(dir)) continue; |
| 30 | 30 | |
| 31 | - var name = read(dir + "/PN"); | |
| 31 | + var name = Unmerge.pnFromDir(pkgVer); | |
| 32 | 32 | if (name == "") continue; |
| 33 | 33 | var depLabel = catName + "/" + name; |
| 34 | 34 | if (depLabel == label) continue; |
| @@ -49,6 +49,14 @@ class Slots { | ||
| 49 | 49 | return false; |
| 50 | 50 | } |
| 51 | 51 | |
| 52 | + public static function installedUse(root: String, cat: String, pkgVer: String): Map<String, Bool> { | |
| 53 | + var m = new Map<String, Bool>(); | |
| 54 | + for (t in ~/[ \t\r\n]+/g.split(read(root + "/var/db/pkg/" + cat + "/" + pkgVer + "/USE"))) { | |
| 55 | + if (t != "") m.set(t, true); | |
| 56 | + } | |
| 57 | + return m; | |
| 58 | + } | |
| 59 | + | |
| 52 | 60 | static function read(file: String): String { |
| 53 | 61 | if (!FileSystem.exists(file)) return ""; |
| 54 | 62 | return StringTools.trim(File.getContent(file)); |
+1 −0 src/portage/Tree.hx
| @@ -38,6 +38,7 @@ class Tree { | ||
| 38 | 38 | var path = ebuildPath(a.cat, a.pkg, v); |
| 39 | 39 | var slot = slotInfo(path); |
| 40 | 40 | if (!AtomParser.slotMatches(a, slot.slot, slot.subslot)) continue; |
| 41 | + if (a.useDeps.length > 0 && !AtomParser.useMatches(a, UseFlags.forPackage(a.cat, a.pkg, iuse(path)))) continue; | |
| 41 | 42 | |
| 42 | 43 | if (Mask.isMasked(a.cat, a.pkg, v)) continue; |
| 43 | 44 |
+328 −0 src/portage/Unmerge.hx new file mode 100644
| @@ -0,0 +1,328 @@ | ||
| 1 | +package portage; | |
| 2 | + | |
| 3 | +import sys.FileSystem; | |
| 4 | +import sys.io.File; | |
| 5 | +import sys.io.Process; | |
| 6 | + | |
| 7 | +typedef InstalledPkg = { | |
| 8 | + cat: String, | |
| 9 | + pkg: String, | |
| 10 | + pkgVer: String, | |
| 11 | + label: String | |
| 12 | +} | |
| 13 | + | |
| 14 | +class Unmerge { | |
| 15 | + static final DEP_FILES = ["DEPEND", "RDEPEND", "PDEPEND", "BDEPEND"]; | |
| 16 | + | |
| 17 | + public static function find(root: String, spec: String): Array<InstalledPkg> { | |
| 18 | + var out: Array<InstalledPkg> = []; | |
| 19 | + var base = root + "/var/db/pkg"; | |
| 20 | + if (!FileSystem.exists(base)) return out; | |
| 21 | + | |
| 22 | + var atom = spec.indexOf("/") >= 0 ? AtomParser.parse(spec) : null; | |
| 23 | + if (spec.indexOf("/") >= 0 && atom == null) return out; | |
| 24 | + | |
| 25 | + for (cat in FileSystem.readDirectory(base)) { | |
| 26 | + var catDir = base + "/" + cat; | |
| 27 | + if (!FileSystem.isDirectory(catDir)) continue; | |
| 28 | + | |
| 29 | + for (pv in FileSystem.readDirectory(catDir)) { | |
| 30 | + var dir = catDir + "/" + pv; | |
| 31 | + if (!FileSystem.isDirectory(dir)) continue; | |
| 32 | + | |
| 33 | + var pn = pnFromDir(pv); | |
| 34 | + if (pn == "" || !StringTools.startsWith(pv, pn + "-")) continue; | |
| 35 | + var ver = Version.parse(pv.substr(pn.length + 1)); | |
| 36 | + if (ver == null) continue; | |
| 37 | + | |
| 38 | + if (atom != null) { | |
| 39 | + if (atom.cat != cat || atom.pkg != pn) continue; | |
| 40 | + var info = Slots.installedSlot(root, cat, pv); | |
| 41 | + if (!AtomParser.matches(atom, ver)) continue; | |
| 42 | + if (!AtomParser.slotMatches(atom, info.slot, info.subslot)) continue; | |
| 43 | + if (!AtomParser.useMatches(atom, Slots.installedUse(root, cat, pv))) continue; | |
| 44 | + } else if (pn != spec) { | |
| 45 | + continue; | |
| 46 | + } | |
| 47 | + | |
| 48 | + out.push({cat: cat, pkg: pn, pkgVer: pv, label: cat + "/" + pv}); | |
| 49 | + } | |
| 50 | + } | |
| 51 | + return out; | |
| 52 | + } | |
| 53 | + | |
| 54 | + public static function dependents(root: String, cat: String, pkg: String): Array<String> { | |
| 55 | + var label = cat + "/" + pkg; | |
| 56 | + var out: Array<String> = []; | |
| 57 | + var base = root + "/var/db/pkg"; | |
| 58 | + if (!FileSystem.exists(base)) return out; | |
| 59 | + | |
| 60 | + for (catName in FileSystem.readDirectory(base)) { | |
| 61 | + var catDir = base + "/" + catName; | |
| 62 | + if (!FileSystem.isDirectory(catDir)) continue; | |
| 63 | + | |
| 64 | + for (pv in FileSystem.readDirectory(catDir)) { | |
| 65 | + var dir = catDir + "/" + pv; | |
| 66 | + if (!FileSystem.isDirectory(dir)) continue; | |
| 67 | + | |
| 68 | + var name = pnFromDir(pv); | |
| 69 | + if (name == "" || catName + "/" + name == label) continue; | |
| 70 | + | |
| 71 | + for (file in DEP_FILES) { | |
| 72 | + if (mentions(read(dir + "/" + file), label)) { | |
| 73 | + var depLabel = catName + "/" + name; | |
| 74 | + if (out.indexOf(depLabel) < 0) out.push(depLabel); | |
| 75 | + break; | |
| 76 | + } | |
| 77 | + } | |
| 78 | + } | |
| 79 | + } | |
| 80 | + return out; | |
| 81 | + } | |
| 82 | + | |
| 83 | + static function mentions(deps: String, label: String): Bool { | |
| 84 | + for (t in ~/[ \t\r\n]+/g.split(deps)) { | |
| 85 | + var s = t; | |
| 86 | + while (s != "" && "<>=~!".indexOf(s.charAt(0)) >= 0) s = s.substr(1); | |
| 87 | + if (s == label || StringTools.startsWith(s, label + ":") || StringTools.startsWith(s, label + "[")) return true; | |
| 88 | + if (StringTools.startsWith(s, label + "-")) { | |
| 89 | + var next = s.charAt(label.length + 1); | |
| 90 | + if (next != "" && next >= "0" && next <= "9") return true; | |
| 91 | + } | |
| 92 | + } | |
| 93 | + return false; | |
| 94 | + } | |
| 95 | + | |
| 96 | + public static function remove(root: String, cat: String, pkgVer: String): Void { | |
| 97 | + var base = StringTools.endsWith(root, "/") ? root.substr(0, root.length - 1) : root; | |
| 98 | + var dir = base + "/var/db/pkg/" + cat + "/" + pkgVer; | |
| 99 | + | |
| 100 | + var dirs: Array<String> = []; | |
| 101 | + for (line in lines(dir + "/CONTENTS")) { | |
| 102 | + var words = line.split(" "); | |
| 103 | + if (words.length < 2) continue; | |
| 104 | + var path = base + words[1]; | |
| 105 | + | |
| 106 | + if (words[0] == "dir") { | |
| 107 | + dirs.push(path); | |
| 108 | + } else if (isModifiedConfig(base, words)) { | |
| 109 | + Sys.println(" keeping modified config file " + words[1]); | |
| 110 | + } else { | |
| 111 | + try { | |
| 112 | + FileSystem.deleteFile(path); | |
| 113 | + } catch (e: Dynamic) {} | |
| 114 | + } | |
| 115 | + } | |
| 116 | + | |
| 117 | + dirs.sort(function(x, y) return y.length - x.length); | |
| 118 | + for (d in dirs) { | |
| 119 | + try { | |
| 120 | + if (FileSystem.exists(d) && FileSystem.isDirectory(d) && FileSystem.readDirectory(d).length == 0) { | |
| 121 | + FileSystem.deleteDirectory(d); | |
| 122 | + } | |
| 123 | + } catch (e: Dynamic) {} | |
| 124 | + } | |
| 125 | + | |
| 126 | + if (FileSystem.exists(dir) && FileSystem.isDirectory(dir)) { | |
| 127 | + for (f in FileSystem.readDirectory(dir)) { | |
| 128 | + try { | |
| 129 | + FileSystem.deleteFile(dir + "/" + f); | |
| 130 | + } catch (e: Dynamic) {} | |
| 131 | + } | |
| 132 | + FileSystem.deleteDirectory(dir); | |
| 133 | + } | |
| 134 | + } | |
| 135 | + | |
| 136 | + public static function allInstalled(root: String): Array<InstalledPkg> { | |
| 137 | + var out: Array<InstalledPkg> = []; | |
| 138 | + var base = root + "/var/db/pkg"; | |
| 139 | + if (!FileSystem.exists(base)) return out; | |
| 140 | + | |
| 141 | + for (cat in FileSystem.readDirectory(base)) { | |
| 142 | + var catDir = base + "/" + cat; | |
| 143 | + if (!FileSystem.isDirectory(catDir)) continue; | |
| 144 | + for (pv in FileSystem.readDirectory(catDir)) { | |
| 145 | + var dir = catDir + "/" + pv; | |
| 146 | + if (!FileSystem.isDirectory(dir)) continue; | |
| 147 | + var pn = pnFromDir(pv); | |
| 148 | + if (pn == "" || !StringTools.startsWith(pv, pn + "-")) continue; | |
| 149 | + out.push({cat: cat, pkg: pn, pkgVer: pv, label: cat + "/" + pv}); | |
| 150 | + } | |
| 151 | + } | |
| 152 | + return out; | |
| 153 | + } | |
| 154 | + | |
| 155 | + public static function depcleanCandidates(root: String): Array<InstalledPkg> { | |
| 156 | + var installed = allInstalled(root); | |
| 157 | + var byName = new Map<String, Array<InstalledPkg>>(); | |
| 158 | + for (p in installed) { | |
| 159 | + var key = p.cat + "/" + p.pkg; | |
| 160 | + if (!byName.exists(key)) byName.set(key, []); | |
| 161 | + byName.get(key).push(p); | |
| 162 | + } | |
| 163 | + | |
| 164 | + var queue: Array<String> = systemNames().concat(worldNames(root)); | |
| 165 | + var keep = new Map<String, Bool>(); | |
| 166 | + while (queue.length > 0) { | |
| 167 | + var name: String = queue.shift(); | |
| 168 | + if (keep.exists(name)) continue; | |
| 169 | + keep.set(name, true); | |
| 170 | + if (!byName.exists(name)) continue; | |
| 171 | + | |
| 172 | + for (p in byName.get(name)) { | |
| 173 | + var dir = root + "/var/db/pkg/" + p.cat + "/" + p.pkgVer; | |
| 174 | + for (file in DEP_FILES) { | |
| 175 | + for (t in ~/[ \t\r\n]+/g.split(read(dir + "/" + file))) { | |
| 176 | + var n = nameOf(t); | |
| 177 | + if (n != "") queue.push(n); | |
| 178 | + } | |
| 179 | + } | |
| 180 | + } | |
| 181 | + } | |
| 182 | + | |
| 183 | + var out: Array<InstalledPkg> = []; | |
| 184 | + for (p in installed) { | |
| 185 | + if (!keep.exists(p.cat + "/" + p.pkg)) out.push(p); | |
| 186 | + } | |
| 187 | + return out; | |
| 188 | + } | |
| 189 | + | |
| 190 | + static function nameOf(token: String): String { | |
| 191 | + var s = token; | |
| 192 | + while (s != "" && "<>=~!".indexOf(s.charAt(0)) >= 0) s = s.substr(1); | |
| 193 | + | |
| 194 | + var cut = s.length; | |
| 195 | + var colon = s.indexOf(":"); | |
| 196 | + if (colon >= 0 && colon < cut) cut = colon; | |
| 197 | + var bracket = s.indexOf("["); | |
| 198 | + if (bracket >= 0 && bracket < cut) cut = bracket; | |
| 199 | + s = s.substr(0, cut); | |
| 200 | + | |
| 201 | + var slash = s.indexOf("/"); | |
| 202 | + if (slash <= 0 || slash == s.length - 1) return ""; | |
| 203 | + var cat = s.substr(0, slash); | |
| 204 | + var rest = s.substr(slash + 1); | |
| 205 | + | |
| 206 | + for (i in 0...rest.length) { | |
| 207 | + if (rest.charAt(i) != "-" || i + 1 >= rest.length) continue; | |
| 208 | + var next = rest.charAt(i + 1); | |
| 209 | + if (next >= "0" && next <= "9" && Version.parse(rest.substr(i + 1)) != null) { | |
| 210 | + return cat + "/" + rest.substr(0, i); | |
| 211 | + } | |
| 212 | + } | |
| 213 | + return cat + "/" + rest; | |
| 214 | + } | |
| 215 | + | |
| 216 | + static function worldNames(root: String): Array<String> { | |
| 217 | + var base = StringTools.endsWith(root, "/") ? root.substr(0, root.length - 1) : root; | |
| 218 | + var out: Array<String> = []; | |
| 219 | + for (line in lines(base + "/var/lib/portage/world")) { | |
| 220 | + var n = nameOf(line); | |
| 221 | + if (n != "" && out.indexOf(n) < 0) out.push(n); | |
| 222 | + } | |
| 223 | + return out; | |
| 224 | + } | |
| 225 | + | |
| 226 | + static function systemNames(): Array<String> { | |
| 227 | + var out: Array<String> = []; | |
| 228 | + for (dir in profileChain()) { | |
| 229 | + for (line in lines(dir + "/packages")) { | |
| 230 | + if (!StringTools.startsWith(line, "*")) continue; | |
| 231 | + var n = nameOf(line.substr(1)); | |
| 232 | + if (n != "" && out.indexOf(n) < 0) out.push(n); | |
| 233 | + } | |
| 234 | + } | |
| 235 | + return out; | |
| 236 | + } | |
| 237 | + | |
| 238 | + static function profileChain(): Array<String> { | |
| 239 | + var out: Array<String> = []; | |
| 240 | + var link = realpath("/etc/portage/make.profile"); | |
| 241 | + if (link != null) collectProfile(link, out, 0); | |
| 242 | + return out; | |
| 243 | + } | |
| 244 | + | |
| 245 | + static function collectProfile(dir: String, out: Array<String>, depth: Int): Void { | |
| 246 | + if (depth > 20) return; | |
| 247 | + for (line in lines(dir + "/parent")) { | |
| 248 | + var p = dir + "/" + line; | |
| 249 | + if (FileSystem.exists(p) && FileSystem.isDirectory(p)) collectProfile(p, out, depth + 1); | |
| 250 | + } | |
| 251 | + out.push(dir); | |
| 252 | + } | |
| 253 | + | |
| 254 | + static function realpath(p: String): Null<String> { | |
| 255 | + if (!FileSystem.exists(p)) return null; | |
| 256 | + try { | |
| 257 | + var proc = new Process("readlink", ["-f", p]); | |
| 258 | + var out = StringTools.trim(proc.stdout.readAll().toString()); | |
| 259 | + proc.close(); | |
| 260 | + return out == "" ? null : out; | |
| 261 | + } catch (e: Dynamic) { | |
| 262 | + return null; | |
| 263 | + } | |
| 264 | + } | |
| 265 | + | |
| 266 | + static function configProtect(): Array<String> { | |
| 267 | + var value = Sys.getEnv("CONFIG_PROTECT"); | |
| 268 | + if (value == null && FileSystem.exists("/etc/portage/make.conf")) { | |
| 269 | + var text = "\n" + File.getContent("/etc/portage/make.conf"); | |
| 270 | + var re = new EReg("\nCONFIG_PROTECT=[\"']?([^\"'\n]*)[\"']?", ""); | |
| 271 | + var rest = text; | |
| 272 | + while (re.match(rest)) { | |
| 273 | + value = re.matched(1); | |
| 274 | + rest = re.matchedRight(); | |
| 275 | + } | |
| 276 | + } | |
| 277 | + if (value == null) value = "/etc /usr/share/config"; | |
| 278 | + return [for (w in ~/[ \t\r\n]+/g.split(value)) if (w != "") w]; | |
| 279 | + } | |
| 280 | + | |
| 281 | + static function isProtected(path: String): Bool { | |
| 282 | + for (prefix in configProtect()) { | |
| 283 | + if (path == prefix || StringTools.startsWith(path, prefix + "/")) return true; | |
| 284 | + } | |
| 285 | + return false; | |
| 286 | + } | |
| 287 | + | |
| 288 | + static function md5Of(path: String): String { | |
| 289 | + if (!FileSystem.exists(path) || FileSystem.isDirectory(path)) return ""; | |
| 290 | + try { | |
| 291 | + var p = new Process("md5sum", [path]); | |
| 292 | + var out = StringTools.trim(p.stdout.readAll().toString()); | |
| 293 | + p.close(); | |
| 294 | + var parts = out.split(" "); | |
| 295 | + return parts.length > 0 ? parts[0] : ""; | |
| 296 | + } catch (e: Dynamic) { | |
| 297 | + return ""; | |
| 298 | + } | |
| 299 | + } | |
| 300 | + | |
| 301 | + static function isModifiedConfig(base: String, words: Array<String>): Bool { | |
| 302 | + if (words.length < 3 || words[0] != "obj") return false; | |
| 303 | + if (!isProtected(words[1])) return false; | |
| 304 | + var current = md5Of(base + words[1]); | |
| 305 | + return current != "" && current != words[2]; | |
| 306 | + } | |
| 307 | + | |
| 308 | + public static function pnFromDir(pv: String): String { | |
| 309 | + for (i in 0...pv.length) { | |
| 310 | + if (pv.charAt(i) != "-" || i + 1 >= pv.length) continue; | |
| 311 | + var next = pv.charAt(i + 1); | |
| 312 | + if (next >= "0" && next <= "9" && Version.parse(pv.substr(i + 1)) != null) { | |
| 313 | + return pv.substr(0, i); | |
| 314 | + } | |
| 315 | + } | |
| 316 | + return ""; | |
| 317 | + } | |
| 318 | + | |
| 319 | + static function read(file: String): String { | |
| 320 | + if (!FileSystem.exists(file)) return ""; | |
| 321 | + return StringTools.trim(File.getContent(file)); | |
| 322 | + } | |
| 323 | + | |
| 324 | + static function lines(file: String): Array<String> { | |
| 325 | + if (!FileSystem.exists(file)) return []; | |
| 326 | + return [for (l in File.getContent(file).split("\n")) if (StringTools.trim(l) != "") StringTools.trim(l)]; | |
| 327 | + } | |
| 328 | +} |