env: add --env argument to spack find

- add a common argument for `-e/--env`
- modify the database to support queries on subsets of hashes
- allow `spack find` to be filtered by hashes in an environment
This commit is contained in:
Todd Gamblin 2018-07-31 01:23:19 -07:00
parent ea7648ff84
commit 47e60d5ef8
5 changed files with 52 additions and 9 deletions

View File

@ -8,6 +8,7 @@
import spack.cmd import spack.cmd
import spack.config import spack.config
import spack.environment
import spack.modules import spack.modules
import spack.spec import spack.spec
import spack.store import spack.store
@ -42,16 +43,23 @@ class ConstraintAction(argparse.Action):
To obtain the specs from a command the function must be called. To obtain the specs from a command the function must be called.
""" """
def __call__(self, parser, namespace, values, option_string=None): def __call__(self, parser, namespace, values, option_string=None):
# Query specs from command line # Query specs from command line
self.values = values self.values = values
namespace.constraint = values namespace.constraint = values
namespace.specs = self._specs namespace.specs = self._specs
# env comes from EnvAction if --env is provided
self.env = None if not hasattr(namespace, 'env') else namespace.env
def _specs(self, **kwargs): def _specs(self, **kwargs):
qspecs = spack.cmd.parse_specs(self.values) qspecs = spack.cmd.parse_specs(self.values)
# If an environment is provided, we'll restrict the search to
# only its installed packages.
if self.env:
kwargs['hashes'] = set(self.env.specs_by_hash.keys())
# return everything for an empty query. # return everything for an empty query.
if not qspecs: if not qspecs:
return spack.store.db.query(**kwargs) return spack.store.db.query(**kwargs)
@ -66,6 +74,16 @@ def _specs(self, **kwargs):
return sorted(specs.values()) return sorted(specs.values())
class EnvAction(argparse.Action):
"""Records the environment to which a command applies."""
def __call__(self, parser, namespace, env_name, option_string=None):
namespace.env = spack.environment.read(env_name)
_arguments['env'] = Args(
'-e', '--env', action=EnvAction, default=None,
help="run this command on a specific environment")
_arguments['constraint'] = Args( _arguments['constraint'] = Args(
'constraint', nargs=argparse.REMAINDER, action=ConstraintAction, 'constraint', nargs=argparse.REMAINDER, action=ConstraintAction,
help='constraint to select a subset of installed packages') help='constraint to select a subset of installed packages')

View File

@ -37,7 +37,8 @@ def setup_parser(subparser):
const='deps', const='deps',
help='show full dependency DAG of installed packages') help='show full dependency DAG of installed packages')
arguments.add_common_arguments(subparser, ['long', 'very_long', 'tags']) arguments.add_common_arguments(
subparser, ['env', 'long', 'very_long', 'tags'])
subparser.add_argument('-f', '--show-flags', subparser.add_argument('-f', '--show-flags',
action='store_true', action='store_true',
@ -49,11 +50,11 @@ def setup_parser(subparser):
help='show full compiler specs') help='show full compiler specs')
implicit_explicit = subparser.add_mutually_exclusive_group() implicit_explicit = subparser.add_mutually_exclusive_group()
implicit_explicit.add_argument( implicit_explicit.add_argument(
'-e', '--explicit', '-x', '--explicit',
action='store_true', action='store_true',
help='show only specs that were installed explicitly') help='show only specs that were installed explicitly')
implicit_explicit.add_argument( implicit_explicit.add_argument(
'-E', '--implicit', '-X', '--implicit',
action='store_true', action='store_true',
help='show only specs that were installed as dependencies') help='show only specs that were installed as dependencies')
subparser.add_argument( subparser.add_argument(

View File

@ -55,9 +55,7 @@ def setup_parser(subparser):
help='prompt the list of modules associated with a constraint' help='prompt the list of modules associated with a constraint'
) )
add_loads_arguments(loads_parser) add_loads_arguments(loads_parser)
arguments.add_common_arguments( arguments.add_common_arguments(loads_parser, ['constraint'])
loads_parser, ['constraint']
)
return sp return sp

