# Request for comment: \`cabal freeze\` doesn't produce a lock file

**URL:** <https://discourse.haskell.org/t/request-for-comment-cabal-freeze-doesnt-produce-a-lock-file/11374>\
**Category:** Uncategorized\
**Created:** [February 9, 2025, 8:37pm UTC](https://discourse.haskell.org/t/request-for-comment-cabal-freeze-doesnt-produce-a-lock-file/11374 "2025-02-09T20:37:23Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![LaurentRDC](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/laurentrdc/32/4950_2.png) [@LaurentRDC](https://discourse.haskell.org/u/LaurentRDC)\
**Post date:** [February 9, 2025, 8:37pm UTC](https://discourse.haskell.org/t/request-for-comment-cabal-freeze-doesnt-produce-a-lock-file/11374/1 "2025-02-09T20:37:23Z")

</div>

Hello all,

I have recently been made aware that the file produced by `cabal freeze` is not a true lock file.

A _lock file_ is a file containing an exhaustive set of pinned dependency versions (and even sometimes with the hash of the source). The key property here is that a lock file is _exhaustive_: it contains the pinned version of every direct and indirect dependency.

You might want to use a lock file in situations where every direct and indirect dependency must be audited, and bringing in new dependencies (directly or indirectly) should only be done deliberately.

The `cabal freeze` command can be used to create a file which looks very much like a lock file, with one caveat: the pinned dependency versions are _not_ exhaustive.

Consider this example: a simple cabal project with a single cabal file, that looks like:

```cabal
executable myexe:
    build-depends: base, text

```

If I run `cabal freeze`, the freeze file might look like this:

```haskell
constraints: any.base ==4.20.0.0,
             any.text ==2.1.1

```

If I edit my cabal file to this:

```cabal
executable myexe:
    build-depends: base, text, containers >=0.6

```

`cabal-install` will happily build my package, even though `containers` was not part of the freeze file. \[1\].

My question for the community is twofold:

1. Did you know about this, or did you assume (like me) that such a situation would result in a build error?
2. Is there a situation where it is desirable for the `cabal freeze` output to be non-exhaustive, or should we work towards plugging this hole?

* * *

1. [Even worse, `cabal build --reject-unconstrained-dependencies=all` will not reject this build either, since the new dependency (`containers`) has a constraint](https://github.com/haskell/cabal/pull/10785#issuecomment-2646565940).

---

<div class="post-metadata">

**Author:** ![Kleidukos](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/kleidukos/32/1213_2.png) [@Kleidukos](https://discourse.haskell.org/u/Kleidukos)\
**Post date:** [February 9, 2025, 8:43pm UTC](https://discourse.haskell.org/t/request-for-comment-cabal-freeze-doesnt-produce-a-lock-file/11374/2 "2025-02-09T20:43:29Z")

</div>

I remember having produced freeze files from which I’d strip some dependencies like `unix` in order for contributors on Windows to be able to build and run the software. Similarly, I know that at work we strip out the boot dependencies of GHC from the freeze file in order for the project to be buildable with two versions of the compiler. If there are ways to produce freeze files that can be flexible enough so that this is unnecessary, I’m all ears! 🙂

---

<div class="post-metadata">

**Author:** ![sclv](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/sclv/32/71_2.png) [@sclv](https://discourse.haskell.org/u/sclv)\
**Post date:** [February 9, 2025, 8:57pm UTC](https://discourse.haskell.org/t/request-for-comment-cabal-freeze-doesnt-produce-a-lock-file/11374/3 "2025-02-09T20:57:40Z")

</div>

I see no reason why someone would want this to be an error. I.e. I don’t think it is a hole that needs to be plugged.

---

<div class="post-metadata">

**Author:** ![TeofilC](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/teofilc/32/3987_2.png) [@TeofilC](https://discourse.haskell.org/u/TeofilC)\
**Post date:** [February 9, 2025, 9:08pm UTC](https://discourse.haskell.org/t/request-for-comment-cabal-freeze-doesnt-produce-a-lock-file/11374/4 "2025-02-09T21:08:32Z")

</div>

I would be quite keen to have lock files as a separate feature of cabal-install.  
I think freeze files as they stand are quite far from lock files.

Other things lacking from freeze files that I would expect from lock files:

- they don’t record source hashes for non-Hackage sources
- they don’t deal with Hackage revisions (though maybe this isn’t a huge problem)
- they aren’t in an easily machine readable format like JSON
- they can’t account for `setup` components requiring distinct versions of packages, and instead all the constraints apply to every scope. This might be a bigger problem in the future if we ever split out plugin/TH dependencies into its own scope

---

<div class="post-metadata">

**Author:** ![Bodigrim](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/bodigrim/32/1457_2.png) [@Bodigrim](https://discourse.haskell.org/u/Bodigrim)\
**Post date:** [February 9, 2025, 11:32pm UTC](https://discourse.haskell.org/t/request-for-comment-cabal-freeze-doesnt-produce-a-lock-file/11374/5 "2025-02-09T23:32:17Z")

</div>

One can create a custom `00-index.tar.gz` (e. g., from a Stackage snapshot) and set it as a local hackage repo in `cabal.config`. This gives you very solid reproducibility: other versions of packages are simply not there, revisions are not there. You can include platform-dependent packages (e. g., both `Win32` and `unix`) and you can include multiple versions of the same package if both were audited.

---

<div class="post-metadata">

**Author:** ![george.fst](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/george.fst/32/4933_2.png) [@george.fst](https://discourse.haskell.org/u/george.fst)\
**Post date:** [February 10, 2025, 12:43am UTC](https://discourse.haskell.org/t/request-for-comment-cabal-freeze-doesnt-produce-a-lock-file/11374/6 "2025-02-10T00:43:29Z")

</div>

> Did you know about this, or did you assume (like me) that such a situation would result in a build error?

I knew about this, because I’d peeked inside these files and seen that they actually just use `cabal.project` syntax, which in itself was a bit of a surprise. Like @TeofilC, I’m not convinced this is ideal for supporting other features we should want as part of freezing/locking.

> I see no reason why someone would want this to be an error. I.e. I don’t think it is a hole that needs to be plugged.

One might reasonably assume that the presence of a freeze file means that all dependency versions are locked down. This would be consistent with how lock files work with other tools, e.g. NPM or Nix flakes. So it would be surprising that one could then switch between `containers-0.6` and `containers-0.7` without Cabal either complaining or automatically updating `cabal.project.freeze`.

Anyway, I think this confirms a bigger issue that I’d suspected, which is that there isn’t widespread agreement on what exactly freeze files are for.

---

<div class="post-metadata">

**Author:** ![utopia](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/utopia/32/4927_2.png) [@utopia](https://discourse.haskell.org/u/utopia)\
**Post date:** [February 10, 2025, 1:45am UTC](https://discourse.haskell.org/t/request-for-comment-cabal-freeze-doesnt-produce-a-lock-file/11374/7 "2025-02-10T01:45:08Z")

</div>

thanks for posting this. i’m just getting into haskell after nearly two decades in ruby on rails, go, and most recently typescript/node. i just took lock files for granted. i’ve also spent a lot of time [arguing that](https://medium.com/p/bb594003354a) version _constraints_ are (generally) an anti-pattern.

in any case, i tried the freeze file, but then it wouldn’t let me change dependency versions. really kind of astonishing given the level of rigor i’m seeing within the haskell language.

---

<div class="post-metadata">

**Author:** ![utopia](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/utopia/32/4927_2.png) [@utopia](https://discourse.haskell.org/u/utopia)\
**Post date:** [February 10, 2025, 1:45am UTC](https://discourse.haskell.org/t/request-for-comment-cabal-freeze-doesnt-produce-a-lock-file/11374/8 "2025-02-10T01:45:56Z")

</div>

having a lock file is absolutely essential for reproducible builds. it’s just dependency management 101 in every other language i’ve seen.

---

<div class="post-metadata">

**Author:** ![utopia](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/utopia/32/4927_2.png) [@utopia](https://discourse.haskell.org/u/utopia)\
**Post date:** [February 10, 2025, 2:15am UTC](https://discourse.haskell.org/t/request-for-comment-cabal-freeze-doesnt-produce-a-lock-file/11374/9 "2025-02-10T02:15:30Z")

</div>

wow, thanks for the added detail.

---

<div class="post-metadata">

**Author:** ![theGhostJW](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/theghostjw/32/1302_2.png) [@theGhostJW](https://discourse.haskell.org/u/theGhostJW)\
**Post date:** [February 10, 2025, 6:54pm UTC](https://discourse.haskell.org/t/request-for-comment-cabal-freeze-doesnt-produce-a-lock-file/11374/10 "2025-02-10T18:54:39Z")

</div>

I see this comment added to a very recent cabal docs MR:

```haskell
 Please also mention that while cabal.project.freeze 
 restricts specified dependency versions, it does NOT 
 prevent from including future dependencies unless 
 reject-unconstrained-dependencies=all is specified

```

> <https://github.com/haskell/cabal/pull/9984/files#r1948174969>
>
> Follow on from the draft #8053, moving the new content to a "How to freeze" how-…to guide.
> 
> \* \[x\] Patches conform to the \[coding conventions\](https://github.com/haskell/cabal/blob/master/CONTRIBUTING.md#other-conventions).
> \* \[\] Is this a PR that fixes CI? If so, it will need to be backported to older cabal release branches (ask maintainers for directions).

Perhaps this is the switch you are looking for?

---

<div class="post-metadata">

**Author:** ![tbidne](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/tbidne/32/2949_2.png) [@tbidne](https://discourse.haskell.org/u/tbidne)\
**Post date:** [February 10, 2025, 9:06pm UTC](https://discourse.haskell.org/t/request-for-comment-cabal-freeze-doesnt-produce-a-lock-file/11374/11 "2025-02-10T21:06:17Z")

</div>

Unfortunately that flag is not enough to ensure exact dependencies. See this comment: [Add a `--lock` flag to `cabal freeze` to promote a freeze file to a lock file by LaurentRDC · Pull Request #10785 · haskell/cabal · GitHub](https://github.com/haskell/cabal/pull/10785#issuecomment-2646565940)

---

<div class="post-metadata">

**Author:** ![sclv](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/sclv/32/71_2.png) [@sclv](https://discourse.haskell.org/u/sclv)\
**Post date:** [February 10, 2025, 10:53pm UTC](https://discourse.haskell.org/t/request-for-comment-cabal-freeze-doesnt-produce-a-lock-file/11374/12 "2025-02-10T22:53:07Z")

</div>

One doesn’t even need to create a custom .tar.gz – a directory of packages itself suffices to be a local no-index repository: [3.1. Configuration — Cabal 3.6.0.0 User's Guide](https://cabal.readthedocs.io/en/3.6/installing-packages.html#local-no-index-repositories)

For fully locked down build structures (for compliance reasons) – the only reason i can imagine people would want a “true” lockfile – this seems like a more thorough and complete solution than any lockfile would be, no matter what semantics it is given.

---

<div class="post-metadata">

**Author:** ![sclv](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/sclv/32/71_2.png) [@sclv](https://discourse.haskell.org/u/sclv)\
**Post date:** [February 10, 2025, 10:58pm UTC](https://discourse.haskell.org/t/request-for-comment-cabal-freeze-doesnt-produce-a-lock-file/11374/13 "2025-02-10T22:58:28Z")

</div>

> [@utopia](#):
>
> having a lock file is absolutely essential for reproducible builds. it’s just dependency management 101 in every other language i’ve seen.

The “freeze” files cabal gives absolutely suffice for reproducible builds – if the package does not change, and if one also locks the index-state, then every build is deterministically the same.

The request is that the package be able to be _changed_ and that change be checked to ensure that it doesn’t draw in new dependencies.

But of course, when you change the package, your build will _never_ reproduce the prior package, because the thing you are building has changed!

So clearly there is some other desire at play beyond “reproducible builds”?

---

<div class="post-metadata">

**Author:** ![LaurentRDC](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/laurentrdc/32/4950_2.png) [@LaurentRDC](https://discourse.haskell.org/u/LaurentRDC)\
**Post date:** [February 11, 2025, 12:11am UTC](https://discourse.haskell.org/t/request-for-comment-cabal-freeze-doesnt-produce-a-lock-file/11374/14 "2025-02-11T00:11:59Z")

</div>

My initial use-case is for compliance reasons indeed, not for reproducible builds.

I like @Kleidukos 's explanation that sometimes you might want to constrain 90%+ of your dependencies, but still allow some flexibility to account for e.g. different platforms. This means that there’s a clear place for freeze files, and that lock files would be a separate concept

---

<div class="post-metadata">

**Author:** ![hasufell](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/hasufell/32/1250_2.png) [@hasufell](https://discourse.haskell.org/u/hasufell)\
**Post date:** [February 11, 2025, 2:15am UTC](https://discourse.haskell.org/t/request-for-comment-cabal-freeze-doesnt-produce-a-lock-file/11374/15 "2025-02-11T02:15:29Z")

</div>

> [@sclv](#):
>
> The “freeze” files cabal gives absolutely suffice for reproducible builds

That doesn’t work across platforms. You’d need freeze files for every OS and architecture.

Additionally, you have no actual hash protection for build inputs. Not that it matters in practice.

* * *

But I agree… if this is about compliance, use repos.

---

<div class="post-metadata">

**Author:** ![sclv](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/sclv/32/71_2.png) [@sclv](https://discourse.haskell.org/u/sclv)\
**Post date:** [February 11, 2025, 7:23pm UTC](https://discourse.haskell.org/t/request-for-comment-cabal-freeze-doesnt-produce-a-lock-file/11374/16 "2025-02-11T19:23:25Z")

</div>

> That doesn’t work across platforms. You’d need freeze files for every OS and architecture.

Well yes, but there’s no precise “meaning” to reproducing a build between linux and windows, the builds are definitionally different.

---

<div class="post-metadata">

**Author:** ![utopia](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/utopia/32/4927_2.png) [@utopia](https://discourse.haskell.org/u/utopia)\
**Post date:** [February 11, 2025, 9:57pm UTC](https://discourse.haskell.org/t/request-for-comment-cabal-freeze-doesnt-produce-a-lock-file/11374/17 "2025-02-11T21:57:27Z")

</div>

if you can’t change the freeze file, how is it useful? i don’t want to just release one time.

> [@sclv](#):
>
> But of course, when you change the package, your build will _never_ reproduce the prior package, because the thing you are building has changed!

huh? what do you mean reproduce the _prior_ package?

---

<div class="post-metadata">

**Author:** ![MangoIV](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/mangoiv/32/3519_2.png) [@MangoIV](https://discourse.haskell.org/u/MangoIV)\
**Post date:** [February 12, 2025, 1:12pm UTC](https://discourse.haskell.org/t/request-for-comment-cabal-freeze-doesnt-produce-a-lock-file/11374/18 "2025-02-12T13:12:03Z")

</div>

> [@utopia](#):
>
> if you can’t change the freeze file, how is it useful? i don’t want to just release one time.

You create a new freeze file based on your changed package description. I think this is pretty consistent with how everyone else does it.

---

<div class="post-metadata">

**Author:** ![sclv](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/sclv/32/71_2.png) [@sclv](https://discourse.haskell.org/u/sclv)\
**Post date:** [February 12, 2025, 9:08pm UTC](https://discourse.haskell.org/t/request-for-comment-cabal-freeze-doesnt-produce-a-lock-file/11374/19 "2025-02-12T21:08:12Z")

</div>

> [@utopia](#):
>
> huh? what do you mean reproduce the _prior_ package?

I mean if I have a package repo at state A, then a “reproducible build” to me is something that turns that repo reproducibly (i.e. over multiple runs on multiple machines) into a binary B. If I change that repo to state A1 by changing the code, then the binary produced will be B1 – different code produces a different binary, that’s the point!

So to ask that we have a “reproducible build” between A and A1 makes no sense to me – both states should produce different binaries, it would be a bug if one build “reproduced” the other.

As MangoIV says above, a freeze file is specific to a fixed state of code. When the code changes, the freeze file should too.

---

<div class="post-metadata">

**Author:** ![sketch3364](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/sketch3364/32/4962_2.png) [@sketch3364](https://discourse.haskell.org/u/sketch3364)\
**Post date:** [February 12, 2025, 11:46pm UTC](https://discourse.haskell.org/t/request-for-comment-cabal-freeze-doesnt-produce-a-lock-file/11374/20 "2025-02-12T23:46:28Z")

</div>

I think you missed the issue: state A1 can create binaries B1, B2, B3, etc. from different runs, _even with a freeze file in the repo_. If the tooling neither warns about nor resolves said discrepancy, that’s not a lock file.

At that point, why even have a freeze file?

[Next page](https://discourse.haskell.org/t/request-for-comment-cabal-freeze-doesnt-produce-a-lock-file/11374.md?page=2)
