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