Add dockerized CI and a GHC 9.2-9.12 matrix - #3
Merged
Conversation
Every flipstone repo running the multi-GHC matrix workflow defines its packages in package.yaml, so move all three here to match. The generated .cabal files are committed alongside, as they are elsewhere. Metadata and bounds carry over unchanged apart from the corrections below. Three bounds were wrong, and building against six GHC versions is what surfaced them: resourcet >= 1.2 && < 1.3 excluded resourcet-1.3.0, which is what lts-24 carries, so ftp-client-conduit could not build there at all. The module uses resourcet only for the MonadResource class, which is unchanged across that boundary, so the bound moves to < 1.4. ftp-client-conduit asked for crypton-connection >= 0.4 while ftp-client accepted >= 0.3. The conduit code uses exactly one symbol from the package, connectionClose, which 0.3 has. Relaxing it to match ftp-client lets both build on lts-22, whose crypton-connection is 0.3.2. base >= 4.8 (ftp-client) and base >= 4.7 (ftp-client-conduit) claimed support back to GHC 7.10. Nothing that old can build these packages, because crypton-connection did not exist then. Both become >= 4.16, the oldest GHC now actually tested, and tested-with records the rest.
This repo had no CI, and stack could not build it at all: stack.yaml pinned lts-16.9 (GHC 8.8.3), which cannot satisfy the crypton-connection dependency the package has claimed since 1cbd6b9. The hspec suite was reachable only by running cabal by hand, which is why it went unnoticed during review of #2. Adopts the same shape as the rest of the org: a dev container on the haskell-tools image, scripts that re-exec themselves into it, one stack-ghc-*.yaml per GHC, and a workflow whose matrix is discovered by globbing those files. scripts/test-all uses the same glob, so local and CI coverage cannot drift. ftp-client is the only repo here depending on crypton-connection, so the old resolvers need help. lts-21 has no crypton-connection and lts-20 has no crypton at all, so both pin the versions that shipped together in lts-22. The newer crypton-x509 1.9 series is deliberately avoided: it wants crypton 1.1 and the crypton-asn1-* rewrite, which would mean replacing most of the snapshot's crypto stack. No fourmolu and no formatting job, following haskell-non-empty-text -- this code has never been formatted and reformatting it would rewrite every line. No strict ghc-options flag yet either; -Wall currently reports 27 warnings in ftp-client and 22 across the other two, which is a separate cleanup. Lock files are committed and scripts/test enforces them in CI with --lock-file error-on-write, so stack.yaml.lock comes out of .gitignore.
Both packages have published metadata changes on this branch -- corrected base bounds, a corrected resourcet bound and a relaxed crypton-connection bound -- with no API change, so both get a patch bump. Neither package had a changelog; seed one for each.
There was a problem hiding this comment.
Pull request overview
Adds Dockerized CI across GHC 9.2–9.12 and restores Stack builds.
Changes:
- Adds six Stack configurations with lock files.
- Converts all packages to hpack and corrects dependency bounds.
- Adds containerized build, test, Cabal, and shellcheck workflows.
Reviewed changes
Copilot reviewed 25 out of 34 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
stack.yaml.lock |
Locks the default snapshot. |
stack.yaml |
Selects GHC 9.10 by default. |
stack-base.yaml |
Shares package and PVP settings. |
stack-ghc-9.2.yaml |
Configures GHC 9.2 dependencies. |
stack-ghc-9.2.yaml.lock |
Locks the GHC 9.2 build. |
stack-ghc-9.4.yaml |
Configures GHC 9.4 dependencies. |
stack-ghc-9.4.yaml.lock |
Locks the GHC 9.4 build. |
stack-ghc-9.6.yaml |
Configures GHC 9.6. |
stack-ghc-9.6.yaml.lock |
Locks the GHC 9.6 build. |
stack-ghc-9.8.yaml |
Configures GHC 9.8. |
stack-ghc-9.8.yaml.lock |
Locks the GHC 9.8 build. |
stack-ghc-9.10.yaml |
Configures GHC 9.10. |
stack-ghc-9.10.yaml.lock |
Locks the GHC 9.10 build. |
stack-ghc-9.12.yaml |
Configures GHC 9.12. |
stack-ghc-9.12.yaml.lock |
Locks the GHC 9.12 build. |
scripts/test-all |
Tests every matrix configuration. |
scripts/test |
Runs local or CI tests. |
scripts/shellcheck |
Adds shell-script linting. |
scripts/shell |
Opens the development shell. |
scripts/lib/run-in-container.sh |
Re-executes scripts in Docker. |
scripts/cabal-latest |
Builds with latest GHC and dependencies. |
scripts/build |
Provides the aggregate build command. |
scripts/bootstrap |
Initializes local Docker resources. |
ftp-client/package.yaml |
Adds hpack metadata and updated bounds. |
ftp-client/ftp-client.cabal |
Regenerates the package definition. |
ftp-client/CHANGELOG.md |
Documents the corrected base bound. |
ftp-client-conduit/package.yaml |
Adds hpack metadata and corrected bounds. |
ftp-client-conduit/ftp-client-conduit.cabal |
Regenerates the conduit package definition. |
ftp-client-conduit/CHANGELOG.md |
Documents dependency-bound corrections. |
example/package.yaml |
Adds hpack metadata for the example. |
example/example.cabal |
Regenerates the example package definition. |
compose.yaml |
Defines the development container. |
.gitignore |
Ignores generated build artifacts. |
.github/workflows/main.yaml |
Adds the CI matrix and validation jobs. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Both from review feedback on #3. scripts/lib/run-in-container.sh had an unquoted $0, which word splits and glob expands on a path containing spaces or metacharacters. The shellcheck job did not catch it: the file has no shebang, so file(1) reports it as ASCII text and scripts/shellcheck, which filters on "shell script", never passed it to the linter. Adding a shebang -- inert in a sourced file -- brings it into the linted set, and shellcheck then does report SC2086 for the unquoted $0, so the check now covers what it previously missed. The workflow only ran on push. A fork pull request pushes to the fork, not here, so external contributions received no checks at all. Adding pull_request alongside push would have doubled every same-repo run: the concurrency group keys on github.ref_name, which is the branch for a push but <n>/merge for a pull request, so the two never share a group and neither cancels the other. Scoping push to the default branch would have stopped branch pushes getting CI. Instead pull_request runs only when the head repository is not this one -- exactly the case where no push event fires here. Same-repo branches get one run from push, fork pull requests get one from pull_request, and nothing runs twice. This does not give same-repo pull requests merge-commit testing; they still test the pushed head. That is unreachable while keeping branch-push CI without duplicate runs, so it is a deliberate trade rather than an oversight.
onslaughtq
marked this pull request as ready for review
August 22, 2026 21:34
ysangkok
approved these changes
Aug 24, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds CI to a repo that had none, and makes the repo buildable with
stackagain.stack.yamlpinnedlts-16.9(GHC 8.8.3), which cannot satisfy thecrypton-connection >= 0.3bound this package has claimed since 1cbd6b9 — sostack buildcould not work here at all, and the hspec suite inftp-client/test/test.hs(which has existed since 2019) was reachable only byrunning
cabalby hand.What's here
haskell-toolsCI following the org pattern:compose.yaml,scripts/that re-exec into the container, onestack-ghc-*.yamlper GHC, anda workflow whose matrix is discovered by globbing those files — so adding a GHC
later is one new file, no workflow edit.
.cabalfilescommitted alongside, as elsewhere in the org.
No Haskell source changes.
Dependency bounds this turned up
Building against six GHCs found three bounds that were wrong:
resourcet >= 1.2 && < 1.3inftp-client-conduitexcludedresourcet-1.3.0,which is what current Stackage snapshots carry — so the package could not
build against them. The module uses resourcet only for the
MonadResourceclass, unchanged across that boundary, so the bound moves to
< 1.4.ftp-client-conduitrequiredcrypton-connection >= 0.4whileftp-clientaccepted
>= 0.3. The conduit code uses exactly one symbol from it,connectionClose, which 0.3 has. Relaxed to match.base >= 4.8(ftp-client) and>= 4.7(ftp-client-conduit) claimed supportback to GHC 7.10 and 7.8. Nothing that old can build these packages, because
crypton-connectiondoes not exist there. Both become>= 4.16, the oldest GHCactually tested, and
tested-withnow records the set.crypton-connectionis absent from lts-21 and earlier, so the 9.2 and 9.4 rungspin the crypton stack explicitly. The comments in those files explain why the
newer
crypton-x5091.9 series is avoided.Deliberately not included
No fourmolu and no formatting job — the code has never been formatted and doing
it here would bury the CI change in a whole-repo diff. No strict
ghc-optionsflag either. Both arrive in later PRs in this stack.
Stack position
First of six stacked branches, each containing the one before it. This one is
source-neutral on purpose, so every later PR arrives with a green matrix:
add-ci-multi-ghc← this PRfix-response-line-partialitypr-2-multiline-and-handle-exports(closes exposing handle creation functions and fixed issue in reading multi line response #2)add-ci-flagadd-henforcer-fourmolufix-audit-findings