Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 58 additions & 12 deletions doc/cabal-package.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1017,24 +1017,70 @@ disambiguation purposes. Example:
$ cabal repl bench:baz

Freezing dependency versions
""""""""""""""""""""""""""""

If a package is built in several different environments, such as a
development environment, a staging environment and a production
environment, it may be necessary or desirable to ensure that the same
dependency versions are selected in each environment. This can be done
with the ``freeze`` command:
^^^^^^^^^^^^^^^^^^^^^^^^^^^^

.. code-block:: console

$ cabal freeze

The command writes the selected version for all dependencies to the
``cabal.config`` file. All environments which share this file will use
the dependency versions specified in it.
generates ``cabal.project.freeze`` file, which describes the exact dependency tree as it was resolved at that moment by Cabal.
This means it captures an exact version of every dependency, including dependencies of dependencies, recursively all the way.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you definitely want to include the caveats from #8059 that this is a platform-specific freeze, and as such is not necessarily usable by other people.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I mostly agree with this comment: #8059 (comment)


Since ``cabal`` reads ``cabal.project.freeze`` when present, and takes into consideration the version constraints in it,
this means that by producing ``cabal.project.freeze`` you are guaranteed that every future ``cabal`` call will use the exact same set of dependencies,
regardless of any updates (even patches) that might get published for these dependencies in the meantime.
Therefore, we have effectively "frozen" the dependencies in place, making our build consistent and reproducible.

``cabal.project.freeze`` is intended to be committed to the version control.
Copy link
Copy Markdown
Member

@jneira jneira Mar 23, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should warn about the fact cabal.project.freeze is heavily os dependent, specially if you are using native libs.
The thing is even harder if you support windows, as widely used libs have conditionals in the .cabal file using os(windows)
See the caveats in a recent issue: #8059

Copy link
Copy Markdown
Member

@jneira jneira Mar 23, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An alternative could be only freeze the hackage index state, as commented here: #8059 (comment)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also i would note it also freezes cabal flags and it could drive to unexpected results: #7944

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, and it is also dependent on ghc version 🙂 : #7367

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And there is another request about makes docs clear on what to do when you have both a executable and a library: #5750.

Copy link
Copy Markdown
Member

@jneira jneira Mar 23, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Martinsos sorry, i dont want to scare you. I am reviewing the existing issues about freeze and it turns out it has several caveats.
The pr looks nice overall and i love its user friendly style. I would ask only to mention the fact they are (or they might be) dependent on ghc and os, rest of things (freeze index state alternative, flags and #5750) could be added in other prs

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hah @jneira, no worries, nothing to be scared of :D, I am glad you are sharing all this info as I wasn't aware of it!
Since we are using cabal freeze in our project currently, I am anyway trying to learn as much as I can about it and how to use it correctly (as is visible in #8059 and #8047), and while doing that I discovered some of these caveats on my own, while the rest I am happy to learn about.
This PR is really a way for me to capture all that I learn on this "journey" and make it a bit easier for others, therefore I am happy to capture as much info as we can.

Let me play a bit more with freeze following days, and I will be coming back here to either update PR with what I learn, or ask additional questions, until we have something that gives a good overview of freeze.

Some questions immediately:

  1. flags -> do I want to freeze them or not? I have to admit I don't understand this topic well enough -> what would be your quick take on it? Are there use cases where I want to freeze them, are there ones where i don't want to? What are those? Is there a general quick advice on how to think about this?
  2. dependent on ghc version -> makes sense, it is not a problem for the project I am currently working on as it is executable that we build with specific ghc always, but I guess it can be a problem for some libraries? Although I thought for them it is not recommended to use freeze file. I guess I am asking what is the situation where one has multi-ghc project and needs freeze file. Probaly should ask that on the relevant issue though and not here -> i is not so important right now anyway.
  3. As for using freeze when project has both executable and a library -> do we know the answer? I am not sure -> I guess it comes down to ensuring that freeze file doesn't get included into the sdist, therefore it doesn't get distributed with a library, but it does get distributed with an executable?

Btw, looking at different issues -> different GHC versions, different OS-es, it seems to me the proper solution might be in the direction of having per-environment (where environment is defined as combo of os, ghc and arch) freeze files. Meaning that user could define environments it cares about, and then on cabal freeze, freeze file for each of those environments would be generated (is that possible? Can we generate freeze file for osx while on linux -> I guess in theory we should be able to, right?). Additionally, cabal would use correct freeze file based on the current environment.
But I will take this question to #8059 .

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

flags -> do I want to freeze them or not? I have to admit I don't understand this topic well enough -> what would be your quick take on it? Are there use cases where I want to freeze them, are there ones where i don't want to? What are those? Is there a general quick advice on how to think about this?

well there are two kind of cabal flags, automatic and manual and that determines its freezing imo:

  • the cabal solver tries the automatic flags to get a working build plan, so freeze them makes more sense imo. The user usually dont care about them (if they work :-) ) although you can set them explicitly. The archetipical example is integer-gmp/integer-simple which switch between two implementations of the same api.
  • manual ones must be explicitly set by the user, you can use the cli -f option or store it in the cabal.project(.local) if you want to avoid set them in every invocation. That is the case of Provide a way to let cabal freeze freeze dependencies, only #7944 where you have a debug flag you only want to set sometimes. So freeze them could make sense or could not, depending of its usage.

But cabal freeze store them all so you have to delete manually the unwanted ones


Do you need this?
"""""""""""""""""

