Remove.vala 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. using Invercargill;
  2. using Invercargill.DataStructures;
  3. /**
  4. * `usm remove [-y|--yes] <packages>` — remove installed packages and,
  5. * by cascade, every installed package that transitively depends on any
  6. * of them (discovered through {@link Usm.SystemState.find_dependant_names},
  7. * so the summary shows the full closure before anything happens). All
  8. * requested cascades merge into one plan and one confirm.
  9. *
  10. * The plan then gains every orphaned dependency: a remaining package
  11. * installed implicitly ({@link Usm.OriginInformation.explicitly_installed}
  12. * false — a resolved dependency, or a record from before the field
  13. * existed) that no package surviving the removal depends on. Orphans
  14. * appear under their own "remove (orphaned)" label in the summary, and
  15. * nothing is removed without confirmation.
  16. *
  17. * After the transaction each removed package's state directory —
  18. * sources, build cache, `installed/` entry — is deleted; OTHER versions'
  19. * directories (the siblings updates keep for downgrade) remain.
  20. */
  21. private int remove_main(string[] args) {
  22. var assume_yes = false;
  23. var package_names = new Vector<string>();
  24. for(int i = 2; i < args.length; i++) {
  25. var argument = args[i];
  26. if(argument == "-y" || argument == "--yes") {
  27. assume_yes = true;
  28. }
  29. else if(argument.has_prefix("-")) {
  30. printerr(@"Unknown option \"$argument\"\n");
  31. return remove_usage();
  32. }
  33. else {
  34. package_names.add(argument);
  35. }
  36. }
  37. if(package_names.length == 0) {
  38. return remove_usage();
  39. }
  40. Usm.SystemState state = null;
  41. try {
  42. state = new Usm.SystemState(paths);
  43. }
  44. catch(Error e) {
  45. printerr(@"This system is not managed by usm: $(e.message)\n");
  46. return 245;
  47. }
  48. try {
  49. var by_package_name = new Dictionary<string, Usm.CachedPackage>();
  50. foreach(var installed in state.get_installed_packages()) {
  51. by_package_name.set(installed.package_name, state.resolve_installed(installed));
  52. }
  53. // One shared visited set across every requested target, so a
  54. // package reached from two cascades is removed once
  55. var closure = new Vector<Usm.CachedPackage>();
  56. var visited = new HashSet<string>();
  57. foreach(var package_name in package_names) {
  58. var target = state.find_installed(package_name);
  59. if(target == null) {
  60. printerr(@"No installed package named \"$package_name\"\n");
  61. return 254;
  62. }
  63. remove_collect_cascade(state, by_package_name, visited, closure, target);
  64. }
  65. var orphans = remove_collect_orphans(by_package_name, closure);
  66. var to_remove = new HashSet<Usm.CachedPackage>();
  67. to_remove.union_with(closure);
  68. to_remove.union_with(orphans);
  69. var transaction = new Usm.Transaction() {
  70. paths = paths,
  71. resource_finder = new Usm.ResourceFinder(paths),
  72. to_remove = to_remove,
  73. state = state
  74. };
  75. // Planning before the prompt lets the removal order (dependents
  76. // before providers, orphans last) be computed and shown
  77. try {
  78. transaction.strategise();
  79. }
  80. catch(Usm.TransactionError e) {
  81. printerr(@"$(e.message)\n");
  82. return 240;
  83. }
  84. var summary = new Usm.TransactionSummary() {
  85. to_remove = closure,
  86. orphaned_removals = orphans
  87. };
  88. if(!Usm.Cli.confirm_transaction(summary, assume_yes)) {
  89. print("Aborted.\n");
  90. return 1;
  91. }
  92. printerr("\nRunning transaction...\n");
  93. transaction.progress_updated.connect(transaction.print_progress);
  94. transaction.run();
  95. // The state directories go only after a successful transaction;
  96. // other versions' directories are never touched
  97. var removed_packages = closure.concat(orphans);
  98. foreach(var package in removed_packages) {
  99. Usm.Util.delete_tree(package.state_path);
  100. }
  101. print(@"Removed $(closure.length + orphans.length) package(s) ($(removed_packages.to_string(p => p.package_name, ", ")))\n");
  102. }
  103. catch(Error e) {
  104. printerr(@"Error: $(e.message)\n");
  105. return 238;
  106. }
  107. return 0;
  108. }
  109. /**
  110. * Depth-first reverse-dependency closure: {@link package} plus every
  111. * installed package that transitively depends on it, appended to
  112. * {@link closure} target-first. {@link visited} keys on the versioned
  113. * cache-directory name so shared dependants collapse to one removal.
  114. */
  115. private void remove_collect_cascade(Usm.SystemState state, Dictionary<string, Usm.CachedPackage> by_package_name,
  116. HashSet<string> visited, Vector<Usm.CachedPackage> closure, Usm.CachedPackage package) throws Error {
  117. if(visited.contains(package.package_name)) {
  118. return;
  119. }
  120. visited.add(package.package_name);
  121. closure.add(package);
  122. foreach(var dependant_name in state.find_dependant_names(package)) {
  123. Usm.CachedPackage dependant;
  124. if(by_package_name.try_get(dependant_name, out dependant)) {
  125. remove_collect_cascade(state, by_package_name, visited, closure, dependant);
  126. }
  127. }
  128. }
  129. /**
  130. * Orphan scan over the packages {@link closure} leaves behind: every
  131. * implicitly installed survivor that no other survivor depends on,
  132. * where dependency means any runtime/build/manage/acquire ref matched
  133. * against the candidate's provides (the same resource-level matching
  134. * {@link Usm.SystemState.find_dependant_names} applies, restricted to
  135. * the survivors because that helper scans the whole installed set).
  136. * Rescans until no new orphan appears so a chain of orphans — libB
  137. * orphaned, then the libC only libB ever needed — is collected whole.
  138. */
  139. private Vector<Usm.CachedPackage> remove_collect_orphans(Dictionary<string, Usm.CachedPackage> by_package_name,
  140. Vector<Usm.CachedPackage> closure) throws Error {
  141. // Manifests, dependency refs and explicit marks are read once up
  142. // front: the fixpoint loop below only consults these tables
  143. var manifests = new Dictionary<string, Usm.Manifest>();
  144. var refs_by_name = new Dictionary<string, HashSet<Usm.ResourceRef>>();
  145. var implicit_names = new HashSet<string>();
  146. foreach(var pair in by_package_name) {
  147. var manifest = pair.value.get_manifest();
  148. manifests.set(pair.key, manifest);
  149. refs_by_name.set(pair.key, remove_dependency_refs(manifest));
  150. if(!remove_explicitly_installed(pair.value)) {
  151. implicit_names.add(pair.key);
  152. }
  153. }
  154. var gone = new HashSet<string>();
  155. foreach(var package in closure) {
  156. gone.add(package.package_name);
  157. }
  158. var orphans = new Vector<Usm.CachedPackage>();
  159. var collected = true;
  160. while(collected) {
  161. collected = false;
  162. foreach(var name in implicit_names) {
  163. if(gone.contains(name)) {
  164. continue;
  165. }
  166. var provides = manifests[name].provides.select<Usm.ResourceRef>(p => p.key);
  167. var needed = false;
  168. foreach(var pair in refs_by_name) {
  169. if(pair.key == name || gone.contains(pair.key)) {
  170. continue;
  171. }
  172. if(pair.value.any(d => provides.any(p => d.satisfied_by(p)))) {
  173. needed = true;
  174. break;
  175. }
  176. }
  177. if(needed) {
  178. continue;
  179. }
  180. gone.add(name);
  181. orphans.add(by_package_name[name]);
  182. collected = true;
  183. }
  184. }
  185. return orphans;
  186. }
  187. /**
  188. * Every dependency ref of {@link manifest} across all phases, flat plus
  189. * the union of all candidate groups of grouped phases.
  190. */
  191. private HashSet<Usm.ResourceRef> remove_dependency_refs(Usm.Manifest manifest) {
  192. var refs = new HashSet<Usm.ResourceRef>();
  193. refs.union_with(manifest.dependencies.runtime.all_refs());
  194. refs.union_with(manifest.dependencies.build.all_refs());
  195. refs.union_with(manifest.dependencies.manage.all_refs());
  196. if(manifest.dependencies.acquire != null) {
  197. refs.union_with(manifest.dependencies.acquire.all_refs());
  198. }
  199. return refs;
  200. }
  201. /**
  202. * The package's explicit-install mark; a missing or unreadable origin
  203. * record means the package predates the field, whose default is false —
  204. * the same value the record itself would carry.
  205. */
  206. private bool remove_explicitly_installed(Usm.CachedPackage package) {
  207. try {
  208. return package.get_origin_information().explicitly_installed;
  209. }
  210. catch(Error e) {
  211. return false;
  212. }
  213. }
  214. private int remove_usage() {
  215. printerr("USAGE:\n\tusm remove [-y|--yes] <packages>\n");
  216. return 255;
  217. }