Files
solvecraft/.github/workflows/ci.yml
T
bherbruck 38680cd37f chore: remove solidcore-smlib-api and the SolidCraft preset (#1823 review)
SolidCraft's C-API composition stays in SolidCraft (its crates/smlib adapter,
tangency-labs/solidcraft#8526), as brett19 asked on #1823. solidcore stays the
SMLib/SM_API port. This removes the solidcore-smlib-api crate, the
UpstreamFixes::solidcraft() preset and its records test, the difftest
"solidcraft" fix alias, plan/smlib-api and the site's SolidCraft API page.

Kept: the general kernel pieces #1823 brought (WR-596 box_face_failures_propagate,
local_operation_simple, the brep p2/p5 and primitive helpers) and every
UpstreamFixes field; SolidCraft chooses which to turn on.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 20:38:39 -04:00

430 lines
18 KiB
YAML

name: CI
on:
push:
branches: [main]
pull_request:
# The merge queue tests each PR merged with the latest main (and the PRs ahead of it).
merge_group:
workflow_dispatch:
inputs:
baseline_sha:
description: "Exact main revision used as baseline"
type: string
diagnostic_only:
description: "Explicitly skip the ratchet for a diagnostic manual run"
type: boolean
default: false
# Every job runs on Blacksmith's hosted runners (blacksmith.sh; ephemeral VMs, sponsored
# minutes, outside the org's 20-runner cap): pushes to main, every pull request (fork or not)
# and Run workflow. Builds are cached with actions/cache (Swatinem/rust-cache and the C++
# reference build cache below). To fall back when the sponsorship ends, change the
# `blacksmith-*` labels back to `ubuntu-latest`. A new push to a PR supersedes the run on its
# previous head, so cancel it. Runs on main are keyed by commit, so they never queue behind or
# cancel each other: every merge is checked.
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
env:
CARGO_TERM_COLOR: always
CARGO_INCREMENTAL: 0
# The upstream pin (UPSTREAM_REPO/UPSTREAM_VERSION/UPSTREAM_COMMIT) comes from
# scripts/upstream-pin.env, loaded by .github/actions/cpp-reference.
permissions:
contents: read
jobs:
# What this run must check. `code`: false only when a pull_request or merge_group change
# touches nothing but docs (DOCS_ONLY below); those skip the build/test/difftest jobs, whose
# required checks then report "skipped" (passing). `full`: code and not a push to main. A
# push to main was already tested on the identical tree by the merge queue, so it runs only
# what publishes main's artifacts (difftest reports for the next PRs' exact-base ratchet,
# solids report); everything else is skipped there.
changes:
runs-on: blacksmith-2vcpu-ubuntu-2404
outputs:
code: ${{ steps.c.outputs.code }}
full: ${{ steps.c.outputs.full }}
steps:
- uses: actions/checkout@v4
- id: c
env:
EVENT: ${{ github.event_name }}
BASE: ${{ github.event.pull_request.base.sha || github.event.merge_group.base_sha }}
run: scripts/ci/changes.sh "$EVENT" "$BASE" >> "$GITHUB_OUTPUT"
ci-scripts:
runs-on: blacksmith-4vcpu-ubuntu-2404
steps:
- uses: actions/checkout@v4
- run: scripts/ci/test-difftest-baseline.sh
- run: python3 -I scripts/ci/check_record_ids.py
fmt:
runs-on: blacksmith-4vcpu-ubuntu-2404
steps:
- uses: actions/checkout@v4
- run: rustup show active-toolchain || rustup toolchain install
- run: cargo fmt --all --check
- name: No new entries in frozen archives (one file per finding / upstream issue)
env:
BASE: ${{ github.event.pull_request.base.sha || github.event.merge_group.base_sha || github.event.before }}
run: scripts/ci/check-frozen-archives.sh "$BASE"
clippy:
needs: changes
if: needs.changes.outputs.full == 'true'
runs-on: blacksmith-8vcpu-ubuntu-2404
env:
SOLIDCORE_CPP_DIR: ${{ github.workspace }}/.cpp
steps:
- uses: actions/checkout@v4
- uses: Swatinem/rust-cache@v2
with:
workspaces: ". -> bin"
- run: cargo clippy --workspace --all-targets -- -D warnings
# The plain run above cannot see code behind the cpp feature (ffi bindings and the
# difftest unit runners); lint it too, with the C++ headers in place.
# difftest/cpp enables solidcore-ffi/cpp and registers the runners.
- uses: ./.github/actions/cpp-reference
- name: Clippy with the C++ feature
run: cargo clippy --workspace --all-targets --features difftest/cpp -- -D warnings
test:
needs: changes
if: needs.changes.outputs.code == 'true'
runs-on: blacksmith-8vcpu-ubuntu-2404
env:
SOLIDCORE_CPP_DIR: ${{ github.workspace }}/.cpp
steps:
- uses: actions/checkout@v4
- uses: Swatinem/rust-cache@v2
with:
workspaces: ". -> bin"
# Every test runs once, optimised, in the difftest profile (Cargo.toml
# [profile.difftest]: opt-level 3 with overflow-checks and debug-assertions still
# on; Rust floating point does not change with opt-level, and this is the build the
# difftest gate already measures parity with). Unoptimised, the suite took ~30 of
# this job's ~40 minutes; optimised it takes a few. Its solidcore is the one the
# solids report and the runner tests below reuse, so it is built once.
# cargo-nextest runs the tests of all binaries in parallel (one process per test),
# so a binary whose tests are slow no longer holds up the rest. nextest skips
# doctests, so they run separately. The two SolidCraft whole-corpus tests are
# #[ignore]d and run nightly (nightly.yml), as before.
- uses: taiki-e/install-action@v2
if: needs.changes.outputs.full == 'true'
with:
tool: cargo-nextest@0.9.150
- name: Workspace tests (difftest profile)
if: needs.changes.outputs.full == 'true'
run: cargo nextest run --workspace --cargo-profile difftest --no-fail-fast
- name: Doctests (difftest profile)
if: needs.changes.outputs.full == 'true'
run: cargo test --workspace --profile difftest --doc
# Solids: the primitives example's per-shape results (built, blocker, checks) for
# the site's Solids page. It reuses the difftest-profile build above and exits 1
# while any primitive fails, so only a crash (or a missing JSON) fails the job.
- name: Solids report (primitives example)
run: |
set +e
cargo run --profile difftest -p solidcore --example primitives -- --json reports/solids/solids.json
rc=$?
set -e
[ "$rc" -le 1 ] && test -s reports/solids/solids.json
# main's solids report is published from here (site), never committed.
- name: Upload solids report
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
uses: actions/upload-artifact@v4
with:
name: solids-report
path: reports/solids/solids.json
if-no-files-found: error
retention-days: 90
# The difftest runners' own unit tests (guards, decoders, probes) need the C++
# reference. Same profile as the difftest job's binary (Cargo.toml
# [profile.difftest]); here rather than after the difftest run, so they no longer
# add to its wall time.
- uses: ./.github/actions/cpp-reference
if: needs.changes.outputs.full == 'true'
- name: Difftest runner tests
if: needs.changes.outputs.full == 'true'
run: cargo test --profile difftest -p difftest -p solidcore-ffi --features cpp
wasm:
needs: changes
if: needs.changes.outputs.full == 'true'
runs-on: blacksmith-4vcpu-ubuntu-2404
steps:
- uses: actions/checkout@v4
- uses: Swatinem/rust-cache@v2
with:
workspaces: ". -> bin"
- run: rustup target add wasm32-unknown-unknown
- run: cargo check -p solidcore --target wasm32-unknown-unknown
# Same packaging check the release workflow runs before publishing (no secrets).
publish-dry-run:
needs: changes
if: needs.changes.outputs.full == 'true'
runs-on: blacksmith-4vcpu-ubuntu-2404
steps:
- uses: actions/checkout@v4
- uses: Swatinem/rust-cache@v2
with:
workspaces: ". -> bin"
- run: cargo publish -p solidcore --locked --dry-run
deny:
needs: changes
if: needs.changes.outputs.full == 'true'
runs-on: blacksmith-2vcpu-ubuntu-2404
steps:
- uses: actions/checkout@v4
- uses: EmbarkStudios/cargo-deny-action@v2
with:
command: check licenses bans sources
# The difftest gate is split so its slow parts overlap (they used to run one after
# another: exact-base wait, sweep, perturbation). `difftest-baseline` fetches and
# validates the exact-base artifact while `difftest-sweep` runs the green sweep and
# `difftest-perturb` the (mandatory) perturbation check; `difftest-compare` then holds the
# sweep's finished reports to the validated base with the same ratchet as `--baseline`;
# `difftest` (the required check's name) fails unless every part succeeded.
difftest-baseline:
needs: changes
if: needs.changes.outputs.full == 'true'
runs-on: blacksmith-2vcpu-ubuntu-2404
permissions:
contents: read
actions: read # reads the exact base revision's run and report artifact
outputs:
baseline: ${{ steps.validate.outputs.baseline }}
steps:
- uses: actions/checkout@v4
# Fail closed: any validation error fails this step (and so the job and `difftest`).
# Only a genuine bootstrap/diagnostic run, which the helper reports by printing
# nothing, continues without a base.
- name: Validate exact-base report artifact
id: validate
env:
GH_TOKEN: ${{ github.token }}
EVENT: ${{ github.event_name }}
BASE: ${{ github.event.pull_request.base.sha || github.event.merge_group.base_sha || github.event.before || inputs.baseline_sha }}
run: |
arg=$(scripts/ci/difftest-baseline.sh "$EVENT" "$BASE" "$RUNNER_TEMP/difftest-baseline" --current-plan "$GITHUB_WORKSPACE")
if [ -n "$arg" ]; then
echo "baseline=yes" >> "$GITHUB_OUTPUT"
else
echo "baseline=no" >> "$GITHUB_OUTPUT"
fi
# Hand the validated reports and their provenance to difftest-compare.
- name: Upload validated baseline
if: steps.validate.outputs.baseline == 'yes'
uses: actions/upload-artifact@v4
with:
name: difftest-validated-baseline
path: ${{ runner.temp }}/difftest-baseline
if-no-files-found: error
retention-days: 1
difftest-sweep:
# Starts right after `changes`: it is the critical path. Clippy with the cpp feature and
# the runner tests run in the clippy and test jobs, in parallel with this one. Also runs on
# pushes to main, where its reports are main's published set.
needs: changes
if: needs.changes.outputs.code == 'true'
runs-on: blacksmith-16vcpu-ubuntu-2404
permissions:
contents: read
actions: read # scripts/fetch-reports.sh reads main's report artifact
env:
SOLIDCORE_CPP_DIR: ${{ github.workspace }}/.cpp
outputs:
any: ${{ steps.green.outputs.any }}
steps:
- uses: actions/checkout@v4
- name: Select optional native execution
id: green
run: |
if grep -Eqs '^\s*status\s*=\s*"(green|partial)"' plan/units/*.toml; then
echo "any=true" >> "$GITHUB_OUTPUT"
else
echo "any=false" >> "$GITHUB_OUTPUT"
echo "No current green units; exact-base retention is still required."
fi
- name: Upstream sources, sccache and the C++ reference
if: steps.green.outputs.any == 'true'
uses: ./.github/actions/cpp-reference
- uses: Swatinem/rust-cache@v2
if: steps.green.outputs.any == 'true'
with:
workspaces: ". -> bin"
key: difftest
# Reports are not committed. main's latest set (uploaded below) only orders the
# --jobs queue, slowest unit first (times.tsv: each unit's wall time in main's last
# run; else its case count; without either, an estimate from its fixtures).
- name: Fetch main's difftest reports (queue order)
if: steps.green.outputs.any == 'true'
env:
GH_TOKEN: ${{ github.token }}
run: scripts/fetch-reports.sh || echo "no previous reports; queue ordered by fixture size"
- name: Difftest green units
if: steps.green.outputs.any == 'true'
# Optimised profile (Cargo.toml [profile.difftest]): identical results, about 20x
# faster than the debug build that used to take ~35 min here. --jobs 0: one child
# process per unit, one per vCPU (same output and reports as a serial run). The
# exact-base ratchet is applied afterwards by difftest-compare, to
# these reports; the per-unit status and value-share gates run here.
run: cargo run --profile difftest -p difftest --features cpp -- --green --jobs 0
- name: Keep only reports of green/partial units
if: steps.green.outputs.any == 'true' && !cancelled()
run: |
python3 - <<'PY'
import pathlib, sys
sys.path.insert(0, "harness/plan")
import units
keep = {u["id"] for u in units.load_units() if u.get("status") in ("green", "partial")}
for p in pathlib.Path("reports/difftest").glob("*.json"):
if p.stem not in keep:
p.unlink()
PY
# Carries the finished reports to difftest-compare (run-internal, short-lived).
- name: Upload sweep reports
if: steps.green.outputs.any == 'true' && !cancelled()
uses: actions/upload-artifact@v4
with:
name: difftest-sweep-reports
path: reports/difftest/*.json
if-no-files-found: error
retention-days: 1
# main's reports are published from here (site, checklist case counts,
# scripts/fetch-reports.sh), never committed.
- name: Upload difftest reports
if: github.event_name == 'push' && github.ref == 'refs/heads/main' && steps.green.outputs.any == 'true' && !cancelled()
uses: actions/upload-artifact@v4
with:
name: difftest-reports
path: |
reports/difftest/*.json
reports/difftest/times.tsv
if-no-files-found: error
retention-days: 90
- name: sccache stats
if: always() && steps.green.outputs.any == 'true'
run: sccache --show-stats || true
# Mandatory on every pull_request and merge_group run, in parallel with the sweep.
difftest-perturb:
needs: changes
if: needs.changes.outputs.full == 'true'
runs-on: blacksmith-16vcpu-ubuntu-2404
env:
SOLIDCORE_CPP_DIR: ${{ github.workspace }}/.cpp
steps:
- uses: actions/checkout@v4
- name: Select optional native execution
id: green
run: |
if grep -Eqs '^\s*status\s*=\s*"(green|partial)"' plan/units/*.toml; then
echo "any=true" >> "$GITHUB_OUTPUT"
else
echo "any=false" >> "$GITHUB_OUTPUT"
echo "No current green units; nothing to perturb."
fi
- name: Upstream sources, sccache and the C++ reference
if: steps.green.outputs.any == 'true'
uses: ./.github/actions/cpp-reference
- uses: Swatinem/rust-cache@v2
if: steps.green.outputs.any == 'true'
with:
workspaces: ". -> bin"
key: difftest
- name: Difftest perturbation check
if: steps.green.outputs.any == 'true'
run: cargo run --profile difftest -p difftest --features cpp -- --green --perturb --jobs 0 --no-report
- name: sccache stats
if: always() && steps.green.outputs.any == 'true'
run: sccache --show-stats || true
# The exact-base ratchet. Needs both the validated base and the finished sweep: if either
# job fails or is skipped, this job is skipped and `difftest` fails.
difftest-compare:
needs: [changes, difftest-baseline, difftest-sweep]
if: needs.changes.outputs.full == 'true'
runs-on: blacksmith-4vcpu-ubuntu-2404
steps:
- uses: actions/checkout@v4
- uses: Swatinem/rust-cache@v2
if: needs.difftest-baseline.outputs.baseline == 'yes' && needs.difftest-sweep.outputs.any == 'true'
with:
workspaces: ". -> bin"
key: difftest-compare
- name: Download validated baseline
if: needs.difftest-baseline.outputs.baseline == 'yes'
uses: actions/download-artifact@v4
with:
name: difftest-validated-baseline
path: ${{ runner.temp }}/difftest-baseline
- name: Download sweep reports
if: needs.difftest-sweep.outputs.any == 'true'
uses: actions/download-artifact@v4
with:
name: difftest-sweep-reports
path: ${{ runner.temp }}/difftest-sweep
# No C++ and no cases: the same ratchet as --baseline, over the finished reports.
- name: Compare sweep reports with the exact base
if: needs.difftest-baseline.outputs.baseline == 'yes' && needs.difftest-sweep.outputs.any == 'true'
run: >-
cargo run -p difftest --
--compare-reports "$RUNNER_TEMP/difftest-sweep"
--baseline "$RUNNER_TEMP/difftest-baseline/reports"
# The required check ("difftest"). Runs even when a part failed or was skipped, and
# succeeds only if every part succeeded.
difftest:
needs: [changes, difftest-baseline, difftest-sweep, difftest-perturb, difftest-compare]
if: always()
runs-on: blacksmith-2vcpu-ubuntu-2404
steps:
- name: Require every difftest part
env:
CHANGES: ${{ needs.changes.result }}
FULL: ${{ needs.changes.outputs.full }}
SWEEP: ${{ needs.difftest-sweep.result }}
RESULTS: ${{ join(needs.*.result, ' ') }}
run: |
echo "changes difftest-baseline difftest-sweep difftest-perturb difftest-compare: $RESULTS"
[ "$CHANGES" = success ] || { echo "the changes job did not succeed"; exit 1; }
if [ "$FULL" != true ]; then
# docs-only change (everything skipped), or a push to main (only the sweep, which
# publishes main's reports, runs; the merge queue already ran the full gate)
case "$SWEEP" in success|skipped) echo "not a full run (full=$FULL)"; exit 0;; esac
echo "difftest-sweep: $SWEEP"; exit 1
fi
for r in $RESULTS; do
[ "$r" = success ] || { echo "a difftest part did not succeed"; exit 1; }
done