acooliodude / portage-hx

Branch: 1b3efb1
Switch branches/tags
main
ACoolioDude Push x1 1b3efb1 1d ago
847 lines · 29 KB Raw Blame History
1 import sys.FileSystem;
2 import sys.io.File;
3 import sys.io.Process;
4
5 typedef Entry = {
6 cat: String,
7 pkg: String,
8 ver: Version,
9 path: String,
10 repo: String,
11 slot: String,
12 deps: Array<String>,
13 subslot: String
14 }
15
16 typedef Status = {
17 tag: String,
18 old: Null<String>
19 }
20
21 typedef Suggestion = {
22 label: String,
23 score: Int
24 }
25
26 class Main {
27 static var repos: Repository;
28 static var seen = new Map<String, Bool>();
29 static var plan: Array<Entry> = [];
30 static var useColor = Sys.getEnv("NO_COLOR") == null;
31 static var trace = Sys.getEnv("EMERGE_TRACE") != null;
32 static var targetRoot = "/";
33 static var showRepo = false;
34 static var update = false;
35 static var syncFlag = false;
36 static var syncOnly: Null<String> = null;
37 static var explainAtom: Null<String> = null;
38 static var vdb: VDB;
39 static var hostVdb: VDB;
40 static var chain: Array<String> = [];
41
42 static final PROGRESS_LABEL = "Calculating dependencies ";
43 static final SPIN_FRAMES = ["|", "/", "-", "\\"];
44 static final FRAME_SECONDS = 0.08;
45 static final MIN_SHOW_SECONDS = 0.6;
46 static var spinIndex = 0;
47 static var progressStart = 0.0;
48 static var lastFrame = 0.0;
49
50 static final NAME_RE = ~/^[a-zA-Z0-9][a-zA-Z0-9_+.-]*$/;
51 static final NON_CATEGORY_DIRS = ["profiles", "eclass", "metadata", "licenses", "scripts", "packages", "distfiles"];
52 static final MAX_SUGGESTIONS = 10;
53 static var indexCache: Null<Array<String>> = null;
54
55 static function c(code: String, s: String): String {
56 return useColor ? '\x1B[${code}m$s\x1B[0m' : s;
57 }
58
59 static function log(msg: String): Void {
60 if (!trace) return;
61 Sys.stderr().writeString(msg + "\n");
62 Sys.stderr().flush();
63 }
64
65 static function startProgress(): Void {
66 progressStart = haxe.Timer.stamp();
67 drawFrame();
68 }
69
70 static function drawFrame(): Void {
71 spinIndex = (spinIndex + 1) % SPIN_FRAMES.length;
72 Sys.print("\r" + PROGRESS_LABEL + SPIN_FRAMES[spinIndex]);
73 Sys.stdout().flush();
74 lastFrame = haxe.Timer.stamp();
75 }
76
77 static function spin(): Void {
78 if (haxe.Timer.stamp() - lastFrame < FRAME_SECONDS) return;
79 drawFrame();
80 }
81
82 static function finishProgress(): Void {
83 while (haxe.Timer.stamp() - progressStart < MIN_SHOW_SECONDS) {
84 drawFrame();
85 Sys.sleep(FRAME_SECONDS);
86 }
87 Sys.print("\r" + PROGRESS_LABEL + "... done!\n");
88 Sys.stdout().flush();
89 }
90
91 static function clearProgress(): Void {
92 Sys.print("\r" + StringTools.lpad("", " ", 40) + "\r");
93 Sys.stdout().flush();
94 }
95
96 static function warn(msg: String): Void {
97 clearProgress();
98 Sys.println("warning: " + msg);
99 }
100
101 static function isRoot(): Bool {
102 try {
103 var p = new Process("id", ["-u"]);
104 var out = StringTools.trim(p.stdout.readAll().toString());
105 p.close();
106 return out == "0";
107 } catch (e: Dynamic) {
108 return false;
109 }
110 }
111
112 static function packageIndex(): Array<String> {
113 if (indexCache != null) return indexCache;
114
115 var seenLabel = new Map<String, Bool>();
116 var out: Array<String> = [];
117 for (r in repos.list) {
118 if (!FileSystem.exists(r.tree.root)) continue;
119 for (cat in FileSystem.readDirectory(r.tree.root)) {
120 if (StringTools.startsWith(cat, ".") || NON_CATEGORY_DIRS.indexOf(cat) >= 0) continue;
121 var catDir = r.tree.root + "/" + cat;
122 if (!FileSystem.isDirectory(catDir)) continue;
123 for (pkg in FileSystem.readDirectory(catDir)) {
124 if (StringTools.startsWith(pkg, ".")) continue;
125 if (!FileSystem.isDirectory(catDir + "/" + pkg)) continue;
126 var label = cat + "/" + pkg;
127 if (seenLabel.exists(label)) continue;
128 seenLabel.set(label, true);
129 out.push(label);
130 }
131 }
132 }
133 out.sort(function(a, b) return a < b ? -1 : (a > b ? 1 : 0));
134 indexCache = out;
135 return out;
136 }
137
138 static function nameOf(label: String): String {
139 return label.substr(label.lastIndexOf("/") + 1);
140 }
141
142 static function levenshtein(a: String, b: String): Int {
143 var prev = [for (j in 0...b.length + 1) j];
144 for (i in 1...a.length + 1) {
145 var cur = [i];
146 for (j in 1...b.length + 1) {
147 var cost = a.charAt(i - 1) == b.charAt(j - 1) ? 0 : 1;
148 var del = prev[j] + 1;
149 var ins = cur[j - 1] + 1;
150 var sub = prev[j - 1] + cost;
151 cur.push(Std.int(Math.min(del, Math.min(ins, sub))));
152 }
153 prev = cur;
154 }
155 return prev[b.length];
156 }
157
158 static function resolveName(name: String): String {
159 var exact = [for (label in packageIndex()) if (nameOf(label) == name) label];
160 if (exact.length == 1) return exact[0];
161
162 if (exact.length > 1) {
163 Sys.println('\n"$name" is in more than one category. Use category/name to pick one:');
164 for (l in exact) Sys.println(" " + l);
165 Sys.exit(1);
166 }
167
168 var hits: Array<Suggestion> = [];
169 var limit = Std.int(Math.max(3, name.length / 3));
170 for (label in packageIndex()) {
171 var pkg = nameOf(label);
172 var score: Int;
173 if (pkg.indexOf(name) >= 0 || (pkg.length >= 3 && name.indexOf(pkg) >= 0)) {
174 score = 0;
175 } else {
176 score = levenshtein(name, pkg);
177 }
178 if (score <= limit) hits.push({label: label, score: score});
179 }
180 hits.sort(function(a, b) {
181 if (a.score != b.score) return a.score - b.score;
182 return a.label < b.label ? -1 : (a.label > b.label ? 1 : 0);
183 });
184
185 if (hits.length == 0) {
186 Sys.println('\nNo package named "$name" in any repository.');
187 Sys.exit(1);
188 }
189
190 Sys.println('\nNo package named "$name". Did you mean:');
191 var shown = hits.length < MAX_SUGGESTIONS ? hits.length : MAX_SUGGESTIONS;
192 for (i in 0...shown) Sys.println(" " + hits[i].label);
193 if (hits.length > shown) Sys.println(' ...and ${hits.length - shown} more');
194 Sys.exit(1);
195 return name;
196 }
197
198 static function installedSatisfies(a: Atom): Bool {
199 for (i in vdb.installed(a.cat, a.pkg)) {
200 var info = Slots.installedSlot(vdb.root, a.cat, a.pkg + "-" + i.ver.raw);
201 if (AtomParser.matches(a, i.ver) && AtomParser.slotMatches(a, info.slot, info.subslot)) return true;
202 if (AtomParser.matches(a, i.ver)) return true;
203 }
204 return false;
205 }
206
207 static var blockerList: Array<{atom: Atom, strong: Bool, by: String}> = [];
208
209 static function checkBlockers(): Bool {
210 var planned = new Map<String, Bool>();
211 for (e in plan) planned.set(e.cat + "/" + e.pkg, true);
212
213 var ok = true;
214 for (b in blockerList) {
215 var label = b.atom.cat + "/" + b.atom.pkg;
216 if (planned.exists(label)) continue;
217 for (i in vdb.installed(b.atom.cat, b.atom.pkg)) {
218 var info = Slots.installedSlot(vdb.root, b.atom.cat, b.atom.pkg + "-" + i.ver.raw);
219 if (!AtomParser.matches(b.atom, i.ver) || !AtomParser.slotMatches(b.atom, info.slot, info.subslot)) continue;
220
221 ok = false;
222 var kind = b.strong ? "!!" : "!";
223 Sys.println(c("1;31", ">>> blocked: " + b.by + " conflicts with installed " + label + "-" + i.ver.raw + " (" + kind + " blocker)"));
224 }
225 }
226 if (!ok) Sys.println(c("1;31", ">>> Resolve the blocker first. Unmerging installed packages is not supported yet."));
227 return ok;
228 }
229
230 static var reportedCycles = new Map<String, Bool>();
231
232 static function reportCycle(key: String): Void {
233 var start = chain.indexOf(key);
234 var loop = chain.slice(start).concat([key]);
235 var text = loop.join(" -> ");
236 if (reportedCycles.exists(text)) return;
237 reportedCycles.set(text, true);
238 warn("circular dependency: " + text + " (cut here; merge order inside the loop may be wrong)");
239 }
240
241 static function slotOf(repoName: String, path: String): {slot: String, subslot: String} {
242 for (r in repos.list) {
243 if (r.name == repoName) return r.tree.slotInfo(path);
244 }
245 return {slot: "0", subslot: "0"};
246 }
247
248 static function installedSubslot(cat: String, pkg: String, slot: String): Null<String> {
249 for (i in vdb.installed(cat, pkg)) {
250 var info = Slots.installedSlot(vdb.root, cat, pkg + "-" + i.ver.raw);
251 if (info.slot == slot) return info.subslot;
252 }
253 return null;
254 }
255
256 static function hostSatisfies(a: Atom): Bool {
257 for (i in hostVdb.installed(a.cat, a.pkg)) {
258 if (AtomParser.matches(a, i.ver)) return true;
259 }
260 return false;
261 }
262
263 static function resolve(a: Atom, label: String, top: Bool) {
264 if (installedSatisfies(a) && (update ? !upgradeAvailable(a) : !top)) {
265 log("installed, skipping " + label);
266 return;
267 }
268
269 spin();
270
271 var hit = repos.best(a);
272 var inst = vdb.installed(a.cat, a.pkg);
273
274 var targetVer: Version = null;
275 var targetRepo: String = null;
276 var targetPath: String = null;
277
278 if (hit != null) {
279 targetVer = hit.ver;
280 targetRepo = hit.repo.name;
281 targetPath = hit.repo.tree.ebuildPath(a.cat, a.pkg, hit.ver);
282 }
283
284 if (inst.length > 0) {
285 var topInst = inst[0];
286 for (i in inst) {
287 if (i.ver.compare(topInst.ver) > 0) topInst = i;
288 }
289
290 if (hit == null || topInst.ver.compare(hit.ver) >= 0) {
291 if (AtomParser.matches(a, topInst.ver)) {
292 var matchedRepo: Dynamic = null;
293 for (r in repos.list) {
294 if (r.name == topInst.repo) {
295 matchedRepo = r;
296 break;
297 }
298 }
299 if (matchedRepo == null && repos.list.length > 0) matchedRepo = repos.list[0];
300
301 if (matchedRepo != null) {
302 var p = matchedRepo.tree.ebuildPath(a.cat, a.pkg, topInst.ver);
303 if (FileSystem.exists(p)) {
304 targetVer = topInst.ver;
305 targetRepo = matchedRepo.name;
306 targetPath = p;
307 }
308 }
309 }
310 }
311 }
312
313 if (targetVer == null) {
314 if (hit == null) throw 'no ebuild matches "$label"';
315 targetVer = hit.ver;
316 targetRepo = hit.repo.name;
317 targetPath = hit.repo.tree.ebuildPath(a.cat, a.pkg, hit.ver);
318 }
319
320 var key = a.cat + "/" + a.pkg + "-" + targetVer.raw;
321 if (seen.exists(key)) {
322 if (chain.indexOf(key) >= 0) reportCycle(key);
323 return;
324 }
325 seen.set(key, true);
326
327 if (chain.length > 150) {
328 throw "dependency chain too deep: " + chain.slice(-10).join(" -> ") + " -> " + key;
329 }
330 chain.push(key);
331 log("resolve " + key);
332
333 var activeRepo = null;
334 for (r in repos.list) {
335 if (r.name == targetRepo) {
336 activeRepo = r;
337 break;
338 }
339 }
340 var tree = activeRepo != null ? activeRepo.tree : repos.list[0].tree;
341 var flags = UseFlags.forPackage(a.cat, a.pkg, tree.iuse(targetPath));
342
343 var runtime = Dependencies.eval(Dependencies.parse(tree.runtimeSpec(targetPath)), flags, chooser);
344
345 var toResolve = runtime.copy();
346 var build = Dependencies.eval(Dependencies.parse(tree.buildSpec(targetPath)), flags, chooser);
347 for (d in build) {
348 if (targetRoot == "/") {
349 toResolve.push(d);
350 } else if (!hostSatisfies(d)) {
351 warn('build dependency not installed on host: ${d.cat}/${d.pkg}');
352 }
353 }
354
355 var depLabels: Array<String> = [];
356 for (d in toResolve) {
357 var dl = d.cat + "/" + d.pkg;
358 depLabels.push(dl);
359 resolve(d, dl, false);
360 }
361
362 chain.pop();
363 var si = slotOf(targetRepo, targetPath);
364 plan.push({cat: a.cat, pkg: a.pkg, ver: targetVer, path: targetPath, repo: targetRepo, deps: depLabels, slot: si.slot, subslot: si.subslot});
365
366 var by = a.cat + "/" + a.pkg + "-" + targetVer.raw;
367 var blocked = Dependencies.blockers(Dependencies.parse(tree.runtimeSpec(targetPath)), flags)
368 .concat(Dependencies.blockers(Dependencies.parse(tree.buildSpec(targetPath)), flags));
369 for (b in blocked) blockerList.push({atom: b.atom, strong: b.strong, by: by});
370
371 for (d in Dependencies.eval(Dependencies.parse(tree.variable(targetPath, "PDEPEND")), flags, chooser)) {
372 resolve(d, d.cat + "/" + d.pkg, false);
373 }
374 }
375
376 static function upgradeAvailable(a: Atom): Bool {
377 var hit = repos.best(a);
378 if (hit == null) return false;
379 for (i in vdb.installed(a.cat, a.pkg)) {
380 if (i.ver.compare(hit.ver) >= 0) return false;
381 }
382 return true;
383 }
384
385 static function syncRepos(only: Null<String>): Bool {
386 Sys.println(c("1;32", only == null ? ">>> Syncing repositories ..." : ">>> Syncing " + only + " ..."));
387 var ok = Sync.run(only);
388 if (!ok) {
389 Sys.println(c("1;31", ">>> Sync finished with errors"));
390 return false;
391 }
392 Sys.println(c("1;32", ">>> Sync complete."));
393 return true;
394 }
395
396 static function rebuildSubslotDependents(): Void {
397 for (e in plan.copy()) {
398 var old = installedSubslot(e.cat, e.pkg, e.slot);
399 if (old == null || old == e.subslot) continue;
400
401 for (dep in Slots.dependentsWithSubslotOp(vdb.root, e.cat, e.pkg)) {
402 var a = AtomParser.parse(dep);
403 if (a == null) continue;
404 clearProgress();
405 Sys.println(c("1;33", ">>> " + dep + " uses " + e.cat + "/" + e.pkg + ":= and must be rebuilt (subslot " + old + " -> " + e.subslot + ")"));
406 try {
407 resolve(a, dep, true);
408 } catch (x: Dynamic) {
409 Sys.println(c("1;31", ">>> could not plan rebuild of " + dep + ": " + x));
410 }
411 }
412 }
413 }
414
415 static function chooser(alts: Array<Array<Atom>>): Int {
416 var firstResolvable = -1;
417 for (i in 0...alts.length) {
418 var resolvable = alts[i].length > 0;
419 var installed = alts[i].length > 0;
420 for (at in alts[i]) {
421 if (repos.best(at) == null) {
422 resolvable = false;
423 break;
424 }
425 if (!installedSatisfies(at)) installed = false;
426 }
427 if (!resolvable) continue;
428 if (installed) return i;
429 if (firstResolvable < 0) firstResolvable = i;
430 }
431 return firstResolvable < 0 ? 0 : firstResolvable;
432 }
433
434 static function status(vdb: VDB, e: Entry): Status {
435 var exactInst = [for (i in vdb.installed(e.cat, e.pkg))
436 if (Slots.installedSlot(vdb.root, e.cat, e.pkg + "-" + i.ver.raw).slot == e.slot) i];
437
438 if (exactInst.length > 0) {
439 var top = exactInst[0];
440 for (i in exactInst) {
441 if (i.ver.compare(top.ver) > 0) top = i;
442 }
443
444 var cmp = e.ver.compare(top.ver);
445 var oldStr = top.ver.raw + "::" + top.repo;
446 if (cmp == 0) return {tag: "R", old: null};
447 if (cmp > 0) return {tag: "U", old: oldStr};
448 return {tag: "UD", old: oldStr};
449 }
450
451 var allInst = vdb.installed(e.cat, e.pkg);
452 if (allInst.length == 0) return {tag: "N", old: null};
453
454 var topAll = allInst[0];
455 for (i in allInst) {
456 if (i.ver.compare(topAll.ver) > 0) topAll = i;
457 }
458 return {tag: "NS", old: topAll.ver.raw};
459 }
460
461 static function isTesting(e: Entry): Bool {
462 var accept = Keywords.active();
463 var kw = "";
464 for (r in repos.list) {
465 if (r.name == e.repo) {
466 kw = r.tree.variable(e.path, "KEYWORDS");
467 break;
468 }
469 }
470
471 var testing = false;
472 for (t in ~/[ \t\r\n]+/g.split(kw)) {
473 if (t == "") continue;
474 if (accept.indexOf(t) < 0) continue;
475 if (StringTools.startsWith(t, "~")) testing = true;
476 else return false;
477 }
478 return testing;
479 }
480
481 static function showPlan(vdb: VDB, verbose: Bool) {
482 if (plan.length == 0) {
483 Sys.println(c("1;32", "Nothing to merge; all requested packages are installed."));
484 return;
485 }
486
487 Sys.println(c("1;32", "These are the packages that would be merged, in order:"));
488 Sys.println("");
489
490 var counts = new Map<String, Int>();
491 for (e in plan) {
492 var st = status(vdb, e);
493 counts.set(st.tag, (counts.exists(st.tag) ? counts.get(st.tag) : 0) + 1);
494
495 var tagCol = switch st.tag {
496 case "N" | "NS": "32";
497 case "U": "34";
498 case "UD": "35";
499 default: "33";
500 };
501
502 var line = c("1;32", "[ebuild ")
503 + c(tagCol, " " + StringTools.rpad(st.tag, " ", 2) + (isTesting(e) ? "+" : " ") + " ")
504 + c("1;32", "]")
505 + " " + c("1;37", e.cat + "/" + e.pkg + "-" + e.ver.raw)
506 + (showRepo ? c("0;36", "::" + e.repo) : "");
507
508 if (st.old != null) {
509 line += " " + c("0;36", "[" + st.old + "]");
510 }
511
512 if (verbose) {
513 var pkgSize = Std.int(getDownloadSize(e) / 1024);
514 if (pkgSize > 0) {
515 line += " " + formatNum(pkgSize) + " KiB";
516 } else {
517 line += " 0 KiB";
518 }
519 }
520
521 Sys.println(line);
522 }
523
524 Sys.println("");
525 var parts: Array<String> = [];
526 if (counts.exists("U")) parts.push(counts.get("U") + " upgrade" + (counts.get("U") == 1 ? "" : "s"));
527 if (counts.exists("UD")) parts.push(counts.get("UD") + " downgrade" + (counts.get("UD") == 1 ? "" : "s"));
528 if (counts.exists("N")) parts.push(counts.get("N") + " new");
529 if (counts.exists("NS")) parts.push(counts.get("NS") + " in new slot");
530 if (counts.exists("R")) parts.push(counts.get("R") + " reinstall");
531
532 var totalSize = 0;
533 for (e in plan) {
534 totalSize += getDownloadSize(e);
535 }
536 var sizeKiB = Std.int(totalSize / 1024);
537
538 var plural = plan.length == 1 ? "" : "s";
539 var summary = "Total: " + plan.length + " package" + plural + " (" + parts.join(", ") + ")";
540 if (sizeKiB > 0) {
541 summary += ", Size of downloads: " + formatNum(sizeKiB) + " KiB";
542 }
543 if (verbose) Sys.println(summary);
544 }
545
546 static function explain(spec: String): Void {
547 var a = AtomParser.parse(spec);
548 if (a == null) {
549 Sys.println("invalid atom: " + spec);
550 return;
551 }
552
553 var hit = repos.best(a);
554 var chosen: Null<Version> = hit == null ? null : hit.ver;
555
556 Sys.println("ACCEPT_KEYWORDS: " + Keywords.active().join(" "));
557 for (r in repos.list) {
558 var vs = r.tree.versions(a.cat, a.pkg);
559 vs.sort(function(x, y) return y.compare(x));
560 for (v in vs) {
561 var kw = r.tree.variable(r.tree.ebuildPath(a.cat, a.pkg, v), "KEYWORDS");
562 var masked = Mask.isMasked(a.cat, a.pkg, v);
563 var kwOk = Keywords.acceptsWith(kw, Mask.extraKeywords(a.cat, a.pkg, v));
564 var picked = chosen != null && chosen.compare(v) == 0;
565 Sys.println(" " + a.cat + "/" + a.pkg + "-" + v.raw + "::" + r.name
566 + " KEYWORDS=\"" + kw + "\""
567 + " masked=" + masked
568 + " keywords-ok=" + kwOk
569 + (picked ? " <- picked" : ""));
570 }
571 }
572 }
573
574 static function expandSets(list: Array<String>): Array<String> {
575 var out: Array<String> = [];
576 for (arg in list) {
577 if (arg == "@world") {
578 for (a in readWorld()) out.push(a);
579 } else {
580 out.push(arg);
581 }
582 }
583 return out;
584 }
585
586 static function readWorld(): Array<String> {
587 var base = StringTools.endsWith(targetRoot, "/") ? targetRoot.substr(0, targetRoot.length - 1) : targetRoot;
588 var path = base + "/var/lib/portage/world";
589 if (!FileSystem.exists(path)) {
590 Sys.println('no world file at ' + path);
591 Sys.exit(1);
592 }
593
594 var out: Array<String> = [];
595 for (raw in File.getContent(path).split("\n")) {
596 var line = StringTools.trim(raw);
597 if (line == "" || StringTools.startsWith(line, "#")) continue;
598 if (AtomParser.parse(line) == null) {
599 Sys.println('warning: skipping world entry "' + line + '"');
600 continue;
601 }
602 out.push(line);
603 }
604 return out;
605 }
606
607 static function fullLabel(e: Entry): String {
608 var base = e.cat + "/" + e.pkg + "-" + e.ver.raw;
609 return showRepo ? base + "::" + e.repo : base;
610 }
611
612 static function expandShortFlags(args: Array<String>): Array<String> {
613 var bundled = new EReg("^-[a-zA-Z]{2,}$", "");
614 var out: Array<String> = [];
615 for (arg in args) {
616 if (bundled.match(arg)) {
617 for (i in 1...arg.length) out.push("-" + arg.charAt(i));
618 } else {
619 out.push(arg);
620 }
621 }
622 return out;
623 }
624
625 static function getDownloadSize(e: Entry): Int {
626 var dir = haxe.io.Path.directory(e.path);
627 var manifestPath = dir + "/Manifest";
628 if (!FileSystem.exists(manifestPath)) return 0;
629
630 var baseVer = e.ver.raw;
631 var dashR = baseVer.lastIndexOf("-r");
632 if (dashR > 0) {
633 baseVer = baseVer.substr(0, dashR);
634 }
635
636 var totalSize = 0;
637 try {
638 var content = File.getContent(manifestPath);
639 for (line in content.split("\n")) {
640 var trimmed = StringTools.trim(line);
641 if (StringTools.startsWith(trimmed, "DIST ")) {
642 var parts = ~/[ \t]+/g.split(trimmed);
643 if (parts.length >= 3) {
644 var filename = parts[1];
645 if (StringTools.contains(filename, baseVer)) {
646 var sz = Std.parseInt(parts[2]);
647 if (sz != null) {
648 totalSize += sz;
649 }
650 }
651 }
652 }
653 }
654 } catch (err: Dynamic) {}
655 return totalSize;
656 }
657
658 static function formatNum(n: Int): String {
659 var s = Std.string(n);
660 var out = "";
661 var len = s.length;
662 for (i in 0...len) {
663 if (i > 0 && (len - i) % 3 == 0) out += ",";
664 out += s.charAt(i);
665 }
666 return out;
667 }
668
669 static function mergeOne(e: Entry, count: Int, total: Int): Bool {
670 var pos = "(" + count + " of " + total + ") ";
671
672 Sys.println("");
673 Sys.println(c("1;32", ">>> Emerging " + pos) + c("1;37", fullLabel(e)));
674
675 var build = Sys.command("ebuild", [e.path, "clean", "unpack", "prepare", "configure", "compile", "install"]);
676 if (build != 0) {
677 Sys.println(c("1;31", ">>> Failed " + pos + fullLabel(e) + " (build exit " + build + ")"));
678 return false;
679 }
680
681 Sys.println(c("1;32", ">>> Installing " + pos) + c("1;37", fullLabel(e)));
682
683 var merge = Sys.command("ebuild", [e.path, "qmerge"]);
684 if (merge != 0) {
685 Sys.println(c("1;31", ">>> Failed " + pos + fullLabel(e) + " (merge exit " + merge + ")"));
686 return false;
687 }
688
689 Sys.println(c("1;32", ">>> Completed " + pos) + c("1;37", fullLabel(e)));
690 return true;
691 }
692
693 static function main() {
694 repos = Repository.load();
695
696 var pretend = false;
697 var ask = false;
698 var atoms: Array<String> = [];
699 var searchQuery: Null<String> = null;
700 var searchDesc = false;
701 var verbose = false;
702
703 var args = expandShortFlags(Sys.args());
704 var idx = 0;
705 while (idx < args.length) {
706 var arg = args[idx++];
707 if (StringTools.startsWith(arg, "--sync=")) {
708 syncFlag = true;
709 syncOnly = arg.substr("--sync=".length);
710 continue;
711 }
712 switch arg {
713 case "--pretend" | "-p": pretend = true;
714 case "--ask" | "-a": ask = true;
715 case "--verbose" | "-v": verbose = true;
716 case "--update" | "-u": update = true;
717 case "--sync":
718 syncFlag = true;
719 if (idx < args.length && Sync.repoNames().indexOf(args[idx]) >= 0) {
720 syncOnly = args[idx++];
721 }
722 case "--explain":
723 if (idx < args.length) explainAtom = args[idx++];
724 case "--search" | "-s":
725 if (idx >= args.length) {
726 Sys.println("--search needs a query");
727 Sys.exit(1);
728 }
729 searchQuery = args[idx++];
730 case "--desc" | "-S": searchDesc = true;
731 case "--root":
732 if (idx >= args.length) {
733 Sys.println("--root needs a directory");
734 Sys.exit(1);
735 }
736 targetRoot = args[idx++];
737 default: atoms.push(arg);
738 }
739 }
740
741 showRepo = verbose;
742
743 if (searchQuery != null) {
744 Search.run(repos, searchQuery, searchDesc);
745 return;
746 }
747
748 if (explainAtom != null) {
749 explain(explainAtom);
750 return;
751 }
752
753 atoms = expandSets(atoms);
754
755 if (syncFlag) {
756 if (!isRoot()) {
757 Sys.println("emerge-hx: superuser access is required for --sync");
758 Sys.exit(1);
759 }
760 if (!syncRepos(syncOnly)) Sys.exit(1);
761 indexCache = null;
762 repos = Repository.load();
763 if (atoms.length == 0) return;
764 }
765
766 if (atoms.length == 0) {
767 Sys.println("usage: emerge-hx [--sync[=REPO]] [-u] [--pretend | --ask] [--root DIR] <atom> [atom...]");
768 Sys.println(" emerge-hx --search <query> [--desc]");
769 Sys.exit(1);
770 }
771
772 if (!pretend && !isRoot()) {
773 if (!ask) {
774 Sys.println("emerge-hx: superuser access is required");
775 Sys.exit(1);
776 }
777 Sys.println("This action requires superuser access...");
778 Sys.print("Would you like to add --pretend to options? [Yes/No] ");
779 var choice = "";
780 try {
781 choice = Sys.stdin().readLine().toLowerCase();
782 } catch (e: Dynamic) {
783 choice = "";
784 }
785 if (choice != "y" && choice != "yes") {
786 Sys.println("Aborted.");
787 Sys.exit(1);
788 }
789 pretend = true;
790 }
791
792 vdb = new VDB(targetRoot);
793 hostVdb = new VDB("/");
794
795 startProgress();
796
797 for (arg in atoms) {
798 var spec = NAME_RE.match(arg) ? resolveName(arg) : arg;
799
800 var a = AtomParser.parse(spec);
801 if (a == null) {
802 clearProgress();
803 Sys.println('invalid atom: $arg');
804 Sys.exit(1);
805 }
806 try {
807 resolve(a, spec, true);
808 } catch (e: Dynamic) {
809 clearProgress();
810 Sys.println('error: $e');
811 Sys.exit(1);
812 }
813 }
814
815 rebuildSubslotDependents();
816
817 finishProgress();
818 Sys.println("");
819
820 if (!checkBlockers()) Sys.exit(1);
821
822 showPlan(vdb, verbose);
823
824 if (pretend || plan.length == 0) return;
825
826 if (ask) {
827 Sys.print("\nWould you like to merge these packages? [Yes/No] ");
828 var answer = "";
829 try {
830 answer = Sys.stdin().readLine();
831 } catch (e: Dynamic) {
832 answer = "";
833 }
834 answer = answer.toLowerCase();
835 if (answer != "y" && answer != "yes") {
836 Sys.println("Aborted.");
837 return;
838 }
839 }
840
841 Sys.putEnv("ROOT", targetRoot);
842 var total = plan.length;
843 for (n in 0...total) {
844 if (!mergeOne(plan[n], n + 1, total)) Sys.exit(1);
845 }
846 }
847 }