|
@@ -7,6 +7,13 @@ using Invercargill.DataStructures;
|
|
|
* (discovered through {@link Usm.SystemState.find_dependant_names}, so
|
|
* (discovered through {@link Usm.SystemState.find_dependant_names}, so
|
|
|
* the summary shows the full closure before anything happens).
|
|
* the summary shows the full closure before anything happens).
|
|
|
*
|
|
*
|
|
|
|
|
+ * 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 —
|
|
* After the transaction each removed package's state directory —
|
|
|
* sources, build cache, `installed/` entry — is deleted; OTHER versions'
|
|
* sources, build cache, `installed/` entry — is deleted; OTHER versions'
|
|
|
* directories (the siblings updates keep for downgrade) remain.
|
|
* directories (the siblings updates keep for downgrade) remain.
|
|
@@ -59,8 +66,11 @@ private int remove_main(string[] args) {
|
|
|
var closure = new Vector<Usm.CachedPackage>();
|
|
var closure = new Vector<Usm.CachedPackage>();
|
|
|
remove_collect_cascade(state, by_package_name, new HashSet<string>(), closure, target);
|
|
remove_collect_cascade(state, by_package_name, new HashSet<string>(), closure, target);
|
|
|
|
|
|
|
|
|
|
+ var orphans = remove_collect_orphans(by_package_name, closure);
|
|
|
|
|
+
|
|
|
var to_remove = new HashSet<Usm.CachedPackage>();
|
|
var to_remove = new HashSet<Usm.CachedPackage>();
|
|
|
to_remove.union_with(closure);
|
|
to_remove.union_with(closure);
|
|
|
|
|
+ to_remove.union_with(orphans);
|
|
|
|
|
|
|
|
var transaction = new Usm.Transaction() {
|
|
var transaction = new Usm.Transaction() {
|
|
|
paths = paths,
|
|
paths = paths,
|
|
@@ -70,7 +80,7 @@ private int remove_main(string[] args) {
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
// Planning before the prompt lets the removal order (dependents
|
|
// Planning before the prompt lets the removal order (dependents
|
|
|
- // before providers) be computed and shown
|
|
|
|
|
|
|
+ // before providers, orphans last) be computed and shown
|
|
|
try {
|
|
try {
|
|
|
transaction.strategise();
|
|
transaction.strategise();
|
|
|
}
|
|
}
|
|
@@ -80,7 +90,8 @@ private int remove_main(string[] args) {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
var summary = new Usm.TransactionSummary() {
|
|
var summary = new Usm.TransactionSummary() {
|
|
|
- to_remove = closure
|
|
|
|
|
|
|
+ to_remove = closure,
|
|
|
|
|
+ orphaned_removals = orphans
|
|
|
};
|
|
};
|
|
|
if(!Usm.Cli.confirm_transaction(summary, assume_yes)) {
|
|
if(!Usm.Cli.confirm_transaction(summary, assume_yes)) {
|
|
|
print("Aborted.\n");
|
|
print("Aborted.\n");
|
|
@@ -93,10 +104,11 @@ private int remove_main(string[] args) {
|
|
|
|
|
|
|
|
// The state directories go only after a successful transaction;
|
|
// The state directories go only after a successful transaction;
|
|
|
// other versions' directories are never touched
|
|
// other versions' directories are never touched
|
|
|
- foreach(var package in closure) {
|
|
|
|
|
|
|
+ var removed_packages = closure.concat(orphans);
|
|
|
|
|
+ foreach(var package in removed_packages) {
|
|
|
Usm.Util.delete_tree(package.state_path);
|
|
Usm.Util.delete_tree(package.state_path);
|
|
|
}
|
|
}
|
|
|
- print(@"Removed $(closure.length) package(s) ($(closure.to_string(p => p.package_name, ", ")))\n");
|
|
|
|
|
|
|
+ print(@"Removed $(closure.length + orphans.length) package(s) ($(removed_packages.to_string(p => p.package_name, ", ")))\n");
|
|
|
}
|
|
}
|
|
|
catch(Error e) {
|
|
catch(Error e) {
|
|
|
printerr(@"Error: $(e.message)\n");
|
|
printerr(@"Error: $(e.message)\n");
|
|
@@ -128,6 +140,96 @@ private void remove_collect_cascade(Usm.SystemState state, Dictionary<string, Us
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+/**
|
|
|
|
|
+ * 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() {
|
|
private int remove_usage() {
|
|
|
printerr("USAGE:\n\tusm remove [-y|--yes] <package>\n");
|
|
printerr("USAGE:\n\tusm remove [-y|--yes] <package>\n");
|
|
|
return 255;
|
|
return 255;
|