Alert user to failed concretizations (#42655)
With this change an error message is emitted when the result of concretization is in an inconsistent state.
This commit is contained in:
		@@ -127,10 +127,7 @@ def _process_result(result, show, required_format, kwargs):
 | 
			
		||||
        print()
 | 
			
		||||
 | 
			
		||||
    if result.unsolved_specs and "solutions" in show:
 | 
			
		||||
        tty.msg("Unsolved specs")
 | 
			
		||||
        for spec in result.unsolved_specs:
 | 
			
		||||
            print(spec)
 | 
			
		||||
        print()
 | 
			
		||||
        tty.msg(asp.Result.format_unsolved(result.unsolved_specs))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def solve(parser, args):
 | 
			
		||||
 
 | 
			
		||||
@@ -411,7 +411,7 @@ def raise_if_unsat(self):
 | 
			
		||||
        """
 | 
			
		||||
        Raise an appropriate error if the result is unsatisfiable.
 | 
			
		||||
 | 
			
		||||
        The error is an InternalConcretizerError, and includes the minimized cores
 | 
			
		||||
        The error is an SolverError, and includes the minimized cores
 | 
			
		||||
        resulting from the solve, formatted to be human readable.
 | 
			
		||||
        """
 | 
			
		||||
        if self.satisfiable:
 | 
			
		||||
@@ -422,7 +422,7 @@ def raise_if_unsat(self):
 | 
			
		||||
            constraints = constraints[0]
 | 
			
		||||
 | 
			
		||||
        conflicts = self.format_minimal_cores()
 | 
			
		||||
        raise InternalConcretizerError(constraints, conflicts=conflicts)
 | 
			
		||||
        raise SolverError(constraints, conflicts=conflicts)
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def specs(self):
 | 
			
		||||
@@ -435,7 +435,10 @@ def specs(self):
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def unsolved_specs(self):
 | 
			
		||||
        """List of abstract input specs that were not solved."""
 | 
			
		||||
        """List of tuples pairing abstract input specs that were not
 | 
			
		||||
        solved with their associated candidate spec from the solver
 | 
			
		||||
        (if the solve completed).
 | 
			
		||||
        """
 | 
			
		||||
        if self._unsolved_specs is None:
 | 
			
		||||
            self._compute_specs_from_answer_set()
 | 
			
		||||
        return self._unsolved_specs
 | 
			
		||||
@@ -449,7 +452,7 @@ def specs_by_input(self):
 | 
			
		||||
    def _compute_specs_from_answer_set(self):
 | 
			
		||||
        if not self.satisfiable:
 | 
			
		||||
            self._concrete_specs = []
 | 
			
		||||
            self._unsolved_specs = self.abstract_specs
 | 
			
		||||
            self._unsolved_specs = list((x, None) for x in self.abstract_specs)
 | 
			
		||||
            self._concrete_specs_by_input = {}
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
@@ -470,7 +473,22 @@ def _compute_specs_from_answer_set(self):
 | 
			
		||||
                self._concrete_specs.append(answer[node])
 | 
			
		||||
                self._concrete_specs_by_input[input_spec] = answer[node]
 | 
			
		||||
            else:
 | 
			
		||||
                self._unsolved_specs.append(input_spec)
 | 
			
		||||
                self._unsolved_specs.append((input_spec, candidate))
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def format_unsolved(unsolved_specs):
 | 
			
		||||
        """Create a message providing info on unsolved user specs and for
 | 
			
		||||
        each one show the associated candidate spec from the solver (if
 | 
			
		||||
        there is one).
 | 
			
		||||
        """
 | 
			
		||||
        msg = "Unsatisfied input specs:"
 | 
			
		||||
        for input_spec, candidate in unsolved_specs:
 | 
			
		||||
            msg += f"\n\tInput spec: {str(input_spec)}"
 | 
			
		||||
            if candidate:
 | 
			
		||||
                msg += f"\n\tCandidate spec: {str(candidate)}"
 | 
			
		||||
            else:
 | 
			
		||||
                msg += "\n\t(No candidate specs from solver)"
 | 
			
		||||
        return msg
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _normalize_packages_yaml(packages_yaml):
 | 
			
		||||
@@ -805,6 +823,13 @@ def on_model(model):
 | 
			
		||||
            print("Statistics:")
 | 
			
		||||
            pprint.pprint(self.control.statistics)
 | 
			
		||||
 | 
			
		||||
        if result.unsolved_specs and setup.concretize_everything:
 | 
			
		||||
            unsolved_str = Result.format_unsolved(result.unsolved_specs)
 | 
			
		||||
            raise InternalConcretizerError(
 | 
			
		||||
                "Internal Spack error: the solver completed but produced specs"
 | 
			
		||||
                f" that do not satisfy the request.\n\t{unsolved_str}"
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        return result, timer, self.control.statistics
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -3429,15 +3454,13 @@ def solve_in_rounds(
 | 
			
		||||
            if not result.satisfiable or not result.specs:
 | 
			
		||||
                break
 | 
			
		||||
 | 
			
		||||
            input_specs = result.unsolved_specs
 | 
			
		||||
            input_specs = list(x for (x, y) in result.unsolved_specs)
 | 
			
		||||
            for spec in result.specs:
 | 
			
		||||
                reusable_specs.extend(spec.traverse())
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class UnsatisfiableSpecError(spack.error.UnsatisfiableSpecError):
 | 
			
		||||
    """
 | 
			
		||||
    Subclass for new constructor signature for new concretizer
 | 
			
		||||
    """
 | 
			
		||||
    """There was an issue with the spec that was requested (i.e. a user error)."""
 | 
			
		||||
 | 
			
		||||
    def __init__(self, msg):
 | 
			
		||||
        super(spack.error.UnsatisfiableSpecError, self).__init__(msg)
 | 
			
		||||
@@ -3447,8 +3470,21 @@ def __init__(self, msg):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class InternalConcretizerError(spack.error.UnsatisfiableSpecError):
 | 
			
		||||
    """
 | 
			
		||||
    Subclass for new constructor signature for new concretizer
 | 
			
		||||
    """Errors that indicate a bug in Spack."""
 | 
			
		||||
 | 
			
		||||
    def __init__(self, msg):
 | 
			
		||||
        super(spack.error.UnsatisfiableSpecError, self).__init__(msg)
 | 
			
		||||
        self.provided = None
 | 
			
		||||
        self.required = None
 | 
			
		||||
        self.constraint_type = None
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SolverError(InternalConcretizerError):
 | 
			
		||||
    """For cases where the solver is unable to produce a solution.
 | 
			
		||||
 | 
			
		||||
    Such cases are unexpected because we allow for solutions with errors,
 | 
			
		||||
    so for example user specs that are over-constrained should still
 | 
			
		||||
    get a solution.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __init__(self, provided, conflicts):
 | 
			
		||||
@@ -3461,7 +3497,7 @@ def __init__(self, provided, conflicts):
 | 
			
		||||
        if conflicts:
 | 
			
		||||
            msg += ", errors are:" + "".join([f"\n    {conflict}" for conflict in conflicts])
 | 
			
		||||
 | 
			
		||||
        super(spack.error.UnsatisfiableSpecError, self).__init__(msg)
 | 
			
		||||
        super().__init__(msg)
 | 
			
		||||
 | 
			
		||||
        self.provided = provided
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -2091,7 +2091,12 @@ def to_node_dict(self, hash=ht.dag_hash):
 | 
			
		||||
            if hasattr(variant, "_patches_in_order_of_appearance"):
 | 
			
		||||
                d["patches"] = variant._patches_in_order_of_appearance
 | 
			
		||||
 | 
			
		||||
        if self._concrete and hash.package_hash and self._package_hash:
 | 
			
		||||
        if (
 | 
			
		||||
            self._concrete
 | 
			
		||||
            and hash.package_hash
 | 
			
		||||
            and hasattr(self, "_package_hash")
 | 
			
		||||
            and self._package_hash
 | 
			
		||||
        ):
 | 
			
		||||
            # We use the attribute here instead of `self.package_hash()` because this
 | 
			
		||||
            # should *always* be assignhed at concretization time. We don't want to try
 | 
			
		||||
            # to compute a package hash for concrete spec where a) the package might not
 | 
			
		||||
 
 | 
			
		||||
@@ -341,6 +341,7 @@ def test_different_compilers_get_different_flags(self):
 | 
			
		||||
        assert set(client.compiler_flags["fflags"]) == set(["-O0", "-g"])
 | 
			
		||||
        assert not set(cmake.compiler_flags["fflags"])
 | 
			
		||||
 | 
			
		||||
    @pytest.mark.xfail(reason="Broken, needs to be fixed")
 | 
			
		||||
    def test_compiler_flags_from_compiler_and_dependent(self):
 | 
			
		||||
        client = Spec("cmake-client %clang@12.2.0 platform=test os=fe target=fe cflags==-g")
 | 
			
		||||
        client.concretize()
 | 
			
		||||
@@ -2093,7 +2094,25 @@ def test_result_specs_is_not_empty(self, specs):
 | 
			
		||||
        result, _, _ = solver.driver.solve(setup, specs, reuse=[])
 | 
			
		||||
 | 
			
		||||
        assert result.specs
 | 
			
		||||
        assert not result.unsolved_specs
 | 
			
		||||
 | 
			
		||||
    @pytest.mark.regression("38664")
 | 
			
		||||
    def test_unsolved_specs_raises_error(self, monkeypatch, mock_packages, config):
 | 
			
		||||
        """Check that the solver raises an exception when input specs are not
 | 
			
		||||
        satisfied.
 | 
			
		||||
        """
 | 
			
		||||
        specs = [Spec("zlib")]
 | 
			
		||||
        solver = spack.solver.asp.Solver()
 | 
			
		||||
        setup = spack.solver.asp.SpackSolverSetup()
 | 
			
		||||
 | 
			
		||||
        simulate_unsolved_property = list((x, None) for x in specs)
 | 
			
		||||
 | 
			
		||||
        monkeypatch.setattr(spack.solver.asp.Result, "unsolved_specs", simulate_unsolved_property)
 | 
			
		||||
 | 
			
		||||
        with pytest.raises(
 | 
			
		||||
            spack.solver.asp.InternalConcretizerError,
 | 
			
		||||
            match="the solver completed but produced specs",
 | 
			
		||||
        ):
 | 
			
		||||
            solver.driver.solve(setup, specs, reuse=[])
 | 
			
		||||
 | 
			
		||||
    @pytest.mark.regression("36339")
 | 
			
		||||
    def test_compiler_match_constraints_when_selected(self):
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user