Versioning Haskell packages
TODO: need to decide on a versioning policy
Library components of packages which are intended to be distributed via a package repository MUST follow this policy. Non-library components of such packages MUST also do so if it is anticipated that they will be used downstream.
(Inversely: components of packages which are not intended to be distributed, or non-library components of packages which are not intended to be used downstream do NOT need to follow this policy).
pkg-a contains a test suite and also an executable that is used to inspect binary blobs that can be produced by using
The test suite is not intended to be used downstream, but the executable is, since it can be helpful for users to diagnose the products of
Hence the test suite does not need bounds, but the executable does.
Non-library components are much less critical, because they cannot be depended upon, and it is rarer that someone will want to e.g. run the tests or benchmarks for an upstream package. However, it can still be the case that this happens, especially for executables, which are sometimes explicitly intended to be used by downstream users. For this reason we tie the choice over whether to include bounds to the decision of the maintainer over whether the component is intended to be used downstream.
Version bounds (both upper and lower) MUST be included when they exclude versions of dependencies that the package is known not to work with. In addition, if it is discovered that a version of a dependency does not work with a version of the package that has been released to a package repository, then the package maintainer SHOULD publish a revision of the released version to include the new bound.
Discovering an incompatible version
The developer of package
pkg-a, which depends on library
pkg-b, tries to build with
This fails, so the developer should:
- Add an upper bound of
pkg-b < N, since
pkg-b-Nis known not to work; or
pkg-ato work with
pkg-b-N, and if this means that
pkg-awill now no longer work with earlier versions of
pkg-b, add a lower bound of
pkg-b >= N.
And then optionally publish a revision of some released versions of
pkg-a to add upper bounds on
Discovering an incompatible version for an upstream dependency
The developer of package
pkg-c, which depends on
pkg-a-M, tries to build with
This fails when building
pkg-a, so the developer of
pkg-c notifies the developer of
One of them should then:
- Publish a revision
pkg-a-Mto have a bound of
pkg-b < N; and
- Add a bound of
pkg-b < Nto the development branch of
pkg-aif it still applies.
Excluding dependency versions which are known not to work is a cheap way to convey information to downstream users. It means that if they try to use the non-working version then they will get a solver error from cabal, instead of a compilation error. It is common to discover this kind of version incompatibility information during development, and so this policy primarily insists that such information be recorded mechanically so that other people benefit from it.
Speculative upper bounds
A package MAY include an upper bound that excludes the next major version of a dependency, even if it is not known whether the next major version will break the package (e.g. because it has not been released yet).
pkg-b depends on
pkg-a tends to have breaking changes in releases, so the developer of
pkg-b decides to pin their dependency on
pkg-a using a caret bound, which implies a (speculative) upper bound of the next major version.
pkg-c depends on
The developer of
pkg-c really doesn't want to have to deal with bumping the upper bound on
pkg-b, so they just leave it off.
Note that due to the above policy about discovering incompatible versions, when a version of
pkg-b is released that does break
pkg-c, one of the developers should publish revisions to exclude the breaking version of
pkg-b from the released versions of
Speculative upper bounds are controversial. They have advantages (ensure that users get a working (if old) build plan; robustness against future changes), and disadvantages (often overly cautious; require large amounts of bound-relaxing to allow even "safe" new major versions). There is no consensus amongst the Cardano engineering community about which is preferable, so we simply note that either approach is acceptable.
The cost of speculative upper bounds is somewhat lower for Cardano packages, since they are typically well-maintained and released frequently, so bounds relaxations can be made and released in a fairly timely fashion. Nonetheless, they still impose costs, in particular requiring that downstream packages be released in order for new versions to be used.
A set of packages defined in the same source repository MUST include version bounds which are as tight as necessary to ensure that they function correctly when distributed via a package repository. It is not possible to give a fully-general rule for what bounds to use, but assuming that the packages are following something like PVP, typically pinning the major version is the right thing to do.
pkg-b are defined in the same source repository, and
pkg-a depends on
pkg-a is at version 1.1.2,
pkg-b is at version 2.4.3, and they both follow the PVP.
pkg-a should bound its dependency on
pkg-b == 2.4.*
Within a single source repository, packages are usually built with all the packages taken from a single commit of the source repository. When the packages are built from a package repository, then cabal may try to build them with different versions, so long as the bounds are satisfied. For this reason it is important to have tight enough bounds on packages which are defined in the same source repository. Typically it should be enough to pin the major version.
A component MAY omit bounds that it is otherwise required to have if those bounds are strictly implied by other dependencies that the package has within the same source repository.
pkg-a has both a library component and an executable component, both of which are used downstream.
Both components depend on
pkg-b-N, and do not work with
pkg-b-(N+1), and the executable depends on the library.
In this case it is acceptable to only put a
pkg-b < N+1 bound on the library, because the executable component strictly depends on the library component of the same version, and the library component has the bound.
It is common to have a repository which has many components/packages depending on some other package P. It is tedious to require every use of P to be well-bounded, especially since the components/packages in a repository should have tight bounds on each other, such that in practice bounding a single use of P should be enough to fix it for every package in the repository. Some care should be taken, however: it is easy to think that all packages in the repository are constrained, when in fact there may be a few that don't depend on the one that bounds P.