using Invercargill; using Invercargill.DataStructures; /** * `usm remove [-y|--yes] ` — 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(); 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(); } 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(); 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(); var visited = new HashSet(); 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(); 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(transaction.print_progress); transaction.run(); // 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) { 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 by_package_name, HashSet visited, Vector 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 remove_collect_orphans(Dictionary by_package_name, Vector 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(); var refs_by_name = new Dictionary>(); var implicit_names = new HashSet(); 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(); foreach(var package in closure) { gone.add(package.package_name); } var orphans = new Vector(); var collected = true; while(collected) { collected = false; foreach(var name in implicit_names) { if(gone.contains(name)) { continue; } var provides = manifests[name].provides.select(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 remove_dependency_refs(Usm.Manifest manifest) { var refs = new HashSet(); 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] \n"); return 255; }