View File

@ -851,7 +851,8 @@ def query(
installed=True, installed=True,
explicit=any, explicit=any,
start_date=None, start_date=None,
end_date=None end_date=None,
hashes=None
): ):
"""Run a query on the database """Run a query on the database
@ -885,19 +886,26 @@ def query(
end_date (datetime, optional): filters the query discarding end_date (datetime, optional): filters the query discarding
specs that have been installed after ``end_date``. specs that have been installed after ``end_date``.
hashes (container): list or set of hashes that we can use to
restrict the search
Returns: Returns:
list of specs that match the query list of specs that match the query
""" """
# TODO: Specs are a lot like queries. Should there be a # TODO: Specs are a lot like queries. Should there be a
# TODO: wildcard spec object, and should specs have attributes # TODO: wildcard spec object, and should specs have attributes
# TODO: like installed and known that can be queried? Or are # TODO: like installed and known that can be queried? Or are
# TODO: these really special cases that only belong here? # TODO: these really special cases that only belong here?
# TODO: handling of hashes restriction is not particularly elegant.
with self.read_transaction(): with self.read_transaction():
# Just look up concrete specs with hashes; no fancy search. # Just look up concrete specs with hashes; no fancy search.
if isinstance(query_spec, spack.spec.Spec) and query_spec.concrete: if isinstance(query_spec, spack.spec.Spec) and query_spec.concrete:
hash_key = query_spec.dag_hash() hash_key = query_spec.dag_hash()
if hash_key in self._data: if (hash_key in self._data and
(not hashes or hash_key in hashes)):
return [self._data[hash_key].spec] return [self._data[hash_key].spec]
else: else:
return [] return []
@ -909,6 +917,9 @@ def query(
end_date = end_date or datetime.datetime.max end_date = end_date or datetime.datetime.max
for key, rec in self._data.items(): for key, rec in self._data.items():
if hashes is not None and rec.spec.dag_hash() not in hashes:
continue
if installed is not any and rec.installed != installed: if installed is not any and rec.installed != installed:
continue continue

View File

@ -31,6 +31,7 @@
import llnl.util.filesystem as fs import llnl.util.filesystem as fs
import llnl.util.tty as tty import llnl.util.tty as tty
import spack.error
import spack.repo import spack.repo
import spack.schema.env import spack.schema.env
import spack.util.spack_json as sjson import spack.util.spack_json as sjson
@ -389,9 +390,15 @@ def repair(environment_name):
def read(environment_name): def read(environment_name):
"""Read environment state from disk."""
# Check that env is in a consistent state on disk # Check that env is in a consistent state on disk
env_root = root(environment_name) env_root = root(environment_name)
if not os.path.isdir(env_root):
raise EnvError("no such environment '%s'" % environment_name)
if not os.access(env_root, os.R_OK):
raise EnvError("can't read environment '%s'" % environment_name)
# Read env.yaml file # Read env.yaml file
env_yaml = spack.config._read_config_file( env_yaml = spack.config._read_config_file(
fs.join_path(env_root, 'env.yaml'), fs.join_path(env_root, 'env.yaml'),
@ -458,3 +465,11 @@ def prepare_config_scope(environment):
tty.msg('Using Spack config %s scope at %s' % tty.msg('Using Spack config %s scope at %s' %
(config_name, config_dir)) (config_name, config_dir))
spack.config.config.push_scope(ConfigScope(config_name, config_dir)) spack.config.config.push_scope(ConfigScope(config_name, config_dir))
class EnvError(spack.error.SpackError):
"""Superclass for all errors to do with Spack environments.
Note that this is called ``EnvError`` to distinguish it from the
builtin ``EnvironmentError``.
"""