Luite/wip plinth ci #38
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| on: | |
| pull_request: | |
| types: [opened, synchronize] | |
| push: | |
| branches: [master, ghc-9.6-plinth] | |
| tags: ['v*'] | |
| workflow_dispatch: | |
| inputs: | |
| release: | |
| description: 'Build release bindists (with docs)' | |
| type: boolean | |
| default: false | |
| jobs: | |
| build: | |
| name: Build / ${{ matrix.name }} | |
| runs-on: ${{ matrix.os }} | |
| container: ${{ matrix.container }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - name: x86_64-linux-glibc | |
| os: ubuntu-latest | |
| container: rockylinux:8 | |
| shell: bash | |
| - name: x86_64-linux-musl | |
| os: ubuntu-latest | |
| container: alpine:3.20 | |
| shell: sh | |
| - name: aarch64-linux-glibc | |
| os: ubuntu-22.04-arm | |
| container: debian:11 | |
| shell: bash | |
| - name: aarch64-linux-musl | |
| os: ubuntu-22.04-arm | |
| docker: alpine:3.20 | |
| shell: bash | |
| - name: aarch64-darwin | |
| os: macos-15 | |
| shell: bash | |
| - name: x86_64-darwin | |
| os: macos-15-large | |
| shell: bash | |
| - name: x86_64-windows | |
| os: windows-latest | |
| shell: msys2 {0} | |
| env: | |
| RELEASE: ${{ inputs.release && '1' || '0' }} | |
| defaults: | |
| run: | |
| shell: ${{ matrix.shell }} | |
| steps: | |
| - name: Install system dependencies (Rocky Linux) | |
| if: matrix.container == 'rockylinux:8' | |
| run: | | |
| dnf install -y 'dnf-command(config-manager)' | |
| dnf config-manager --set-enabled powertools | |
| dnf install -y epel-release | |
| dnf install -y --allowerasing \ | |
| gcc gcc-c++ make autoconf automake \ | |
| python3 tar xz curl git perl which findutils \ | |
| gmp-devel ncurses-devel zlib-devel libffi-devel \ | |
| openssh-clients patchelf | |
| - name: Install system dependencies (Debian) | |
| if: matrix.container == 'debian:11' | |
| run: | | |
| apt-get update | |
| apt-get install -y \ | |
| gcc g++ make autoconf automake \ | |
| python3 tar xz-utils curl git perl \ | |
| libgmp-dev libncurses-dev zlib1g-dev libffi-dev \ | |
| openssh-client patchelf | |
| - name: Install system dependencies (Alpine) | |
| if: matrix.container == 'alpine:3.20' | |
| run: | | |
| apk update | |
| apk add \ | |
| alpine-sdk autoconf automake bash build-base coreutils \ | |
| curl gcc g++ git gmp gmp-dev grep linux-headers gzip \ | |
| ncurses-dev ncurses-libs ncurses-static \ | |
| perl python3 sudo wget xz zlib-dev zlib-static \ | |
| patchelf findutils llvm17 clang17 openssh-client | |
| - name: Configure git for Windows | |
| if: runner.os == 'Windows' | |
| shell: bash | |
| run: git config --global core.symlinks true | |
| - uses: actions/checkout@v4 | |
| with: | |
| submodules: false | |
| - name: Mark workspace as safe for git | |
| if: matrix.container | |
| shell: bash | |
| run: git config --global --add safe.directory "$GITHUB_WORKSPACE" | |
| - name: Use GitHub mirrors for submodules | |
| shell: bash | |
| run: | | |
| # Redirect gitlab.haskell.org submodules to GitHub mirrors (much faster) | |
| git config --global url."https://github.com/ghc/packages-".insteadOf "https://gitlab.haskell.org/ghc/packages/" | |
| git config --global url."https://github.com/ghc/".insteadOf "https://gitlab.haskell.org/ghc/" | |
| # Keep these on gitlab (missing or stale GitHub mirrors) | |
| git config --global url."https://gitlab.haskell.org/ghc/packages/exceptions.git".insteadOf "https://gitlab.haskell.org/ghc/packages/exceptions.git" | |
| git config --global url."https://gitlab.haskell.org/ghc/libffi-tarballs.git".insteadOf "https://gitlab.haskell.org/ghc/libffi-tarballs.git" | |
| - name: Setup SSH for submodules | |
| shell: bash | |
| run: | | |
| mkdir -p ~/.ssh | |
| echo "${{ secrets.SUBMODULE_SSH_KEY }}" > ~/.ssh/id_rsa | |
| chmod 600 ~/.ssh/id_rsa | |
| ssh-keyscan github.com >> ~/.ssh/known_hosts | |
| - name: Init and update submodules | |
| shell: bash | |
| env: | |
| GIT_SSH_COMMAND: "ssh -i $HOME/.ssh/id_rsa -o UserKnownHostsFile=$HOME/.ssh/known_hosts -o StrictHostKeyChecking=yes" | |
| run: | | |
| git submodule sync | |
| git submodule update --init --recursive | |
| - name: Setup MSYS2 | |
| if: runner.os == 'Windows' | |
| uses: msys2/setup-msys2@v2 | |
| with: | |
| msystem: MINGW64 | |
| path-type: inherit | |
| update: false | |
| install: >- | |
| autoconf automake make tar xz patch curl | |
| mingw-w64-x86_64-python | |
| - name: Install autotools (macOS) | |
| if: runner.os == 'macOS' | |
| run: brew install autoconf automake | |
| - uses: haskell-actions/setup@v2 | |
| if: ${{ !matrix.docker }} | |
| name: Setup Haskell tools | |
| with: | |
| ghc-version: '9.6.7' | |
| cabal-version: 'latest' | |
| cabal-update: true | |
| - name: Build bindist | |
| if: ${{ !matrix.docker }} | |
| run: ./plinth-build.sh | |
| # For entries that can't use container: (Alpine on ARM), run the | |
| # entire build inside Docker. The workspace is mounted from the host | |
| # so checkout/submodules done above are available. | |
| - name: Build bindist (Docker) | |
| if: ${{ matrix.docker }} | |
| run: | | |
| docker run --rm \ | |
| -v "$GITHUB_WORKSPACE:/workspace" \ | |
| -w /workspace \ | |
| -e RELEASE="${{ env.RELEASE }}" \ | |
| ${{ matrix.docker }} sh -c ' | |
| set -eux | |
| apk update | |
| apk add \ | |
| alpine-sdk autoconf automake bash build-base coreutils \ | |
| curl gcc g++ git gmp gmp-dev grep linux-headers gzip \ | |
| ncurses-dev ncurses-libs ncurses-static \ | |
| perl python3 sudo wget xz zlib-dev zlib-static \ | |
| patchelf findutils llvm17 clang17 | |
| # Install GHCup + GHC + cabal | |
| curl --proto "=https" --tlsv1.2 -sSf https://get-ghcup.haskell.org | \ | |
| BOOTSTRAP_HASKELL_NONINTERACTIVE=1 \ | |
| BOOTSTRAP_HASKELL_GHC_VERSION=9.6.7 \ | |
| BOOTSTRAP_HASKELL_CABAL_VERSION=latest \ | |
| sh | |
| . "$HOME/.ghcup/env" | |
| git config --global --add safe.directory /workspace | |
| git config --global url."https://github.com/ghc/packages-".insteadOf "https://gitlab.haskell.org/ghc/packages/" | |
| git config --global url."https://github.com/ghc/".insteadOf "https://gitlab.haskell.org/ghc/" | |
| git config --global url."https://gitlab.haskell.org/ghc/packages/exceptions.git".insteadOf "https://gitlab.haskell.org/ghc/packages/exceptions.git" | |
| git config --global url."https://gitlab.haskell.org/ghc/libffi-tarballs.git".insteadOf "https://gitlab.haskell.org/ghc/libffi-tarballs.git" | |
| ./plinth-build.sh | |
| ' | |
| - name: Upload bindist | |
| uses: ./.github/actions/upload | |
| with: | |
| name: bindist-${{ matrix.name }} | |
| path: _build/bindist/*.tar.xz | |
| - name: Upload metadata | |
| uses: ./.github/actions/upload | |
| with: | |
| name: metadata-${{ matrix.name }} | |
| path: _build/bindist/*-ghcup-metadata.yaml | |
| test: | |
| name: Test / ${{ matrix.name }} | |
| needs: build | |
| runs-on: ${{ matrix.os }} | |
| container: ${{ matrix.container }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| # Test x86_64-linux-glibc bindist across distros | |
| - name: x86_64-linux / rocky-8 | |
| os: ubuntu-latest | |
| container: rockylinux:8 | |
| shell: bash | |
| bindist: x86_64-linux-glibc | |
| pkg: yum | |
| full_test: true | |
| - name: x86_64-linux / rocky-9 | |
| os: ubuntu-latest | |
| container: rockylinux:9 | |
| shell: bash | |
| bindist: x86_64-linux-glibc | |
| pkg: dnf | |
| - name: x86_64-linux / debian-11 | |
| os: ubuntu-latest | |
| container: debian:11 | |
| shell: bash | |
| bindist: x86_64-linux-glibc | |
| pkg: apt | |
| - name: x86_64-linux / debian-12 | |
| os: ubuntu-latest | |
| container: debian:12 | |
| shell: bash | |
| bindist: x86_64-linux-glibc | |
| pkg: apt | |
| - name: x86_64-linux / ubuntu-22.04 | |
| os: ubuntu-latest | |
| container: ubuntu:22.04 | |
| shell: bash | |
| bindist: x86_64-linux-glibc | |
| pkg: apt | |
| - name: x86_64-linux / ubuntu-24.04 | |
| os: ubuntu-latest | |
| container: ubuntu:24.04 | |
| shell: bash | |
| bindist: x86_64-linux-glibc | |
| pkg: apt | |
| - name: x86_64-linux / fedora-42 | |
| os: ubuntu-latest | |
| container: fedora:42 | |
| shell: bash | |
| bindist: x86_64-linux-glibc | |
| pkg: dnf | |
| # Test x86_64-linux-musl bindist | |
| - name: x86_64-linux / alpine-3.20 | |
| os: ubuntu-latest | |
| container: alpine:3.20 | |
| shell: sh | |
| bindist: x86_64-linux-musl | |
| pkg: apk | |
| # Test aarch64-linux-glibc bindist | |
| - name: aarch64-linux / debian-11 | |
| os: ubuntu-22.04-arm | |
| container: debian:11 | |
| shell: bash | |
| bindist: aarch64-linux-glibc | |
| pkg: apt | |
| full_test: true | |
| - name: aarch64-linux / debian-12 | |
| os: ubuntu-22.04-arm | |
| container: debian:12 | |
| shell: bash | |
| bindist: aarch64-linux-glibc | |
| pkg: apt | |
| - name: aarch64-linux / ubuntu-24.04 | |
| os: ubuntu-22.04-arm | |
| container: ubuntu:24.04 | |
| shell: bash | |
| bindist: aarch64-linux-glibc | |
| pkg: apt | |
| # Test aarch64-linux-musl bindist (Docker, not container:) | |
| - name: aarch64-linux / alpine-3.20 | |
| os: ubuntu-22.04-arm | |
| docker: alpine:3.20 | |
| shell: bash | |
| bindist: aarch64-linux-musl | |
| - name: aarch64-darwin | |
| os: macos-15 | |
| shell: bash | |
| bindist: aarch64-darwin | |
| - name: x86_64-darwin | |
| os: macos-15-large | |
| shell: bash | |
| bindist: x86_64-darwin | |
| - name: x86_64-windows | |
| os: windows-latest | |
| shell: msys2 {0} | |
| bindist: x86_64-windows | |
| env: | |
| # Only run the full plinth test suite on selected entries | |
| RUN_PLINTH_TEST: ${{ matrix.full_test && '1' || '0' }} | |
| defaults: | |
| run: | |
| shell: ${{ matrix.shell }} | |
| steps: | |
| - name: Install test dependencies (apt) | |
| if: matrix.pkg == 'apt' | |
| run: | | |
| apt-get update | |
| apt-get install -y \ | |
| gcc g++ make autoconf automake curl git \ | |
| libgmp-dev libncurses-dev zlib1g-dev libffi-dev \ | |
| openssh-client file binutils patchelf | |
| - name: Install test dependencies (dnf) | |
| if: matrix.pkg == 'dnf' | |
| run: | | |
| dnf install -y \ | |
| gcc gcc-c++ make autoconf automake curl git \ | |
| gmp-devel ncurses-devel zlib-devel libffi-devel \ | |
| openssh-clients file binutils findutils patchelf | |
| - name: Install test dependencies (yum) | |
| if: matrix.pkg == 'yum' | |
| run: | | |
| dnf install -y epel-release | |
| yum install -y \ | |
| gcc gcc-c++ make autoconf automake curl git \ | |
| gmp-devel ncurses-devel zlib-devel libffi-devel \ | |
| openssh-clients file binutils findutils patchelf | |
| - name: Install test dependencies (apk) | |
| if: matrix.pkg == 'apk' | |
| run: | | |
| apk update | |
| apk add \ | |
| gcc g++ musl-dev make autoconf automake curl git \ | |
| gmp-dev ncurses-dev zlib-dev libffi-dev \ | |
| file binutils findutils patchelf bash coreutils \ | |
| openssh-client | |
| - uses: actions/checkout@v4 | |
| with: | |
| submodules: false | |
| - name: Mark workspace as safe for git | |
| if: matrix.container | |
| shell: bash | |
| run: git config --global --add safe.directory "$GITHUB_WORKSPACE" | |
| - name: Use GitHub mirrors for submodules | |
| if: runner.os != 'Windows' | |
| shell: bash | |
| run: | | |
| git config --global url."https://github.com/ghc/packages-".insteadOf "https://gitlab.haskell.org/ghc/packages/" | |
| git config --global url."https://github.com/ghc/".insteadOf "https://gitlab.haskell.org/ghc/" | |
| git config --global url."https://gitlab.haskell.org/ghc/packages/exceptions.git".insteadOf "https://gitlab.haskell.org/ghc/packages/exceptions.git" | |
| git config --global url."https://gitlab.haskell.org/ghc/libffi-tarballs.git".insteadOf "https://gitlab.haskell.org/ghc/libffi-tarballs.git" | |
| # Submodules are needed on Unix for the plinth test suite | |
| # (run as part of verify-bindist-relocatable.sh) | |
| - name: Setup SSH for submodules | |
| if: runner.os != 'Windows' | |
| shell: bash | |
| run: | | |
| mkdir -p ~/.ssh | |
| echo "${{ secrets.SUBMODULE_SSH_KEY }}" > ~/.ssh/id_rsa | |
| chmod 600 ~/.ssh/id_rsa | |
| ssh-keyscan github.com >> ~/.ssh/known_hosts | |
| - name: Init and update submodules | |
| if: runner.os != 'Windows' | |
| shell: bash | |
| env: | |
| GIT_SSH_COMMAND: "ssh -i $HOME/.ssh/id_rsa -o UserKnownHostsFile=$HOME/.ssh/known_hosts -o StrictHostKeyChecking=yes" | |
| run: | | |
| git submodule sync | |
| git submodule update --init --recursive | |
| - name: Setup MSYS2 | |
| if: runner.os == 'Windows' | |
| uses: msys2/setup-msys2@v2 | |
| with: | |
| msystem: MINGW64 | |
| path-type: inherit | |
| update: false | |
| install: tar xz | |
| # Haskell tools needed for the full plinth test suite (cabal build/run) | |
| - uses: haskell-actions/setup@v2 | |
| if: matrix.full_test | |
| name: Setup Haskell tools | |
| with: | |
| ghc-version: '9.6.7' | |
| cabal-version: 'latest' | |
| cabal-update: true | |
| - name: Download bindist | |
| uses: ./.github/actions/download | |
| with: | |
| name: bindist-${{ matrix.bindist }} | |
| path: _build/bindist/ | |
| - name: Verify bindist | |
| if: runner.os != 'Windows' && !matrix.docker | |
| run: ./verify-bindist-relocatable.sh | |
| # For Docker-based test entries (Alpine on ARM), run verification | |
| # inside Docker with the workspace mounted from the host. | |
| - name: Verify bindist (Docker) | |
| if: ${{ matrix.docker }} | |
| run: | | |
| docker run --rm \ | |
| -v "$GITHUB_WORKSPACE:/workspace" \ | |
| -w /workspace \ | |
| -e RUN_PLINTH_TEST=0 \ | |
| ${{ matrix.docker }} sh -c ' | |
| set -eux | |
| apk update | |
| apk add \ | |
| gcc g++ musl-dev make autoconf automake curl git \ | |
| gmp-dev ncurses-dev zlib-dev libffi-dev \ | |
| file binutils findutils patchelf bash coreutils | |
| git config --global --add safe.directory /workspace | |
| ./verify-bindist-relocatable.sh | |
| ' | |
| # On Windows the bindist has no configure script; verify by | |
| # extracting and running the relocatable binaries directly. | |
| - name: Verify bindist (Windows) | |
| if: runner.os == 'Windows' | |
| shell: msys2 {0} | |
| run: | | |
| tarball=(_build/bindist/*.tar.xz) | |
| dir=$(basename "$tarball" .tar.xz) | |
| mkdir -p _test/bindist | |
| tar -xf "$tarball" -C _test/bindist | |
| echo "=== uplc-ghc --version ===" | |
| _test/bindist/"$dir"/bin/uplc-ghc.exe --version | |
| echo "=== uplc-ghc -e ===" | |
| _test/bindist/"$dir"/bin/uplc-ghc.exe -e 'putStrLn "hello"' | |
| echo "=== ghc-pkg check ===" | |
| _test/bindist/"$dir"/bin/ghc-pkg.exe check --no-user-package-db | |
| release: | |
| name: Release | |
| needs: [build, test] | |
| if: startsWith(github.ref, 'refs/tags/v') | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Download all bindist artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| pattern: bindist-* | |
| path: artifacts/bindists/ | |
| - name: Download all metadata artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| pattern: metadata-* | |
| path: artifacts/metadata/ | |
| - name: Collect release assets | |
| run: | | |
| mkdir -p release/ | |
| for dir in artifacts/bindists/*/; do | |
| for tarball in "$dir"*.tar.xz; do | |
| [ -f "$tarball" ] || continue | |
| cp "$tarball" release/ | |
| done | |
| done | |
| echo "Release assets:" | |
| ls -lh release/ | |
| - name: Merge ghcup metadata | |
| run: | | |
| pip install pyyaml | |
| python3 << 'PYEOF' | |
| import yaml, glob, os, copy | |
| tag = os.environ['GITHUB_REF_NAME'] | |
| repo = os.environ['GITHUB_REPOSITORY'] | |
| base_url = f"https://github.com/{repo}/releases/download/{tag}" | |
| # Collect all release tarball filenames for URL rewriting | |
| release_files = set(os.listdir('release')) | |
| merged = {'toolRequirements': {}, 'ghcupDownloads': {'plinth': {}}} | |
| for f in sorted(glob.glob('artifacts/metadata/*/*.yaml')): | |
| with open(f) as fh: | |
| data = yaml.safe_load(fh) | |
| for version, vinfo in data['ghcupDownloads']['plinth'].items(): | |
| if version not in merged['ghcupDownloads']['plinth']: | |
| merged['ghcupDownloads']['plinth'][version] = { | |
| 'viTags': vinfo.get('viTags', []), | |
| 'viArch': {} | |
| } | |
| dest_arch = merged['ghcupDownloads']['plinth'][version]['viArch'] | |
| for arch, platforms in vinfo['viArch'].items(): | |
| if arch not in dest_arch: | |
| dest_arch[arch] = {} | |
| for platform, versioning in platforms.items(): | |
| entry = copy.deepcopy(versioning) | |
| # Rewrite dlUri from file:// to release asset URL | |
| for vkey in entry: | |
| vdata = entry[vkey] | |
| if isinstance(vdata, dict) and 'dlUri' in vdata: | |
| old_uri = vdata['dlUri'] | |
| filename = os.path.basename(old_uri) | |
| if filename in release_files: | |
| vdata['dlUri'] = f"{base_url}/{filename}" | |
| else: | |
| print(f"WARNING: {filename} not in release assets") | |
| vdata['dlUri'] = f"{base_url}/{filename}" | |
| dest_arch[arch][platform] = entry | |
| os.makedirs('release', exist_ok=True) | |
| with open('release/ghcup-metadata.yaml', 'w') as fh: | |
| yaml.dump(merged, fh, default_flow_style=False, sort_keys=False) | |
| print("Merged metadata written to release/ghcup-metadata.yaml") | |
| with open('release/ghcup-metadata.yaml') as fh: | |
| print(fh.read()) | |
| PYEOF | |
| - name: Create GitHub Release | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| gh release create "$GITHUB_REF_NAME" \ | |
| --title "Plinth $GITHUB_REF_NAME" \ | |
| --generate-notes \ | |
| release/* | |
| - name: Deploy metadata to GitHub Pages | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| # Fetch existing gh-pages metadata if it exists | |
| EXISTING="" | |
| if git fetch origin gh-pages 2>/dev/null; then | |
| git show origin/gh-pages:ghcup-metadata.yaml > existing-metadata.yaml 2>/dev/null && EXISTING="existing-metadata.yaml" || true | |
| fi | |
| # Merge new version into existing metadata (cumulative channel) | |
| python3 << PYEOF | |
| import yaml, os | |
| existing_file = "$EXISTING" | |
| new_file = "release/ghcup-metadata.yaml" | |
| if existing_file and os.path.exists(existing_file): | |
| with open(existing_file) as fh: | |
| existing = yaml.safe_load(fh) or {} | |
| else: | |
| existing = {} | |
| with open(new_file) as fh: | |
| new = yaml.safe_load(fh) | |
| # Deep-merge: add new version entries into existing metadata | |
| if 'ghcupDownloads' not in existing: | |
| existing['ghcupDownloads'] = {} | |
| if 'plinth' not in existing['ghcupDownloads']: | |
| existing['ghcupDownloads']['plinth'] = {} | |
| existing.setdefault('toolRequirements', {}) | |
| for version, vinfo in new['ghcupDownloads']['plinth'].items(): | |
| if version not in existing['ghcupDownloads']['plinth']: | |
| existing['ghcupDownloads']['plinth'][version] = vinfo | |
| else: | |
| dest = existing['ghcupDownloads']['plinth'][version] | |
| if 'viArch' not in dest: | |
| dest['viArch'] = {} | |
| for arch, platforms in vinfo.get('viArch', {}).items(): | |
| if arch not in dest['viArch']: | |
| dest['viArch'][arch] = {} | |
| dest['viArch'][arch].update(platforms) | |
| with open('pages-metadata.yaml', 'w') as fh: | |
| yaml.dump(existing, fh, default_flow_style=False, sort_keys=False) | |
| PYEOF | |
| # Commit to gh-pages branch | |
| git checkout --orphan gh-pages-deploy | |
| git rm -rf . | |
| cp pages-metadata.yaml ghcup-metadata.yaml | |
| git add ghcup-metadata.yaml | |
| git commit -m "Update ghcup metadata for $GITHUB_REF_NAME" | |
| git push -f origin HEAD:gh-pages |