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

Flag to force the use of the latest allowed package #8387

Open
nomeata opened this issue Aug 16, 2022 · 15 comments
Open

Flag to force the use of the latest allowed package #8387

nomeata opened this issue Aug 16, 2022 · 15 comments

Comments

@nomeata
Copy link
Contributor

nomeata commented Aug 16, 2022

I am thinking about how to make dependency upgrades more automatic and more reliable (https://mobile.twitter.com/nomeata/status/1558859792919191552).

Imagine we have a tool or bot that creates tentatively PRs that extend the dependency ranges of your package pkg, e.g. from foo <2.0 to foo <2.1. Ideally, if the CI for this change is green it should be ok to merge it (after also checking the changelog for semantic changes, of course).

But right now, a typical CI setup will not provide that: If pkgs also depends on bar, and all versions of bar depend on foo <2.0, then Cabal will continue to pick foo-1, the build will succeed, and the PR may be merged. But once bar allows foo-2.0, now Cabal will pick that, and an incompatibility might cause the build or the tests to break – but now on master or on Hackage.

When doing dependency bumps locally, I'd use --constraint="foo>=2.0" to avoid this. But what can we do in automatic CI?

I could imagine a flag --constrain-latest-deps=pkgs that instructs the Cabal solver to use, for every dependency of pkgs, exactly the highest known version that is within pkgs’s version bounds. I can then use this flag in CI, and know that my upper version bounds are actually realizable.

Would that be useful, doable and welcome?

@phadej
Copy link
Collaborator

phadej commented Aug 16, 2022

Note, constraint syntax is not only version bounds. Why not --constraint=pkg largest? (edit: maybe largest is better, as latest /= largest) https://hackage.haskell.org/package/cabal-install-3.8.1.0/docs/Distribution-Client-Dependency.html#t:PackageProperty

using the latest version for every dependency won't work without extra heuristics, e.g. base or template-haskell are not upgradeable (and even if they were, not sure you won't to do that always). Heuristics are also fragile, so I'd rather be explicit yet slightly verbose.

it's also a case that some latest versions cannot be picked, e.g. latest tasty supports just some GHCs, and when testing with older GHCs older tasty is picked, and that is totally fine.

@nomeata
Copy link
Contributor Author

nomeata commented Aug 16, 2022

Yes, it is just a heuristic and won’t apply in all cases, unfortunately.

I thought of a syntax for constraint, but --constraint=pkg largest looks odd, since it should not constraint pkg, but rather the dependencies of pkgs. Or did you mean --constraint=foo largest (to go with the running example from above)?

With --constraint foo largest I wonder – largest w.r.t to what? Is it ok to leave the pkgs implicit? If not, maybe --constraint=foo largest-allowed-by=pkg maybe?

But would I have to list that flag manually for all dependencies of pkg? That seems error-prone as well…

(I don’t have great answers yet)

@nomeata
Copy link
Contributor Author

nomeata commented Aug 16, 2022

and when testing with older GHCs older tasty is picked, and that is totally fine.

Right. In my packages I would probably use this flag only when testing against the latest version of GHC, so maybe in most cases that’s fine?

@phadej
Copy link
Collaborator

phadej commented Aug 16, 2022

Right. In my packages I would probably use this flag only when testing against the latest version of GHC, so maybe in most cases that’s fine?

It's not, e.g. there is unix-2.8.0.0, which you cannot pick yet e.g. when you test inspection-testing, as ghc depends on unix.

@phadej
Copy link
Collaborator

phadej commented Aug 16, 2022

With --constraint foo largest I wonder – largest w.r.t to what?

Largest in the used package indices (i.e. Hackage in most cases). That's what people care about, that the latest/largest package version from Hackage can be used.

@nomeata
Copy link
Contributor Author

nomeata commented Aug 16, 2022

Hmm, that's not quite what I want to achieve here. Maybe there are good reasons to explicitly exclude an existing new version of some dependency (why else would we even have upper bounds else).

But it's always wrong™ to include a new major version of a dependency when it was never part of a successful build, is it?

