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
This commit is contained in:
parent
2595b58503
commit
1cdee03c4b
3 changed files with 72 additions and 28 deletions
|
@ -401,6 +401,19 @@ def colorize(string):
|
|||
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 @@ def one_of(self, *args):
|
|||
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 @@ def fact(self, head):
|
|||
|
||||
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 @@ def rule(self, head, body):
|
|||
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 @@ def rule(self, head, body):
|
|||
[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 @@ def spec_versions(self, spec):
|
|||
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 @@ def pkg_rules(self, pkg):
|
|||
|
||||
self.gen.newline()
|
||||
|
||||
# conflicts
|
||||
self.conflict_rules(pkg)
|
||||
|
||||
# default compilers for this package
|
||||
self.package_compiler_defaults(pkg)
|
||||
|
||||
|
@ -948,7 +996,7 @@ def _supported_targets(self, compiler, targets):
|
|||
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)
|
||||
|
|
|
@ -10,11 +10,11 @@
|
|||
|
||||
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 @@ def test_compiler_child(self):
|
|||
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 @@ def test_simultaneous_concretization_of_specs(self, abstract_specs):
|
|||
@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', [
|
||||
|
|
|
@ -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'
|
||||
]
|
||||
)
|
||||
|
|
Loading…
Reference in a new issue