name: Build JASP binary and publish to AUR # JASP publishes no Linux binary of its own, so this builds one on Arch and # ships it as jasp-desktop-bin. Because JASP links against the system R and Qt6, # a rebuild is needed both when JASP releases and when Arch bumps R or Qt6 — # check-upstream.py decides which. on: schedule: # 22:00 Danish time, all year: the build saturates the runner host for # hours, so it runs when nobody is using it. The TZ prefix keeps the start # at 22:00 across daylight saving time instead of drifting with UTC. - cron: 'TZ=Europe/Copenhagen 0 22 * * *' workflow_dispatch: inputs: jasp_tag: description: 'Specific JASP tag to build (blank = latest release)' required: false default: '' force: description: 'Build even if nothing changed' type: boolean default: false concurrency: group: jasp-build cancel-in-progress: false # Nothing leaves the runner unless the repo variable PUBLISH is 'true'. Without # it a run builds, verifies and generates the PKGBUILD, and leaves the tarball # in ${WORK_DIR}/out for inspection: a dry run that cannot touch the tarball # host, state.json or the AUR. env: AUR_MAINTAINER: ${{ vars.AUR_MAINTAINER }} TARBALL_BASE_URL: ${{ vars.TARBALL_BASE_URL }} WORK_DIR: /work jobs: check: runs-on: ubuntu-latest outputs: needs_build: ${{ steps.decide.outputs.needs_build }} jasp_tag: ${{ steps.decide.outputs.jasp_tag }} reasons: ${{ steps.decide.outputs.reasons }} steps: - uses: actions/checkout@v4 - name: Decide whether a rebuild is needed id: decide env: JASP_TAG: ${{ inputs.jasp_tag }} FORCE_BUILD: ${{ inputs.force }} run: python3 scripts/check-upstream.py build: needs: check if: needs.check.outputs.needs_build == 'true' runs-on: ubuntu-latest # The R module tree is several hundred packages compiled from source; the # full build runs for hours. The act_runner timeout and Gitea's # ENDLESS_TASK_TIMEOUT must both allow for this (see README). timeout-minutes: 900 # The build step's `docker run` talks to the runner's docker daemon, not to # this job container, so the two only share files through a Docker volume. # Without it the scripts prepared here never reach the build and the # tarball the build writes is invisible to the publish steps. Named volumes # rather than host paths: the runner does not expand ${{ vars }} in # container.volumes, and a host path would put runner internals in the repo. container: image: docker.gitea.com/runner-images:ubuntu-latest volumes: - jasp-build:/work steps: - uses: actions/checkout@v4 - name: Report plan run: | echo "Building JASP ${{ needs.check.outputs.jasp_tag }}" echo "Reasons: ${{ needs.check.outputs.reasons }}" - name: Prepare work directory run: | # WORK_DIR is a volume mount point, so empty it rather than removing it. sudo find "${WORK_DIR}" -mindepth 1 -delete sudo mkdir -p "${WORK_DIR}/out" sudo cp -a build packaging scripts "${WORK_DIR}/" df -h / - name: Build JASP in an Arch container env: GH_TOKEN: ${{ secrets.GH_TOKEN }} run: | docker rm -f jasp-build 2>/dev/null || true docker run --rm --name jasp-build \ -v jasp-build:/work \ -v jasp-cache:/cache \ -e JASP_TAG="${{ needs.check.outputs.jasp_tag }}" \ -e OUT_DIR=/work/out \ -e JOBS=5 \ -e GITHUB_PAT="${GH_TOKEN}" \ archlinux:base-devel \ bash /work/build/build-jasp.sh - name: Show build result run: | cat "${WORK_DIR}/out/build-info.json" ls -lh "${WORK_DIR}/out/" - name: Generate PKGBUILD run: | sudo cp "${WORK_DIR}/out/build-info.json" . python3 scripts/gen-pkgbuild.py \ --build-info build-info.json \ --template packaging/PKGBUILD.in \ --out PKGBUILD \ --base-url "${TARBALL_BASE_URL}" \ --maintainer "${AUR_MAINTAINER}" cat PKGBUILD # Must run before the AUR push: the PKGBUILD's sha256sum refers to this # file, so publishing the package before the tarball exists would leave # users with a 404. - name: Publish tarball to asger.weirsoe.dk if: vars.PUBLISH == 'true' env: TARBALL_SSH_KEY: ${{ secrets.TARBALL_SSH_KEY }} # Secrets, not variables: run logs of a public repo are public, and # Gitea prints variables in them but masks secrets. TARBALL_HOST: ${{ secrets.TARBALL_HOST }} TARBALL_PATH: ${{ secrets.TARBALL_PATH }} run: | ASSET="$(python3 -c 'import json;print(json.load(open("release-meta.json"))["tarball"])')" bash scripts/publish-tarball.sh "${WORK_DIR}/out/${ASSET}" - name: Verify the published URL actually serves the tarball if: vars.PUBLISH == 'true' run: | URL="$(python3 -c 'import json;print(json.load(open("release-meta.json"))["download_url"])')" WANT="$(python3 -c 'import json;print(json.load(open("release-meta.json"))["sha256"])')" echo "checking $URL" GOT="$(curl -fsSL --retry 3 "$URL" | sha256sum | awk '{print $1}')" if [ "$GOT" != "$WANT" ]; then echo "error: what the public URL serves does not match the PKGBUILD sha256" >&2 echo " want $WANT" >&2 echo " got $GOT" >&2 exit 1 fi echo "public URL serves the expected bytes" # The same PKGBUILD the AUR gets, next to the tarball, so the copy on # the tarball host always matches the AUR package. Written to a dotfile # first and renamed, so the host's index never sees a half-written file. # Cosmetic for the AUR release, so a failure here does not stop it. - name: Publish PKGBUILD next to the tarball if: vars.PUBLISH == 'true' continue-on-error: true env: TARBALL_SSH_KEY: ${{ secrets.TARBALL_SSH_KEY }} TARBALL_HOST: ${{ secrets.TARBALL_HOST }} TARBALL_PATH: ${{ secrets.TARBALL_PATH }} run: | mkdir -p ~/.ssh printf '%s\n' "$TARBALL_SSH_KEY" > ~/.ssh/pkgbuild_key chmod 600 ~/.ssh/pkgbuild_key dest="${TARBALL_PATH}/jasp-desktop-bin.PKGBUILD" ssh -i ~/.ssh/pkgbuild_key -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new \ "root@${TARBALL_HOST}" \ "cat > '${TARBALL_PATH}/.jasp-desktop-bin.PKGBUILD.part' && mv '${TARBALL_PATH}/.jasp-desktop-bin.PKGBUILD.part' '$dest' && chmod 644 '$dest'" \ < PKGBUILD echo "PKGBUILD published next to the tarball" # Only the PKGBUILD and metadata cross the job boundary; the tarball # itself already lives on the tarball host. - name: Hand PKGBUILD to the AUR job if: vars.PUBLISH == 'true' uses: actions/upload-artifact@v3 with: name: aur-package path: | PKGBUILD release-meta.json - name: Record built state if: vars.PUBLISH == 'true' env: STATE_PUSH_TOKEN: ${{ secrets.STATE_PUSH_TOKEN }} run: bash scripts/record-state.sh - name: Clean up build tree if: always() run: | sudo rm -rf "${WORK_DIR}/jasp-desktop" "${WORK_DIR}/build" "${WORK_DIR}/stage" || true df -h / aur: needs: [check, build] if: vars.PUBLISH == 'true' runs-on: ubuntu-latest container: image: archlinux:base-devel steps: - name: Install base tools run: pacman -Syu --noconfirm git openssh pacman-contrib nodejs sudo - uses: actions/checkout@v4 - uses: actions/download-artifact@v3 with: name: aur-package - name: Push to AUR env: AUR_SSH_PRIVATE_KEY: ${{ secrets.AUR_SSH_PRIVATE_KEY }} run: bash scripts/push-aur.sh PKGBUILD