Skip to content

Conversation

@sellout
Copy link
Contributor

@sellout sellout commented Aug 6, 2025

Overview

It looks like the Cabal file had been broken for about a year. I meant to reduce the noise in the package options, and found that it wouldn’t even build. So, I fixed the build and added a minimal CI job to keep it working.

Fixes #5225.

Implementation notes

I tried to make the CI as minimal as possible – it only runs when a Cabal or Stack file is changed, only runs on one platform (x86_64-linux), only runs on PRs (not other branch pushes), doesn’t run tests, and doesn’t even do a full build1 but should do enough to catch when it’s fallen out of sync with stack.yaml.

Interesting/controversial decisions

Simplifying the package options in cabal.project wasn’t always supported, but it was added three years ago (in 3.8) and we recommend Cabal 3.10 (which came out 2.5 years ago), so I think it’s a safe feature to use.

Also, since the build was broken for about a year and no one noticed, another option is to just remove the cabal.project file.

Test coverage

A new CI job is added to ensure that this continues to work.

Loose ends

I personally don’t like setting compiler options in project files2 (Cabal or Stack), since they don’t cover published packages, but since we’re not putting any of this on Hackage anyway, I can’t complain too much.

Footnotes

  1. It does cabal build all --only-configure, which ensures all the dependencies of the leaf packages are built & available.

  2. Other than things like -Werror, which should only be enabled for local development.

@sellout sellout force-pushed the simplify-cabal branch 3 times, most recently from b91c338 to b708f8c Compare August 6, 2025 17:08
@ceedubs
Copy link
Contributor

ceedubs commented Aug 6, 2025

Thank you! If we have checks that this isn't totally broken then I'd be inclined to move it to the normal place instead of contrib so cabal builds work without needing to do one-of symlinks or however other people are doing it.

@sellout sellout force-pushed the simplify-cabal branch 3 times, most recently from 12860dd to 9afe903 Compare August 6, 2025 18:32
@sellout
Copy link
Contributor Author

sellout commented Aug 6, 2025

so cabal builds work without needing to do one-of symlinks or however other people are doing it.

I’ve been doing cabal build all --project-file contrib/cabal.project (newer versions support the slightly briefer --project-dir contrib, but not in 3.10, which is what we currently recommend).

I think @aryairani doesn’t want it moved, to make it clear that it’s unsupported. But we could set up an alias or something in the Nix dev shell, so that it magically works under Nix at least.

@sellout
Copy link
Contributor Author

sellout commented Aug 6, 2025

One thing with this, as I was iterating to get everything working, I found it basically impossible to get my environment to match the CI one. I did

rm -r ~/.local/state/cabal/store/
rm -r ~/.cache/cabal/
rm contrib/cabal.project.freeze
cabal clean
cabal update
cabal freeze --project-file contrib/cabal.project

And the generated contrib/cabal.project.freeze doesn’t have the same versions as CI wants to build.

So, I wasn’t locally catching the same issues that CI was, which meant a bit more back-and-forth with force pushes. I’m not sure what part of my local state I wasn’t clearing out, but I figured it wasn’t complaining about, say, numerals needing allow-newer because I already have a built numerals library somewhere.

Oh … I wonder if it’s because my Cabal comes from Nix, and maybe already prefers the Stackage-sourced packages from Nix. 🤔

@sellout
Copy link
Contributor Author

sellout commented Aug 6, 2025

On the plus side, this should run rarely (only when a Stack or Cabal file changes), but apparently I have something wrong with the caching, because the previous build cached 80 MB, the next build retrieved a (stale) entry from the cache, but then proceeded to rebuild everything … which took 23 minutes.

@sellout sellout force-pushed the simplify-cabal branch 3 times, most recently from 0fe83ec to 58e7723 Compare August 6, 2025 20:24
@sellout
Copy link
Contributor Author

sellout commented Aug 6, 2025

