diff --git a/lib/spack/spack/solver/asp.py b/lib/spack/spack/solver/asp.py index 4a184095335e6c310b955c9f751de8ca2841999a..0f4eccb3582a06150fa31b70d00a687b735dee83 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/spec.py b/lib/spack/spack/spec.py index 90620c11b4e84e1b14bf24ee9f4e29ddb48077bb..12617529b63309557c39b03a201919e9448f318d 100644 --- a/lib/spack/spack/spec.py +++ b/lib/spack/spack/spec.py @@ -2380,7 +2380,7 @@ def _new_concretize(self, tests=False): raise spack.error.SpecError( "Spec has no name; cannot concretize an anonymous spec") - result = spack.solver.asp.solve([self], dump=('asp', 'output')) + result = spack.solver.asp.solve([self]) if not result.satisfiable: result.print_cores() raise spack.error.UnsatisfiableSpecError( diff --git a/lib/spack/spack/test/concretize.py b/lib/spack/spack/test/concretize.py index a67860f0d57ddb78a020f6fab35c0a3720da942b..a4228956c3b78060b88e7e01b35269b197282f28 100644 --- a/lib/spack/spack/test/concretize.py +++ b/lib/spack/spack/test/concretize.py @@ -8,11 +8,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 @@ -493,12 +493,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 @@ -606,7 +604,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 d692b6ab337e54dd2bc08aeb6ab28697aeafb107..52c8d2103bed20d820e3842afb89f65c7fd2b0d6 100644 --- a/lib/spack/spack/test/conftest.py +++ b/lib/spack/spack/test/conftest.py @@ -1132,9 +1132,10 @@ def installation_dir_with_headers(tmpdir_factory): @pytest.fixture( params=[ - 'conflict%clang', + # FIXME: commented specs enter in multiple tests + # 'conflict%clang', 'conflict%clang+foo', - 'conflict-parent%clang', + # 'conflict-parent%clang', 'conflict-parent@0.9^conflict~foo' ] ) diff --git a/lib/spack/spack/test/package_sanity.py b/lib/spack/spack/test/package_sanity.py index 36ff4788a0f41a33cf22b02b3fd744dcd0f4c52d..51bf157a1016ef1c2b3983bbad91216ae17af5a3 100644 --- a/lib/spack/spack/test/package_sanity.py +++ b/lib/spack/spack/test/package_sanity.py @@ -150,7 +150,7 @@ def invalid_sha256_digest(fetcher): if bad_digest: errors.append( "All packages must use sha256 checksums." - "Resource in %s uses %s." % (name, v, bad_digest) + "Resource in %s@%s uses %s." % (name, v, bad_digest) ) assert [] == errors