Skip to content

Commit a8a6d94

Browse files
committed
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
1 parent 5b725a3 commit a8a6d94

File tree

4 files changed

+338
-0
lines changed

4 files changed

+338
-0
lines changed

lib/spack/spack/cmd/solve.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Copyright 2013-2019 Lawrence Livermore National Security, LLC and other
2+
# Spack Project Developers. See the top-level COPYRIGHT file for details.
3+
#
4+
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
5+
6+
from __future__ import print_function
7+
8+
import argparse
9+
10+
import spack
11+
import spack.cmd
12+
import spack.cmd.common.arguments as arguments
13+
import spack.package
14+
import spack.solver.asp as asp
15+
16+
description = "concretize a specs using an ASP solver"
17+
section = 'developer'
18+
level = 'long'
19+
20+
21+
def setup_parser(subparser):
22+
arguments.add_common_arguments(subparser, ['long', 'very_long'])
23+
subparser.add_argument(
24+
'-y', '--yaml', action='store_true', default=False,
25+
help='print concrete spec as YAML')
26+
subparser.add_argument(
27+
'-c', '--cover', action='store',
28+
default='nodes', choices=['nodes', 'edges', 'paths'],
29+
help='how extensively to traverse the DAG (default: nodes)')
30+
subparser.add_argument(
31+
'-N', '--namespaces', action='store_true', default=False,
32+
help='show fully qualified package names')
33+
subparser.add_argument(
34+
'-I', '--install-status', action='store_true', default=False,
35+
help='show install status of packages. packages can be: '
36+
'installed [+], missing and needed by an installed package [-], '
37+
'or not installed (no annotation)')
38+
subparser.add_argument(
39+
'-t', '--types', action='store_true', default=False,
40+
help='show dependency types')
41+
subparser.add_argument(
42+
'specs', nargs=argparse.REMAINDER, help="specs of packages")
43+
44+
45+
def solve(parser, args):
46+
specs = spack.cmd.parse_specs(args.specs)
47+
asp.solve(specs)

