Skip to content
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

Limit qualifier depth #3220

Merged
merged 2 commits into from
Apr 4, 2016
Merged

Limit qualifier depth #3220

merged 2 commits into from
Apr 4, 2016

Conversation

edsko
Copy link
Contributor

@edsko edsko commented Mar 12, 2016

This PR changes the structure of package paths to:

data PP        = PP Namespace Qualifier
data Namespace = DefaultNamespace | Independent Int
data Qualifier = Unqualified | Base PN | Setup PN

This separates out a namespace from a qualifier; the same space is just used to distinguish top-level goals, which are supposed to be entirely independent from each other. Qualifiers are used when we want to make independent choices for dependencies. Crucially, qualifiers do not have any kind of nesting structure. This addresses the problem described in detail in #3160, but it also introduces a change in behaviour; we will find fewer solutions (though still strictly more solutions than existing released versions of cabal).

My suggestion would be that @dcoutts first cherry-picks this PR into the nix-local-build branch so we can experiment with it, before we merge this into master.

In #3170 we introduced a cycle check to the solver. This check is necessary to
reject cycling solutions (which would previously have resulted in an internal
error when we verify the install plan). However, this by itself is not
sufficient. If we have a cycle through setup dependencies, the solver loops
because it starts building an infinite tree. This is explained in detail in

In this commit we just add some unit tests that provide a minimal example that
exposes the bug.
A package path now consists of a namespace and a qualifier. The namespace is
either DefaultNamespace or Independent _i_, for some _i_; this is used for
independent top-level goals. Then the qualifier is either Unqualified
(default), Setup _pn_ for the setup dependencies of package _pn_, or Base _pn_,
for a dependency on base by package _pn_ (used only when we detect the presence
of a base shim). Qualifiers are not tested anymore.

This avoids non-termination in the solver. The unit tests now pass.
@23Skidoo
Copy link
Member

@edsko Do you want this to also go into 1.24?

@edsko
Copy link
Contributor Author

edsko commented Mar 12, 2016

Hmmmm. That's a harder question. I think I'm going to say yes, I think that's a good idea. After all, qualified goals are new right? So any kind of qualification already increases the search space over any existing cabal releases? So then the question is, is a single qualifier enough? But if there aren't situations where it isn't, then at least it's not worse than it is now. However, without this change the solver may loop (and I fear quite frequently will, especially once there are one or two commonly used packages on Hackage that have explicit setup dependencies). I think that is a significant degradation from a users perspective, and will not improve cabal's image. So yes, I think I would tentatively suggest to include this. But I would be keen to hear other people's opinions, in particular @dcoutts and @kosmikus.

On 12 Mar 2016, at 20:41, Mikhail Glushenkov notifications@github.com wrote:

@edsko Do you want this to also go into 1.24?


Reply to this email directly or view it on GitHub.

@23Skidoo 23Skidoo added this to the cabal-install 1.24 milestone Mar 12, 2016
@23Skidoo
Copy link
Member

OK, noted.

@grayjay
Copy link
Collaborator

grayjay commented Mar 13, 2016

I just read #3160, and I wanted to float an idea. I'm not very familiar with the implementation of independent goals, so I don't know if it makes sense.

I was trying to understand why the solver would choose different instances for packageA-setup.packageC-setup.packageD and packageB-setup.packageC-setup.packageD. It seems like that could only happen when packageA and packageB use different versions of packageC, as in this example:

packageA-setup -> packageC-1
packageB-setup -> packageC-2
packageC-1-setup -> packageD-1
packageC-2-setup -> packageD-2

Since setup-depends doesn't use conditionals, packageC's flags don't affect the choice of dependencies for its setup script.

Would it make sense to also qualify the goal with the package version, as in packageC-1-setup.packageD? Then, limiting the number of qualifiers to 1 wouldn't decrease the set of solvable dependency problems. This idea may not be general enough. For example, my reasoning doesn't apply to independent test suites, because tests can share flags with the library and executables.

EDIT: The solver could also pick different instances for packageA-setup.packageC-setup.packageD and packageB-setup.packageC-setup.packageD to break a cycle, e.g., if packageD had a conditional dependency on packageA or packageB. However, that situation seems unlikely.

@dcoutts
Copy link
Contributor

dcoutts commented Mar 13, 2016

@edsko cherry-picked to nix-local-build branch. This resolves the solver looping that we were seeing when building cabal-install itself with 7.10. That is, cabal new-build ./cabal-install -w ghc-7.10.1 --dry-run -v3 used to to loop and you could see from the solver log it was getting into a qualified goals loop, and with these two patches it no longer does that in this case.

@edsko
Copy link
Contributor Author

edsko commented Mar 16, 2016

@grayjay Sorry for the slow reply, very busy at the moment.

It is indeed tempting to try to somehow incorporate version numbers in the qualifiers; I too have considered this. For setup dependencies it does seem to be the right solution: how else could the two uses of C have different requirements on D for their setup script? However, I don't think it quite works. After all, in your example, A and B might not be specifying an explicit version of C; perhaps the choice of a version of C is forced by another setup dependency, maybe something like

A-1.0 -setup-> C.*
A-1.0 -setup-> E-1.0
B-1.0 -setup-> C.*
B-1.0 -setup-> E-2.0
C-1.0 -setup-> D-1.0
C-2.0 -setup-> D-2.0
E-1.0 -> C-1.0
E-2.0 -> C-2.0

In general, when we qualify these goals, we might not yet have that version information; after all, it is the task of the solver to figure out those versions. Really we want to say "the version that we need here (whatever that version may be), but of course that is precisely the purpose of qualifiers.

Whenever I found myself thinking along these lines (wanting to have version numbers in qualifiers), I always ended up concluding that no, that doesn't work, because we are putting the cart before the horse. Somewhat more philosophically, the idea of qualifiers is that within a certain namespace (certain package path), we can pick only one version; if you want to allow versions to differ, you need to introduce different namespaces. Adding version numbers into the qualifiers kind of confuses these two concepts. Of course, that's not to say that the idea of qualifiers is necessarily the right one :)

@grayjay
Copy link
Collaborator

grayjay commented Mar 16, 2016

@edsko I didn't consider that the solver doesn't need to know a package's version before it adds goals for the setup dependencies, and that my suggestion mixes up variables and values. Thank you for the detailed explanation!

@edsko
Copy link
Contributor Author

edsko commented Apr 4, 2016

@23Skidoo having discussed this with @dcoutts once more, I think I will stick to my proposal above to include this in the release. One qualifier is strictly more general than none, so compared to the released cabal increasing it to a single qualifier cannot make anything worse, and can only result in more solutions. It may or may not turn out that a single qualifier is not sufficient and that we need to increase the search space some more, but we can do that in a later release and anyway it would be much better to do that given some real examples than just speculate, because I think any further increase (which does not go all the way to infinity) will necessarily be of somewhat empirical nature. Having an infinite number of qualifiers I'm afraid will make the solver loop relatively often, which is not something we want to release. I suggest to merge.

@23Skidoo
Copy link
Member

23Skidoo commented Apr 4, 2016

OK, merging.

@23Skidoo 23Skidoo merged commit 62c3161 into haskell:master Apr 4, 2016
@23Skidoo
Copy link
Member

23Skidoo commented Apr 4, 2016

Merged into master and cherry-picked into 1.24. Thanks!

@kosmikus
Copy link
Contributor

Yes, I'm aware this has already been merged. Nevertheless: I have finally looked at this and I'm ok with this change. Thanks.

@edsko
Copy link
Contributor Author

edsko commented Apr 14, 2016

@kosmikus Mosterd. Maaltijd. 😁

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants