Remove.vala 8.7 KB

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