ci: migrated release trigger from tag-push to branch-push
- Removed `tags: ["v*"]` trigger; release now fires from the single atomic `main` push that carries the tag. - Replaced `gate.skip` with `gate.is-release` and `gate.release-tag` so downstream jobs detect the tag via `git tag --points-at HEAD`. - Passed `release-tag` explicitly to release steps (gh-release, curl, release notes) since `github.ref` is now `refs/heads/main`, not the tag. - Fixed rust-cache key collision on macOS x64 by setting `RUSTFLAGS` (target-cpu) before cache restore and including the native source hash in the shared key.
This commit is contained in:
@@ -12,7 +12,7 @@ inputs:
|
||||
description: Target arch (x64, arm64)
|
||||
required: true
|
||||
variant:
|
||||
description: Optional build variant (baseline, modern)
|
||||
description: Optional build variant (baseline, modern); required for native x64 builds.
|
||||
required: false
|
||||
default: ""
|
||||
target:
|
||||
@@ -46,17 +46,54 @@ runs:
|
||||
toolchain_bin="$(dirname "$(rustup which cargo)")"
|
||||
echo "$toolchain_bin" >> "$GITHUB_PATH"
|
||||
echo "Prepended $toolchain_bin to PATH"
|
||||
# `Swatinem/rust-cache` keys target/ off its restore-time environment, so
|
||||
# set RUSTFLAGS before restoring it. If x64 target-cpu is only selected
|
||||
# inside ci-build-native.ts/build-native.ts, cargo invalidates the restored
|
||||
# target/ but rust-cache sees an exact key and refuses to save the rebuilt
|
||||
# artifacts, causing macOS x64 baseline to rebuild forever.
|
||||
#
|
||||
# Include the native source hash in the shared key as well: rust-cache's
|
||||
# lockfile scan misses the workspace root Cargo.toml version that Cargo
|
||||
# fingerprints for workspace crates. Without it, release version bumps can
|
||||
# get an exact hit for artifacts Cargo must rebuild.
|
||||
#
|
||||
# sccache is still layered on top of rust-cache: target/ wins when warm,
|
||||
# sccache fills the gaps when target/ is cold.
|
||||
- name: Configure native Rust flags
|
||||
if: inputs.target == ''
|
||||
shell: bash
|
||||
env:
|
||||
TARGET_ARCH: ${{ inputs.arch }}
|
||||
TARGET_VARIANT: ${{ inputs.variant }}
|
||||
run: |
|
||||
case "$TARGET_ARCH:$TARGET_VARIANT" in
|
||||
x64:modern)
|
||||
rustflags="-C target-cpu=x86-64-v3"
|
||||
;;
|
||||
x64:baseline)
|
||||
rustflags="-C target-cpu=x86-64-v2"
|
||||
;;
|
||||
x64:*)
|
||||
echo "::error::x64 native builds require variant=modern or variant=baseline"
|
||||
exit 1
|
||||
;;
|
||||
*)
|
||||
if [ -n "${RUSTFLAGS:-}" ]; then
|
||||
echo "Using caller-provided RUSTFLAGS=$RUSTFLAGS"
|
||||
exit 0
|
||||
fi
|
||||
rustflags="-C target-cpu=native"
|
||||
;;
|
||||
esac
|
||||
|
||||
echo "RUSTFLAGS=$rustflags" >> "$GITHUB_ENV"
|
||||
echo "Configured RUSTFLAGS=$rustflags"
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
shared-key: native-${{ inputs.platform }}-${{ inputs.arch }}-${{ inputs.variant || 'default' }}
|
||||
shared-key: native-${{ inputs.platform }}-${{ inputs.arch }}-${{ inputs.variant || 'default' }}-h${{ inputs.hash }}
|
||||
cache-on-failure: true
|
||||
save-if: ${{ inputs.save_cache == 'true' }}
|
||||
cache-workspace-crates: true
|
||||
# `Swatinem/rust-cache` keys target/ off Cargo.lock content; release
|
||||
# tag pushes bump workspace versions, busting that key every time. sccache
|
||||
# caches at the rustc-invocation level (source + flags), so it still hits
|
||||
# across version bumps. Layered on top of rust-cache: target/ wins when
|
||||
# warm, sccache fills the gaps when target/ is cold.
|
||||
- name: Setup sccache
|
||||
uses: mozilla-actions/sccache-action@v0.0.10
|
||||
- name: Enable sccache for cargo
|
||||
|
||||
+55
-50
@@ -3,7 +3,6 @@ name: CI
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
tags: ["v*"]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
workflow_dispatch:
|
||||
@@ -18,41 +17,53 @@ concurrency:
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
# During a release the version-bump commit and its `v*` tag are pushed
|
||||
# atomically (`git push --atomic origin main refs/tags/v*` in
|
||||
# scripts/release.ts), so GitHub fires two `push` events — one for
|
||||
# `refs/heads/main`, one for the tag — that would each run the full build.
|
||||
# The tag run is authoritative: it self-contains the native build and runs
|
||||
# the release/publish jobs (release_binary downloads natives from its own
|
||||
# run). Detect when this branch-push run is for a commit that already
|
||||
# carries a release tag and skip the duplicate build here; normal main
|
||||
# pushes (no `v*` tag at HEAD) and PRs are unaffected.
|
||||
# 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
|
||||
# `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:
|
||||
runs-on: ubuntu-22.04
|
||||
outputs:
|
||||
skip: ${{ steps.check.outputs.skip }}
|
||||
is-release: ${{ steps.check.outputs.is-release }}
|
||||
release-tag: ${{ steps.check.outputs.release-tag }}
|
||||
steps:
|
||||
# `fetch-tags` is scoped to main-branch pushes — the only case where
|
||||
# the dedup detection below runs `git tag --points-at HEAD`. On a tag
|
||||
# push the ref resolves to `refs/tags/v*`, and combining `--tags`
|
||||
# (from fetch-tags) with checkout's explicit `+<sha>:refs/tags/v*`
|
||||
# refspec makes git refuse with "Cannot fetch both <sha> and
|
||||
# refs/tags/v* to refs/tags/v*". Disabling it off-main avoids the clash.
|
||||
# Only a main-branch push 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.
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-tags: ${{ github.ref == 'refs/heads/main' }}
|
||||
- name: Detect duplicate release branch-push run
|
||||
- name: Detect release tag at HEAD
|
||||
id: check
|
||||
shell: bash
|
||||
run: |
|
||||
skip=false
|
||||
if [ "${{ github.event_name }}" = "push" ] && [ "${{ github.ref }}" = "refs/heads/main" ]; then
|
||||
if git tag --points-at HEAD | grep -qE '^v[0-9]'; then
|
||||
echo "HEAD carries a release tag; skipping the duplicate branch-push build (the tag run is authoritative)."
|
||||
skip=true
|
||||
fi
|
||||
is_release=false
|
||||
release_tag=""
|
||||
case "${{ github.ref }}" in
|
||||
refs/tags/v[0-9]*)
|
||||
release_tag="${{ github.ref_name }}"
|
||||
;;
|
||||
refs/heads/main)
|
||||
if [ "${{ github.event_name }}" != "pull_request" ]; then
|
||||
release_tag=$(git tag --points-at HEAD | grep -E '^v[0-9]' | head -n1 || true)
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
if [ -n "$release_tag" ]; then
|
||||
echo "HEAD carries release tag $release_tag; this run builds and publishes the release."
|
||||
is_release=true
|
||||
fi
|
||||
echo "skip=$skip" >> "$GITHUB_OUTPUT"
|
||||
{
|
||||
echo "is-release=$is_release"
|
||||
echo "release-tag=$release_tag"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
# 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
|
||||
@@ -66,8 +77,6 @@ jobs:
|
||||
# Non-tag native jobs are skipped when their canary hits; the canary
|
||||
# retention window (see build-native action) is the effective TTL.
|
||||
rust-hash:
|
||||
needs: [gate]
|
||||
if: ${{ needs.gate.outputs.skip != 'true' }}
|
||||
runs-on: ubuntu-22.04
|
||||
outputs:
|
||||
hash: ${{ steps.compute.outputs.hash }}
|
||||
@@ -156,8 +165,6 @@ jobs:
|
||||
|
||||
# Fast lint + type check (no Rust, no native build needed)
|
||||
check:
|
||||
needs: [gate]
|
||||
if: ${{ needs.gate.outputs.skip != 'true' }}
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -174,10 +181,10 @@ 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. Tags always rebuild for fresh artifacts.
|
||||
# unless rust-hash found a cached run. Release pushes always rebuild for fresh artifacts.
|
||||
native_linux:
|
||||
needs: [gate, rust-hash]
|
||||
if: ${{ needs.gate.outputs.skip != 'true' && (startsWith(github.ref, 'refs/tags/v') || needs.rust-hash.outputs.linux-run-id == '') }}
|
||||
if: ${{ needs.gate.outputs.is-release == 'true' || needs.rust-hash.outputs.linux-run-id == '' }}
|
||||
runs-on: ubuntu-22.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -194,14 +201,14 @@ jobs:
|
||||
arch: x64
|
||||
variant: ${{ matrix.variant }}
|
||||
rust_checks: ${{ matrix.rust_checks && 'true' || 'false' }}
|
||||
save_cache: ${{ github.event_name == 'push' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v')) }}
|
||||
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.skip != 'true' && (startsWith(github.ref, 'refs/tags/v') || (github.event_name == 'push' && github.ref == 'refs/heads/main' && needs.rust-hash.outputs.release-run-id == '')) }}
|
||||
if: ${{ needs.gate.outputs.is-release == 'true' || (github.event_name == 'push' && github.ref == 'refs/heads/main' && needs.rust-hash.outputs.release-run-id == '') }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -220,12 +227,12 @@ jobs:
|
||||
arch: ${{ matrix.arch }}
|
||||
variant: ${{ matrix.variant }}
|
||||
target: ${{ matrix.target }}
|
||||
save_cache: ${{ github.event_name == 'push' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v')) }}
|
||||
save_cache: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
|
||||
|
||||
test:
|
||||
runs-on: ubuntu-22.04
|
||||
needs: [gate, native_linux, rust-hash]
|
||||
if: ${{ !cancelled() && needs.gate.outputs.skip != 'true' && needs.native_linux.result != 'failure' }}
|
||||
needs: [native_linux, rust-hash]
|
||||
if: ${{ !cancelled() && needs.native_linux.result != 'failure' }}
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -271,8 +278,6 @@ jobs:
|
||||
run: bun run ci:test:smoke
|
||||
|
||||
install_methods:
|
||||
needs: [gate]
|
||||
if: ${{ needs.gate.outputs.skip != 'true' }}
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -286,8 +291,7 @@ jobs:
|
||||
with:
|
||||
shared-key: install-methods-linux-x64
|
||||
cache-on-failure: true
|
||||
save-if: ${{ github.event_name == 'push' && (github.ref == 'refs/heads/main' ||
|
||||
startsWith(github.ref, 'refs/tags/v')) }}
|
||||
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
|
||||
@@ -318,11 +322,11 @@ jobs:
|
||||
run: bun run ci:test:install-methods
|
||||
|
||||
release_binary:
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/v') && !cancelled() &&
|
||||
if: ${{ needs.gate.outputs.is-release == 'true' && !cancelled() &&
|
||||
needs.native_linux.result == 'success' && needs.native_release.result ==
|
||||
'success' && needs.test.result == 'success' && needs.check.result ==
|
||||
'success' && needs.install_methods.result == 'success' }}
|
||||
needs: [check, native_linux, native_release, test, install_methods, rust-hash]
|
||||
needs: [gate, check, native_linux, native_release, test, install_methods, rust-hash]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -420,9 +424,9 @@ jobs:
|
||||
path: ${{ matrix.binary_path }}
|
||||
|
||||
release-github:
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/v') && !cancelled() &&
|
||||
if: ${{ needs.gate.outputs.is-release == 'true' && !cancelled() &&
|
||||
needs.release_binary.result == 'success' }}
|
||||
needs: [release_binary]
|
||||
needs: [gate, release_binary]
|
||||
runs-on: ubuntu-22.04
|
||||
permissions:
|
||||
contents: write
|
||||
@@ -432,7 +436,7 @@ jobs:
|
||||
with:
|
||||
bun-version: "1.3"
|
||||
- name: Generate release notes from CHANGELOGs
|
||||
run: bun scripts/ci-release-notes.ts
|
||||
run: bun scripts/ci-release-notes.ts ${{ needs.gate.outputs.release-tag }}
|
||||
- name: Download release binaries
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
@@ -442,6 +446,7 @@ jobs:
|
||||
- name: Create GitHub Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
tag_name: ${{ needs.gate.outputs.release-tag }}
|
||||
files: |
|
||||
packages/coding-agent/binaries/omp-*
|
||||
body_path: release-notes.md
|
||||
@@ -449,16 +454,16 @@ jobs:
|
||||
|
||||
|
||||
release_github_verify:
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/v') && !cancelled() &&
|
||||
if: ${{ needs.gate.outputs.is-release == 'true' && !cancelled() &&
|
||||
needs['release-github'].result == 'success' }}
|
||||
needs: [release-github]
|
||||
needs: [gate, release-github]
|
||||
runs-on: macos-14
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Download published macOS arm64 binary
|
||||
run: |
|
||||
curl -fsSL -o omp-darwin-arm64 "https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/omp-darwin-arm64"
|
||||
curl -fsSL -o omp-darwin-arm64 "https://github.com/${{ github.repository }}/releases/download/${{ needs.gate.outputs.release-tag }}/omp-darwin-arm64"
|
||||
chmod +x omp-darwin-arm64
|
||||
- name: Verify published macOS arm64 binary
|
||||
run: |
|
||||
@@ -467,11 +472,11 @@ jobs:
|
||||
HOME="$runtime_dir/home" XDG_DATA_HOME="$runtime_dir/xdg" ./omp-darwin-arm64 --version
|
||||
|
||||
release-npm:
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/v') && !cancelled() &&
|
||||
if: ${{ needs.gate.outputs.is-release == 'true' && !cancelled() &&
|
||||
needs.release_binary.result == 'success' &&
|
||||
needs.release_github_verify.result == 'success' &&
|
||||
!inputs.skip_npm }}
|
||||
needs: [release_binary, release_github_verify]
|
||||
needs: [gate, 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
|
||||
|
||||
+2
-2
@@ -362,8 +362,8 @@ async function cmdRelease(version: string): Promise<void> {
|
||||
} else {
|
||||
console.log("\nTo retry after fixing (repeat until CI passes):");
|
||||
console.log(" git commit -m \"fix: <brief description>\"");
|
||||
console.log(" git push origin main");
|
||||
console.log(` git tag -f v${version} && git push origin v${version} --force`);
|
||||
console.log(` git tag -f v${version}`);
|
||||
console.log(` git push --atomic origin main +refs/tags/v${version}`);
|
||||
console.log(" bun scripts/release.ts watch");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user