Source code for spack.cmd.info

# Copyright 2013-2024 Lawrence Livermore National Security, LLC and other
# Spack Project Developers. See the top-level COPYRIGHT file for details.
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)

import sys
import textwrap
from itertools import zip_longest

import llnl.util.tty as tty
import llnl.util.tty.color as color
from llnl.util.tty.colify import colify

import spack.deptypes as dt
import spack.fetch_strategy as fs
import spack.install_test
import spack.repo
import spack.spec
import spack.version
from spack.cmd.common import arguments
from spack.package_base import preferred_version

description = "get detailed information on a particular package"
section = "basic"
level = "short"

header_color = "@*b"
plain_format = "@."


[docs] def padder(str_list, extra=0): """Return a function to pad elements of a list.""" length = max(len(str(s)) for s in str_list) + extra def pad(string): string = str(string) padding = max(0, length - len(string)) return string + (padding * " ") return pad
[docs] def setup_parser(subparser): subparser.add_argument( "-a", "--all", action="store_true", default=False, help="output all package information" ) options = [ ("--detectable", print_detectable.__doc__), ("--maintainers", print_maintainers.__doc__), ("--no-dependencies", "do not " + print_dependencies.__doc__), ("--no-variants", "do not " + print_variants.__doc__), ("--no-versions", "do not " + print_versions.__doc__), ("--phases", print_phases.__doc__), ("--tags", print_tags.__doc__), ("--tests", print_tests.__doc__), ("--virtuals", print_virtuals.__doc__), ("--variants-by-name", "list variants in strict name order; don't group by condition"), ] for opt, help_comment in options: subparser.add_argument(opt, action="store_true", help=help_comment) arguments.add_common_arguments(subparser, ["package"])
[docs] def section_title(s): return header_color + s + plain_format
[docs] def version(s): return spack.spec.VERSION_COLOR + s + plain_format
[docs] def variant(s): return spack.spec.ENABLED_VARIANT_COLOR + s + plain_format
[docs] def license(s): return spack.spec.VERSION_COLOR + s + plain_format
[docs] class VariantFormatter: def __init__(self, pkg): self.variants = pkg.variants self.headers = ("Name [Default]", "When", "Allowed values", "Description") # Don't let name or possible values be less than max widths _, cols = tty.terminal_size() max_name = min(self.column_widths[0], 30) max_when = min(self.column_widths[1], 30) max_vals = min(self.column_widths[2], 20) # allow the description column to extend as wide as the terminal. max_description = min( self.column_widths[3], # min width 70 cols, 14 cols of margins and column spacing max(cols, 70) - max_name - max_vals - 14, ) self.column_widths = (max_name, max_when, max_vals, max_description) # Compute the format self.fmt = "%%-%ss%%-%ss%%-%ss%%s" % ( self.column_widths[0] + 4, self.column_widths[1] + 4, self.column_widths[2] + 4, )
[docs] def default(self, v): s = "on" if v.default is True else "off" if not isinstance(v.default, bool): s = v.default return s
@property def lines(self): if not self.variants: yield " None" return else: yield " " + self.fmt % self.headers underline = tuple([w * "=" for w in self.column_widths]) yield " " + self.fmt % underline yield "" for k, e in sorted(self.variants.items()): v, w = e name = textwrap.wrap( "{0} [{1}]".format(k, self.default(v)), width=self.column_widths[0] ) if all(spec == spack.spec.Spec() for spec in w): w = "--" when = textwrap.wrap(str(w), width=self.column_widths[1]) allowed = v.allowed_values.replace("True, False", "on, off") allowed = textwrap.wrap(allowed, width=self.column_widths[2]) description = [] for d_line in v.description.split("\n"): description += textwrap.wrap(d_line, width=self.column_widths[3]) for t in zip_longest(name, when, allowed, description, fillvalue=""): yield " " + self.fmt % t
def _fmt_value(v): if v is None or isinstance(v, bool): return str(v).lower() else: return str(v) def _fmt_name_and_default(variant): """Print colorized name [default] for a variant.""" return color.colorize(f"@c{{{variant.name}}} @C{{[{_fmt_value(variant.default)}]}}") def _fmt_when(when, indent): return color.colorize(f"{indent * ' '}@B{{when}} {color.cescape(when)}") def _fmt_variant_description(variant, width, indent): """Format a variant's description, preserving explicit line breaks.""" return "\n".join( textwrap.fill( line, width=width, initial_indent=indent * " ", subsequent_indent=indent * " " ) for line in variant.description.split("\n") ) def _fmt_variant(variant, max_name_default_len, indent, when=None, out=None): out = out or sys.stdout _, cols = tty.terminal_size() name_and_default = _fmt_name_and_default(variant) name_default_len = color.clen(name_and_default) values = variant.values if not isinstance(variant.values, (tuple, list, spack.variant.DisjointSetsOfValues)): values = [variant.values] # put 'none' first, sort the rest by value sorted_values = sorted(values, key=lambda v: (v != "none", v)) pad = 4 # min padding between 'name [default]' and values value_indent = (indent + max_name_default_len + pad) * " " # left edge of values # This preserves any formatting (i.e., newlines) from how the description was # written in package.py, but still wraps long lines for small terminals. # This allows some packages to provide detailed help on their variants (see, e.g., gasnet). formatted_values = "\n".join( textwrap.wrap( f"{', '.join(_fmt_value(v) for v in sorted_values)}", width=cols - 2, initial_indent=value_indent, subsequent_indent=value_indent, ) ) formatted_values = formatted_values[indent + name_default_len + pad :] # name [default] value1, value2, value3, ... padding = pad * " " color.cprint(f"{indent * ' '}{name_and_default}{padding}@c{{{formatted_values}}}", stream=out) # when <spec> description_indent = indent + 4 if when is not None and when != spack.spec.Spec(): out.write(_fmt_when(when, description_indent - 2)) out.write("\n") # description, preserving explicit line breaks from the way it's written in the package file out.write(_fmt_variant_description(variant, cols - 2, description_indent)) out.write("\n") def _variants_by_name_when(pkg): """Adaptor to get variants keyed by { name: { when: { [Variant...] } }.""" # TODO: replace with pkg.variants_by_name(when=True) when unified directive dicts are merged. variants = {} for name, (variant, whens) in sorted(pkg.variants.items()): for when in whens: variants.setdefault(name, {}).setdefault(when, []).append(variant) return variants def _variants_by_when_name(pkg): """Adaptor to get variants keyed by { when: { name: Variant } }""" # TODO: replace with pkg.variants when unified directive dicts are merged. variants = {} for name, (variant, whens) in pkg.variants.items(): for when in whens: variants.setdefault(when, {})[name] = variant return variants def _print_variants_header(pkg): """output variants""" if not pkg.variants: print(" None") return color.cprint("") color.cprint(section_title("Variants:")) variants_by_name = _variants_by_name_when(pkg) # Calculate the max length of the "name [default]" part of the variant display # This lets us know where to print variant values. max_name_default_len = max( color.clen(_fmt_name_and_default(variant)) for name, when_variants in variants_by_name.items() for variants in when_variants.values() for variant in variants ) return max_name_default_len, variants_by_name def _unconstrained_ver_first(item): """sort key that puts specs with open version ranges first""" spec, _ = item return (spack.version.any_version not in spec.versions, spec)
[docs] def info(parser, args): spec = spack.spec.Spec(args.package) pkg_cls = spack.repo.PATH.get_pkg_class(spec.name) pkg = pkg_cls(spec) # Output core package information header = section_title("{0}: ").format(pkg.build_system_class) + pkg.name color.cprint(header) color.cprint("") color.cprint(section_title("Description:")) if pkg.__doc__: color.cprint(color.cescape(pkg.format_doc(indent=4))) else: color.cprint(" None") if getattr(pkg, "homepage"): color.cprint(section_title("Homepage: ") + pkg.homepage) # Now output optional information in expected order sections = [ (args.all or args.maintainers, print_maintainers), (args.all or args.detectable, print_detectable), (args.all or args.tags, print_tags), (args.all or not args.no_versions, print_versions), (args.all or not args.no_variants, print_variants), (args.all or args.phases, print_phases), (args.all or not args.no_dependencies, print_dependencies), (args.all or args.virtuals, print_virtuals), (args.all or args.tests, print_tests), (args.all or True, print_licenses), ] for print_it, func in sections: if print_it: func(pkg, args) color.cprint("")