Repository navigation
266 lines (255 loc) · 13 KB
/
Copy pathrelease-please.yml
File metadata and controls
266 lines (255 loc) · 13 KB
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
name: release-please
# Maintains a single open "release PR" on main that bumps all workspace
# versions, updates per-crate CHANGELOG.md files, and updates the
# .release-please-manifest.json based on Conventional Commits since the
# last release. Merging that PR lands the bumps and changelogs only: it does
# NOT tag, because `skip-github-release` is true in release-please-config.json.
# A maintainer creates the v{X.Y.Z} tag and GitHub Release by hand afterwards,
# which is what triggers the publish workflows. See docs/GITHUB_OPERATIONS.md.
# That same `release: published` event also re-runs this workflow so it can
# anchor on the freshly created tag and correct the next release PR (see #308).
#
# Uses a fine-grained PAT (RELEASE_PLEASE_TOKEN) instead of the default
# GITHUB_TOKEN so that PRs and tags created by this workflow trigger
# downstream CI and publish workflows. With GITHUB_TOKEN, GitHub
# suppresses workflow triggers on events it creates (anti-recursion).
#
# After release-please opens or updates the release PR, a follow-up
# step reconciles Cargo.lock against the bumped Cargo.toml and pushes
# the lockfile commit onto the release-please branch. Without this,
# the release tag points at a tree where Cargo.toml is at the new
# version but Cargo.lock still pins the previous one; CI's
# `cargo build --release` then silently rewrites the lockfile in-place
# at build time, dirtying the worktree, and `build.rs` stamps a
# `-dirty-<timestamp>` marker onto the released binary. Keeping the
# lockfile in sync at release-PR time avoids that. (`build.rs` also
# excludes Cargo.lock from its dirty check as a safety net.)
#
# A defense-in-depth sentinel verifies the lockfile diff only touches
# workspace-member version rows. If a future cargo version touches
# non-workspace rows (or bumps the lockfile format version), the
# sentinel ABORTS the workflow and blocks the release; resolve by
# either updating the sentinel or landing the dep bump on main first
# as its own commit.
#
# Maintainers don't run anything by hand:
# 1. Land conventional-commit PRs into main.
# 2. Merge the release-please PR when ready to ship.
#
# See docs/GITHUB_OPERATIONS.md → "Cutting a release" for details.
on:
push:
branches: [main]
# #308: the run that fires on the release-PR *merge* races ahead of the
# hand-cut tag (skip-github-release: true), so release-please can't see the
# just-released version and miscomputes the next rc backward. Re-running when
# the maintainer publishes the Release — the tag now exists — makes it anchor
# on that version and regenerate the next release PR correctly. The manual-tag
# runbook promotes the release PR's `autorelease: tagged` label BEFORE
# `gh release create` so this re-run doesn't hit the outstanding-pending-PR
# abort (see docs/GITHUB_OPERATIONS.md). The verify-release-pr-version guard
# backstops any residual lag.
release:
types: [published]
workflow_dispatch: {}
# Serialize runs so the lockfile-sync push doesn't race a concurrent
# `release-please` run pointed at the same release branch.
concurrency:
group: release-please
cancel-in-progress: false
permissions:
contents: write
pull-requests: write
jobs:
release-please:
if: github.repository == 'tableau/hyper-api-rust'
runs-on: ubuntu-latest
steps:
- uses: googleapis/release-please-action@v5
id: release
with:
config-file: release-please-config.json
manifest-file: .release-please-manifest.json
token: ${{ secrets.RELEASE_PLEASE_TOKEN }}
# release-please-action@v5 calls manifest.createPullRequests()
# which routes through `createOrUpdatePullRequest` — the action
# emits its `pr` JSON output on BOTH first creation and any
# subsequent update from a later push to main. Verified by
# reading the action source.
#
# The `pr != ''` gate fires whenever there's a release PR after
# this run, so we get a re-sync opportunity each time release-
# please re-runs. If release-please force-pushes the release
# branch (it does — its bot commits get rewritten on each
# re-run) and clobbers our prior lockfile-sync commit, this
# follow-up step re-fires on the SAME run and re-pushes the
# synced lockfile. The eventually-merged release PR head is
# always lockfile-synced. The build.rs Cargo.lock pathspec
# exclusion is the safety net that protects the released
# binary even if a maintainer merges in a tiny window between
# release-please's force-push and our follow-up step landing.
# Extract the release-please branch name into its own step
# output. We can't reference `fromJson(steps.release.outputs.pr)`
# directly in `ref:` / `env:` further down, because GitHub
# Actions evaluates every `${{ }}` expression in the workflow
# at job-load time — even on steps gated by `if:` that won't
# run. When `pr` is empty (no release PR this run),
# `fromJson('')` errors with "Error reading JToken from
# JsonReader." Wrapping the parse in a `run:` block keeps it
# inside the `if`-gate.
- name: Resolve release-please branch name
id: branch
if: ${{ steps.release.outputs.pr != '' }}
env:
PR_JSON: ${{ steps.release.outputs.pr }}
run: |
set -euo pipefail
BRANCH=$(printf '%s' "$PR_JSON" | jq -r '.headBranchName')
if [ -z "$BRANCH" ] || [ "$BRANCH" = "null" ]; then
echo "::error::Could not extract headBranchName from release-please output"
printf '%s\n' "$PR_JSON" | head -5
exit 1
fi
echo "name=$BRANCH" >> "$GITHUB_OUTPUT"
- name: Checkout release-please branch
if: ${{ steps.release.outputs.pr != '' }}
uses: actions/checkout@v7
with:
ref: ${{ steps.branch.outputs.name }}
token: ${{ secrets.RELEASE_PLEASE_TOKEN }}
- uses: actions-rust-lang/setup-rust-toolchain@v2
if: ${{ steps.release.outputs.pr != '' }}
with:
toolchain: stable
cache: false # one-shot job; cache adds latency without benefit
rustflags: ""
# `cargo metadata --format-version=1` forces a lockfile
# reconciliation pass against the current Cargo.tomls. Empirically
# validated on this workspace: it flips exactly the 9 path-crate
# version rows in the root lockfile (hyperdb-api, hyperdb-api-core,
# hyperdb-api-derive, hyperdb-api-node, hyperdb-api-salesforce,
# hyperdb-bootstrap, hyperdb-mcp, sea-query-hyperdb, and
# hyperdb-compile-check — the last is its own workspace but is
# still a path dep in the root lock) and touches no external dep
# rows. The "Verify lockfile diff is workspace-only" sentinel below
# catches it if a future cargo version diverges.
#
# Why not `cargo generate-lockfile`: it does a full re-resolve
# and bumps unrelated transitive deps to their latest semver
# (verified: bumps `crypto-common`, `matchit`, et al. on this
# workspace, plus changes the lockfile format version). That's
# noisy in a release commit.
#
# Why not `cargo update --workspace [--offline]`: `--workspace`
# doesn't prevent external dep re-resolution from the cache,
# and `--offline` makes it brittle.
# NOTE: this step intentionally does NOT install protoc / system
# libs. `cargo metadata` only resolves the dependency graph — it
# does not compile build.rs or proc-macros. Do NOT extend this
# step to run `cargo build` / `cargo check` without first
# installing the system libraries used by the test/release
# workflows (libfontconfig1-dev, mold, protobuf-compiler).
- name: Sync Cargo.lock with bumped workspace versions
if: ${{ steps.release.outputs.pr != '' }}
# Two lockfiles need this, not one. hyperdb-compile-check declares its
# own [workspace] so it has a separate Cargo.lock, and it is in
# release-please's extra-files — so its version gets bumped while its
# lockfile still records the previous one. Left unsynced, every release
# PR fails the `msrv` job, which is the only gate that builds that
# crate with --locked.
run: |
cargo metadata --format-version=1 > /dev/null
cargo metadata --format-version=1 \
--manifest-path hyperdb-compile-check/Cargo.toml > /dev/null
# Sentinel: fail loudly if the lockfile diff touches anything
# other than workspace-member version rows. Two-layer check:
# 1. The lockfile format-version line (`version = N` at the
# top, unquoted integer) must NOT change. Format bumps
# come from full re-resolves like `cargo generate-lockfile`,
# not from a metadata reconciliation pass — if we see one,
# something has gone sideways.
# 2. Every changed `[[package]]` block's name must be a
# workspace member, derived at runtime from `cargo metadata`
# so a future workspace addition / rename / removal stays
# automatically in sync (no separate hard-coded list to
# maintain).
#
# Defense against a future cargo version that decides to bump
# transitive deps during what should be a workspace-only sync.
- name: Verify lockfile diff is workspace-only
if: ${{ steps.release.outputs.pr != '' }}
run: |
set -euo pipefail
if git diff --quiet -- Cargo.lock; then
echo "Cargo.lock unchanged — nothing to verify."
exit 0
fi
# Layer 1: catch lockfile-format-version changes. The
# top-of-file `version = 4` line is unquoted; package
# versions are always quoted (`version = "0.2.1"`). A
# `^[+-]version = [0-9]+$` line in the diff is necessarily
# the format header.
if git diff -- Cargo.lock | grep -Eq '^[+-]version = [0-9]+$'; then
echo "::error::Cargo.lock format version changed — refusing to commit."
git diff -- Cargo.lock | head -10
exit 1
fi
# Layer 2: enumerate workspace members at runtime, then
# check each name appearing in the diff is in that set.
# `select(.source==null)` filters out registry packages.
#
# hyperdb-compile-check is deliberately NOT a member of the root
# workspace (it declares its own `[workspace]` so it can build/test
# standalone), so root `cargo metadata` omits it — yet it IS a path
# crate in the root lockfile and gets a version bump on release. Union
# in its own workspace's members so the guard doesn't false-positive
# on it. Both sides are runtime-derived, so workspace additions stay
# automatically in sync with no hard-coded list.
WORKSPACE=$( { \
cargo metadata --no-deps --format-version=1; \
cargo metadata --no-deps --format-version=1 \
--manifest-path hyperdb-compile-check/Cargo.toml; \
} \
| jq -r '.packages[] | select(.source==null) | .name' \
| sort -u)
CHANGED=$(git diff -U1 -- Cargo.lock \
| awk '/^ name = / { gsub(/"/, "", $3); name=$3 } /^[+-]version = / { print name }' \
| sort -u)
UNEXPECTED=""
for pkg in $CHANGED; do
case " $(echo $WORKSPACE | tr '\n' ' ') " in
*" $pkg "*) ;;
*) UNEXPECTED="$UNEXPECTED $pkg" ;;
esac
done
if [ -n "$UNEXPECTED" ]; then
echo "::error::Cargo.lock sync touched non-workspace packages:$UNEXPECTED"
git diff -- Cargo.lock | head -120
exit 1
fi
echo "Cargo.lock changes touched only workspace members:"
echo "$CHANGED"
- name: Commit Cargo.lock if changed
if: ${{ steps.release.outputs.pr != '' }}
run: |
set -euo pipefail
LOCKFILES="Cargo.lock hyperdb-compile-check/Cargo.lock"
if git diff --quiet -- $LOCKFILES; then
echo "Lockfiles already in sync — no commit needed."
exit 0
fi
# github-actions[bot] is the canonical identity for
# workflow-authored commits. Verified against this repo's
# log: prior release-please commits were authored under
# the user's PAT (no bot account exists), so we can't
# match the existing style automatically — github-actions[bot]
# is unambiguous and reads correctly in the PR history.
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add $LOCKFILES
git commit -m "chore: sync Cargo.lock with bumped workspace versions"
# Explicit refspec — don't rely on actions/checkout's
# tracking branch staying the default in future versions.
git push origin "HEAD:${BRANCH}"
env:
BRANCH: ${{ steps.branch.outputs.name }}