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
|
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):
|
class PyclingoDriver(object):
|
||||||
def __init__(self, cores=True, asp=None):
|
def __init__(self, cores=True, asp=None):
|
||||||
"""Driver for the Python clingo interface.
|
"""Driver for the Python clingo interface.
|
||||||
|
@ -438,6 +451,18 @@ def one_of(self, *args):
|
||||||
def _and(self, *args):
|
def _and(self, *args):
|
||||||
return AspAnd(*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):
|
def fact(self, head):
|
||||||
"""ASP fact (a rule without a body)."""
|
"""ASP fact (a rule without a body)."""
|
||||||
sym = head.symbol()
|
sym = head.symbol()
|
||||||
|
@ -450,12 +475,7 @@ def fact(self, head):
|
||||||
|
|
||||||
def rule(self, head, body):
|
def rule(self, head, body):
|
||||||
"""ASP rule (an implication)."""
|
"""ASP rule (an implication)."""
|
||||||
if isinstance(body, AspAnd):
|
args = _normalize_body(body)
|
||||||
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))
|
|
||||||
|
|
||||||
symbols = [head.symbol()] + args
|
symbols = [head.symbol()] + args
|
||||||
atoms = {}
|
atoms = {}
|
||||||
|
@ -466,15 +486,7 @@ def rule(self, head, body):
|
||||||
rule_str = "%s :- %s." % (
|
rule_str = "%s :- %s." % (
|
||||||
head.symbol(), ",".join(str(a) for a in args))
|
head.symbol(), ",".join(str(a) for a in args))
|
||||||
|
|
||||||
# rule atoms need to be choices before we can assume them
|
rule_atoms = self._register_rule_for_cores(rule_str)
|
||||||
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 = []
|
|
||||||
|
|
||||||
# print rule before adding
|
# print rule before adding
|
||||||
self.out.write("%s\n" % rule_str)
|
self.out.write("%s\n" % rule_str)
|
||||||
|
@ -483,6 +495,18 @@ def rule(self, head, body):
|
||||||
[atoms[s] for s in args] + rule_atoms
|
[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):
|
def one_of_iff(self, head, versions):
|
||||||
self.out.write("%s :- %s.\n" % (head, AspOneOf(*versions)))
|
self.out.write("%s :- %s.\n" % (head, AspOneOf(*versions)))
|
||||||
self.out.write("%s :- %s.\n" % (AspOneOf(*versions), head))
|
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))
|
self.version_constraints.add((spec.name, spec.versions))
|
||||||
return [fn.version_satisfies(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):
|
def available_compilers(self):
|
||||||
"""Facts about available compilers."""
|
"""Facts about available compilers."""
|
||||||
|
|
||||||
|
@ -750,6 +795,9 @@ def pkg_rules(self, pkg):
|
||||||
|
|
||||||
self.gen.newline()
|
self.gen.newline()
|
||||||
|
|
||||||
|
# conflicts
|
||||||
|
self.conflict_rules(pkg)
|
||||||
|
|
||||||
# default compilers for this package
|
# default compilers for this package
|
||||||
self.package_compiler_defaults(pkg)
|
self.package_compiler_defaults(pkg)
|
||||||
|
|
||||||
|
@ -948,7 +996,7 @@ def _supported_targets(self, compiler, targets):
|
||||||
try:
|
try:
|
||||||
target.optimization_flags(compiler.name, compiler.version)
|
target.optimization_flags(compiler.name, compiler.version)
|
||||||
supported.append(target)
|
supported.append(target)
|
||||||
except llnl.util.cpu.UnsupportedMicroarchitecture as e:
|
except llnl.util.cpu.UnsupportedMicroarchitecture:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
return sorted(supported, reverse=True)
|
return sorted(supported, reverse=True)
|
||||||
|
|
|
@ -10,11 +10,11 @@
|
||||||
|
|
||||||
import spack.architecture
|
import spack.architecture
|
||||||
import spack.concretize
|
import spack.concretize
|
||||||
|
import spack.error
|
||||||
import spack.repo
|
import spack.repo
|
||||||
|
|
||||||
from spack.concretize import find_spec, NoValidVersionError
|
from spack.concretize import find_spec
|
||||||
from spack.error import SpecError, SpackError
|
from spack.spec import Spec, CompilerSpec
|
||||||
from spack.spec import Spec, CompilerSpec, ConflictsInSpecError
|
|
||||||
from spack.version import ver
|
from spack.version import ver
|
||||||
from spack.util.mock_package import MockPackageMultiRepo
|
from spack.util.mock_package import MockPackageMultiRepo
|
||||||
import spack.compilers
|
import spack.compilers
|
||||||
|
@ -495,12 +495,10 @@ def test_compiler_child(self):
|
||||||
assert s['dyninst'].satisfies('%gcc')
|
assert s['dyninst'].satisfies('%gcc')
|
||||||
|
|
||||||
def test_conflicts_in_spec(self, conflict_spec):
|
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)
|
s = Spec(conflict_spec)
|
||||||
with pytest.raises(exc_type):
|
with pytest.raises(spack.error.SpackError):
|
||||||
s.concretize()
|
s.concretize()
|
||||||
|
assert not s.concrete
|
||||||
|
|
||||||
def test_no_conflixt_in_external_specs(self, conflict_spec):
|
def test_no_conflixt_in_external_specs(self, conflict_spec):
|
||||||
# clear deps because external specs cannot depend on anything
|
# 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'])
|
@pytest.mark.parametrize('spec', ['noversion', 'noversion-bundle'])
|
||||||
def test_noversion_pkg(self, spec):
|
def test_noversion_pkg(self, spec):
|
||||||
"""Test concretization failures for no-version packages."""
|
"""Test concretization failures for no-version packages."""
|
||||||
with pytest.raises(SpackError):
|
with pytest.raises(spack.error.SpackError):
|
||||||
Spec(spec).concretized()
|
Spec(spec).concretized()
|
||||||
|
|
||||||
@pytest.mark.parametrize('spec, best_achievable', [
|
@pytest.mark.parametrize('spec, best_achievable', [
|
||||||
|
|
|
@ -1185,9 +1185,7 @@ def installation_dir_with_headers(tmpdir_factory):
|
||||||
|
|
||||||
@pytest.fixture(
|
@pytest.fixture(
|
||||||
params=[
|
params=[
|
||||||
'conflict%clang',
|
|
||||||
'conflict%clang+foo',
|
'conflict%clang+foo',
|
||||||
'conflict-parent%clang',
|
|
||||||
'conflict-parent@0.9^conflict~foo'
|
'conflict-parent@0.9^conflict~foo'
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in a new issue