concretizer: beginnings of solve() command
- `spack solve` command outputs a really basic ASP program that handles unconditional dependencies, architecture and versions - doesn't yet handle conflicts, picking latest versions, preferred versions, compilers, etc. - doesn't handle variants
This commit is contained in:
parent
5b725a37bc
commit
a8a6d943d6
4 changed files with 338 additions and 0 deletions
47
lib/spack/spack/cmd/solve.py
Normal file
47
lib/spack/spack/cmd/solve.py
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
# Copyright 2013-2019 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)
|
||||||
|
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
import spack
|
||||||
|
import spack.cmd
|
||||||
|
import spack.cmd.common.arguments as arguments
|
||||||
|
import spack.package
|
||||||
|
import spack.solver.asp as asp
|
||||||
|
|
||||||
|
description = "concretize a specs using an ASP solver"
|
||||||
|
section = 'developer'
|
||||||
|
level = 'long'
|
||||||
|
|
||||||
|
|
||||||
|
def setup_parser(subparser):
|
||||||
|
arguments.add_common_arguments(subparser, ['long', 'very_long'])
|
||||||
|
subparser.add_argument(
|
||||||
|
'-y', '--yaml', action='store_true', default=False,
|
||||||
|
help='print concrete spec as YAML')
|
||||||
|
subparser.add_argument(
|
||||||
|
'-c', '--cover', action='store',
|
||||||
|
default='nodes', choices=['nodes', 'edges', 'paths'],
|
||||||
|
help='how extensively to traverse the DAG (default: nodes)')
|
||||||
|
subparser.add_argument(
|
||||||
|
'-N', '--namespaces', action='store_true', default=False,
|
||||||
|
help='show fully qualified package names')
|
||||||
|
subparser.add_argument(
|
||||||
|
'-I', '--install-status', action='store_true', default=False,
|
||||||
|
help='show install status of packages. packages can be: '
|
||||||
|
'installed [+], missing and needed by an installed package [-], '
|
||||||
|
'or not installed (no annotation)')
|
||||||
|
subparser.add_argument(
|
||||||
|
'-t', '--types', action='store_true', default=False,
|
||||||
|
help='show dependency types')
|
||||||
|
subparser.add_argument(
|
||||||
|
'specs', nargs=argparse.REMAINDER, help="specs of packages")
|
||||||
|
|
||||||
|
|
||||||
|
def solve(parser, args):
|
||||||
|
specs = spack.cmd.parse_specs(args.specs)
|
||||||
|
asp.solve(specs)
|
|
@ -763,6 +763,25 @@ def possible_dependencies(
|
||||||
|
|
||||||
return visited
|
return visited
|
||||||
|
|
||||||
|
def enum_constraints(self, visited=None):
|
||||||
|
"""Return transitive dependency constraints on this package."""
|
||||||
|
if visited is None:
|
||||||
|
visited = set()
|
||||||
|
visited.add(self.name)
|
||||||
|
|
||||||
|
names = []
|
||||||
|
clauses = []
|
||||||
|
|
||||||
|
for name in self.dependencies:
|
||||||
|
if name not in visited and not spack.spec.Spec(name).virtual:
|
||||||
|
pkg = spack.repo.get(name)
|
||||||
|
dvis, dnames, dclauses = pkg.enum_constraints(visited)
|
||||||
|
visited |= dvis
|
||||||
|
names.extend(dnames)
|
||||||
|
clauses.extend(dclauses)
|
||||||
|
|
||||||
|
return visited
|
||||||
|
|
||||||
# package_dir and module are *class* properties (see PackageMeta),
|
# package_dir and module are *class* properties (see PackageMeta),
|
||||||
# but to make them work on instances we need these defs as well.
|
# but to make them work on instances we need these defs as well.
|
||||||
@property
|
@property
|
||||||
|
|
4
lib/spack/spack/solver/__init__.py
Normal file
4
lib/spack/spack/solver/__init__.py
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
# Copyright 2013-2019 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)
|
268
lib/spack/spack/solver/asp.py
Normal file
268
lib/spack/spack/solver/asp.py
Normal file
|
@ -0,0 +1,268 @@
|
||||||
|
# Copyright 2013-2019 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)
|
||||||
|
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
|
import collections
|
||||||
|
import types
|
||||||
|
|
||||||
|
import spack
|
||||||
|
import spack.cmd
|
||||||
|
import spack.spec
|
||||||
|
import spack.package
|
||||||
|
|
||||||
|
|
||||||
|
def title(name):
|
||||||
|
print()
|
||||||
|
print("%% %s" % name)
|
||||||
|
print("% -----------------------------------------")
|
||||||
|
|
||||||
|
|
||||||
|
def section(name):
|
||||||
|
print()
|
||||||
|
print("%")
|
||||||
|
print("%% %s" % name)
|
||||||
|
print("%")
|
||||||
|
|
||||||
|
|
||||||
|
def _id(thing):
|
||||||
|
"""Quote string if needed for it to be a valid identifier."""
|
||||||
|
return '"%s"' % str(thing)
|
||||||
|
|
||||||
|
|
||||||
|
def issequence(obj):
|
||||||
|
if isinstance(obj, basestring):
|
||||||
|
return False
|
||||||
|
return isinstance(obj, (collections.Sequence, types.GeneratorType))
|
||||||
|
|
||||||
|
|
||||||
|
def listify(args):
|
||||||
|
if len(args) == 1 and issequence(args[0]):
|
||||||
|
return list(args[0])
|
||||||
|
return list(args)
|
||||||
|
|
||||||
|
|
||||||
|
def packagize(pkg):
|
||||||
|
if isinstance(pkg, spack.package.PackageMeta):
|
||||||
|
return pkg
|
||||||
|
return spack.repo.path.get_pkg_class(pkg)
|
||||||
|
|
||||||
|
|
||||||
|
def specify(spec):
|
||||||
|
if isinstance(spec, spack.spec.Spec):
|
||||||
|
return spec
|
||||||
|
return spack.spec.Spec(spec)
|
||||||
|
|
||||||
|
|
||||||
|
class AspFunction(object):
|
||||||
|
def __init__(self, name):
|
||||||
|
self.name = name
|
||||||
|
self.args = []
|
||||||
|
|
||||||
|
def __call__(self, *args):
|
||||||
|
self.args[:] = args
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "%s(%s)" % (
|
||||||
|
self.name, ', '.join(_id(arg) for arg in self.args))
|
||||||
|
|
||||||
|
|
||||||
|
class AspAnd(object):
|
||||||
|
def __init__(self, *args):
|
||||||
|
args = listify(args)
|
||||||
|
self.args = args
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
s = ", ".join(str(arg) for arg in self.args)
|
||||||
|
return s
|
||||||
|
|
||||||
|
|
||||||
|
class AspOr(object):
|
||||||
|
def __init__(self, *args):
|
||||||
|
args = listify(args)
|
||||||
|
self.args = args
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return " | ".join(str(arg) for arg in self.args)
|
||||||
|
|
||||||
|
|
||||||
|
class AspNot(object):
|
||||||
|
def __init__(self, arg):
|
||||||
|
self.arg = arg
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "not %s" % self.arg
|
||||||
|
|
||||||
|
|
||||||
|
class AspBuilder(object):
|
||||||
|
def _or(self, *args):
|
||||||
|
return AspOr(*args)
|
||||||
|
|
||||||
|
def _and(self, *args):
|
||||||
|
return AspAnd(*args)
|
||||||
|
|
||||||
|
def _not(self, arg):
|
||||||
|
return AspNot(arg)
|
||||||
|
|
||||||
|
def _fact(self, head):
|
||||||
|
"""ASP fact (a rule without a body)."""
|
||||||
|
print("%s." % head)
|
||||||
|
|
||||||
|
def _rule(self, head, body):
|
||||||
|
"""ASP rule (an implication)."""
|
||||||
|
print("%s :- %s." % (head, body))
|
||||||
|
|
||||||
|
def _constraint(self, body):
|
||||||
|
"""ASP integrity constraint (rule with no head; can't be true)."""
|
||||||
|
print(":- %s." % body)
|
||||||
|
|
||||||
|
def __getattr__(self, name):
|
||||||
|
return AspFunction(name)
|
||||||
|
|
||||||
|
|
||||||
|
asp = AspBuilder()
|
||||||
|
|
||||||
|
|
||||||
|
def pkg_version_rules(pkg):
|
||||||
|
pkg = packagize(pkg)
|
||||||
|
asp._rule(
|
||||||
|
asp._or(asp.version(pkg.name, v) for v in pkg.versions),
|
||||||
|
asp.node(pkg.name))
|
||||||
|
|
||||||
|
|
||||||
|
def spec_versions(spec):
|
||||||
|
spec = specify(spec)
|
||||||
|
|
||||||
|
if spec.concrete:
|
||||||
|
asp._rule(asp.version(spec.name, spec.version),
|
||||||
|
asp.node(spec.name))
|
||||||
|
else:
|
||||||
|
version = spec.versions
|
||||||
|
impossible, possible = [], []
|
||||||
|
for v in spec.package.versions:
|
||||||
|
if v.satisfies(version):
|
||||||
|
possible.append(v)
|
||||||
|
else:
|
||||||
|
impossible.append(v)
|
||||||
|
|
||||||
|
if impossible:
|
||||||
|
asp._rule(
|
||||||
|
asp._and(asp._not(asp.version(spec.name, v))
|
||||||
|
for v in impossible),
|
||||||
|
asp.node(spec.name))
|
||||||
|
if possible:
|
||||||
|
asp._rule(
|
||||||
|
asp._or(asp.version(spec.name, v) for v in possible),
|
||||||
|
asp.node(spec.name))
|
||||||
|
|
||||||
|
|
||||||
|
def pkg_rules(pkg):
|
||||||
|
pkg = packagize(pkg)
|
||||||
|
|
||||||
|
# versions
|
||||||
|
pkg_version_rules(pkg)
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
for name, conditions in pkg.dependencies.items():
|
||||||
|
for cond, dep in conditions.items():
|
||||||
|
asp._fact(asp.depends_on(dep.pkg.name, dep.spec.name))
|
||||||
|
|
||||||
|
|
||||||
|
def spec_rules(spec):
|
||||||
|
asp._fact(asp.node(spec.name))
|
||||||
|
spec_versions(spec)
|
||||||
|
|
||||||
|
# seed architecture at the root (we'll propagate later)
|
||||||
|
# TODO: use better semantics.
|
||||||
|
arch = spack.spec.ArchSpec(spack.architecture.sys_type())
|
||||||
|
spec_arch = spec.architecture
|
||||||
|
if spec_arch:
|
||||||
|
if spec_arch.platform:
|
||||||
|
arch.platform = spec_arch.platform
|
||||||
|
if spec_arch.os:
|
||||||
|
arch.os = spec_arch.os
|
||||||
|
if spec_arch.target:
|
||||||
|
arch.target = spec_arch.target
|
||||||
|
asp._fact(asp.arch_platform(spec.name, arch.platform))
|
||||||
|
asp._fact(asp.arch_os(spec.name, arch.os))
|
||||||
|
asp._fact(asp.arch_target(spec.name, arch.target))
|
||||||
|
|
||||||
|
# TODO
|
||||||
|
# dependencies
|
||||||
|
# compiler
|
||||||
|
# external_path
|
||||||
|
# external_module
|
||||||
|
# compiler_flags
|
||||||
|
# namespace
|
||||||
|
|
||||||
|
#
|
||||||
|
# These are handwritten parts for the Spack ASP model.
|
||||||
|
#
|
||||||
|
|
||||||
|
|
||||||
|
#: generate the problem space, establish cardinality constraints
|
||||||
|
_generate = """\
|
||||||
|
% One version, arch, etc. per package
|
||||||
|
{ version(P, V) : version(P, V) } = 1 :- node(P).
|
||||||
|
{ arch_platform(P, A) : arch_platform(P, A) } = 1 :- node(P).
|
||||||
|
{ arch_os(P, A) : arch_os(P, A) } = 1 :- node(P).
|
||||||
|
{ arch_target(P, T) : arch_target(P, T) } = 1 :- node(P).
|
||||||
|
"""
|
||||||
|
|
||||||
|
#: define the rules of Spack concretization
|
||||||
|
_define = """\
|
||||||
|
% dependencies imply new nodes.
|
||||||
|
node(D) :- node(P), depends_on(P, D).
|
||||||
|
|
||||||
|
% propagate platform, os, target downwards
|
||||||
|
arch_platform(D, A) :- node(D), depends_on(P, D), arch_platform(P, A).
|
||||||
|
arch_os(D, A) :- node(D), depends_on(P, D), arch_os(P, A).
|
||||||
|
arch_target(D, A) :- node(D), depends_on(P, D), arch_target(P, A).
|
||||||
|
"""
|
||||||
|
|
||||||
|
#: what parts of the model to display to read models back in
|
||||||
|
_display = """\
|
||||||
|
#show node/1.
|
||||||
|
#show depends_on/2.
|
||||||
|
#show version/2.
|
||||||
|
#show arch_platform/2.
|
||||||
|
#show arch_os/2.
|
||||||
|
#show arch_target/2.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def solve(specs):
|
||||||
|
"""Solve for a stable model of specs.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
specs (list): list of Specs to solve.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# get list of all possible dependencies
|
||||||
|
pkg_names = set(spec.fullname for spec in specs)
|
||||||
|
pkgs = [spack.repo.path.get_pkg_class(name) for name in pkg_names]
|
||||||
|
pkgs = spack.package.possible_dependencies(*pkgs)
|
||||||
|
|
||||||
|
title("Generate")
|
||||||
|
print(_generate)
|
||||||
|
|
||||||
|
title("Define")
|
||||||
|
print(_define)
|
||||||
|
|
||||||
|
title("Package Constraints")
|
||||||
|
for pkg in pkgs:
|
||||||
|
section(pkg)
|
||||||
|
pkg_rules(pkg)
|
||||||
|
|
||||||
|
title("Spec Constraints")
|
||||||
|
for spec in specs:
|
||||||
|
section(str(spec))
|
||||||
|
spec_rules(spec)
|
||||||
|
|
||||||
|
title("Display")
|
||||||
|
print(_display)
|
||||||
|
print()
|
||||||
|
print()
|
Loading…
Reference in a new issue