Merge pull request #10364 from haskell/mergify/bp/3.14/pr-10341
[cabal.git] / .github / workflows / validate.yml
blob432277a9d5c5aee6454e949681f8bf4004dffb4d
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     branches:
18       - master
19   pull_request:
20     paths-ignore:
21       - "doc/**"
22       - "**/README.md"
23       - "CONTRIBUTING.md"
24   release:
25     types:
26       - created
27   workflow_call:
29   # See https://github.com/haskell/cabal/blob/master/CONTRIBUTING.md#hackage-revisions
30   workflow_dispatch:
31     inputs:
32       allow-newer:
33         description: allow-newer line
34         required: false
35         type: string
36       constraints:
37         description: constraints line
38         required: false
39         type: string
41 env:
42   # We choose a stable ghc version across all os's
43   # which will be used to do the next release
44   GHC_FOR_RELEASE: "9.4.8"
45   # Ideally we should use the version about to be released for hackage tests and benchmarks
46   GHC_FOR_SOLVER_BENCHMARKS: "9.4.8"
47   GHC_FOR_COMPLETE_HACKAGE_TESTS: "9.4.8"
48   COMMON_FLAGS: "-j 2 -v"
50   # See https://github.com/haskell/cabal/blob/master/CONTRIBUTING.md#hackage-revisions
51   ALLOWNEWER: ${{ github.event.inputs.allow-newer }}
52   CONSTRAINTS: ${{ github.event.inputs.constraints }}
54 jobs:
55   validate:
56     name: Validate ${{ matrix.sys.os }} ghc-${{ matrix.ghc }}
57     runs-on: ${{ matrix.sys.os }}
58     outputs:
59       GHC_FOR_RELEASE: ${{ format('["{0}"]', env.GHC_FOR_RELEASE) }}
60     strategy:
61       fail-fast: false
62       matrix:
63         sys:
64           - { os: windows-latest, shell: "C:/msys64/usr/bin/bash.exe -e {0}" }
65           - { os: ubuntu-latest, shell: bash }
66           - { os: macos-13, shell: bash }
67         # If you remove something from here, then add it to the old-ghcs job.
68         # Also a removed GHC from here means that we are actually dropping
69         # support, so the PR *must* have a changelog entry.
70         ghc:
71           [
72             "9.10.1",
73             "9.8.2",
74             "9.6.4",
75             "9.4.8",
76             "9.2.8",
77             "9.0.2",
78             "8.10.7",
79             "8.8.4",
80           ]
81         exclude:
82           # Throws fatal "cabal-tests.exe: fd:8: hGetLine: end of file" exception
83           # even with --io-manager=native
84           - sys:
85               { os: windows-latest, shell: "C:/msys64/usr/bin/bash.exe -e {0}" }
86             ghc: "9.0.2"
87           # corrupts GHA cache or the fabric of reality itself, see https://github.com/haskell/cabal/issues/8356
88           - sys:
89               { os: windows-latest, shell: "C:/msys64/usr/bin/bash.exe -e {0}" }
90             ghc: "8.10.7"
91           # lot of segfaults caused by ghc bugs
92           - sys:
93               { os: windows-latest, shell: "C:/msys64/usr/bin/bash.exe -e {0}" }
94             ghc: "8.8.4"
95     defaults:
96       run:
97         shell: ${{ matrix.sys.shell }}
98     steps:
99       - name: Work around XDG directories existence (haskell-actions/setup#62)
100         if: runner.os == 'macOS'
101         run: |
102           rm -rf ~/.config/cabal
103           rm -rf ~/.cache/cabal
105       - uses: actions/checkout@v4
107       # See https://github.com/haskell/cabal/blob/master/CONTRIBUTING.md#hackage-revisions
108       - name: Add manually supplied allow-newer
109         if: github.event_name == 'workflow_dispatch' && github.event.inputs.allow-newer != ''
110         run: |
111           echo "allow-newer: ${{ github.event.inputs.allow-newer }}" >> cabal.validate.project
113       - name: Add manually supplied constraints
114         if: github.event_name == 'workflow_dispatch' && github.event.inputs.constraints != ''
115         run: |
116           echo "constraints: ${{ github.event.inputs.constraints }}" >> cabal.validate.project
118       - uses: haskell-actions/setup@v2
119         id: setup-haskell
120         with:
121           ghc-version: ${{ matrix.ghc }}
122           cabal-version: 3.12.1.0 # see https://github.com/haskell/cabal/pull/10251
123           ghcup-release-channel: https://raw.githubusercontent.com/haskell/ghcup-metadata/master/ghcup-prereleases-0.0.8.yaml
125       #  See the following link for a breakdown of the following step
126       #  https://github.com/haskell/actions/issues/7#issuecomment-745697160
127       - uses: actions/cache@v4
128         with:
129           # validate.sh uses a special build dir
130           path: |
131             ${{ steps.setup-haskell.outputs.cabal-store }}
132             dist-*
133           key: ${{ runner.os }}-${{ matrix.ghc }}-${{ github.sha }}
134           restore-keys: ${{ runner.os }}-${{ matrix.ghc }}-
136       - name: Work around git problem https://bugs.launchpad.net/ubuntu/+source/git/+bug/1993586 (cabal PR #8546)
137         run: git config --global protocol.file.allow always
139       # The tool is not essential to the rest of the test suite. If
140       # hackage-repo-tool is not present, any test that requires it will
141       # be skipped.
142       # We want to keep this in the loop but we don't want to fail if
143       # hackage-repo-tool breaks or fails to support a newer GHC version.
144       - name: Install hackage-repo-tool
145         continue-on-error: true
146         run: cabal install --ignore-project hackage-repo-tool
148       # Needed by cabal-testsuite/PackageTests/Configure/setup.test.hs
149       - name: Install Autotools
150         if: runner.os == 'macOS'
151         run: brew install automake
153       - name: Set validate inputs
154         run: |
155           FLAGS="${{ env.COMMON_FLAGS }}"
156           if [[ "${{ matrix.ghc }}" == "${{ env.GHC_FOR_SOLVER_BENCHMARKS }}" ]]; then
157             FLAGS="$FLAGS --solver-benchmarks"
158           fi
159           if [[ "${{ matrix.ghc }}" == "${{ env.GHC_FOR_COMPLETE_HACKAGE_TESTS }}" ]]; then
160             FLAGS="$FLAGS --complete-hackage-tests"
161           fi
162           echo "FLAGS=$FLAGS" >> "$GITHUB_ENV"
164       - name: Validate print-config
165         run: sh validate.sh $FLAGS -s print-config
167       - name: Validate print-tool-versions
168         run: sh validate.sh $FLAGS -s print-tool-versions
170       - name: Validate build
171         run: sh validate.sh $FLAGS -s build
173       - name: Tar cabal head executable
174         if: matrix.ghc == env.GHC_FOR_RELEASE
175         run: |
176           CABAL_EXEC=$(cabal list-bin --builddir=dist-newstyle-validate-ghc-${{ matrix.ghc }} --project-file=cabal.validate.project cabal-install:exe:cabal)
177           # We have to tar the executable to preserve executable permissions
178           # see https://github.com/actions/upload-artifact/issues/38
179           if [[ "${{ runner.os }}" == "Windows" ]]; then
180             # `cabal list-bin` gives us a windows path but tar needs the posix one
181             CABAL_EXEC=$(cygpath "$CABAL_EXEC")
182           fi
183           if [[ "${{ runner.os }}" == "macOS" ]]; then
184              # Workaround to avoid bsdtar corrupts the executable
185              # so executing it after untar throws `cannot execute binary file`
186              # see https://github.com/actions/virtual-environments/issues/2619#issuecomment-788397841
187              sudo /usr/sbin/purge
188           fi
189           DIR=$(dirname "$CABAL_EXEC")
190           FILE=$(basename "$CABAL_EXEC")
191           CABAL_EXEC_TAR="cabal-head-${{ runner.os }}-x86_64.tar.gz"
192           tar -czvf "$CABAL_EXEC_TAR" -C "$DIR" "$FILE"
193           echo "CABAL_EXEC_TAR=$CABAL_EXEC_TAR" >> "$GITHUB_ENV"
195       # We upload the cabal executable built with the ghc used in the release for:
196       # - Reuse it in the dogfooding job (although we could use the cached build dir)
197       # - Make it available in the workflow to make easier testing it locally
198       - name: Upload cabal-install executable to workflow artifacts
199         if: matrix.ghc == env.GHC_FOR_RELEASE
200         uses: actions/upload-artifact@v4
201         with:
202           name: cabal-${{ runner.os }}-x86_64
203           path: ${{ env.CABAL_EXEC_TAR }}
205       - name: Validate lib-tests
206         env:
207           # `rawSystemStdInOut reports text decoding errors`
208           # test does not find ghc without the full path in windows
209           GHCPATH: ${{ steps.setup-haskell.outputs.ghc-exe }}
210         run: sh validate.sh $FLAGS -s lib-tests
212       - name: Validate lib-suite
213         run: sh validate.sh $FLAGS -s lib-suite
215       - name: Validate cli-tests
216         run: sh validate.sh $FLAGS -s cli-tests
218       - name: Validate cli-suite
219         run: sh validate.sh $FLAGS -s cli-suite
221       - name: Validate solver-benchmarks-tests
222         if: matrix.ghc == env.GHC_FOR_SOLVER_BENCHMARKS
223         run: sh validate.sh $FLAGS -s solver-benchmarks-tests
225       - name: Validate solver-benchmarks-run
226         if: matrix.ghc == env.GHC_FOR_SOLVER_BENCHMARKS
227         run: sh validate.sh $FLAGS -s solver-benchmarks-run
229   validate-old-ghcs:
230     name: Validate old ghcs ${{ matrix.extra-ghc }}
231     runs-on: ubuntu-latest
232     needs: validate
234     strategy:
235       matrix:
236         extra-ghc:
237           ["8.4.4", "8.2.2", "8.0.2"]
238           ## GHC 7.10.3 does not install on ubuntu-22.04 with ghcup.
239           ## Older GHCs are not supported by ghcup in the first place.
240       fail-fast: false
242     steps:
243       - uses: actions/checkout@v4
245       - name: Install prerequisites for old GHCs
246         run: |
247           sudo apt-get update
248           sudo apt-get install libncurses5 libtinfo5
250       - name: Install extra compiler
251         run: ghcup install ghc ${{ matrix.extra-ghc }}
253       - name: GHCup logs
254         if: always()
255         run: cat /usr/local/.ghcup/logs/*
257       - name: Install primary compiler
258         uses: haskell-actions/setup@v2
259         id: setup-haskell
260         with:
261           ghc-version: ${{ env.GHC_FOR_RELEASE }}
262           cabal-version: latest
264       - name: GHC versions
265         run: |
266           ghc --version
267           "ghc-${{ matrix.extra-ghc }}" --version
269       # As we are reusing the cached build dir from the previous step
270       # the generated artifacts are available here,
271       # including the cabal executable and the test suite
272       - uses: actions/cache@v4
273         with:
274           path: |
275             ${{ steps.setup-haskell.outputs.cabal-store }}
276             dist-*
277           key: ${{ runner.os }}-${{ env.GHC_FOR_RELEASE }}-${{ github.sha }}
278           restore-keys: ${{ runner.os }}-${{ env.GHC_FOR_RELEASE }}-
280       - name: Validate build
281         run: sh validate.sh ${{ env.COMMON_FLAGS }} -s build
283       - name: "Validate lib-suite-extras --extra-hc ghc-${{ matrix.extra-ghc }}"
284         env:
285           EXTRA_GHC: ghc-${{ matrix.extra-ghc }}
286         run: sh validate.sh ${{ env.COMMON_FLAGS }} --lib-only -s lib-suite-extras --extra-hc "${{ env.EXTRA_GHC }}"
288   build-alpine:
289     name: Build statically linked using alpine
290     runs-on: ubuntu-latest
291     container: "alpine:3.19"
292     steps:
293       - name: Install extra dependencies
294         shell: sh
295         run: |
296           apk add bash curl sudo jq pkgconfig \
297           zlib-dev zlib-static binutils-gold curl \
298           gcc g++ gmp-dev libc-dev libffi-dev make \
299           musl-dev ncurses-dev perl tar xz
301       - uses: actions/checkout@v4
303       # See https://github.com/haskell/cabal/blob/master/CONTRIBUTING.md#hackage-revisions
304       - name: Manually supplied constraints/allow-newer
305         if: github.event_name == 'workflow_dispatch'
306         run: |
307           echo "allow-newer: ${ALLOWNEWER}"  >> cabal.validate.project
308           echo "constraints: ${CONSTRAINTS}" >> cabal.validate.project
310       - uses: haskell-actions/setup@v2
311         id: setup-haskell
312         with:
313           ghc-version: ${{ env.GHC_FOR_RELEASE }}
314           cabal-version: latest # latest is mandatory for cabal-testsuite, see https://github.com/haskell/cabal/issues/8133
316       #  See the following link for a breakdown of the following step
317       #  https://github.com/haskell/actions/issues/7#issuecomment-745697160
318       - uses: actions/cache@v4
319         with:
320           # validate.sh uses a special build dir
321           path: |
322             ${{ steps.setup-haskell.outputs.cabal-store }}
323             dist-*
324           key: ${{ runner.os }}-${{ env.GHC_FOR_RELEASE }}-${{ github.sha }}
325           restore-keys: ${{ runner.os }}-${{ env.GHC_FOR_RELEASE }}-
327       - name: Enable statically linked executables
328         run: |
329           echo 'executable-static: true' >> cabal.validate.project
331       - name: Build
332         run: sh validate.sh $FLAGS -s build
334       - name: Tar cabal head executable
335         run: |
336           CABAL_EXEC=$(cabal list-bin --builddir=dist-newstyle-validate-ghc-${{ env.GHC_FOR_RELEASE }} --project-file=cabal.validate.project cabal-install:exe:cabal)
337           # We have to tar the executable to preserve executable permissions
338           # see https://github.com/actions/upload-artifact/issues/38
339           DIR=$(dirname "$CABAL_EXEC")
340           FILE=$(basename "$CABAL_EXEC")
341           CABAL_EXEC_TAR="cabal-head-${{ runner.os }}-static-x86_64.tar.gz"
342           tar -czvf "$CABAL_EXEC_TAR" -C "$DIR" "$FILE"
343           echo "CABAL_EXEC_TAR=$CABAL_EXEC_TAR" >> "$GITHUB_ENV"
345       - name: Upload cabal-install executable to workflow artifacts
346         uses: actions/upload-artifact@v4
347         with:
348           name: cabal-${{ runner.os }}-static-x86_64
349           path: ${{ env.CABAL_EXEC_TAR }}
351   # The previous jobs use a released version of cabal to build cabal HEAD itself
352   # This one uses the cabal HEAD generated executable in the previous step
353   # to build itself again, as sanity check
354   dogfooding:
355     name: Dogfooding ${{ matrix.os }} ghc-${{ matrix.ghc }}
356     runs-on: ${{ matrix.os }}
357     needs: validate
358     strategy:
359       matrix:
360         os: [ubuntu-latest, macos-13, windows-latest]
361         # We only use one ghc version the used one for the next release (defined at top of the workflow)
362         # We need to build an array dynamically to inject the appropiate env var in a previous job,
363         # see https://docs.github.com/en/actions/learn-github-actions/expressions#fromjson
364         ghc: ${{ fromJSON (needs.validate.outputs.GHC_FOR_RELEASE) }}
366     steps:
367       - name: Work around XDG directories existence (haskell-actions/setup#62)
368         if: runner.os == 'macOS'
369         run: |
370           rm -rf ~/.config/cabal
371           rm -rf ~/.cache/cabal
373       - uses: actions/checkout@v4
375       - uses: haskell-actions/setup@v2
376         id: setup-haskell
377         with:
378           ghc-version: ${{ matrix.ghc }}
379           cabal-version: latest # default, we are not using it in this job
381       - name: Download cabal executable from workflow artifacts
382         uses: actions/download-artifact@v4
383         with:
384           name: cabal-${{ runner.os }}-x86_64
385           path: cabal-head
387       - name: Untar the cabal executable
388         run: tar -xzf "./cabal-head/cabal-head-${{ runner.os }}-x86_64.tar.gz" -C cabal-head
390       - name: print-config using cabal HEAD
391         run: sh validate.sh ${{ env.COMMON_FLAGS }} --with-cabal ./cabal-head/cabal -s print-config
393       # We dont use cache to force a build with a fresh store dir and build dir
394       # This way we check cabal can build all its dependencies
395       - name: Build using cabal HEAD
396         run: sh validate.sh ${{ env.COMMON_FLAGS }} --with-cabal ./cabal-head/cabal -s build
398   prerelease-head:
399     name: Create a GitHub prerelease with the binary artifacts
400     runs-on: ubuntu-latest
401     if: github.ref == 'refs/heads/master'
403     # IMPORTANT! Any job added to the workflow should be added here too
404     needs: [validate, validate-old-ghcs, build-alpine, dogfooding]
406     steps:
407       - uses: actions/download-artifact@v4
408         with:
409           name: cabal-Windows-x86_64
411       - uses: actions/download-artifact@v4
412         with:
413           name: cabal-Linux-x86_64
415       - uses: actions/download-artifact@v4
416         with:
417           name: cabal-Linux-static-x86_64
419       - uses: actions/download-artifact@v4
420         with:
421           name: cabal-macOS-x86_64
423       - name: Create GitHub prerelease
424         uses: marvinpinto/action-automatic-releases@v1.2.1
425         with:
426           repo_token: ${{ secrets.GITHUB_TOKEN }}
427           automatic_release_tag: cabal-head
428           prerelease: true
429           title: cabal-head
430           files: |
431             cabal-head-Windows-x86_64.tar.gz
432             cabal-head-Linux-x86_64.tar.gz
433             cabal-head-Linux-static-x86_64.tar.gz
434             cabal-head-macOS-x86_64.tar.gz
436   # We use this job as a summary of the workflow
437   # It will fail if any of the previous jobs does
438   # This way we can use it exclusively in branch protection rules
439   # and abstract away the concrete jobs of the workflow, including their names
440   validate-post-job:
441     if: always()
442     name: Validate post job
443     runs-on: ubuntu-latest
444     # IMPORTANT! Any job added to the workflow should be added here too
445     needs: [validate, validate-old-ghcs, build-alpine, dogfooding]
447     steps:
448       - run: |
449           echo "jobs info: ${{ toJSON(needs) }}"
450       - if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled')
451         run: exit 1