* Windows: start Direct3D 12 first, and Vulkan only without a D3D12 GPU wgpu started Vulkan, D3D12 and OpenGL together and preferred Vulkan, so every Windows start loaded all installed Vulkan drivers. An old Intel Vulkan driver (igvk64.dll) crashed 0.10.1 at startup inside the driver. The app now probes D3D12, then Vulkan, then OpenGL, one backend at a time, and starts only the first with a hardware adapter (WARP when none has one). WGPU_BACKEND still wins; graphics.json's "backend" sets the order by hand; the software renderer (safe mode, the retry) uses D3D12 alone. A new Windows CI job starts the real app under several settings. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * CI: a test filter YAML parses Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * CI: the startup smoke test writes graphics.json where the app reads it Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * CI: run the WARP shader tests one at a time WARP's shader JIT (d3d10warp.dll) crashed twice in 124 runs of the gpu tests with four test threads, with a null read inside its own optimizer while several devices compiled compute pipelines at once. Serial runs did not crash. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
430 lines
17 KiB
YAML
430 lines
17 KiB
YAML
# 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
|
|
- name: Web app offline use and updates (headless Chrome)
|
|
run: cargo xtask web-offline-test
|
|
|
|
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
|
|
|
|
# D3D12's older shader compiler (FXC, used where dxcompiler.dll is absent) is stricter than
|
|
# DXC, Vulkan and Metal: a shader it cannot compile once crashed the app at startup. Build the
|
|
# GPU tessellation pipeline with FXC on the runner's WARP software adapter and compare its
|
|
# results with the CPU.
|
|
windows-fxc:
|
|
name: Windows D3D12 FXC shaders
|
|
needs: changes
|
|
if: needs.changes.outputs.native == 'true'
|
|
runs-on: windows-latest
|
|
timeout-minutes: 45
|
|
env:
|
|
WGPU_BACKEND: dx12
|
|
WGPU_DX12_COMPILER: fxc
|
|
SOLIDCRAFT_REQUIRE_GPU: "1"
|
|
steps:
|
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
- uses: dtolnay/rust-toolchain@stable
|
|
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
|
|
with:
|
|
key: windows-fxc
|
|
# One test at a time: WARP's shader JIT crashes now and then (a null read in
|
|
# d3d10warp.dll's JIT optimizer, about 1 run in 60) when several devices compile compute
|
|
# pipelines at once, which the parallel tests do. Minidumps showed it in WARP itself, not
|
|
# in SolidCraft or FXC; serial runs never hit it.
|
|
- run: cargo test -p solidcraft-gpu --profile ci --locked -- --test-threads=1
|
|
|
|
# The Windows app starts D3D12 first and only the backend it uses (a Vulkan driver crashed the
|
|
# app at startup on Intel). Start the real app on the runner (its D3D12 adapter is WARP) with
|
|
# several saved settings and safe mode, and check each draws a frame and quits cleanly.
|
|
windows-startup:
|
|
name: Windows app startup
|
|
needs: changes
|
|
if: needs.changes.outputs.native == 'true'
|
|
runs-on: windows-latest
|
|
timeout-minutes: 60
|
|
steps:
|
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
- uses: dtolnay/rust-toolchain@stable
|
|
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
|
|
with:
|
|
key: windows-startup
|
|
- name: Backend order and probing on Windows
|
|
run: cargo test -p solidcraft --profile ci --locked -- startup::tests
|
|
- name: Build the app
|
|
run: cargo build -p solidcraft --profile ci --locked
|
|
- name: Start the app under different settings
|
|
shell: pwsh
|
|
run: tools/windows-startup-smoke.ps1 -Exe target/ci/solidcraft.exe
|
|
|
|
# Preserve the existing check name for branch protection, including docs-only PRs.
|
|
ci:
|
|
needs: [changes, checks, clippy, build-tests, tests, doc-tests, oracle, windows-fxc, windows-startup]
|
|
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 }}
|
|
WINDOWS_FXC: ${{ needs.windows-fxc.result }}
|
|
WINDOWS_STARTUP: ${{ needs.windows-startup.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
|
|
test "$WINDOWS_FXC" = success
|
|
test "$WINDOWS_STARTUP" = 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
|