command: add spack find --json

This is another machine-readable version of `spack find`.  Supplying the
`--json` argument causes specs to be written out as json records,
easily filered with tools like jq.

e.g.:

    $ spack find --json python | jq -C ".[] | { name, version } "
    [
      {
        "name": "python",
        "version": "2.7.16"
      },
      {
        "name": "bzip2",
        "version": "1.0.8"
      }
    ]
This commit is contained in:
Todd Gamblin 2019-09-01 16:42:53 -07:00
parent 64af0a9874
commit 1a1f5674df
4 changed files with 52 additions and 4 deletions

View file

@ -21,6 +21,7 @@
import spack.paths import spack.paths
import spack.spec import spack.spec
import spack.store import spack.store
import spack.util.spack_json as sjson
from spack.error import SpackError from spack.error import SpackError
@ -214,6 +215,24 @@ def display_formatted_specs(specs, format_string, deps=False):
print(" " * depth, dep.format(format_string)) print(" " * depth, dep.format(format_string))
def display_specs_as_json(specs, deps=False):
"""Convert specs to a list of json records."""
seen = set()
records = []
for spec in specs:
if spec.dag_hash() not in seen:
seen.add(spec.dag_hash())
records.append(spec.to_record_dict())
if deps:
for dep in spec.traverse():
if dep.dag_hash() not in seen:
seen.add(spec.dag_hash())
records.append(dep.to_record_dict())
sjson.dump(records, sys.stdout)
def display_specs(specs, args=None, **kwargs): def display_specs(specs, args=None, **kwargs):
"""Display human readable specs with customizable formatting. """Display human readable specs with customizable formatting.

View file

@ -2,6 +2,7 @@
# Spack Project Developers. See the top-level COPYRIGHT file for details. # Spack Project Developers. See the top-level COPYRIGHT file for details.
# #
# SPDX-License-Identifier: (Apache-2.0 OR MIT) # SPDX-License-Identifier: (Apache-2.0 OR MIT)
from __future__ import print_function from __future__ import print_function
import llnl.util.tty as tty import llnl.util.tty as tty
@ -12,7 +13,6 @@
import spack.repo import spack.repo
import spack.cmd as cmd import spack.cmd as cmd
import spack.cmd.common.arguments as arguments import spack.cmd.common.arguments as arguments
from spack.cmd import display_specs
from spack.util.string import plural from spack.util.string import plural
description = "list and search installed packages" description = "list and search installed packages"
@ -36,6 +36,9 @@ def setup_parser(subparser):
format_group.add_argument( format_group.add_argument(
"--format", action="store", default=None, "--format", action="store", default=None,
help="output specs with the specified format string") help="output specs with the specified format string")
format_group.add_argument(
"--json", action="store_true", default=False,
help="output specs as machine-readable json records")
# TODO: separate this entirely from the "mode" option -- it's # TODO: separate this entirely from the "mode" option -- it's
# TODO: orthogonal, but changing it for all commands that use it with # TODO: orthogonal, but changing it for all commands that use it with
@ -159,14 +162,14 @@ def display_env(env, args, decorator):
else: else:
tty.msg('Root specs') tty.msg('Root specs')
# TODO: Change this to not print extraneous deps and variants # TODO: Change this to not print extraneous deps and variants
display_specs( cmd.display_specs(
env.user_specs, args, env.user_specs, args,
decorator=lambda s, f: color.colorize('@*{%s}' % f)) decorator=lambda s, f: color.colorize('@*{%s}' % f))
print() print()
if args.show_concretized: if args.show_concretized:
tty.msg('Concretized roots') tty.msg('Concretized roots')
display_specs( cmd.display_specs(
env.specs_by_hash.values(), args, decorator=decorator) env.specs_by_hash.values(), args, decorator=decorator)
print() print()
@ -205,4 +208,4 @@ def find(parser, args):
if env: if env:
display_env(env, args, decorator) display_env(env, args, decorator)
tty.msg("%s" % plural(len(results), 'installed package')) tty.msg("%s" % plural(len(results), 'installed package'))
display_specs(results, args, decorator=decorator, all_headers=True) cmd.display_specs(results, args, decorator=decorator, all_headers=True)

View file

@ -952,6 +952,7 @@ def _shell_vars(self):
('PKG_CONFIG_PATH', ['lib/pkgconfig', 'lib64/pkgconfig']), ('PKG_CONFIG_PATH', ['lib/pkgconfig', 'lib64/pkgconfig']),
('CMAKE_PREFIX_PATH', ['']), ('CMAKE_PREFIX_PATH', ['']),
] ]
path_updates = list() path_updates = list()
if default_view_name in self.views: if default_view_name in self.views:
for var, subdirs in updates: for var, subdirs in updates:

View file

@ -1540,6 +1540,31 @@ def to_dict(self, hash=ht.dag_hash):
return syaml_dict([('spec', node_list)]) return syaml_dict([('spec', node_list)])
def to_record_dict(self):
"""Return a "flat" dictionary with name and hash as top-level keys.
This is similar to ``to_node_dict()``, but the name and the hash
are "flattened" into the dictionary for easiler parsing by tools
like ``jq``. Instead of being keyed by name or hash, the
dictionary "name" and "hash" fields, e.g.::
{
"name": "openssl"
"hash": "3ws7bsihwbn44ghf6ep4s6h4y2o6eznv"
"version": "3.28.0",
"arch": {
...
}
But is otherwise the same as ``to_node_dict()``.
"""
dictionary = syaml_dict()
dictionary["name"] = self.name
dictionary["hash"] = self.dag_hash()
dictionary.update(self.to_node_dict()[self.name])
return dictionary
def to_yaml(self, stream=None, hash=ht.dag_hash): def to_yaml(self, stream=None, hash=ht.dag_hash):
return syaml.dump( return syaml.dump(
self.to_dict(hash), stream=stream, default_flow_style=False) self.to_dict(hash), stream=stream, default_flow_style=False)