Repository navigation
Wire vocabulary correction into the pipeline #89
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # PR / push CI: correctness, compliance, MSRV, headless, and a cheap build of | |
| # every shipped target so cross-platform breakage is caught per-PR instead of | |
| # at release time. | |
| # | |
| # Design decisions (full rationale in docs/build-and-release.md#ci): | |
| # - Scripts, not inline run-blocks, for anything longer than two lines: | |
| # contributors run scripts/ci-*.sh locally and get the identical result. | |
| # - Swatinem/rust-cache over manual actions/cache: it keys on the lockfile | |
| # AND toolchain AND target list, and prunes stale artifacts, which manual | |
| # cache keys always eventually get wrong. | |
| # - Every cargo invocation is --locked: CI must build what the lockfile | |
| # says, or the lockfile is fiction. | |
| name: ci | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| # A superseded push to the same PR cancels the older run; saves the finite | |
| # macOS/Windows runner minutes for the commit that matters. | |
| concurrency: | |
| group: ci-${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| env: | |
| CARGO_TERM_COLOR: always | |
| # Incremental compilation is a lose on CI: the cache restore already covers | |
| # warm starts, and incremental artifacts bloat the cache by gigabytes. | |
| CARGO_INCREMENTAL: "0" | |
| jobs: | |
| # Lint + test on the two OSes with OS-specific code paths. Linux covers the | |
| # pure crates; macOS covers ax-edit's real FFI. Windows joins this list the | |
| # day a Windows backend exists. | |
| check: | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-24.04, macos-15] | |
| runs-on: ${{ matrix.os }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| # rustup reads rust-toolchain.toml automatically; no toolchain action | |
| # needed for the default case, which is one less pinned dependency. | |
| - name: Install Linux system dependencies | |
| if: runner.os == 'Linux' | |
| run: scripts/ci-install-linux-deps.sh | |
| - uses: Swatinem/rust-cache@v2 | |
| with: | |
| # One shared cache per OS for check+test; the build matrix below | |
| # keys separately per target. | |
| shared-key: check | |
| - run: scripts/ci-check.sh | |
| shell: bash | |
| - name: Attribute failure | |
| if: failure() | |
| run: >- | |
| echo "::error title=check failed on ${{ matrix.os }}::scripts/ci-check.sh (fmt/clippy/test) failed on ${{ matrix.os }}. Search the step log above for the first 'error' line." | |
| # MSRV is validated with its own toolchain and WITHOUT --locked semantics | |
| # being violated: same lockfile, older compiler. If this fails, either fix | |
| # the code or bump the documented MSRV in a deliberate PR. | |
| # | |
| # MSRV is 1.86: the icu_* crates (transitive via idna/url) declare | |
| # rust-version 1.86 and refuse to build on 1.85. Full rationale in | |
| # docs/build-and-release.md#msrv-policy. Keep this version in sync with | |
| # that doc and the rust-toolchain.toml comment. | |
| msrv: | |
| runs-on: ubuntu-24.04 | |
| env: | |
| MSRV: "1.86.0" | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Install Linux system dependencies | |
| run: scripts/ci-install-linux-deps.sh | |
| - name: Install MSRV toolchain | |
| run: rustup toolchain install "$MSRV" --profile minimal | |
| - uses: Swatinem/rust-cache@v2 | |
| with: | |
| shared-key: msrv | |
| - name: Build with MSRV | |
| run: cargo +"$MSRV" build --workspace --locked | |
| - name: Attribute failure | |
| if: failure() | |
| run: >- | |
| echo "::error title=MSRV build failed::Workspace does not build with Rust $MSRV. Either fix the code or bump the documented MSRV (docs/build-and-release.md#msrv-policy) in a deliberate PR." | |
| # Licence + CVE gate. Runs on PRs (catches a bad dep at review time) and | |
| # is additionally scheduled nightly by audit-nightly below, because new | |
| # CVEs arrive without any commit happening. | |
| compliance: | |
| runs-on: ubuntu-24.04 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Install Linux system dependencies | |
| run: scripts/ci-install-linux-deps.sh | |
| - uses: Swatinem/rust-cache@v2 | |
| with: | |
| shared-key: compliance | |
| # cargo-install'd tools live in ~/.cargo/bin and are cached too. | |
| cache-all-crates: "true" | |
| - run: scripts/ci-compliance.sh | |
| shell: bash | |
| - name: Upload SBOM | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: sbom | |
| path: dist/sbom/ | |
| # Cross-target build check: every triple we ship must at least COMPILE on | |
| # every PR. Cheap (check-only where possible, no packaging) and catches | |
| # cfg() breakage the day it is written, not at release tag time. | |
| build-matrix: | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - os: macos-15 | |
| target: aarch64-apple-darwin | |
| - os: macos-15 | |
| target: x86_64-apple-darwin | |
| - os: windows-2025 | |
| target: x86_64-pc-windows-msvc | |
| - os: windows-2025 | |
| # aarch64 Windows cross-compiles from the x86_64 runner; MSVC | |
| # ships the cross linker so no extra toolchain is required. | |
| target: aarch64-pc-windows-msvc | |
| - os: ubuntu-24.04 | |
| target: x86_64-unknown-linux-gnu | |
| - os: ubuntu-24.04 | |
| target: x86_64-unknown-linux-musl | |
| - os: ubuntu-24.04 | |
| target: aarch64-unknown-linux-gnu | |
| use-cross: true | |
| - os: ubuntu-24.04 | |
| target: aarch64-unknown-linux-musl | |
| use-cross: true | |
| runs-on: ${{ matrix.os }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: Swatinem/rust-cache@v2 | |
| with: | |
| # Per-target cache key: artifacts for different triples never | |
| # share object files, so sharing a cache only causes eviction. | |
| key: ${{ matrix.target }} | |
| - name: Add target | |
| if: ${{ !matrix.use-cross }} | |
| run: rustup target add ${{ matrix.target }} | |
| - name: Install Linux system dependencies | |
| if: runner.os == 'Linux' && !matrix.use-cross | |
| run: scripts/ci-install-linux-deps.sh | |
| - name: Install musl tools | |
| if: contains(matrix.target, 'musl') && !matrix.use-cross | |
| run: sudo apt-get update && sudo apt-get install -y musl-tools | |
| # Prebuilt binary instead of `cargo install cross`: same pinned version, | |
| # seconds instead of ~2 minutes of compiling cross from source per run. | |
| - name: Install cross | |
| if: matrix.use-cross | |
| uses: taiki-e/install-action@v2 | |
| with: | |
| tool: cross@0.2.5 | |
| - name: Build (${{ matrix.target }}) | |
| shell: bash | |
| run: | | |
| FLAGS=(--workspace --locked --target ${{ matrix.target }}) | |
| # musl targets build the shape we actually ship for them: the | |
| # static headless daemon (docs/build-and-release.md#matrix). The | |
| # default feature set pulls cpal -> alsa-sys, and there is no musl | |
| # ALSA on the runner or in the cross image, so a default-features | |
| # musl build can only ever fail in alsa-sys's pkg-config probe. | |
| # This mirrors scripts/build-headless.sh, not a weakening: every | |
| # crate still compiles for the triple, minus the audio backend | |
| # that a static server binary must not link anyway. | |
| case "${{ matrix.target }}" in | |
| *musl*) FLAGS+=(--no-default-features) ;; | |
| esac | |
| if [ "${{ matrix.use-cross }}" = "true" ]; then | |
| # alsa-sys uses the pkg-config crate, which refuses to run when | |
| # host != target unless explicitly allowed. The arm64 .pc files | |
| # are installed by the Cross.toml pre-build hook. | |
| export PKG_CONFIG_ALLOW_CROSS=1 | |
| export PKG_CONFIG_PATH=/usr/lib/aarch64-linux-gnu/pkgconfig | |
| cross build "${FLAGS[@]}" | |
| else | |
| cargo build "${FLAGS[@]}" | |
| fi | |
| - name: Attribute failure | |
| if: failure() | |
| shell: bash | |
| run: >- | |
| echo "::error title=build failed for ${{ matrix.target }}::Workspace does not compile for ${{ matrix.target }} on ${{ matrix.os }} (cross: ${{ matrix.use-cross || 'false' }}). The first 'error' line in the Build step above names the crate at fault." | |
| # Headless build: proves the daemon path stays display-free. Runs on a | |
| # plain ubuntu container with no X/Wayland present, which IS the deployment | |
| # environment being claimed. | |
| headless: | |
| runs-on: ubuntu-24.04 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: Swatinem/rust-cache@v2 | |
| with: | |
| shared-key: headless | |
| - name: Install musl tools | |
| run: sudo apt-get update && sudo apt-get install -y musl-tools | |
| - name: Build static headless daemon | |
| run: | | |
| rustup target add x86_64-unknown-linux-musl | |
| scripts/build-headless.sh x86_64-unknown-linux-musl | |
| - name: Smoke test with no display server | |
| # Explicitly unset every display variable; if the binary needed a | |
| # display to even start, this is where it dies. | |
| run: | | |
| env -u DISPLAY -u WAYLAND_DISPLAY dist/headless/outloud-spiked-x86_64-unknown-linux-musl dry-run "change hello to goodbye" | |
| # Performance tripwire. Every other job answers "is it correct"; this one | |
| # answers "is it still fast", which no correctness test can see: a change | |
| # that makes the overlay ten times slower produces exactly the same text | |
| # and passes every assertion in the workspace. | |
| # | |
| # It measures only PURE paths (the rolling-window animation, hypothesis | |
| # ingest, command parsing) precisely so it can run here. The richer gate in | |
| # crates/ax-edit/benches/gate.rs measures the real accessibility path and | |
| # correctly skips without a focused text field, which means on a CI runner | |
| # it protects nothing. This one cannot skip. | |
| # | |
| # Budgets carry ~10x headroom over measured cost, so a shared runner being | |
| # slow does not fail the build. See the bench's own comment for numbers. | |
| perf: | |
| runs-on: ubuntu-24.04 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: Swatinem/rust-cache@v2 | |
| with: | |
| shared-key: perf | |
| - name: Perf gate | |
| run: cargo bench -p overlay --bench perf_gate | |
| - name: Attribute failure | |
| if: failure() | |
| run: | | |
| echo "::error title=perf gate::A hot path got materially slower. This is not a correctness failure: the app still produces the right text, only later. See crates/overlay/benches/perf_gate.rs for the budgets and the reasoning." | |
| # Reproducibility tripwire: double-build on Linux and require identical | |
| # hashes. Only x86_64-linux (the cheapest runner) because determinism bugs | |
| # are almost never target-specific; the flags are shared by all targets. | |
| repro: | |
| runs-on: ubuntu-24.04 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| # build-repro.sh derives SOURCE_DATE_EPOCH from git history. | |
| fetch-depth: 1 | |
| - name: Install Linux system dependencies | |
| run: scripts/ci-install-linux-deps.sh | |
| # NOTE: no rust-cache here on purpose. A cache-restored target dir | |
| # defeats the point of proving a clean double-build is deterministic. | |
| - name: Double build and compare hashes | |
| run: REPRO_VERIFY=1 scripts/build-repro.sh | |
| - name: Attribute failure | |
| if: failure() | |
| run: >- | |
| echo "::error title=repro job failed::Either the workspace failed to compile on x86_64 Linux (see the first 'error' line above) or the double build produced different hashes; the script prints both hashes and suggests diffoscope." |