# Copyright Spack Project Developers. See COPYRIGHT file for details.
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
import argparse
import re
import sys
import spack
import spack.cmd
import spack.cmd.spec
import spack.config
import spack.environment
import spack.hash_types as ht
import spack.llnl.util.tty as tty
import spack.llnl.util.tty.color as color
import spack.package_base
import spack.solver.asp as asp
import spack.spec
description = "concretize a specs using an ASP solver"
section = "developer"
level = "long"
#: output options
show_options = ("asp", "opt", "output", "solutions")
[docs]
def setup_parser(subparser: argparse.ArgumentParser) -> None:
# Solver arguments
subparser.add_argument(
"--show",
action="store",
default="opt,solutions",
help="select outputs\n\ncomma-separated list of:\n"
" asp asp program text\n"
" opt optimization criteria for best model\n"
" output raw clingo output\n"
" solutions models found by asp program\n"
" all all of the above",
)
subparser.add_argument(
"--timers",
action="store_true",
default=False,
help="print out timers for different solve phases",
)
subparser.add_argument(
"--stats", action="store_true", default=False, help="print out statistics from clingo"
)
spack.cmd.spec.setup_parser(subparser)
def _process_result(result, show, required_format, kwargs):
opt, _, _ = min(result.answers)
if ("opt" in show) and (not required_format):
tty.msg("Best of %d considered solutions." % result.nmodels)
print()
maxlen = max(len(s.name) for s in result.criteria)
color.cprint("@*{ Priority Value Criterion}")
for i, criterion in enumerate(result.criteria, 1):
value = f"@K{{{criterion.value:>5}}}"
grey_out = True
if criterion.value > 0:
value = f"@*{{{criterion.value:>5}}}"
grey_out = False
if grey_out:
lc = "@K"
elif criterion.kind == asp.OptimizationKind.CONCRETE:
lc = "@b"
elif criterion.kind == asp.OptimizationKind.BUILD:
lc = "@g"
else:
lc = "@y"
color.cprint(f" @K{{{i:8}}} {value} {lc}{{{criterion.name:<{maxlen}}}}")
print()
print()
color.cprint(" @*{Legend:}")
color.cprint(" @g{Specs to be built}")
color.cprint(" @b{Reused specs}")
color.cprint(" @y{Other criteria}")
print()
# dump the solutions as concretized specs
if "solutions" in show:
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:
tty.msg(asp.Result.format_unsolved(result.unsolved_specs))
[docs]
def solve(parser, args):
# these are the same options as `spack spec`
install_status_fn = spack.spec.Spec.install_status
fmt = spack.spec.DISPLAY_FORMAT
if args.namespaces:
fmt = "{namespace}." + fmt
kwargs = {
"cover": args.cover,
"format": fmt,
"hashlen": None if args.very_long else 7,
"show_types": args.types,
"status_fn": install_status_fn if args.install_status else None,
"hashes": args.long or args.very_long,
"highlight_version_fn": (
spack.package_base.non_preferred_version if args.non_defaults else None
),
"highlight_variant_fn": (
spack.package_base.non_default_variant if args.non_defaults else None
),
}
# process output options
show = re.split(r"\s*,\s*", args.show)
if "all" in show:
show = show_options
for d in show:
if d not in show_options:
raise ValueError(
"Invalid option for '--show': '%s'\nchoose from: (%s)"
% (d, ", ".join(show_options + ("all",)))
)
# Format required for the output (JSON, YAML or None)
required_format = args.format
# If we have an active environment, pick the specs from there
env = spack.environment.active_environment()
if args.specs:
specs = spack.cmd.parse_specs(args.specs)
elif env:
specs = list(env.user_specs)
else:
args.subparser.error("requires at least one spec or an active environment")
solver = asp.Solver()
output = sys.stdout if "asp" in show else None
setup_only = set(show) == {"asp"}
unify = spack.config.get("concretizer:unify")
allow_deprecated = spack.config.get("config:deprecated", False)
if unify == "when_possible":
for idx, result in enumerate(
solver.solve_in_rounds(
specs,
out=output,
timers=args.timers,
stats=args.stats,
allow_deprecated=allow_deprecated,
)
):
if "solutions" in show:
tty.msg("ROUND {0}".format(idx))
tty.msg("")
else:
print("% END ROUND {0}\n".format(idx))
if not setup_only:
_process_result(result, show, required_format, kwargs)
elif unify:
# set up solver parameters
# Note: reuse and other concretizer prefs are passed as configuration
result = solver.solve(
specs,
out=output,
timers=args.timers,
stats=args.stats,
setup_only=setup_only,
allow_deprecated=allow_deprecated,
)
if not setup_only:
_process_result(result, show, required_format, kwargs)
else:
for spec in specs:
tty.msg("SOLVING SPEC:", spec)
result = solver.solve(
[spec],
out=output,
timers=args.timers,
stats=args.stats,
setup_only=setup_only,
allow_deprecated=allow_deprecated,
)
if not setup_only:
_process_result(result, show, required_format, kwargs)