Debug why Cabal is choosing a version

At commit a0b03f1b3ed7ee31b7ee19caaa58d88afa5eb9d1 in Fourmolu, in the web/site/ subdirectory, if I run cabal build --dry-run, it resolves to hakyll-3.2.0.10. If I specify --constraint='hakyll>4', it shows the version I expect, hakyll 4.16.2.2. I would expect Cabal to always choose the more recent version if it’s possible, so why is it resolving to 3.2.0.10? And why did it just start doing so?

CI passed on this commit on Oct 13, 5p PST: Fix sort-constraints build (#439) · fourmolu/fourmolu@a0b03f1 · GitHub

But the same commit is getting a different build plan now. I can repro locally, but it’s also happening in CI for other PRs.

2 Likes

Does anything from this post onwards (on another Cabal-UX topic) help?

I do wish Cabal’s solver were more easily observable, though I don’t know exactly what I’d want from it in general. It’s a pretty hard UX problem.

Anyway, the solver is, for whatever reason*, prioritising picking a new containers over picking a new hakyll. Some change to some package on Hackage in the last few days has meant that we’re newly able to pick containers-0.7. This is out of bounds for lrucache, a dependency of hakyll which hasn’t been updated in 6 years. So a very old hakyll is chosen which doesn’t depend on lrucache.

I notice that hakyll has in that time been given a revision bumping it’s pandoc bound so it may well be some consequence of that. I haven’t looked in to it any further. Similarly, I don’t know why we end up with hakyll-3.2.0.10 when its lrucache dependency wasn’t added until hakyll-3.4.1.0.

For the record, some commands that helped me work this out:

cd web/site

copy-plan() {
    cat dist-newstyle/cache/plan.json | jq > $1
    cat $1 | jq -r '.["install-plan"] | map(select(.["pkg-name"] == "hakyll")) | .[0].["pkg-version"]'
    cat $1 | jq -r '.["install-plan"] | map(select(.["pkg-name"] == "containers")) | .[0].["pkg-version"]'
}

cabal build --dry
copy-plan basic.json # 3.2.0.10, 0.7
cabal build --dry --constraint='hakyll>4'
copy-plan constrained.json # 4.16.2.2, 0.6.8
cabal build --dry --index-state=2024-10-05T00:00:00Z
copy-plan old-index.json # 4.16.2.2, 0.6.8

GET_VERSIONS='.["install-plan"] | map({"pkg-name": .["pkg-name"], "pkg-version": .["pkg-version"]})'
diff -C3 <(cat basic.json | jq "$GET_VERSIONS") <(cat constrained.json | jq "$GET_VERSIONS")

* I don’t actually know quite what heuristic is used when there are multiple potential build plans, or whether this is documented anywhere? Usually, it’s just “use the latest version of everything” but in circumstances like this it’s not quite so simple.

tl;dr Use --allow-newer=lrucache:containers.

I think this issue might be the cause: https://github.com/haskell/cabal/issues/9669

TLDR is that the current behaviour is that we only pick a different version of boot packages if constraints disallow the already installed one

That issue may influence the specifics here, but I don’t think containers being a boot package is particularly important. There is just sometimes (very rarely) a tension between using the newest version of one package and of another.

1 Like

containers being a boot package is what plausibly answers the question "why a newer containers is picked over a newer hakyll". But I agree that it’s far from clear that it’s the right answer.

A way to get more-expert opinion is to open an issue on the cabal bug tracker and tag @grayjay — Kristen is our local solver guru.

1 Like

--allow-newer=lrucache:containers worked, thanks.

Opened Cabal issue here: Why Cabal changed build plans on us? · Issue #10470 · haskell/cabal · GitHub

1 Like