summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
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)