ci: gates build the web bundle and the dist payload once, pass them between jobs
Gates / test (push) Successful in 1m37s
Gates / test-aarch64 (push) Successful in 6m46s
Gates / frontend (push) Successful in 1m6s
Gates / package (push) Successful in 4m4s
Gates / container (push) Successful in 15s
CI / gates (push) Successful in 13m54s
Gates / test (push) Successful in 1m37s
Gates / test-aarch64 (push) Successful in 6m46s
Gates / frontend (push) Successful in 1m6s
Gates / package (push) Successful in 4m4s
Gates / container (push) Successful in 15s
CI / gates (push) Successful in 13m54s
This commit is contained in:
+96
-52
@@ -14,9 +14,28 @@ name: Gates
|
|||||||
# token. A moved tag upstream would execute there. ci.yml itself still uses no
|
# token. A moved tag upstream would execute there. ci.yml itself still uses no
|
||||||
# third-party action; its only `uses:` is this workflow.
|
# third-party action; its only `uses:` is this workflow.
|
||||||
#
|
#
|
||||||
# Resolved with `git ls-remote <repo> refs/tags/vN 'refs/tags/vN^{}'` on
|
# Resolved with `git ls-remote <repo> refs/tags/vN 'refs/tags/vN^{}'` — the
|
||||||
# 2026-08-07. All three are lightweight tags — no `^{}` peel row — so the
|
# three originals on 2026-08-07, the two artifact actions on 2026-08-12. Every
|
||||||
# listed SHA is the commit.
|
# tag pinned here is lightweight — no `^{}` peel row — so the listed SHA is the
|
||||||
|
# commit. download-artifact's `v3-node20` is the exception that proves the
|
||||||
|
# rule: it is annotated, and its peel row names a different commit than the tag
|
||||||
|
# object. This file pins `v3.1.0-node20`, which is lightweight.
|
||||||
|
#
|
||||||
|
# The artifact actions are pinned to v3, and to the node20 rebuilds of v3, for
|
||||||
|
# two separate reasons.
|
||||||
|
#
|
||||||
|
# v4 and v5 abort in the *client*, not the server. @actions/artifact asks
|
||||||
|
# isGhes(), Gitea answers the way GitHub Enterprise Server does, and the action
|
||||||
|
# refuses before it uploads anything. The server side is present: Gitea 1.27.1
|
||||||
|
# routes the whole v4 twirp service — CreateArtifact, FinalizeArtifact,
|
||||||
|
# ListArtifacts, GetSignedArtifactURL and DeleteArtifact all answer 401, where
|
||||||
|
# an unrouted path answers 404. The forks that patch isGhes() out are
|
||||||
|
# third-party code, which is what the pinning rule above exists to keep away
|
||||||
|
# from the job that holds the signing subkey.
|
||||||
|
#
|
||||||
|
# The stock v3 tags — upload ff15f030 (v3.2.1), download 9bc31d5c (v3.0.2) —
|
||||||
|
# declare `using: node16`, and a runner is not required to still ship a node16.
|
||||||
|
# The `-node20` rebuilds are that same action code on a runtime that exists.
|
||||||
on:
|
on:
|
||||||
workflow_call:
|
workflow_call:
|
||||||
|
|
||||||
@@ -130,7 +149,30 @@ jobs:
|
|||||||
working-directory: web
|
working-directory: web
|
||||||
run: npm run assert-bundled
|
run: npm run assert-bundled
|
||||||
|
|
||||||
|
# The package and container jobs consume this bundle instead of building
|
||||||
|
# their own. The upload is last on purpose: a bundle that failed the
|
||||||
|
# licence inventory or any check above must not reach the jobs that
|
||||||
|
# package it into a tarball and an image.
|
||||||
|
#
|
||||||
|
# Artifact names are scoped to the run, so the ci.yml and release.yml
|
||||||
|
# runs that share this runner cannot read each other's.
|
||||||
|
#
|
||||||
|
# A later move to v4 has to add `include-hidden-files: true` here.
|
||||||
|
# `npm run build` writes the freshness stamp to the hidden file
|
||||||
|
# web/dist/.src-hash (milestone-15 ruling 5), and v4.4.0 and later drop
|
||||||
|
# dotfiles by default. It is inactive today — v3 keeps them, and the
|
||||||
|
# package job's target path skips the stamp check regardless — but a move
|
||||||
|
# to v4 that also pointed the download back at web/dist would fail with
|
||||||
|
# "web/dist is stale".
|
||||||
|
- name: Upload the built web UI
|
||||||
|
uses: actions/upload-artifact@c24449f33cd45d4826c6702db7e49f7cdb9b551d # v3.2.1-node20
|
||||||
|
with:
|
||||||
|
name: web-dist
|
||||||
|
path: web/dist
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
package:
|
package:
|
||||||
|
needs: [frontend]
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@@ -149,20 +191,22 @@ jobs:
|
|||||||
- name: Create the fetch temp dir zig assumes
|
- name: Create the fetch temp dir zig assumes
|
||||||
run: mkdir -p "${ZIG_GLOBAL_CACHE_DIR:?}/tmp"
|
run: mkdir -p "${ZIG_GLOBAL_CACHE_DIR:?}/tmp"
|
||||||
|
|
||||||
- name: Set up Node
|
# `dist` refuses web/dist-placeholder (ruling 4), so a real bundle has to
|
||||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
# exist before the packaging gate runs. It arrives from the frontend job,
|
||||||
|
# already formatted, linted, typechecked, tested and licence-checked.
|
||||||
|
#
|
||||||
|
# The target is deliberately not `web/dist`: build.zig runs the freshness
|
||||||
|
# stamp check for that exact path and no other (milestone-15 ruling 5),
|
||||||
|
# and the check shells out to `node`. Here it would buy nothing — the
|
||||||
|
# stamp hashes the web/ sources, not the bundle, so against a checkout of
|
||||||
|
# the same commit that built the bundle it can only agree. An explicit
|
||||||
|
# path is the case build.zig documents for a bundle built elsewhere, and
|
||||||
|
# taking it keeps node out of this job entirely.
|
||||||
|
- name: Download the web UI built by the frontend job
|
||||||
|
uses: actions/download-artifact@ad191675b41f6a5b46da9a048cb6893812da158b # v3.1.0-node20
|
||||||
with:
|
with:
|
||||||
node-version: ${{ env.NODE_VERSION }}
|
name: web-dist
|
||||||
cache: npm
|
path: web-dist-ci
|
||||||
cache-dependency-path: web/package-lock.json
|
|
||||||
|
|
||||||
# `dist` refuses web/dist-placeholder (ruling 4), so the real bundle has
|
|
||||||
# to exist before the packaging gate runs.
|
|
||||||
- name: Build the web UI
|
|
||||||
working-directory: web
|
|
||||||
run: |
|
|
||||||
npm ci
|
|
||||||
npm run build
|
|
||||||
|
|
||||||
# Ruling 2: build.zig.zon is the only place besides the tag that carries
|
# Ruling 2: build.zig.zon is the only place besides the tag that carries
|
||||||
# the version, and ruling 5 makes verify-dist assert the two agree. The
|
# the version, and ruling 5 makes verify-dist assert the two agree. The
|
||||||
@@ -180,7 +224,7 @@ jobs:
|
|||||||
zig build dist \
|
zig build dist \
|
||||||
-Dversion-string="$CI_VERSION" \
|
-Dversion-string="$CI_VERSION" \
|
||||||
-Dgit-commit="$GITHUB_SHA" \
|
-Dgit-commit="$GITHUB_SHA" \
|
||||||
-Dweb-dist=web/dist \
|
-Dweb-dist=web-dist-ci \
|
||||||
-Doptimize=ReleaseSafe
|
-Doptimize=ReleaseSafe
|
||||||
|
|
||||||
# verify-dist owns every assert the CI shell used to make: ELF static
|
# verify-dist owns every assert the CI shell used to make: ELF static
|
||||||
@@ -196,42 +240,51 @@ jobs:
|
|||||||
zig build verify-dist \
|
zig build verify-dist \
|
||||||
-Dversion-string="$CI_VERSION" \
|
-Dversion-string="$CI_VERSION" \
|
||||||
-Dgit-commit="$GITHUB_SHA" \
|
-Dgit-commit="$GITHUB_SHA" \
|
||||||
-Dweb-dist=web/dist \
|
-Dweb-dist=web-dist-ci \
|
||||||
-Doptimize=ReleaseSafe
|
-Doptimize=ReleaseSafe
|
||||||
|
|
||||||
|
# deploy/docker/Dockerfile copies both of these trees and nothing else
|
||||||
|
# out of zig-out/dist: the binary comes from dist/bin/<triple>/, and
|
||||||
|
# /LICENSE and /THIRD-PARTY-NOTICES come from the matching dist/stage/
|
||||||
|
# directory, which the container job then hashes against the image. The
|
||||||
|
# tarballs and SHA256SUMS stay behind — no later job reads them, and
|
||||||
|
# verify-dist has already checked them here.
|
||||||
|
#
|
||||||
|
# The zip round-trip drops the executable bit. That is survivable only
|
||||||
|
# because the Dockerfile chmods the binary itself and the contents
|
||||||
|
# assertion compares sha256 of file contents, never modes. The archive
|
||||||
|
# modes are asserted by verify-dist, above, on the originals.
|
||||||
|
- name: Upload the staged payload for the container job
|
||||||
|
uses: actions/upload-artifact@c24449f33cd45d4826c6702db7e49f7cdb9b551d # v3.2.1-node20
|
||||||
|
with:
|
||||||
|
name: dist-payload
|
||||||
|
path: |
|
||||||
|
zig-out/dist/bin
|
||||||
|
zig-out/dist/stage
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
container:
|
container:
|
||||||
|
needs: [package]
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
||||||
|
|
||||||
- name: Set up Zig
|
# Restored under zig-out/dist because that is where the Dockerfile's
|
||||||
uses: mlugg/setup-zig@d1434d08867e3ee9daa34448df10607b98908d29 # v2.2.1
|
# COPY lines look, with the repository root as the build context. This
|
||||||
|
# job compiles nothing and bundles nothing: the payload is the one the
|
||||||
|
# package job already built and verify-dist already checked, which is
|
||||||
|
# also the point — an image built from a second, independent `dist` run
|
||||||
|
# would prove nothing about the artifacts the release publishes.
|
||||||
|
- name: Download the staged payload built by the package job
|
||||||
|
uses: actions/download-artifact@ad191675b41f6a5b46da9a048cb6893812da158b # v3.1.0-node20
|
||||||
with:
|
with:
|
||||||
version: ${{ env.ZIG_VERSION }}
|
name: dist-payload
|
||||||
|
path: zig-out/dist
|
||||||
|
|
||||||
# zig 0.16.0's package fetcher creates tmp/<hex>.zip inside the global
|
# Same single source of truth as the package job (rulings 2 and 5). This
|
||||||
# cache without creating tmp/ first (src/Package/Fetch.zig:1499), and
|
# job still needs the version for the stage directory name it hashes
|
||||||
# setup-zig's restored cache never contains tmp/. Without this, every
|
# against the image and for the VERSION build arg.
|
||||||
# dependency fetch dies with "failed to create temporary zip file:
|
|
||||||
# FileNotFound" before any network I/O.
|
|
||||||
- name: Create the fetch temp dir zig assumes
|
|
||||||
run: mkdir -p "${ZIG_GLOBAL_CACHE_DIR:?}/tmp"
|
|
||||||
|
|
||||||
- name: Set up Node
|
|
||||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
|
||||||
with:
|
|
||||||
node-version: ${{ env.NODE_VERSION }}
|
|
||||||
cache: npm
|
|
||||||
cache-dependency-path: web/package-lock.json
|
|
||||||
|
|
||||||
- name: Build the web UI
|
|
||||||
working-directory: web
|
|
||||||
run: |
|
|
||||||
npm ci
|
|
||||||
npm run build
|
|
||||||
|
|
||||||
# Same single source of truth as the package job (rulings 2 and 5).
|
|
||||||
- name: Read the version from build.zig.zon
|
- name: Read the version from build.zig.zon
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
@@ -239,15 +292,6 @@ jobs:
|
|||||||
test -n "$version"
|
test -n "$version"
|
||||||
echo "CI_VERSION=$version" >> "$GITHUB_ENV"
|
echo "CI_VERSION=$version" >> "$GITHUB_ENV"
|
||||||
|
|
||||||
- name: Build the release artifacts
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
zig build dist \
|
|
||||||
-Dversion-string="$CI_VERSION" \
|
|
||||||
-Dgit-commit="$GITHUB_SHA" \
|
|
||||||
-Dweb-dist=web/dist \
|
|
||||||
-Doptimize=ReleaseSafe
|
|
||||||
|
|
||||||
# Image tags, container names and published host ports are all
|
# Image tags, container names and published host ports are all
|
||||||
# daemon-global. This workflow is called by both ci.yml and release.yml
|
# daemon-global. This workflow is called by both ci.yml and release.yml
|
||||||
# and the self-hosted runners share one docker daemon, so the fixed
|
# and the self-hosted runners share one docker daemon, so the fixed
|
||||||
|
|||||||
@@ -4,3 +4,4 @@ zig-pkg/
|
|||||||
web/node_modules/
|
web/node_modules/
|
||||||
web/dist/
|
web/dist/
|
||||||
web/dist-sourcemap/
|
web/dist-sourcemap/
|
||||||
|
web-dist-ci/
|
||||||
|
|||||||
Reference in New Issue
Block a user