From 09eb86e077d1273412862d773c169e03822adb82 Mon Sep 17 00:00:00 2001 From: Harmen Stoppels Date: Thu, 16 Feb 2023 14:26:30 +0100 Subject: spack uninstall: follow run/link edges on --dependents (#34058) `spack gc` removes build deps of explicitly installed specs, but somehow if you take one of the specs that `spack gc` would remove, and feed it to `spack uninstall /` by hash, it complains about all the dependents that still rely on it. This resolves the inconsistency by only following run/link type deps in spack uninstall. That way you can finally do `spack uninstall cmake` without having to remove all packages built with cmake. --- lib/spack/spack/cmd/uninstall.py | 8 +-- lib/spack/spack/test/cmd/uninstall.py | 78 +++++++++++----------- .../packages/diamond-link-bottom/package.py | 15 +++++ .../packages/diamond-link-left/package.py | 17 +++++ .../packages/diamond-link-right/package.py | 17 +++++ .../packages/diamond-link-top/package.py | 18 +++++ 6 files changed, 111 insertions(+), 42 deletions(-) create mode 100644 var/spack/repos/builtin.mock/packages/diamond-link-bottom/package.py create mode 100644 var/spack/repos/builtin.mock/packages/diamond-link-left/package.py create mode 100644 var/spack/repos/builtin.mock/packages/diamond-link-right/package.py create mode 100644 var/spack/repos/builtin.mock/packages/diamond-link-top/package.py diff --git a/lib/spack/spack/cmd/uninstall.py b/lib/spack/spack/cmd/uninstall.py index 000ec8a470..869ab88f62 100644 --- a/lib/spack/spack/cmd/uninstall.py +++ b/lib/spack/spack/cmd/uninstall.py @@ -133,7 +133,7 @@ def find_matching_specs(env, specs, allow_multiple_matches=False, force=False, o return specs_from_cli -def installed_dependents(specs, env): +def installed_runtime_dependents(specs, env): """Map each spec to a list of its installed dependents. Args: @@ -160,10 +160,10 @@ def installed_dependents(specs, env): for spec in specs: for dpt in traverse.traverse_nodes( - spec.dependents(deptype="all"), + spec.dependents(deptype=("link", "run")), direction="parents", visited=visited, - deptype="all", + deptype=("link", "run"), root=True, key=lambda s: s.dag_hash(), ): @@ -265,7 +265,7 @@ def get_uninstall_list(args, specs, env): # args.all takes care of the case where '-a' is given in the cli base_uninstall_specs = set(find_matching_specs(env, specs, args.all, args.force)) - active_dpts, outside_dpts = installed_dependents(base_uninstall_specs, env) + active_dpts, outside_dpts = installed_runtime_dependents(base_uninstall_specs, env) # It will be useful to track the unified set of specs with dependents, as # well as to separately track specs in the current env with dependents spec_to_dpts = {} diff --git a/lib/spack/spack/test/cmd/uninstall.py b/lib/spack/spack/test/cmd/uninstall.py index bd1e8a1b49..a4b6410ef8 100644 --- a/lib/spack/spack/test/cmd/uninstall.py +++ b/lib/spack/spack/test/cmd/uninstall.py @@ -50,15 +50,17 @@ def test_correct_installed_dependents(mutable_database): callpath = spack.store.db.query_local("callpath")[0] # Ensure it still has dependents and dependencies - dependents = callpath.dependents(deptype="all") - dependencies = callpath.dependencies(deptype="all") + dependents = callpath.dependents(deptype=("run", "link")) + dependencies = callpath.dependencies(deptype=("run", "link")) assert dependents and dependencies # Uninstall it, so it's missing. callpath.package.do_uninstall(force=True) # Retrieve all dependent hashes - inside_dpts, outside_dpts = spack.cmd.uninstall.installed_dependents(dependencies, None) + inside_dpts, outside_dpts = spack.cmd.uninstall.installed_runtime_dependents( + dependencies, None + ) dependent_hashes = [s.dag_hash() for s in itertools.chain(*outside_dpts.values())] set_dependent_hashes = set(dependent_hashes) @@ -213,9 +215,9 @@ class TestUninstallFromEnv(object): """Tests an installation with two environments e1 and e2, which each have shared package installations: - e1 has dt-diamond-left -> dt-diamond-bottom + e1 has diamond-link-left -> diamond-link-bottom - e2 has dt-diamond-right -> dt-diamond-bottom + e2 has diamond-link-right -> diamond-link-bottom """ env = SpackCommand("env") @@ -230,16 +232,16 @@ class TestUninstallFromEnv(object): TestUninstallFromEnv.env("create", "e1") e1 = spack.environment.read("e1") with e1: - TestUninstallFromEnv.add("dt-diamond-left") - TestUninstallFromEnv.add("dt-diamond-bottom") + TestUninstallFromEnv.add("diamond-link-left") + TestUninstallFromEnv.add("diamond-link-bottom") TestUninstallFromEnv.concretize() install("--fake") TestUninstallFromEnv.env("create", "e2") e2 = spack.environment.read("e2") with e2: - TestUninstallFromEnv.add("dt-diamond-right") - TestUninstallFromEnv.add("dt-diamond-bottom") + TestUninstallFromEnv.add("diamond-link-right") + TestUninstallFromEnv.add("diamond-link-bottom") TestUninstallFromEnv.concretize() install("--fake") @@ -251,47 +253,47 @@ class TestUninstallFromEnv(object): assert concretized_spec.package.installed def test_uninstall_force_dependency_shared_between_envs(self, environment_setup): - """If you "spack uninstall -f --dependents dt-diamond-bottom" from + """If you "spack uninstall -f --dependents diamond-link-bottom" from e1, then all packages should be uninstalled (but not removed) from both e1 and e2. """ e1 = spack.environment.read("e1") with e1: - uninstall("-f", "-y", "--dependents", "dt-diamond-bottom") + uninstall("-f", "-y", "--dependents", "diamond-link-bottom") # The specs should still be in the environment, since # --remove was not specified assert set(root.name for (root, _) in e1.concretized_specs()) == set( - ["dt-diamond-left", "dt-diamond-bottom"] + ["diamond-link-left", "diamond-link-bottom"] ) for _, concretized_spec in e1.concretized_specs(): assert not concretized_spec.package.installed - # Everything in e2 depended on dt-diamond-bottom, so should also + # Everything in e2 depended on diamond-link-bottom, so should also # have been uninstalled. The roots should be unchanged though. e2 = spack.environment.read("e2") with e2: assert set(root.name for (root, _) in e2.concretized_specs()) == set( - ["dt-diamond-right", "dt-diamond-bottom"] + ["diamond-link-right", "diamond-link-bottom"] ) for _, concretized_spec in e2.concretized_specs(): assert not concretized_spec.package.installed def test_uninstall_remove_dependency_shared_between_envs(self, environment_setup): - """If you "spack uninstall --dependents --remove dt-diamond-bottom" from + """If you "spack uninstall --dependents --remove diamond-link-bottom" from e1, then all packages are removed from e1 (it is now empty); - dt-diamond-left is also uninstalled (since only e1 needs it) but - dt-diamond-bottom is not uninstalled (since e2 needs it). + diamond-link-left is also uninstalled (since only e1 needs it) but + diamond-link-bottom is not uninstalled (since e2 needs it). """ e1 = spack.environment.read("e1") with e1: dtdiamondleft = next( concrete for (_, concrete) in e1.concretized_specs() - if concrete.name == "dt-diamond-left" + if concrete.name == "diamond-link-left" ) - output = uninstall("-y", "--dependents", "--remove", "dt-diamond-bottom") + output = uninstall("-y", "--dependents", "--remove", "diamond-link-bottom") assert "The following specs will be removed but not uninstalled" in output assert not list(e1.roots()) assert not dtdiamondleft.package.installed @@ -301,32 +303,32 @@ class TestUninstallFromEnv(object): e2 = spack.environment.read("e2") with e2: assert set(root.name for (root, _) in e2.concretized_specs()) == set( - ["dt-diamond-right", "dt-diamond-bottom"] + ["diamond-link-right", "diamond-link-bottom"] ) for _, concretized_spec in e2.concretized_specs(): assert concretized_spec.package.installed def test_uninstall_dependency_shared_between_envs_fail(self, environment_setup): - """If you "spack uninstall --dependents dt-diamond-bottom" from + """If you "spack uninstall --dependents diamond-link-bottom" from e1 (without --remove or -f), then this should fail (this is needed by e2). """ e1 = spack.environment.read("e1") with e1: - output = uninstall("-y", "--dependents", "dt-diamond-bottom", fail_on_error=False) + output = uninstall("-y", "--dependents", "diamond-link-bottom", fail_on_error=False) assert "There are still dependents." in output assert "use `spack env remove`" in output # The environment should be unchanged and nothing should have been # uninstalled assert set(root.name for (root, _) in e1.concretized_specs()) == set( - ["dt-diamond-left", "dt-diamond-bottom"] + ["diamond-link-left", "diamond-link-bottom"] ) for _, concretized_spec in e1.concretized_specs(): assert concretized_spec.package.installed def test_uninstall_force_and_remove_dependency_shared_between_envs(self, environment_setup): - """If you "spack uninstall -f --dependents --remove dt-diamond-bottom" from + """If you "spack uninstall -f --dependents --remove diamond-link-bottom" from e1, then all packages should be uninstalled and removed from e1. All packages will also be uninstalled from e2, but the roots will remain unchanged. @@ -336,53 +338,53 @@ class TestUninstallFromEnv(object): dtdiamondleft = next( concrete for (_, concrete) in e1.concretized_specs() - if concrete.name == "dt-diamond-left" + if concrete.name == "diamond-link-left" ) - uninstall("-f", "-y", "--dependents", "--remove", "dt-diamond-bottom") + uninstall("-f", "-y", "--dependents", "--remove", "diamond-link-bottom") assert not list(e1.roots()) assert not dtdiamondleft.package.installed e2 = spack.environment.read("e2") with e2: assert set(root.name for (root, _) in e2.concretized_specs()) == set( - ["dt-diamond-right", "dt-diamond-bottom"] + ["diamond-link-right", "diamond-link-bottom"] ) for _, concretized_spec in e2.concretized_specs(): assert not concretized_spec.package.installed def test_uninstall_keep_dependents_dependency_shared_between_envs(self, environment_setup): - """If you "spack uninstall -f --remove dt-diamond-bottom" from - e1, then dt-diamond-bottom should be uninstalled, which leaves + """If you "spack uninstall -f --remove diamond-link-bottom" from + e1, then diamond-link-bottom should be uninstalled, which leaves "dangling" references in both environments, since - dt-diamond-left and dt-diamond-right both need it. + diamond-link-left and diamond-link-right both need it. """ e1 = spack.environment.read("e1") with e1: dtdiamondleft = next( concrete for (_, concrete) in e1.concretized_specs() - if concrete.name == "dt-diamond-left" + if concrete.name == "diamond-link-left" ) - uninstall("-f", "-y", "--remove", "dt-diamond-bottom") - # dt-diamond-bottom was removed from the list of roots (note that - # it would still be installed since dt-diamond-left depends on it) - assert set(x.name for x in e1.roots()) == set(["dt-diamond-left"]) + uninstall("-f", "-y", "--remove", "diamond-link-bottom") + # diamond-link-bottom was removed from the list of roots (note that + # it would still be installed since diamond-link-left depends on it) + assert set(x.name for x in e1.roots()) == set(["diamond-link-left"]) assert dtdiamondleft.package.installed e2 = spack.environment.read("e2") with e2: assert set(root.name for (root, _) in e2.concretized_specs()) == set( - ["dt-diamond-right", "dt-diamond-bottom"] + ["diamond-link-right", "diamond-link-bottom"] ) dtdiamondright = next( concrete for (_, concrete) in e2.concretized_specs() - if concrete.name == "dt-diamond-right" + if concrete.name == "diamond-link-right" ) assert dtdiamondright.package.installed dtdiamondbottom = next( concrete for (_, concrete) in e2.concretized_specs() - if concrete.name == "dt-diamond-bottom" + if concrete.name == "diamond-link-bottom" ) assert not dtdiamondbottom.package.installed diff --git a/var/spack/repos/builtin.mock/packages/diamond-link-bottom/package.py b/var/spack/repos/builtin.mock/packages/diamond-link-bottom/package.py new file mode 100644 index 0000000000..b3f1640945 --- /dev/null +++ b/var/spack/repos/builtin.mock/packages/diamond-link-bottom/package.py @@ -0,0 +1,15 @@ +# Copyright 2013-2022 Lawrence Livermore National Security, LLC and other +# Spack Project Developers. See the top-level COPYRIGHT file for details. +# +# SPDX-License-Identifier: (Apache-2.0 OR MIT) + +from spack.package import * + + +class DiamondLinkBottom(Package): + """Part of diamond-link-{top,left,right,bottom} group""" + + homepage = "http://www.example.com" + url = "http://www.example.com/diamond-link-bottom-1.0.tar.gz" + + version("1.0", "0123456789abcdef0123456789abcdef") diff --git a/var/spack/repos/builtin.mock/packages/diamond-link-left/package.py b/var/spack/repos/builtin.mock/packages/diamond-link-left/package.py new file mode 100644 index 0000000000..6b78feec0a --- /dev/null +++ b/var/spack/repos/builtin.mock/packages/diamond-link-left/package.py @@ -0,0 +1,17 @@ +# Copyright 2013-2022 Lawrence Livermore National Security, LLC and other +# Spack Project Developers. See the top-level COPYRIGHT file for details. +# +# SPDX-License-Identifier: (Apache-2.0 OR MIT) + +from spack.package import * + + +class DiamondLinkLeft(Package): + """Part of diamond-link-{top,left,right,bottom} group""" + + homepage = "http://www.example.com" + url = "http://www.example.com/diamond-link-left-1.0.tar.gz" + + version("1.0", "0123456789abcdef0123456789abcdef") + + depends_on("diamond-link-bottom", type="link") diff --git a/var/spack/repos/builtin.mock/packages/diamond-link-right/package.py b/var/spack/repos/builtin.mock/packages/diamond-link-right/package.py new file mode 100644 index 0000000000..28e19f3d3d --- /dev/null +++ b/var/spack/repos/builtin.mock/packages/diamond-link-right/package.py @@ -0,0 +1,17 @@ +# Copyright 2013-2022 Lawrence Livermore National Security, LLC and other +# Spack Project Developers. See the top-level COPYRIGHT file for details. +# +# SPDX-License-Identifier: (Apache-2.0 OR MIT) + +from spack.package import * + + +class DiamondLinkRight(Package): + """Part of diamond-link-{top,left,right,bottom} group""" + + homepage = "http://www.example.com" + url = "http://www.example.com/diamond-link-right-1.0.tar.gz" + + version("1.0", "0123456789abcdef0123456789abcdef") + + depends_on("diamond-link-bottom", type="link") diff --git a/var/spack/repos/builtin.mock/packages/diamond-link-top/package.py b/var/spack/repos/builtin.mock/packages/diamond-link-top/package.py new file mode 100644 index 0000000000..eb8c6e5890 --- /dev/null +++ b/var/spack/repos/builtin.mock/packages/diamond-link-top/package.py @@ -0,0 +1,18 @@ +# Copyright 2013-2022 Lawrence Livermore National Security, LLC and other +# Spack Project Developers. See the top-level COPYRIGHT file for details. +# +# SPDX-License-Identifier: (Apache-2.0 OR MIT) + +from spack.package import * + + +class DiamondLinkTop(Package): + """Part of diamond-link-{top,left,right,bottom} group""" + + homepage = "http://www.example.com" + url = "http://www.example.com/diamond-link-top-1.0.tar.gz" + + version("1.0", "0123456789abcdef0123456789abcdef") + + depends_on("diamond-link-left", type="link") + depends_on("diamond-link-right", type="link") -- cgit v1.2.3-70-g09d2