| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242 |
- using Invercargill;
- using Invercargill.DataStructures;
- /**
- * `usm remove [-y|--yes] <packages>` — remove installed packages and,
- * by cascade, every installed package that transitively depends on any
- * of them (discovered through {@link Usm.SystemState.find_dependant_names},
- * so the summary shows the full closure before anything happens). All
- * requested cascades merge into one plan and one confirm.
- *
- * The plan then gains every orphaned dependency: a remaining package
- * installed implicitly ({@link Usm.OriginInformation.explicitly_installed}
- * false — a resolved dependency, or a record from before the field
- * existed) that no package surviving the removal depends on. Orphans
- * appear under their own "remove (orphaned)" label in the summary, and
- * nothing is removed without confirmation.
- *
- * After the transaction each removed package's state directory —
- * sources, build cache, `installed/` entry — is deleted; OTHER versions'
- * directories (the siblings updates keep for downgrade) remain.
- */
- private int remove_main(string[] args) {
- var assume_yes = false;
- var package_names = new Vector<string>();
- for(int i = 2; i < args.length; i++) {
- var argument = args[i];
- if(argument == "-y" || argument == "--yes") {
- assume_yes = true;
- }
- else if(argument.has_prefix("-")) {
- printerr(@"Unknown option \"$argument\"\n");
- return remove_usage();
- }
- else {
- package_names.add(argument);
- }
- }
- if(package_names.length == 0) {
- return remove_usage();
- }
- var progress = new Usm.Cli.ProgressBar();
- Usm.SystemState state = null;
- try {
- state = new Usm.SystemState(paths);
- }
- catch(Error e) {
- printerr(@"This system is not managed by usm: $(e.message)\n");
- return 245;
- }
- try {
- var by_package_name = new Dictionary<string, Usm.CachedPackage>();
- foreach(var installed in state.get_installed_packages()) {
- by_package_name.set(installed.package_name, state.resolve_installed(installed));
- }
- // One shared visited set across every requested target, so a
- // package reached from two cascades is removed once
- var closure = new Vector<Usm.CachedPackage>();
- var visited = new HashSet<string>();
- foreach(var package_name in package_names) {
- var target = state.find_installed(package_name);
- if(target == null) {
- printerr(@"No installed package named \"$package_name\"\n");
- return 254;
- }
- remove_collect_cascade(state, by_package_name, visited, closure, target);
- }
- var orphans = remove_collect_orphans(by_package_name, closure);
- var to_remove = new HashSet<Usm.CachedPackage>();
- to_remove.union_with(closure);
- to_remove.union_with(orphans);
- var transaction = new Usm.Transaction() {
- paths = paths,
- resource_finder = new Usm.ResourceFinder(paths),
- to_remove = to_remove,
- state = state
- };
- // Planning before the prompt lets the removal order (dependents
- // before providers, orphans last) be computed and shown
- try {
- transaction.strategise();
- }
- catch(Usm.TransactionError e) {
- printerr(@"$(e.message)\n");
- return 240;
- }
- var summary = new Usm.TransactionSummary() {
- to_remove = closure,
- orphaned_removals = orphans
- };
- if(!Usm.Cli.confirm_transaction(summary, assume_yes)) {
- print("Aborted.\n");
- return 1;
- }
- printerr("\nRunning transaction...\n");
- transaction.progress_updated.connect(progress.on_transaction_progress);
- transaction.run();
- progress.finish();
- // The state directories go only after a successful transaction;
- // other versions' directories are never touched
- var removed_packages = closure.concat(orphans);
- foreach(var package in removed_packages) {
- Usm.Util.delete_tree(package.state_path);
- }
- print(@"Removed $(closure.length + orphans.length) package(s) ($(removed_packages.to_string(p => p.package_name, ", ")))\n");
- }
- catch(Error e) {
- progress.fail();
- printerr(@"Error: $(e.message)\n");
- return 238;
- }
- return 0;
- }
- /**
- * Depth-first reverse-dependency closure: {@link package} plus every
- * installed package that transitively depends on it, appended to
- * {@link closure} target-first. {@link visited} keys on the versioned
- * cache-directory name so shared dependants collapse to one removal.
- */
- private void remove_collect_cascade(Usm.SystemState state, Dictionary<string, Usm.CachedPackage> by_package_name,
- HashSet<string> visited, Vector<Usm.CachedPackage> closure, Usm.CachedPackage package) throws Error {
- if(visited.contains(package.package_name)) {
- return;
- }
- visited.add(package.package_name);
- closure.add(package);
- foreach(var dependant_name in state.find_dependant_names(package)) {
- Usm.CachedPackage dependant;
- if(by_package_name.try_get(dependant_name, out dependant)) {
- remove_collect_cascade(state, by_package_name, visited, closure, dependant);
- }
- }
- }
- /**
- * Orphan scan over the packages {@link closure} leaves behind: every
- * implicitly installed survivor that no other survivor depends on,
- * where dependency means any runtime/build/manage/acquire ref matched
- * against the candidate's provides (the same resource-level matching
- * {@link Usm.SystemState.find_dependant_names} applies, restricted to
- * the survivors because that helper scans the whole installed set).
- * Rescans until no new orphan appears so a chain of orphans — libB
- * orphaned, then the libC only libB ever needed — is collected whole.
- */
- private Vector<Usm.CachedPackage> remove_collect_orphans(Dictionary<string, Usm.CachedPackage> by_package_name,
- Vector<Usm.CachedPackage> closure) throws Error {
- // Manifests, dependency refs and explicit marks are read once up
- // front: the fixpoint loop below only consults these tables
- var manifests = new Dictionary<string, Usm.Manifest>();
- var refs_by_name = new Dictionary<string, HashSet<Usm.ResourceRef>>();
- var implicit_names = new HashSet<string>();
- foreach(var pair in by_package_name) {
- var manifest = pair.value.get_manifest();
- manifests.set(pair.key, manifest);
- refs_by_name.set(pair.key, remove_dependency_refs(manifest));
- if(!remove_explicitly_installed(pair.value)) {
- implicit_names.add(pair.key);
- }
- }
- var gone = new HashSet<string>();
- foreach(var package in closure) {
- gone.add(package.package_name);
- }
- var orphans = new Vector<Usm.CachedPackage>();
- var collected = true;
- while(collected) {
- collected = false;
- foreach(var name in implicit_names) {
- if(gone.contains(name)) {
- continue;
- }
- var provides = manifests[name].provides.select<Usm.ResourceRef>(p => p.key);
- var needed = false;
- foreach(var pair in refs_by_name) {
- if(pair.key == name || gone.contains(pair.key)) {
- continue;
- }
- if(pair.value.any(d => provides.any(p => d.satisfied_by(p)))) {
- needed = true;
- break;
- }
- }
- if(needed) {
- continue;
- }
- gone.add(name);
- orphans.add(by_package_name[name]);
- collected = true;
- }
- }
- return orphans;
- }
- /**
- * Every dependency ref of {@link manifest} across all phases, flat plus
- * the union of all candidate groups of grouped phases.
- */
- private HashSet<Usm.ResourceRef> remove_dependency_refs(Usm.Manifest manifest) {
- var refs = new HashSet<Usm.ResourceRef>();
- refs.union_with(manifest.dependencies.runtime.all_refs());
- refs.union_with(manifest.dependencies.build.all_refs());
- refs.union_with(manifest.dependencies.manage.all_refs());
- if(manifest.dependencies.acquire != null) {
- refs.union_with(manifest.dependencies.acquire.all_refs());
- }
- return refs;
- }
- /**
- * The package's explicit-install mark; a missing or unreadable origin
- * record means the package predates the field, whose default is false —
- * the same value the record itself would carry.
- */
- private bool remove_explicitly_installed(Usm.CachedPackage package) {
- try {
- return package.get_origin_information().explicitly_installed;
- }
- catch(Error e) {
- return false;
- }
- }
- private int remove_usage() {
- printerr("USAGE:\n\tusm remove [-y|--yes] <packages>\n");
- return 255;
- }
|