ci(ci): aligned CI release metadata flow after job and output renames

- Renamed the gate and native jobs in CI for consistent release naming.
- Renamed reusable-artifact output keys for native lookup compatibility.
- Rewired release and test jobs to read tags, flags, and hashes from metadata outputs.
This commit is contained in:
can1357
2026-06-08 12:26:31 +02:00
parent 59cb767374
commit 61c2e29532
4 changed files with 121 additions and 104 deletions
+118 -101
View File
@@ -21,30 +21,32 @@ env:
jobs:
# scripts/release.ts pushes the version-bump commit and its `v*` tag
# atomically (`git push --atomic origin main refs/tags/v*`), so a release
# now arrives as a single `push` to `refs/heads/main` — we no longer trigger
# on the tag ref at all (see `on.push`). This one branch-push run is therefore
# authoritative: it runs the full build AND, when HEAD carries a release tag,
# the release/publish jobs. `gate` resolves that tag once so downstream jobs
# switch on `is-release` and address the tag by name — `github.ref` is
# atomically (`git push --atomic origin refs/heads/main:refs/heads/main
# <sha>:refs/tags/v<version>`), so a release now arrives as a single `push` to
# `refs/heads/main` — we no longer trigger on the tag ref at all (see
# `on.push`). This one branch-push run is therefore authoritative: it runs the
# full build AND, when HEAD carries a release tag, the release/publish jobs.
# `release_metadata` resolves that tag once so downstream jobs switch on
# `is-release` and address the tag by name — `github.ref` is
# `refs/heads/main` here, not the tag. A `workflow_dispatch` from a `v*` tag
# ref is also treated as a release (the manual re-publish escape hatch).
gate:
# ref (or from a tagged main HEAD) is also treated as a release.
release_metadata:
name: Resolve release metadata
runs-on: ubuntu-22.04
outputs:
is-release: ${{ steps.check.outputs.is-release }}
release-tag: ${{ steps.check.outputs.release-tag }}
is-release: ${{ steps.detect.outputs.is-release }}
release-tag: ${{ steps.detect.outputs.release-tag }}
steps:
# Only a main-branch push needs tags fetched, so `git tag --points-at
# Only a main-branch run needs tags fetched, so `git tag --points-at
# HEAD` can see the freshly-pushed `v*`. A tag-ref dispatch reads the
# tag straight from `github.ref_name`, and fetching `--tags` while
# checkout uses an explicit tag refspec makes git refuse — so scope
# fetch-tags to main pushes.
# fetch-tags to main refs.
- uses: actions/checkout@v4
with:
fetch-tags: ${{ github.ref == 'refs/heads/main' }}
- name: Detect release tag at HEAD
id: check
id: detect
shell: bash
run: |
is_release=false
@@ -71,27 +73,29 @@ jobs:
# Compute a stable hash of every input that affects the native cdylib output,
# then look for any prior successful main run that already uploaded the
# native artifacts for this hash. Two independent outputs:
# * `linux-run-id` — set when the linux x64 canary (`pi-natives-linux-x64-modern-h<hash>`)
# is present on a prior main run, so `test`/`native_linux` can reuse it.
# * `release-run-id` — set when ALL native_release platforms also have
# non-expired artifacts on that same prior run, so `native_release` can
# skip the cold rebuild on main pushes after dep changes have already
# warmed sccache there.
# Non-tag native jobs are skipped when their canary hits; the canary
# * `linux-x64-run-id` — set when the linux x64 canary
# (`pi-natives-linux-x64-modern-h<hash>`) is present on a prior main run,
# so `test`/`native_linux_x64` can reuse it.
# * `cross-platform-run-id` — set when ALL cross-platform native artifacts
# also have non-expired artifacts on that same prior run, so
# `native_cross_platform` can skip the cold rebuild on main pushes after
# dep changes have already warmed sccache there.
# Non-release native jobs are skipped when their canary hits; the canary
# retention window (see build-native action) is the effective TTL.
rust-hash:
native_artifact_lookup:
name: Look up cached native artifacts
runs-on: ubuntu-22.04
outputs:
hash: ${{ steps.compute.outputs.hash }}
linux-run-id: ${{ steps.find.outputs.linux-run-id }}
release-run-id: ${{ steps.find.outputs.release-run-id }}
source-hash: ${{ steps.compute.outputs.source-hash }}
linux-x64-run-id: ${{ steps.find.outputs.linux-x64-run-id }}
cross-platform-run-id: ${{ steps.find.outputs.cross-platform-run-id }}
steps:
- uses: actions/checkout@v4
- name: Compute rust source hash
- name: Compute native source hash
id: compute
shell: bash
run: |
hash=$(find crates Cargo.toml Cargo.lock rust-toolchain.toml \
source_hash=$(find crates Cargo.toml Cargo.lock rust-toolchain.toml \
packages/natives/scripts packages/natives/package.json \
scripts/ci-build-native.ts scripts/host-detect.ts \
-type f -print0 \
@@ -99,44 +103,46 @@ jobs:
| xargs -0 sha256sum \
| sha256sum \
| cut -c1-16)
echo "hash=$hash" >> "$GITHUB_OUTPUT"
echo "Rust source hash: $hash"
echo "source-hash=$source_hash" >> "$GITHUB_OUTPUT"
echo "Native source hash: $source_hash"
- name: Find prior main build with matching native artifacts
id: find
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
shell: bash
run: |
hash="${{ steps.compute.outputs.hash }}"
# Canary for native_linux: presence of the modern artifact implies
# the baseline sibling is also there (they upload from the same job).
hash="${{ steps.compute.outputs.source-hash }}"
# Canary for native_linux_x64: presence of the modern artifact
# implies the baseline sibling is also there (they upload from the
# same job).
linux_canary="pi-natives-linux-x64-modern-h${hash}"
# Required set for native_release reuse — names must match the
# Required set for cross-platform reuse — names must match the
# `actions/upload-artifact` `name:` template in build-native action.
release_required=(
cross_platform_required=(
"pi-natives-linux-arm64-h${hash}"
"pi-natives-darwin-x64-baseline-h${hash}"
"pi-natives-darwin-arm64-h${hash}"
"pi-natives-win32-x64-baseline-h${hash}"
)
linux_run_id=""
release_run_id=""
linux_x64_run_id=""
cross_platform_run_id=""
for candidate in $(gh run list \
--workflow=ci.yml --branch=main --status=success --event=push \
--limit=20 --json databaseId --jq='.[].databaseId'); do
names=$(gh api "/repos/${{ github.repository }}/actions/runs/$candidate/artifacts?per_page=100" \
--jq '.artifacts[] | select(.expired == false) | .name')
if [ -z "$linux_run_id" ] && echo "$names" | grep -qFx "$linux_canary"; then
linux_run_id="$candidate"
if [ -z "$linux_x64_run_id" ] && echo "$names" | grep -qFx "$linux_canary"; then
linux_x64_run_id="$candidate"
fi
if [ -z "$release_run_id" ]; then
if [ -z "$cross_platform_run_id" ]; then
all_found=true
# Release reuse requires the linux canary AND every cross-platform
# artifact, since release_binary downloads them from the same run.
# Cross-platform reuse requires the linux canary AND every
# cross-platform artifact, since release_binary downloads them
# from the same run.
if ! echo "$names" | grep -qFx "$linux_canary"; then
all_found=false
else
for req in "${release_required[@]}"; do
for req in "${cross_platform_required[@]}"; do
if ! echo "$names" | grep -qFx "$req"; then
all_found=false
break
@@ -144,30 +150,31 @@ jobs:
done
fi
if $all_found; then
release_run_id="$candidate"
cross_platform_run_id="$candidate"
fi
fi
if [ -n "$linux_run_id" ] && [ -n "$release_run_id" ]; then
if [ -n "$linux_x64_run_id" ] && [ -n "$cross_platform_run_id" ]; then
break
fi
done
if [ -n "$linux_run_id" ]; then
echo "Reusing native_linux artifacts from run $linux_run_id"
if [ -n "$linux_x64_run_id" ]; then
echo "Reusing Linux x64 native artifacts from run $linux_x64_run_id"
else
echo "No cached native_linux artifacts for hash $hash; native_linux will rebuild."
echo "No cached Linux x64 native artifacts for hash $hash; native_linux_x64 will rebuild."
fi
if [ -n "$release_run_id" ]; then
echo "Reusing native_release artifacts from run $release_run_id"
if [ -n "$cross_platform_run_id" ]; then
echo "Reusing cross-platform native artifacts from run $cross_platform_run_id"
else
echo "No cached native_release artifacts for hash $hash; native_release will rebuild on main."
echo "No cached cross-platform native artifacts for hash $hash; native_cross_platform will rebuild on main."
fi
{
echo "linux-run-id=$linux_run_id"
echo "release-run-id=$release_run_id"
echo "linux-x64-run-id=$linux_x64_run_id"
echo "cross-platform-run-id=$cross_platform_run_id"
} >> "$GITHUB_OUTPUT"
# Fast lint + type check (no Rust, no native build needed)
check:
name: Lint & type check
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
@@ -184,10 +191,12 @@ jobs:
run: bun run ci:check:full
# Linux x64 baseline + modern: required by `test`, so it runs on every PR
# unless rust-hash found a cached run. Release pushes always rebuild for fresh artifacts.
native_linux:
needs: [gate, rust-hash]
if: ${{ needs.gate.outputs.is-release == 'true' || needs.rust-hash.outputs.linux-run-id == '' }}
# unless native_artifact_lookup found a cached run. Release runs always
# rebuild for fresh artifacts.
native_linux_x64:
name: "Native: Linux x64 (${{ matrix.variant }})"
needs: [release_metadata, native_artifact_lookup]
if: ${{ needs.release_metadata.outputs.is-release == 'true' || needs.native_artifact_lookup.outputs.linux-x64-run-id == '' }}
runs-on: ubuntu-22.04
strategy:
fail-fast: false
@@ -199,7 +208,7 @@ jobs:
- uses: actions/checkout@v4
- uses: ./.github/actions/build-native
with:
hash: ${{ needs.rust-hash.outputs.hash }}
hash: ${{ needs.native_artifact_lookup.outputs.source-hash }}
platform: linux
arch: x64
variant: ${{ matrix.variant }}
@@ -207,11 +216,12 @@ jobs:
save_cache: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
# Pre-warm the cross-platform native build cache on `main`, in addition to
# building the artifacts that ship in release tags. Skipped on main when the
# rust-hash canary already found a recent run with all artifacts intact.
native_release:
needs: [gate, rust-hash]
if: ${{ needs.gate.outputs.is-release == 'true' || (github.event_name == 'push' && github.ref == 'refs/heads/main' && needs.rust-hash.outputs.release-run-id == '') }}
# building the artifacts that ship in releases. Skipped on main when
# native_artifact_lookup already found a recent run with all artifacts intact.
native_cross_platform:
name: "Native: ${{ matrix.platform }} ${{ matrix.arch }}"
needs: [release_metadata, native_artifact_lookup]
if: ${{ needs.release_metadata.outputs.is-release == 'true' || (github.event_name == 'push' && github.ref == 'refs/heads/main' && needs.native_artifact_lookup.outputs.cross-platform-run-id == '') }}
strategy:
fail-fast: false
matrix:
@@ -225,7 +235,7 @@ jobs:
- uses: actions/checkout@v4
- uses: ./.github/actions/build-native
with:
hash: ${{ needs.rust-hash.outputs.hash }}
hash: ${{ needs.native_artifact_lookup.outputs.source-hash }}
platform: ${{ matrix.platform }}
arch: ${{ matrix.arch }}
variant: ${{ matrix.variant }}
@@ -233,9 +243,10 @@ jobs:
save_cache: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
test:
name: Test & smoke (TS)
runs-on: ubuntu-22.04
needs: [native_linux, rust-hash]
if: ${{ !cancelled() && needs.native_linux.result != 'failure' }}
needs: [native_linux_x64, native_artifact_lookup]
if: ${{ !cancelled() && needs.native_linux_x64.result != 'failure' }}
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
@@ -251,25 +262,25 @@ jobs:
run: |
sudo apt-get update
sudo apt-get install -y libcairo2-dev libpango1.0-dev libjpeg-dev libgif-dev librsvg2-dev fd-find ripgrep imagemagick
sudo ln -s $(which fdfind) /usr/local/bin/fd
sudo ln -sf "$(command -v fdfind)" /usr/local/bin/fd
sudo ln -sf /usr/bin/convert /usr/local/bin/magick
- run: bun install --frozen-lockfile
- name: Resolve native source run
- name: Resolve Linux x64 native artifact run
id: source
shell: bash
run: |
if [ "${{ needs.native_linux.result }}" = "success" ]; then
echo "run-id=${{ github.run_id }}" >> "$GITHUB_OUTPUT"
if [ "${{ needs.native_linux_x64.result }}" = "success" ]; then
echo "artifact-run-id=${{ github.run_id }}" >> "$GITHUB_OUTPUT"
else
echo "run-id=${{ needs.rust-hash.outputs.linux-run-id }}" >> "$GITHUB_OUTPUT"
echo "artifact-run-id=${{ needs.native_artifact_lookup.outputs.linux-x64-run-id }}" >> "$GITHUB_OUTPUT"
fi
- name: Download native addons
uses: actions/download-artifact@v4
with:
pattern: pi-natives-linux-x64-*-h${{ needs.rust-hash.outputs.hash }}
pattern: pi-natives-linux-x64-*-h${{ needs.native_artifact_lookup.outputs.source-hash }}
path: packages/natives/native
merge-multiple: true
run-id: ${{ steps.source.outputs.run-id }}
run-id: ${{ steps.source.outputs.artifact-run-id }}
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Test workspace (TS)
# `test:ts` sets GITHUB_ACTIONS=0 inline so `bun test` skips its
@@ -281,6 +292,7 @@ jobs:
run: bun run ci:test:smoke
install_methods:
name: Install method smoke tests
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
@@ -297,8 +309,8 @@ jobs:
save-if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
cache-workspace-crates: true
# Layer sccache on top of rust-cache for the same reason as the
# build-native action: tag pushes bump workspace versions and bust
# the target/ cache, but sccache hits at the rustc-unit level survive.
# build-native action: release version bumps bust the target/ cache,
# but sccache hits at the rustc-unit level survive.
- name: Setup sccache
uses: mozilla-actions/sccache-action@v0.0.10
- name: Enable sccache for cargo
@@ -318,18 +330,19 @@ jobs:
run: |
sudo apt-get update
sudo apt-get install -y libcairo2-dev libpango1.0-dev libjpeg-dev libgif-dev librsvg2-dev fd-find ripgrep imagemagick
sudo ln -s $(which fdfind) /usr/local/bin/fd
sudo ln -sf "$(command -v fdfind)" /usr/local/bin/fd
sudo ln -sf /usr/bin/convert /usr/local/bin/magick
- run: bun install --frozen-lockfile
- name: Install method smoke tests
run: bun run ci:test:install-methods
release_binary:
if: ${{ needs.gate.outputs.is-release == 'true' && !cancelled() &&
needs.native_linux.result == 'success' && needs.native_release.result ==
name: "Release binary: ${{ matrix.target_id }}"
if: ${{ needs.release_metadata.outputs.is-release == 'true' && !cancelled() &&
needs.native_linux_x64.result == 'success' && needs.native_cross_platform.result ==
'success' && needs.test.result == 'success' && needs.check.result ==
'success' && needs.install_methods.result == 'success' }}
needs: [gate, check, native_linux, native_release, test, install_methods, rust-hash]
needs: [release_metadata, check, native_linux_x64, native_cross_platform, test, install_methods, native_artifact_lookup]
strategy:
fail-fast: false
matrix:
@@ -374,7 +387,7 @@ jobs:
contents: read
id-token: write
env:
MACOS_SIGNING: ${{ secrets.APPLE_CERTIFICATE_P12 != '' && secrets.APPLE_API_KEY != '' }}
MACOS_SIGNING: ${{ secrets.APPLE_CERTIFICATE_P12 != '' && secrets.APPLE_CERTIFICATE_PASSWORD != '' && secrets.APPLE_API_KEY_ID != '' && secrets.APPLE_API_ISSUER_ID != '' && secrets.APPLE_API_KEY != '' }}
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
@@ -384,7 +397,7 @@ jobs:
with:
node-version: "24"
registry-url: "https://registry.npmjs.org"
# Trusted publishing allowed-actions flags require npm >= 11.16.0.
# Keep npm aligned with trusted publishing setup (>= 11.16.0).
- name: Ensure npm supports trusted publishing
if: ${{ !inputs.skip_npm }}
run: npm install -g npm@latest
@@ -397,7 +410,7 @@ jobs:
- name: Download native addon(s)
uses: actions/download-artifact@v4
with:
pattern: pi-natives-${{ matrix.platform }}-${{ matrix.arch }}*-h${{ needs.rust-hash.outputs.hash }}
pattern: pi-natives-${{ matrix.platform }}-${{ matrix.arch }}*-h${{ needs.native_artifact_lookup.outputs.source-hash }}
path: packages/natives/native
merge-multiple: true
- name: Build release binary
@@ -441,10 +454,11 @@ jobs:
name: omp-binary-${{ matrix.target_id }}
path: ${{ matrix.binary_path }}
release-github:
if: ${{ needs.gate.outputs.is-release == 'true' && !cancelled() &&
release_github:
name: Publish GitHub release
if: ${{ needs.release_metadata.outputs.is-release == 'true' && !cancelled() &&
needs.release_binary.result == 'success' }}
needs: [gate, release_binary]
needs: [release_metadata, release_binary]
runs-on: ubuntu-22.04
permissions:
contents: write
@@ -454,7 +468,7 @@ jobs:
with:
bun-version: "1.3"
- name: Generate release notes from CHANGELOGs
run: bun scripts/ci-release-notes.ts ${{ needs.gate.outputs.release-tag }}
run: bun scripts/ci-release-notes.ts ${{ needs.release_metadata.outputs.release-tag }}
- name: Download release binaries
uses: actions/download-artifact@v4
with:
@@ -464,7 +478,7 @@ jobs:
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ needs.gate.outputs.release-tag }}
tag_name: ${{ needs.release_metadata.outputs.release-tag }}
files: |
packages/coding-agent/binaries/omp-*
body_path: release-notes.md
@@ -472,18 +486,19 @@ jobs:
release_github_verify:
if: ${{ needs.gate.outputs.is-release == 'true' && !cancelled() &&
needs['release-github'].result == 'success' }}
needs: [gate, release-github]
name: Verify published release (macOS)
if: ${{ needs.release_metadata.outputs.is-release == 'true' && !cancelled() &&
needs.release_github.result == 'success' }}
needs: [release_metadata, release_github]
runs-on: macos-14
permissions:
contents: read
env:
MACOS_SIGNING: ${{ secrets.APPLE_CERTIFICATE_P12 != '' && secrets.APPLE_API_KEY != '' }}
MACOS_SIGNING: ${{ secrets.APPLE_CERTIFICATE_P12 != '' && secrets.APPLE_CERTIFICATE_PASSWORD != '' && secrets.APPLE_API_KEY_ID != '' && secrets.APPLE_API_ISSUER_ID != '' && secrets.APPLE_API_KEY != '' }}
steps:
- name: Download published macOS arm64 binary
run: |
curl -fsSL -o omp-darwin-arm64 "https://github.com/${{ github.repository }}/releases/download/${{ needs.gate.outputs.release-tag }}/omp-darwin-arm64"
curl -fsSL -o omp-darwin-arm64 "https://github.com/${{ github.repository }}/releases/download/${{ needs.release_metadata.outputs.release-tag }}/omp-darwin-arm64"
chmod +x omp-darwin-arm64
- name: Verify published macOS arm64 binary
run: |
@@ -504,12 +519,13 @@ jobs:
# lookup, so surface the result without gating the release on it.
spctl -a -t exec -vv ./omp-darwin-arm64 || echo "spctl non-zero (expected for unstapled bare binary; ticket served online)"
release-npm:
if: ${{ needs.gate.outputs.is-release == 'true' && !cancelled() &&
release_npm:
name: Publish to npm
if: ${{ needs.release_metadata.outputs.is-release == 'true' && !cancelled() &&
needs.release_binary.result == 'success' &&
needs.release_github_verify.result == 'success' &&
!inputs.skip_npm }}
needs: [gate, release_binary, release_github_verify]
needs: [release_metadata, release_binary, release_github_verify]
runs-on: ubuntu-22.04
# `id-token: write` lets npm mint the GitHub OIDC token it exchanges for a
# short-lived publish token (trusted publishing + provenance). When a
@@ -527,8 +543,8 @@ jobs:
with:
node-version: "24"
registry-url: "https://registry.npmjs.org"
# Trusted publishing (OIDC) and auto-provenance need npm >= 11.5.1.
- name: Ensure npm supports OIDC trusted publishing
# Keep npm aligned with trusted publishing setup (>= 11.16.0).
- name: Ensure npm supports trusted publishing
run: npm install -g npm@latest
- name: Cache bun dependencies
uses: actions/cache@v4
@@ -547,12 +563,13 @@ jobs:
# Regenerate the Homebrew tap formula (can1357/homebrew-tap) from the freshly
# published release assets and push it. Gated on release_github_verify so the
# tap only cuts over to a release whose published binary was verified (matches
# how release-npm is gated). No-ops when HOMEBREW_TAP_DEPLOY_KEY is unset, so a
# how release_npm is gated). No-ops when HOMEBREW_TAP_DEPLOY_KEY is unset, so a
# release never blocks on tap access.
release_brew:
if: ${{ needs.gate.outputs.is-release == 'true' && !cancelled() &&
name: Update Homebrew tap
if: ${{ needs.release_metadata.outputs.is-release == 'true' && !cancelled() &&
needs.release_github_verify.result == 'success' }}
needs: [gate, release_github_verify]
needs: [release_metadata, release_github_verify]
runs-on: ubuntu-22.04
env:
HAS_TAP_KEY: ${{ secrets.HOMEBREW_TAP_DEPLOY_KEY != '' }}
@@ -575,13 +592,13 @@ jobs:
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
bun scripts/ci-update-brew-formula.ts "${{ needs.gate.outputs.release-tag }}" --out homebrew-tap/Formula/omp.rb
bun scripts/ci-update-brew-formula.ts "${{ needs.release_metadata.outputs.release-tag }}" --out homebrew-tap/Formula/omp.rb
cd homebrew-tap
if git diff --quiet -- Formula/omp.rb; then
echo "formula already up to date for ${{ needs.gate.outputs.release-tag }}"
echo "formula already up to date for ${{ needs.release_metadata.outputs.release-tag }}"
exit 0
fi
git -c user.name="github-actions[bot]" \
-c user.email="41898282+github-actions[bot]@users.noreply.github.com" \
commit -m "omp ${{ needs.gate.outputs.release-tag }}" -- Formula/omp.rb
commit -m "omp ${{ needs.release_metadata.outputs.release-tag }}" -- Formula/omp.rb
git push origin HEAD:main