Repository: spack/spack

Issue:
buildcache create does not work with uninstalled root specs of environment
`spack buildcache` tries to create a cache entry for non-installed root specs:

### Steps to reproduce the issue

```console
$ spack env create my_env
$ spack -e my_env install --only=dependencies perl
$ spack -e my_env buildcache create -d tmp
==> Error: perl does not match any installed packages.
==> Error: use one of the matching specs above
```

### Error Message

```console
# spack --debug --stacktrace -e ci buildcache create -d tmp
lib/spack/spack/cmd/__init__.py:122 ==> [2020-08-03-22:53:22.459456] Imported buildcache from built-in commands
lib/spack/spack/cmd/__init__.py:122 ==> [2020-08-03-22:53:22.461071] Imported buildcache from built-in commands
lib/spack/spack/config.py:805 ==> [2020-08-03-22:53:22.462045] Reading config file /opt/spack/var/spack/environments/ci/spack.yaml
lib/spack/spack/config.py:805 ==> [2020-08-03-22:53:22.472989] Reading config file /opt/spack/etc/spack/defaults/mirrors.yaml
lib/spack/spack/cmd/buildcache.py:361 ==> [2020-08-03-22:53:22.474214] Buildcache files will be output to file:///tmp/build_cache
lib/spack/spack/cmd/buildcache.py:253 ==> [2020-08-03-22:53:22.474733] find_matching_specs: about to parse specs for {perl}
lib/spack/spack/database.py:362 ==> [2020-08-03-22:53:22.475940] DATABASE LOCK TIMEOUT: 3s
lib/spack/spack/database.py:366 ==> [2020-08-03-22:53:22.476053] PACKAGE LOCK TIMEOUT: No timeout
lib/spack/spack/config.py:805 ==> [2020-08-03-22:53:22.477811] Reading config file /opt/spack/etc/spack/defaults/repos.yaml
lib/spack/spack/config.py:805 ==> [2020-08-03-22:53:22.479661] Reading config file /etc/spack/repos.yaml
lib/spack/spack/cmd/buildcache.py:268 ==> [2020-08-03-22:53:22.528319] Error: perl does not match any installed packages.
lib/spack/spack/cmd/buildcache.py:273 ==> [2020-08-03-22:53:22.528420] Error: use one of the matching specs above
Traceback (most recent call last):
  File "/opt/spack/lib/spack/spack/main.py", line 762, in main
    return _invoke_command(command, parser, args, unknown)
  File "/opt/spack/lib/spack/spack/main.py", line 490, in _invoke_command
    return_val = command(parser, args)
  File "/opt/spack/lib/spack/spack/cmd/buildcache.py", line 775, in buildcache
    args.func(args)
  File "/opt/spack/lib/spack/spack/cmd/buildcache.py", line 459, in createtarball
    rebuild_index=args.rebuild_index)
  File "/opt/spack/lib/spack/spack/cmd/buildcache.py", line 363, in _createtarball
    matches = find_matching_specs(pkgs, env=env)
  File "/opt/spack/lib/spack/spack/cmd/buildcache.py", line 273, in find_matching_specs
    tty.die('use one of the matching specs above')
  File "/opt/spack/lib/spack/llnl/util/tty/__init__.py", line 239, in die
    sys.exit(1)
SystemExit: 1
```

### Information on your system

* **Spack:** 0.15.3
* **Python:** 3.6.9
* **Platform:** linux-ubuntu18.04-zen2

### Additional 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:

Resolving patch:
diff --git a/lib/spack/spack/cmd/buildcache.py b/lib/spack/spack/cmd/buildcache.py
--- a/lib/spack/spack/cmd/buildcache.py
+++ b/lib/spack/spack/cmd/buildcache.py
@@ -239,8 +239,9 @@ def find_matching_specs(pkgs, allow_multiple_matches=False, env=None):
        concretized specs given from cli
 
     Args:
-        specs: list of specs to be matched against installed packages
-        allow_multiple_matches : if True multiple matches are admitted
+        pkgs (string): spec to be matched against installed packages
+        allow_multiple_matches (bool): if True multiple matches are admitted
+        env (Environment): active environment, or ``None`` if there is not one
 
     Return:
         list of specs
@@ -332,26 +333,25 @@ def _createtarball(env, spec_yaml=None, packages=None, add_spec=True,
                    signing_key=None, force=False, make_relative=False,
                    unsigned=False, allow_root=False, rebuild_index=False):
     if spec_yaml:
-        packages = set()
         with open(spec_yaml, 'r') as fd:
             yaml_text = fd.read()
             tty.debug('createtarball read spec yaml:')
             tty.debug(yaml_text)
             s = Spec.from_yaml(yaml_text)
-            packages.add('/{0}'.format(s.dag_hash()))
+            package = '/{0}'.format(s.dag_hash())
+            matches = find_matching_specs(package, env=env)
 
     elif packages:
-        packages = packages
+        matches = find_matching_specs(packages, env=env)
 
     elif env:
-        packages = env.concretized_user_specs
+        matches = [env.specs_by_hash[h] for h in env.concretized_order]
 
     else:
         tty.die("build cache file creation requires at least one" +
-                " installed package spec, an activate environment," +
+                " installed package spec, an active environment," +
                 " or else a path to a yaml file containing a spec" +
                 " to install")
-    pkgs = set(packages)
     specs = set()
 
     mirror = spack.mirror.MirrorCollection().lookup(output_location)
@@ -360,8 +360,6 @@ def _createtarball(env, spec_yaml=None, packages=None, add_spec=True,
     msg = 'Buildcache files will be output to %s/build_cache' % outdir
     tty.msg(msg)
 
-    matches = find_matching_specs(pkgs, env=env)
-
     if matches:
         tty.debug('Found at least one matching spec')
 
@@ -371,11 +369,16 @@ def _createtarball(env, spec_yaml=None, packages=None, add_spec=True,
             tty.debug('skipping external or virtual spec %s' %
                       match.format())
         else:
-            if add_spec:
+            lookup = spack.store.db.query_one(match)
+
+            if not add_spec:
+                tty.debug('skipping matching root spec %s' % match.format())
+            elif lookup is None:
+                tty.debug('skipping uninstalled matching spec %s' %
+                          match.format())
+            else:
                 tty.debug('adding matching spec %s' % match.format())
                 specs.add(match)
-            else:
-                tty.debug('skipping matching spec %s' % match.format())
 
             if not add_deps:
                 continue
@@ -388,9 +391,14 @@ def _createtarball(env, spec_yaml=None, packages=None, add_spec=True,
                 if d == 0:
                     continue
 
+                lookup = spack.store.db.query_one(node)
+
                 if node.external or node.virtual:
                     tty.debug('skipping external or virtual dependency %s' %
                               node.format())
+                elif lookup is None:
+                    tty.debug('skipping uninstalled depenendency %s' %
+                              node.format())
                 else:
                     tty.debug('adding dependency %s' % node.format())
                     specs.add(node)


Test patch: