-
Notifications
You must be signed in to change notification settings - Fork 704
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
How to freeze #9984
Draft
philderbeast
wants to merge
18
commits into
haskell:master
Choose a base branch
from
cabalism:docs/freeze-brrgh
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+263
−30
Draft
How to freeze #9984
Changes from all commits
Commits
Show all changes
18 commits
Select commit
Hold shift + click to select a range
d181780
Improved docs on cabal freeze.
Martinsos 39cdfc9
Move freeze discussion to guide
philderbeast ffd69d5
Typo continuos
philderbeast 72235ab
Use description list for common workflows
philderbeast 9fbcd1d
Define what freezing is
philderbeast 0e0b86d
Move freezing downwards in the guide
philderbeast fef04b4
Extra .freeze extension
philderbeast 156856e
Add a dependency then freeze harder
philderbeast 813a17d
Do you need to freeze as new section title
philderbeast 01bb1c7
How to control versions
philderbeast 8ac981d
Add other ways to control versions
philderbeast 2a9174a
Add section titles
philderbeast b4294f4
Stackage as a curated package set
philderbeast e60c5ca
Constraints are additive
philderbeast e9c1e38
Add version ranges
philderbeast 5fd552d
Add content to the version ranges section
philderbeast d5a62b0
Add version constraints and exceptions
philderbeast b7b8296
Demo of --with-compiler effect
philderbeast File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,247 @@ | ||
How to control versions | ||
======================= | ||
|
||
There are various ways and places to limit what versions the solver can pick for | ||
dependencies. | ||
|
||
Version ranges | ||
Within a package description, version ranges for dependencies can be; tight | ||
or loose or missing altogether. | ||
|
||
Version constraints | ||
Within a project, version constraints for dependencies limit the versions | ||
that the solver can pick. | ||
|
||
Curated version sets | ||
A project can import curated sets of packages and versions that are known to | ||
work together, such as those Stackage provides for Cabal's use. | ||
|
||
Capped repository versions | ||
Adding ``index-state`` to a project limits versions coming from Hackage to | ||
include only those that were available at the cutoff time. | ||
|
||
Frozen versions | ||
Pins the versions picked by the solver for all dependencies. This is a way | ||
to preserve a set of versions found by the solver, a solver-curated set, if | ||
you will. | ||
|
||
Version exceptions | ||
Allow newer or older dependencies. | ||
|
||
Version ranges | ||
-------------- | ||
|
||
A dependency can have a version range specified in the package description. | ||
Published packages are expected to attach version ranges to dependencies that | ||
conform to a set of rules that ``cabal check`` command will check for and report | ||
on: | ||
|
||
- that lower bounds are inclusive | ||
- that upper bounds are exclusive | ||
- that upper bounds don't have trailing zeros | ||
- that upper bounds are not missing | ||
- that upper bounds are not missing for ``base`` | ||
|
||
For large projects with many packages, it would be a lot of work to keep all | ||
package dependency version ranges up to date. Ways of overcoming this are: | ||
|
||
- use a bash script to replace or update version ranges in all package descriptions | ||
- use a package generator like ``hpack-dhall-cabal`` to import sets of dependencies with version ranges | ||
- use a ``cabal.project`` file to specify version constraints for some or all dependencies | ||
- use a curated set of packages and versions and import these into the project | ||
|
||
Version constraints | ||
------------------- | ||
|
||
Version constraints can be applied to the project or command line. | ||
|
||
.. Note:: | ||
|
||
While they do not directly restrain dependency versions, flag constraints | ||
can alter the set of dependencies of a package. | ||
|
||
.. Warning:: | ||
|
||
Version constraints are additive. If I add a constraint it doesn't remove or | ||
replace prior constraints on versions. Constraints don't have override | ||
semantics. | ||
|
||
Curated version sets | ||
-------------------- | ||
|
||
Stackage provides curated sets of packages and versions that are known to work | ||
together and are updated regularly. The latest resolver is the nightly and this | ||
typically lags a bit behind the latest available GHC version. The LTS resolvers | ||
will each follow a specific GHC version and are updated less frequently. | ||
|
||
Capped repository versions | ||
-------------------------- | ||
|
||
.. _freeze-versions: | ||
|
||
Frozen versions | ||
--------------- | ||
|
||
Pinning adds a version equality constraint for each package in the set of | ||
project dependencies, explicit and transitive. The ``cabal freeze`` command | ||
saves these to a file named the same as the whole of the project file name but | ||
with a extra ``.freeze`` extension, so the freeze file for ``cabal.project`` is | ||
``cabal.project.freeze``. Effectively a ``.freeze`` file is an implicit project | ||
import, same as the ``.local`` file for projects. | ||
|
||
.. Warning:: | ||
|
||
The order of imports of ``.local`` and ``.freeze`` files is important. The | ||
``.local`` file is imported last, after the ``.freeze`` file, giving the | ||
user a final say in the setting of any fields that have override semantics. | ||
|
||
.. Warning:: | ||
|
||
A project or package that may build with a variety of compiler versions will | ||
be locked to a particular compiler version by freezing. For instance, if | ||
``cabal init`` is used to create an executable package, | ||
``unconstrained-base-exe.cabal``, after removing version constraints on base | ||
it can be frozen with various compiler versions that will each bring in | ||
their set of boot libraries as dependencies. | ||
|
||
.. code-block:: cabal | ||
|
||
executable unconstrained-base-exe | ||
build-depends: base >0 | ||
|
||
.. code-block:: diff | ||
|
||
$ cabal freeze --with-compiler=ghc-8.10.7 | ||
$ mv cabal.project.freeze ghc-8.10.7.freeze | ||
|
||
$ cabal freeze --with-compiler=ghc-9.10.1 | ||
$ mv cabal.project.freeze ghc-9.10.1.freeze | ||
|
||
$ cabal freeze --with-compiler=ghc-9.12.1 | ||
$ mv cabal.project.freeze ghc-9.12.1.freeze | ||
|
||
$ diff ghc-8.10.7.freeze ghc-9.12.1.freeze --unified | ||
-constraints: any.base ==4.14.3.0, | ||
- any.ghc-prim ==0.6.1, | ||
- any.integer-gmp ==1.0.3.0, | ||
- any.rts ==1.0.1 | ||
+constraints: any.base ==4.21.0.0, | ||
+ any.ghc-bignum ==1.3, | ||
+ any.ghc-internal ==9.1201.0, | ||
+ any.ghc-prim ==0.13.0, | ||
+ any.rts ==1.0.2 | ||
|
||
$ diff ghc-9.10.1.freeze ghc-9.12.1.freeze --unified | ||
-constraints: any.base ==4.20.0.0, | ||
+constraints: any.base ==4.21.0.0, | ||
any.ghc-bignum ==1.3, | ||
- any.ghc-internal ==9.1001.0, | ||
- any.ghc-prim ==0.11.0, | ||
+ any.ghc-internal ==9.1201.0, | ||
+ any.ghc-prim ==0.13.0, | ||
any.rts ==1.0.2 | ||
|
||
Do you need to freeze? | ||
^^^^^^^^^^^^^^^^^^^^^^ | ||
|
||
Why would you want to freeze? Don't we want to get minor updates of our | ||
dependencies, or at least patches, as soon as we can? Well, although they | ||
shouldn't, it is possible that any kind of update introduces new bugs, | ||
performance issues, or some other kind of unexpected behaviour. This is where | ||
``cabal.project.freeze`` comes in, as it ensures that dependencies don't | ||
unexpectedly change. You can still update your dependencies, but you have to do | ||
it on purpose, by modifying or by deleting and regenerating | ||
``cabal.project.freeze`` file, and in the meantime you are guaranteed no | ||
surprises will happen. | ||
|
||
This consistency can be valuable as it ensures that all teammates, deployments, | ||
and continuous integration are installing the exactly same dependencies. So if | ||
you are running and testing the code on your local machine, you are guaranteed | ||
that your teammate and your continuous integration will be running the exact | ||
same code, and that at the end that exact same code will get deployed. | ||
|
||
A ``.freeze`` file can be good to have when developing for yourself or within a | ||
private team. If anyone using it can somehow have different inputs to | ||
the solver then the ``.freeze`` file can be troublesome. It can prevent the | ||
solver from finding a different version of a dependency that would satisfy a | ||
different architecture or a different compiler version and boot libraries. | ||
|
||
.. Warning:: | ||
|
||
If publishing a package to Hackage, not matter what kind of component it | ||
contains, don't include a ``.freeze`` file, don't add it to any field of the | ||
package description that would have ``cabal sdist`` include it in the | ||
``.tar.gz``. In general, don't include anything in the package description | ||
that relates to the project environment, like ``cabal.project`` or | ||
``cabal.project.local``. | ||
|
||
Freezing workflows | ||
^^^^^^^^^^^^^^^^^^ | ||
|
||
.. Warning:: | ||
For each of these workflows, you may have to first delete the | ||
``index-state`` line from ``cabal.project`` (and from | ||
``cabal.project.freeze`` if it exists) and then run ``cabal update`` to | ||
ensure that cabal will have newer versions to re-resolve the dependencies | ||
with. Alternatively, you can run ``cabal update | ||
--ignore-project``. | ||
|
||
Freeze | ||
If the ``cabal.project.freeze`` file doesn't exist, generating one is a | ||
great way to see what versions of dependencies are currently being used even | ||
if you choose to discard the ``.freeze`` file after inspecting it. | ||
|
||
Thaw, Freeze | ||
If you changed the version ranges of any of the dependencies in any of your | ||
project's package descriptions, in any ``.cabal`` file, then delete the | ||
``cabal.project.freeze`` file if it already exists and run ``cabal freeze`` | ||
to generate fresh version of ``cabal.project.freeze``. | ||
|
||
Freeze, Freeze (Freezing Harder) | ||
You might in some cases want to skip deletion of ``cabal.project.freeze``, | ||
but keep in mind that in that case ``cabal freeze`` will use existing | ||
``cabal.project.freeze`` when resolving dependencies, therefore not updating | ||
any existing dependencies, only adding new ones. | ||
|
||
Partial Thaw, Freeze | ||
If you want to a pick up a different version of a single dependency, you can | ||
delete its constraint from ``cabal.project.freeze`` and then run ``cabal | ||
freeze`` again. | ||
|
||
.. Note:: | ||
|
||
If not sure, pick the "thaw, freeze" workflow, as it is the safest, the | ||
simplest and the most common. Finally, you will always want to commit the | ||
changed ``cabal.project.freeze`` to version control. | ||
|
||
Ensuring everything is frozen | ||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ||
|
||
.. Note:: | ||
|
||
If the ``.freeze`` file already has version equality constraints for every | ||
package that is a dependency of the project, then the solver will not be | ||
able to find a different version for any of them, the ``.freeze`` file | ||
cannot change and, at that point when every dependency is frozen, ``cabal | ||
freeze`` becomes an idempotent operation. | ||
|
||
Adding a dependency to one of the packages in a project without freezing harder | ||
leaves the newly added dependency susceptible to getting updated unexpectedly | ||
when the solver can find a different version for it. Running ``cabal freeze`` | ||
will show this vulnerability to a human or an automated check that notices a new | ||
version equality constraint in the ``.freeze`` file, a constraint for a package | ||
that wasn't in the ``.freeze`` file before. | ||
|
||
To automate this check, make it a part of continuous integration or make a | ||
pre-commit hook for it. A simple check for this might be to compare the md5sum | ||
of the ``.freeze`` file before and after running ``cabal freeze``. If the | ||
checksums are the same, then the ``.freeze`` file didn't change, and all | ||
versions are frozen. | ||
|
||
.. code-block:: bash | ||
|
||
[[ -f cabal.project.freeze ]] || exit 1 | ||
OLD_FREEZE_SUM=$(md5sum cabal.project.freeze) | ||
cabal freeze || exit 1 | ||
NEW_FREEZE_SUM=$(md5sum cabal.project.freeze) | ||
exit [[ "$NEW_FREEZE_SUM" == "$OLD_FREEZE_SUM" ]] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please also mention that while
cabal.project.freeze
restricts specified dependency versions, it does NOT prevent from including future dependencies unlessreject-unconstrained-dependencies=all
is specified