mirror of
https://github.com/nearai/ironclaw.git
synced 2026-09-03 08:06:01 +08:00
* Enable distributed sccache in key CI workflows * Fix distributed sccache action config writing * Skip dist sccache for wasmtime crate tests * Escape distributed sccache config values * Disable dist sccache for wasm product adapter tests * Avoid dist sccache for Reborn wasmtime test graphs * Use OVH Redis as shared sccache backend * Use cache-only sccache for Reborn CLI smoke * Warn on sccache dist status probe failure
370 lines
15 KiB
YAML
370 lines
15 KiB
YAML
name: Code Style
|
|
on:
|
|
pull_request:
|
|
merge_group:
|
|
branches:
|
|
- main
|
|
types:
|
|
- checks_requested
|
|
# Pushes to main refresh the rust-cache entries that PR and merge-group
|
|
# jobs restore from. Non-push runs stay restore-only.
|
|
push:
|
|
branches:
|
|
- main
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
concurrency:
|
|
group: code-style-${{ github.head_ref || github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
env:
|
|
# Tests must never touch the real OS keychain (macOS Keychain auth dialog /
|
|
# Linux Secret Service). Guarded by src/secrets/keychain.rs::os_keychain_suppressed:
|
|
# cfg!(test) covers unit tests; this covers integration/e2e that link the
|
|
# non-cfg(test) library.
|
|
IRONCLAW_DISABLE_OS_KEYCHAIN: "1"
|
|
|
|
jobs:
|
|
changes:
|
|
name: Detect code changes
|
|
runs-on: ubuntu-latest
|
|
outputs:
|
|
has_code: ${{ steps.non_pr.outputs.has_code || steps.diff.outputs.has_code }}
|
|
has_boundary_check: ${{ steps.non_pr.outputs.has_boundary_check || steps.diff.outputs.has_boundary_check }}
|
|
has_reborn_cli: ${{ steps.non_pr.outputs.has_reborn_cli || steps.diff.outputs.has_reborn_cli }}
|
|
steps:
|
|
- id: non_pr
|
|
if: github.event_name == 'push'
|
|
run: |
|
|
echo "has_code=true" >> "$GITHUB_OUTPUT"
|
|
echo "has_boundary_check=true" >> "$GITHUB_OUTPUT"
|
|
echo "has_reborn_cli=true" >> "$GITHUB_OUTPUT"
|
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
|
if: github.event_name == 'pull_request' || github.event_name == 'merge_group'
|
|
with:
|
|
fetch-depth: 0
|
|
persist-credentials: false
|
|
- id: diff
|
|
if: github.event_name == 'pull_request' || github.event_name == 'merge_group'
|
|
env:
|
|
BASE_SHA: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.event.merge_group.base_sha }}
|
|
HEAD_SHA: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
|
|
run: |
|
|
CHANGED_FILES="$(git diff --name-only "$BASE_SHA"..."$HEAD_SHA")"
|
|
|
|
if printf '%s\n' "$CHANGED_FILES" | grep -Eq '^(src/|crates/|channels-src/|tools-src/|tests/|migrations/|Cargo\.toml$|Cargo\.lock$|Dockerfile$|build\.rs$|\.gitignore$|scripts/check_no_panics\.py$|scripts/check_gateway_boundaries\.py$|\.github/workflows/code_style\.yml$)'; then
|
|
echo "has_code=true" >> "$GITHUB_OUTPUT"
|
|
else
|
|
echo "has_code=false" >> "$GITHUB_OUTPUT"
|
|
echo "No code changes — style checks will be skipped"
|
|
fi
|
|
|
|
if printf '%s\n' "$CHANGED_FILES" | grep -Eq '^(scripts/check_gateway_boundaries\.py$|\.github/workflows/code_style\.yml$)'; then
|
|
echo "has_boundary_check=true" >> "$GITHUB_OUTPUT"
|
|
else
|
|
echo "has_boundary_check=false" >> "$GITHUB_OUTPUT"
|
|
fi
|
|
|
|
if printf '%s\n' "$CHANGED_FILES" | grep -Eq '^(crates/ironclaw_reborn/|crates/ironclaw_reborn_cli/|crates/ironclaw_reborn_config/|crates/ironclaw_architecture/tests/reborn_dependency_boundaries\.rs$|Cargo\.toml$|Cargo\.lock$|\.github/workflows/code_style\.yml$)'; then
|
|
echo "has_reborn_cli=true" >> "$GITHUB_OUTPUT"
|
|
else
|
|
echo "has_reborn_cli=false" >> "$GITHUB_OUTPUT"
|
|
fi
|
|
|
|
clippy-matrix:
|
|
name: Configure clippy matrix
|
|
needs: changes
|
|
if: needs.changes.outputs.has_code == 'true'
|
|
runs-on: ubuntu-latest
|
|
outputs:
|
|
matrix: ${{ steps.set.outputs.matrix }}
|
|
steps:
|
|
- id: set
|
|
run: |
|
|
FULL='[{"name":"all-features","flags":"--all-features"},{"name":"default","flags":""},{"name":"libsql-only","flags":"--no-default-features --features libsql"}]'
|
|
SLIM='[{"name":"all-features","flags":"--all-features"}]'
|
|
|
|
if [ "${{ github.event_name }}" = "pull_request" ] || [ "${{ github.event_name }}" = "merge_group" ]; then
|
|
echo "matrix=${SLIM}" >> "$GITHUB_OUTPUT"
|
|
else
|
|
echo "matrix=${FULL}" >> "$GITHUB_OUTPUT"
|
|
fi
|
|
|
|
format:
|
|
name: Formatting
|
|
needs: changes
|
|
if: needs.changes.outputs.has_code == 'true'
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
|
with:
|
|
persist-credentials: false
|
|
- name: Install Rust
|
|
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
|
|
with:
|
|
components: rustfmt
|
|
- name: Check formatting
|
|
run: cargo fmt --all -- --check
|
|
|
|
gateway-js-syntax:
|
|
name: Gateway JS syntax
|
|
needs: changes
|
|
if: needs.changes.outputs.has_code == 'true'
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
|
with:
|
|
persist-credentials: false
|
|
- name: Install Node.js
|
|
uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4
|
|
with:
|
|
node-version: "22"
|
|
- name: Check gateway JS syntax
|
|
run: |
|
|
find crates/ironclaw_gateway/static/js -type f -name '*.js' \
|
|
-exec node --check {} +
|
|
|
|
deny-check:
|
|
name: cargo-deny
|
|
needs: changes
|
|
if: needs.changes.outputs.has_code == 'true'
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
|
with:
|
|
persist-credentials: false
|
|
- name: Run cargo deny
|
|
uses: EmbarkStudios/cargo-deny-action@3fd3802e88374d3fe9159b834c7714ec57d6c979 # v2
|
|
|
|
tracked-ignored-files:
|
|
name: Tracked ignored files
|
|
needs: changes
|
|
if: needs.changes.outputs.has_code == 'true'
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
|
with:
|
|
persist-credentials: false
|
|
- name: Reject tracked files that match .gitignore
|
|
run: |
|
|
tracked_ignored="$(git ls-files -ci --exclude-standard)"
|
|
if [ -n "$tracked_ignored" ]; then
|
|
echo "Tracked files match .gitignore; remove them from source control or update .gitignore:"
|
|
printf '%s\n' "$tracked_ignored"
|
|
exit 1
|
|
fi
|
|
|
|
clippy:
|
|
name: Clippy (${{ matrix.name }})
|
|
needs: [changes, clippy-matrix]
|
|
if: needs.changes.outputs.has_code == 'true'
|
|
runs-on: ubuntu-latest
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include: ${{ fromJSON(needs.clippy-matrix.outputs.matrix) }}
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
|
with:
|
|
persist-credentials: false
|
|
- name: Install Rust
|
|
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
|
|
with:
|
|
components: clippy
|
|
- name: Install Node.js for WebUI bundle builds
|
|
if: contains(matrix.flags, '--all-features')
|
|
uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4
|
|
with:
|
|
node-version: "22"
|
|
cache: "npm"
|
|
cache-dependency-path: crates/ironclaw_webui_v2/frontend/package-lock.json
|
|
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
|
|
with:
|
|
shared-key: clippy
|
|
# Keep saves to protected-branch and merge_group runs so the ~10 GB
|
|
# repo cache LRU is seeded by shared states, not arbitrary PR branches.
|
|
save-if: ${{ matrix.name == 'all-features' && ((github.event_name == 'push' && github.ref == 'refs/heads/main') || github.event_name == 'merge_group') }}
|
|
- name: Setup OVH sccache
|
|
uses: ./.github/actions/setup-sccache-dist
|
|
with:
|
|
scheduler-url: ${{ vars.SCCACHE_DIST_SCHEDULER_URL }}
|
|
auth-token: ${{ secrets.SCCACHE_DIST_AUTH_TOKEN }}
|
|
cache-ssh-host: ${{ vars.SCCACHE_CACHE_SSH_HOST }}
|
|
cache-ssh-user: ${{ vars.SCCACHE_CACHE_SSH_USER }}
|
|
cache-ssh-port: ${{ vars.SCCACHE_CACHE_SSH_PORT }}
|
|
cache-ssh-private-key: ${{ secrets.SCCACHE_CACHE_SSH_PRIVATE_KEY }}
|
|
cache-ssh-known-hosts: ${{ secrets.SCCACHE_CACHE_SSH_KNOWN_HOSTS }}
|
|
redis-password: ${{ secrets.SCCACHE_REDIS_PASSWORD }}
|
|
- name: Check lints
|
|
run: cargo clippy --all --tests --examples ${{ matrix.flags }} -- -D warnings
|
|
|
|
clippy-windows:
|
|
name: Clippy Windows (${{ matrix.name }})
|
|
needs: [changes, clippy-matrix]
|
|
if: needs.changes.outputs.has_code == 'true' && github.event_name == 'push'
|
|
runs-on: windows-latest
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include: ${{ fromJSON(needs.clippy-matrix.outputs.matrix) }}
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
|
with:
|
|
persist-credentials: false
|
|
- name: Install Rust
|
|
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
|
|
with:
|
|
components: clippy
|
|
- name: Install Node.js for WebUI bundle builds
|
|
if: contains(matrix.flags, '--all-features')
|
|
uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4
|
|
with:
|
|
node-version: "22"
|
|
cache: "npm"
|
|
cache-dependency-path: crates/ironclaw_webui_v2/frontend/package-lock.json
|
|
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
|
|
with:
|
|
key: windows-${{ matrix.name }}
|
|
save-if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
|
|
- name: Check lints
|
|
run: cargo clippy --all --tests --examples ${{ matrix.flags }} -- -D warnings
|
|
|
|
reborn-cli-smoke:
|
|
name: Reborn CLI smoke tests
|
|
needs: changes
|
|
if: needs.changes.outputs.has_reborn_cli == 'true'
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
|
with:
|
|
persist-credentials: false
|
|
- name: Install Rust
|
|
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
|
|
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
|
|
with:
|
|
shared-key: reborn-cli-smoke
|
|
# Keep saves to protected-branch and merge_group runs so the ~10 GB
|
|
# repo cache LRU is seeded by shared states, not arbitrary PR branches.
|
|
save-if: ${{ (github.event_name == 'push' && github.ref == 'refs/heads/main') || github.event_name == 'merge_group' }}
|
|
- name: Setup OVH sccache
|
|
uses: ./.github/actions/setup-sccache-dist
|
|
with:
|
|
# Keep this Wasmtime-heavy smoke target cache-only. sccache-dist can
|
|
# corrupt generated Wasmtime/Wiggle inputs and produce bogus type
|
|
# resolution failures.
|
|
cache-ssh-host: ${{ vars.SCCACHE_CACHE_SSH_HOST }}
|
|
cache-ssh-user: ${{ vars.SCCACHE_CACHE_SSH_USER }}
|
|
cache-ssh-port: ${{ vars.SCCACHE_CACHE_SSH_PORT }}
|
|
cache-ssh-private-key: ${{ secrets.SCCACHE_CACHE_SSH_PRIVATE_KEY }}
|
|
cache-ssh-known-hosts: ${{ secrets.SCCACHE_CACHE_SSH_KNOWN_HOSTS }}
|
|
redis-password: ${{ secrets.SCCACHE_REDIS_PASSWORD }}
|
|
- name: Test Reborn boot config crate
|
|
run: cargo test -p ironclaw_reborn_config
|
|
- name: Test Reborn CLI binary crate
|
|
run: cargo test -p ironclaw_reborn_cli
|
|
- name: Test Reborn libSQL restart integration
|
|
run: |
|
|
cargo test -p ironclaw_reborn \
|
|
--features libsql-restart-tests \
|
|
--test loop_driver_host \
|
|
turn_runner_worker_completes_after_libsql_turn_and_thread_services_reopen \
|
|
-- --nocapture
|
|
- name: Test Reborn architecture boundaries
|
|
run: cargo test -p ironclaw_architecture reborn
|
|
|
|
no-panics:
|
|
name: No panics in production code
|
|
needs: changes
|
|
if: needs.changes.outputs.has_code == 'true' && github.event_name != 'push'
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
|
with:
|
|
fetch-depth: 0
|
|
persist-credentials: false
|
|
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
|
|
with:
|
|
python-version: "3.12"
|
|
- name: Check for .unwrap(), .expect(), assert!() in production code
|
|
env:
|
|
BASE_SHA: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.event.merge_group.base_sha }}
|
|
run: python3 scripts/check_no_panics.py --base "$BASE_SHA" --head HEAD
|
|
|
|
gateway-boundaries:
|
|
name: Gateway platform/feature boundaries
|
|
needs: changes
|
|
if: needs.changes.outputs.has_code == 'true' || needs.changes.outputs.has_boundary_check == 'true'
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
|
with:
|
|
persist-credentials: false
|
|
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
|
|
with:
|
|
python-version: "3.12"
|
|
- name: Check that platform/ has no back-edges into handlers/ or features/
|
|
run: python3 scripts/check_gateway_boundaries.py
|
|
- name: Self-test the boundary script
|
|
run: python3 scripts/check_gateway_boundaries.py test
|
|
|
|
code-style:
|
|
name: Code Style (fmt + clippy)
|
|
runs-on: ubuntu-latest
|
|
if: always()
|
|
needs:
|
|
- changes
|
|
- clippy-matrix
|
|
- format
|
|
- gateway-js-syntax
|
|
- clippy
|
|
- clippy-windows
|
|
- deny-check
|
|
- tracked-ignored-files
|
|
- reborn-cli-smoke
|
|
- no-panics
|
|
- gateway-boundaries
|
|
steps:
|
|
- run: |
|
|
if [[ "${{ needs.changes.outputs.has_code }}" == "false" ]]; then
|
|
echo "No code changes — style checks skipped correctly"
|
|
exit 0
|
|
fi
|
|
|
|
for job_result in \
|
|
"format=${{ needs.format.result }}" \
|
|
"gateway-js-syntax=${{ needs.gateway-js-syntax.result }}" \
|
|
"clippy=${{ needs.clippy.result }}" \
|
|
"deny-check=${{ needs.deny-check.result }}" \
|
|
"tracked-ignored-files=${{ needs.tracked-ignored-files.result }}" \
|
|
"gateway-boundaries=${{ needs.gateway-boundaries.result }}"; do
|
|
name="${job_result%%=*}"
|
|
result="${job_result##*=}"
|
|
if [[ "$result" != "success" ]]; then
|
|
echo "$name failed: $result"
|
|
exit 1
|
|
fi
|
|
done
|
|
|
|
for job_result in \
|
|
"no-panics=${{ needs.no-panics.result }}" \
|
|
"reborn-cli-smoke=${{ needs.reborn-cli-smoke.result }}" \
|
|
"clippy-windows=${{ needs.clippy-windows.result }}"; do
|
|
name="${job_result%%=*}"
|
|
result="${job_result##*=}"
|
|
if [[ "$result" != "success" && "$result" != "skipped" ]]; then
|
|
echo "$name failed: $result"
|
|
exit 1
|
|
fi
|
|
done
|