summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorMassimiliano Culpo <massimiliano.culpo@gmail.com>2020-10-26 20:37:38 +0100
committerTodd Gamblin <tgamblin@llnl.gov>2020-11-17 10:04:13 -0800
commit1cdee03c4beae49cbc9c07ca45ec77882679717d (patch)
tree1d565e23533d127a327a33246b219c4a3c1204c3 /lib
parent2595b58503f390dab2b4a53791afcfef36e214b2 (diff)
downloadspack-1cdee03c4beae49cbc9c07ca45ec77882679717d.tar.gz
spack-1cdee03c4beae49cbc9c07ca45ec77882679717d.tar.bz2
spack-1cdee03c4beae49cbc9c07ca45ec77882679717d.tar.xz
spack-1cdee03c4beae49cbc9c07ca45ec77882679717d.zip
concretizer: add conflict rules from packages
Conflict rules from packages are added as integrity constraints in the ASP formulation. Most of the code to generate them has been reused from PyclingoDriver.rules
Diffstat (limited to 'lib')
-rw-r--r--lib/spack/spack/solver/asp.py80
-rw-r--r--lib/spack/spack/test/concretize.py18
-rw-r--r--lib/spack/spack/test/conftest.py2
3 files changed, 72 insertions, 28 deletions
diff --git a/lib/spack/spack/solver/asp.py b/lib/spack/spack/solver/asp.py
index 4a18409533..0f4eccb358 100644
--- a/lib/spack/spack/solver/asp.py
+++ b/lib/spack/spack/solver/asp.py
@@ -401,6 +401,19 @@ class ClingoDriver(object):
return result
+def _normalize_body(body):
+ """Accept an AspAnd object or a single Symbol and return a list of
+ symbols.
+ """
+ if isinstance(body, AspAnd):
+ args = [f.symbol() for f in body.args]
+ elif isinstance(body, clingo.Symbol):
+ args = [body]
+ else:
+ raise TypeError("Invalid typee for rule body: ", type(body))
+ return args
+
+
class PyclingoDriver(object):
def __init__(self, cores=True, asp=None):
"""Driver for the Python clingo interface.
@@ -438,6 +451,18 @@ class PyclingoDriver(object):
def _and(self, *args):
return AspAnd(*args)
+ def _register_rule_for_cores(self, rule_str):
+ # rule atoms need to be choices before we can assume them
+ if self.cores:
+ rule_sym = clingo.Function("rule", [rule_str])
+ rule_atom = self.backend.add_atom(rule_sym)
+ self.backend.add_rule([rule_atom], [], choice=True)
+ self.assumptions.append(rule_atom)
+ rule_atoms = [rule_atom]
+ else:
+ rule_atoms = []
+ return rule_atoms
+
def fact(self, head):
"""ASP fact (a rule without a body)."""
sym = head.symbol()
@@ -450,12 +475,7 @@ class PyclingoDriver(object):
def rule(self, head, body):
"""ASP rule (an implication)."""
- if isinstance(body, AspAnd):
- args = [f.symbol() for f in body.args]
- elif isinstance(body, clingo.Symbol):
- args = [body]
- else:
- raise TypeError("Invalid typee for rule body: ", type(body))
+ args = _normalize_body(body)
symbols = [head.symbol()] + args
atoms = {}
@@ -466,15 +486,7 @@ class PyclingoDriver(object):
rule_str = "%s :- %s." % (
head.symbol(), ",".join(str(a) for a in args))
- # rule atoms need to be choices before we can assume them
- if self.cores:
- rule_sym = clingo.Function("rule", [rule_str])
- rule_atom = self.backend.add_atom(rule_sym)
- self.backend.add_rule([rule_atom], [], choice=True)
- self.assumptions.append(rule_atom)
- rule_atoms = [rule_atom]
- else:
- rule_atoms = []
+ rule_atoms = self._register_rule_for_cores(rule_str)
# print rule before adding
self.out.write("%s\n" % rule_str)
@@ -483,6 +495,18 @@ class PyclingoDriver(object):
[atoms[s] for s in args] + rule_atoms
)
+ def integrity_constraint(self, body):
+ symbols, atoms = _normalize_body(body), {}
+ for s in symbols:
+ atoms[s] = self.backend.add_atom(s)
+
+ rule_str = ":- {0}.".format(",".join(str(a) for a in symbols))
+ rule_atoms = self._register_rule_for_cores(rule_str)
+
+ # print rule before adding
+ self.out.write("{0}\n".format(rule_str))
+ self.backend.add_rule([], [atoms[s] for s in symbols] + rule_atoms)
+
def one_of_iff(self, head, versions):
self.out.write("%s :- %s.\n" % (head, AspOneOf(*versions)))
self.out.write("%s :- %s.\n" % (AspOneOf(*versions), head))
@@ -661,6 +685,27 @@ class SpackSolverSetup(object):
self.version_constraints.add((spec.name, spec.versions))
return [fn.version_satisfies(spec.name, spec.versions)]
+ def conflict_rules(self, pkg):
+ for trigger, constraints in pkg.conflicts.items():
+ for constraint, _ in constraints:
+ constraint_body = spack.spec.Spec(pkg.name)
+ constraint_body.constrain(constraint)
+ constraint_body.constrain(trigger)
+
+ clauses = []
+ for s in constraint_body.traverse():
+ clauses += self.spec_clauses(s, body=True)
+
+ # TODO: find a better way to generate clauses for integrity
+ # TODO: constraints, instead of generating them for the body
+ # TODO: of a rule and filter unwanted functions.
+ to_be_filtered = [
+ 'node_compiler_hard', 'node_compiler_version_satisfies'
+ ]
+ clauses = [x for x in clauses if x.name not in to_be_filtered]
+
+ self.gen.integrity_constraint(AspAnd(*clauses))
+
def available_compilers(self):
"""Facts about available compilers."""
@@ -750,6 +795,9 @@ class SpackSolverSetup(object):
self.gen.newline()
+ # conflicts
+ self.conflict_rules(pkg)
+
# default compilers for this package
self.package_compiler_defaults(pkg)
@@ -948,7 +996,7 @@ class SpackSolverSetup(object):
try:
target.optimization_flags(compiler.name, compiler.version)
supported.append(target)
- except llnl.util.cpu.UnsupportedMicroarchitecture as e:
+ except llnl.util.cpu.UnsupportedMicroarchitecture:
continue
return sorted(supported, reverse=True)
diff --git a/lib/spack/spack/test/concretize.py b/lib/spack/spack/test/concretize.py
index 72f8632ad2..4b087f8ad8 100644
--- a/lib/spack/spack/test/concretize.py
+++ b/lib/spack/spack/test/concretize.py
@@ -10,11 +10,11 @@ import llnl.util.lang
import spack.architecture
import spack.concretize
+import spack.error
import spack.repo
-from spack.concretize import find_spec, NoValidVersionError
-from spack.error import SpecError, SpackError
-from spack.spec import Spec, CompilerSpec, ConflictsInSpecError
+from spack.concretize import find_spec
+from spack.spec import Spec, CompilerSpec
from spack.version import ver
from spack.util.mock_package import MockPackageMultiRepo
import spack.compilers
@@ -495,12 +495,10 @@ class TestConcretize(object):
assert s['dyninst'].satisfies('%gcc')
def test_conflicts_in_spec(self, conflict_spec):
- # Check that an exception is raised an caught by the appropriate
- # exception types.
- for exc_type in (ConflictsInSpecError, RuntimeError, SpecError):
- s = Spec(conflict_spec)
- with pytest.raises(exc_type):
- s.concretize()
+ s = Spec(conflict_spec)
+ with pytest.raises(spack.error.SpackError):
+ s.concretize()
+ assert not s.concrete
def test_no_conflixt_in_external_specs(self, conflict_spec):
# clear deps because external specs cannot depend on anything
@@ -608,7 +606,7 @@ class TestConcretize(object):
@pytest.mark.parametrize('spec', ['noversion', 'noversion-bundle'])
def test_noversion_pkg(self, spec):
"""Test concretization failures for no-version packages."""
- with pytest.raises(SpackError):
+ with pytest.raises(spack.error.SpackError):
Spec(spec).concretized()
@pytest.mark.parametrize('spec, best_achievable', [
diff --git a/lib/spack/spack/test/conftest.py b/lib/spack/spack/test/conftest.py
index 0a9d1e6a7b..8a94f6e82a 100644
--- a/lib/spack/spack/test/conftest.py
+++ b/lib/spack/spack/test/conftest.py
@@ -1185,9 +1185,7 @@ def installation_dir_with_headers(tmpdir_factory):
@pytest.fixture(
params=[
- 'conflict%clang',
'conflict%clang+foo',
- 'conflict-parent%clang',
'conflict-parent@0.9^conflict~foo'
]
)