# Fast checks always run; native builds run for code/CI changes. # Tests are built once and run in 4 shards. A few heavy tests (see .config/nextest.toml) are # skipped on pull requests and run on pushes to main and nightly. # Cross-compilation is opt-in through Run workflow; release builds remain in release.yml. name: CI on: pull_request: push: branches: [main] schedule: - cron: "17 6 * * *" workflow_dispatch: inputs: cross_compile: description: "Also check WebAssembly and RISC-V (before a release)" type: boolean default: false permissions: contents: read concurrency: # A new PR push replaces the old run. Only one run per group may wait, so a busy main would # cancel the waiting nightly (or a Run workflow) before it starts: they get groups of their own. group: ci-${{ github.event_name }}-${{ github.ref }} cancel-in-progress: ${{ github.event_name == 'pull_request' }} env: CARGO_TERM_COLOR: always jobs: dependency-advisories: name: Dependency advisories if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' runs-on: ubuntu-24.04 timeout-minutes: 20 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: dtolnay/rust-toolchain@stable - name: Install advisory scanner run: cargo install cargo-deny --version 0.20.2 --locked - name: Check locked dependencies run: cargo deny --locked check --warn unmaintained advisories bans sources changes: name: Select checks runs-on: ubuntu-24.04 outputs: native: ${{ steps.paths.outputs.native }} kernel: ${{ steps.paths.outputs.kernel }} steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 - id: paths name: Detect code and CI changes env: EVENT_NAME: ${{ github.event_name }} BASE_SHA: ${{ github.event.pull_request.base.sha }} HEAD_SHA: ${{ github.event.pull_request.head.sha }} run: | native=false kernel=false if [ "$EVENT_NAME" != pull_request ]; then native=true else # Compare both old and new paths of renames; unknown paths run native checks. git diff --no-renames --name-only -z "$BASE_SHA...$HEAD_SHA" > "$RUNNER_TEMP/ci-paths" while IFS= read -r -d '' path; do case "$path" in crates/kernel/*|vendor/truck-*) kernel=true ;; esac case "$path" in .github/workflows/ci.yml) native=true ;; *.rs|Cargo.toml|Cargo.lock|*/Cargo.toml|.cargo/*|rust-toolchain*) native=true ;; docs/*|*.md|.github/*|packaging/*|LICENSE*|NOTICE|THIRD-PARTY-LICENSES.txt) ;; *) native=true ;; esac done < "$RUNNER_TEMP/ci-paths" fi echo "native=$native" >> "$GITHUB_OUTPUT" echo "kernel=$kernel" >> "$GITHUB_OUTPUT" checks: name: Format and repository checks runs-on: ubuntu-24.04 timeout-minutes: 15 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: dtolnay/rust-toolchain@stable with: components: rustfmt - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2 with: key: repo-checks - name: Format run: cargo fmt --all -- --check - name: Asset attribution run: cargo xtask assets - name: Licences run: cargo xtask licences --check - name: Dependency layers run: cargo xtask layers clippy: name: Native Linux Clippy needs: changes if: needs.changes.outputs.native == 'true' runs-on: ubuntu-24.04 timeout-minutes: 30 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: dtolnay/rust-toolchain@stable with: components: clippy - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2 with: key: native-clippy - run: cargo clippy --workspace --all-targets --profile ci --locked -- -D warnings build-tests: name: Build tests needs: changes if: needs.changes.outputs.native == 'true' runs-on: ubuntu-24.04 timeout-minutes: 45 env: CARGO_INCREMENTAL: "0" RUSTC_WRAPPER: sccache SCCACHE_GHA_ENABLED: "true" SCCACHE_IGNORE_SERVER_IO_ERROR: "1" steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: dtolnay/rust-toolchain@stable - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2 with: key: native-tests - name: Set up compiler cache uses: mozilla-actions/sccache-action@fc920bf0ec8de6ee65d409111f7ec508035751ba # v0.0.11 with: version: v0.16.0 - uses: taiki-e/install-action@1168f6810a3cb30a3514bf5756a3b7f0422c6df8 # v2.87.28 with: tool: cargo-nextest - name: Compile tests into a nextest archive run: cargo nextest archive --workspace --cargo-profile ci --locked --archive-file tests.tar.zst - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 with: name: nextest-archive path: tests.tar.zst retention-days: 1 compression-level: 0 - name: Compiler cache statistics if: always() run: sccache --show-stats tests: name: Native Linux tests (${{ matrix.shard }}/4) needs: [changes, build-tests] runs-on: ubuntu-24.04 timeout-minutes: 60 strategy: fail-fast: false matrix: shard: [1, 2, 3, 4] steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: taiki-e/install-action@1168f6810a3cb30a3514bf5756a3b7f0422c6df8 # v2.87.28 with: tool: cargo-nextest - uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 with: name: nextest-archive - name: Run tests env: EVENT_NAME: ${{ github.event_name }} SHARD: ${{ matrix.shard }} run: | # Pull requests skip the heavy tests (default-filter in .config/nextest.toml). all=() if [ "$EVENT_NAME" != pull_request ]; then all=(--ignore-default-filter); fi # Extract into ./target: tests that run a built binary (env!("CARGO_BIN_EXE_*")) have # the build job's path, which is the same checkout path here. cargo nextest run --archive-file tests.tar.zst --workspace-remap . --extract-to . --extract-overwrite \ --profile ci --partition "slice:$SHARD/4" "${all[@]}" - name: Robustness goldens sample (pull requests that touch the kernel) # Every 8th seed of tests/robustness/goldens.json, a quarter per shard; pushes to main and # the nightly run every seed in the step above. if: github.event_name == 'pull_request' && needs.changes.outputs.kernel == 'true' env: SHARD: ${{ matrix.shard }} run: | cargo nextest run --archive-file tests.tar.zst --workspace-remap . --extract-to . --extract-overwrite \ --profile ci --ignore-default-filter \ -E "package(solidcraft-engine) & test(=tests::robustness_goldens::sample_$((SHARD - 1)))" - name: Upload test timings if: always() uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 with: name: junit-${{ matrix.shard }} path: target/nextest/ci/junit.xml retention-days: 7 action-matrix: # Every ordinary action on every face, edge, vertex and body of the design corpus, against # tests/action_matrix/baseline.json (docs/action-matrix.md). Every test run checks a sample; # the whole matrix runs nightly (or by hand), a quarter per job. name: Action matrix (${{ matrix.part }}/4) needs: build-tests if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' runs-on: ubuntu-24.04 timeout-minutes: 60 strategy: fail-fast: false matrix: part: [1, 2, 3, 4] steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: taiki-e/install-action@1168f6810a3cb30a3514bf5756a3b7f0422c6df8 # v2.87.28 with: tool: cargo-nextest - uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 with: name: nextest-archive - name: Run a quarter of the matrix env: PART: ${{ matrix.part }} run: | cargo nextest run --archive-file tests.tar.zst --workspace-remap . --extract-to . --extract-overwrite \ --profile ci --run-ignored only -E "binary(action_matrix) & test(=action_matrix_full_$PART)" doc-tests: name: Doc tests needs: changes if: needs.changes.outputs.native == 'true' runs-on: ubuntu-24.04 timeout-minutes: 45 env: CARGO_INCREMENTAL: "0" RUSTC_WRAPPER: sccache SCCACHE_GHA_ENABLED: "true" SCCACHE_IGNORE_SERVER_IO_ERROR: "1" steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: dtolnay/rust-toolchain@stable - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2 with: key: doc-tests - name: Set up compiler cache uses: mozilla-actions/sccache-action@fc920bf0ec8de6ee65d409111f7ec508035751ba # v0.0.11 with: version: v0.16.0 # nextest does not run doc tests. - run: cargo test --workspace --profile ci --locked --doc oracle: name: Reference tests needs: changes if: needs.changes.outputs.native == 'true' runs-on: ubuntu-24.04 timeout-minutes: 60 env: CARGO_INCREMENTAL: "0" RUSTC_WRAPPER: sccache SCCACHE_GHA_ENABLED: "true" SCCACHE_IGNORE_SERVER_IO_ERROR: "1" steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: dtolnay/rust-toolchain@stable - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2 with: key: oracle - name: Set up compiler cache uses: mozilla-actions/sccache-action@fc920bf0ec8de6ee65d409111f7ec508035751ba # v0.0.11 with: version: v0.16.0 - name: Build the command line run: cargo build -p solidcraft-cli --profile ci --locked - name: Replay tests/oracle and compare with expected.json run: cargo xtask oracle --cli target/ci/solidcraft-cli - name: Upload the report if: always() uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 with: name: oracle-report path: docs/oracle.md # Preserve the existing check name for branch protection, including docs-only PRs. ci: needs: [changes, checks, clippy, build-tests, tests, doc-tests, oracle] if: always() runs-on: ubuntu-24.04 steps: - name: Verify required checks env: CHANGES: ${{ needs.changes.result }} CHECKS: ${{ needs.checks.result }} NATIVE: ${{ needs.changes.outputs.native }} CLIPPY: ${{ needs.clippy.result }} BUILD: ${{ needs.build-tests.result }} TESTS: ${{ needs.tests.result }} DOCS: ${{ needs.doc-tests.result }} ORACLE: ${{ needs.oracle.result }} run: | test "$CHANGES" = success test "$CHECKS" = success if [ "$NATIVE" = true ]; then test "$CLIPPY" = success test "$BUILD" = success test "$TESTS" = success test "$DOCS" = success test "$ORACLE" = success fi wasm: name: Compile WebAssembly (manual) if: github.event_name == 'workflow_dispatch' && inputs.cross_compile runs-on: ubuntu-24.04 timeout-minutes: 30 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: dtolnay/rust-toolchain@stable with: targets: wasm32-unknown-unknown - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2 with: key: wasm - run: cargo xtask wasm riscv64-gates: name: Compile RISC-V (manual) if: github.event_name == 'workflow_dispatch' && inputs.cross_compile runs-on: ubuntu-24.04 timeout-minutes: 30 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Check out craft-fonts uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: repository: storytold/craft-fonts ref: ${{ env.CRAFT_FONTS_REF }} path: craft-fonts - uses: dtolnay/rust-toolchain@stable with: targets: riscv64gc-unknown-linux-gnu - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2 with: key: riscv64 - name: Install cross-compilation dependencies run: | # Ubuntu's regular archive serves amd64; RISC-V packages live in ports. # The 24.04 runner uses deb822 sources, so scope every Ubuntu stanza. sudo sed -i '/^Architectures:/d; /^Types:/a Architectures: amd64' /etc/apt/sources.list.d/ubuntu.sources sudo tee /etc/apt/sources.list.d/riscv64.sources >/dev/null <<'EOF' Types: deb URIs: http://ports.ubuntu.com/ubuntu-ports Suites: noble noble-updates noble-backports noble-security Components: main restricted universe multiverse Architectures: riscv64 Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg EOF sudo dpkg --add-architecture riscv64 sudo apt-get update sudo apt-get install -y gcc-riscv64-linux-gnu libasound2-dev:riscv64 libxkbcommon-dev:riscv64 \ libwayland-dev:riscv64 libx11-dev:riscv64 libxrandr-dev:riscv64 libxi-dev:riscv64 libgl1-mesa-dev:riscv64 - name: Cargo check on RISC-V env: CARGO_TARGET_RISCV64GC_UNKNOWN_LINUX_GNU_LINKER: riscv64-linux-gnu-gcc PKG_CONFIG_SYSROOT_DIR: / PKG_CONFIG_PATH: /usr/lib/riscv64-linux-gnu/pkgconfig:/usr/share/pkgconfig PKG_CONFIG_ALLOW_CROSS: 1 run: | . "$HOME/.cargo/env" export CRAFT_FONTS_DIR="$PWD/craft-fonts" cargo check --all-targets --locked --target riscv64gc-unknown-linux-gnu