Why would you want this? 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 continuos integration will be running the exact same code,
and that at the end that exact same code will get deployed.

Usual use-case for using ``cabal freeze`` is when developing end-user code, for example an executable that you will distribute.
On the other hand, if you are developing a library, you will not want to distribute it together with the ``cabal.project.freeze`` file, as it would make it very hard for cabal to resolve dependencies for users of the library, since they would be too strict.

Common workflow
"""""""""""""""

Common workflow for using ``cabal freeze``, if you changed any dependencies in ``<yourproject>.cabal`` file or want to update their versions, is to delete ``cabal.project.freeze`` file (if it already exists) and run ``cabal freeze`` to generate fresh version of ``cabal.project.freeze``.

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.
If not sure, best to delete ``cabal.project.freeze`` first and then run ``cabal freeze``.

Finally, you will always want to commit the new ``cabal.project.freeze`` to the version control.

Ensuring everything is frozen
"""""""""""""""""""""""""""""

Since ``cabal`` reads both ``<yourproject>.cabal`` and ``cabal.project.freeze`` files and combines version constraints from them, you can get into a state where not all dependencies are frozen, i.e. if you add a dependency to ``<yourproject>.cabal`` but forget to regenerate ``cabal.project.freeze`` after it -> now this new dependency will not be frozen and might get updated unexpectedly.

To check if you are in such state, you can just run ``cabal freeze`` and check if ``cabal.project.freeze`` changed its contents -> if so, somebody forgot to regenerate ``cabal.project.freeze`` previously. This will also fix the problem at the same time.

To automate this check, you can make it a part of your continuous integration, or a part of your pre-commit hook.

Example of how this can be done via bash script:

.. 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" ]]


Generating dependency version bounds
""""""""""""""""""""""""""""""""""""
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Cabal also has the ability to suggest dependency version bounds that
conform to `Package Versioning Policy`_, which is
Expand Down Expand Up @@ -1063,7 +1109,7 @@ For example, given the following dependencies specified in
bar >= 1.1 && < 1.2

Listing outdated dependency version bounds
""""""""""""""""""""""""""""""""""""""""""
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Manually updating dependency version bounds in a ``.cabal`` file or a
freeze file can be tedious, especially when there's a lot of
Expand Down
2 changes: 1 addition & 1 deletion doc/cabal-project.rst
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ file with ``profiling: True``.

The full configuration of a project is determined by combining the
following sources (later entries override earlier ones, except for appendable
options):
options, like dependency version constraints):

1. ``~/.cabal/config`` (the user-wide global configuration)

Expand Down