Skip to content

Directly call in-library functions to build packages #8444

Directly call in-library functions to build packages

Directly call in-library functions to build packages #8444

Workflow file for this run

name: Validate
# See: https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#concurrency.
concurrency:
group: ${{ github.ref }}-${{ github.workflow }}
cancel-in-progress: true
# Note: This workflow file contains the required job "Validate post job". We are using path filtering
# here to ignore PRs which only change documentation. This can cause a problem, see the workflow file
# "validate.skip.yml" for a description of the problem and the solution provided in that file.
on:
push:
paths-ignore:
- "doc/**"
- "**/README.md"
- "CONTRIBUTING.md"
- "changelog.d/**"
# only top level for these, because various test packages have them too
- "*/ChangeLog.md"
- "*/changelog.md"
- "release-notes/**"
branches:
- master
# hardcoded LTS branch, change when new LTS released!
- '3.12'
pull_request:
paths-ignore:
- "doc/**"
- "**/README.md"
- "CONTRIBUTING.md"
- "changelog.d/**"
- "*/ChangeLog.md"
- "*/changelog.md"
- "release-notes/**"
release:
types:
- created
workflow_call:
# See https://github.com/haskell/cabal/blob/master/CONTRIBUTING.md#hackage-revisions
workflow_dispatch:
inputs:
allow-newer:
description: allow-newer line
required: false
type: string
constraints:
description: constraints line
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"
# See https://github.com/haskell/cabal/blob/master/CONTRIBUTING.md#hackage-revisions
ALLOWNEWER: ${{ github.event.inputs.allow-newer }}
CONSTRAINTS: ${{ github.event.inputs.constraints }}
jobs:
validate:
name: Validate ${{ matrix.sys.os }} ghc-${{ matrix.ghc }}
runs-on: ${{ matrix.sys.os }}
outputs:
GHC_FOR_RELEASE: ${{ format('["{0}"]', env.GHC_FOR_RELEASE) }}
strategy:
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 }
# 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",
]
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 }}
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"
- 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
- 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
- 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
# 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 }}-${{ 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"
- name: Validate print-config
run: sh validate.sh $FLAGS -s print-config
- name: Validate print-tool-versions
run: sh validate.sh $FLAGS -s print-tool-versions
- name: Validate build
run: sh validate.sh $FLAGS -s build
- 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
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.
validate-old-ghcs:
name: Validate old ghcs ${{ matrix.extra-ghc }}
runs-on: ubuntu-22.04
needs: validate
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
steps:
- uses: actions/checkout@v4
- name: Install prerequisites for old GHCs
run: |
sudo apt-get update
sudo apt-get install libncurses5 libtinfo5
- name: Install extra compiler
run: ghcup install ghc ${{ matrix.extra-ghc }}
- name: GHCup logs
if: always()
run: cat /usr/local/.ghcup/logs/*
- name: Install primary compiler
uses: haskell-actions/setup@v2
id: setup-haskell
with:
ghc-version: ${{ env.GHC_FOR_RELEASE }}
cabal-version: latest
- name: GHC versions
run: |
ghc --version
"ghc-${{ matrix.extra-ghc }}" --version
# 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
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.
build-alpine:
name: Build statically linked using alpine
runs-on: ubuntu-latest
container: "alpine:3.19"
steps:
- name: Install extra dependencies
shell: sh
run: |
apk add bash curl sudo jq pkgconfig \
zlib-dev zlib-static binutils-gold curl \
gcc g++ gmp-dev libc-dev libffi-dev make \
musl-dev ncurses-dev perl tar xz
- 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
with:
name: cabal-${{ runner.os }}-static-x86_64
path: ${{ env.CABAL_EXEC_TAR }}
# 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
# to build itself again, as sanity check
dogfooding:
name: Dogfooding ${{ matrix.sys.os }} ghc-${{ matrix.ghc }}
runs-on: ${{ matrix.sys.os }}
needs: validate
strategy:
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 }
# 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) }}
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"
- 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
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
prerelease-head:
name: Create a GitHub prerelease with the binary artifacts
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/master'
permissions:
contents: write
# IMPORTANT! Any job added to the workflow should be added here too
needs: [validate, validate-old-ghcs, build-alpine, dogfooding]
steps:
# for now this is hardcoded. is there a better way?
- uses: actions/download-artifact@v4
with:
pattern: cabal-*
path: binaries
- name: Create GitHub prerelease
uses: softprops/action-gh-release@v2
with:
tag_name: cabal-head
prerelease: true
files: binaries/cabal-*
prerelease-lts:
name: Create a GitHub LTS prerelease with the binary artifacts
runs-on: ubuntu-latest
# The LTS branch is hardcoded for now, update it on a new LTS!
# if: github.ref == 'refs/heads/3.12'
permissions:
contents: write
# IMPORTANT! Any job added to the workflow should be added here too
needs: [validate, validate-old-ghcs, build-alpine, dogfooding]
steps:
- uses: actions/download-artifact@v4
with:
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
if: github.ref == 'refs/heads/3.12'
uses: softprops/action-gh-release@v2
with:
tag_name: cabal-lts-head
prerelease: true
files: binaries/cabal-*
# We use this job as a summary of the workflow
# It will fail if any of the previous jobs does
# This way we can use it exclusively in branch protection rules
# and abstract away the concrete jobs of the workflow, including their names
validate-post-job:
if: always()
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]
steps:
- run: |
echo "jobs info: ${{ toJSON(needs) }}"
- if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled')
run: exit 1