Ok, cool. Fixed the cache issue – it now takes 2 minutes, and will use stale cache entries to minimize rebuilds on a miss.

@sellout sellout marked this pull request as ready for review August 6, 2025 20:28
@sellout sellout requested a review from a team as a code owner August 6, 2025 20:28
@aryairani
Copy link
Contributor

so cabal builds work without needing to do one-of symlinks or however other people are doing it.

I’ve been doing cabal build all --project-file contrib/cabal.project (newer versions support the slightly briefer --project-dir contrib, but not in 3.10, which is what we currently recommend).

I think @aryairani doesn’t want it moved, to make it clear that it’s unsupported. But we could set up an alias or something in the Nix dev shell, so that it magically works under Nix at least.

Well, the thing is that the Cabal build has to succeed for CI to pass, then we do have to officially support it, which I'm kind of hesitant about.

@aryairani
Copy link
Contributor

I'll note that it only takes 5 seconds to run though, after 2 mins of installing Haskell 😅

@sellout
Copy link
Contributor Author

sellout commented Aug 7, 2025

Well, the thing is that the Cabal build has to succeed for CI to pass, then we do have to officially support it, which I'm kind of hesitant about.

sigh This is something that GitHub has been avoiding supporting for over five years now. There are various workarounds depending on the particular situation, but it’s a mess.

I think in our case, if we don’t add the Cabal job to Mergify, it should work out fine. Except that we’ll see a ❌ next to one of the checks when Cabal stops working. But it won’t get in the way of merging or anything.

@sellout
Copy link
Contributor Author

sellout commented Aug 7, 2025

I'll note that it only takes 5 seconds to run though, after 2 mins of installing Haskell 😅

Yeah, I’m surprised haskell-actions/setup doesn’t have some built-in support for caching GHC & Cabal. Maybe we could add an explicit caching step for that?

@ceedubs
Copy link
Contributor

ceedubs commented Aug 7, 2025

@dolio you use cabal instead of stack, right? What have you been doing for cabal builds? Do you have local changes applied on the cabal files that you don't check in?

@dolio
Copy link
Contributor

dolio commented Aug 7, 2025

Yeah, I use cabal. I usually use my own files for it, since I started before the file got checked in. They're not too different than the checked-in ones, though. The differences are

  • The packages section has unison-cli-integration and unison-runtime
  • I have allow-newer: numerals:*
  • I recently added Chris' hs-mcp git repository
  • I don't use the haskeline repository
  • I don't use any constraints: section
  • I don't have most of the ghc-options sections at the bottom with warnings and stuff

Also, for package versions, I use a cabal.project.freeze file that matches the stack resolver. You can get them here; just replace the version with the one you want (I linked to the one we currently use). I might have a couple edits, though, because e.g. the hashtables version doesn't build on Fedora anymore (I think it uses C code that's rejected on newer gcc versions).

Oh, also, the constraints section is probably good, at least. Some stuff isn't in the stackage file, so if you want a particular version it needs to be specified elsewhere. As is I'm getting whatever the solver picks for those (which still works, but might vary depending on how recent your package db is and such).

It looks like this PR fixes up most of the discrepancies, and the ghc-options at the bottom is a lot nicer than the extreme repetition that I was avoiding, so maybe I could switch.

@sellout
Copy link
Contributor Author

sellout commented Aug 7, 2025

* The `packages` section has `unison-cli-integration` and `unison-runtime`

Ah, unison-cli-integration tickles something I wasn’t sure how to check – what if the Cabal file is missing a “leaf” package. Catching missing dependencies is easy (which is why this PR adds unison-runtime).

* I have `allow-newer: numerals*`
* I recently added Chris' `hs-mcp` git repository

These are fixed in this PR.

* I don't use the `haskeline` repository

Interesting … I notice the Stackage freeze file has haskeline installed, which I guess is because it’s a dependency of Stack itself or something? The haskeline dependency is confusing – we’re not pinned to the

