Repository: spack/spack

Issue:
Spack emits warnings when uninstalling
### Steps to reproduce

Install a fake hdf5 spec:
```console
% spack install --fake hdf5~mpi
```
then try to uninstall everything:
```
% spack uninstall -ay
```

### Error message

It seems a few specs are uninstalled after their dependencies:
```console
% spack uninstall -ay          
==> Successfully uninstalled hdf5@1.12.2%apple-clang@13.1.6~cxx~fortran~hl~ipo~java~mpi+shared~szip~threadsafe+tools api=default build_type=RelWithDebInfo arch=darwin-monterey-cannonlake/caxxhht
==> Successfully uninstalled perl@5.34.1%apple-clang@13.1.6+cpanm+shared+threads arch=darwin-monterey-cannonlake/l4d3tqx
==> Successfully uninstalled diffutils@3.8%apple-clang@13.1.6 arch=darwin-monterey-cannonlake/677cyo3
==> Successfully uninstalled cmake@3.23.2%apple-clang@13.1.6~doc+ncurses+ownlibs~qt build_type=Release arch=darwin-monterey-cannonlake/tq6p7gg
==> Successfully uninstalled gdbm@1.19%apple-clang@13.1.6 arch=darwin-monterey-cannonlake/4ybhqy2
==> Successfully uninstalled pkgconf@1.8.0%apple-clang@13.1.6 arch=darwin-monterey-cannonlake/fmbswuq
==> Successfully uninstalled libiconv@1.16%apple-clang@13.1.6 libs=shared,static arch=darwin-monterey-cannonlake/jccfr3e
==> Successfully uninstalled berkeley-db@18.1.40%apple-clang@13.1.6+cxx~docs+stl patches=b231fcc arch=darwin-monterey-cannonlake/emmgknu
==> Successfully uninstalled openssl@1.1.1p%apple-clang@13.1.6~docs~shared certs=mozilla arch=darwin-monterey-cannonlake/qgrwq3g
==> Warning: Inconsistent state! Dependent l4d3tqxkjeemrtchiti6styj6uvf7edg of c2ltv4o2apqzkwoddn2chb5so247yjw6 not in DB
==> Successfully uninstalled bzip2@1.0.8%apple-clang@13.1.6~debug~pic+shared arch=darwin-monterey-cannonlake/c2ltv4o
==> Warning: Inconsistent state! Dependent 4ybhqy26jqehdr24exjfx2kgxgbs2guu of 4brdp3xifvsyf7lvxw64osk5flfft7g2 not in DB
==> Warning: Inconsistent state! Dependent l4d3tqxkjeemrtchiti6styj6uvf7edg of 4brdp3xifvsyf7lvxw64osk5flfft7g2 not in DB
==> Successfully uninstalled readline@8.1%apple-clang@13.1.6 arch=darwin-monterey-cannonlake/4brdp3x
==> Warning: Inconsistent state! Dependent l4d3tqxkjeemrtchiti6styj6uvf7edg of idlxgon6vnmzvr4mdr6tgiatdsszurz6 not in DB
==> Successfully uninstalled zlib@1.2.12%apple-clang@13.1.6+optimize+pic+shared patches=0d38234 arch=darwin-monterey-cannonlake/idlxgon
==> Successfully uninstalled ca-certificates-mozilla@2022-03-29%apple-clang@13.1.6 arch=darwin-monterey-cannonlake/obbwdew
==> Successfully uninstalled ncurses@6.2%apple-clang@13.1.6~symlinks+termlib abi=none arch=darwin-monterey-cannonlake/p7kdu65
```

### Information on your system

* **Spack:** 0.19.0.dev0 (58b144c0dae88d6a06d3d138c077c4c70909c5f7)
* **Python:** 3.8.9
* **Platform:** darwin-monterey-cannonlake
* **Concretizer:** clingo

### General information

- [X] I have run `spack debug report` and reported the version of Spack/Python/Platform
- [X] I have searched the issues of this repo and believe this is not a duplicate
- [X] I have run the failing commands in debug mode and reported the output


Discussion:
@becker33 did you mention a PR that fixed this? This bug isn't that severe, but it is really annoying and visible.
This should be solving the issue #33817 

Resolving patch:
diff --git a/lib/spack/spack/cmd/gc.py b/lib/spack/spack/cmd/gc.py
--- a/lib/spack/spack/cmd/gc.py
+++ b/lib/spack/spack/cmd/gc.py
@@ -43,4 +43,4 @@ def gc(parser, args):
     if not args.yes_to_all:
         spack.cmd.uninstall.confirm_removal(specs)
 
-    spack.cmd.uninstall.do_uninstall(None, specs, force=False)
+    spack.cmd.uninstall.do_uninstall(specs, force=False)
diff --git a/lib/spack/spack/cmd/uninstall.py b/lib/spack/spack/cmd/uninstall.py
--- a/lib/spack/spack/cmd/uninstall.py
+++ b/lib/spack/spack/cmd/uninstall.py
@@ -230,54 +230,21 @@ def _remove_from_env(spec, env):
         pass  # ignore non-root specs
 
 
-def do_uninstall(env, specs, force):
-    """Uninstalls all the specs in a list.
-
-    Args:
-        env (spack.environment.Environment or None): active environment, or ``None``
-            if there is not one
-        specs (list): list of specs to be uninstalled
-        force (bool): force uninstallation (boolean)
-    """
-    packages = []
-    for item in specs:
-        try:
-            # should work if package is known to spack
-            packages.append(item.package)
-        except spack.repo.UnknownEntityError:
-            # The package.py file has gone away -- but still
-            # want to uninstall.
-            spack.package_base.PackageBase.uninstall_by_spec(item, force=True)
-
-    # A package is ready to be uninstalled when nothing else references it,
-    # unless we are requested to force uninstall it.
-    def is_ready(dag_hash):
-        if force:
-            return True
-
-        record = spack.store.db.query_local_by_spec_hash(dag_hash)
-        if not record.ref_count:
-            return True
-
-        # If this spec is only used as a build dependency, we can uninstall
-        return all(
-            dspec.deptypes == ("build",) or not dspec.parent.installed
-            for dspec in record.spec.edges_from_dependents()
-        )
-
-    while packages:
-        ready = [x for x in packages if is_ready(x.spec.dag_hash())]
-        if not ready:
-            msg = (
-                "unexpected error [cannot proceed uninstalling specs with"
-                " remaining link or run dependents {0}]"
-            )
-            msg = msg.format(", ".join(x.name for x in packages))
-            raise spack.error.SpackError(msg)
-
-        packages = [x for x in packages if x not in ready]
-        for item in ready:
-            item.do_uninstall(force=force)
+def do_uninstall(specs, force=False):
+    # TODO: get rid of the call-sites that use this function,
+    # so that we don't have to do a dance of list -> set -> list -> set
+    hashes_to_remove = set(s.dag_hash() for s in specs)
+
+    for s in traverse.traverse_nodes(
+        specs,
+        order="topo",
+        direction="children",
+        root=True,
+        cover="nodes",
+        deptype="all",
+    ):
+        if s.dag_hash() in hashes_to_remove:
+            spack.package_base.PackageBase.uninstall_by_spec(s, force=force)
 
 
 def get_uninstall_list(args, specs, env):
@@ -419,7 +386,7 @@ def uninstall_specs(args, specs):
         confirm_removal(uninstall_list)
 
     # Uninstall everything on the list
-    do_uninstall(env, uninstall_list, args.force)
+    do_uninstall(uninstall_list, args.force)
 
     if env:
         with env.write_transaction():


Test patch: