-
Notifications
You must be signed in to change notification settings - Fork 696
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Solver loops when there are cycles through setup dependencies #3160
Comments
Qualified Goals TradeoffsQualified goals were introduced to increase the solution space. The original motivation was explicit setup dependencies. If we have a package DB containing
then we want to be able install When we solve for Current approachThe general idea is that within a certain namespace (top-level, or the name space determined by a particular series of qualifiers) we can only install a single version/instance of a package. Introducing more qualifiers means decoupling package choices, thus allowing for more solutions. Asking for versions C-1.0 and C-1.1 to be installed at top-level will not work because they will share the goal Currently if we are solving for a particular package Enlarging the solution space somewhatCurrently we introduce qualifiers only for setup dependencies (and when the user indicated that the top-levels goals are independent, although we do not expose this at the command line at the moment). However, as discussed in #1575 (comment), we might also want to introduce a qualifier for the dependencies of test suites (at least those dependencies that are not shared with the library itself). Consider:
Right now this situation is impossible; the solver will not be able to find a solution. However, we could introduce qualified goals for dependencies of test suites, just like we do for setup dependencies. Then when we solve for This illustrates that a goal of the form Remark: self-dependencies for setup scriptsAlthough it's probably not very important, if we have
we also end up with a goal of the same form Problem in the solverIf we solved for C in the example from the previous section, the solver would actually start creating an infinite series of goals to solve for; Reducing the solution spaceHence, we need to approximate and artificially restrict the solution space. My suggestion is to remove the notion of a package path, and just allow for a single qualifier. This still allows for setup dependencies to be satisfied independently from top-level dependencies, and even means the setup dependencies of different packages are independent from each other. It also still allows the Thus, where we currently would create a goal In reality, I think the impact of restricting qualifiers to a single package name and component, rather than a series of them (a package path), is very small. Most setup dependencies will be simple, so the situation where we want different versions for the setup dependencies of setup dependencies than we want for those setup dependencies [sic] seems somewhat far-fetched. For test suites it is even less relevant because we generally want to build test suites for top-level goals only anyway, and we don't want to build the test suites of the dependencies of our test suites. We can consider other heuristics (allow package paths up to a certain length, package paths where qualifiers cannot occur more than once, package paths without infinitely repeating subpaths [how would we even detect that?]), but I don't increased expressive power is worth it, and the more complicated the heuristic the more difficult it would be understand why the solver cannot find a solution Moreover, any such heuristic (at least the ones I just mentioned) as really just as arbitrary as restricting paths to be of length 1; you can always find an example that would be solvable without the heuristic but no longer solvable with the heuristic. Just the longer the paths are that you allow, the more deeply nested the constraints will need to be before we fail to find a solution. In a way, the introduction of qualified goals changed the maximum paths length from 0 to be infinitely long; I'm suggesting that if we pick a finite path length, 1 will probably suffice. So my vote is to allow for a single qualifier, unless there are very clear reasons (example use cases) not to. |
/cc @kosmikus |
Actually, I just realized my comment above isn't quite right. Bear with me. |
Ok, comment updated. Section on existing limitations was incorrect. My conclusion still stands however. |
Related: #1575. One other example where this occurs in the wild is the |
Note that this is addressed by #3220 . |
Should this be closed now that #3220 is merged? |
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:
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 onCabal
(at least, it does when using thenew-build
command;unbounded-delays
has a custom setup script without an explicit declaration of setup dependencies, sonew-build
adds an implicit setup dependency onCabal
)Cabal
, with the flag+tests
enabled, has a regular dependency ontasty
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: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.The text was updated successfully, but these errors were encountered: