Skip to content

Commit 437531d

Browse files
authored
ci: gate release pull requests on the trusted release checks
1 parent 1be0c94 commit 437531d

29 files changed

Lines changed: 1226 additions & 39 deletions

‎.github/workflows/release-please.yml‎

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -144,12 +144,17 @@ jobs:
144144
exit 1
145145
}
146146
147-
# Verify is the required status, so it only succeeds after every trusted check passes,
148-
# including building the distributions that Publish reuses once the release is tagged.
149-
# Verify's test suites skip release pull requests, whose product code already passed
150-
# them on main; release performance runs on main after each merge and does not gate.
151-
# Each dispatched run carries this attempt's request id in its run name and is matched
152-
# on it exactly.
147+
# Verify is the required status. On Release Please branches this commit status is its
148+
# only source, because the Verify workflow reports its job under another name there, so
149+
# the pull request merges only after every trusted check passes on this head, which
150+
# already includes any lockfile refresh. The checks include building the distributions
151+
# that Publish reuses once the release is tagged. Verify's test suites skip release pull
152+
# requests, whose product code already passed them on main; release performance runs on
153+
# main after each merge and does not gate. Each dispatched run carries this attempt's
154+
# request id in its run name and is matched on it exactly.
155+
if ! python3 -m scripts.check_native_layering; then
156+
fail_verify "Release versions or Cargo.lock workspace versions disagree"
157+
fi
153158
workflows=(pull-request-metadata.yml version-guard.yml release-distributions.yml)
154159
contexts=("Pull Request Metadata" "Block automatic 1.0 releases" "Release distributions")
155160
for context in "${contexts[@]}"; do

‎.github/workflows/verify.yml‎

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,8 +104,9 @@ jobs:
104104
105105
# Release Please pull requests change only release metadata on top of product code that
106106
# already passed every suite before it reached main, so the suites are skipped. Their
107-
# required Verify status is posted by the trusted release checks in release-please.yml
108-
# after the metadata, version and distribution checks pass.
107+
# required Verify status is posted only by the trusted release checks in
108+
# release-please.yml after the metadata, version and distribution checks pass; the
109+
# verify job below reports under another name on Release Please branches.
109110
if [[ "${EVENT_NAME}" == "pull_request" \
110111
&& "${HEAD_REPOSITORY}" == "${GITHUB_REPOSITORY}" \
111112
&& "${HEAD_REF}" == release-please--branches--* ]] && release_metadata_only; then
@@ -861,7 +862,17 @@ jobs:
861862
sdist: false
862863

