acooliodude / portage-hx

Push x3 Browse files
ACoolioDude committed 1d ago parent 1627b1f commit cbc41dedc4c7d04b85039f5adbde0914b3b65e3e
Showing 5 changed files with 616 additions and 16 deletions. UnifiedSplit
+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 }