Source code for spack.cmd.find

# Copyright Spack Project Developers. See COPYRIGHT file for details.
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)

import argparse
import copy
import sys
from typing import List, Optional, Tuple

import spack.binary_distribution
import spack.config
import spack.environment as ev
import spack.repo
import spack.solver.reuse
import spack.spec
import spack.store
import spack.util.lang
from spack import cmd
from spack.cmd.common import arguments
from spack.externals_config import create_external_parser, external_config_with_implicit_externals
from spack.util import tty
from spack.util.tty import color
from spack.util.tty.color import colorize

from ..enums import InstallRecordStatus

description = "list and search installed packages"
section = "query"
level = "short"


[docs] def setup_parser(subparser: argparse.ArgumentParser) -> None: format_group = subparser.add_mutually_exclusive_group() format_group.add_argument( "--format", action="store", default=None, help="output specs with the specified format string", ) format_group.add_argument( "-H", "--hashes", action="store_const", dest="format", const="{/hash}", help="same as ``--format {/hash}``; use with ``xargs`` or ``$()``", ) format_group.add_argument( "--json", action="store_true", default=False, help="output specs as machine-readable json records", ) subparser.add_argument( "-I", "--install-status", action="store_true", help="show install status of packages" ) subparser.add_argument( "--specfile-format", action="store_true", help="show the specfile format for installed deps ", ) subparser.add_argument( "-d", "--deps", action="store_true", help="output dependencies along with found specs" ) subparser.add_argument( "-p", "--paths", action="store_true", help="show paths to package install directories" ) subparser.add_argument( "--groups", action="store_true", default=None, dest="groups", help="display specs in arch/compiler groups (default on)", ) subparser.add_argument( "--no-groups", action="store_false", default=None, dest="groups", help="do not group specs by arch/compiler", ) arguments.add_common_arguments(subparser, ["long", "very_long", "tags", "namespaces"]) subparser.add_argument( "-r", "--only-roots", action="store_true", help="don't show full list of installed specs in an environment", ) concretized_vs_packages = subparser.add_mutually_exclusive_group() concretized_vs_packages.add_argument( "-c", "--show-concretized", action="store_true", help="show concretized specs in an environment", ) concretized_vs_packages.add_argument( "--show-configured-externals", action="store_true", help="show externals defined in the 'packages' section of the configuration", ) subparser.add_argument( "-f", "--show-flags", action="store_true", dest="show_flags", help="show spec compiler flags", ) subparser.add_argument( "--show-full-compiler", action="store_true", dest="show_full_compiler", help="(DEPRECATED) show full compiler specs. Currently it's a no-op", ) implicit_explicit = subparser.add_mutually_exclusive_group() implicit_explicit.add_argument( "-x", "--explicit", action="store_true", help="show only specs that were installed explicitly", ) implicit_explicit.add_argument( "-X", "--implicit", action="store_true", help="show only specs that were installed as dependencies", ) subparser.add_argument( "-e", "--external", action="store_true", help="show only specs that are marked as externals", ) subparser.add_argument( "-u", "--unknown", action="store_true", dest="unknown", help="show only specs Spack does not have a package for", ) subparser.add_argument( "-m", "--missing", action="store_true", dest="missing", help="show missing dependencies as well as installed specs", ) subparser.add_argument( "-v", "--variants", action="store_true", dest="variants", help="show variants in output (can be long)", ) subparser.add_argument( "--loaded", action="store_true", help="show only packages loaded in the user environment" ) only_missing_or_deprecated = subparser.add_mutually_exclusive_group() only_missing_or_deprecated.add_argument( "-M", "--only-missing", action="store_true", dest="only_missing", help="show only missing dependencies", ) only_missing_or_deprecated.add_argument( "--only-deprecated", action="store_true", help="show only deprecated packages" ) subparser.add_argument( "--deprecated", action="store_true", help="show deprecated packages as well as installed specs", ) subparser.add_argument( "--install-tree", action="store", default="all", help="Install trees to query: 'all' (default), 'local', 'upstream', upstream name or path", ) subparser.add_argument("--start-date", help="earliest date of installation [YYYY-MM-DD]") subparser.add_argument("--end-date", help="latest date of installation [YYYY-MM-DD]") arguments.add_common_arguments(subparser, ["constraint"])
[docs] def query_arguments(args): if args.only_missing and (args.deprecated or args.missing): args.subparser.error("cannot use --only-missing with --deprecated, or --missing") if args.only_deprecated and (args.deprecated or args.missing): args.subparser.error("cannot use --only-deprecated with --deprecated, or --missing") installed = InstallRecordStatus.INSTALLED if args.only_missing: installed = InstallRecordStatus.MISSING elif args.only_deprecated: installed = InstallRecordStatus.DEPRECATED if args.missing: installed |= InstallRecordStatus.MISSING if args.deprecated: installed |= InstallRecordStatus.DEPRECATED predicate_fn = None if args.unknown: predicate_fn = lambda x: not spack.repo.PATH.exists(x.spec.name) explicit = None if args.explicit: explicit = True if args.implicit: explicit = False q_args = {"installed": installed, "predicate_fn": predicate_fn, "explicit": explicit} install_tree = args.install_tree upstreams = spack.config.get("upstreams", {}) if install_tree in upstreams.keys(): install_tree = upstreams[install_tree]["install_tree"] q_args["install_tree"] = install_tree # Time window of installation for attribute in ("start_date", "end_date"): date = getattr(args, attribute) if date: q_args[attribute] = spack.util.lang.pretty_string_to_date(date) return q_args
[docs] def make_env_decorator(env): """Create a function for decorating specs when in an environment.""" roots = set(env.roots()) removed = set(env.removed_specs()) def decorator(spec, fmt): # add +/-/* to show added/removed/root specs if any(spec.dag_hash() == r.dag_hash() for r in roots): return color.colorize(f"@*{{{fmt}}}") elif spec in removed: return color.colorize(f"@K{{{fmt}}}") else: return fmt return decorator
[docs] def display_env(env, args, decorator, results, status_fn=None): """Display extra find output when running in an environment. In an environment, ``spack find`` outputs a preliminary section showing the root specs of the environment (this is in addition to the section listing out specs matching the query parameters). """ total_roots = sum(len(env.user_specs_by(group=g)) for g in env.manifest.groups()) root_spec_str = f"{total_roots or 'no'} root {'spec' if total_roots == 1 else 'specs'}" tty.msg(f"In environment {env.name} ({root_spec_str})") concrete_specs = {x.root: env.specs_by_hash[x.hash] for x in env.concretized_roots} _status_fn = status_fn if status_fn is not None else spack.spec.Spec.install_status def root_decorator(spec, string): """Decorate root specs with their install status if needed""" concrete = concrete_specs.get(spec) if concrete: status = color.colorize(_status_fn(concrete).value) hash = concrete.dag_hash() else: status = color.colorize(spack.spec.InstallStatus.absent.value) hash = "-" * 32 # TODO: status has two extra spaces on the end of it, but fixing this and other spec # TODO: space format idiosyncrasies is complicated. Fix this eventually status = status[:-2] if args.long or args.very_long: hash = color.colorize(f"@K{{{hash[: 7 if args.long else None]}}}") return f"{status} {hash} {string}" else: return f"{status} {string}" with spack.store.STORE.db.read_transaction(): for group in env.manifest.groups(): group_specs = env.user_specs_by(group=group) if not group_specs: continue if env.has_groups(): header = ( f"{spack.spec.ARCHITECTURE_COLOR}{{root specs}} / " f"{spack.spec.COMPILER_COLOR}{{{group}}}" ) tty.hline(colorize(header), char="-") cmd.display_specs( group_specs, args, # these are overrides of CLI args paths=False, long=False, very_long=False, # these enforce details in the root specs to show what the user asked for groups=False, namespaces=True, show_flags=True, decorator=root_decorator, variants=True, specfile_format=args.specfile_format, ) print() if env.included_concrete_env_root_dirs: tty.msg("Included specs") # Root specs cannot be displayed with prefixes, since those are not # set for abstract specs. Same for hashes root_args = copy.copy(args) root_args.paths = False # Roots are displayed with variants, etc. so that we can see # specifically what the user asked for. cmd.display_specs( env.included_user_specs, root_args, decorator=lambda s, f: color.colorize("@*{%s}" % f), namespace=True, show_flags=True, variants=True, specfile_format=args.specfile_format, ) print()
def _find_query( args: argparse.Namespace, env: Optional[ev.Environment] ) -> Tuple[List[spack.spec.Spec], List[spack.spec.Spec]]: q_args = query_arguments(args) concretized_but_not_installed = [] if args.show_configured_externals: packages_with_externals = external_config_with_implicit_externals(spack.config.CONFIG) completion_mode = spack.config.CONFIG.get("concretizer:externals:completion") results = spack.solver.reuse.spec_filter_from_packages_yaml( external_parser=create_external_parser(packages_with_externals, completion_mode), packages_with_externals=packages_with_externals, include=[], exclude=[], ).selected_specs() elif env: all_env_specs = env.all_specs() if args.constraint: init_specs = cmd.parse_specs(args.constraint) env_specs = env.all_matching_specs(*init_specs) else: env_specs = all_env_specs spec_hashes = {x.dag_hash() for x in env_specs} specs_meeting_q_args = set(spack.store.STORE.db.query(hashes=list(spec_hashes), **q_args)) results = list() with spack.store.STORE.db.read_transaction(): for spec in env_specs: if not spec.installed: concretized_but_not_installed.append(spec) if spec in specs_meeting_q_args: results.append(spec) else: results = args.specs(**q_args) if args.external: results = [s for s in results if s.external] # use groups by default except with format. if args.groups is None: args.groups = not args.format # Exit early with an error code if no package matches the constraint if concretized_but_not_installed and args.show_concretized: pass elif results: pass elif args.constraint: raise cmd.NoSpecMatches() # If tags have been specified on the command line, filter by tags if args.tags: packages_with_tags = spack.repo.PATH.packages_with_tags(*args.tags) results = [x for x in results if x.name in packages_with_tags] concretized_but_not_installed = [ x for x in concretized_but_not_installed if x.name in packages_with_tags ] if args.loaded: results = cmd.filter_loaded_specs(results) return results, concretized_but_not_installed
[docs] def find(parser, args): env = ev.active_environment() if not env and args.only_roots: args.subparser.error("-r / --only-roots requires an active environment") if not env and args.show_concretized: args.subparser.error("-c / --show-concretized requires an active environment") try: results, concretized_but_not_installed = _find_query(args, env) except cmd.NoSpecMatches: # Note: this uses args.constraint vs. args.constraint_specs because # the latter only exists if you call args.specs() tty.die(f"No package matches the query: {' '.join(args.constraint)}") if args.install_status or args.show_concretized: spack.binary_distribution.load_buildcache_index() status_fn = cmd.buildcache_status_fn(spack.binary_distribution.BINARY_INDEX) else: status_fn = None # Display the result if args.json: cmd.display_specs_as_json(results, deps=args.deps) else: decorator = make_env_decorator(env) if env else lambda s, f: f if not args.format: if env: display_env(env, args, decorator, results, status_fn=status_fn) if not args.only_roots: display_results = list(results) if args.show_concretized: display_results += concretized_but_not_installed cmd.display_specs( display_results, args, decorator=decorator, all_headers=True, status_fn=status_fn, specfile_format=args.specfile_format, ) # print number of installed packages last (as the list may be long) if sys.stdout.isatty() and args.groups: installed_suffix = "" concretized_suffix = " to be installed" if args.only_roots: installed_suffix += " (not shown)" concretized_suffix += " (not shown)" else: if env and not args.show_concretized: concretized_suffix += " (show with `spack find -c`)" pkg_type = "loaded" if args.loaded else "installed" cmd.print_how_many_pkgs(results, pkg_type, suffix=installed_suffix) if env: cmd.print_how_many_pkgs( concretized_but_not_installed, "concretized", suffix=concretized_suffix )