summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorTodd Gamblin <tgamblin@llnl.gov>2024-06-03 22:29:14 +0200
committerGitHub <noreply@github.com>2024-06-03 13:29:14 -0700
commit85f13442d2a7486daba81fdd9a3b6a1182ba11f6 (patch)
treee048fb9bfaf9d7eccf03d4bca8ac8cb19e5d8917 /lib
parentf478a65635379a783615462c4417f8464cc03a74 (diff)
downloadspack-85f13442d2a7486daba81fdd9a3b6a1182ba11f6.tar.gz
spack-85f13442d2a7486daba81fdd9a3b6a1182ba11f6.tar.bz2
spack-85f13442d2a7486daba81fdd9a3b6a1182ba11f6.tar.xz
spack-85f13442d2a7486daba81fdd9a3b6a1182ba11f6.zip
Consolidate concretization output for environments (#44489)
When Spack concretizes environments, it prints every (newly concretized) root spec individually with all of its dependencies. For most reasonably sized environments, this is too much output. This is true for three commands: * `spack concretize` when concretizing an environment with newly added specs * `spack install` when installing an environment with newly added specs * `spack spec` with no arguments in an environment The output dates back to before we had unified environments or nicer spec traversal routines, and we can improve it. This PR makes environment concretization output analogous to what we do for regular specs. Just like `spack spec` for a single spec, we show all root specs with no indentation, so you can easily see the specs you explicitly requested. Dependencies are shown: 1. With indentation according to their depth in a breadth-first traversal starting at the roots; 2. Only once if they appear on paths from multiple roots So, the default is now consistent with `spack spec` for one spec--it's `--cover=nodes`. i.e., if there are 100 specs in your environment, you'll get 100 lines of output. If you want to see more details, you can do that with `spack spec` using the arguments you're already familiar with. For example, if you wanted to see dependency types and *all* dependencies, you could use `spack spec -l --cover=edges`. Or you could add deptypes and namespaces with, e.g. `spack spec -ltN`. With no arguments in an environment, `spack spec` concretizes (if necessary) and shows the concretized environment. If you run `spack concretize` *first*, inspecting the environment repeatedly with `spack spec` will be fast, as everything is already in the `spack.lock` file. - [x] factor most logic of `Spec.tree()` out of `Spec` class into `spack.spec.tree()`, which can take multiple specs as roots. - [x] make `Spec.tree()` call `spack.spec.tree()` - [x] `spack.environment.display_specs()` now uses `spack.spec.tree()` - [x] Update `spack concretize` - [x] Update `spack install` - [x] Update `spack spec` to call `spack.spec.tree()` for environments. - [x] Continue to output specs individually for `spack spec` when using `--yaml` or `--json`
Diffstat (limited to 'lib')
-rw-r--r--lib/spack/spack/cmd/concretize.py9
-rw-r--r--lib/spack/spack/cmd/install.py5
-rw-r--r--lib/spack/spack/cmd/solve.py19
-rw-r--r--lib/spack/spack/cmd/spec.py10
-rw-r--r--lib/spack/spack/environment/environment.py31
-rw-r--r--lib/spack/spack/spec.py174
6 files changed, 161 insertions, 87 deletions
diff --git a/lib/spack/spack/cmd/concretize.py b/lib/spack/spack/cmd/concretize.py
index e07c0eb18c..061a03fc4d 100644
--- a/lib/spack/spack/cmd/concretize.py
+++ b/lib/spack/spack/cmd/concretize.py
@@ -3,6 +3,9 @@
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
+import llnl.util.tty as tty
+from llnl.string import plural
+
import spack.cmd
import spack.cmd.common.arguments
import spack.environment as ev
@@ -43,5 +46,9 @@ def concretize(parser, args):
with env.write_transaction():
concretized_specs = env.concretize(force=args.force, tests=tests)
if not args.quiet:
- ev.display_specs(concretized_specs)
+ if concretized_specs:
+ tty.msg(f"Concretized {plural(len(concretized_specs), 'spec')}:")
+ ev.display_specs([concrete for _, concrete in concretized_specs])
+ else:
+ tty.msg("No new specs to concretize.")
env.write()
diff --git a/lib/spack/spack/cmd/install.py b/lib/spack/spack/cmd/install.py
index 69de44df57..8458e7ce05 100644
--- a/lib/spack/spack/cmd/install.py
+++ b/lib/spack/spack/cmd/install.py
@@ -10,6 +10,7 @@ import sys
from typing import List
import llnl.util.filesystem as fs
+from llnl.string import plural
from llnl.util import lang, tty
import spack.build_environment
@@ -375,7 +376,9 @@ def _maybe_add_and_concretize(args, env, specs):
# `spack concretize`
tests = compute_tests_install_kwargs(env.user_specs, args.test)
concretized_specs = env.concretize(tests=tests)
- ev.display_specs(concretized_specs)
+ if concretized_specs:
+ tty.msg(f"Concretized {plural(len(concretized_specs), 'spec')}")
+ ev.display_specs([concrete for _, concrete in concretized_specs])
# save view regeneration for later, so that we only do it
# once, as it can be slow.
diff --git a/lib/spack/spack/cmd/solve.py b/lib/spack/spack/cmd/solve.py
index 165ede0dbc..2d6197f758 100644
--- a/lib/spack/spack/cmd/solve.py
+++ b/lib/spack/spack/cmd/solve.py
@@ -114,15 +114,16 @@ def _process_result(result, show, required_format, kwargs):
# dump the solutions as concretized specs
if "solutions" in show:
- for spec in result.specs:
- # With -y, just print YAML to output.
- if required_format == "yaml":
- # use write because to_yaml already has a newline.
- sys.stdout.write(spec.to_yaml(hash=ht.dag_hash))
- elif required_format == "json":
- sys.stdout.write(spec.to_json(hash=ht.dag_hash))
- else:
- sys.stdout.write(spec.tree(color=sys.stdout.isatty(), **kwargs))
+ if required_format:
+ for spec in result.specs:
+ # With -y, just print YAML to output.
+ if required_format == "yaml":
+ # use write because to_yaml already has a newline.
+ sys.stdout.write(spec.to_yaml(hash=ht.dag_hash))
+ elif required_format == "json":
+ sys.stdout.write(spec.to_json(hash=ht.dag_hash))
+ else:
+ sys.stdout.write(spack.spec.tree(result.specs, color=sys.stdout.isatty(), **kwargs))
print()
if result.unsolved_specs and "solutions" in show:
diff --git a/lib/spack/spack/cmd/spec.py b/lib/spack/spack/cmd/spec.py
index e2d5cb1055..ae08e1f977 100644
--- a/lib/spack/spack/cmd/spec.py
+++ b/lib/spack/spack/cmd/spec.py
@@ -105,11 +105,19 @@ def spec(parser, args):
if env:
env.concretize()
specs = env.concretized_specs()
+
+ # environments are printed together in a combined tree() invocation,
+ # except when using --yaml or --json, which we print spec by spec below.
+ if not args.format:
+ tree_kwargs["key"] = spack.traverse.by_dag_hash
+ tree_kwargs["hashes"] = args.long or args.very_long
+ print(spack.spec.tree([concrete for _, concrete in specs], **tree_kwargs))
+ return
else:
tty.die("spack spec requires at least one spec or an active environment")
for input, output in specs:
- # With -y, just print YAML to output.
+ # With --yaml or --json, just print the raw specs to output
if args.format:
if args.format == "yaml":
# use write because to_yaml already has a newline.
diff --git a/lib/spack/spack/environment/environment.py b/lib/spack/spack/environment/environment.py
index 22a1a0d322..54a9ab1795 100644
--- a/lib/spack/spack/environment/environment.py
+++ b/lib/spack/spack/environment/environment.py
@@ -24,6 +24,7 @@ import llnl.util.tty.color as clr
from llnl.util.link_tree import ConflictingSpecsError
from llnl.util.symlink import readlink, symlink
+import spack.cmd
import spack.compilers
import spack.concretize
import spack.config
@@ -2473,27 +2474,21 @@ def _equiv_dict(first, second):
return same_values and same_keys_with_same_overrides
-def display_specs(concretized_specs):
- """Displays the list of specs returned by `Environment.concretize()`.
+def display_specs(specs):
+ """Displays a list of specs traversed breadth-first, covering nodes, with install status.
Args:
- concretized_specs (list): list of specs returned by
- `Environment.concretize()`
+ specs (list): list of specs
"""
-
- def _tree_to_display(spec):
- return spec.tree(
- recurse_dependencies=True,
- format=spack.spec.DISPLAY_FORMAT,
- status_fn=spack.spec.Spec.install_status,
- hashlen=7,
- hashes=True,
- )
-
- for user_spec, concrete_spec in concretized_specs:
- tty.msg("Concretized {0}".format(user_spec))
- sys.stdout.write(_tree_to_display(concrete_spec))
- print("")
+ tree_string = spack.spec.tree(
+ specs,
+ format=spack.spec.DISPLAY_FORMAT,
+ hashes=True,
+ hashlen=7,
+ status_fn=spack.spec.Spec.install_status,
+ key=traverse.by_dag_hash,
+ )
+ print(tree_string)
def _concretize_from_constraints(spec_constraints, tests=False):
diff --git a/lib/spack/spack/spec.py b/lib/spack/spack/spec.py
index fe859e1fc4..5bdb00173d 100644
--- a/lib/spack/spack/spec.py
+++ b/lib/spack/spack/spec.py
@@ -1287,6 +1287,102 @@ class SpecBuildInterface(lang.ObjectWrapper):
return self.wrapped_obj.copy(*args, **kwargs)
+def tree(
+ specs: List["spack.spec.Spec"],
+ *,
+ color: Optional[bool] = None,
+ depth: bool = False,
+ hashes: bool = False,
+ hashlen: Optional[int] = None,
+ cover: str = "nodes",
+ indent: int = 0,
+ format: str = DEFAULT_FORMAT,
+ deptypes: Union[Tuple[str, ...], str] = "all",
+ show_types: bool = False,
+ depth_first: bool = False,
+ recurse_dependencies: bool = True,
+ status_fn: Optional[Callable[["Spec"], InstallStatus]] = None,
+ prefix: Optional[Callable[["Spec"], str]] = None,
+ key=id,
+) -> str:
+ """Prints out specs and their dependencies, tree-formatted with indentation.
+
+ Status function may either output a boolean or an InstallStatus
+
+ Args:
+ color: if True, always colorize the tree. If False, don't colorize the tree. If None,
+ use the default from llnl.tty.color
+ depth: print the depth from the root
+ hashes: if True, print the hash of each node
+ hashlen: length of the hash to be printed
+ cover: either "nodes" or "edges"
+ indent: extra indentation for the tree being printed
+ format: format to be used to print each node
+ deptypes: dependency types to be represented in the tree
+ show_types: if True, show the (merged) dependency type of a node
+ depth_first: if True, traverse the DAG depth first when representing it as a tree
+ recurse_dependencies: if True, recurse on dependencies
+ status_fn: optional callable that takes a node as an argument and return its
+ installation status
+ prefix: optional callable that takes a node as an argument and return its
+ installation prefix
+ """
+ out = ""
+
+ if color is None:
+ color = clr.get_color_when()
+
+ for d, dep_spec in traverse.traverse_tree(
+ sorted(specs), cover=cover, deptype=deptypes, depth_first=depth_first, key=key
+ ):
+ node = dep_spec.spec
+
+ if prefix is not None:
+ out += prefix(node)
+ out += " " * indent
+
+ if depth:
+ out += "%-4d" % d
+
+ if status_fn:
+ status = status_fn(node)
+ if status in list(InstallStatus):
+ out += clr.colorize(status.value, color=color)
+ elif status:
+ out += clr.colorize("@g{[+]} ", color=color)
+ else:
+ out += clr.colorize("@r{[-]} ", color=color)
+
+ if hashes:
+ out += clr.colorize("@K{%s} ", color=color) % node.dag_hash(hashlen)
+
+ if show_types:
+ if cover == "nodes":
+ # when only covering nodes, we merge dependency types
+ # from all dependents before showing them.
+ depflag = 0
+ for ds in node.edges_from_dependents():
+ depflag |= ds.depflag
+ else:
+ # when covering edges or paths, we show dependency
+ # types only for the edge through which we visited
+ depflag = dep_spec.depflag
+
+ type_chars = dt.flag_to_chars(depflag)
+ out += "[%s] " % type_chars
+
+ out += " " * d
+ if d > 0:
+ out += "^"
+ out += node.format(format, color=color) + "\n"
+
+ # Check if we wanted just the first line
+ if not recurse_dependencies:
+ break
+
+ return out
+
+
@lang.lazy_lexicographic_ordering(set_hash=False)
class Spec:
#: Cache for spec's prefix, computed lazily in the corresponding property
@@ -4604,13 +4700,14 @@ class Spec:
recurse_dependencies: bool = True,
status_fn: Optional[Callable[["Spec"], InstallStatus]] = None,
prefix: Optional[Callable[["Spec"], str]] = None,
+ key=id,
) -> str:
- """Prints out this spec and its dependencies, tree-formatted
- with indentation.
+ """Prints out this spec and its dependencies, tree-formatted with indentation.
- Status function may either output a boolean or an InstallStatus
+ See multi-spec ``spack.spec.tree()`` function for details.
Args:
+ specs: List of specs to format.
color: if True, always colorize the tree. If False, don't colorize the tree. If None,
use the default from llnl.tty.color
depth: print the depth from the root
@@ -4628,60 +4725,23 @@ class Spec:
prefix: optional callable that takes a node as an argument and return its
installation prefix
"""
- out = ""
-
- if color is None:
- color = clr.get_color_when()
-
- for d, dep_spec in traverse.traverse_tree(
- [self], cover=cover, deptype=deptypes, depth_first=depth_first
- ):
- node = dep_spec.spec
-
- if prefix is not None:
- out += prefix(node)
- out += " " * indent
-
- if depth:
- out += "%-4d" % d
-
- if status_fn:
- status = status_fn(node)
- if status in list(InstallStatus):
- out += clr.colorize(status.value, color=color)
- elif status:
- out += clr.colorize("@g{[+]} ", color=color)
- else:
- out += clr.colorize("@r{[-]} ", color=color)
-
- if hashes:
- out += clr.colorize("@K{%s} ", color=color) % node.dag_hash(hashlen)
-
- if show_types:
- if cover == "nodes":
- # when only covering nodes, we merge dependency types
- # from all dependents before showing them.
- depflag = 0
- for ds in node.edges_from_dependents():
- depflag |= ds.depflag
- else:
- # when covering edges or paths, we show dependency
- # types only for the edge through which we visited
- depflag = dep_spec.depflag
-
- type_chars = dt.flag_to_chars(depflag)
- out += "[%s] " % type_chars
-
- out += " " * d
- if d > 0:
- out += "^"
- out += node.format(format, color=color) + "\n"
-
- # Check if we wanted just the first line
- if not recurse_dependencies:
- break
-
- return out
+ return tree(
+ [self],
+ color=color,
+ depth=depth,
+ hashes=hashes,
+ hashlen=hashlen,
+ cover=cover,
+ indent=indent,
+ format=format,
+ deptypes=deptypes,
+ show_types=show_types,
+ depth_first=depth_first,
+ recurse_dependencies=recurse_dependencies,
+ status_fn=status_fn,
+ prefix=prefix,
+ key=key,
+ )
def __repr__(self):
return str(self)