CI: cabal-head prerelease: move to a current GitHub Action for prereleases
[cabal.git] / .github / workflows / validate.yml
blobae5e2c7a746cfcc73eb06920644659e1f2529bcb
1 name: Validate
3 # See: https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#concurrency.
4 concurrency:
5   group: ${{ github.ref }}-${{ github.workflow }}
6   cancel-in-progress: true
8 # Note: This workflow file contains the required job "Validate post job". We are using path filtering
9 # here to ignore PRs which only change documentation. This can cause a problem, see the workflow file
10 # "validate.skip.yml" for a description of the problem and the solution provided in that file.
11 on:
12   push:
13     paths-ignore:
14       - "doc/**"
15       - "**/README.md"
16       - "CONTRIBUTING.md"
17       - "changelog.d/**"
18       # only top level for these, because various test packages have them too
19       - "*/ChangeLog.md"
20       - "*/changelog.md"
21       - "release-notes/**"
22     branches:
23       - master
24   pull_request:
25     paths-ignore:
26       - "doc/**"
27       - "**/README.md"
28       - "CONTRIBUTING.md"
29       - "changelog.d/**"
30       - "*/ChangeLog.md"
31       - "*/changelog.md"
32       - "release-notes/**"
33   release:
34     types:
35       - created
36   workflow_call:
38   # See https://github.com/haskell/cabal/blob/master/CONTRIBUTING.md#hackage-revisions
39   workflow_dispatch:
40     inputs:
41       allow-newer:
42         description: allow-newer line
43         required: false
44         type: string
45       constraints:
46         description: constraints line
47         required: false
48         type: string
50 env:
51   # We choose a stable ghc version across all os's
52   # which will be used to do the next release
53   GHC_FOR_RELEASE: "9.4.8"
54   # Ideally we should use the version about to be released for hackage tests and benchmarks
55   GHC_FOR_SOLVER_BENCHMARKS: "9.4.8"
56   GHC_FOR_COMPLETE_HACKAGE_TESTS: "9.4.8"
57   COMMON_FLAGS: "-j 2 -v"
59   # See https://github.com/haskell/cabal/blob/master/CONTRIBUTING.md#hackage-revisions
60   ALLOWNEWER: ${{ github.event.inputs.allow-newer }}
61   CONSTRAINTS: ${{ github.event.inputs.constraints }}
63 jobs:
64   validate:
65     name: Validate ${{ matrix.sys.os }} ghc-${{ matrix.ghc }}
66     runs-on: ${{ matrix.sys.os }}
67     outputs:
68       GHC_FOR_RELEASE: ${{ format('["{0}"]', env.GHC_FOR_RELEASE) }}
69     strategy:
70       fail-fast: false
71       matrix:
72         sys:
73           - { os: windows-latest, shell: "C:/msys64/usr/bin/bash.exe -e {0}" }
74           - { os: ubuntu-latest, shell: bash }
75           - { os: macos-latest, shell: bash }
76         # If you remove something from here, then add it to the old-ghcs job.
77         # Also a removed GHC from here means that we are actually dropping
78         # support, so the PR *must* have a changelog entry.
79         ghc:
80           [
81             "9.10.1",
82             "9.8.2",
83             "9.6.6",
84             "9.4.8",
85             "9.2.8",
86             "9.0.2",
87             "8.10.7",
88             "8.8.4",
89           ]
90         exclude:
91           # Throws fatal "cabal-tests.exe: fd:8: hGetLine: end of file" exception
92           # even with --io-manager=native
93           - sys:
94               { os: windows-latest, shell: "C:/msys64/usr/bin/bash.exe -e {0}" }
95             ghc: "9.0.2"
96           # corrupts GHA cache or the fabric of reality itself, see https://github.com/haskell/cabal/issues/8356
97           - sys:
98               { os: windows-latest, shell: "C:/msys64/usr/bin/bash.exe -e {0}" }
99             ghc: "8.10.7"
100           # lot of segfaults caused by ghc bugs
101           - sys:
102               { os: windows-latest, shell: "C:/msys64/usr/bin/bash.exe -e {0}" }
103             ghc: "8.8.4"
104           # ghc before 8.10.5 doesn't run on AArch64
105           # 9.0.2 suffers from https://gitlab.haskell.org/ghc/ghc/-/issues/20592
106           # 8.10.7 throws asm errors in hashable's cbits suggesting the runner doesn't
107           # support a CPU extension for hardware SHA; may be fixable with flags
108           - sys:
109               { os: macos-latest, shell: bash }
110             ghc: "9.0.2"
111           - sys:
112               { os: macos-latest, shell: bash }
113             ghc: "8.10.7"
114           - sys:
115               { os: macos-latest, shell: bash }
116             ghc: "8.8.4"
117     defaults:
118       run:
119         shell: ${{ matrix.sys.shell }}
120     steps:
121       - name: Work around XDG directories existence (haskell-actions/setup#62)
122         if: runner.os == 'macOS'
123         run: |
124           rm -rf ~/.config/cabal
125           rm -rf ~/.cache/cabal
127       - uses: actions/checkout@v4
129       # See https://github.com/haskell/cabal/blob/master/CONTRIBUTING.md#hackage-revisions
130       - name: Add manually supplied allow-newer
131         if: github.event_name == 'workflow_dispatch' && github.event.inputs.allow-newer != ''
132         run: |
133           echo "allow-newer: ${{ github.event.inputs.allow-newer }}" >> cabal.validate.project
135       - name: Add manually supplied constraints
136         if: github.event_name == 'workflow_dispatch' && github.event.inputs.constraints != ''
137         run: |
138           echo "constraints: ${{ github.event.inputs.constraints }}" >> cabal.validate.project
140       - uses: haskell-actions/setup@v2
141         id: setup-haskell
142         with:
143           ghc-version: ${{ matrix.ghc }}
144           cabal-version: 3.12.1.0 # see https://github.com/haskell/cabal/pull/10251
145           ghcup-release-channel: https://raw.githubusercontent.com/haskell/ghcup-metadata/master/ghcup-prereleases-0.0.8.yaml
147       #  See the following link for a breakdown of the following step
148       #  https://github.com/haskell/actions/issues/7#issuecomment-745697160
149       - uses: actions/cache@v4
150         with:
151           # validate.sh uses a special build dir
152           path: |
153             ${{ steps.setup-haskell.outputs.cabal-store }}
154             dist-*
155           key: ${{ runner.os }}-${{ matrix.ghc }}-${{ github.sha }}
156           restore-keys: ${{ runner.os }}-${{ matrix.ghc }}-
158       # The tool is not essential to the rest of the test suite. If
159       # hackage-repo-tool is not present, any test that requires it will
160       # be skipped.
161       # We want to keep this in the loop but we don't want to fail if
162       # hackage-repo-tool breaks or fails to support a newer GHC version.
163       - name: Install hackage-repo-tool
164         continue-on-error: true
165         run: cabal install --ignore-project hackage-repo-tool
167       # Needed by cabal-testsuite/PackageTests/Configure/setup.test.hs
168       - name: "MAC: Install Autotools"
169         if: runner.os == 'macOS'
170         run: brew install automake
172       # Needed by cabal-testsuite/PackageTests/Configure/setup.test.hs
173       - name: "WIN: Install Autotools"
174         if: runner.os == 'Windows'
175         run: /usr/bin/pacman --noconfirm -S autotools
177       - name: Set validate inputs
178         run: |
179           FLAGS="${{ env.COMMON_FLAGS }}"
180           if [[ "${{ matrix.ghc }}" == "${{ env.GHC_FOR_SOLVER_BENCHMARKS }}" ]]; then
181             FLAGS="$FLAGS --solver-benchmarks"
182           fi
183           if [[ "${{ matrix.ghc }}" == "${{ env.GHC_FOR_COMPLETE_HACKAGE_TESTS }}" ]]; then
184             FLAGS="$FLAGS --complete-hackage-tests"
185           fi
186           echo "FLAGS=$FLAGS" >> "$GITHUB_ENV"
188       - name: Validate print-config
189         run: sh validate.sh $FLAGS -s print-config
191       - name: Validate print-tool-versions
192         run: sh validate.sh $FLAGS -s print-tool-versions
194       - name: Validate build
195         run: sh validate.sh $FLAGS -s build
197       - name: Canonicalize architecture
198         run: |
199           case ${{ runner.arch }} in
200             X86) arch=i386 ;;
201             X64) arch=x86_64 ;;
202             ARM64) arch=aarch64 ;;
203             *) echo "Unsupported architecture, please fix validate.yaml" 2>/dev/null; exit 1 ;;
204           esac
205           echo "CABAL_ARCH=$arch" >> "$GITHUB_ENV"
207       - name: Tar cabal head executable
208         if: matrix.ghc == env.GHC_FOR_RELEASE
209         run: |
210           CABAL_EXEC=$(cabal list-bin --builddir=dist-newstyle-validate-ghc-${{ matrix.ghc }} --project-file=cabal.validate.project cabal-install:exe:cabal)
211           # We have to tar the executable to preserve executable permissions
212           # see https://github.com/actions/upload-artifact/issues/38
213           if [[ "${{ runner.os }}" == "Windows" ]]; then
214             # `cabal list-bin` gives us a windows path but tar needs the posix one
215             CABAL_EXEC=$(cygpath "$CABAL_EXEC")
216           fi
217           if [[ "${{ runner.os }}" == "macOS" ]]; then
218              # Workaround to avoid bsdtar corrupts the executable
219              # so executing it after untar throws `cannot execute binary file`
220              # see https://github.com/actions/virtual-environments/issues/2619#issuecomment-788397841
221              sudo /usr/sbin/purge
222           fi
223           DIR=$(dirname "$CABAL_EXEC")
224           FILE=$(basename "$CABAL_EXEC")
225           CABAL_EXEC_TAR="cabal-head-${{ runner.os }}-$CABAL_ARCH.tar.gz"
226           tar -czvf "$CABAL_EXEC_TAR" -C "$DIR" "$FILE"
227           echo "CABAL_EXEC_TAR=$CABAL_EXEC_TAR" >> "$GITHUB_ENV"
229       # We upload the cabal executable built with the ghc used in the release for:
230       # - Reuse it in the dogfooding job (although we could use the cached build dir)
231       # - Make it available in the workflow to make easier testing it locally
232       - name: Upload cabal-install executable to workflow artifacts
233         if: matrix.ghc == env.GHC_FOR_RELEASE
234         uses: actions/upload-artifact@v4
235         with:
236           name: cabal-${{ runner.os }}-${{ env.CABAL_ARCH }}
237           path: ${{ env.CABAL_EXEC_TAR }}
239       - name: Validate tests
240         env:
241           # `rawSystemStdInOut reports text decoding errors`
242           # test does not find ghc without the full path in windows
243           GHCPATH: ${{ steps.setup-haskell.outputs.ghc-exe }}
244         run: |
245           set +e
246           rc=0
247           tests="lib-tests lib-suite cli-tests cli-suite"
248           if [ "${{ matrix.ghc }}" = "${{ env.GHC_FOR_SOLVER_BENCHMARKS }}" ]; then
249             tests="$tests solver-benchmarks-tests solver-benchmarks-run"
250           fi
251           for test in $tests; do
252             echo Validate "$test"
253             sh validate.sh $FLAGS -s "$test" || rc=1
254             echo End "$test"
255           done
256           exit $rc
258   validate-old-ghcs:
259     name: Validate old ghcs ${{ matrix.extra-ghc }}
260     runs-on: ubuntu-latest
261     needs: validate
263     strategy:
264       matrix:
265         extra-ghc:
266           ["8.4.4", "8.2.2", "8.0.2"]
267           ## GHC 7.10.3 does not install on ubuntu-22.04 with ghcup.
268           ## Older GHCs are not supported by ghcup in the first place.
269       fail-fast: false
271     steps:
272       - uses: actions/checkout@v4
274       - name: Install prerequisites for old GHCs
275         run: |
276           sudo apt-get update
277           sudo apt-get install libncurses5 libtinfo5
279       - name: Install extra compiler
280         run: ghcup install ghc ${{ matrix.extra-ghc }}
282       - name: GHCup logs
283         if: always()
284         run: cat /usr/local/.ghcup/logs/*
286       - name: Install primary compiler
287         uses: haskell-actions/setup@v2
288         id: setup-haskell
289         with:
290           ghc-version: ${{ env.GHC_FOR_RELEASE }}
291           cabal-version: latest
293       - name: GHC versions
294         run: |
295           ghc --version
296           "ghc-${{ matrix.extra-ghc }}" --version
298       # As we are reusing the cached build dir from the previous step
299       # the generated artifacts are available here,
300       # including the cabal executable and the test suite
301       - uses: actions/cache@v4
302         with:
303           path: |
304             ${{ steps.setup-haskell.outputs.cabal-store }}
305             dist-*
306           key: ${{ runner.os }}-${{ env.GHC_FOR_RELEASE }}-${{ github.sha }}
307           restore-keys: ${{ runner.os }}-${{ env.GHC_FOR_RELEASE }}-
309       - name: Validate build
310         id: build
311         run: sh validate.sh ${{ env.COMMON_FLAGS }} -s build
313       - name: "Validate lib-suite-extras --extra-hc ghc-${{ matrix.extra-ghc }}"
314         env:
315           EXTRA_GHC: ghc-${{ matrix.extra-ghc }}
316         continue-on-error: true
317         run: sh validate.sh ${{ env.COMMON_FLAGS }} --lib-only -s lib-suite-extras --extra-hc "${{ env.EXTRA_GHC }}"
319   build-alpine:
320     name: Build statically linked using alpine
321     runs-on: ubuntu-latest
322     container: "alpine:3.19"
323     steps:
324       - name: Install extra dependencies
325         shell: sh
326         run: |
327           apk add bash curl sudo jq pkgconfig \
328           zlib-dev zlib-static binutils-gold curl \
329           gcc g++ gmp-dev libc-dev libffi-dev make \
330           musl-dev ncurses-dev perl tar xz
332       - uses: actions/checkout@v4
334       # See https://github.com/haskell/cabal/blob/master/CONTRIBUTING.md#hackage-revisions
335       - name: Manually supplied constraints/allow-newer
336         if: github.event_name == 'workflow_dispatch'
337         run: |
338           echo "allow-newer: ${ALLOWNEWER}"  >> cabal.validate.project
339           echo "constraints: ${CONSTRAINTS}" >> cabal.validate.project
341       - uses: haskell-actions/setup@v2
342         id: setup-haskell
343         with:
344           ghc-version: ${{ env.GHC_FOR_RELEASE }}
345           cabal-version: latest # latest is mandatory for cabal-testsuite, see https://github.com/haskell/cabal/issues/8133
347       #  See the following link for a breakdown of the following step
348       #  https://github.com/haskell/actions/issues/7#issuecomment-745697160
349       - uses: actions/cache@v4
350         with:
351           # validate.sh uses a special build dir
352           path: |
353             ${{ steps.setup-haskell.outputs.cabal-store }}
354             dist-*
355           key: ${{ runner.os }}-${{ env.GHC_FOR_RELEASE }}-${{ github.sha }}
356           restore-keys: ${{ runner.os }}-${{ env.GHC_FOR_RELEASE }}-
358       - name: Enable statically linked executables
359         run: |
360           echo 'executable-static: true' >> cabal.validate.project
362       - name: Build
363         run: sh validate.sh $FLAGS -s build
365       - name: Tar cabal head executable
366         run: |
367           CABAL_EXEC=$(cabal list-bin --builddir=dist-newstyle-validate-ghc-${{ env.GHC_FOR_RELEASE }} --project-file=cabal.validate.project cabal-install:exe:cabal)
368           # We have to tar the executable to preserve executable permissions
369           # see https://github.com/actions/upload-artifact/issues/38
370           DIR=$(dirname "$CABAL_EXEC")
371           FILE=$(basename "$CABAL_EXEC")
372           CABAL_EXEC_TAR="cabal-head-${{ runner.os }}-static-x86_64.tar.gz"
373           tar -czvf "$CABAL_EXEC_TAR" -C "$DIR" "$FILE"
374           echo "CABAL_EXEC_TAR=$CABAL_EXEC_TAR" >> "$GITHUB_ENV"
376       - name: Upload cabal-install executable to workflow artifacts
377         uses: actions/upload-artifact@v4
378         with:
379           name: cabal-${{ runner.os }}-static-x86_64
380           path: ${{ env.CABAL_EXEC_TAR }}
382   # The previous jobs use a released version of cabal to build cabal HEAD itself
383   # This one uses the cabal HEAD generated executable in the previous step
384   # to build itself again, as sanity check
385   dogfooding:
386     name: Dogfooding ${{ matrix.sys.os }} ghc-${{ matrix.ghc }}
387     runs-on: ${{ matrix.sys.os }}
388     needs: validate
389     strategy:
390       matrix:
391         sys:
392           - { os: windows-latest, shell: "C:/msys64/usr/bin/bash.exe -e {0}" }
393           - { os: ubuntu-latest, shell: bash }
394           - { os: macos-latest, shell: bash }
395         # We only use one ghc version the used one for the next release (defined at top of the workflow)
396         # We need to build an array dynamically to inject the appropiate env var in a previous job,
397         # see https://docs.github.com/en/actions/learn-github-actions/expressions#fromjson
398         ghc: ${{ fromJSON (needs.validate.outputs.GHC_FOR_RELEASE) }}
400     defaults:
401       run:
402         shell: ${{ matrix.sys.shell }}
404     steps:
405       # TODO: make a reusable action for this
406       - name: Canonicalize architecture
407         run: |
408           case ${{ runner.arch }} in
409             X86) arch=i386 ;;
410             X64) arch=x86_64 ;;
411             ARM64) arch=aarch64 ;;
412             *) echo "Unsupported architecture" 2>/dev/null; exit 1 ;;
413           esac
414           echo "CABAL_ARCH=$arch" >> "$GITHUB_ENV"
416       - name: Work around XDG directories existence (haskell-actions/setup#62)
417         if: runner.os == 'macOS'
418         run: |
419           rm -rf ~/.config/cabal
420           rm -rf ~/.cache/cabal
422       - uses: actions/checkout@v4
424       - uses: haskell-actions/setup@v2
425         id: setup-haskell
426         with:
427           ghc-version: ${{ matrix.ghc }}
428           cabal-version: latest # default, we are not using it in this job
430       - name: Download cabal executable from workflow artifacts
431         uses: actions/download-artifact@v4
432         with:
433           name: cabal-${{ runner.os }}-${{ env.CABAL_ARCH }}
434           path: cabal-head
436       - name: Untar the cabal executable
437         run: tar -xzf "./cabal-head/cabal-head-${{ runner.os }}-$CABAL_ARCH.tar.gz" -C cabal-head
439       - name: print-config using cabal HEAD
440         run: sh validate.sh ${{ env.COMMON_FLAGS }} --with-cabal ./cabal-head/cabal -s print-config
442       # We dont use cache to force a build with a fresh store dir and build dir
443       # This way we check cabal can build all its dependencies
444       - name: Build using cabal HEAD
445         run: sh validate.sh ${{ env.COMMON_FLAGS }} --with-cabal ./cabal-head/cabal -s build
447   prerelease-head:
448     name: Create a GitHub prerelease with the binary artifacts
449     runs-on: ubuntu-latest
450     if: github.ref == 'refs/heads/master'
451     permissions:
452       contents: write
454     # IMPORTANT! Any job added to the workflow should be added here too
455     needs: [validate, validate-old-ghcs, build-alpine, dogfooding]
457     steps:
458       # for now this is hardcoded. is there a better way?
459       - uses: actions/download-artifact@v4
460         with:
461           pattern: cabal-*
462           path: binaries
464       - name: Create GitHub prerelease
465         uses: softprops/action-gh-release@v2
466         with:
467           tag_name: cabal-head
468           prerelease: true
469           files: binaries/cabal-*
471   prerelease-lts:
472     name: Create a GitHub LTS prerelease with the binary artifacts
473     runs-on: ubuntu-latest
474     # The LTS branch is hardcoded for now, update it on a new LTS!
475     if: github.ref == 'refs/heads/3.12'
477     # IMPORTANT! Any job added to the workflow should be added here too
478     needs: [validate, validate-old-ghcs, build-alpine, dogfooding]
480     steps:
481     - uses: actions/download-artifact@v4
482       with:
483         pattern: cabal-*
484         path: binaries
486     - run: |
487         # bash-ism, but we forced bash above
488         mv cabal-{,lts-}head-Windows-x86_64.tar.gz
489         mv cabal-{,lts-}head-Linux-x86_64.tar.gz
490         mv cabal-{,lts-}head-Linux-static-x86_64.tar.gz
491         mv cabal-{,lts-}head-macOS-aarch64.tar.gz
493     - name: Create GitHub prerelease
494       uses: softprops/action-gh-release@v2
495       with:
496         tag_name: cabal-lts-head
497         prerelease: true
498         files: binaries/cabal-*
500   # We use this job as a summary of the workflow
501   # It will fail if any of the previous jobs does
502   # This way we can use it exclusively in branch protection rules
503   # and abstract away the concrete jobs of the workflow, including their names
504   validate-post-job:
505     if: always()
506     name: Validate post job
507     runs-on: ubuntu-latest
508     # IMPORTANT! Any job added to the workflow should be added here too
509     needs: [validate, validate-old-ghcs, build-alpine, dogfooding]
511     steps:
512       - run: |
513           echo "jobs info: ${{ toJSON(needs) }}"
514       - if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled')
515         run: exit 1