* I don't use any `constraints:` section

Yeah, the freeze file would eliminate the need for this.

* I don't have most of the `ghc-options` sections at the bottom with warnings and stuff

This PR eliminates most of that, and brings it in line with the Stack settings.

Also, for package versions, I use a cabal.project.freeze file that matches the stack resolver.

Ah, I didn’t realize Stackage produced freeze files. Cool! I was thinking we should have one for the Cabal build anyway, and it’d help keep things even more consistent between the two.

I might have a couple edits, though, because e.g. the hashtables version doesn't build on Fedora anymore (I think it uses C code that's rejected on newer gcc versions).

Well, that’s annoying. If I update this PR with the freeze file, do you think you could try out this branch and make any edits to the freeze file that you need? Then you could hopefully use the “officially unsupported” cabal.project file?

One issue with the freeze file is that I wonder if @ceedubs’s symlink approach will work – will Cabal notice that it should look in ./contrib/ for the freeze file if it’s finding the cabal.project outside of ./contrib/? (I’m cautiously optimistic, as it must be resolving the paths in the cabal.project file relative to the canonical location, not the symlink location.)

@dolio
Copy link
Contributor

dolio commented Aug 7, 2025

Yeah, I don't actually know. It might require symlinking both if you want to do it that way.

I think unison-cli-integration might be part of a test suite that you usually wouldn't think to run, except when you break it in CI (which I have).

I can definitely test the branch out.

@sellout sellout force-pushed the simplify-cabal branch 2 times, most recently from 3a6f7ae to 8fab6dd Compare August 7, 2025 18:46
@sellout
Copy link
Contributor Author

sellout commented Aug 7, 2025

Yeah, I don't actually know. It might require symlinking both if you want to do it that way.

My optimism didn’t pan out – I don’t think the single symlink worked.

I can definitely test the branch out.

It should be testable now, with the changes I mentioned. (And what would it take for you to abandon your local files in favor of these?)

It’s currently failing in CI. I originally thought it was a merge issue, since the MCP stuff had changed in trunk, but I’ve rebased and it’s still failing, so maybe it’s a caching issue.

@dolio
Copy link
Contributor

dolio commented Aug 7, 2025

Okay, according to diff, the only real difference in the freeze file I was using is hashtables ==1.4.2. That's to fix the error on new GCCs. I just rebuilt all dependencies from scratch a day or two ago, so with that change they should all work, I think.

And I can probably switch to these once they're merged. They're similar enough to what I was using, and the program-options section was nicer than what I was doing.

Not sure what's going on with the error. Sorry.

@dolio
Copy link
Contributor

dolio commented Aug 7, 2025

Update: I get the same MCP error building your branch locally. So it's not anything to do with caching.

I think it looks like trunk has changed the hs-mcp commit, so you need to copy that change to the cabal.project.

sellout added 5 commits August 7, 2025 21:05
This should make our builds more consistent between Stack & Cabal.
Makes it easier to keep cabal.project up to date if you can eyeball things.

The one substantive change here is that cabal.project adds `constraints`
for non-Stackage packages that are pinned in stack.yaml.
@sellout
Copy link
Contributor Author

sellout commented Aug 8, 2025

One way to make Cabal maybe feel less like a CI requirement is to only run this workflow on trunk.

Won’t get complaints during development, but we’ll see it red on trunk … but then trunk is red, which is maybe worse.

sellout added 2 commits August 7, 2025 23:12
It previously applied to a caching step, but now applies to the `on.pull_request.paths`.
@aryairani
Copy link
Contributor

Fwiw I don't mind if the file isn't in ./contrib, I'm just trying to avoid having to maintain both the stack project files and the cabal project files myself in each PR generally.

Is there a way it can be automatically generated from stack.yaml?

One way to make Cabal maybe feel less like a CI requirement is to only run this workflow on trunk.

