Cabal freeze file includes multiple choices. Why?

I was under the impression that the cabal.project.freeze file generated by cabal freeze was supposed to lock the exact version of all the dependencies used by your package. I was therefore surprised to see something like this in my freeze file

$ cat cabal.project.freeze | grep '||'
constraints: any.Cabal ==3.10.2.0 || ==3.10.3.0,
             any.Cabal-syntax ==3.10.2.0 || ==3.10.3.0,
             any.directory ==1.3.8.1 || ==1.3.8.4,
             any.filepath ==1.4.200.1 || ==1.5.2.0,
             any.process ==1.6.18.0 || ==1.6.19.0,
             any.unix ==2.8.4.0 || ==2.8.5.1,

So if I’m reading this correctly, my freeze file actually admits 64 different possible solutions?

I’m curious if anyone knows what’s going on here? The cabal freeze documentation is silent on the matter.

2 Likes

I too was just wondering this. Here’s a relevant issue.

2 Likes

Ah. That is very helpful. Thank you!

Freeze should only use equals. Anything else is a bug on its face.

I think the lack of good and easy lockfile support is Haskell’s one weakness compared to mainstream language package managers. It’s not complicated but cabal doesn’t do it well.

1 Like

Beyond this issue, what other do you see? I’m interested in your feedback. :slight_smile:

stack does it well, no ?

When my deps are in Stackage, yeah. But cabal does tend to work well for most things in Hackage, which is really nice. The main pain point is the impurity of your Hackage index which in turns causes your plan to change as you cabal update.

The freeze file is kind of like a Stackage a la carte for my individual project - a set of versions known to build together.

1 Like

I guess I find that pinning index-state achieves pretty much everything I would want from a freeze file. I don’t know if it’s bulletproof, but in practice it makes everyone get the same plan which is what I want anyway. And it has the virtue that you can change version bounds etc without having to regenerate something, you only need to change it if you want something newer.

5 Likes

Does cabal guarantee that the plan won’t change across cabal versions? That’s the only other thing I can think of.

That said, I think checking in a freeze file is still nice because it makes versions explicit in source instead of implicit. I think that’s one appeal of them in other language’s tooling too.

stack doesn’t need things to be in stackage. It pins things equally well that are in stackage, hackage or a random git repo.

1 Like

Kind of - can it run the solver for my non-stackage deps? I’d like something a bit more decentralized and a la carte.

On stack you can use extra-deps in your stack.yaml to list any hackage or git repos you want, eg β€œpandoc-1.2.3”, that aren’t in stackage. The lock file was implemented in 2019, which does the expected locking down of precise content hashes. As you describe, it’s like generating your own stackage snapshot β€œplus extra ones,” with the same guarantees. It checks version bounds for you.

Stack itself pulls packages from https://casa.stackage.org/, which is a content addressed storage (hash of the contents), to better enable this workflow.

There’s a good example of this here inflex/stack.yaml at master Β· chrisdone-archive/inflex Β· GitHub

No, stack doesn’t do that (currently; it used to run cabal’s solver a few years back). For solving, you want cabal.

^ This is what I was replying to, it seemed you might have overlooked stack which was designed for this and does it well.

But perhaps you’re wishing for a tool that nails both solving and freezing (smart dependency search for easy experimentation and dealing with less-maintained software; reproducibility for production). I’d like that too.

1 Like

Updo, a tool for generating projects, has specific targets for Stack and Cabal but the default target generates projects for Stack and Cabal at the same time (Cabal before Stack). The upshot of running Updo this way is that Cabal projects get to use stackage (as much or as little as you please) and both Cabal’s freeze file (to see what the solver picked) and Stack suggest what exact version equality constraints to use for packages (for versions of packages either not in a resolver or that need overriding).

Some things that would help improve Updo;

  • If with Cabal, version equality constraints could be overridden
  • If with Cabal, we could specify hackage revisions (some-package == x.y.z@rev:1)
  • If with Stack, we could specify fine-grained allow-newer

With Updo, it is reasonably easy to combine the benefits of stackage package sets and solving for versions of packages outside those sets.

On lock files; I recommend committing Stack’s lock file but wouldn’t recommend committing Cabal’s lock file (the freeze file) as they vary by platform.

1 Like

Thanks for those comments. Updo sounds powerful, though personally the prospect of adding another layer on top of all the same existing tools is a little scary (as is dhall).

You know about allow-newer-deps I guess.