Compare commits
3 Commits
develop
...
better-dep
Author | SHA1 | Date | |
---|---|---|---|
![]() |
690fad1182 | ||
![]() |
13446994ab | ||
![]() |
327462e8e2 |
@ -5,6 +5,7 @@
|
|||||||
import sys
|
import sys
|
||||||
import textwrap
|
import textwrap
|
||||||
from itertools import zip_longest
|
from itertools import zip_longest
|
||||||
|
from typing import Callable, Dict, TypeVar
|
||||||
|
|
||||||
import llnl.util.tty as tty
|
import llnl.util.tty as tty
|
||||||
import llnl.util.tty.color as color
|
import llnl.util.tty.color as color
|
||||||
@ -14,11 +15,12 @@
|
|||||||
import spack.deptypes as dt
|
import spack.deptypes as dt
|
||||||
import spack.fetch_strategy as fs
|
import spack.fetch_strategy as fs
|
||||||
import spack.install_test
|
import spack.install_test
|
||||||
|
import spack.package_base
|
||||||
import spack.repo
|
import spack.repo
|
||||||
import spack.spec
|
import spack.spec
|
||||||
import spack.variant
|
import spack.variant
|
||||||
from spack.cmd.common import arguments
|
from spack.cmd.common import arguments
|
||||||
from spack.package_base import preferred_version
|
from spack.util.typing import SupportsRichComparison
|
||||||
|
|
||||||
description = "get detailed information on a particular package"
|
description = "get detailed information on a particular package"
|
||||||
section = "basic"
|
section = "basic"
|
||||||
@ -28,6 +30,44 @@
|
|||||||
plain_format = "@."
|
plain_format = "@."
|
||||||
|
|
||||||
|
|
||||||
|
class Formatter:
|
||||||
|
"""Generic formatter for elements displayed by `spack info`.
|
||||||
|
|
||||||
|
Elements have four parts: name, values, when condition, and description. They can
|
||||||
|
be formatted two ways (shown here for variants)::
|
||||||
|
|
||||||
|
Grouped by when (default)::
|
||||||
|
|
||||||
|
when +cuda
|
||||||
|
cuda_arch [none] none, 10, 100, 100a, 101,
|
||||||
|
101a, 11, 12, 120, 120a, 13
|
||||||
|
CUDA architecture
|
||||||
|
|
||||||
|
Or, by name (each name has a when nested under it)::
|
||||||
|
|
||||||
|
cuda_arch [none] none, 10, 100, 100a, 101,
|
||||||
|
101a, 11, 12, 120, 120a, 13
|
||||||
|
when +cuda
|
||||||
|
CUDA architecture
|
||||||
|
|
||||||
|
The values and description will be wrapped if needed. the name (and any additional info)
|
||||||
|
will not (so they should be kept short).
|
||||||
|
|
||||||
|
Subclasses are responsible for generating colorized text, but not wrapping,
|
||||||
|
indentation, or other formatting, for the name, values, and description.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def format_name(self, element) -> str:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def format_values(self, element) -> str:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def format_description(self, element) -> str:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
def padder(str_list, extra=0):
|
def padder(str_list, extra=0):
|
||||||
"""Return a function to pad elements of a list."""
|
"""Return a function to pad elements of a list."""
|
||||||
length = max(len(str(s)) for s in str_list) + extra
|
length = max(len(str(s)) for s in str_list) + extra
|
||||||
@ -140,17 +180,19 @@ def lines(self):
|
|||||||
yield " " + self.fmt % t
|
yield " " + self.fmt % t
|
||||||
|
|
||||||
|
|
||||||
|
class DependencyFormatter(Formatter):
|
||||||
|
def format_name(self, dep) -> str:
|
||||||
|
return str(dep.spec)
|
||||||
|
|
||||||
|
def format_values(self, dep) -> str:
|
||||||
|
return str(dt.flag_to_tuple(dep.depflag))
|
||||||
|
|
||||||
|
|
||||||
def print_dependencies(pkg, args):
|
def print_dependencies(pkg, args):
|
||||||
"""output build, link, and run package dependencies"""
|
"""output build, link, and run package dependencies"""
|
||||||
|
|
||||||
for deptype in ("build", "link", "run"):
|
print_fn = print_by_name if args.variants_by_name else print_grouped_by_when
|
||||||
color.cprint("")
|
print_fn("Dependencies", pkg.dependencies, DependencyFormatter())
|
||||||
color.cprint(section_title("%s Dependencies:" % deptype.capitalize()))
|
|
||||||
deps = sorted(pkg.dependencies_of_type(dt.flag_from_string(deptype)))
|
|
||||||
if deps:
|
|
||||||
colify(deps, indent=4)
|
|
||||||
else:
|
|
||||||
color.cprint(" None")
|
|
||||||
|
|
||||||
|
|
||||||
def print_detectable(pkg, args):
|
def print_detectable(pkg, args):
|
||||||
@ -263,66 +305,70 @@ def print_tests(pkg, args):
|
|||||||
color.cprint(" None")
|
color.cprint(" None")
|
||||||
|
|
||||||
|
|
||||||
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: "spack.spec.Spec", indent: int):
|
def _fmt_when(when: "spack.spec.Spec", indent: int):
|
||||||
return color.colorize(f"{indent * ' '}@B{{when}} {color.cescape(str(when))}")
|
return color.colorize(f"{indent * ' '}@B{{when}} {color.cescape(str(when))}")
|
||||||
|
|
||||||
|
|
||||||
def _fmt_variant_description(variant, width, indent):
|
def _fmt_variant_value(v):
|
||||||
"""Format a variant's description, preserving explicit line breaks."""
|
return str(v).lower() if v is None or isinstance(v, bool) else str(v)
|
||||||
return "\n".join(
|
|
||||||
textwrap.fill(
|
|
||||||
line, width=width, initial_indent=indent * " ", subsequent_indent=indent * " "
|
class VariantFormatter(Formatter):
|
||||||
|
def format_name(self, variant) -> str:
|
||||||
|
return color.colorize(
|
||||||
|
f"@c{{{variant.name}}} @C{{[{_fmt_variant_value(variant.default)}]}}"
|
||||||
)
|
)
|
||||||
for line in variant.description.split("\n")
|
|
||||||
)
|
def format_values(self, variant) -> str:
|
||||||
|
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))
|
||||||
|
|
||||||
|
return color.colorize(f"@c{{{', '.join(_fmt_variant_value(v) for v in sorted_values)}}}")
|
||||||
|
|
||||||
|
def format_description(self, variant) -> str:
|
||||||
|
return variant.description
|
||||||
|
|
||||||
|
|
||||||
def _fmt_variant(variant, max_name_default_len, indent, when=None, out=None):
|
def _fmt_definition(
|
||||||
|
name_field, values_field, description, max_name_len, indent, when=None, out=None
|
||||||
|
):
|
||||||
|
"""Format a definition entry in `spack info` output.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
name_field: name and optional info, e.g. a default; should be short.
|
||||||
|
values_field: possible values for the entry; Wrapped if long.
|
||||||
|
description: description of the field (wrapped if overly long)
|
||||||
|
indent: size of leading indent for entry
|
||||||
|
when: optional when condition
|
||||||
|
out: stream to print to
|
||||||
|
"""
|
||||||
out = out or sys.stdout
|
out = out or sys.stdout
|
||||||
|
|
||||||
_, cols = tty.terminal_size()
|
_, cols = tty.terminal_size()
|
||||||
|
|
||||||
name_and_default = _fmt_name_and_default(variant)
|
name_len = color.clen(name_field)
|
||||||
name_default_len = color.clen(name_and_default)
|
|
||||||
|
|
||||||
values = variant.values
|
pad = 4 # min padding between name and values
|
||||||
if not isinstance(variant.values, (tuple, list, spack.variant.DisjointSetsOfValues)):
|
value_indent = (indent + max_name_len + pad) * " " # left edge of values
|
||||||
values = [variant.values]
|
|
||||||
|
|
||||||
# put 'none' first, sort the rest by value
|
if values_field:
|
||||||
sorted_values = sorted(values, key=lambda v: (v != "none", v))
|
formatted_values = "\n".join(
|
||||||
|
textwrap.wrap(
|
||||||
pad = 4 # min padding between 'name [default]' and values
|
values_field,
|
||||||
value_indent = (indent + max_name_default_len + pad) * " " # left edge of values
|
width=cols - 2,
|
||||||
|
initial_indent=value_indent,
|
||||||
# This preserves any formatting (i.e., newlines) from how the description was
|
subsequent_indent=value_indent,
|
||||||
# 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,
|
|
||||||
)
|
)
|
||||||
)
|
# trim initial indentation
|
||||||
formatted_values = formatted_values[indent + name_default_len + pad :]
|
formatted_values = formatted_values[indent + name_len + pad :]
|
||||||
|
|
||||||
# name [default] value1, value2, value3, ...
|
# name [default] value1, value2, value3, ...
|
||||||
padding = pad * " "
|
out.write(f"{indent * ' '}{name_field}{pad * ' '}{formatted_values}\n")
|
||||||
color.cprint(f"{indent * ' '}{name_and_default}{padding}@c{{{formatted_values}}}", stream=out)
|
|
||||||
|
|
||||||
# when <spec>
|
# when <spec>
|
||||||
description_indent = indent + 4
|
description_indent = indent + 4
|
||||||
@ -330,38 +376,65 @@ def _fmt_variant(variant, max_name_default_len, indent, when=None, out=None):
|
|||||||
out.write(_fmt_when(when, description_indent - 2))
|
out.write(_fmt_when(when, description_indent - 2))
|
||||||
out.write("\n")
|
out.write("\n")
|
||||||
|
|
||||||
# description, preserving explicit line breaks from the way it's written in the package file
|
# description, preserving explicit line breaks from the way it's written in the
|
||||||
out.write(_fmt_variant_description(variant, cols - 2, description_indent))
|
# package file, but still wrapoing long lines for small terminals. This allows
|
||||||
out.write("\n")
|
# descriptions to provide detailed help in descriptions (see, e.g., gasnet's variants).
|
||||||
|
if description:
|
||||||
|
formatted_description = "\n".join(
|
||||||
|
textwrap.fill(
|
||||||
|
line,
|
||||||
|
width=cols - 2,
|
||||||
|
initial_indent=description_indent * " ",
|
||||||
|
subsequent_indent=description_indent * " ",
|
||||||
|
)
|
||||||
|
for line in description.split("\n")
|
||||||
|
)
|
||||||
|
out.write(formatted_description)
|
||||||
|
out.write("\n")
|
||||||
|
|
||||||
|
|
||||||
def _print_variants_header(pkg):
|
K = TypeVar("K", bound=SupportsRichComparison)
|
||||||
"""output variants"""
|
V = TypeVar("V")
|
||||||
|
|
||||||
if not pkg.variants:
|
|
||||||
print(" None")
|
|
||||||
return
|
|
||||||
|
|
||||||
|
def print_header(header: str, when_indexed_dictionary: Dict, formatter: Formatter):
|
||||||
color.cprint("")
|
color.cprint("")
|
||||||
color.cprint(section_title("Variants:"))
|
color.cprint(section_title(f"{header}:"))
|
||||||
|
|
||||||
# Calculate the max length of the "name [default]" part of the variant display
|
if not when_indexed_dictionary:
|
||||||
# This lets us know where to print variant values.
|
print(" None")
|
||||||
max_name_default_len = max(
|
|
||||||
color.clen(_fmt_name_and_default(variant))
|
|
||||||
for name in pkg.variant_names()
|
def max_name_length(when_indexed_dictionary: Dict, formatter: Formatter) -> int:
|
||||||
for _, variant in pkg.variant_definitions(name)
|
# Calculate the max length of the first field of the definition. Lets us know how
|
||||||
|
# much to pad other fields on the first line.
|
||||||
|
return max(
|
||||||
|
color.clen(formatter.format_name(definition))
|
||||||
|
for subkey in spack.package_base._subkeys(when_indexed_dictionary)
|
||||||
|
for _, definition in spack.package_base._definitions(when_indexed_dictionary, subkey)
|
||||||
)
|
)
|
||||||
|
|
||||||
return max_name_default_len
|
|
||||||
|
|
||||||
|
def print_grouped_by_when(header: str, when_indexed_dictionary: Dict, formatter: Formatter):
|
||||||
|
"""Generic method to print metadata grouped by when conditions."""
|
||||||
|
|
||||||
def print_variants_grouped_by_when(pkg):
|
print_header(header, when_indexed_dictionary, formatter)
|
||||||
max_name_default_len = _print_variants_header(pkg)
|
if not when_indexed_dictionary:
|
||||||
|
return
|
||||||
|
|
||||||
|
max_name_len = max_name_length(when_indexed_dictionary, formatter)
|
||||||
|
|
||||||
|
# Calculate the max length of the first field of the definition. Lets us know how
|
||||||
|
# much to pad other fields on the first line.
|
||||||
|
max_name_len = max(
|
||||||
|
color.clen(formatter.format_name(definition))
|
||||||
|
for subkey in spack.package_base._subkeys(when_indexed_dictionary)
|
||||||
|
for _, definition in spack.package_base._definitions(when_indexed_dictionary, subkey)
|
||||||
|
)
|
||||||
|
|
||||||
indent = 4
|
indent = 4
|
||||||
for when, variants_by_name in pkg.variant_items():
|
for when, by_name in when_indexed_dictionary.items():
|
||||||
padded_values = max_name_default_len + 4
|
padded_values = max_name_len + 4
|
||||||
start_indent = indent
|
start_indent = indent
|
||||||
|
|
||||||
if when != spack.spec.Spec():
|
if when != spack.spec.Spec():
|
||||||
@ -373,27 +446,46 @@ def print_variants_grouped_by_when(pkg):
|
|||||||
padded_values -= 2
|
padded_values -= 2
|
||||||
start_indent += 2
|
start_indent += 2
|
||||||
|
|
||||||
for name, variant in sorted(variants_by_name.items()):
|
for subkey, definition in sorted(by_name.items()):
|
||||||
_fmt_variant(variant, padded_values, start_indent, None, out=sys.stdout)
|
_fmt_definition(
|
||||||
|
formatter.format_name(definition),
|
||||||
|
formatter.format_values(definition),
|
||||||
|
formatter.format_description(definition),
|
||||||
|
max_name_len,
|
||||||
|
start_indent,
|
||||||
|
when=None,
|
||||||
|
out=sys.stdout,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def print_variants_by_name(pkg):
|
def print_by_name(header: str, when_indexed_dictionary: Dict, formatter: Formatter):
|
||||||
max_name_default_len = _print_variants_header(pkg)
|
print_header(header, when_indexed_dictionary, formatter)
|
||||||
max_name_default_len += 4
|
if not when_indexed_dictionary:
|
||||||
|
return
|
||||||
|
|
||||||
|
max_name_len = max_name_length(when_indexed_dictionary, formatter)
|
||||||
|
max_name_len += 4
|
||||||
|
|
||||||
indent = 4
|
indent = 4
|
||||||
for name in pkg.variant_names():
|
|
||||||
for when, variant in pkg.variant_definitions(name):
|
for subkey in spack.package_base._subkeys(when_indexed_dictionary):
|
||||||
_fmt_variant(variant, max_name_default_len, indent, when, out=sys.stdout)
|
for when, definition in spack.package_base._definitions(when_indexed_dictionary, subkey):
|
||||||
|
_fmt_definition(
|
||||||
|
formatter.format_name(definition),
|
||||||
|
formatter.format_values(definition),
|
||||||
|
formatter.format_description(definition),
|
||||||
|
max_name_len,
|
||||||
|
indent,
|
||||||
|
when=when,
|
||||||
|
out=sys.stdout,
|
||||||
|
)
|
||||||
sys.stdout.write("\n")
|
sys.stdout.write("\n")
|
||||||
|
|
||||||
|
|
||||||
def print_variants(pkg, args):
|
def print_variants(pkg, args):
|
||||||
"""output variants"""
|
"""output variants"""
|
||||||
if args.variants_by_name:
|
print_fn = print_by_name if args.variants_by_name else print_grouped_by_when
|
||||||
print_variants_by_name(pkg)
|
print_fn("Variants", pkg.variants, VariantFormatter())
|
||||||
else:
|
|
||||||
print_variants_grouped_by_when(pkg)
|
|
||||||
|
|
||||||
|
|
||||||
def print_versions(pkg, args):
|
def print_versions(pkg, args):
|
||||||
@ -413,7 +505,7 @@ def print_versions(pkg, args):
|
|||||||
else:
|
else:
|
||||||
pad = padder(pkg.versions, 4)
|
pad = padder(pkg.versions, 4)
|
||||||
|
|
||||||
preferred = preferred_version(pkg)
|
preferred = spack.package_base.preferred_version(pkg)
|
||||||
|
|
||||||
def get_url(version):
|
def get_url(version):
|
||||||
try:
|
try:
|
||||||
|
@ -444,7 +444,7 @@ def _precedence(obj) -> int:
|
|||||||
"""Get either a 'precedence' attribute or item from an object."""
|
"""Get either a 'precedence' attribute or item from an object."""
|
||||||
precedence = getattr(obj, "precedence", None)
|
precedence = getattr(obj, "precedence", None)
|
||||||
if precedence is None:
|
if precedence is None:
|
||||||
raise KeyError(f"Couldn't get precedence from {type(obj)}")
|
return 0 # raise KeyError(f"Couldn't get precedence from {type(obj)}")
|
||||||
return precedence
|
return precedence
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user