So that's the question: can we automatically check that all upper bounds as specified by the package are actually realizable and indeed compile and work?

Testing that is a property that, once green, stays green (assuming PVP is followed). The property “works against latest ok hackage” is not stable in that sense.

I guess build-against-all-latest-on-hackage is also useful (e.g. in as a step before creating a version-bumping-PR), and if it's easier, fine with me.

@nomeata
Copy link
Contributor Author

nomeata commented Aug 16, 2022

Actually, the latest is not the best package to test.

Assume the dependency is bumped from foo <2.0 to foo <2.1, and hackage has foo-2.0.1 and foo-2.0.2. Then really one should test against foo-2.0.1 – testing against foo-2.0.2 might miss the fact that one is using API added in foo-2.0.2.

So here is a variant of the proposal, easier to implement. It only helps when package authors use the ^>= style: A flag is added to Cabal that effectively changes ^>= to == (and if there is a disjunction with multiple ^>= versions, picks the latest of these).

This way, the idea behind ^>=, namely to “assert the positive knowledge that this package is known to be semantically compatible with the releases”, is checked.

Another benefit of this is that it is even more stable against Hackage changes.

Does that hold (more) water?

@Mikolaj
Copy link
Member

Mikolaj commented Aug 16, 2022

Is this prefer-newest dual to #8261?

@nomeata
Copy link
Contributor Author

nomeata commented Aug 16, 2022

Hmm, not quite, I think. It says

By default, when solver has a choice of multiple versions of the same package, it will first try to derive a build plan with the latest version. This flag switches the behaviour, making the solver to prefer the oldest packages available.

but it starts with “the solver has a choice”, so it sounds it’s just about preferring one possible option over another option. (but I might be wrong)

What I think we want here is to force a specific version, and fail if it is not possible, rather than silently using an older version.

@phadej
Copy link
Collaborator

phadej commented Aug 16, 2022

@nomeata it makes sense, but remember that constraint: foo pick-latest-caret-version-of-deps will itself depend on the version of foo, so foo have to be pinned to specific version. You are implicitly assuming that foo is local package, but at the level constraints are specified it's not known.

I'm not sure whether constraints can be "expanded" late in the current solver design, I doubt, and I'd rather not do that.

Instead, it would be enough to have a prototype a tool to dump the required constraints when given a (path to) .cabal file. (build-depends: dep ^>= 1.0.0.0 || ^>=1.1.0.0 to constraints: dep ==1.1.0.0). Given that cabal-install-3.8 supports includes in project files, that would be easy to integrate into CI scripts. No need to teach cabal-install and go through release process to assess whether this is valuable addition.

@nomeata
Copy link
Contributor Author

nomeata commented Aug 16, 2022

Yes, that is a very reasonable approach! :-)

@andreasabel
Copy link
Member

So that's the question: can we automatically check that all upper bounds as specified by the package are actually realizable and indeed compile and work?

That would be great. Currently, I grep through the output of cabal-plan to find out whether an upper bound relaxation has been picked up indeed.

@andreabedini
Copy link
Collaborator

I am not sure I understand the problem 🤔🧐

Re forcing cabal-install to pick the latest version of a (direct or transitive) dependency: doesn't --allow-newer=foo remove all the upper bounds on foo, leading the solver to pick the latest one? I must be missing something here.

Re figuring out what is imposing an upper bound on foo. I faced this problem myself and eventually hacked togheter an active-bounds command that for each dependency matches the chosen version with all other dependencies upper bounds.

Kinda worked but perhaps there's a simpler way to look at this problem. Also I'm pretty sure I had written an O(n^2) algorithm 😂.

@nomeata
Copy link
Contributor Author

nomeata commented Jul 9, 2023

--allow-newer=foo

This uses the latest existing version, which may be different than the latest allowed version by the present package.

Here is code demonstrating what I want to do, if that's clearer: https://github.com/nomeata/cabal-force-upper-bound :-)

@andreabedini
Copy link
Collaborator

This uses the latest existing version, which may be different than the latest allowed version by the present package.

gotcha! I had missed this, thank you for repeating it for me :)

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

No branches or pull requests

5 participants