Cut releases from CI and publish the ghcup channel from a branch #143
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] | |
| # Releases are cut from here: run the workflow with a release_version to build | |
| # and test that version, and tick `publish` to have the release job tag it and | |
| # publish it once everything has passed. See Note [ghcup release channel]. | |
| workflow_dispatch: | |
| inputs: | |
| release_version: | |
| description: >- | |
| Build as a release stamped with this version (e.g. 9.6.166.1) instead | |
| of the usual dated snapshot version. Leave empty for an ordinary build. | |
| required: false | |
| default: '' | |
| publish: | |
| description: >- | |
| Tag and publish that version once every build and test job has passed. | |
| Leave off to rehearse a release without publishing anything. | |
| type: boolean | |
| required: false | |
| default: false | |
| # Least-privilege GITHUB_TOKEN: jobs only check out code (contents: read). | |
| # Submodules use a dedicated SSH key and artifacts use the Actions runtime | |
| # token, so no write scopes are needed. | |
| permissions: | |
| contents: read | |
| env: | |
| # The version being released ("9.6.166.1", or "v9.6.166.1" -- the steps below | |
| # strip the "v"), empty on ordinary pushes and PRs. It is passed to | |
| # plinth-build.sh as RELEASE_VERSION, which stamps the compiler with exactly | |
| # that version instead of the usual <base>.<date>. See Note [Release | |
| # versioning] in plinth-build.sh. | |
| REQUESTED_VERSION: ${{ github.event.inputs.release_version || '' }} | |
| jobs: | |
| # Reject an unreleasable request in seconds instead of after a full matrix | |
| # build. The release job repeats none of this: it inherits the verdict by | |
| # depending on this job. See Note [ghcup release channel]. | |
| release-precheck: | |
| name: release-precheck | |
| if: ${{ inputs.publish }} | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| submodules: false | |
| - name: Check the requested release version | |
| run: | | |
| set -eu | |
| VERSION="${REQUESTED_VERSION#v}" | |
| if [ -z "$VERSION" ]; then | |
| echo "error: publish was requested without a release_version" | |
| exit 1 | |
| fi | |
| # The same rule plinth-build.sh applies when stamping the compiler, so a | |
| # version it would reject never reaches the build. See Note [Release | |
| # versioning] there. | |
| BASE_VERSION=$(sed -n 's/^AC_INIT(\[[^]]*\], *\[\([^]]*\)\].*/\1/p' configure.ac) | |
| case "$VERSION" in | |
| "$BASE_VERSION"|"$BASE_VERSION".*) ;; | |
| *) | |
| echo "error: $VERSION does not extend the version in configure.ac ($BASE_VERSION)" | |
| exit 1 | |
| ;; | |
| esac | |
| # Tags are created by the release job, so an existing one means this | |
| # version was already released. | |
| if git ls-remote --exit-code --tags origin "refs/tags/v$VERSION" >/dev/null 2>&1; then | |
| echo "error: v$VERSION is already released" | |
| exit 1 | |
| fi | |
| echo "releasing $VERSION from $GITHUB_REF_NAME ($GITHUB_SHA)" | |
| plinth-build-linux: | |
| name: plinth-build / ${{ matrix.os }} / ghc ${{ matrix.ghc }} | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-latest, ubuntu-24.04-arm] | |
| ghc: ['9.6.7'] | |
| steps: | |
| # checkout first: the local composite action (.github/actions/prepare) must | |
| # be on disk before the runner can resolve it. | |
| - uses: actions/checkout@v4 | |
| with: | |
| submodules: false | |
| - uses: ./.github/actions/prepare | |
| with: | |
| ghc-version: ${{ matrix.ghc }} | |
| # BINDIST=1 produces the fixed-up uplc-ghc bindist (+ .tar.xz archive) while | |
| # keeping the lean dev flavour. The plinth-test job consumes that archive. | |
| # RELEASE_VERSION is empty except on a release build, where it pins the | |
| # version; see Note [Release versioning] in plinth-build.sh. | |
| - name: Build uplc-ghc bindist | |
| run: RELEASE_VERSION="${REQUESTED_VERSION#v}" BINDIST=1 ./plinth-build.sh | |
| - name: Upload uplc-ghc bindist | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| # key by platform + GHC so multiple build matrix legs (or another job) | |
| # never collide on the artifact name within a single run. See the | |
| # matching download in the plinth-test job, which shares this matrix. | |
| name: plinth-bindist-${{ matrix.os }}-${{ matrix.ghc }} | |
| path: _build/bindist/*.tar.xz | |
| retention-days: 1 | |
| # the archive is already xz-compressed; don't re-zip it | |
| compression-level: 0 | |
| # plinth-test runs in its own job for a separate status check and timeout | |
| # budget. It depends on `plinth-build-linux` (it can't run in parallel: it | |
| # needs the built compiler), downloading the bindist archive instead of | |
| # rebuilding GHC. Its matrix mirrors the build so the artifact-name key | |
| # matches per leg. | |
| plinth-test-linux: | |
| name: plinth-test / ${{ matrix.os }} / ghc ${{ matrix.ghc }} | |
| needs: plinth-build-linux | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-latest, ubuntu-24.04-arm] | |
| ghc: ['9.6.7'] | |
| steps: | |
| # checkout first: the local composite action (.github/actions/prepare) must | |
| # be on disk before the runner can resolve it. The prepare action also | |
| # fetches submodules, which the test needs: plinth/test/cabal.project pulls | |
| # plutus-tx/plutus-core/plutus-ledger-api from the plutus submodule. | |
| - uses: actions/checkout@v4 | |
| with: | |
| submodules: false | |
| - uses: ./.github/actions/prepare | |
| with: | |
| ghc-version: ${{ matrix.ghc }} | |
| # A single Plinth plugin (Core -> PLC) compile of a makeLift-heavy module | |
| # (PlutusLedgerApi.V3.Contexts) spikes past the ~16 GB runner RAM and gets | |
| # OOM-killed even with JOBS=1. Add a 16 GB swapfile on the large /mnt disk | |
| # so the spike spills to swap and the compile completes. | |
| - name: Add 16 GB swap | |
| run: | | |
| sudo fallocate -l 16G /mnt/swapfile | |
| sudo chmod 600 /mnt/swapfile | |
| sudo mkswap /mnt/swapfile | |
| sudo swapon /mnt/swapfile | |
| swapon --show | |
| free -h | |
| - name: Download uplc-ghc bindist | |
| uses: actions/download-artifact@v4 | |
| with: | |
| # matches the plinth-build upload name; both jobs share the same matrix. | |
| name: plinth-bindist-${{ matrix.os }}-${{ matrix.ghc }} | |
| path: _build/artifact | |
| # Install the bindist with the usual configure + make install; the bindist's | |
| # wrappers/ dir (added by the BINDIST fixup) makes this produce bin/uplc-ghc. | |
| # Each step is guarded so a missing artifact or failed install fails loudly | |
| # here, not with a confusing error deep in the test. | |
| - name: Install uplc-ghc bindist | |
| run: | | |
| shopt -s nullglob | |
| archives=(_build/artifact/*.tar.xz) | |
| if [ ${#archives[@]} -ne 1 ]; then | |
| echo "error: expected exactly one bindist archive, found: ${archives[*]:-none}" | |
| exit 1 | |
| fi | |
| mkdir -p _build/bindist | |
| echo "extracting ${archives[0]}" | |
| tar --xz -xf "${archives[0]}" -C _build/bindist | |
| bindirs=(_build/bindist/ghc-*) | |
| if [ ${#bindirs[@]} -ne 1 ]; then | |
| echo "error: expected one extracted bindist dir, found: ${bindirs[*]:-none}" | |
| exit 1 | |
| fi | |
| ( cd "${bindirs[0]}" && ./configure --prefix="$GITHUB_WORKSPACE/_build/install" && make install ) | |
| uplc="$GITHUB_WORKSPACE/_build/install/bin/uplc-ghc" | |
| if [ ! -x "$uplc" ]; then | |
| echo "error: $uplc missing or not executable after make install" | |
| ls -l "$GITHUB_WORKSPACE/_build/install/bin" || true | |
| exit 1 | |
| fi | |
| rm -rf _build/artifact | |
| # Build packages serially (JOBS=1): compiling with the Plinth plugin | |
| # (Core -> PLC) is memory-heavy, and concurrent GHC processes OOM-kill the | |
| # runner (seen mid-compile on PlutusLedgerApi.V3). | |
| - name: Test uplc-ghc | |
| run: JOBS=1 GHC="$GITHUB_WORKSPACE/_build/install/bin/uplc-ghc" ./plinth-test.sh | |
| # musl (Alpine) build, mirroring plinth-build-linux. GitHub runners are glibc | |
| # and haskell-actions/setup only ships glibc GHC, so the toolchain + build run | |
| # inside an alpine:3.20 container via `docker run` (with a musl boot-GHC from | |
| # ghcup). The workspace is mounted in; host-level free-disk / submodule steps | |
| # still run on the runner first. `docker run` (not a job-level `container:`) | |
| # keeps those host steps working. | |
| plinth-build-linux-musl: | |
| name: plinth-build / ${{ matrix.os }} (musl) / ghc ${{ matrix.ghc }} | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-latest, ubuntu-24.04-arm] | |
| ghc: ['9.6.7'] | |
| steps: | |
| - name: Free disk space | |
| uses: jlumbroso/free-disk-space@main | |
| with: | |
| tool-cache: false | |
| android: true | |
| dotnet: true | |
| haskell: false | |
| large-packages: false | |
| docker-images: true | |
| swap-storage: true | |
| - uses: actions/checkout@v4 | |
| with: | |
| submodules: false | |
| # The runner's workspace disk (/) is small; /mnt has ~70 GB. Bind-mount it as | |
| # _build so the GHC build has room. docker -v is recursive, so this nested | |
| # mount is visible inside the container at /workspace/_build. | |
| - name: Build on the large /mnt disk | |
| run: | | |
| sudo mkdir -p /mnt/_build | |
| mkdir -p "$GITHUB_WORKSPACE/_build" | |
| sudo mount --bind /mnt/_build "$GITHUB_WORKSPACE/_build" | |
| sudo chown "$USER" "$GITHUB_WORKSPACE/_build" | |
| # The static plugin baked into uplc-ghc is built from the plutus submodule. | |
| - name: Init and update submodules | |
| run: | | |
| git submodule sync | |
| git submodule update --init --recursive --depth 1 --jobs 4 | |
| # BINDIST=1 produces the fixed-up uplc-ghc bindist (+ .tar.xz). plinth-build.sh | |
| # appends a -musl suffix to the archive name when run on musl libc. | |
| - name: Build uplc-ghc bindist (Alpine/musl) | |
| run: | | |
| docker run --rm \ | |
| -v "$GITHUB_WORKSPACE:/workspace" -w /workspace \ | |
| -e BINDIST=1 -e LANG=C.UTF-8 -e LC_ALL=C.UTF-8 \ | |
| -e RELEASE_VERSION="${REQUESTED_VERSION#v}" \ | |
| alpine:3.20 sh -c ' | |
| set -eux | |
| . ./.github/alpine-setup.sh | |
| ./plinth-build.sh | |
| ' | |
| - name: Upload uplc-ghc bindist (musl) | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: plinth-bindist-musl-${{ matrix.os }}-${{ matrix.ghc }} | |
| path: _build/bindist/*.tar.xz | |
| retention-days: 1 | |
| # the archive is already xz-compressed; don't re-zip it | |
| compression-level: 0 | |
| # musl test, mirroring plinth-test-linux: consume the musl bindist, install it | |
| # and run the test suite inside alpine:3.20. Depends on plinth-build-linux-musl. | |
| plinth-test-linux-musl: | |
| name: plinth-test / ${{ matrix.os }} (musl) / ghc ${{ matrix.ghc }} | |
| needs: plinth-build-linux-musl | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-latest, ubuntu-24.04-arm] | |
| ghc: ['9.6.7'] | |
| steps: | |
| - name: Free disk space | |
| uses: jlumbroso/free-disk-space@main | |
| with: | |
| tool-cache: false | |
| android: true | |
| dotnet: true | |
| haskell: false | |
| large-packages: false | |
| docker-images: true | |
| swap-storage: true | |
| - uses: actions/checkout@v4 | |
| with: | |
| submodules: false | |
| # plinth/test/cabal.project pulls plutus-tx/plutus-core/plutus-ledger-api | |
| # from the plutus submodule. | |
| - name: Init and update submodules | |
| run: | | |
| git submodule sync | |
| git submodule update --init --recursive --depth 1 --jobs 4 | |
| # A single Plinth plugin compile of a makeLift-heavy module | |
| # (PlutusLedgerApi.V3.Contexts) spikes past the ~16 GB runner RAM and gets | |
| # OOM-killed even with JOBS=1. Add a 16 GB swapfile on the large /mnt disk | |
| # so the spike spills to swap; the container shares the host's memory+swap. | |
| - name: Add 16 GB swap | |
| run: | | |
| sudo fallocate -l 16G /mnt/swapfile | |
| sudo chmod 600 /mnt/swapfile | |
| sudo mkswap /mnt/swapfile | |
| sudo swapon /mnt/swapfile | |
| swapon --show | |
| free -h | |
| - name: Download uplc-ghc bindist (musl) | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: plinth-bindist-musl-${{ matrix.os }}-${{ matrix.ghc }} | |
| path: _build/artifact | |
| # Install the musl bindist (configure + make install) and run the test suite | |
| # inside Alpine. Mirrors the glibc test job's install guards (exactly one | |
| # archive, exactly one extracted dir, executable check) and JOBS=1. | |
| - name: Install and test uplc-ghc (Alpine/musl) | |
| run: | | |
| docker run --rm \ | |
| -v "$GITHUB_WORKSPACE:/workspace" -w /workspace \ | |
| -e LANG=C.UTF-8 -e LC_ALL=C.UTF-8 \ | |
| alpine:3.20 sh -c ' | |
| set -eux | |
| . ./.github/alpine-setup.sh | |
| archives=$(ls _build/artifact/*.tar.xz) | |
| n=$(echo "$archives" | wc -l) | |
| if [ "$n" -ne 1 ]; then | |
| echo "error: expected exactly one bindist archive, found: ${archives:-none}" | |
| exit 1 | |
| fi | |
| mkdir -p _build/bindist | |
| echo "extracting $archives" | |
| tar --xz -xf "$archives" -C _build/bindist | |
| bindirs=$(ls -d _build/bindist/ghc-*) | |
| if [ "$(echo "$bindirs" | wc -l)" -ne 1 ]; then | |
| echo "error: expected one extracted bindist dir, found: ${bindirs:-none}" | |
| exit 1 | |
| fi | |
| ( cd "$bindirs" && ./configure --prefix=/workspace/_build/install && make install ) | |
| uplc=/workspace/_build/install/bin/uplc-ghc | |
| if [ ! -x "$uplc" ]; then | |
| echo "error: $uplc missing or not executable after make install" | |
| ls -l /workspace/_build/install/bin || true | |
| exit 1 | |
| fi | |
| rm -rf _build/artifact | |
| JOBS=1 GHC="$uplc" ./plinth-test.sh | |
| ' | |
| plinth-build-windows: | |
| name: plinth-build / windows-latest / ghc ${{ matrix.ghc }} | |
| runs-on: windows-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| ghc: ['9.6.7'] | |
| defaults: | |
| run: | |
| shell: msys2 {0} | |
| steps: | |
| # Git on Windows defaults to not creating symlinks; the submodules and | |
| # bindist rely on them, so enable symlink support before checkout. | |
| - name: Configure git for Windows | |
| shell: bash | |
| run: git config --global core.symlinks true | |
| - uses: actions/checkout@v4 | |
| with: | |
| submodules: false | |
| # The pre-MSYS2 step runs in the default Git-for-Windows bash because the | |
| # MSYS2 environment is not set up yet. All submodules are public HTTPS, so | |
| # no credentials are needed. | |
| - name: Init and update submodules | |
| shell: bash | |
| run: | | |
| git submodule sync | |
| # Shallow (single-commit) submodule clones: the build never needs | |
| # submodule history, and these 29 submodules (notably plutus) carry a | |
| # lot of it. --jobs fetches them in parallel. GitHub allows fetching | |
| # the pinned SHA directly, so depth 1 works even when it isn't a | |
| # branch tip. | |
| git submodule update --init --recursive --depth 1 --jobs 4 | |
| # MINGW64 toolchain + the autotools/python/sphinx needed by the build. | |
| # path-type: inherit keeps the Windows PATH (incl. the GHC/cabal installed | |
| # below) visible inside the MSYS2 shell. | |
| - name: Setup MSYS2 | |
| 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 mingw-w64-x86_64-python-sphinx | |
| - uses: haskell-actions/setup@v2 | |
| id: setup | |
| name: Setup Haskell tools | |
| with: | |
| ghc-version: ${{ matrix.ghc }} | |
| cabal-version: "latest" | |
| cabal-update: true | |
| # BINDIST=1 produces the fixed-up uplc-ghc bindist (+ .tar.xz archive); the | |
| # plinth-test-windows job consumes that archive instead of rebuilding. | |
| - name: Build uplc-ghc bindist | |
| run: RELEASE_VERSION="${REQUESTED_VERSION#v}" BINDIST=1 ./plinth-build.sh | |
| - name: Upload uplc-ghc bindist | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| # key by GHC so this never collides with another job's artifact within a | |
| # run. See the matching download in the plinth-test-windows job. | |
| name: plinth-bindist-windows-${{ matrix.ghc }} | |
| path: _build/bindist/*.tar.xz | |
| retention-days: 1 | |
| # the archive is already xz-compressed; don't re-zip it | |
| compression-level: 0 | |
| # plinth-test on Windows, mirroring plinth-test-linux: consume the bindist | |
| # produced by plinth-build-windows, install it (configure + make install under | |
| # MSYS2) and run the test suite. Depends on plinth-build-windows. | |
| plinth-test-windows: | |
| name: plinth-test / windows-latest / ghc ${{ matrix.ghc }} | |
| needs: plinth-build-windows | |
| runs-on: windows-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| ghc: ['9.6.7'] | |
| defaults: | |
| run: | |
| shell: msys2 {0} | |
| steps: | |
| # Git on Windows defaults to not creating symlinks; the submodules and the | |
| # bindist's wrappers/ dir rely on them, so enable symlink support first. | |
| - name: Configure git for Windows | |
| shell: bash | |
| run: git config --global core.symlinks true | |
| - uses: actions/checkout@v4 | |
| with: | |
| submodules: false | |
| # The pre-MSYS2 step runs in the default Git-for-Windows bash because the | |
| # MSYS2 environment is not set up yet. plinth/test/cabal.project pulls | |
| # plutus-tx/plutus-core/plutus-ledger-api from the plutus submodule. | |
| - name: Init and update submodules | |
| shell: bash | |
| run: | | |
| git submodule sync | |
| # Shallow (single-commit) submodule clones: the build never needs | |
| # submodule history. --jobs fetches them in parallel. | |
| git submodule update --init --recursive --depth 1 --jobs 4 | |
| # MINGW64 toolchain incl. tar/xz to extract the bindist. path-type: inherit | |
| # keeps the Windows PATH (incl. the cabal installed below) visible. | |
| - name: Setup MSYS2 | |
| 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 mingw-w64-x86_64-python-sphinx | |
| # plinth-test.sh invokes cabal, so cabal must be on PATH. (The boot GHC this | |
| # also installs is unused by the test.) | |
| - uses: haskell-actions/setup@v2 | |
| id: setup | |
| name: Setup Haskell tools | |
| with: | |
| ghc-version: ${{ matrix.ghc }} | |
| cabal-version: "latest" | |
| cabal-update: true | |
| - name: Download uplc-ghc bindist | |
| uses: actions/download-artifact@v4 | |
| with: | |
| # matches the plinth-build-windows upload name. | |
| name: plinth-bindist-windows-${{ matrix.ghc }} | |
| path: _build/artifact | |
| # Unlike Unix, the Windows GHC bindist has no configure/make install: it is | |
| # relocatable, so we just extract it and run bin/uplc-ghc.exe in place. | |
| - name: Extract uplc-ghc bindist | |
| run: | | |
| shopt -s nullglob | |
| archives=(_build/artifact/*.tar.xz) | |
| if [ ${#archives[@]} -ne 1 ]; then | |
| echo "error: expected exactly one bindist archive, found: ${archives[*]:-none}" | |
| exit 1 | |
| fi | |
| mkdir -p _build/install | |
| echo "extracting ${archives[0]}" | |
| tar --xz -xf "${archives[0]}" -C _build/install | |
| bindirs=(_build/install/ghc-*) | |
| if [ ${#bindirs[@]} -ne 1 ]; then | |
| echo "error: expected one extracted bindist dir, found: ${bindirs[*]:-none}" | |
| exit 1 | |
| fi | |
| uplc="${bindirs[0]}/bin/uplc-ghc.exe" | |
| if [ ! -x "$uplc" ]; then | |
| echo "error: $uplc missing or not executable in extracted bindist" | |
| ls -l "${bindirs[0]}/bin" || true | |
| exit 1 | |
| fi | |
| rm -rf _build/artifact | |
| # Build packages serially (JOBS=1): compiling with the Plinth plugin | |
| # (Core -> PLC) is memory-heavy. Pass GHC as an absolute path: plinth-test.sh | |
| # cd's into plinth/test before invoking cabal, so a repo-root-relative path | |
| # would be resolved against the wrong directory. | |
| - name: Test uplc-ghc | |
| run: | | |
| bindirs=(_build/install/ghc-*) | |
| JOBS=1 GHC="$(pwd)/${bindirs[0]}/bin/uplc-ghc.exe" ./plinth-test.sh | |
| # macOS build, mirroring plinth-build-linux. macOS uses the Unix bindist | |
| # (configure + make install), so the build is much like Linux; the | |
| # differences are: no /mnt large-disk mount (the runner's disk is large | |
| # enough for the lean flavour) and autoconf/automake come from Homebrew | |
| # rather than the base image. Runs on Apple Silicon (aarch64-darwin). | |
| plinth-build-macos: | |
| name: plinth-build / macos-15 / ghc ${{ matrix.ghc }} | |
| runs-on: macos-15 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| ghc: ['9.6.7'] | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| submodules: false | |
| # The static plugin baked into uplc-ghc is built from the plutus submodule. | |
| - name: Init and update submodules | |
| run: | | |
| git submodule sync | |
| # Shallow (single-commit) submodule clones: the build never needs | |
| # submodule history. --jobs fetches them in parallel. | |
| git submodule update --init --recursive --depth 1 --jobs 4 | |
| # The GHC source build's boot step runs autoreconf, which needs | |
| # autoconf/automake; they are not in the macOS runner image. The lean | |
| # BINDIST flavour builds with --docs=none, so no sphinx is needed. | |
| - name: Install autotools (macOS) | |
| run: brew install autoconf automake | |
| - uses: haskell-actions/setup@v2 | |
| id: setup | |
| name: Setup Haskell tools | |
| with: | |
| ghc-version: ${{ matrix.ghc }} | |
| cabal-version: "latest" | |
| cabal-update: true | |
| # BINDIST=1 produces the fixed-up uplc-ghc bindist (+ .tar.xz archive); the | |
| # plinth-test-macos job consumes that archive instead of rebuilding. | |
| - name: Build uplc-ghc bindist | |
| run: RELEASE_VERSION="${REQUESTED_VERSION#v}" BINDIST=1 ./plinth-build.sh | |
| - name: Upload uplc-ghc bindist | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| # key by GHC so this never collides with another job's artifact within a | |
| # run. See the matching download in the plinth-test-macos job. | |
| name: plinth-bindist-macos-${{ matrix.ghc }} | |
| path: _build/bindist/*.tar.xz | |
| retention-days: 1 | |
| # the archive is already xz-compressed; don't re-zip it | |
| compression-level: 0 | |
| # plinth-test on macOS, mirroring plinth-test-linux: consume the bindist | |
| # produced by plinth-build-macos, install it (configure + make install) and | |
| # run the test suite. Depends on plinth-build-macos. | |
| plinth-test-macos: | |
| name: plinth-test / macos-15 / ghc ${{ matrix.ghc }} | |
| needs: plinth-build-macos | |
| runs-on: macos-15 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| ghc: ['9.6.7'] | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| submodules: false | |
| # plinth/test/cabal.project pulls plutus-tx/plutus-core/plutus-ledger-api | |
| # from the plutus submodule. | |
| - name: Init and update submodules | |
| run: | | |
| git submodule sync | |
| git submodule update --init --recursive --depth 1 --jobs 4 | |
| # plinth-test.sh invokes cabal, so cabal must be on PATH. (The boot GHC this | |
| # also installs is unused by the test.) | |
| - uses: haskell-actions/setup@v2 | |
| id: setup | |
| name: Setup Haskell tools | |
| with: | |
| ghc-version: ${{ matrix.ghc }} | |
| cabal-version: "latest" | |
| cabal-update: true | |
| - name: Download uplc-ghc bindist | |
| uses: actions/download-artifact@v4 | |
| with: | |
| # matches the plinth-build-macos upload name. | |
| name: plinth-bindist-macos-${{ matrix.ghc }} | |
| path: _build/artifact | |
| # Install the bindist with the usual configure + make install; the bindist's | |
| # wrappers/ dir (added by the BINDIST fixup) makes this produce bin/uplc-ghc. | |
| # Each step is guarded so a missing artifact or failed install fails loudly | |
| # here, not with a confusing error deep in the test. | |
| - name: Install uplc-ghc bindist | |
| run: | | |
| shopt -s nullglob | |
| archives=(_build/artifact/*.tar.xz) | |
| if [ ${#archives[@]} -ne 1 ]; then | |
| echo "error: expected exactly one bindist archive, found: ${archives[*]:-none}" | |
| exit 1 | |
| fi | |
| mkdir -p _build/bindist | |
| echo "extracting ${archives[0]}" | |
| tar --xz -xf "${archives[0]}" -C _build/bindist | |
| bindirs=(_build/bindist/ghc-*) | |
| if [ ${#bindirs[@]} -ne 1 ]; then | |
| echo "error: expected one extracted bindist dir, found: ${bindirs[*]:-none}" | |
| exit 1 | |
| fi | |
| ( cd "${bindirs[0]}" && ./configure --prefix="$GITHUB_WORKSPACE/_build/install" && make install ) | |
| uplc="$GITHUB_WORKSPACE/_build/install/bin/uplc-ghc" | |
| if [ ! -x "$uplc" ]; then | |
| echo "error: $uplc missing or not executable after make install" | |
| ls -l "$GITHUB_WORKSPACE/_build/install/bin" || true | |
| exit 1 | |
| fi | |
| rm -rf _build/artifact | |
| # Build packages serially (JOBS=1): compiling with the Plinth plugin | |
| # (Core -> PLC) is memory-heavy. A single makeLift-heavy compile | |
| # (PlutusLedgerApi.V3.Contexts) spikes well past the runner's RAM; unlike | |
| # the Linux job (explicit swapfile) macOS grows its swap dynamically, so the | |
| # spike spills to swap on its own and no swap-setup step is needed here. | |
| - name: Test uplc-ghc | |
| run: JOBS=1 GHC="$GITHUB_WORKSPACE/_build/install/bin/uplc-ghc" ./plinth-test.sh | |
| # ghcup install tests: verify each platform's bindist installs cleanly as the | |
| # custom `plinth` ghcup tool -- the same path end users take -- on every push, | |
| # not just on tags. Each mirrors its plinth-test-* sibling but installs via | |
| # ghcup + generate-ghcup-metadata.py (file:// metadata) instead of a bare | |
| # configure+make, so a broken dlInstallSpec / generator / metadata schema is | |
| # caught in the PR that introduces it. plinth-ghcup-test.sh does the work; it | |
| # needs only the bindist tarball + python3 + a ghcup >= 0.2.1.0 (no GHC | |
| # rebuild, no submodules -- the sample it compiles is plain Haskell). | |
| plinth-ghcup-test-linux: | |
| name: plinth-ghcup-test / ${{ matrix.os }} / ghc ${{ matrix.ghc }} | |
| needs: plinth-build-linux | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-latest, ubuntu-24.04-arm] | |
| ghc: ['9.6.7'] | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| submodules: false | |
| - name: Download uplc-ghc bindist | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: plinth-bindist-${{ matrix.os }}-${{ matrix.ghc }} | |
| path: _build/artifact | |
| # A stock ghcup for this architecture; the installer DSL needs >= 0.2.1.0 | |
| # (plinth-ghcup-test.sh capability-gates on it). | |
| - name: Install stock ghcup | |
| run: | | |
| set -eux | |
| mkdir -p "$HOME/.local/bin" | |
| curl -fL "https://downloads.haskell.org/~ghcup/$(uname -m)-linux-ghcup" \ | |
| -o "$HOME/.local/bin/ghcup" | |
| chmod +x "$HOME/.local/bin/ghcup" | |
| echo "$HOME/.local/bin" >> "$GITHUB_PATH" | |
| - name: Install and test uplc-ghc via ghcup | |
| run: BINDIST_DIR=_build/artifact ./plinth-ghcup-test.sh | |
| plinth-ghcup-test-linux-musl: | |
| name: plinth-ghcup-test / ${{ matrix.os }} (musl) / ghc ${{ matrix.ghc }} | |
| needs: plinth-build-linux-musl | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-latest, ubuntu-24.04-arm] | |
| ghc: ['9.6.7'] | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| submodules: false | |
| - name: Download uplc-ghc bindist (musl) | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: plinth-bindist-musl-${{ matrix.os }}-${{ matrix.ghc }} | |
| path: _build/artifact | |
| # Run inside Alpine so the musl uplc-ghc can execute. Unlike the build/test | |
| # jobs we do NOT source alpine-setup.sh: that bootstraps a full boot GHC + | |
| # cabal we would never use here. Instead install just the deps needed to run | |
| # uplc-ghc and link the sample, and download the ghcup binary directly -- | |
| # the same x86_64/aarch64-linux ghcup the official installer uses on Alpine. | |
| # | |
| # coreutils/findutils/grep are the GNU tools the bindist's `make install` | |
| # (which ghcup runs for us) is exercised with upstream; busybox's applets | |
| # differ in flag support, so don't rely on them for that path. | |
| # | |
| # EXPECT_MUSL=1 asserts the artifact really is the musl bindist: the ghcup | |
| # platform is derived from the bindist name, and a musl bindist mislabelled | |
| # as glibc installs fine here while being broken for users on Alpine. | |
| - name: Install and test uplc-ghc via ghcup (Alpine/musl) | |
| run: | | |
| docker run --rm \ | |
| -v "$GITHUB_WORKSPACE:/workspace" -w /workspace \ | |
| -e LANG=C.UTF-8 -e LC_ALL=C.UTF-8 \ | |
| alpine:3.20 sh -c ' | |
| set -eux | |
| apk add --no-cache bash build-base coreutils curl findutils grep \ | |
| gmp-dev ncurses-dev python3 tar xz | |
| mkdir -p "$HOME/.local/bin" | |
| curl -fL "https://downloads.haskell.org/~ghcup/$(uname -m)-linux-ghcup" \ | |
| -o "$HOME/.local/bin/ghcup" | |
| chmod +x "$HOME/.local/bin/ghcup" | |
| BINDIST_DIR=_build/artifact GHCUP_BIN="$HOME/.local/bin/ghcup" \ | |
| EXPECT_MUSL=1 bash ./plinth-ghcup-test.sh | |
| ' | |
| plinth-ghcup-test-macos: | |
| name: plinth-ghcup-test / macos-15 / ghc ${{ matrix.ghc }} | |
| needs: plinth-build-macos | |
| runs-on: macos-15 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| ghc: ['9.6.7'] | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| submodules: false | |
| - name: Download uplc-ghc bindist | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: plinth-bindist-macos-${{ matrix.ghc }} | |
| path: _build/artifact | |
| # macos-15 is Apple Silicon (aarch64-apple-darwin). | |
| - name: Install stock ghcup | |
| run: | | |
| set -eux | |
| mkdir -p "$HOME/.local/bin" | |
| curl -fL "https://downloads.haskell.org/~ghcup/aarch64-apple-darwin-ghcup" \ | |
| -o "$HOME/.local/bin/ghcup" | |
| chmod +x "$HOME/.local/bin/ghcup" | |
| echo "$HOME/.local/bin" >> "$GITHUB_PATH" | |
| - name: Install and test uplc-ghc via ghcup | |
| run: BINDIST_DIR=_build/artifact ./plinth-ghcup-test.sh | |
| plinth-ghcup-test-windows: | |
| name: plinth-ghcup-test / windows-latest / ghc ${{ matrix.ghc }} | |
| needs: plinth-build-windows | |
| runs-on: windows-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| ghc: ['9.6.7'] | |
| defaults: | |
| run: | |
| shell: msys2 {0} | |
| steps: | |
| # The bindist / ghcup use symlinks; enable them before checkout. | |
| - name: Configure git for Windows | |
| shell: bash | |
| run: git config --global core.symlinks true | |
| - uses: actions/checkout@v4 | |
| with: | |
| submodules: false | |
| # python3 (for generate-ghcup-metadata.py) + tar/xz/curl to fetch ghcup and | |
| # unpack. cygpath (used by plinth-ghcup-test.sh for native/MSYS path | |
| # translation) is part of the base MSYS2 install. | |
| - name: Setup MSYS2 | |
| uses: msys2/setup-msys2@v2 | |
| with: | |
| msystem: MINGW64 | |
| path-type: inherit | |
| update: false | |
| install: >- | |
| tar xz curl mingw-w64-x86_64-python | |
| - name: Download uplc-ghc bindist | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: plinth-bindist-windows-${{ matrix.ghc }} | |
| path: _build/artifact | |
| - name: Install stock ghcup | |
| run: | | |
| set -eux | |
| mkdir -p "$HOME/.local/bin" | |
| curl -fL "https://downloads.haskell.org/~ghcup/x86_64-mingw64-ghcup.exe" \ | |
| -o "$HOME/.local/bin/ghcup.exe" | |
| - name: Install and test uplc-ghc via ghcup | |
| run: BINDIST_DIR=_build/artifact GHCUP_BIN="$HOME/.local/bin/ghcup.exe" ./plinth-ghcup-test.sh | |
| # Note [ghcup release channel] | |
| # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
| # Once every build and test job has passed, tag the revision, publish the | |
| # per-platform bindists as a GitHub Release and (re)generate the ghcup channel | |
| # metadata on the ghcup-channel branch (see Note [Tagging a release] below). | |
| # uplc-ghc is distributed as a custom third-party ghcup tool named `plinth`, | |
| # installable with a stock ghcup >= 0.2.1.0 (installer DSL). Users then run: | |
| # ghcup config add-release-channel \ | |
| # https://raw.githubusercontent.com/input-output-hk/ghc-plinth/ghcup-channel/ghcup-plinth.yaml | |
| # ghcup install plinth <version> | |
| # | |
| # The channel lives on its own orphan branch, served straight from | |
| # raw.githubusercontent.com (which is also where ghcup fetches its own | |
| # metadata). Publishing is then a plain git push, and the branch is CI's alone, | |
| # so a release can never conflict with anything else being pushed. | |
| # raw.githubusercontent.com caches for 5 minutes (max-age=300), so a fresh | |
| # release can take that long to become visible to ghcup. | |
| # | |
| # The generator (generate-ghcup-metadata.py) accumulates every released version | |
| # into ghcup-plinth.versions.json on that branch and re-emits the channel YAML, | |
| # so old versions stay installable. Before publishing, we validate the metadata | |
| # end-to-end by installing plinth from a file:// copy of it (real download + | |
| # configure + make + symlink), decoupled from the not-yet-public release URLs. | |
| release: | |
| name: release / ghcup channel | |
| if: ${{ inputs.publish }} | |
| # Gate publishing on everything that exercises the bindists: the ghcup | |
| # install tests and the Plinth test suites, on every platform. Both sets | |
| # transitively require the build jobs and their uploaded artifacts, so the | |
| # builds don't need listing here. Without the plinth-test-* jobs a release | |
| # could ship a compiler whose test suite failed. | |
| needs: | |
| - release-precheck | |
| - plinth-ghcup-test-linux | |
| - plinth-ghcup-test-linux-musl | |
| - plinth-ghcup-test-windows | |
| - plinth-ghcup-test-macos | |
| - plinth-test-linux | |
| - plinth-test-linux-musl | |
| - plinth-test-windows | |
| - plinth-test-macos | |
| runs-on: ubuntu-latest | |
| # Overrides the top-level `contents: read`: this job creates the tag and the | |
| # Release, and pushes the regenerated channel to the ghcup-channel branch. | |
| permissions: | |
| contents: write | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| submodules: false | |
| # Collect every platform's bindist tarball into dist/. Each artifact holds a | |
| # single ghc-<ver>-<platform>[-musl].tar.xz with a distinct name, so | |
| # merge-multiple flattens them without collisions. | |
| - name: Download all bindists | |
| uses: actions/download-artifact@v4 | |
| with: | |
| pattern: plinth-bindist-* | |
| path: dist | |
| merge-multiple: true | |
| - name: List bindists and set version | |
| run: | | |
| set -eux | |
| ls -l dist/*.tar.xz | |
| # The version released, and the ghcup tool version. The build jobs | |
| # stamped the compiler with the same string (RELEASE_VERSION), so every | |
| # tarball must be named for it; if not, the channel would advertise a | |
| # version no bindist provides. See Note [Release versioning] in | |
| # plinth-build.sh. | |
| VERSION="${REQUESTED_VERSION#v}" | |
| for f in dist/*.tar.xz; do | |
| case "$(basename "$f")" in | |
| "ghc-$VERSION-"*) ;; | |
| *) echo "error: $f is not a bindist for version $VERSION"; exit 1 ;; | |
| esac | |
| done | |
| echo "VERSION=$VERSION" >> "$GITHUB_ENV" | |
| echo "TAG=v$VERSION" >> "$GITHUB_ENV" | |
| # The install is already validated end-to-end on every platform by the | |
| # plinth-ghcup-test-* jobs this job `needs`, so no re-validation here. | |
| # Note [Tagging a release] | |
| # ~~~~~~~~~~~~~~~~~~~~~~~~ | |
| # The tag is created here, at the end, rather than being what starts the | |
| # release: everything above has already built and tested this exact commit, | |
| # so a v* tag exists only for a revision that passed. A failed or abandoned | |
| # release attempt leaves no trace in the tag list, and the checked-out commit | |
| # is $GITHUB_SHA -- the revision the run was dispatched on. | |
| - name: Tag the release | |
| run: | | |
| set -eux | |
| git config user.name "github-actions[bot]" | |
| git config user.email "41898282+github-actions[bot]@users.noreply.github.com" | |
| git tag -a "$TAG" -m "uplc-ghc $VERSION" "$GITHUB_SHA" | |
| git push origin "$TAG" | |
| # Publish the Release with all tarballs. Create it if missing, otherwise | |
| # (re-)upload the assets. | |
| - name: Create GitHub Release | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| set -eux | |
| if ! gh release view "$TAG" >/dev/null 2>&1; then | |
| gh release create "$TAG" \ | |
| --title "uplc-ghc $VERSION" \ | |
| --notes "Plinth standalone compiler (uplc-ghc) bindists for $VERSION. Install via ghcup: see README." \ | |
| dist/*.tar.xz | |
| else | |
| gh release upload "$TAG" dist/*.tar.xz --clobber | |
| fi | |
| # Regenerate the channel with the real Release download URLs and push it to | |
| # the ghcup-channel branch, which is what users point ghcup at. The generator | |
| # merges into the existing versions DB so previously released versions remain | |
| # in the channel. See Note [ghcup release channel]. | |
| # | |
| # The branch holds nothing but the channel files, so it is built up in a | |
| # standalone repo rather than checked out: cloning it into a working tree of | |
| # this (very large) repo would mean deleting the whole GHC tree first. It is | |
| # created as an orphan on the first release, so no manual setup is needed. | |
| - name: Publish the ghcup channel | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| CHANNEL_BRANCH: ghcup-channel | |
| # no -x: the push URL carries the token (GitHub masks it, but don't print it) | |
| run: | | |
| set -eu | |
| mkdir channel-out | |
| ( | |
| cd channel-out | |
| git init -q -b "$CHANNEL_BRANCH" | |
| git config user.name "github-actions[bot]" | |
| git config user.email "41898282+github-actions[bot]@users.noreply.github.com" | |
| git remote add origin "https://x-access-token:${GH_TOKEN}@github.com/${GITHUB_REPOSITORY}" | |
| if git fetch -q --depth 1 origin "$CHANNEL_BRANCH" 2>/dev/null; then | |
| git reset -q --hard FETCH_HEAD | |
| else | |
| echo "first release: creating the $CHANNEL_BRANCH branch" | |
| fi | |
| ) | |
| python3 generate-ghcup-metadata.py \ | |
| --version "$VERSION" \ | |
| --base-url "https://github.com/${GITHUB_REPOSITORY}/releases/download/${TAG}" \ | |
| --db channel-out/ghcup-plinth.versions.json \ | |
| --output channel-out/ghcup-plinth.yaml \ | |
| --release-day "$(date -u +%Y-%m-%d)" \ | |
| --set-latest \ | |
| dist/*.tar.xz | |
| cat > channel-out/README.md <<EOF | |
| # ghcup channel for the Plinth compiler | |
| This branch only carries the ghcup metadata for \`uplc-ghc\`; it is not | |
| part of the source tree. Both files are generated by CI on a \`v*\` tag | |
| (see the \`release\` job in \`.github/workflows/ci.yml\`) -- do not edit | |
| them by hand. | |
| ghcup config add-release-channel https://raw.githubusercontent.com/${GITHUB_REPOSITORY}/${CHANNEL_BRANCH}/ghcup-plinth.yaml | |
| ghcup install plinth latest | |
| EOF | |
| cd channel-out | |
| git add -A | |
| git commit -q -m "ghcup channel: publish plinth ${VERSION} (${TAG})" \ | |
| || echo "no changes to commit" | |
| git push -q origin "HEAD:$CHANNEL_BRANCH" | |
| echo "published https://raw.githubusercontent.com/${GITHUB_REPOSITORY}/${CHANNEL_BRANCH}/ghcup-plinth.yaml" |