lib/spack/spack/package.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -763,6 +763,25 @@ def possible_dependencies(
763763

764764
return visited
765765

766+
def enum_constraints(self, visited=None):
767+
"""Return transitive dependency constraints on this package."""
768+
if visited is None:
769+
visited = set()
770+
visited.add(self.name)
771+
772+
names = []
773+
clauses = []
774+
775+
for name in self.dependencies:
776+
if name not in visited and not spack.spec.Spec(name).virtual:
777+
pkg = spack.repo.get(name)
778+
dvis, dnames, dclauses = pkg.enum_constraints(visited)
779+
visited |= dvis
780+
names.extend(dnames)
781+
clauses.extend(dclauses)
782+
783+
return visited
784+
766785
# package_dir and module are *class* properties (see PackageMeta),
767786
# but to make them work on instances we need these defs as well.
768787
@property

lib/spack/spack/solver/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Copyright 2013-2019 Lawrence Livermore National Security, LLC and other
2+
# Spack Project Developers. See the top-level COPYRIGHT file for details.
3+
#
4+
# SPDX-License-Identifier: (Apache-2.0 OR MIT)

lib/spack/spack/solver/asp.py

Lines changed: 268 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,268 @@
1+
# Copyright 2013-2019 Lawrence Livermore National Security, LLC and other
2+
# Spack Project Developers. See the top-level COPYRIGHT file for details.
3+
#
4+
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
5+
6+
from __future__ import print_function
7+
8+
import collections
9+
import types
10+
11+
import spack
12+
import spack.cmd
13+
import spack.spec
14+
import spack.package
15+
16+
17+
def title(name):
18+
print()
19+
print("%% %s" % name)
20+
print("% -----------------------------------------")
21+
22+
23+
def section(name):
24+
print()
25+
print("%")
26+
print("%% %s" % name)
27+
print("%")
28+
29+
30+
def _id(thing):
31+
"""Quote string if needed for it to be a valid identifier."""
32+
return '"%s"' % str(thing)
33+
34+
35+
def issequence(obj):
36+
if isinstance(obj, basestring):
37+
return False
38+
return isinstance(obj, (collections.Sequence, types.GeneratorType))
39+
40+
41+
def listify(args):
42+
if len(args) == 1 and issequence(args[0]):
43+
return list(args[0])
44+
return list(args)
45+
46+
47+
def packagize(pkg):
48+
if isinstance(pkg, spack.package.PackageMeta):
49+
return pkg
50+
return spack.repo.path.get_pkg_class(pkg)
51+
52+
53+
def specify(spec):
54+
if isinstance(spec, spack.spec.Spec):
55+
return spec
56+
return spack.spec.Spec(spec)
57+
58+
59+
class AspFunction(object):
60+
def __init__(self, name):
61+
self.name = name
62+
self.args = []
63+
64+
def __call__(self, *args):
65+
self.args[:] = args
66+
return self
67+
68+
def __str__(self):
69+
return "%s(%s)" % (
70+
self.name, ', '.join(_id(arg) for arg in self.args))
71+
72+
73+
class AspAnd(object):
74+
def __init__(self, *args):
75+
args = listify(args)
76+
self.args = args
77+
78+
def __str__(self):
79+
s = ", ".join(str(arg) for arg in self.args)
80+
return s
81+
82+
83+
class AspOr(object):
84+
def __init__(self, *args):
85+
args = listify(args)
86+
self.args = args
87+
88+
def __str__(self):
89+
return " | ".join(str(arg) for arg in self.args)
90+
91+
92+
class AspNot(object):
93+
def __init__(self, arg):
94+
self.arg = arg
95+
96+
def __str__(self):
97+
return "not %s" % self.arg
98+
99+
100+
class AspBuilder(object):
101+
def _or(self, *args):
102+
return AspOr(*args)
103+
104+
def _and(self, *args):
105+
return AspAnd(*args)
106+
107+
def _not(self, arg):
108+
return AspNot(arg)
109+
110+
def _fact(self, head):
111+
"""ASP fact (a rule without a body)."""
112+
print("%s." % head)
113+
114+
def _rule(self, head, body):
115+
"""ASP rule (an implication)."""
116+
print("%s :- %s." % (head, body))
117+
118+
def _constraint(self, body):
119+
"""ASP integrity constraint (rule with no head; can't be true)."""
120+
print(":- %s." % body)
121+
122+
def __getattr__(self, name):
123+
return AspFunction(name)
124+
125+
126+
asp = AspBuilder()
127+
128+
129+
def pkg_version_rules(pkg):
130+
pkg = packagize(pkg)
131+
asp._rule(
132+
asp._or(asp.version(pkg.name, v) for v in pkg.versions),
133+
asp.node(pkg.name))
134+
135+
136+
def spec_versions(spec):
137+
spec = specify(spec)
138+
139+
if spec.concrete:
140+
asp._rule(asp.version(spec.name, spec.version),
141+
asp.node(spec.name))
142+
else:
143+
version = spec.versions
144+
impossible, possible = [], []
145+
for v in spec.package.versions:
146+
if v.satisfies(version):
147+
possible.append(v)
148+
else:
149+
impossible.append(v)
150+
151+
if impossible:
152+
asp._rule(
153+
asp._and(asp._not(asp.version(spec.name, v))
154+
for v in impossible),
155+
asp.node(spec.name))
156+
if possible:
157+
asp._rule(
158+
asp._or(asp.version(spec.name, v) for v in possible),
159+
asp.node(spec.name))
160+
161+
162+
def pkg_rules(pkg):
163+
pkg = packagize(pkg)
164+
165+
# versions
166+
pkg_version_rules(pkg)
167+
168+
# dependencies
169+
for name, conditions in pkg.dependencies.items():
170+
for cond, dep in conditions.items():
171+
asp._fact(asp.depends_on(dep.pkg.name, dep.spec.name))
172+
173+
174+
def spec_rules(spec):
175+
asp._fact(asp.node(spec.name))
176+
spec_versions(spec)
177+
178+
# seed architecture at the root (we'll propagate later)
179+
# TODO: use better semantics.
180+
arch = spack.spec.ArchSpec(spack.architecture.sys_type())
181+
spec_arch = spec.architecture
182+
if spec_arch:
183+
if spec_arch.platform:
184+
arch.platform = spec_arch.platform
185+
if spec_arch.os:
186+
arch.os = spec_arch.os
187+
if spec_arch.target:
188+
arch.target = spec_arch.target
189+
asp._fact(asp.arch_platform(spec.name, arch.platform))
190+
asp._fact(asp.arch_os(spec.name, arch.os))
191+
asp._fact(asp.arch_target(spec.name, arch.target))
192+
193+
# TODO
194+
# dependencies
195+
# compiler
196+
# external_path
197+
# external_module
198+
# compiler_flags
199+
# namespace
200+
201+
#
202+
# These are handwritten parts for the Spack ASP model.
203+
#
204+
205+
206+
#: generate the problem space, establish cardinality constraints
207+
_generate = """\
208+
% One version, arch, etc. per package
209+
{ version(P, V) : version(P, V) } = 1 :- node(P).
210+
{ arch_platform(P, A) : arch_platform(P, A) } = 1 :- node(P).
211+
{ arch_os(P, A) : arch_os(P, A) } = 1 :- node(P).
212+
{ arch_target(P, T) : arch_target(P, T) } = 1 :- node(P).
213+
"""
214+
215+
#: define the rules of Spack concretization
216+
_define = """\
217+
% dependencies imply new nodes.
218+
node(D) :- node(P), depends_on(P, D).
219+
220+
% propagate platform, os, target downwards
221+
arch_platform(D, A) :- node(D), depends_on(P, D), arch_platform(P, A).
222+
arch_os(D, A) :- node(D), depends_on(P, D), arch_os(P, A).
223+
arch_target(D, A) :- node(D), depends_on(P, D), arch_target(P, A).
224+
"""
225+
226+
#: what parts of the model to display to read models back in
227+
_display = """\
228+
#show node/1.
229+
#show depends_on/2.
230+
#show version/2.
231+
#show arch_platform/2.
232+
#show arch_os/2.
233+
#show arch_target/2.
234+
"""
235+
236+
237+
def solve(specs):
238+
"""Solve for a stable model of specs.
239+
240+
Arguments:
241+
specs (list): list of Specs to solve.
242+
"""
243+
244+
# get list of all possible dependencies
245+
pkg_names = set(spec.fullname for spec in specs)
246+
pkgs = [spack.repo.path.get_pkg_class(name) for name in pkg_names]
247+
pkgs = spack.package.possible_dependencies(*pkgs)
248+
249+
title("Generate")
250+
print(_generate)
251+
252+
title("Define")
253+
print(_define)
254+
255+
title("Package Constraints")
256+
for pkg in pkgs:
257+
section(pkg)
258+
pkg_rules(pkg)
259+
260+
title("Spec Constraints")
261+
for spec in specs:
262+
section(str(spec))
263+
spec_rules(spec)
264+
265+
title("Display")
266+
print(_display)
267+
print()
268+
print()

0 commit comments

Comments
 (0)