Repository: spack/spack

Issue:
Recursive dependency resolution in database search
Certain commands (e.g. `spack find`, `spack location`) are unable to resolve installed packages when dependencies of dependencies are specified. In particular, I have several non-builtin packages: `parsec` optionally depend on an MPI implementation or on another library, `lci`, and `dplasma` depends on `parsec`.

Having installed `dplasma ^parsec transport=mpi ^openmpi` and `dplasma ^parsec transport=lci ^lci completion=am,sync`, I run into issues where Spack is unable to resolve the dependency when looking for installed packages:
```console
$ spack find -l dplasma
==> 2 installed packages
-- linux-centos7-haswell / gcc@7.2.0 ----------------------------
sz65j37 dplasma@local  spjdu6u dplasma@local
$ 
$ spack find -l dplasma ^openmpi
==> 1 installed packages
-- linux-centos7-haswell / gcc@7.2.0 ----------------------------
spjdu6u dplasma@local
$
$ spack find -l dplasma ^parsec ^openmpi
==> No package matches the query: dplasma ^parsec ^openmpi
```

### Steps to reproduce the issue

```console
$ spack install namd ^charmpp backend=mpi ^openmpi
$ spack install namd ^charmpp backend=ucx ^ucx
$
$ spack find namd ^openmpi
==> 1 installed packages
-- linux-centos7-haswell / gcc@7.2.0 ----------------------------
dplasma@2.14
$ spack find namd ^charmpp ^openmpi
==> No package matches the query 'namd ^charmpp ^openmpi'
```

(Note: I the packages I've specifically encountered this with aren't builtin ones, but I expect the same issue should occur with `namd` and many other packages.)

### Error Message

```console
$ spack --debug --stacktrace find -l dplasma ^parsec ^openmpi
lib/spack/spack/cmd/__init__.py:122 ==> [2020-09-05-17:19:23.540980] Imported find from built-in commands
lib/spack/spack/cmd/__init__.py:122 ==> [2020-09-05-17:19:23.542771] Imported find from built-in commands
lib/spack/spack/config.py:835 ==> [2020-09-05-17:19:23.611505] Reading config file /home/omor1/spack/etc/spack/defaults/config.yaml
lib/spack/spack/config.py:835 ==> [2020-09-05-17:19:23.633781] Reading config file /home/omor1/.spack/config.yaml
lib/spack/spack/database.py:362 ==> [2020-09-05-17:19:23.655744] DATABASE LOCK TIMEOUT: 3s
lib/spack/spack/database.py:366 ==> [2020-09-05-17:19:23.656217] PACKAGE LOCK TIMEOUT: No timeout
lib/spack/spack/config.py:835 ==> [2020-09-05-17:19:23.713785] Reading config file /home/omor1/spack/etc/spack/defaults/repos.yaml
lib/spack/spack/config.py:835 ==> [2020-09-05-17:19:23.717171] Reading config file /home/omor1/.spack/repos.yaml
lib/spack/spack/cmd/find.py:222 ==> [2020-09-05-17:19:24.764859] No package matches the query: dplasma ^parsec ^openmpi
```

### Information on your system

* **Spack:** 0.15.4-793-94142519d
* **Python:** 2.7.5
* **Platform:** linux-centos7-haswell

### Additional information

<!-- These boxes can be checked by replacing [ ] with [x] or by clicking them after submitting the issue. -->
- [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:
Pinging @adamjstewart and @scheibelp as requested.
This is a double-iteration bug in `spack.spec.Spec.satisfies_dependencies`:

```python
selfdeps = self.traverse(root=False)
otherdeps = other.traverse(root=False)
if not all(any(d.satisfies(dep, strict=True) for d in selfdeps)
           for dep in otherdeps):
    return False
```

For reference, this issue has existed since 8ae380fb71eefc288a532b4574cf53cec32f74e4.

Resolving patch:
diff --git a/lib/spack/spack/spec.py b/lib/spack/spack/spec.py
--- a/lib/spack/spack/spec.py
+++ b/lib/spack/spack/spec.py
@@ -3009,8 +3009,9 @@ def satisfies_dependencies(self, other, strict=False):
             if not self._dependencies:
                 return False
 
-            selfdeps = self.traverse(root=False)
-            otherdeps = other.traverse(root=False)
+            # use list to prevent double-iteration
+            selfdeps = list(self.traverse(root=False))
+            otherdeps = list(other.traverse(root=False))
             if not all(any(d.satisfies(dep, strict=True) for d in selfdeps)
                        for dep in otherdeps):
                 return False


Test patch:
diff --git a/lib/spack/spack/test/spec_semantics.py b/lib/spack/spack/test/spec_semantics.py
--- a/lib/spack/spack/test/spec_semantics.py
+++ b/lib/spack/spack/test/spec_semantics.py
@@ -1014,6 +1014,13 @@ def test_error_message_unknown_variant(self):
         with pytest.raises(UnknownVariantError, match=r'package has no such'):
             s.concretize()
 
+    @pytest.mark.regression('18527')
+    def test_satisfies_dependencies_ordered(self):
+        d = Spec('zmpi ^fake')
+        s = Spec('mpileaks')
+        s._add_dependency(d, ())
+        assert s.satisfies('mpileaks ^zmpi ^fake', strict=True)
+
 
 @pytest.mark.regression('3887')
 @pytest.mark.parametrize('spec_str', [