artifacts: - cover.out - bench.txt environment: APK_REPO: alpine/v3.22/bigbes/x86_64 ARTIFACTS_CHANNEL: ~bigbes/main ARTIFACTS_DIST: v3.22 ARTIFACTS_ENDPOINT: https://artifacts.srht.bigb.es BENCH_ORIGIN: https://bench.srht.bigb.es BENCH_REPO: ~bigbes/sr-ht-spec BOOTSTRAP_REV: 779ad9f174ea5ab7e755f6df0ec9e5912d67dd16 BUILD_SUBMITTER: git.sr.ht CORE_VER: 0.84.5 COVER_ORIGIN: https://cov.srht.bigb.es COVER_REPO: ~bigbes/sr-ht-spec GIT_REF: refs/heads/master REPO: sr-ht-spec S3_BUCKET: repo S3_ENDPOINT: https://s3.bigb.es image: alpine/edge packages: - abuild - curl - go - git - rclone - sassc - minify - postgresql - postgresql-client secrets: - apk-ci-s3 - 7dde4219-0783-4581-a67d-c94749de3600 - 0e5b3530-6f19-4f30-9b73-9339dd382e46 - c7968415-1a6d-4ca0-a188-150fb7f57b65 sources: - "https://git.srht.bigb.es/~bigbes/sr-ht-spec#64cae3af81d4b0039edc8ec3946bed36166a447b" submitter: git.sr.ht: allow-refs: - refs/heads/master - refs/tags/v* tasks: - 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-spec/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 apk pkgver: a tag, else tag_git, else # the family's commit count. The raw describe output is not a legal # pkgver and _git sorts AFTER the release: docs/ci.md#version. # # EXPORTED rather than sed-ed into the APKBUILD (which reads $PKGVER) # because rewriting a tracked file would flip the VCS stamp Go records in # the binary to dirty — do not "tidy" it back into a sed. The tree is # printed because this is the last moment it is provably clean. 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: | # Restore the Go module and build caches, both keyed by go.sum: the # dependency tree dominates compile time, and it only changes when go.sum # does. A miss is just a cold build, never an error. 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 # abuild redirects the Go caches into its throwaway $tmpdir (and an # upstream typo slaves GOMODCACHE to GOCACHE), so env exports here can't # stick — the APKBUILD's build() re-pins both to these home locations. # --optional makes a miss a cold build, not an error. 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`: the `all` pattern # resolves the whole module graph, test dependencies of dependencies # included, and APPENDS their hashes to the tracked go.sum — 170 lines on # this tree, measured. A modified tracked file is a "-dirty" apk, which is # the failure this whole commit is about. docs/ci.md#cache_restore. go mod download go mod verify # And the tree is printed because the two lines above are the last thing # that touches it before abuild does. docs/ci.md#cache_restore. git status --porcelain - postgres: | # A real Postgres in the VM. Without it 67 tests of db/, service/ and # cmd/specsrht-migrate/ skip themselves and the build goes green having # exercised none of the persistence layer — the migration-agreement check # included. Every flag below is load-bearing: docs/ci.md#postgres. sudo install -d -o postgres -g postgres /run/postgresql /var/lib/postgresql/data sudo -u postgres initdb -D /var/lib/postgresql/data sudo -u postgres pg_ctl -D /var/lib/postgresql/data -l /tmp/pg.log -w start \ -o "-k /run/postgresql -h 127.0.0.1 \ -c fsync=off -c full_page_writes=off -c synchronous_commit=off" sudo -u postgres createuser -s "$(id -un)" sudo -u postgres createdb -O "$(id -un)" specsrht_test echo "export SPECSRHT_TEST_PG='postgresql://$(id -un)@127.0.0.1/specsrht_test?sslmode=disable'" \ >> ~/.buildenv - test: | cd "$REPO" # An empty DSN would skip every Postgres-backed suite and leave the build # green over untested code — and `options="!check"` in the APKBUILD means # this task is the only place the suites run at all. It also catches a # reordering of the two tasks. docs/ci.md#test. if [ -z "$SPECSRHT_TEST_PG" ]; then echo "SPECSRHT_TEST_PG is unset: the postgres task did not export it," >&2 echo "so every database suite would skip and this build would lie." >&2 exit 1 fi test -z "$(gofmt -l .)" || { gofmt -l .; echo "gofmt: files above need formatting" >&2; exit 1; } go vet ./... # `make cover`, not a bare `go test ./...`: the Makefile names the -timeout # and the coverage flags, and it is the suites `make test` runs, so the # profile is a by-product of the gate. docs/ci.md#test. make cover COVERPROFILE="$HOME/cover.out" - build: | cd "$REPO" # -d: makedepends come from `packages:`. The APKBUILD runs `make css` # before `make build` and asserts the result with `make check-css`. # 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 that was handed no secrets, not # a fallback: with ~/.apk-ci.env absent every earlier task has still run # and a signed apk is sitting in $HOME/packages. On a push the secret is # there and this publishes. 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. Old versions stay so a pinned deployment can # always be rebuilt — the same reason the upstream mirror is append-only. 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" - publish_artifacts: | # The same apk into the artifacts.sr.ht channel, beside the S3 copy above. # The two destinations are independent on purpose: S3 is the road phoebe # still walks, this one is the road being opened. Its failure is its own, # and it never rolls back the upload that already succeeded. # See docs/ci.md#publish_artifacts. if [ ! -r ~/.srht-token ]; then echo "no ~/.srht-token: nothing was published to artifacts.sr.ht" exit 0 fi count=$(find "$HOME/packages" -name '*.apk' | wc -l) test "$count" -gt 0 || { echo "no .apk under $HOME/packages" >&2; exit 1; } url="$ARTIFACTS_ENDPOINT/api/v1/pkg/$ARTIFACTS_CHANNEL/apk/$ARTIFACTS_DIST" # A `find | while read` would run the body in a subshell under ash and # lose $failed with it, so the loop reads a word list instead. failed= for file in $(find "$HOME/packages" -name '*.apk'); do set +x # the token must not reach the log code=$(curl -sS -o /tmp/artifacts.out -w '%{http_code}' -X PUT \ -H "Authorization: Bearer $(cat ~/.srht-token)" \ --data-binary "@$file" "$url") set -x case "$code" in 200|201) echo "published $(basename "$file") -> $code" ;; 409) # Same version, other bytes. abuild stamps mtimes into the archive, # so resubmitting one commit builds a byte-different apk under the # same pkgver; the published copy stands and the build stays green. echo "WARNING: $(basename "$file") already published with other bytes; kept the published copy" ;; *) echo "FAILED $(basename "$file") -> $code" >&2 cat /tmp/artifacts.out >&2 echo >&2 failed=1 ;; esac done test -z "$failed" || exit 1 echo "index: $ARTIFACTS_ENDPOINT/$ARTIFACTS_CHANNEL/apk/$ARTIFACTS_DIST/x86_64/APKINDEX.tar.gz" - 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 - coverage: | # Dogfooding: the profile the test task wrote, POSTed to this instance's # own cov.sr.ht. Before bench, whose run is minutes. docs/ci.md#coverage. cd "$REPO" # Missing or empty is a 400 about a body rather than about the build. test -s "$HOME/cover.out" || { echo "no ~/cover.out" >&2; exit 1; } if [ ! -r ~/.srht-token ]; then echo "no ~/.srht-token: no cov.sr.ht credentials in this build" echo "the profile is this build's cover.out artifact and is not lost" exit 0 fi # Both ref prefixes stripped (this builds tags too), key is the idempotency # key, no Content-Type (the service sniffs), set +x so the header stays out # of the log, --fail-with-body so a rejection is loud and readable. # docs/ci.md#the-two-requests. ref="${GIT_REF#refs/heads/}"; ref="${ref#refs/tags/}" url="$COVER_ORIGIN/api/v1/repos/$COVER_REPO/reports" url="$url?commit=$(git rev-parse HEAD)&ref=$ref&key=$JOB_ID&job_url=$JOB_URL" echo "uploading cover.out to $url" set +x curl -sS --fail-with-body -X POST \ -H "Authorization: Bearer $(cat ~/.srht-token)" \ --data-binary "@$HOME/cover.out" \ "$url" echo - bench: | # Dogfooding: this service's own benchmarks, to this instance's own # bench.sr.ht. Last and its own task on purpose, and this VM measures a # shape rather than a number. docs/ci.md#bench. cd "$REPO" # -s so the recipe is not echoed into the body, and a redirect and a cat # and NOT `| tee` — tee's exit status would let a failed run pass. make -s bench > "$HOME/bench.txt" cat "$HOME/bench.txt" # `go test -bench` matching nothing prints `ok` and exits 0, and an empty # body is valid benchfmt, so the names are checked. docs/ci.md#the-two-greps grep -q '^BenchmarkCompare' "$HOME/bench.txt" grep -q '^BenchmarkLinkPass' "$HOME/bench.txt" if [ ! -r ~/.srht-token ]; then echo "no ~/.srht-token: no bench.sr.ht credentials in this build" echo "the run is above and is this build's bench.txt artifact" exit 0 fi # The coverage request's shape, plus visibility= — which acts only on the # POST that creates $BENCH_REPO. ref="${GIT_REF#refs/heads/}"; ref="${ref#refs/tags/}" url="$BENCH_ORIGIN/api/v1/repos/$BENCH_REPO/runs" url="$url?commit=$(git rev-parse HEAD)&ref=$ref&key=$JOB_ID&job_url=$JOB_URL" url="$url&visibility=public" echo "uploading bench.txt to $url" set +x curl -sS --fail-with-body -X POST \ -H "Authorization: Bearer $(cat ~/.srht-token)" \ --data-binary "@$HOME/bench.txt" \ "$url" echo