Repository navigation
169 lines (164 loc) · 8.14 KB
/
Copy pathapi-diff.yml
File metadata and controls
169 lines (164 loc) · 8.14 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
name: Public API Snapshot
# Public-API surface job. Runs `cargo public-api` against each
# workspace crate that ships a stable API surface (oxicode-ai, oxicode-agent,
# oxicode-sdk). On every run it captures the surface and uploads it as a
# workflow artifact. On pull requests it additionally ENFORCES the
# breaking-change policy: the PR surface is diffed against `origin/main`
# and any removed symbol fails the job unless CHANGELOG.md carries a
# `### Breaking` or `### Removed` heading under `## [Unreleased]`.
#
# Snapshot capture is itself strict: a failed or empty per-crate
# snapshot fails the job, so a system-dep or rustdoc-JSON toolchain
# mismatch cannot silently capture zero baselines. The enforcement
# step is fail-safe: if a per-crate `main` baseline cannot be captured,
# that crate is skipped with a warning rather than false-positive
# failing the job.
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
env:
CARGO_TERM_COLOR: always
# The workspace `rust-toolchain.toml` pins `channel = "1.96"`; the
# toolchain override below installs matching components so cargo can
# actually use it. ci.yml has the same RUST_VERSION_MSRV env entry
# and the same justification comment.
RUST_VERSION_MSRV: "1.96"
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
api-diff:
name: Public API Snapshot
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ env.RUST_VERSION_MSRV }}
# cargo-public-api builds rustdoc JSON internally, which is
# still nightly-only. Each release of cargo-public-api
# understands only a window of nightly rustdoc JSON outputs
# (see the compatibility matrix in cargo-public-api/README.md),
# so pinning a specific date keeps the job reproducible across
# CI runs. nightly-2026-05-02 (rustc 1.97.0-nightly) is the
# first pinned nightly >= the workspace MSRV (1.96); the old
# pin (nightly-2025-08-02, rustc 1.90.0) was below MSRV and
# could not compile the workspace. cargo-public-api 0.52.0
# dropped its `--toolchain` flag; the invocation must use
# `cargo +nightly-2026-05-02 public-api` explicitly so the
# nightly (not the rust-toolchain.toml 1.96 default) builds
# the rustdoc JSON.
- uses: dtolnay/rust-toolchain@master
with:
toolchain: nightly-2026-05-02
- name: Install system dependencies
# `oxicode-agent` pulls in `oxibrowser`, and Cargo.lock still
# contains `openssl` / `openssl-sys` entries (3 hits as of
# this commit). cargo-public-api compiles each crate's
# rustdoc JSON, which performs a full build, so any
# transitive openssl-sys link step needs the system libssl.
# ci.yml's smoke-test (L93) and msrv (L143) install the
# same package for the same reason.
run: sudo apt-get update && sudo apt-get install -y libssl-dev libdbus-1-dev
- uses: Swatinem/rust-cache@v2
with:
shared-key: "api-diff"
- name: Install cargo-public-api
# `taiki-e/install-action` does not list cargo-public-api in
# its supported-tools manifest, so we install via `cargo
# install --locked` (matches the brief). This is the slow
# path; revisit if it dominates job time.
run: cargo install cargo-public-api --locked
- name: Snapshot public API surface
# Capture stdout per crate; route stderr to a sibling file so
# a tool warning does not pollute the API listing.
# Failure handling is intentionally strict: a missing or
# empty snapshot is a `::error` (not `::warning`) so the job
# cannot silently capture zero baselines and we cannot
# proceed with a false-positive "DONE". The three target
# crates are stable-API-bearing and MUST produce a non-empty
# snapshot; if any one is missing, treat that as a build
# failure (most often a system-dep or rustdoc-JSON toolchain
# mismatch). Per-crate .stderr artifacts are uploaded for
# triage.
run: |
mkdir -p api-snapshots
failed=0
for crate in oxicode-ai oxicode-agent oxicode-sdk; do
if cargo +nightly-2026-05-02 public-api -p "$crate" \
> "api-snapshots/${crate}.txt" \
2> "api-snapshots/${crate}.stderr"; then
lines=$(wc -l < "api-snapshots/${crate}.txt" | tr -d ' ')
if [ "$lines" = "0" ]; then
echo "::error::cargo public-api for ${crate} succeeded but produced 0 lines — likely a rustdoc-JSON toolchain or system-dep mismatch; see api-snapshots/${crate}.stderr"
failed=1
else
echo "captured ${crate}: ${lines} lines"
fi
else
echo "::error::cargo public-api failed for ${crate}; see api-snapshots/${crate}.stderr"
failed=1
fi
done
if [ "$failed" -ne 0 ]; then
echo "::error::One or more snapshots failed; refusing to upload empty baselines."
exit 1
fi
- name: Upload snapshots as artifact
uses: actions/upload-artifact@v4
with:
name: public-api-snapshots
path: api-snapshots/
if-no-files-found: warn
- name: Enforce no undocumented API removals (PR only)
# On pull requests, diff the PR surface (captured above in
# api-snapshots/) against origin/main. Any symbol present on main
# but absent on the PR is a REMOVAL. Removals are allowed only when
# CHANGELOG.md carries a `### Breaking` or `### Removed` heading
# under `## [Unreleased]`. Fail-safe: if a per-crate main baseline
# cannot be captured (empty), that crate is skipped with a warning
# rather than false-positive failing the job.
if: github.event_name == 'pull_request'
run: |
set -uo pipefail
if ! git fetch origin main --depth=50; then
echo "::warning::Could not fetch origin/main (transient network issue); skipping API-removal enforcement on this run."
exit 0
fi
if ! git worktree add "$RUNNER_TEMP/main-baseline" origin/main; then
echo "::warning::Could not create worktree of origin/main; skipping API-removal enforcement on this run."
exit 0
fi
mkdir -p api-snapshots-main
removed_any=0
for crate in oxicode-ai oxicode-agent oxicode-sdk; do
( cd "$RUNNER_TEMP/main-baseline" && cargo +nightly-2026-05-02 public-api -p "$crate" ) \
> "api-snapshots-main/${crate}.txt" 2> "api-snapshots-main/${crate}.stderr" || true
lines=$( wc -l < "api-snapshots-main/${crate}.txt" | tr -d ' ' )
if [ "$lines" = "0" ]; then
echo "::warning::main baseline for ${crate} is empty; skipping enforcement for this crate. See api-snapshots-main/${crate}.stderr."
continue
fi
removed=$( comm -23 \
<( sort "api-snapshots-main/${crate}.txt" ) \
<( sort "api-snapshots/${crate}.txt" ) )
if [ -n "$removed" ]; then
echo "::error::${crate}: public-API symbols removed relative to main:"
printf '%s\n' "$removed" | sed 's/^/ - /'
removed_any=1
fi
done
git worktree remove "$RUNNER_TEMP/main-baseline" --force || true
if [ "$removed_any" -ne 0 ]; then
if awk '/^## \[Unreleased\]/{f=1;next} f&&/^## \[/{exit} f' CHANGELOG.md \
| grep -qE '^### (Breaking|Removed)'; then
echo "::notice::API removals detected and covered by a Breaking/Removed entry under [Unreleased] in CHANGELOG.md."
else
echo "::error::Public-API symbols were removed without a '### Breaking' or '### Removed' entry under '## [Unreleased]' in CHANGELOG.md."
echo "::error::Document each removed symbol and its replacement there, or restore the symbols."
exit 1
fi
fi