863864
verify:
864-
name: Verify
865+
# The required Verify of a Release Please pull request is the commit status that the trusted
866+
# release checks in release-please.yml post on the branch's current head, after the lockfile
867+
# refresh and every dispatched check. This job reports under another name there, so its quick
868+
# metadata-only pass can never satisfy Verify first; a skipped job would count as passing, so
869+
# the job always runs and only its name changes. Until those checks post Verify on the head,
870+
# the pull request waits for an expected check, and every new push to the branch waits again.
871+
name: >-
872+
${{ (github.event_name == 'pull_request'
873+
&& github.event.pull_request.head.repo.full_name == github.repository
874+
&& startsWith(github.event.pull_request.head.ref, 'release-please--branches--'))
875+
&& 'Release pull request suites' || 'Verify' }}
865876
if: always()
866877
needs:
867878
[

‎Cargo.lock‎

Lines changed: 17 additions & 17 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎Makefile‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
SHELL := /bin/bash
22

3-
.PHONY: verify verify-quick verify-pg coverage cli-preview rust-check check-native-layering check-python-boundary \
3+
.PHONY: verify verify-quick verify-pg coverage cli-preview rust-check check-native-layering check-python-boundary check-release-gate \
44
check-e2e-shards test-e2e-duckdb test-e2e-duckdb-build-core \
55
test-e2e-duckdb-build-incremental \
66
test-e2e-duckdb-cli-data test-e2e-duckdb-cli test-e2e-duckdb-inspection \
@@ -623,7 +623,7 @@ verify-quick:
623623
exit $$status
624624

625625

626-
check-ci: check-e2e-shards check-native-layering check-python-boundary rust-lint
626+
check-ci: check-e2e-shards check-native-layering check-python-boundary check-release-gate rust-lint
627627
uv run ruff format --check .
628628
uv run ruff check .
629629
uv run ty check src tests
@@ -639,6 +639,10 @@ check-python-boundary:
639639
uv run python -m scripts.check_python_boundary
640640

641641

642+
check-release-gate:
643+
uv run python -m scripts.check_release_gate
644+
645+
642646
check-pr-metadata:
643647
@test -n "$(PR_TITLE)" || { echo "PR_TITLE is required" >&2; exit 2; }
644648
@test -n "$(PR_BODY_FILE)" || { echo "PR_BODY_FILE is required" >&2; exit 2; }

‎crates/README.md‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@ in `[workspace.metadata.native-layers]` in the workspace `Cargo.toml`, and
5252
- a crate below `sqlbuild-analysis` reaches polyglot.
5353

5454
The same check fails when the workspace version, the `pyproject.toml` version and
55-
`.release-please-manifest.json` disagree, because the version is part of the build identity.
55+
`.release-please-manifest.json` disagree, or when a workspace crate's `Cargo.lock` entry carries a
56+
version other than its manifest's, because the version is part of the build identity.
5657

5758
`fensu check` applies the Rust structure rules inside each crate: other crates use items through
5859
`main/` entry points, `models.rs`, `types.rs` and `constants.rs`, not through `_helpers/`.

‎scripts/check_release_gate.py‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
"""Direct wrapper for the release pull request Verify gate check."""
2+
3+
from __future__ import annotations
4+
5+
from scripts.release_gate.main.check import run_release_gate_check
6+
7+
8+
def main(argv: list[str] | None = None) -> int:
9+
"""Check the release pull request Verify gate."""
10+
return run_release_gate_check(argv)
11+
12+
13+
if __name__ == "__main__":
14+
raise SystemExit(main())

‎scripts/native_layering/_helpers/versions.py‎

Lines changed: 65 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,25 +8,28 @@
88
from typing import Any
99

1010
from scripts.native_layering.constants import (
11+
CARGO_LOCKFILE,
1112
CARGO_MANIFEST,
1213
CLI_DISTRIBUTION_PIN_PREFIX,
14+
LOCK_SOURCE_KEY,
15+
PYPROJECT_LOCK_SOURCE,
16+
PYPROJECT_LOCKFILE,
1317
PYPROJECT_MANIFEST,
1418
RELEASE_MANIFEST,
1519
RELEASE_MANIFEST_PACKAGE,
1620
)
1721

1822

1923
def get_native_version_errors(root: Path) -> tuple[str, ...]:
20-
"""Return an error when the Cargo workspace, Python package and release versions differ."""
24+
"""Return errors when the Cargo, Python, lockfile and release versions disagree."""
2125
with (root / CARGO_MANIFEST).open("rb") as handle:
2226
cargo: dict[str, Any] = tomllib.load(handle)
2327
with (root / PYPROJECT_MANIFEST).open("rb") as handle:
2428
pyproject: dict[str, Any] = tomllib.load(handle)
2529
release: dict[str, Any] = json.loads((root / RELEASE_MANIFEST).read_text(encoding="utf-8"))
30+
workspace_version: Any = cargo.get("workspace", {}).get("package", {}).get("version")
2631
versions: dict[str, Any] = {
27-
f"{CARGO_MANIFEST} [workspace.package] version": cargo.get("workspace", {})
28-
.get("package", {})
29-
.get("version"),
32+
f"{CARGO_MANIFEST} [workspace.package] version": workspace_version,
3033
f"{PYPROJECT_MANIFEST} [project] version": pyproject.get("project", {}).get("version"),
3134
f"{RELEASE_MANIFEST} version": release.get(RELEASE_MANIFEST_PACKAGE),
3235
f"{PYPROJECT_MANIFEST} sqlbuild-cli dependency": next(
@@ -38,7 +41,62 @@ def get_native_version_errors(root: Path) -> tuple[str, ...]:
3841
None,
3942
),
4043
}
41-
if len(set(versions.values())) == 1:
44+
errors: list[str] = []
45+
if len(set(versions.values())) != 1:
46+
listed: str = ", ".join(f"{source} is {version}" for source, version in versions.items())
47+
errors.append(f"Release versions differ: {listed}.")
48+
errors.extend(
49+
_cargo_lockfile_errors(root=root, cargo=cargo, workspace_version=workspace_version)
50+
)
51+
errors.extend(_python_lockfile_errors(root=root, pyproject=pyproject))
52+
return tuple(errors)
53+
54+
55+
def _python_lockfile_errors(*, root: Path, pyproject: dict[str, Any]) -> tuple[str, ...]:
56+
"""Report a uv.lock entry for the project that carries a version other than pyproject's."""
57+
project: dict[str, Any] = pyproject.get("project", {})
58+
with (root / PYPROJECT_LOCKFILE).open("rb") as handle:
59+
lock: dict[str, Any] = tomllib.load(handle)
60+
locked: list[Any] = [
61+
entry.get("version")
62+
for entry in lock.get("package", ())
63+
if entry.get("name") == project.get("name")
64+
and entry.get(LOCK_SOURCE_KEY) == PYPROJECT_LOCK_SOURCE
65+
]
66+
if locked == [project.get("version")]:
67+
return ()
68+
listed: str = ", ".join(map(str, locked)) or "no version"
69+
return (
70+
f"{PYPROJECT_LOCKFILE} locks {project.get('name')} at {listed}, "
71+
f"not {PYPROJECT_MANIFEST} {project.get('version')}. Run `uv lock`.",
72+
)
73+
74+
75+
def _cargo_lockfile_errors(
76+
*, root: Path, cargo: dict[str, Any], workspace_version: Any
77+
) -> tuple[str, ...]:
78+
"""Report workspace crates whose Cargo.lock entry differs from their manifest version."""
79+
expected: dict[str, Any] = {}
80+
for pattern in cargo.get("workspace", {}).get("members", ()):
81+
for directory in sorted(root.glob(pattern)):
82+
with (directory / CARGO_MANIFEST).open("rb") as handle:
83+
package: dict[str, Any] = tomllib.load(handle).get("package", {})
84+
version: Any = package.get("version")
85+
if isinstance(version, dict) and version.get("workspace") is True:
86+
version = workspace_version
87+
expected[package.get("name")] = version
88+
with (root / CARGO_LOCKFILE).open("rb") as handle:
89+
lock: dict[str, Any] = tomllib.load(handle)
90+
stale: list[str] = sorted(
91+
f"{entry['name']} is {entry.get('version')}, not {expected[entry['name']]}"
92+
for entry in lock.get("package", ())
93+
if LOCK_SOURCE_KEY not in entry
94+
and entry.get("name") in expected
95+
and entry.get("version") != expected[entry["name"]]
96+
)
97+
if not stale:
4298
return ()
43-
listed: str = ", ".join(f"{source} is {version}" for source, version in versions.items())
44-
return (f"Release versions differ: {listed}.",)
99+
return (
100+
f"{CARGO_LOCKFILE} workspace crate versions are stale: {'; '.join(stale)}. "
101+
"Run `cargo update --workspace`.",
102+
)

‎scripts/native_layering/constants.py‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@
55
POLYGLOT_PACKAGE: str = "polyglot-sql"
66
DEPENDENCY_TABLES: tuple[str, ...] = ("dependencies", "build-dependencies", "dev-dependencies")
77
CARGO_MANIFEST: str = "Cargo.toml"
8+
CARGO_LOCKFILE: str = "Cargo.lock"
89
PYPROJECT_MANIFEST: str = "pyproject.toml"
10+
PYPROJECT_LOCKFILE: str = "uv.lock"
11+
LOCK_SOURCE_KEY: str = "source"
12+
PYPROJECT_LOCK_SOURCE: dict[str, str] = {"editable": "."}
913
RELEASE_MANIFEST: str = ".release-please-manifest.json"
1014
RELEASE_MANIFEST_PACKAGE: str = "."
1115
CLI_DISTRIBUTION_PIN_PREFIX: str = "sqlbuild-cli=="

‎scripts/release_gate/__init__.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""Release pull request Verify gate checks."""
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""Release gate helpers."""

0 commit comments

Comments
 (0)