Skip to content

Solver loops when there are cycles through setup dependencies #3160

Closed
@edsko

Description

@edsko

Currently the solver does not try to do anything clever when there are cyclic dependencies in the package dependency graph. For example, if A depends on B and B depends on A, and we solve for A, the solver will simply return that we need to install A and B; cabal will then subsequently fail with an internal error about in invalid install plan. Not great, but not a major problem either.

However, the problem gets worse when we have setup dependencies (or other kinds of qualified dependencies). Suppose package C has a setup dependency on D, and D depends (normally) on C, and we solve for C. Now the solver starts adding an infinite number goals:

      C
      C-setup.D
(*)   C-setup.C
      C-setup.C-setup.D
      C-setup.C-setup.C
      C-setup.C-setup.C-setup.D
      ...

and so calling, say, cabal new-build will not terminate.

This might seem like a contrived situation, but it is not. To see how this might arise in practice, consider:

  • unbounded-delays has a setup dependency on Cabal (at least, it does when using the new-build command; unbounded-delays has a custom setup script without an explicit declaration of setup dependencies, so new-build adds an implicit setup dependency on Cabal)
  • Cabal, with the flag +tests enabled, has a regular dependency on tasty
  • tasty has a regular dependency on, you guessed it, unbounded-delays

Of course, the solver should instead pick Cabal -tests, but it it picks + tests first, it will go down an infinite path and never return; it won't notice that this solution cannot lead anywhere.

I'm working on a solution to this problem, and will hopefully submit a PR, but submitting the issue now so that you are aware that there is a problem.


By the by: The line marked (*) looks odd, but it's morally correct; compare to the goals that we would get if D instead relied on E, which would have a setup dependency on F:

       C
       C-setup.D
(**)   C-setup.E
(***)  C-setup.E-setup.F

Line (**) means that the version we pick for E when building C's setup script is independent of the version we pick elsewhere in the solution, which is correct. Similarly, line (***) says that the version of F that we pick to build E's setup script, for the version of E that we we use to build C's setup script, is independent from all other uses of F.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions