Source code for spack.cmd.verify

# Copyright Spack Project Developers. See COPYRIGHT file for details.
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
import argparse
import io
from typing import List, Optional

import spack.cmd
import spack.environment as ev
import spack.llnl.util.tty as tty
import spack.spec
import spack.store
import spack.verify
import spack.verify_libraries
from spack.cmd.common import arguments
from spack.llnl.string import plural
from spack.llnl.util.filesystem import visit_directory_tree

description = "verify spack installations on disk"
section = "admin"
level = "long"


[docs] def setup_parser(subparser: argparse.ArgumentParser): sp = subparser.add_subparsers(metavar="SUBCOMMAND", dest="verify_command") manifest_subparser = sp.add_parser( "manifest", help=verify_manifest.__doc__, description=verify_manifest.__doc__ ) manifest_subparser.set_defaults(subparser=manifest_subparser) manifest_subparser.add_argument( "-l", "--local", action="store_true", help="verify only locally installed packages" ) manifest_subparser.add_argument( "-j", "--json", action="store_true", help="output json-formatted errors" ) manifest_subparser.add_argument("-a", "--all", action="store_true", help="verify all packages") manifest_subparser.add_argument( "specs_or_files", nargs=argparse.REMAINDER, help="specs or files to verify" ) manifest_sp_type = manifest_subparser.add_mutually_exclusive_group() manifest_sp_type.add_argument( "-s", "--specs", action="store_const", const="specs", dest="type", default="specs", help="treat entries as specs (default)", ) manifest_sp_type.add_argument( "-f", "--files", action="store_const", const="files", dest="type", default="specs", help="treat entries as absolute filenames\n\ncannot be used with '-a'", ) libraries_subparser = sp.add_parser( "libraries", help=verify_libraries.__doc__, description=verify_libraries.__doc__ ) libraries_subparser.set_defaults(subparser=libraries_subparser) arguments.add_common_arguments(libraries_subparser, ["constraint"]) versions_subparser = sp.add_parser( "versions", help=verify_versions.__doc__, description=verify_versions.__doc__ ) versions_subparser.set_defaults(subparser=versions_subparser) arguments.add_common_arguments(versions_subparser, ["constraint"])
[docs] def verify(parser, args): cmd = args.verify_command if cmd == "libraries": return verify_libraries(args) elif cmd == "manifest": return verify_manifest(args) elif cmd == "versions": return verify_versions(args) parser.error("invalid verify subcommand")
[docs] def verify_versions(args): """Check that all versions of installed packages are known to Spack and non-deprecated. Reports errors for any of the following: 1. Installed package not loadable from the repo 2. Installed package version not known by the package recipe 3. Installed package version deprecated in the package recipe """ specs = args.specs(installed=True) msg_lines = _verify_version(specs) if msg_lines: tty.die("\n".join(msg_lines))
def _verify_version(specs): """Helper method for verify_versions.""" missing_package = [] unknown_version = [] deprecated_version = [] for spec in specs: try: pkg = spec.package except Exception as e: tty.debug(str(e)) missing_package.append(spec) continue if spec.version not in pkg.versions: unknown_version.append(spec) continue if pkg.versions[spec.version].get("deprecated", False): deprecated_version.append(spec) msg_lines = [] if missing_package or unknown_version or deprecated_version: errors = len(missing_package) + len(unknown_version) + len(deprecated_version) msg_lines = [f"{errors} installed packages have unknown/deprecated versions\n"] msg_lines += [ f" Cannot check version for {spec} at {spec.prefix}. Cannot load package." for spec in missing_package ] msg_lines += [ f" Spec {spec} at {spec.prefix} has version {spec.version} unknown to Spack." for spec in unknown_version ] msg_lines += [ f" Spec {spec} at {spec.prefix} has deprecated version {spec.version}." for spec in deprecated_version ] return msg_lines
[docs] def verify_libraries(args): """verify that shared libraries of install packages can be located in rpaths (Linux only)""" specs_from_db = [s for s in args.specs(installed=True) if not s.external] tty.info(f"Checking {len(specs_from_db)} packages for shared library resolution") errors = 0 for spec in specs_from_db: try: pkg = spec.package except Exception: tty.warn(f"Skipping {spec.cformat('{name}{@version}{/hash}')} due to missing package") error_msg = _verify_libraries(spec, pkg.unresolved_libraries) if error_msg is not None: errors += 1 tty.error(error_msg) if errors: tty.error(f"Cannot resolve shared libraries in {plural(errors, 'package')}") return 1
def _verify_libraries(spec: spack.spec.Spec, unresolved_libraries: List[str]) -> Optional[str]: """Go over the prefix of the installed spec and verify its shared libraries can be resolved.""" visitor = spack.verify_libraries.ResolveSharedElfLibDepsVisitor( [*spack.verify_libraries.ALLOW_UNRESOLVED, *unresolved_libraries] ) visit_directory_tree(spec.prefix, visitor) if not visitor.problems: return None output = io.StringIO() visitor.write(output, indent=4, brief=True) message = output.getvalue().rstrip() return f"{spec.cformat('{name}{@version}{/hash}')}: {spec.prefix}:\n{message}"
[docs] def verify_manifest(args): """verify that install directories have not been modified since installation""" local = args.local if args.type == "files": if args.all: args.subparser.error("cannot use --all with --files") for file in args.specs_or_files: results = spack.verify.check_file_manifest(file) if results.has_errors(): if args.json: print(results.json_string()) else: print(results) return 0 else: spec_args = spack.cmd.parse_specs(args.specs_or_files) if args.all: query = spack.store.STORE.db.query_local if local else spack.store.STORE.db.query # construct spec list if spec_args: spec_list = spack.cmd.parse_specs(args.specs_or_files) specs = [] for spec in spec_list: specs += query(spec, installed=True) else: specs = query(installed=True) elif args.specs_or_files: # construct disambiguated spec list env = ev.active_environment() specs = list(map(lambda x: spack.cmd.disambiguate_spec(x, env, local=local), spec_args)) else: args.subparser.error("use --all or specify specs to verify") for spec in specs: tty.debug("Verifying package %s") results = spack.verify.check_spec_manifest(spec) if results.has_errors(): if args.json: print(results.json_string()) else: tty.msg("In package %s" % spec.format("{name}/{hash:7}")) print(results) return 1 else: tty.debug(results)