Skip to content

Build fully static compiler binary using ghc-musl #1637

Build fully static compiler binary using ghc-musl

Build fully static compiler binary using ghc-musl #1637

Workflow file for this run

name: "CI"
on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]
paths:
- .github/workflows/**/*.yml
- app/**/*
- bundle/**/*
- ci/**/*
- license-generator/**/*
- src/**/*
- test/**/*
- .gitignore
- .hlint.yaml
- .hspec
- cabal.project
- purescript.cabal
- Setup.hs
- stack.yaml
- stack.yaml.lock
- update-changelog.hs
- weeder.dhall
release:
types: [ "published" ]
defaults:
run:
shell: "bash"
env:
CI_PRERELEASE: "${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}"
CI_RELEASE: "${{ github.event_name == 'release' }}"
STACK_VERSION: "3.3.1"
concurrency:
# We never want two prereleases building at the same time, since they would
# likely both claim the same version number. Pull request builds can happen
# in parallel with anything else, since they don't mutate global state with a
# release. Release builds don't change their behavior based on published
# state, so they don't interfere with each other and there's no point in
# canceling a prerelease build if a release build starts; and we would never
# want a release build to be canceled by a prerelease build either. (GitHub
# Actions is either too cheap to give us `if` expressions or too lazy to
# document them, but we have untyped boolean operators to fall back on.)
group: "${{ github.event_name != 'push' && github.run_id || 'continuous-deployment' }}"
cancel-in-progress: true
jobs:
build:
strategy:
fail-fast: false # do not cancel builds for other OSes if one fails
matrix:
include:
- image: haskell:9.8.4 # Also upgrade version in the lint job below
os: ubuntu-latest # Exact version is not important, as it's only the container host)
- image: haskell:9.8.4
os: ubuntu-24.04-arm # Exact version is not important, as it's only the container host
- image: quay.io/benz0li/ghc-musl:9.8.4
os: ubuntu-latest
static: true
- os: macos-13 # x64
- os: macos-14 # arm64
- os: windows-2019 # x64
runs-on: "${{ matrix.os }}"
container: "${{ matrix.image }}"
env:
CI_STATIC: "${{ matrix.static }}"
outputs:
do-not-prerelease: "${{ steps.build.outputs.do-not-prerelease }}"
version: "${{ steps.build.outputs.version }}"
steps:
- # We need `gh` installed on the Linux version. Otherwise, release artifacts won't be uploaded.
name: "(Linux only) Install gh"
if: startsWith(matrix.image, 'haskell')
run: |
curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg
chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | tee /etc/apt/sources.list.d/github-cli.list > /dev/null
apt-get update
apt-get install -y gh
- name: "(Alpine only) Install gh"
if: ${{ matrix.static }}
run: |
apk add github-cli
- uses: "actions/checkout@v4"
- uses: "actions/setup-node@v4"
if: ${{ ! matrix.static }}
with:
node-version: "22"
- name: "(Alpine only) Install Node"
if: ${{ matrix.static }}
run: |
apk add nodejs npm
- id: "haskell"
name: "(Non-Linux only) Install Haskell"
if: startsWith(matrix.os, 'macos') || startsWith(matrix.os, 'windows')
uses: "haskell-actions/setup@v2"
with:
ghc-version: "9.8.4"
enable-stack: true
stack-version: "${{ env.STACK_VERSION }}"
stack-no-global: true
- name: "(Linux only) Fix working directory ownership"
if: ${{ startsWith(matrix.image, 'haskell') || matrix.static }}
run: |
chown root:root .
- uses: "actions/cache@v4"
with:
path: |
/root/.stack
${{ steps.haskell.outputs.stack-root }}
key: "${{ matrix.image || runner.os }}-v2-${{ hashFiles('stack.yaml.lock', 'purescript.cabal') }}"
- name: "(Windows only) Configure Stack to store its programs in STACK_ROOT"
# This ensures that the local GHC and MSYS binaries that Stack installs
# are included in the cache. (This behavior is the default on
# non-Windows OSes.)
if: "${{ runner.os == 'Windows' }}"
run: |
mkdir -p "$STACK_ROOT"
echo "local-programs-path: $STACK_ROOT/programs" > $STACK_ROOT/config.yaml
- name: "(Alpine only) Configure Stack"
if: ${{ matrix.static }}
run: |
ci/fix-home stack config set system-ghc --global true
ci/fix-home stack config set install-ghc --global false
- id: "build"
run: "ci/fix-home ci/build.sh"
- name: "(Linux only) Glob tests"
if: ${{ startsWith(matrix.image, 'haskell') }}
working-directory: "sdist-test"
# We build in this directory in build.sh, so this is where we need to
# launch `stack exec`. The actual glob checks happen in a temporary directory.
run: |
apt-get install -y tree
../ci/fix-home stack exec bash ../glob-test.sh
- name: "(Alpine only) Glob tests"
if: ${{ matrix.static }}
working-directory: "sdist-test"
run: |
apk add tree
../ci/fix-home stack exec bash ../glob-test.sh
- name: "(Linux only) Build the entire package set"
if: ${{ startsWith(matrix.image, 'haskell') }}
# We build in this directory in build.sh, so this is where we need to
# launch `stack exec`. The actual package-set building happens in a
# temporary directory.
working-directory: "sdist-test"
# The presence or absence of the --haddock flag changes the location
# into which stack places all build artifacts. Since we use --haddock
# in our CI builds, in order to actually get stack to find the purs
# binary it created, we need to use the flag here as well.
#
# Moreover, npm has a hook issue that will cause spago to fail to install
# We upgrade npm to fix this
run: |
apt-get install -y jq
../ci/fix-home stack --haddock exec ../ci/build-package-set.sh
- name: "(Alpine only) Build the entire package set"
if: ${{ matrix.static }}
working-directory: "sdist-test"
run: |
apk add jq
../ci/fix-home stack --haddock exec ../ci/build-package-set.sh
- name: Verify that 'libtinfo' isn't in binary
if: ${{ runner.os == 'Linux' && matrix.static }}
working-directory: "sdist-test"
run: |
if [ $(ldd $(../ci/fix-home stack path --local-doc-root)/../bin/purs | grep 'libtinfo' | wc -l) -ge 1 ]; then
echo "libtinfo detected"
ldd $(../ci/fix-home stack path --local-doc-root)/../bin/purs | grep 'libtinfo'
exit 1
fi
- name: "(Alpine only) Install perl-utils"
if: ${{ matrix.static }}
run: |
apk add perl-utils
- name: "(Release/prerelease only) Create bundle"
if: "${{ env.CI_RELEASE == 'true' || env.CI_PRERELEASE == 'true' && steps.build.outputs.do-not-prerelease != 'true' }}"
run: |
os_name="${{ runner.os }}"
os_arch="${{ runner.arch }}"
os_static="${{ matrix.static }}"
case "$os_name" in
Linux)
case "$os_arch" in
ARM64)
bundle_os=linux-arm64;;
*)
bundle_os=linux64;;
esac
case "$os_static" in
true)
bundle_os="${bundle_os}-static";;
*)
bundle_os="${bundle_os}-dynamic";;
esac;;
macOS)
case "$os_arch" in
ARM64)
bundle_os=macos-arm64;;
*)
bundle_os=macos;;
esac;;
Windows)
bundle_os=win64;;
*)
echo "Unknown OS name: $os_name"
exit 1;;
esac
cd sdist-test
../ci/fix-home bundle/build.sh "$bundle_os"
- name: "(Prerelease only) Upload bundle"
if: "${{ env.CI_PRERELEASE == 'true' && steps.build.outputs.do-not-prerelease != 'true' }}"
uses: "actions/[email protected]"
with:
name: "${{ runner.os }}-${{ runner.arch }}-bundle"
path: |
sdist-test/bundle/*.sha
sdist-test/bundle/*.tar.gz
- name: "(Release only) Publish bundle"
if: "${{ env.CI_RELEASE == 'true' }}"
env:
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
run: "gh release upload --clobber ${{ github.ref_name }} sdist-test/bundle/*.{tar.gz,sha}"
lint:
container: haskell:9.8.4
runs-on: ubuntu-latest # Exact version is not important, as it's only the container host
steps:
- uses: "actions/checkout@v4"
- name: "Fix working directory ownership"
run: |
chown root:root .
- uses: "actions/cache@v4"
with:
path: |
/root/.stack
key: "lint-${{ hashFiles('stack.yaml.lock', 'purescript.cabal') }}"
- run: "ci/fix-home ci/run-hlint.sh --git"
env:
VERSION: "3.10"
- name: Install weeder
run: |
ci/fix-home stack --no-terminal --jobs=2 \
build --copy-compiler-tool weeder-2.9.0
- run: |
ci/fix-home stack --no-terminal --jobs=2 \
build --fast --ghc-options -fwrite-ide-info
- run: "ci/fix-home stack exec weeder -- --hie-directory .stack-work"
# Now do it again, with the test suite included. We don't want a
# reference from our test suite to count in the above check; the fact
# that a function is tested is not evidence that it's needed. But we also
# don't want to leave weeds lying around in our test suite either.
- run: |
ci/fix-home stack --no-terminal --jobs=2 \
build --fast --test --no-run-tests --ghc-options -fwrite-ide-info
- run: "ci/fix-home stack exec weeder -- --hie-directory .stack-work"
make-prerelease:
runs-on: ubuntu-latest
needs:
- "build"
- "lint"
if: "${{ github.event_name == 'push' && needs.build.outputs.do-not-prerelease != 'true' }}"
steps:
- uses: "actions/download-artifact@v4"
- uses: "ncipollo/[email protected]"
with:
tag: "v${{ needs.build.outputs.version }}"
artifacts: "*-bundle/*"
prerelease: true
body: "This is an automated preview release. Get the latest stable release [here](https://github.com/purescript/purescript/releases/latest)."
- uses: "actions/checkout@v4"
- uses: "actions/setup-node@v4"
with:
node-version: "16.x"
registry-url: "https://registry.npmjs.org"
- name: "Publish npm package"
working-directory: "npm-package"
env:
BUILD_VERSION: "${{ needs.build.outputs.version }}"
NODE_AUTH_TOKEN: "${{ secrets.NPM_TOKEN }}"
run: |
src_version=$(node -pe 'require("./package.json").version')
npm version --allow-same-version "$BUILD_VERSION"
sed -i -e "s/--purs-ver=${src_version//./\\.}/--purs-ver=$BUILD_VERSION/" package.json
npm publish --tag next