Won’t get complaints during development, but we’ll see it red on trunk … but then trunk is red, which is maybe worse.

Yeah, I don't want trunk to be red.

It looks like what some people are doing to work around the Github limitation is to create a job that always passes, but then posts a warning to the PR on: pull_request stating that the cabal build is busted. e.g. burningmantech/ranger-ims-server#1347 / https://github.com/mainmatter/continue-on-error-comment.

Then to make it "not official" do we just say we're allowed to ignore that warning if we feel like it; and some cabal friend will fix it eventually. All commits from that point until it's fixed will presumably get the warning, though, so it might be noisy.

@sellout
Copy link
Contributor Author

sellout commented Aug 8, 2025

Is there a way it can be automatically generated from stack.yaml?

It looks like there’s a tool for this! https://hackage.haskell.org/package/stack2cabal (but it hasn’t been updated since GHC 8.10).

It looks like what some people are doing to work around the Github limitation is to create a job that always passes, but then posts a warning to the PR on: pull_request stating that the cabal build is busted. e.g. burningmantech/ranger-ims-server#1347 / https://github.com/mainmatter/continue-on-error-comment.

Yeah, this seems reasonable – the comment could even @ mention volunteers who are interested in keeping the Cabal build working (I opened mainmatter/continue-on-error-comment#13 so we could hopefully customize the message).

One complication (I think) is that the workflow needs to switch from on.pull_request to on.pull_request_target and be merged to trunk before it’ll work (see https://github.com/mainmatter/continue-on-error-comment#permissions).

Then to make it "not official" do we just say we're allowed to ignore that warning if we feel like it; and some cabal friend will fix it eventually. All commits from that point until it's fixed will presumably get the warning, though, so it might be noisy.

Well, the job only runs on PRs that change stack.yaml, cabal.project, or *.cabal – I’m not sure what percentage of PRs that is (a lot of things might end up affecting a *.cabal file), but it’s not all of them, at least.

And yeah, the comment can be phrased in a way to downplay the importance, let someone else get to it, etc.

@aryairani
Copy link
Contributor

aryairani commented Aug 8, 2025

Is there a way it can be automatically generated from stack.yaml?

It looks like there’s a tool for this! hackage.haskell.org/package/stack2cabal (but it hasn’t been updated since GHC 8.10).

Oh hm; we're on 9.6.5.

Yeah, this seems reasonable – the comment could even @ mention volunteers who are interested in keeping the Cabal build working (I opened mainmatter/continue-on-error-comment#13 so we could hopefully customize the message).

Fwiw, I wasn't clear that we want to use continue-on-error necessarily? Or maybe that is exactly what we want. I probably would just write a single custom step with an if that depends on the previous step's failure, and does the commenting action.

One complication (I think) is that the workflow needs to switch from on.pull_request to on.pull_request_target and be merged to trunk before it’ll work (see mainmatter/continue-on-error-comment#permissions).

I didn't check the link but maybe moot if we don't use https://github.com/mainmatter/continue-on-error-comment

Well, the job only runs on PRs that change stack.yaml, cabal.project, or *.cabal – I’m not sure what percentage of PRs that is (a lot of things might end up affecting a *.cabal file), but it’s not all of them, at least.

And yeah, the comment can be phrased in a way to downplay the importance, let someone else get to it, etc.

True, I'm down

@sellout
Copy link
Contributor Author

sellout commented Aug 8, 2025

It looks like there’s a tool for this! hackage.haskell.org/package/stack2cabal (but it hasn’t been updated since GHC 8.10).

Oh hm; we're on 9.6.5.

That doesn’t matter too much – it’s a standalone executable. It’s more that it might not support newer features of Stack & Cabal. But … I couldn’t even get it to compile on GHC 8.10 out of the box, so it’d be some effort to adopt this approach.

Fwiw, I wasn't clear that we want to use continue-on-error necessarily? Or maybe that is exactly what we want. I probably would just write a single custom step with an if that depends on the previous step's failure, and does the commenting action.

Yeah, but workflows are fiddly – using an existing action when possible is a win, IMO. But if there’s friction, sure, it’s easy enough to do internally.

One complication (I think) is that the workflow needs to switch from on.pull_request to on.pull_request_target and be merged to trunk before it’ll work (see mainmatter/continue-on-error-comment#permissions).

I didn't check the link but maybe moot if we don't use https://github.com/mainmatter/continue-on-error-comment

It looks like it’s necessary in order for a workflow to be able to comment on a PR opened from a fork.

@dolio
Copy link
Contributor

dolio commented Aug 8, 2025

I don't really see the point of checking in an automated dump of the stack files, if that's the idea.

@aryairani
Copy link
Contributor

I don't really see the point of checking in an automated dump of the stack files, if that's the idea.

You guys tell me — why or why not?

@sellout
Copy link
Contributor Author

sellout commented Aug 9, 2025

I don't really see the point of checking in an automated dump of the stack files, if that's the idea.

I’m curious about this, too. I’m in the habit of committing a lot of generated files to my repos, because I tend to do everything pretty programmatically with Nix. So, to allow non-Nix users to contribute, I commit generated things like Cabal files, hlint config, etc. – and we do similar, at least with Cabal files.

If we want non-Stack users to be contributors, I think we either need to commit these files (as we do with Cabal files) or have a repo-local script that will allow contributors to do it without external dependencies. If we were to do it with external dependencies, like stack2cabal, I would be inclined to say that the Cabal files should also be done that way and removed from the repo.

And the other issue is if they’re slightly hand-edited (like the freeze file in this PR). We can have a patching step in the generation script to handle that, but those slight differences from the generated files make committing more attractive.

Granted, I would love to delete all the generated files from my repos, but I haven’t figured out how to make that work for outside contributors yet.

Regardless, I don’t think generating files is something that would be done in this PR, regardless, since the tooling for it would need work.

@sellout
Copy link
Contributor Author

sellout commented Aug 18, 2025

What should I do to get this mergable? Switch CI to use the continue-but-comment approach? Anything else?

@aryairani
Copy link
Contributor

aryairani commented Aug 18, 2025

Yeah, I think just test what happens when the cabal build fails — I'd still want Github to show the overall green checkmark, but if the Cabal task added a comment to a PR, that'd be fine but not mandatory. If it showed the "Skipped" icon in the Checks list, while still showing Green overall, that seems nice but not mandatory.

We now have a freeze file committed to the repo, which matches the Stack
resolver, so just use that one.
Cabal failing to build the package won’t cause any CI jobs to fail, but
it will add a comment to the PR. The comment is removed when the Cabal
build is fixed.
@sellout
Copy link
Contributor Author

sellout commented Aug 27, 2025

Ok, I just pushed the continue-on-error-comment changes. Note that because it uses pull_request_target, it won’t run on this PR (it needs to be in the target branch to be run … which is the only way to allow PRs opened on forks to add comments). But I tested it using pull_request in #5850. The only change from there to here is replacing pull_request with pull_request_target … so it should behave once merged 🙄

@aryairani
Copy link
Contributor

it should behave once merged 🙄

Yeah, that's always the way with any of the CI stuff. We'll merge it and see what happens.

Maybe we can use a similar technique on the Nix jobs too if this is successful; I've been nudging them along the last few days (or is there a way to make them more robust? separate issue)

@aryairani aryairani merged commit fee952d into unisonweb:trunk Aug 27, 2025
17 checks passed
@sellout
Copy link
Contributor Author

sellout commented Aug 27, 2025

Ah, I hadn’t realized the Nix jobs were failing. I’ll take a look.

@sellout sellout deleted the simplify-cabal branch September 3, 2025 14:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

test Cabal in CI

4 participants