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
|
||||
|
||||
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),
|
||||
# but to make them work on instances we need these defs as well.
|
||||
@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