ci: add cargo-public-api check for breaking API changes (#924)

* chore: bump Rust toolchain to 1.96

* ci: add cargo-public-api breaking-change check

Adds a release-type-aware `cargo-public-api` diff job that fails on
changed/removed public items unless the PR's commits mark a breaking
(major) release. This catches source-breaking API changes that
cargo-semver-checks cannot yet detect (e.g. function return-type or
field-type changes).
This commit is contained in:
Dale Seo 2026-06-24 15:19:04 -04:00 committed by GitHub
parent b79e0d9df3
commit 42a1069833
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 72 additions and 1 deletions

View file

@ -112,6 +112,77 @@ jobs:
--only-explicit-features \ --only-explicit-features \
--features "$FEATURES" --features "$FEATURES"
public-api:
name: Public API Check
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0
# cargo-public-api builds rustdoc JSON, which requires a nightly toolchain
# to be installed (it does not need to be the default; the tool invokes it
# via `cargo +nightly`).
- name: Install Rust
uses: dtolnay/rust-toolchain@nightly
- uses: Swatinem/rust-cache@v2
- name: Install cargo-public-api
uses: taiki-e/install-action@v2
with:
tool: cargo-public-api
# Mirror the SemVer Check job's release-type detection: a breaking-change
# commit marker (`!:` or `BREAKING CHANGE:`) means a major release (any API
# change is allowed); otherwise a minor release (additions allowed, but
# changed/removed public items are denied). This catches breaking changes
# that cargo-semver-checks cannot yet detect, such as a change to a
# function's return type or a field's type.
# See https://github.com/obi1kenobi/cargo-semver-checks/issues/5
- name: Determine release type and deny flags
run: |
if git log --format=%B \
${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }} \
| grep -Eq '(^[A-Za-z0-9_-]+(\([^)]*\))?!:|^BREAKING[ -]CHANGE:)'; then
SEMVER_RELEASE_TYPE=major
else
SEMVER_RELEASE_TYPE=minor
fi
case "$SEMVER_RELEASE_TYPE" in
major) DENY="" ;;
patch) DENY="--deny added --deny changed --deny removed" ;;
*) DENY="--deny changed --deny removed" ;;
esac
echo "SEMVER_RELEASE_TYPE=$SEMVER_RELEASE_TYPE" >> "$GITHUB_ENV"
echo "DENY=$DENY" >> "$GITHUB_ENV"
- name: Check rmcp (default features)
run: |
cargo public-api \
--package rmcp \
-ss \
diff \
$DENY \
--force \
${{ github.event.pull_request.base.sha }}..${{ github.sha }}
- name: Check rmcp (all features except local)
run: |
FEATURES=$(cargo metadata --no-deps --format-version 1 \
| jq -r '[.packages[] | select(.name == "rmcp") | .features | keys[]
| select(startswith("__") | not)
| select(. != "local")] | join(",")')
cargo public-api \
--package rmcp \
--features "$FEATURES" \
-ss \
diff \
$DENY \
--force \
${{ github.event.pull_request.base.sha }}..${{ github.sha }}
spelling: spelling:
name: spell check with typos name: spell check with typos
runs-on: ubuntu-latest runs-on: ubuntu-latest

View file

@ -1,3 +1,3 @@
[toolchain] [toolchain]
channel = "1.92" channel = "1.96"
components = ["rustc", "rust-std", "cargo", "clippy", "rustfmt", "rust-docs"] components = ["rustc", "rust-std", "cargo", "clippy", "rustfmt", "rust-docs"]