haskell / cabal

Official upstream development repository for Cabal and cabal-install
https://haskell.org/cabal
Other
1.63k stars 697 forks source link

Packages sometimes (?) want to declare constraints on indirect dependencies #9558

Open michaelpj opened 11 months ago

michaelpj commented 11 months ago

In recent discussion I made the claim that there are situations where a package may want to state constraints on indirect dependencies. Here are a couple of examples where that applies:

  1. The indirect dependency has a bug

Suppose we have:

but my-pkg does not depend on foo-parser directly.

Now suppose that foo-parser-1 has a bug such that it doesn't process certain kinds of .foo files properly. Then the tests for my-pkg might be failing if I have such files around. Suppose the bug is fixed in foo-parser-1.0.0.1 (a patch release since it's just a bug fix behavioural change).

Then it is the case that my-pkg won't work unless it is built with foo-parser-1.0.0.1 in the build plan, but we have no way to state that without either a) adding a spurious dependency on foo-parser, or b) getting a pointless release of foo-processor which adds a lower bound of foo-parser-1.0.0.1.

(Why would we care? Isn't foo-1.0.0.1 newer, so cabal will pick it? Usually yes, but various things can get in the way (index-state set too old, other constraints preventing it from being picked), and we would e.g. like to get a solver error in that case rather than wrong behaviour.)

  1. Some combinations of versions may not work together

turtle depends on optparse-applicative and ansi-wl-pprint. For various complicated reasons, most combinations of these libraries work together, but the specific combination of a new optparse-applicative and an old ansi-wl-pprint would not and would lead to build errors.

From the point of view of the turtle maintainers, the situation seems fine, but for a package which might force an old ansi-wl-pprint, it would be useful to also be able to force an old optparse-applicative, even if that is not a direct dependency.

https://github.com/Gabriella439/turtle/pull/446#issuecomment-1660656912


Having written these down, I think I don't find them that convincing. In fact, the problems could all be fixed by getting the upstream package that actually has a direct dependency to put in more restrictive bounds and do a release. The issue is really just that it can take a while to do that, and until you do you can end up being in the annoying position of having to tell all your downstream users "you need to add X constraints to your cabal.project".

Bodigrim commented 11 months ago

In fact, the problems could all be fixed by getting the upstream package that actually has a direct dependency to put in more restrictive bounds and do a release.

Does it need a release though? Would not a revision be enough?

I think a revision of foo-processor to disallow foo-parser-1 would be enough - surely we do not want a buggy version to appear in any build plans.

(2) is more complicated, indeed the most permissible solution requires something like https://github.com/Gabriella439/turtle/pull/447, but it's also possible to restrict bounds to ansi-wl-pprint >= 1.0, optparse-applicative >= 0.18 in a revision.

michaelpj commented 11 months ago

Yes, I think in most of these cases a revision would be fine. You could imagine a more complex situation: perhaps both foo-parser-1 and foo-parser-1.0.0.1 are buggy in different ways and foo-processor doesn't want to force downstream to pick a buggy version.

In fact, that suggests a third case:

  1. Indirect dependencies have major behavioural changes

Suppose that foo-parser-2 changes the behaviour in some significant non-backwards-compatible way. foo-processor is able to support both foo-parser-1 and foo-parser-2 through CPP or otherwise. But my-pkg really only works with the foo-parser-1 behaviour.

An example from the wild might be aeson-1 vs aeson-2: imagine that foo-processor worked with both via CPP, but had a function that returned the HashMap/KeyMap part of an Object. Then my-pkg might expect to be able to operate on the HashMap, but would break if it gets a KeyMap. So we have an indirect requirement to use aeson-1.