name: CI
# Reproduce every job locally: `cargo xtask ci`
# Job → command map: `.claude/rules/ci.md`
# Changing a `run:` here without updating those two is a bug.
on:
push:
branches: [main, master]
pull_request:
# Cancel superseded runs on the same ref (rapid PR pushes / force-pushes).
# Always cancel here: CI never signs or publishes artifacts.
concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
CARGO_TERM_COLOR: always
CARGO_INCREMENTAL: 0
RUSTFLAGS: "-D warnings"
RUSTC_WRAPPER: sccache
SCCACHE_GHA_ENABLED: "true"
# PR caches are isolated to refs/pull/*/merge and still consume the repo's
# 10 GB allowance. Let PRs reuse master's compiler objects without filling
# the cache with entries no other PR can read.
SCCACHE_GHA_RW_MODE: ${{ (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/main') && 'READ_WRITE' || 'READ_ONLY' }}
# Cache policy:
# - rust-cache keeps Cargo's registry/git inputs only; caching target/ produced
# 0.8-1.4 GB archives whose transfer often cost more than the cache saved.
# - sccache stores compiler outputs individually, so jobs fetch only objects
# they need and can share unchanged dependencies across Cargo commands.
# - only master/main writes CI caches; PR-scoped entries cannot warm other PRs.
jobs:
fmt:
name: rustfmt
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt
- run: cargo fmt --all -- --check
typos:
name: typos
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: crate-ci/typos@8a48f81b6c64dcfea44b3633223084c4be58ac5f # v1.49.0
with:
config: .config/typos.toml
# The single-source-of-truth guards in .ast-grep/rules: each names the module
# that owns a decision and fails on its ingredients anywhere else.
ast-grep:
name: ast-grep
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: ast-grep/action@370e627637505696e4365db4a149af9f2311afe8 # v1.4
with:
version: 0.45.1
publish-closure:
name: publish closure
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
- uses: mozilla-actions/sccache-action@fc920bf0ec8de6ee65d409111f7ec508035751ba # v0.0.11
with:
version: "v0.16.0"
- uses: Swatinem/rust-cache@v2
with:
prefix-key: v2-cargo
shared-key: linux-stable-debug
cache-targets: false
save-if: ${{ github.ref == 'refs/heads/master' || github.ref == 'refs/heads/main' }}
- run: cargo xtask release check-publish
# The shell equivalent of `rustfmt` + `clippy`. The file set is every tracked
# file shfmt recognises as a shell script, so a new script is covered the day
# it lands — including the extensionless `.agents/*` ones, which it detects by
# shebang. shfmt's formatting options come from `.editorconfig`; passing it a
# printer flag here would silently discard that file.
shell:
name: shell
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: taiki-e/install-action@v2
with:
tool: shellcheck,shfmt
- name: List tracked shell scripts
run: git ls-files -z | xargs -0 shfmt -f | tee "${RUNNER_TEMP}/shell-scripts"
- name: shellcheck
run: xargs shellcheck < "${RUNNER_TEMP}/shell-scripts"
- name: shfmt
run: xargs shfmt -d < "${RUNNER_TEMP}/shell-scripts"
clippy:
name: clippy
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
with:
components: clippy
- uses: mozilla-actions/sccache-action@fc920bf0ec8de6ee65d409111f7ec508035751ba # v0.0.11
with:
version: "v0.16.0"
- uses: Swatinem/rust-cache@v2
with:
prefix-key: v2-cargo
shared-key: linux-stable-debug
cache-targets: false
save-if: ${{ github.ref == 'refs/heads/master' || github.ref == 'refs/heads/main' }}
- run: |
sudo apt-get update
sudo apt-get install -y \
libudev-dev \
gcc g++ clang libfontconfig-dev libwayland-dev \
libxkbcommon-x11-dev libx11-xcb-dev \
libssl-dev libzstd-dev pkg-config
- run: cargo clippy --workspace --all-targets -- -D warnings
# The floor tracks current stable (see `rust-version` in the root Cargo.toml),
# so what this job buys is keeping that number honest: it fails the moment
# someone reaches for an API newer than what `cargo install` users are
# promised, which the floating-stable jobs above cannot notice. Both
# first-class platforms, because macOS and Linux compile disjoint cfg-gated
# code (evdev/zbus vs the ObjC surface) and a single runner would miss a
# too-new stabilization on the other side.
msrv:
name: MSRV (cargo check, ${{ matrix.os }})
runs-on: ${{ matrix.os }}
env:
# `rust-toolchain.toml` pins the channel to `stable`, and rustup honours
# that file over whatever toolchain this job installs — so for its whole
# life this check silently ran stable and never once verified the floor.
# RUSTUP_TOOLCHAIN outranks the file; keep it in step with the version
# installed above and with `rust-version`.
RUSTUP_TOOLCHAIN: "1.98"
strategy:
fail-fast: false
matrix:
os: [macos-latest, ubuntu-latest]
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@1.98
- uses: mozilla-actions/sccache-action@fc920bf0ec8de6ee65d409111f7ec508035751ba # v0.0.11
with:
version: "v0.16.0"
- uses: Swatinem/rust-cache@v2
with:
prefix-key: v2-cargo
# Host OS splits Linux vs macOS; the rustc version is already in the key.
shared-key: msrv-debug
cache-targets: false
save-if: ${{ github.ref == 'refs/heads/master' || github.ref == 'refs/heads/main' }}
- if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y \
libudev-dev \
gcc g++ clang libfontconfig-dev libwayland-dev \
libxkbcommon-x11-dev libx11-xcb-dev \
libssl-dev libzstd-dev pkg-config
- run: cargo check --workspace --all-targets
# Stable Linux typecheck is covered by `clippy` above (clippy subsumes
# `cargo check` for the same --workspace --all-targets set — same rationale
# as clippy-windows). The declared floor stays on the `msrv` matrix.
docs:
name: rustdoc (non-GUI crates)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
- uses: mozilla-actions/sccache-action@fc920bf0ec8de6ee65d409111f7ec508035751ba # v0.0.11
with:
version: "v0.16.0"
- uses: Swatinem/rust-cache@v2
with:
prefix-key: v2-cargo
# Share debug deps with clippy/test-linux; RUSTDOCFLAGS is step-scoped
# below so it does not fork the rust-cache env hash.
shared-key: linux-stable-debug
cache-targets: false
save-if: ${{ github.ref == 'refs/heads/master' || github.ref == 'refs/heads/main' }}
- run: |
sudo apt-get update
sudo apt-get install -y \
libudev-dev \
pkg-config
# Everything except the GPUI crates, which would drag the whole graphics
# toolchain into this job. Excluding by name rather than listing the
# covered crates keeps a new crate documented by default — the hid-only
# version of this step let five broken links rot in `openlogi-core`.
- name: cargo doc
env:
RUSTDOCFLAGS: "-D warnings"
run: |
cargo doc --workspace --no-deps --document-private-items \
--exclude openlogi-ui \
--exclude openlogi-desktop \
--exclude openlogi-overlay \
--exclude openlogi-agent
# openlogi-core / openlogi-hid / openlogi-assets / openlogi / openlogi-hook
# all compile on Linux today (the hook crate has stubs for non-macOS targets).
# openlogi-desktop needs GPUI's wayland / x11 system libs; the same dependency set
# the clippy job installs is sufficient. Test workspace there as well so the
# config-roundtrip and binding suites run on a non-macOS host.
test-linux:
name: tests (linux)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
- uses: mozilla-actions/sccache-action@fc920bf0ec8de6ee65d409111f7ec508035751ba # v0.0.11
with:
version: "v0.16.0"
- uses: Swatinem/rust-cache@v2
with:
prefix-key: v2-cargo
shared-key: linux-stable-debug
cache-targets: false
save-if: ${{ github.ref == 'refs/heads/master' || github.ref == 'refs/heads/main' }}
- run: |
sudo apt-get update
sudo apt-get install -y \
libudev-dev \
gcc g++ clang libfontconfig-dev libwayland-dev \
libxkbcommon-x11-dev libx11-xcb-dev \
libssl-dev libzstd-dev pkg-config
- run: cargo test --workspace --exclude openlogi-desktop
test-macos:
name: tests (macos, ${{ matrix.arch }})
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: false
matrix:
include:
- arch: arm64
runner: macos-latest
- arch: x86_64
runner: macos-15-intel
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
- uses: mozilla-actions/sccache-action@fc920bf0ec8de6ee65d409111f7ec508035751ba # v0.0.11
with:
version: "v0.16.0"
- uses: Swatinem/rust-cache@v2
with:
prefix-key: v2-cargo
# Host triple already splits arm64 vs x86_64 in the final cache key.
shared-key: macos-stable-debug
cache-targets: false
save-if: ${{ github.ref == 'refs/heads/master' || github.ref == 'refs/heads/main' }}
- name: Verify runner architecture
run: test "$(uname -m)" = "${{ matrix.arch }}"
- run: cargo test --workspace --all-targets
# Runs the workspace's tests on Windows — including the `cfg(windows)` ones,
# which `clippy (windows)` only compiles and no other host can execute. The
# same shape as the Linux job: everything but the desktop crate.
test-windows:
name: tests (windows)
runs-on: windows-latest
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
- uses: mozilla-actions/sccache-action@fc920bf0ec8de6ee65d409111f7ec508035751ba # v0.0.11
with:
version: "v0.16.0"
- uses: Swatinem/rust-cache@v2
with:
prefix-key: v2-cargo
shared-key: windows-stable-debug
cache-targets: false
save-if: ${{ github.ref == 'refs/heads/master' || github.ref == 'refs/heads/main' }}
- run: cargo test --workspace --exclude openlogi-desktop
cargo-deny:
name: cargo-deny
runs-on: ubuntu-latest
env:
# cargo-deny calls `rustc -vV` through cargo metadata but does not compile.
# This job deliberately skips the sccache setup used by compilation jobs.
RUSTC_WRAPPER: ""
steps:
- uses: actions/checkout@v7
# Prebuilt binary install is faster than cargo-deny-action's bootstrap path
# (~100s observed); advisory DB is still fetched by `cargo deny check`.
- uses: taiki-e/install-action@v2
with:
tool: cargo-deny
# Global flags before subcommand (matches cargo-deny-action defaults).
# The graph is rooted at the CLI — i.e. exactly the crates published to
# crates.io. cargo-deny picks its roots from the manifest it is given, so
# this was implicit while `openlogi` was the workspace root package; with a
# virtual root it must be explicit, or the git-pinned gpui/zed tree enters
# the graph and the policy in `.cargo/deny.toml` no longer holds. Widening
# the scope to the app crates is tracked separately — see that file.
- run: cargo deny --config .cargo/deny.toml --all-features --manifest-path crates/openlogi/Cargo.toml check
clippy-windows:
name: clippy (windows)
runs-on: windows-latest
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
with:
components: clippy
- uses: mozilla-actions/sccache-action@fc920bf0ec8de6ee65d409111f7ec508035751ba # v0.0.11
with:
version: "v0.16.0"
- uses: Swatinem/rust-cache@v2
with:
prefix-key: v2-cargo
shared-key: windows-stable-debug
cache-targets: false
save-if: ${{ github.ref == 'refs/heads/master' || github.ref == 'refs/heads/main' }}
# Whole workspace, matching the Linux jobs — GPUI's DirectX backend
# covers the GUI on Windows. clippy subsumes `cargo check` and
# additionally lints the Windows-only code paths (the WH_MOUSE_LL hook,
# SendInput synthesis, native HID writer, the composite HID++ channel,
# and the agent's HKCU-Run autostart) that the Linux/macOS clippy jobs
# never compile.
- run: cargo clippy --workspace --all-targets -- -D warnings
wasm:
name: wasm (portable crates)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
with:
targets: wasm32-unknown-unknown
- uses: mozilla-actions/sccache-action@fc920bf0ec8de6ee65d409111f7ec508035751ba # v0.0.11
with:
version: "v0.16.0"
- uses: Swatinem/rust-cache@v2
with:
prefix-key: v2-cargo
# Only Cargo inputs are cached, so the host's stable registry/git
# cache is reusable even though rustc emits wasm objects via sccache.
shared-key: linux-stable-debug
cache-targets: false
save-if: ${{ github.ref == 'refs/heads/master' || github.ref == 'refs/heads/main' }}
# A portability gate, not a deliverable: nothing here is built for the
# browser. `wasm32-unknown-unknown` has no OS under it, so a crate that
# picks up a filesystem, a randomness source or a thread stops compiling
# here and nowhere else. The crate list lives in xtask
# (`WASM_PORTABLE_CRATES`) and the drift test keeps the two in step.
- run: cargo check -p openlogi-device-registry -p openlogi-hidpp -p openlogi-device --target wasm32-unknown-unknown
# `openlogi-core` earns its place only with `fs` off: that feature is
# the config file, and a config file needs a filesystem.
- run: cargo check -p openlogi-core --no-default-features --target wasm32-unknown-unknown