From c4230704d739115a13284d8f713b8ef781309d5b Mon Sep 17 00:00:00 2001 From: Martin Sosic Date: Fri, 18 Mar 2022 22:41:05 +0100 Subject: [PATCH 1/2] Improved docs on cabal freeze. --- doc/cabal-package.rst | 68 +++++++++++++++++++++++++++++++++++-------- doc/cabal-project.rst | 2 +- 2 files changed, 57 insertions(+), 13 deletions(-) diff --git a/doc/cabal-package.rst b/doc/cabal-package.rst index 2cad579ea04..7261246ea0f 100644 --- a/doc/cabal-package.rst +++ b/doc/cabal-package.rst @@ -1017,24 +1017,68 @@ 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. + +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. + +``cabal.project.freeze`` is intended to be committed to the version control. + +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. + +Common workflow +=============== + +Common workflow for using ``cabal freeze``, if you changed any dependencies in ``.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 committ the new ``cabal.project.freeze`` to the version control. + +Ensuring everything is frozen +============================= + +Since ``cabal`` reads both ``.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 ``.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. + +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 @@ -1063,7 +1107,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 diff --git a/doc/cabal-project.rst b/doc/cabal-project.rst index 988572e0a3d..12376ef342d 100644 --- a/doc/cabal-project.rst +++ b/doc/cabal-project.rst @@ -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) From 2d17559d2ce43838a9d06d452d89232cbb03f22d Mon Sep 17 00:00:00 2001 From: Martin Sosic Date: Fri, 18 Mar 2022 23:01:22 +0100 Subject: [PATCH 2/2] fix --- doc/cabal-package.rst | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/doc/cabal-package.rst b/doc/cabal-package.rst index 7261246ea0f..bc9c23ed798 100644 --- a/doc/cabal-package.rst +++ b/doc/cabal-package.rst @@ -1029,12 +1029,12 @@ This means it captures an exact version of every dependency, including dependenc 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. +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. 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. @@ -1047,22 +1047,24 @@ So if you are running and testing the code on your local machine, you are guaran 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. +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 ``.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 committ the new ``cabal.project.freeze`` to the version control. + +Finally, you will always want to commit the new ``cabal.project.freeze`` to the version control. Ensuring everything is frozen -============================= +""""""""""""""""""""""""""""" Since ``cabal`` reads both ``.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 ``.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. +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.