# builds.sr.ht manifest for diff.sr.ht. One linear pipeline: install the
# cache helper, assemble the shared SCSS, restore caches, package with abuild,
# publish the apk, save the caches.
#
# The reasoning behind every task lives in docs/ci.md, not here: builds.sr.ht
# stores the submitted manifest in a varchar(16384), so a manifest over 16 KiB
# cannot be submitted at all — and the failure is a branch with no CI, not a red
# build. Add paragraphs to docs/ci.md and a pointer here.
image: alpine/edge
packages:
  - abuild
  - curl
  - go
  - git
  - rclone
  # For `make css`, not for the package — see docs/ci.md#packages.
  - sassc
  - minify
secrets:
  # File secret `apk-ci-s3`, installed at ~/.apk-ci.env, containing
  # APK_CI_S3_ACCESS_KEY / APK_CI_S3_SECRET_KEY for the Garage `repo` bucket.
  - apk-ci-s3
  # S3 credentials for the cacher CI cache (Garage `docker-cache` bucket),
  # same pair the bencher and ci-cacher builds use.
  - 7dde4219-0783-4581-a67d-c94749de3600   # ~/.s3-cache-key-id
  - 0e5b3530-6f19-4f30-9b73-9339dd382e46   # ~/.s3-cache-key-secret
sources:
  - https://git.srht.bigb.es/~bigbes/sr-ht-compare
environment:
  REPO: sr-ht-compare
  APK_REPO: alpine/v3.22/bigbes/x86_64
  S3_BUCKET: repo
  S3_ENDPOINT: https://s3.bigb.es
  # CORE_VER must track the deployment's SRHT_CORE_VER; BOOTSTRAP_REV is the
  # submodule commit core.sr.ht pins at that tag. See docs/ci.md#environment.
  CORE_VER: "0.84.5"
  BOOTSTRAP_REV: 779ad9f174ea5ab7e755f6df0ec9e5912d67dd16
submitter:
  git.sr.ht:
    allow-refs:
      - refs/heads/master
      - "refs/tags/v*"
