~bigbes/sr-ht-compare

ref: 7dfec322eb11c76af7eabd4318daeda9a52ae232 sr-ht-compare/.build.yml -rw-r--r-- 15.5 KiB
7dfec322 — bigbes ci: report this build's coverage and benchmarks 2 days ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
# 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, and upload this build's own coverage and
# benchmarks to cov.sr.ht and bench.sr.ht.
#
# 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
  # A tokens.sr.ht working token, the same secret the sibling services mount.
  # It must carry artifacts:upload (publish_artifacts), cov:upload (coverage)
  # and bench:upload (bench); missing one fails that upload and no other.
  # See docs/ci.md#secrets.
  - c7968415-1a6d-4ca0-a188-150fb7f57b65   # ~/.srht-token
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
  # The second destination of the same apk: one channel of artifacts.sr.ht
  # holds the packages of every sibling service, so a consumer adds one
  # repository line. See docs/ci.md#publish_artifacts.
  ARTIFACTS_ENDPOINT: https://artifacts.srht.bigb.es
  ARTIFACTS_CHANNEL: "~bigbes/main"
  ARTIFACTS_DIST: v3.22
  # 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
  # Where this build reports on itself. Both repository names are the one on the
  # `sources:` line above and not the service's: the daemon was renamed to
  # diff.sr.ht, the git repository was not. docs/ci.md#coverage, #bench.
  COVER_ORIGIN: https://cov.srht.bigb.es
  COVER_REPO: "~bigbes/sr-ht-compare"
  BENCH_ORIGIN: https://bench.srht.bigb.es
  BENCH_REPO: "~bigbes/sr-ht-compare"
# Literal paths relative to $HOME; `artifacts:` has no globbing, which is why
# the apk is not here. See docs/ci.md#artifacts.
artifacts:
  - cover.out
  - bench.txt
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 cover` and not a bare `go test ./...`: the test command lives in
      # one place, and `cover` is `test` with two flags in it. There is no DSN
      # guard and no service to reach — every suite here is hermetic — so
      # nothing can skip, and a failure fails this task. That is what the
      # APKBUILD's `!check` rests on. The profile goes to $HOME, not into the
      # checkout, which an untracked file would stamp "+dirty". docs/ci.md#test.
      make cover COVERPROFILE="$HOME/cover.out"
  - 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"
  - 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: |
      # This build's own coverage, to the instance's cov.sr.ht, and nothing
      # after it depends on it. docs/ci.md#coverage.
      cd "$REPO"
      if [ ! -r ~/.srht-token ]; then
        echo "no ~/.srht-token: nothing uploaded; the profile is this build's"
        echo "cover.out artifact"
        exit 0
      fi
      # GIT_REF is absent on a manual submission and ref is optional; key is the
      # idempotency key. BOTH prefixes are stripped — tags build too.
      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 so the header never reaches the log; no Content-Type (the service
      # sniffs); --fail-with-body prints the JSON error AND exits non-zero.
      # docs/ci.md#the-request.
      set +x
      curl -sS --fail-with-body -X POST \
        -H "Authorization: Bearer $(cat ~/.srht-token)" \
        --data-binary "@$HOME/cover.out" \
        "$url"
      echo
  - bench: |
      # This build's own benchmarks — the diffing path a request is spent in —
      # to bench.sr.ht. A VM this small measures a shape, not a number.
      # docs/ci.md#bench.
      cd "$REPO"
      # -s keeps the recipe out of the body; a redirect and a cat and NOT
      # `| tee`, which would hand the task tee's exit status and let a failing
      # benchmark pass. $HOME is where artifacts look.
      make -s bench > "$HOME/bench.txt"
      cat "$HOME/bench.txt"
      # `go test -bench` that matches NOTHING prints "ok" and exits 0, and an
      # empty file is valid benchfmt: without these a renamed benchmark uploads
      # nothing and reports success. Nothing here can skip. docs/ci.md#the-greps.
      grep -q '^BenchmarkDiff/' "$HOME/bench.txt"
      grep -q '^BenchmarkCommitPatch' "$HOME/bench.txt"
      grep -q '^BenchmarkCutPatch/' "$HOME/bench.txt"
      if [ ! -r ~/.srht-token ]; then
        echo "no ~/.srht-token: the benchmarks ran and are above; nothing was"
        echo "uploaded. The file is this build's bench.txt artifact."
        exit 0
      fi
      # visibility 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