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 @@ 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) 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 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', [ 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' ] )