tasks:
  # S3-backed CI cache helper (go.bigb.es/cacher), installed from its own
  # published release. Two tasks and not one: install.sh appends its PATH export
  # to ~/.buildenv, which only the NEXT task sources. The installer verifies
  # what it downloads against checksums.txt. See docs/ci.md#cacher.
  - cacher_install: |
      curl -fsSL https://bigbes.pages.srht.bigb.es/ci-cacher/install.sh | sh
  - cacher_init: |
      cacher init \
        --endpoint    https://s3.bigb.es \
        --region      garage \
        --bucket      docker-cache \
        --prefix      sr-ht-compare/deps \
        --key-file    ~/.s3-cache-key-id \
        --secret-file ~/.s3-cache-key-secret
  - scss: |
      # Assemble the shared sourcehut partials no apk ships, the way
      # core.sr.ht's `make install-scss` would, cached by the two pins so an
      # outage at git.sr.ht or github.com can't fail us. --exec runs on a miss
      # and seeds the cache after; it sees exported vars only, hence the inline
      # key and the single quotes. See docs/ci.md#scss.
      cacher dir download "scss/${CORE_VER}-${BOOTSTRAP_REV}.tar.zst" ~/scss --exec '
        git clone --depth 1 --branch "$CORE_VER" \
          https://git.sr.ht/~sircmpwn/core.sr.ht /tmp/core
        mkdir -p ~/scss/bootstrap
        cp /tmp/core/scss/*.scss /tmp/core/scss/*.css ~/scss/
        git init -q /tmp/bootstrap
        git -C /tmp/bootstrap remote add origin https://github.com/twbs/bootstrap
        git -C /tmp/bootstrap fetch -q --depth 1 origin "$BOOTSTRAP_REV"
        git -C /tmp/bootstrap checkout -q FETCH_HEAD
        cp -r /tmp/bootstrap/scss ~/scss/bootstrap/scss
      '
      sudo mkdir -p /usr/share/sourcehut
      sudo cp -r ~/scss /usr/share/sourcehut/scss
  - keygen: |
      # Throwaway signing key, and -i is not optional: docs/ci.md#keygen.
      SUDO=sudo abuild-keygen -a -n -i -q
  - version: |
      # ONE `git describe` decides the pkgver: a tag, else <tag>_git<n>, else the
      # family's 0.0.<commit count>. EXPORTED rather than sed-ed into the tracked
      # APKBUILD (which reads $PKGVER), because rewriting a tracked file flips
      # the VCS stamp Go records into every binary built afterwards to dirty —
      # do not "tidy" it back into a sed. The tree is printed because this is the
      # last moment it is provably clean. See docs/ci.md#version.
      cd "$REPO"
      desc=$(git describe --tags --always --dirty)
      base=${desc%-dirty}
      case "$base" in
        v*-g*) n=${base%-g*}; ver="${n%-*}"; ver="${ver#v}_git${n##*-}" ;;
        v*)    ver="${base#v}" ;;
        *)     ver="0.0.$(git rev-list --count HEAD)" ;;
      esac
      echo "export PKGVER=$ver" >> ~/.buildenv
      echo "building $ver from $desc"
      git status --porcelain
  - cache_restore: |
      # Go module and build caches keyed by go.sum; --optional makes a genuine
      # miss a cold build rather than an error, which `|| true` could not tell
      # apart from bad credentials. abuild re-pins both, so the exports here are
      # only for the repair block below. See docs/ci.md#cache_restore.
      KEY_MOD=$(cacher key "gomod/{hash}.tar.zst" --hash-from "$REPO/go.sum")
      KEY_GOC=$(cacher key "gocache/{hash}.tar.zst" --hash-from "$REPO/go.sum")
      echo "export KEY_MOD=$KEY_MOD KEY_GOC=$KEY_GOC" >> ~/.buildenv
      cacher dir download "$KEY_MOD" ~/go/pkg/mod        --optional
      cacher dir download "$KEY_GOC" ~/.cache/go-build   --optional
      # Repair block for the HALF-restored module cache — the normal failure
      # here, not a freak one, and it reads like a code bug. Do not remove and
      # do not soften to `|| true`:
      # docs/ci.md#the-half-restored-module-cache.
      cd "$REPO"
      chmod -R u+w ~/go/pkg/mod 2>/dev/null || true
      if ! go mod verify >/dev/null 2>&1; then
        echo "restored module cache did not verify — discarding it"
        rm -rf ~/go/pkg/mod
      fi
      # `go mod download`, and NOT `go mod download all` — do not add the `all`
      # back as an optimization. It resolves the whole module graph, including
      # test dependencies of dependencies, and APPENDS their hashes to go.sum:
      # 145 lines in this repository, 228 in the bench sibling, whose build #359
      # packaged a "-dirty" binary because of it. A modified tracked file in the
      # checkout at `go build` time is the exact stamp this pipeline exists to
      # prevent. Without `all` go.sum is untouched and `go mod verify` still
      # passes. See docs/ci.md#not-go-mod-download-all.
      go mod download
      go mod verify
      # This print is NOT belt-and-braces. It is the ONLY thing here that
      # catches a go.sum rewrite, and no flag stands behind it. Do not replace
      # it with an appeal to -mod=readonly: readonly governs updates to the
      # module REQUIREMENTS, it does not stop writes to go.sum. Measured with
      # GOFLAGS=-mod=readonly explicitly on the command line — `go mod download
      # all` still appended the same 145 lines and still exited 0. A modified or
      # untracked file here is a "+dirty" stamp in the packaged binary.
      # docs/ci.md#not-go-mod-download-all.
      git status --porcelain
  - test: |
      cd "$REPO"
      # gofmt -l exits 0 whether or not it printed anything, so the only way to
      # fail on its output is to look at the output; tee keeps the filenames in
      # the log, where they are the whole diagnosis. See docs/ci.md#test.
      gofmt -l . | tee /tmp/fmt
      test ! -s /tmp/fmt || { echo "gofmt would change the files above" >&2; exit 1; }
      go vet ./...
      # `make test` and not a bare `go test ./...`: the test command lives in
      # one place. There is no DSN guard and no service to reach — every suite
      # here is hermetic (gitx builds a bare repo with the local git, web is
      # httptest + ecoretest) — so nothing can skip, and a failure is a non-zero
      # exit that fails this task. That is what the APKBUILD's `!check` now
      # rests on. docs/ci.md#test.
      make test
  - build: |
      cd "$REPO"
      # -d: makedepends come from `packages:` above, so skip abuild's own
      # dependency resolution. The APKBUILD runs `make css` before the compile
      # and `make check-css` after it, because web/ go:embed-s static/.
      # See docs/ci.md#build.
      REPODEST=$HOME/packages abuild -d
      find "$HOME/packages" -name '*.apk'
  - publish: |
      # The gate is the honest answer to a build handed no secrets, not a
      # fallback: without ~/.apk-ci.env every earlier task has still run and a
      # signed apk is sitting in $HOME/packages. See docs/ci.md#publish.
      if [ ! -r ~/.apk-ci.env ]; then
        echo "no ~/.apk-ci.env: this build has no apk repo credentials"
        echo "the package was built and signed, and is not published"
        exit 0
      fi
      set +x   # never echo the S3 credentials into the build log
      . ~/.apk-ci.env
      export RCLONE_CONFIG_GARAGE_TYPE=s3
      export RCLONE_CONFIG_GARAGE_PROVIDER=Other
      export RCLONE_CONFIG_GARAGE_ENDPOINT="$S3_ENDPOINT"
      export RCLONE_CONFIG_GARAGE_REGION=garage
      export RCLONE_CONFIG_GARAGE_FORCE_PATH_STYLE=true
      export RCLONE_CONFIG_GARAGE_ACCESS_KEY_ID="$APK_CI_S3_ACCESS_KEY"
      export RCLONE_CONFIG_GARAGE_SECRET_ACCESS_KEY="$APK_CI_S3_SECRET_KEY"
      set -x
      # Upload only, never delete; abuild nests output under
      # $REPODEST/<repo>/<arch>/, so flatten to a fixed prefix rather than
      # mirroring the tree. See docs/ci.md#publish.
      find "$HOME/packages" -name '*.apk' -print | while read -r f; do
        rclone copyto "$f" "garage:$S3_BUCKET/$APK_REPO/$(basename "$f")"
        echo "uploaded $(basename "$f")"
      done
      echo "published; apk-mirror on phoebe re-indexes within 15 minutes"
  - cache_save: |
      # AFTER publish so an S3 hiccup cannot strand a good apk, and fatal on
      # purpose. Without --force an upload skips a key already there, so no
      # `cacher exists ||` guard is needed. See docs/ci.md#cache_save.
      cacher dir upload "$KEY_MOD" ~/go/pkg/mod
      cacher dir upload "$KEY_GOC" ~/.cache/go-build
