From e7c86c8e348e47b3000a2584602e02f308cd5758 Mon Sep 17 00:00:00 2001 From: brandon s allbery kf8nh Date: Fri, 1 Nov 2024 19:27:14 -0400 Subject: [PATCH] attempt to use local actions Extremely experimental, and with all the limitations and restrictions I keep finding in GitHub Actions it'll probably fail in the messiest way it can. At present this is incomplete but sufficient to see if this has any chance of working to begin with. If it somehow does, I'll look into abstracting out the other sub-jobs, then making an overnight validate for Tier 2 platforms and probably a prerelease job (which would fix the recently revealed problem where if there is no need to rebase on merge, no prerelease is made). --- .github/actions/cabal-setup/action.yml | 150 ++++++ .github/actions/dogfooding/action.yml | 50 ++ .github/actions/validate-build/action.yml | 89 ++++ .github/actions/validate-old/action.yml | 43 ++ .github/config.yml | 16 + .github/workflows/validate.yml | 572 ++++++++++------------ 6 files changed, 602 insertions(+), 318 deletions(-) create mode 100644 .github/actions/cabal-setup/action.yml create mode 100644 .github/actions/dogfooding/action.yml create mode 100644 .github/actions/validate-build/action.yml create mode 100644 .github/actions/validate-old/action.yml create mode 100644 .github/config.yml diff --git a/.github/actions/cabal-setup/action.yml b/.github/actions/cabal-setup/action.yml new file mode 100644 index 00000000000..bda086a9289 --- /dev/null +++ b/.github/actions/cabal-setup/action.yml @@ -0,0 +1,150 @@ +name: Cabal setup +description: Set up a workflow for Cabal + +inputs: + ghc: + description: ghc version to use + required: true + extra-ghc: + description: additional ghc for tests + required: false + default: '' + allow-newer: + description: allow-newer line + required: false + default: '' + constraints: + description: constraints line + required: false + default: '' + static: + description: whether to build statically + required: false + default: 'false' + shell: + description: shell to use + required: false + default: 'bash' + with_cache: + description: whether to instantiate cache + required: false + default: 'true' + +outputs: + ghc-exe: + description: Path to ghc installed by setup-haskell + value: ${{ steps.setup-haskell.outputs.ghc-exe }} + +runs: + using: composite + steps: + - name: Make sure ghc is specified + if: inputs.ghc == '' + shell: ${{ inputs.shell }} + run: exit 1 + + - name: Work around existence of XDG directories (haskell-actions/setup#62) + if: runner.os == 'macOS' + shell: ${{ inputs.shell }} + run: | + rm -rf ~/.config/cabal + rm -rf ~/.cache/cabal + + - name: "WIN: Setup TMP environment variable" + if: runner.os == 'Windows' + shell: ${{ inputs.shell }} + run: | + echo "TMP=${{ runner.temp }}" >> "$GITHUB_ENV" + + # See https://github.com/haskell/cabal/blob/master/CONTRIBUTING.md#hackage-revisions + - name: Add manually supplied allow-newer + if: inputs.allow-newer != '' + shell: ${{ inputs.shell }} + run: | + echo "allow-newer: ${{ inputs.allow-newer }}" >> cabal.validate.project + + - name: Add manually supplied constraints + if: inputs.constraints != '' + shell: ${{ inputs.shell }} + run: | + echo "constraints: ${{ inputs.constraints }}" >> cabal.validate.project + + - name: Enable statically linked executables + if: inputs.static == 'true' + shell: ${{ inputs.shell }} + run: | + echo 'executable-static: true' >> cabal.validate.project + + # must happen before the main setup so the correct ghc is default + - name: Install extra ghc for tests + if: inputs.extra-ghc != '' + uses: haskell-actions/setup@v2 + with: + ghc-version: ${{ inputs.extra-ghc }} + cabal-version: '3.12.1.0' # see https://github.com/haskell/cabal/pull/10251 + + - uses: haskell-actions/setup@v2 + id: setup-haskell + with: + ghc-version: ${{ inputs.ghc }} + cabal-version: '3.12.1.0' + #ghcup-release-channel: https://raw.githubusercontent.com/haskell/ghcup-metadata/master/ghcup-prereleases-0.0.8.yaml + + # See the following link for a breakdown of the following step + # https://github.com/haskell/actions/issues/7#issuecomment-745697160 + - uses: actions/cache@v4 + if: inputs.with_cache != 'false' + with: + # validate.sh uses a special build dir + path: | + ${{ steps.setup-haskell.outputs.cabal-store }} + dist-* + key: ${{ runner.os }}-${{ inputs.ghc }}-${{ github.sha }} + restore-keys: ${{ runner.os }}-${{ inputs.ghc }}- + + # Needed by cabal-testsuite/PackageTests/Configure/setup.test.hs + - name: "MAC: Install Autotools" + if: runner.os == 'macOS' + shell: ${{ inputs.shell }} + run: | + brew install automake + + # Needed by cabal-testsuite/PackageTests/Configure/setup.test.hs + - name: "WIN: Install Autotools" + if: runner.os == 'Windows' + shell: ${{ inputs.shell }} + run: | + /usr/bin/pacman --noconfirm -S autotools + + - name: Set validate inputs + shell: ${{ inputs.shell }} + run: | + FLAGS="$COMMON_FLAGS" + if [[ "${{ inputs.ghc }}" == "$GHC_FOR_SOLVER_BENCHMARKS" ]]; then + FLAGS="$FLAGS --solver-benchmarks" + fi + if [[ "${{ inputs.ghc }}" == "$GHC_FOR_COMPLETE_HACKAGE_TESTS" ]]; then + FLAGS="$FLAGS --complete-hackage-tests" + fi + echo "FLAGS=$FLAGS" >> "$GITHUB_ENV" + + - name: Validate print-config + shell: ${{ inputs.shell }} + run: | + sh validate.sh $FLAGS -s print-config + + - name: Validate print-tool-versions + shell: ${{ inputs.shell }} + run: | + sh validate.sh $FLAGS -s print-tool-versions + + - name: Canonicalize architecture + shell: ${{ inputs.shell }} + run: | + case ${{ runner.arch }} in + X86) arch=i386 ;; + X64) arch=x86_64 ;; + ARM64) arch=aarch64 ;; + *) echo "Unsupported architecture, please fix validate.yaml" 2>/dev/null; exit 1 ;; + esac + echo "CABAL_ARCH=$arch" >> "$GITHUB_ENV" diff --git a/.github/actions/dogfooding/action.yml b/.github/actions/dogfooding/action.yml new file mode 100644 index 00000000000..c0cb7f437e4 --- /dev/null +++ b/.github/actions/dogfooding/action.yml @@ -0,0 +1,50 @@ +name: Dogfooding cabal-install on a ghc/platform +description: Run a cabal-install uncached validate from a previously built binary +inputs: + ghc: + description: ghc version to use + required: true + shell: + description: shell to use + required: false + default: 'bash' + allow-newer: + description: allow-newer line + required: false + constraints: + description: constraints line + required: false + +runs: + using: composite + steps: + - uses: ./.github/actions/cabal-setup + with: + ghc: ${{ inputs.ghc }} + shell: ${{ inputs.shell }} + allow-newer: ${{ inputs.allow_newer }} + constraints: ${{ inputs.constraints }} + # We don't use cache to force a build with a fresh store dir and build dir + # This way we check cabal can build all its dependencies + with_cache: 'false' + + - name: Download cabal executable from workflow artifacts + uses: actions/download-artifact@v4 + with: + name: cabal-${{ runner.os }}-${{ env.CABAL_ARCH }} + path: cabal-head + + - name: Untar the cabal executable + shell: ${{ inputs.shell }} + run: | + tar -xzf "./cabal-head/cabal-head-${{ runner.os }}-$CABAL_ARCH.tar.gz" -C cabal-head + + - name: print-config using cabal HEAD + shell: ${{ inputs.shell }} + run: | + sh validate.sh $COMMON_FLAGS --with-cabal ./cabal-head/cabal -s print-config + + - name: Build using cabal HEAD + shell: ${{ inputs.shell }} + run: | + sh validate.sh $COMMON_FLAGS --with-cabal ./cabal-head/cabal -s build diff --git a/.github/actions/validate-build/action.yml b/.github/actions/validate-build/action.yml new file mode 100644 index 00000000000..f3d15000170 --- /dev/null +++ b/.github/actions/validate-build/action.yml @@ -0,0 +1,89 @@ +name: Validate build +description: Build for a full validate on a ghc version +inputs: + ghc: + description: ghc version to use + required: true + allow-newer: + description: allow-newer line + required: false + constraints: + description: constraints line + required: false + static: + description: whether to build statically + required: false + default: 'false' + shell: + description: shell to use + required: false + default: 'bash' + with_cache: + description: whether to instantiate cache + required: false + default: 'true' + +runs: + using: composite + steps: + - uses: ./.github/actions/cabal-setup + id: cabal-setup + with: + shell: ${{ inputs.shell }} + ghc: ${{ inputs.ghc }} + allow-newer: ${{ inputs.allow-newer }} + constraints: ${{ inputs.constraints }} + static: ${{ inputs.static }} + with_cache: ${{ inputs.with_cache }} + + # The tool is not essential to the rest of the test suite. If + # hackage-repo-tool is not present, any test that requires it will + # be skipped. + # We want to keep this in the loop but we don't want to fail if + # hackage-repo-tool breaks or fails to support a newer GHC version. + - name: Install hackage-repo-tool + continue-on-error: true + shell: ${{ inputs.shell }} + run: | + cabal install --ignore-project hackage-repo-tool + + - name: Validate build + shell: ${{ inputs.shell }} + run: | + echo ::group::Build + sh validate.sh $FLAGS -s build + + - name: Tar cabal head executable + if: inputs.ghc == env.GHC_FOR_RELEASE + shell: ${{ inputs.shell }} + run: | + echo ::group::Tar + CABAL_EXEC=$(cabal list-bin --builddir=dist-newstyle-validate-ghc-${{ inputs.ghc }} --project-file=cabal.validate.project cabal-install:exe:cabal) + # We have to tar the executable to preserve executable permissions + # see https://github.com/actions/upload-artifact/issues/38 + if [[ "${{ runner.os }}" == "Windows" ]]; then + # `cabal list-bin` gives us a windows path but tar needs the posix one + CABAL_EXEC=$(cygpath "$CABAL_EXEC") + fi + if [[ "${{ runner.os }}" == "macOS" ]]; then + # Workaround to avoid bsdtar corrupting the executable + # such that executing it after untar throws `cannot execute binary file` + # see https://github.com/actions/virtual-environments/issues/2619#issuecomment-788397841 + sudo /usr/sbin/purge + fi + DIR=$(dirname "$CABAL_EXEC") + FILE=$(basename "$CABAL_EXEC") + CABAL_EXEC_TAR="cabal-head-${{ runner.os }}${{ inputs.static == 'true' && '-static' || '' }}-$CABAL_ARCH.tar.gz" + tar -czvf "$CABAL_EXEC_TAR" -C "$DIR" "$FILE" + echo "CABAL_EXEC_TAR=$CABAL_EXEC_TAR" >> "$GITHUB_ENV" + + # We upload the cabal executable built with the ghc used in the release for: + # - Reuse it in the dogfooding job (although we could use the cached build dir) + # - Make it available in the workflow to make easier testing it locally + # (Using the cache in dogfooding would defeat its purpose, though.) + - name: Upload cabal-install executable to workflow artifacts + if: inputs.ghc == env.GHC_FOR_RELEASE + uses: actions/upload-artifact@v4 + with: + name: cabal-${{ runner.os }}${{ inputs.static == 'true' && '-static' || '' }}-${{ env.CABAL_ARCH }} + path: ${{ env.CABAL_EXEC_TAR }} diff --git a/.github/actions/validate-old/action.yml b/.github/actions/validate-old/action.yml new file mode 100644 index 00000000000..85b2293815f --- /dev/null +++ b/.github/actions/validate-old/action.yml @@ -0,0 +1,43 @@ +name: Validate old ghcs +description: Run a Cabal-only validate on an older ghc version +inputs: + ghc: + description: ghc version to use + required: true + extra-ghc: + description: old ghc version to test + required: true + shell: + description: shell to use + required: false + default: 'bash' + +runs: + using: composite + steps: + - name: Install prerequisites for old GHCs + shell: ${{ inputs.shell }} + run: | + sudo apt-get update + sudo apt-get install libncurses5 libtinfo5 + + - uses: ./.github/actions/cabal-setup + with: + ghc: ${{ inputs.ghc }} + extra-ghc: ${{ inputs.extra-ghc }} + + - name: GHC versions + shell: ${{ inputs.shell }} + run: | + ghc --version + "ghc-${{ inputs.extra-ghc }}" --version + + - name: Validate build + shell: ${{ inputs.shell }} + run: | + sh validate.sh $COMMON_FLAGS -s build + + - name: "Validate lib-suite-extras --extra-hc ghc-${{ inputs.extra-ghc }}" + shell: ${{ inputs.shell }} + run: | + sh validate.sh $COMMON_FLAGS --lib-only -s lib-suite-extras --extra-hc "ghc-${{ inputs.extra-ghc }}" diff --git a/.github/config.yml b/.github/config.yml new file mode 100644 index 00000000000..0fb11b042ad --- /dev/null +++ b/.github/config.yml @@ -0,0 +1,16 @@ +# note: GHC_FOR_RELEASE must be an element of both GHC_FOR_VALIDATE and GHC_FOR_BOOTSTRAP! +GHC_FOR_RELEASE: "9.4.8" +GHC_FOR_SOLVER_BENCHMARKS: $(GHC_FOR_RELEASE) +GHC_FOR_COMPLETE_HACKAGE_TESTS: $(GHC_FOR_RELEASE) +# these will be decoded with fromJSON, and must be quoted to keep YAML from making objects +# If you remove something from here, then add it to GHC_FOR_VALIDATE_OLD. +# Also a removed GHC from here means that we are actually dropping +# support, so the PR *must* have a changelog entry. +GHC_FOR_VALIDATE: '["9.10.1", "9.8.2", "9.6.6", "9.4.8", "9.2.8", "9.0.2", "8.10.7", "8.8.4"]' +## GHC 7.10.3 does not install on ubuntu-22.04 with ghcup. +## Older GHCs are not supported by ghcup in the first place. +GHC_FOR_VALIDATE_OLD: '["8.4.4", "8.2.2", "8.0.2"]' +GHC_FOR_BOOTSTRAP: '["9.8.2", "9.6.6", "9.4.8", "9.2.8", "9.0.2"]' +# +COMMON_FLAGS: -j 2 -v +LTS_BRANCH: '3.12' diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 9395720b417..6cead3f3dce 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -49,46 +49,70 @@ on: required: false type: string -env: - # We choose a stable ghc version across all os's - # which will be used to do the next release - GHC_FOR_RELEASE: "9.4.8" - # Ideally we should use the version about to be released for hackage tests and benchmarks - GHC_FOR_SOLVER_BENCHMARKS: "9.4.8" - GHC_FOR_COMPLETE_HACKAGE_TESTS: "9.4.8" - COMMON_FLAGS: "-j 2 -v" +jobs: - # See https://github.com/haskell/cabal/blob/master/CONTRIBUTING.md#hackage-revisions - ALLOWNEWER: ${{ github.event.inputs.allow-newer }} - CONSTRAINTS: ${{ github.event.inputs.constraints }} + config: + runs-on: ubuntu-latest + # `matrix` can't access `env`, but it can access `outputs` from `needs` jobs. + # So we have to "export" some things here as job outputs. + # + # And GitHub documents that $GITHUB_ENV doesn't persist between jobs, only between + # steps. But somehow the `validate` job was getting it anyway, while `build-alpine` + # wasn't. + # + # I give up. Everything is now explicitly (GitHub hasn't invented iteration yet) listed + # as an output and (again explicitly) loaded into the environment in the first step of + # each job. Thanks, GitHub. (Some parts of it are not obviously used, because you need + # to dig around the local actions to see where other parts are used. $COMMON_FLAGS is + # one notable example.) + # + # Oh, and anchors aren't supported either, so yes, it has to be copy-pasted. Thanks again. + # (Actually, somehow GitHub missed making ${{ }}) expansions illegal in `run`.) + outputs: + # expose the configuration variables for use by jobs + GHC_FOR_RELEASE: ${{ steps.conf.outputs['GHC_FOR_RELEASE'] }} + GHC_FOR_SOLVER_BENCHMARKS: ${{ steps.conf.outputs['GHC_FOR_SOLVER_BENCHMARKS'] }} + GHC_FOR_COMPLETE_HACKAGE_TESTS: ${{ steps.conf.outputs['GHC_FOR_COMPLETE_HACKAGE_TESTS'] }} + GHC_FOR_VALIDATE: ${{ steps.conf.outputs['GHC_FOR_VALIDATE'] }} + GHC_FOR_VALIDATE_OLD: ${{ steps.conf.outputs['GHC_FOR_VALIDATE_OLD'] }} + GHC_FOR_BOOTSTRAP: ${{ steps.conf.outputs['GHC_FOR_BOOTSTRAP'] }} + COMMON_FLAGS: ${{ steps.conf.outputs['COMMON_FLAGS'] }} + LTS_RELEASE: ${{ steps.conf.outputs['LTS_RELEASE'] }} + # convenience for loading these into the environment + env: | + echo "GHC_FOR_RELEASE=${{ steps.conf.outputs['GHC_FOR_RELEASE'] }}" >> "$GITHUB_ENV" + echo "GHC_FOR_SOLVER_BENCHMARKS=${{ steps.conf.outputs['GHC_FOR_SOLVER_BENCHMARKS'] }}" >> "$GITHUB_ENV" + echo "GHC_FOR_COMPLETE_HACKAGE_TESTS=${{ steps.conf.outputs['GHC_FOR_COMPLETE_HACKAGE_TESTS'] }}" >> "$GITHUB_ENV" + echo "GHC_FOR_VALIDATE=${{ steps.conf.outputs['GHC_FOR_VALIDATE'] }}" >> "$GITHUB_ENV" + echo "GHC_FOR_VALIDATE_OLD=${{ steps.conf.outputs['GHC_FOR_VALIDATE_OLD'] }}" >> "$GITHUB_ENV" + echo "GHC_FOR_BOOTSTRAP=${{ steps.conf.outputs['GHC_FOR_BOOTSTRAP'] }}" >> "$GITHUB_ENV" + echo "COMMON_FLAGS=${{ steps.conf.outputs['COMMON_FLAGS'] }}" >> "$GITHUB_ENV" + echo "LTS_RELEASE=${{ steps.conf.outputs['LTS_RELEASE'] }}" >> "$GITHUB_ENV" + steps: + - uses: actions/checkout@v4 -jobs: - validate: - name: Validate ${{ matrix.sys.os }} ghc-${{ matrix.ghc }} + - uses: pietrobolcato/action-read-yaml@1.1.0 + id: conf + with: + config: ${{ github.workspace }}/.github/config.yml + + validate-build: + name: Validate build ${{ matrix.sys.os }} ghc-${{ matrix.ghc }} runs-on: ${{ matrix.sys.os }} - outputs: - GHC_FOR_RELEASE: ${{ format('["{0}"]', env.GHC_FOR_RELEASE) }} + needs: config strategy: fail-fast: false + # The matrix has to be duplicated in multiple places, because it never + # occurred to the YAML "programmers" at GitHub that it might be useful to + # refer to it in multiple jobs. matrix: sys: + # 'bash' on Windows apparently gets you the one from Git for Windows, + # whereas this needs the one from msys - { os: windows-latest, shell: "C:/msys64/usr/bin/bash.exe -e {0}" } - { os: ubuntu-22.04, shell: bash } - { os: macos-latest, shell: bash } - # If you remove something from here, then add it to the old-ghcs job. - # Also a removed GHC from here means that we are actually dropping - # support, so the PR *must* have a changelog entry. - ghc: - [ - "9.10.1", - "9.8.2", - "9.6.6", - "9.4.8", - "9.2.8", - "9.0.2", - "8.10.7", - "8.8.4", - ] + ghc: ${{ fromJSON(needs.config.outputs.GHC_FOR_VALIDATE) }} exclude: # Throws fatal "cabal-tests.exe: fd:8: hGetLine: end of file" exception # even with --io-manager=native @@ -119,222 +143,218 @@ jobs: defaults: run: shell: ${{ matrix.sys.shell }} - steps: - - name: Work around XDG directories existence (haskell-actions/setup#62) - if: runner.os == 'macOS' - run: | - rm -rf ~/.config/cabal - rm -rf ~/.cache/cabal - - name: "WIN: Setup TMP environment variable" - if: runner.os == 'Windows' - run: | - echo "TMP=${{ runner.temp }}" >> "$GITHUB_ENV" + steps: + - run: ${{ needs.config.outputs.env }} - uses: actions/checkout@v4 - # See https://github.com/haskell/cabal/blob/master/CONTRIBUTING.md#hackage-revisions - - name: Add manually supplied allow-newer - if: github.event_name == 'workflow_dispatch' && github.event.inputs.allow-newer != '' - run: | - echo "allow-newer: ${{ github.event.inputs.allow-newer }}" >> cabal.validate.project + - uses: ./.github/actions/validate-build + with: + shell: ${{ matrix.sys.shell }} + ghc: ${{ matrix.ghc }} + allow-newer: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.allow_newer || '' }} + constraints: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.constraints || '' }} + + # Thanks yet again to YAML-as-code, I need three of these now. This is abysmally stupid. + # First, apparently it is now necessary to split out _only_ `cli-suite`. (Because somehow that was supposed to make this "simpler") + # Second, supposedly I can use `include` to add in the `solver-benchmarks` tests, but if I do + # I get an incomprehensible error about `runs-on` being empty. + validate-tests: + name: Validate tests ${{ matrix.sys.os }} ghc-${{ matrix.ghc }} + runs-on: ${{ matrix.sys.os }} + needs: [config, validate-build] + strategy: + fail-fast: false + matrix: + sys: + # 'bash' on Windows apparently gets you the one from Git for Windows, + # whereas this needs the one from msys + - { os: windows-latest, shell: "C:/msys64/usr/bin/bash.exe -e {0}" } + - { os: ubuntu-22.04, shell: bash } + - { os: macos-latest, shell: bash } + ghc: ${{ fromJSON(needs.config.outputs.GHC_FOR_VALIDATE) }} + exclude: + # Throws fatal "cabal-tests.exe: fd:8: hGetLine: end of file" exception + # even with --io-manager=native + - sys: + { os: windows-latest, shell: "C:/msys64/usr/bin/bash.exe -e {0}" } + ghc: "9.0.2" + # corrupts GHA cache or the fabric of reality itself, see https://github.com/haskell/cabal/issues/8356 + - sys: + { os: windows-latest, shell: "C:/msys64/usr/bin/bash.exe -e {0}" } + ghc: "8.10.7" + # lot of segfaults caused by ghc bugs + - sys: + { os: windows-latest, shell: "C:/msys64/usr/bin/bash.exe -e {0}" } + ghc: "8.8.4" + # ghc before 8.10.5 doesn't run on AArch64 + # 9.0.2 suffers from https://gitlab.haskell.org/ghc/ghc/-/issues/20592 + # 8.10.7 throws asm errors in hashable's cbits suggesting the runner doesn't + # support a CPU extension for hardware SHA; may be fixable with flags + - sys: + { os: macos-latest, shell: bash } + ghc: "9.0.2" + - sys: + { os: macos-latest, shell: bash } + ghc: "8.10.7" + - sys: + { os: macos-latest, shell: bash } + ghc: "8.8.4" + defaults: + run: + shell: ${{ matrix.sys.shell }} - - name: Add manually supplied constraints - if: github.event_name == 'workflow_dispatch' && github.event.inputs.constraints != '' - run: | - echo "constraints: ${{ github.event.inputs.constraints }}" >> cabal.validate.project + steps: + - run: ${{ needs.config.outputs.env }} - - uses: haskell-actions/setup@v2 - id: setup-haskell - with: - ghc-version: ${{ matrix.ghc }} - cabal-version: 3.12.1.0 # see https://github.com/haskell/cabal/pull/10251 - ghcup-release-channel: https://raw.githubusercontent.com/haskell/ghcup-metadata/master/ghcup-prereleases-0.0.8.yaml + - uses: actions/checkout@v4 - # See the following link for a breakdown of the following step - # https://github.com/haskell/actions/issues/7#issuecomment-745697160 - - uses: actions/cache@v4 + - uses: ./.github/actions/cabal-setup with: - # validate.sh uses a special build dir - path: | - ${{ steps.setup-haskell.outputs.cabal-store }} - dist-* - key: ${{ runner.os }}-${{ matrix.ghc }}-${{ github.sha }} - restore-keys: ${{ runner.os }}-${{ matrix.ghc }}- - - # The tool is not essential to the rest of the test suite. If - # hackage-repo-tool is not present, any test that requires it will - # be skipped. - # We want to keep this in the loop but we don't want to fail if - # hackage-repo-tool breaks or fails to support a newer GHC version. - - name: Install hackage-repo-tool - continue-on-error: true - run: cabal install --ignore-project hackage-repo-tool - - # Needed by cabal-testsuite/PackageTests/Configure/setup.test.hs - - name: "MAC: Install Autotools" - if: runner.os == 'macOS' - run: brew install automake - - # Needed by cabal-testsuite/PackageTests/Configure/setup.test.hs - - name: "WIN: Install Autotools" - if: runner.os == 'Windows' - run: /usr/bin/pacman --noconfirm -S autotools - - - name: Set validate inputs - run: | - FLAGS="${{ env.COMMON_FLAGS }}" - if [[ "${{ matrix.ghc }}" == "${{ env.GHC_FOR_SOLVER_BENCHMARKS }}" ]]; then - FLAGS="$FLAGS --solver-benchmarks" - fi - if [[ "${{ matrix.ghc }}" == "${{ env.GHC_FOR_COMPLETE_HACKAGE_TESTS }}" ]]; then - FLAGS="$FLAGS --complete-hackage-tests" - fi - echo "FLAGS=$FLAGS" >> "$GITHUB_ENV" + shell: ${{ matrix.sys.shell }} + ghc: ${{ matrix.ghc }} + allow-newer: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.allow_newer || '' }} + constraints: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.constraints || '' }} - - name: Validate print-config - run: sh validate.sh $FLAGS -s print-config + # Note: we rely on cabal and the test suites being already cached here. If they aren't, this + # may build them incorrectly and then possibly fail. (`cabal-setup` loads the cache for us.) + - run: | + echo ::group::lib-tests + sh validate.sh $FLAGS -s lib-tests + echo ::group::lib-suite + sh validate.sh $FLAGS -s lib-suite + echo ::group::cli-tests + sh validate.sh $FLAGS -s cli-tests + + validate-cli: + name: Validate CLI ${{ matrix.sys.os }} ghc-${{ matrix.ghc }} + runs-on: ${{ matrix.sys.os }} + needs: [config, validate-build] + strategy: + fail-fast: false + matrix: + sys: + # 'bash' on Windows apparently gets you the one from Git for Windows, + # whereas this needs the one from msys + - { os: windows-latest, shell: "C:/msys64/usr/bin/bash.exe -e {0}" } + - { os: ubuntu-22.04, shell: bash } + - { os: macos-latest, shell: bash } + ghc: ${{ fromJSON(needs.config.outputs.GHC_FOR_VALIDATE) }} + exclude: + # Throws fatal "cabal-tests.exe: fd:8: hGetLine: end of file" exception + # even with --io-manager=native + - sys: + { os: windows-latest, shell: "C:/msys64/usr/bin/bash.exe -e {0}" } + ghc: "9.0.2" + # corrupts GHA cache or the fabric of reality itself, see https://github.com/haskell/cabal/issues/8356 + - sys: + { os: windows-latest, shell: "C:/msys64/usr/bin/bash.exe -e {0}" } + ghc: "8.10.7" + # lot of segfaults caused by ghc bugs + - sys: + { os: windows-latest, shell: "C:/msys64/usr/bin/bash.exe -e {0}" } + ghc: "8.8.4" + # ghc before 8.10.5 doesn't run on AArch64 + # 9.0.2 suffers from https://gitlab.haskell.org/ghc/ghc/-/issues/20592 + # 8.10.7 throws asm errors in hashable's cbits suggesting the runner doesn't + # support a CPU extension for hardware SHA; may be fixable with flags + - sys: + { os: macos-latest, shell: bash } + ghc: "9.0.2" + - sys: + { os: macos-latest, shell: bash } + ghc: "8.10.7" + - sys: + { os: macos-latest, shell: bash } + ghc: "8.8.4" + defaults: + run: + shell: ${{ matrix.sys.shell }} - - name: Validate print-tool-versions - run: sh validate.sh $FLAGS -s print-tool-versions + steps: + - run: ${{ needs.config.outputs.env }} - - name: Validate build - run: sh validate.sh $FLAGS -s build + - uses: actions/checkout@v4 - - name: Canonicalize architecture - run: | - case ${{ runner.arch }} in - X86) arch=i386 ;; - X64) arch=x86_64 ;; - ARM64) arch=aarch64 ;; - *) echo "Unsupported architecture, please fix validate.yaml" 2>/dev/null; exit 1 ;; - esac - echo "CABAL_ARCH=$arch" >> "$GITHUB_ENV" - - - name: Tar cabal head executable - if: matrix.ghc == env.GHC_FOR_RELEASE - run: | - CABAL_EXEC=$(cabal list-bin --builddir=dist-newstyle-validate-ghc-${{ matrix.ghc }} --project-file=cabal.validate.project cabal-install:exe:cabal) - # We have to tar the executable to preserve executable permissions - # see https://github.com/actions/upload-artifact/issues/38 - if [[ "${{ runner.os }}" == "Windows" ]]; then - # `cabal list-bin` gives us a windows path but tar needs the posix one - CABAL_EXEC=$(cygpath "$CABAL_EXEC") - fi - if [[ "${{ runner.os }}" == "macOS" ]]; then - # Workaround to avoid bsdtar corrupts the executable - # so executing it after untar throws `cannot execute binary file` - # see https://github.com/actions/virtual-environments/issues/2619#issuecomment-788397841 - sudo /usr/sbin/purge - fi - DIR=$(dirname "$CABAL_EXEC") - FILE=$(basename "$CABAL_EXEC") - CABAL_EXEC_TAR="cabal-head-${{ runner.os }}-$CABAL_ARCH.tar.gz" - tar -czvf "$CABAL_EXEC_TAR" -C "$DIR" "$FILE" - echo "CABAL_EXEC_TAR=$CABAL_EXEC_TAR" >> "$GITHUB_ENV" - - # We upload the cabal executable built with the ghc used in the release for: - # - Reuse it in the dogfooding job (although we could use the cached build dir) - # - Make it available in the workflow to make easier testing it locally - - name: Upload cabal-install executable to workflow artifacts - if: matrix.ghc == env.GHC_FOR_RELEASE - uses: actions/upload-artifact@v4 + - uses: ./.github/actions/cabal-setup with: - name: cabal-${{ runner.os }}-${{ env.CABAL_ARCH }} - path: ${{ env.CABAL_EXEC_TAR }} - - - name: Validate tests - env: - # `rawSystemStdInOut reports text decoding errors` - # test does not find ghc without the full path in windows - GHCPATH: ${{ steps.setup-haskell.outputs.ghc-exe }} - run: | - set +e - rc=0 - tests="lib-tests lib-suite cli-tests cli-suite" - if [ "${{ matrix.ghc }}" = "${{ env.GHC_FOR_SOLVER_BENCHMARKS }}" ]; then - tests="$tests solver-benchmarks-tests solver-benchmarks-run" - fi - for test in $tests; do - echo Validate "$test" - sh validate.sh $FLAGS -s "$test" || rc=1 - echo End "$test" - done - exit $rc - # The above ensures all the tests get run, for a single platform+ghc. - # Trying to ensure they run for *all* combinations but still fail - # at the end seems to be extremely difficult at best. It's doable, - # but it requires a continuously growing stack of conditions and - # one possibly nightmarish final conditional. 'fail-fast' gets us - # partway there, at least, but is still imperfect. + shell: ${{ matrix.sys.shell }} + ghc: ${{ matrix.ghc }} + allow-newer: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.allow_newer || '' }} + constraints: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.constraints || '' }} - validate-old-ghcs: - name: Validate old ghcs ${{ matrix.extra-ghc }} - runs-on: ubuntu-22.04 - needs: validate + # Note: we rely on cabal and the test suites being already cached here. If they aren't, this + # may build them incorrectly and then possibly fail. (`cabal-setup` loads the cache for us.) + - run: | + sh validate.sh $FLAGS -s cli-suite + validate-solver: + name: Validate solver ${{ matrix.sys.os }} ghc-${{ needs.config.outputs.GHC_FOR_RELEASE }} + runs-on: ${{ matrix.sys.os }} + needs: [config, validate-build] strategy: - matrix: - extra-ghc: - ["8.4.4", "8.2.2", "8.0.2"] - ## GHC 7.10.3 does not install on ubuntu-22.04 with ghcup. - ## Older GHCs are not supported by ghcup in the first place. fail-fast: false + matrix: + sys: + - { os: windows-latest, shell: "C:/msys64/usr/bin/bash.exe -e {0}" } + - { os: ubuntu-22.04, shell: bash } + - { os: macos-latest, shell: bash } + defaults: + run: + shell: ${{ matrix.sys.shell }} steps: + - run: ${{ needs.config.outputs.env }} + - uses: actions/checkout@v4 - - name: Install prerequisites for old GHCs - run: | - sudo apt-get update - sudo apt-get install libncurses5 libtinfo5 + - uses: ./.github/actions/cabal-setup + with: + shell: ${{ matrix.sys.shell }} + ghc: ${{ needs.config.outputs.GHC_FOR_RELEASE }} + allow-newer: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.allow_newer || '' }} + constraints: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.constraints || '' }} - - name: Install extra compiler - run: ghcup install ghc ${{ matrix.extra-ghc }} + # Note: we rely on cabal and the test suites being already cached here. If they aren't, this + # may build them incorrectly and then possibly fail. (`cabal-setup` loads the cache for us.) + - run: | + echo ::group::solver-benchmarks-tests + sh validate.sh $FLAGS -s solver-benchmarks-tests + echo ::group::solver-benchmarks-run + sh validate.sh $FLAGS -s solver-benchmarks-run - - name: GHCup logs - if: always() - run: cat /usr/local/.ghcup/logs/* + validate-old-ghcs: + name: Validate old ghcs ${{ matrix.extra-ghc }} + runs-on: ubuntu-22.04 + needs: [config, validate-build] - - name: Install primary compiler - uses: haskell-actions/setup@v2 - id: setup-haskell - with: - ghc-version: ${{ env.GHC_FOR_RELEASE }} - cabal-version: latest + strategy: + matrix: + extra-ghc: ${{ fromJSON(needs.config.outputs.GHC_FOR_VALIDATE_OLD) }} + fail-fast: false - - name: GHC versions - run: | - ghc --version - "ghc-${{ matrix.extra-ghc }}" --version + steps: + - run: ${{ needs.config.outputs.env }} + shell: bash - # As we are reusing the cached build dir from the previous step - # the generated artifacts are available here, - # including the cabal executable and the test suite - - uses: actions/cache@v4 + - uses: actions/checkout@v4 + + - uses: ./.github/actions/validate-old with: - path: | - ${{ steps.setup-haskell.outputs.cabal-store }} - dist-* - key: ${{ runner.os }}-${{ env.GHC_FOR_RELEASE }}-${{ github.sha }} - restore-keys: ${{ runner.os }}-${{ env.GHC_FOR_RELEASE }}- - - - name: Validate build - id: build - run: sh validate.sh ${{ env.COMMON_FLAGS }} -s build - - - name: "Validate lib-suite-extras --extra-hc ghc-${{ matrix.extra-ghc }}" - env: - EXTRA_GHC: ghc-${{ matrix.extra-ghc }} - run: sh validate.sh ${{ env.COMMON_FLAGS }} --lib-only -s lib-suite-extras --extra-hc "${{ env.EXTRA_GHC }}" - # See the comment above about running all tests but still failing if one - # of them does; it also applies here. + shell: bash + ghc: ${{ needs.config.outputs.GHC_FOR_RELEASE }} + extra-ghc: ${{ matrix.extra-ghc }} build-alpine: name: Build statically linked using alpine runs-on: ubuntu-latest container: "alpine:3.19" + needs: config steps: + - run: ${{ needs.config.outputs.env }} + shell: sh + - name: Install extra dependencies shell: sh run: | @@ -345,122 +365,44 @@ jobs: - uses: actions/checkout@v4 - # See https://github.com/haskell/cabal/blob/master/CONTRIBUTING.md#hackage-revisions - - name: Manually supplied constraints/allow-newer - if: github.event_name == 'workflow_dispatch' - run: | - echo "allow-newer: ${ALLOWNEWER}" >> cabal.validate.project - echo "constraints: ${CONSTRAINTS}" >> cabal.validate.project - - - uses: haskell-actions/setup@v2 - id: setup-haskell - with: - ghc-version: ${{ env.GHC_FOR_RELEASE }} - cabal-version: latest # latest is mandatory for cabal-testsuite, see https://github.com/haskell/cabal/issues/8133 - - # See the following link for a breakdown of the following step - # https://github.com/haskell/actions/issues/7#issuecomment-745697160 - - uses: actions/cache@v4 - with: - # validate.sh uses a special build dir - path: | - ${{ steps.setup-haskell.outputs.cabal-store }} - dist-* - key: ${{ runner.os }}-${{ env.GHC_FOR_RELEASE }}-${{ github.sha }} - restore-keys: ${{ runner.os }}-${{ env.GHC_FOR_RELEASE }}- - - - name: Enable statically linked executables - run: | - echo 'executable-static: true' >> cabal.validate.project - - - name: Build - run: sh validate.sh $FLAGS -s build - - - name: Tar cabal head executable - run: | - CABAL_EXEC=$(cabal list-bin --builddir=dist-newstyle-validate-ghc-${{ env.GHC_FOR_RELEASE }} --project-file=cabal.validate.project cabal-install:exe:cabal) - # We have to tar the executable to preserve executable permissions - # see https://github.com/actions/upload-artifact/issues/38 - DIR=$(dirname "$CABAL_EXEC") - FILE=$(basename "$CABAL_EXEC") - CABAL_EXEC_TAR="cabal-head-${{ runner.os }}-static-x86_64.tar.gz" - tar -czvf "$CABAL_EXEC_TAR" -C "$DIR" "$FILE" - echo "CABAL_EXEC_TAR=$CABAL_EXEC_TAR" >> "$GITHUB_ENV" - - - name: Upload cabal-install executable to workflow artifacts - uses: actions/upload-artifact@v4 + - uses: ./.github/actions/validate-build with: - name: cabal-${{ runner.os }}-static-x86_64 - path: ${{ env.CABAL_EXEC_TAR }} + ghc: ${{ env.GHC_FOR_RELEASE }} + static: 'true' + allow-newer: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.allow_newer || '' }} + constraints: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.constraints || '' }} # The previous jobs use a released version of cabal to build cabal HEAD itself - # This one uses the cabal HEAD generated executable in the previous step + # This one uses the cabal HEAD generated executable in `validate-build` # to build itself again, as sanity check dogfooding: - name: Dogfooding ${{ matrix.sys.os }} ghc-${{ matrix.ghc }} + name: Dogfooding ${{ matrix.sys.os }} ghc-${{ needs.config.outputs.GHC_FOR_RELEASE }} runs-on: ${{ matrix.sys.os }} - needs: validate + needs: [config, validate-build] strategy: matrix: sys: + # 'bash' on Windows apparently gets you the one from git for Windows, + # whereas this needs the one from msys - { os: windows-latest, shell: "C:/msys64/usr/bin/bash.exe -e {0}" } - { os: ubuntu-22.04, shell: bash } - { os: macos-latest, shell: bash } - # We only use one ghc version the used one for the next release (defined at top of the workflow) - # We need to build an array dynamically to inject the appropiate env var in a previous job, - # see https://docs.github.com/en/actions/learn-github-actions/expressions#fromjson - ghc: ${{ fromJSON (needs.validate.outputs.GHC_FOR_RELEASE) }} + fail-fast: false defaults: run: shell: ${{ matrix.sys.shell }} steps: - # TODO: make a reusable action for this - - name: Canonicalize architecture - run: | - case ${{ runner.arch }} in - X86) arch=i386 ;; - X64) arch=x86_64 ;; - ARM64) arch=aarch64 ;; - *) echo "Unsupported architecture" 2>/dev/null; exit 1 ;; - esac - echo "CABAL_ARCH=$arch" >> "$GITHUB_ENV" - - - name: "MAC: Work around XDG directories existence (haskell-actions/setup#62)" - if: runner.os == 'macOS' - run: | - rm -rf ~/.config/cabal - rm -rf ~/.cache/cabal - - - name: "WIN: Setup TMP environment variable" - if: runner.os == 'Windows' - run: | - echo "TMP=${{ runner.temp }}" >> "$GITHUB_ENV" + - run: ${{ needs.config.outputs.env }} - uses: actions/checkout@v4 - - uses: haskell-actions/setup@v2 - id: setup-haskell - with: - ghc-version: ${{ matrix.ghc }} - cabal-version: latest # default, we are not using it in this job - - - name: Download cabal executable from workflow artifacts - uses: actions/download-artifact@v4 + - uses: ./.github/actions/dogfooding with: - name: cabal-${{ runner.os }}-${{ env.CABAL_ARCH }} - path: cabal-head - - - name: Untar the cabal executable - run: tar -xzf "./cabal-head/cabal-head-${{ runner.os }}-$CABAL_ARCH.tar.gz" -C cabal-head - - - name: print-config using cabal HEAD - run: sh validate.sh ${{ env.COMMON_FLAGS }} --with-cabal ./cabal-head/cabal -s print-config - - # We dont use cache to force a build with a fresh store dir and build dir - # This way we check cabal can build all its dependencies - - name: Build using cabal HEAD - run: sh validate.sh ${{ env.COMMON_FLAGS }} --with-cabal ./cabal-head/cabal -s build + ghc: ${{ needs.config.outputs.GHC_FOR_RELEASE }} + shell: ${{ matrix.sys.shell }} + allow-newer: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.allow_newer || '' }} + constraints: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.constraints || '' }} prerelease-head: name: Create a GitHub prerelease with the binary artifacts @@ -470,7 +412,7 @@ jobs: contents: write # IMPORTANT! Any job added to the workflow should be added here too - needs: [validate, validate-old-ghcs, build-alpine, dogfooding] + needs: [validate-tests, validate-cli, validate-solver, validate-old-ghcs, build-alpine, dogfooding] steps: # for now this is hardcoded. is there a better way? @@ -495,7 +437,7 @@ jobs: contents: write # IMPORTANT! Any job added to the workflow should be added here too - needs: [validate, validate-old-ghcs, build-alpine, dogfooding] + needs: [config, validate-tests, validate-cli, validate-solver, validate-old-ghcs, build-alpine, dogfooding] steps: - uses: actions/download-artifact@v4 @@ -503,13 +445,6 @@ jobs: pattern: cabal-* path: binaries - - run: | - # bash-ism, but we forced bash above - cd binaries - for f in cabal-*; do - mv "$f" "cabal-lts-${f##cabal-}" - done - - run: echo ${{ github.ref }} - name: Create GitHub prerelease @@ -517,6 +452,7 @@ jobs: uses: softprops/action-gh-release@v2 with: tag_name: cabal-lts-head + target_commitish: '3.12' prerelease: true files: binaries/cabal-* @@ -529,7 +465,7 @@ jobs: name: Validate post job runs-on: ubuntu-latest # IMPORTANT! Any job added to the workflow should be added here too - needs: [validate, validate-old-ghcs, build-alpine, dogfooding] + needs: [validate-build, validate-tests, validate-cli, validate-solver, validate-old-ghcs, build-alpine, dogfooding] steps: - run: |