From 65a9e7dd6bd571a5678de4618aa84d231a6911b4 Mon Sep 17 00:00:00 2001 From: Roman Ivanov Date: Thu, 15 Oct 2026 14:25:49 -0700 Subject: [PATCH 1/6] ci: run CodeQL on an 8-core runner Self-hosted runners reject pull_request, so the workflow triggers on the copy-pr-bot mirror push. The nv-cpu-general image has no rustup; the rust:1.98.0-bookworm container supplies it. Signed-off-by: Roman Ivanov --- .github/workflows/codeql.yml | 37 ++++++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 60aaba1f3d..7f60f30a01 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -1,21 +1,17 @@ --- name: codeql -# Push and pull_request run only when Rust, workflow, or script inputs change. +# Push runs only when Rust, workflow, or script inputs change. +# Self-hosted runners reject pull_request. copy-pr-bot mirrors trusted PRs to +# pull-request/, and this workflow triggers on that push +# (config: .github/copy-pr-bot.yaml). # A Markdown-only commit skips both analyzers. The weekly schedule has no path # filter, so that scan still covers the whole tree. on: push: - branches: [main] - paths: - - '**/*.rs' - - '**/Cargo.toml' - - '**/Cargo.lock' - - '.github/**' - - '**/*.sh' - - '**/*.py' - pull_request: - branches: [main] + branches: + - main + - "pull-request/[0-9]+" paths: - '**/*.rs' - '**/Cargo.toml' @@ -35,7 +31,14 @@ concurrency: jobs: analyze: name: Analyze (${{ matrix.language }}) - runs-on: ubuntu-latest + # nv-cpu-general: m7i.2xlarge, 8 vCPU / 32 GiB. + # The VM image has no rustup. rust:1.98.0-bookworm matches the root + # toolchain pin and is buildpack-deps, so git is there for checkout and + # rustup is on PATH. The Oxide nightly still comes from + # cuda-oxide/rust-toolchain.toml when the extractor enters that tree. + runs-on: linux-amd64-cpu8 + container: + image: rust:1.98.0-bookworm timeout-minutes: 120 permissions: security-events: write @@ -54,6 +57,16 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 + # The workspace is mounted from the runner, so git refuses it until this + # directory is marked safe. CodeQL's result upload calls git. + - name: Mark the workspace as a safe git directory + run: git config --global --add safe.directory "$GITHUB_WORKSPACE" + + # A missing rustup makes the Rust extractor exit 0 with an empty database. + - name: Confirm Rust + if: matrix.language == 'rust' + run: rustup show active-toolchain + - name: Initialize CodeQL uses: github/codeql-action/init@v4 with: From 52d6a74f6aa445ae684251f346492af656deb730 Mon Sep 17 00:00:00 2001 From: Roman Ivanov Date: Thu, 15 Oct 2026 14:43:18 -0700 Subject: [PATCH 2/6] ci: run CodeQL on a 4-core runner linux-amd64-cpu4 matches ubuntu-latest. The 8-core run did not shorten the Rust extract. Signed-off-by: Roman Ivanov --- .github/workflows/codeql.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 7f60f30a01..6a99cffcfd 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -31,12 +31,12 @@ concurrency: jobs: analyze: name: Analyze (${{ matrix.language }}) - # nv-cpu-general: m7i.2xlarge, 8 vCPU / 32 GiB. + # nv-cpu-general: m7i.xlarge, 4 vCPU / 16 GiB. Same size as ubuntu-latest. # The VM image has no rustup. rust:1.98.0-bookworm matches the root # toolchain pin and is buildpack-deps, so git is there for checkout and # rustup is on PATH. The Oxide nightly still comes from # cuda-oxide/rust-toolchain.toml when the extractor enters that tree. - runs-on: linux-amd64-cpu8 + runs-on: linux-amd64-cpu4 container: image: rust:1.98.0-bookworm timeout-minutes: 120 From fa5a805b1a6fb7566e42d1a0aab69ce137826431 Mon Sep 17 00:00:00 2001 From: Roman Ivanov Date: Thu, 15 Oct 2026 15:37:49 -0700 Subject: [PATCH 3/6] ci: run the remaining workflows on linux-amd64-cpu4 Self-hosted runners reject pull_request, so these workflows trigger on the copy-pr-bot mirror push. Jobs that call cargo use the rust:1.98.0-bookworm container. The Pages deploy job stays on ubuntu-latest. Signed-off-by: Roman Ivanov --- .github/workflows/book.yml | 33 ++++++++---------- .github/workflows/cargo-deny.yml | 32 ++++++++++++++---- .github/workflows/ci-status.yml | 10 +++--- .github/workflows/ci.yml | 19 ++++------- .github/workflows/clippy.yml | 20 ++++++++--- .github/workflows/cutile-rs.yml | 2 +- .github/workflows/docs.yml | 31 +++++++++-------- .github/workflows/examples-compile.yml | 31 +++++++++-------- .github/workflows/fmt.yml | 13 +++++--- .github/workflows/naming-guard.yml | 14 ++++---- .github/workflows/status-guard.yml | 46 ++++++++++++++++---------- .github/workflows/unit-tests.yml | 38 ++++++++++++++++----- 12 files changed, 177 insertions(+), 112 deletions(-) diff --git a/.github/workflows/book.yml b/.github/workflows/book.yml index 732ff8b667..ec0746e717 100644 --- a/.github/workflows/book.yml +++ b/.github/workflows/book.yml @@ -1,21 +1,14 @@ name: Deploy CUDA Rust Docs # Path-filtered lane: only documentation sources can change what this builds. +# Self-hosted runners reject pull_request. copy-pr-bot mirrors trusted PRs to +# pull-request/, and the build job triggers on that push. `deploy` stays on +# main: a mirror push is a push event, so an event-name check would publish it. on: push: - branches: [main] - paths: - - 'cuda-oxide/cuda-oxide-book/**' - - 'cutile-rs/cutile-book/**' - - 'cutile-rs/scripts/build_versioned_book.sh' - - 'docs/**' - - '.github/workflows/book.yml' - # Validate the book on PRs too. The build job alone runs there; `deploy` is - # gated below, so a pull request never publishes. Without this the book was - # only ever built after merge, so a PR that broke it could not be caught by - # review or by CI. - pull_request: - branches: [main] + branches: + - main + - "pull-request/[0-9]+" paths: - 'cuda-oxide/cuda-oxide-book/**' - 'cutile-rs/cutile-book/**' @@ -39,11 +32,11 @@ permissions: # a slower older commit deploy after a newer one and replace the published site. concurrency: group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: ${{ github.event_name == 'pull_request' }} + cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} jobs: build: - runs-on: ubuntu-latest + runs-on: linux-amd64-cpu4 # An umbrella page plus two Sphinx books. Bound the job so a # wedged install or a sphinx that never returns fails instead of burning to # the 6-hour default. @@ -76,9 +69,9 @@ jobs: run: | # Unset CUTILE_DOCS_TAGS → build every tag matching the pattern. # Empty CUTILE_DOCS_TAGS → current tree only (fast PR check). - if [ "${{ github.event_name }}" = "pull_request" ]; then - export CUTILE_DOCS_TAGS="" - fi + case "${{ github.ref }}" in + refs/heads/pull-request/*) export CUTILE_DOCS_TAGS="" ;; + esac bash docs/build_all_docs.sh # Only `deploy` consumes this, and `deploy` is skipped on pull requests, @@ -86,7 +79,7 @@ jobs: # job is to prove both Sphinx builds succeed, which the steps above already # did; gate the upload on the same condition `deploy` uses so the two # cannot drift apart. - - if: github.event_name != 'pull_request' + - if: github.ref == 'refs/heads/main' uses: actions/upload-pages-artifact@v3 with: path: docs/build/html @@ -96,7 +89,7 @@ jobs: # Publishing is for main only. A pull request builds the book to prove it # is sound and stops there; it never touches the Pages environment or # holds the deployment permissions below. - if: github.event_name != 'pull_request' + if: github.ref == 'refs/heads/main' permissions: pages: write id-token: write diff --git a/.github/workflows/cargo-deny.yml b/.github/workflows/cargo-deny.yml index 73f426cc77..b7f2b93320 100644 --- a/.github/workflows/cargo-deny.yml +++ b/.github/workflows/cargo-deny.yml @@ -1,8 +1,8 @@ --- name: cargo-deny -# Runs as a job of the top-level CI workflow (.github/workflows/ci.yml) on -# push/pull_request, plus a standalone weekly security scan via schedule. +# Runs as a job of the top-level CI workflow (.github/workflows/ci.yml), +# plus a standalone weekly security scan via schedule. # Concurrency for the CI path is governed by CI's own group. # # Four parts of the same concern. `check` enforces the license *policy* over @@ -32,12 +32,17 @@ permissions: jobs: check: - runs-on: ubuntu-latest + runs-on: linux-amd64-cpu4 + container: + image: rust:1.98.0-bookworm timeout-minutes: 10 steps: - name: Clone repository uses: actions/checkout@v4 + - name: Mark the workspace as a safe git directory + run: git config --global --add safe.directory "$GITHUB_WORKSPACE" + - name: Install cargo-deny run: cargo install cargo-deny --locked @@ -94,12 +99,17 @@ jobs: license-manifest: name: dependency-licenses.csv covers every declared crate - runs-on: ubuntu-latest + runs-on: linux-amd64-cpu4 + container: + image: rust:1.98.0-bookworm timeout-minutes: 10 steps: - name: Clone repository uses: actions/checkout@v4 + - name: Mark the workspace as a safe git directory + run: git config --global --add safe.directory "$GITHUB_WORKSPACE" + # Only reads manifests: `cargo metadata` resolves the graph without # running build scripts, so this needs no CUDA toolkit and no LLVM. - name: Verify the license inventory covers the workspace @@ -107,12 +117,17 @@ jobs: example-policy: name: deny.toml governs the example workspaces - runs-on: ubuntu-latest + runs-on: linux-amd64-cpu4 + container: + image: rust:1.98.0-bookworm timeout-minutes: 15 steps: - name: Clone repository uses: actions/checkout@v4 + - name: Mark the workspace as a safe git directory + run: git config --global --add safe.directory "$GITHUB_WORKSPACE" + - name: Install cargo-deny run: cargo install cargo-deny --locked @@ -125,12 +140,17 @@ jobs: source-headers: name: every source file carries the SPDX header - runs-on: ubuntu-latest + runs-on: linux-amd64-cpu4 + container: + image: rust:1.98.0-bookworm timeout-minutes: 5 steps: - name: Clone repository uses: actions/checkout@v4 + - name: Mark the workspace as a safe git directory + run: git config --global --add safe.directory "$GITHUB_WORKSPACE" + # Pure text check over tracked files: no toolchain, no network. The OSRB # review fixed 32 missing headers in #812 and the next two scripts to # land (#819, #835) drifted again; this makes the third time the last. diff --git a/.github/workflows/ci-status.yml b/.github/workflows/ci-status.yml index 2c9f44b127..1b7a72791d 100644 --- a/.github/workflows/ci-status.yml +++ b/.github/workflows/ci-status.yml @@ -5,11 +5,9 @@ name: CI status # One status for the whole repository, so the README needs one badge. # -# Badges are per workflow file, and the two pipelines have to stay separate -# for now: ci.yml (SIMT and the shared host crates) runs on GitHub-hosted -# runners on push and pull_request; cutile-rs.yml (Tile) runs on NVIDIA -# self-hosted runners, which cannot run on pull_request events, so it -# triggers on push to main and to the copy-pr-bot mirror branches. +# Badges are per workflow file. ci.yml and cutile-rs.yml both run on NVIDIA +# self-hosted runners, which reject pull_request, so both trigger on push to +# main and to the copy-pr-bot mirror branches. # # This workflow runs whenever either of them completes on main. For each # pipeline it finds the newest completed push run on main that actually @@ -34,7 +32,7 @@ permissions: jobs: status: name: combined CI status on main - runs-on: ubuntu-latest + runs-on: linux-amd64-cpu4 timeout-minutes: 5 steps: - name: Report the newest executed main run of each pipeline diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bd0c764954..13c1df6c1e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,20 +4,15 @@ name: CI # Path-filtered SIMT lane: skip when a change cannot affect cuda-oxide or the # shared host crates. `cuda-oxide/**` is deliberately broad (prose as well as # code) because guard scripts cross-check the book against the crates. +# +# Self-hosted runners reject pull_request. copy-pr-bot mirrors trusted PRs to +# pull-request/, and this workflow triggers on that push +# (config: .github/copy-pr-bot.yaml). on: push: - branches: [main] - paths: - - 'cuda-oxide/**' - - 'cuda-bindings/**' - - 'cuda-core/**' - - 'cuda-core-derive/**' - - 'cuda-async/**' - - 'Cargo.toml' - - 'Cargo.lock' - - '.github/workflows/**' - pull_request: - branches: [main] + branches: + - main + - "pull-request/[0-9]+" paths: - 'cuda-oxide/**' - 'cuda-bindings/**' diff --git a/.github/workflows/clippy.yml b/.github/workflows/clippy.yml index 018779dbb9..a6b26f121d 100644 --- a/.github/workflows/clippy.yml +++ b/.github/workflows/clippy.yml @@ -1,9 +1,9 @@ --- name: clippy -# Runs as a job of the top-level CI workflow (.github/workflows/ci.yml), -# which is the single push/pull_request entry point. Concurrency is governed -# by CI's own group, so this workflow declares none of its own. +# Runs as a job of the top-level CI workflow (.github/workflows/ci.yml). +# Concurrency is governed by CI's own group, so this workflow declares none +# of its own. on: workflow_call: @@ -12,12 +12,24 @@ permissions: jobs: clippy: - runs-on: ubuntu-latest + runs-on: linux-amd64-cpu4 + container: + image: rust:1.98.0-bookworm timeout-minutes: 30 steps: - name: Clone repository uses: actions/checkout@v4 + - name: Mark the workspace as a safe git directory + run: git config --global --add safe.directory "$GITHUB_WORKSPACE" + + - name: Install sudo + run: | + if ! command -v sudo >/dev/null 2>&1; then + apt-get update -qq + apt-get install -y -qq --no-install-recommends sudo + fi + - name: Install CUDA toolkit uses: Jimver/cuda-toolkit@v0.2.35 id: cuda-toolkit diff --git a/.github/workflows/cutile-rs.yml b/.github/workflows/cutile-rs.yml index 665ed14296..9afa02a557 100644 --- a/.github/workflows/cutile-rs.yml +++ b/.github/workflows/cutile-rs.yml @@ -47,7 +47,7 @@ jobs: # succeed. When copy-pr-bot yields no detectable file diff, both outputs are # false and the jobs run anyway so the mirror push is never silently skipped. changes: - runs-on: ubuntu-latest + runs-on: linux-amd64-cpu4 outputs: tile: ${{ steps.changes.outputs.tile }} any_changed: ${{ steps.changes.outputs.any_changed }} diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index c924c78d7f..8e15875983 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -3,19 +3,12 @@ name: docs # Path-filtered SIMT lane (same roots as ci.yml). on: + # Self-hosted runners reject pull_request. copy-pr-bot mirrors trusted PRs + # to pull-request/, and this workflow triggers on that push. push: - branches: [main] - paths: - - 'cuda-oxide/**' - - 'cuda-bindings/**' - - 'cuda-core/**' - - 'cuda-core-derive/**' - - 'cuda-async/**' - - 'Cargo.toml' - - 'Cargo.lock' - - '.github/workflows/**' - pull_request: - branches: [main] + branches: + - main + - "pull-request/[0-9]+" paths: - 'cuda-oxide/**' - 'cuda-bindings/**' @@ -41,12 +34,24 @@ permissions: jobs: rustdoc-and-doctests: name: cargo doc + doctests - runs-on: ubuntu-latest + runs-on: linux-amd64-cpu4 + container: + image: rust:1.98.0-bookworm timeout-minutes: 30 steps: - name: Clone repository uses: actions/checkout@v4 + - name: Mark the workspace as a safe git directory + run: git config --global --add safe.directory "$GITHUB_WORKSPACE" + + - name: Install sudo + run: | + if ! command -v sudo >/dev/null 2>&1; then + apt-get update -qq + apt-get install -y -qq --no-install-recommends sudo + fi + - name: Install CUDA toolkit # `cargo doc --workspace` compiles cuda-bindings' build-time bindgen, # which needs `cuda.h`. Same toolkit pin as the unit-tests job. diff --git a/.github/workflows/examples-compile.yml b/.github/workflows/examples-compile.yml index 0f8544cb63..2facf8943a 100644 --- a/.github/workflows/examples-compile.yml +++ b/.github/workflows/examples-compile.yml @@ -3,19 +3,12 @@ name: examples-compile # Path-filtered SIMT lane (same roots as ci.yml). on: + # Self-hosted runners reject pull_request. copy-pr-bot mirrors trusted PRs + # to pull-request/, and this workflow triggers on that push. push: - branches: [main] - paths: - - 'cuda-oxide/**' - - 'cuda-bindings/**' - - 'cuda-core/**' - - 'cuda-core-derive/**' - - 'cuda-async/**' - - 'Cargo.toml' - - 'Cargo.lock' - - '.github/workflows/**' - pull_request: - branches: [main] + branches: + - main + - "pull-request/[0-9]+" paths: - 'cuda-oxide/**' - 'cuda-bindings/**' @@ -38,7 +31,9 @@ permissions: jobs: examples-compile: name: compile all examples (device pipeline, GPU-less) - runs-on: ubuntu-latest + runs-on: linux-amd64-cpu4 + container: + image: rust:1.98.0-bookworm # Every example crate: backend build + shared dep tree + per-example # device codegen. Typically well under an hour with the shared # CARGO_TARGET_DIR; the timeout leaves headroom for cold runners. @@ -49,6 +44,16 @@ jobs: - name: Clone repository uses: actions/checkout@v4 + - name: Mark the workspace as a safe git directory + run: git config --global --add safe.directory "$GITHUB_WORKSPACE" + + - name: Install sudo + run: | + if ! command -v sudo >/dev/null 2>&1; then + apt-get update -qq + apt-get install -y -qq --no-install-recommends sudo + fi + # Every example workspace must pin the same dependency versions. The # shared CARGO_TARGET_DIR below keys build caches on the resolved # dependency subtree, so one drifted Cargo.lock silently costs a full diff --git a/.github/workflows/fmt.yml b/.github/workflows/fmt.yml index 4d8035a248..4f21920576 100644 --- a/.github/workflows/fmt.yml +++ b/.github/workflows/fmt.yml @@ -1,9 +1,9 @@ --- name: fmt -# Runs as a job of the top-level CI workflow (.github/workflows/ci.yml), -# which is the single push/pull_request entry point. Concurrency is governed -# by CI's own group, so this workflow declares none of its own. +# Runs as a job of the top-level CI workflow (.github/workflows/ci.yml). +# Concurrency is governed by CI's own group, so this workflow declares none +# of its own. on: workflow_call: @@ -12,12 +12,17 @@ permissions: jobs: fmt: - runs-on: ubuntu-latest + runs-on: linux-amd64-cpu4 + container: + image: rust:1.98.0-bookworm timeout-minutes: 10 steps: - name: Clone repository uses: actions/checkout@v4 + - name: Mark the workspace as a safe git directory + run: git config --global --add safe.directory "$GITHUB_WORKSPACE" + # rustfmt comes from cuda-oxide/rust-toolchain.toml's component list, # like clippy, # so `cargo fmt` resolves against the same nightly as the rest of CI. diff --git a/.github/workflows/naming-guard.yml b/.github/workflows/naming-guard.yml index 00eb30f37e..09c1f11081 100644 --- a/.github/workflows/naming-guard.yml +++ b/.github/workflows/naming-guard.yml @@ -9,14 +9,12 @@ name: naming-guard # here would rerun the same search; covering them means adding them to # SEARCH_ROOTS in the script first. on: + # Self-hosted runners reject pull_request. copy-pr-bot mirrors trusted PRs + # to pull-request/, and this workflow triggers on that push. push: - branches: [main] - paths: - - 'cuda-oxide/crates/**' - - 'cuda-oxide/scripts/check-reserved-prefixes.sh' - - '.github/workflows/naming-guard.yml' - pull_request: - branches: [main] + branches: + - main + - "pull-request/[0-9]+" paths: - 'cuda-oxide/crates/**' - 'cuda-oxide/scripts/check-reserved-prefixes.sh' @@ -34,7 +32,7 @@ permissions: jobs: reserved-symbols-guard: name: reserved-oxide-symbols literal guard - runs-on: ubuntu-latest + runs-on: linux-amd64-cpu4 timeout-minutes: 5 steps: - name: Clone repository diff --git a/.github/workflows/status-guard.yml b/.github/workflows/status-guard.yml index 1bb635e1df..aa61d2d38d 100644 --- a/.github/workflows/status-guard.yml +++ b/.github/workflows/status-guard.yml @@ -1,11 +1,13 @@ --- name: status-guard +# Self-hosted runners reject pull_request. copy-pr-bot mirrors trusted PRs to +# pull-request/, and this workflow triggers on that push. on: push: - branches: [main] - pull_request: - branches: [main] + branches: + - main + - "pull-request/[0-9]+" # Each commit on main gets its own group, so it starts at once. Any other ref # still shares one group and cancels the older run. @@ -19,7 +21,7 @@ permissions: jobs: error-example-status-guard: name: error-example status guard - runs-on: ubuntu-latest + runs-on: linux-amd64-cpu4 timeout-minutes: 5 steps: - name: Clone repository @@ -35,7 +37,7 @@ jobs: # marker and needed a follow-up commit on main to stop reporting FAIL. example-smoketest-contract: name: smoketest example contract - runs-on: ubuntu-latest + runs-on: linux-amd64-cpu4 timeout-minutes: 5 steps: - name: Clone repository @@ -50,7 +52,7 @@ jobs: # were both absent, 1,700 lines with no row between them. crate-inventory: name: crate inventory - runs-on: ubuntu-latest + runs-on: linux-amd64-cpu4 timeout-minutes: 5 steps: - name: Clone repository @@ -69,7 +71,7 @@ jobs: # book quotes behind. toolchain-pin-parity: name: toolchain pin parity - runs-on: ubuntu-latest + runs-on: linux-amd64-cpu4 timeout-minutes: 5 steps: - name: Clone repository @@ -85,7 +87,7 @@ jobs: # missing when that table was written (#794). cli-doc-coverage: name: CLI command reference - runs-on: ubuntu-latest + runs-on: linux-amd64-cpu4 timeout-minutes: 5 steps: - name: Clone repository @@ -103,7 +105,7 @@ jobs: # the crate and example READMEs. book-api-names: name: book device-API names - runs-on: ubuntu-latest + runs-on: linux-amd64-cpu4 timeout-minutes: 5 steps: - name: Clone repository @@ -124,12 +126,17 @@ jobs: # bodies, and partial snippets with no enclosing item, never trip it. book-import-order: name: book import order - runs-on: ubuntu-latest + runs-on: linux-amd64-cpu4 + container: + image: rust:1.98.0-bookworm timeout-minutes: 10 steps: - name: Clone repository uses: actions/checkout@v4 + - name: Mark the workspace as a safe git directory + run: git config --global --add safe.directory "$GITHUB_WORKSPACE" + # rustfmt comes from cuda-oxide/rust-toolchain.toml's component list through the # rustup proxy, as in fmt.yml, so the order checked is the pinned # nightly's. No cargo, no CUDA toolkit, no network beyond the toolchain. @@ -144,7 +151,7 @@ jobs: # in markdown and in `///` / `//!` doc comments. Git and Python, no toolchain. source-references: name: source references - runs-on: ubuntu-latest + runs-on: linux-amd64-cpu4 timeout-minutes: 10 steps: - name: Clone repository @@ -163,7 +170,7 @@ jobs: # because the page derives staleness from it. book-catalog-stamp: name: book catalog stamp - runs-on: ubuntu-latest + runs-on: linux-amd64-cpu4 timeout-minutes: 5 steps: - name: Clone repository @@ -181,7 +188,7 @@ jobs: # reading as complete is what hid them. test-matrix-coverage: name: test-matrix coverage - runs-on: ubuntu-latest + runs-on: linux-amd64-cpu4 timeout-minutes: 5 steps: - name: Clone repository @@ -201,12 +208,17 @@ jobs: # here. device-only-build: name: device-only kernel crate - runs-on: ubuntu-latest + runs-on: linux-amd64-cpu4 + container: + image: rust:1.98.0-bookworm timeout-minutes: 10 steps: - name: Clone repository uses: actions/checkout@v4 + - name: Mark the workspace as a safe git directory + run: git config --global --add safe.directory "$GITHUB_WORKSPACE" + # Deliberately no CUDA toolkit step: a toolkit on the runner would mask # exactly the dependency this job exists to keep out of the graph. - name: Verify a kernel-only crate builds with no host surface @@ -220,7 +232,7 @@ jobs: # snippets, which is how some forty stale lines outlived the crate switch. host-api-paths: name: host API paths in docs and templates - runs-on: ubuntu-latest + runs-on: linux-amd64-cpu4 timeout-minutes: 5 steps: - name: Clone repository @@ -238,7 +250,7 @@ jobs: # compile. shared-crate-pin-parity: name: shared host-crate pin parity - runs-on: ubuntu-latest + runs-on: linux-amd64-cpu4 timeout-minutes: 5 steps: - name: Clone repository @@ -254,7 +266,7 @@ jobs: # consumer, so the in-tree version, both manifests, and every lock must agree. oxide-artifacts-parity: name: oxide-artifacts release parity - runs-on: ubuntu-latest + runs-on: linux-amd64-cpu4 timeout-minutes: 5 steps: - name: Clone repository diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index cf9a33179b..8a20749cb0 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -1,9 +1,9 @@ --- name: unit-tests -# Runs as a job of the top-level CI workflow (.github/workflows/ci.yml), -# which is the single push/pull_request entry point. Concurrency is governed -# by CI's own group, so this workflow declares none of its own. +# Runs as a job of the top-level CI workflow (.github/workflows/ci.yml). +# Concurrency is governed by CI's own group, so this workflow declares none +# of its own. on: workflow_call: @@ -13,13 +13,15 @@ permissions: jobs: rust-unit-tests: name: cargo test -p ${{ matrix.package }} - runs-on: ubuntu-latest + runs-on: linux-amd64-cpu4 + container: + image: rust:1.98.0-bookworm timeout-minutes: 30 strategy: fail-fast: false matrix: - # Each entry must be runnable on a stock `ubuntu-latest` runner — i.e. - # no live GPU. `needs_cuda: true` installs the CUDA toolkit for build- + # Each entry must be runnable on a driverless runner — no live GPU. + # `needs_cuda: true` installs the CUDA toolkit for build- # time bindgen (every crate that transitively depends on # `cuda-bindings` needs `cuda.h` to compile). `test_args` overrides # the default `--all-targets` where a crate needs a narrower set. @@ -95,6 +97,16 @@ jobs: - name: Clone repository uses: actions/checkout@v4 + - name: Mark the workspace as a safe git directory + run: git config --global --add safe.directory "$GITHUB_WORKSPACE" + + - name: Install sudo + run: | + if ! command -v sudo >/dev/null 2>&1; then + apt-get update -qq + apt-get install -y -qq --no-install-recommends sudo + fi + - name: Install CUDA toolkit if: matrix.needs_cuda uses: Jimver/cuda-toolkit@v0.2.35 @@ -150,12 +162,17 @@ jobs: rustc-codegen-cuda-unit-tests: name: cargo test --lib (rustc-codegen-cuda) - runs-on: ubuntu-latest + runs-on: linux-amd64-cpu4 + container: + image: rust:1.98.0-bookworm timeout-minutes: 30 steps: - name: Clone repository uses: actions/checkout@v4 + - name: Mark the workspace as a safe git directory + run: git config --global --add safe.directory "$GITHUB_WORKSPACE" + # This standalone workspace uses rustc-private APIs, so run its tests # directly rather than pretending the root package matrix covers them. # The live CUDA-tool tests remain ignored; these regressions need no GPU @@ -170,7 +187,9 @@ jobs: generated-intrinsics: name: generated intrinsics are current and ABI-compatible - runs-on: ubuntu-latest + runs-on: linux-amd64-cpu4 + container: + image: rust:1.98.0-bookworm timeout-minutes: 30 steps: - name: Clone repository with ABI history @@ -178,6 +197,9 @@ jobs: with: fetch-depth: 0 + - name: Mark the workspace as a safe git directory + run: git config --global --add safe.directory "$GITHUB_WORKSPACE" + # cuda-intrinsics-gen shells out to rustfmt when it writes generated # sources; it comes from rust-toolchain.toml's component list. From e1dba52c8bb4943d15f8d5fa4c899fc369001a5f Mon Sep 17 00:00:00 2001 From: Roman Ivanov Date: Thu, 15 Oct 2026 17:02:30 -0700 Subject: [PATCH 4/6] ci: run CUDA jobs in the Ubuntu devel image The toolkit install needs lsb_release and an Ubuntu apt repo, so it fails in rust:bookworm. Container steps were also running under dash, which rejects set -o pipefail. Signed-off-by: Roman Ivanov --- .../actions/setup-cuda-container/action.yml | 40 +++++++ .github/workflows/cargo-deny.yml | 5 + .github/workflows/clippy.yml | 41 +++---- .github/workflows/codeql.yml | 5 + .github/workflows/docs.yml | 39 +++---- .github/workflows/examples-compile.yml | 63 ++++------ .github/workflows/fmt.yml | 5 + .github/workflows/status-guard.yml | 5 + .github/workflows/unit-tests.yml | 109 ++++++++++-------- 9 files changed, 173 insertions(+), 139 deletions(-) create mode 100644 .github/actions/setup-cuda-container/action.yml diff --git a/.github/actions/setup-cuda-container/action.yml b/.github/actions/setup-cuda-container/action.yml new file mode 100644 index 0000000000..c5536c3cf6 --- /dev/null +++ b/.github/actions/setup-cuda-container/action.yml @@ -0,0 +1,40 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +--- +name: Set up the CUDA devel container +description: >- + Installs git and the host packages the nvidia/cuda devel image lacks, checks + out the repository with that git, and installs rustup. The image already + contains the CUDA toolkit. Later steps pick up rust-toolchain.toml from + their working directory. + +runs: + using: composite + steps: + - name: Install host packages + shell: bash + run: | + apt-get update -qq + apt-get install -y -qq --no-install-recommends \ + build-essential ca-certificates curl git pkg-config libclang-dev wget + + # checkout needs git on PATH. Without it the action downloads a tarball + # and leaves no repository, so the safe-directory step and anything that + # calls git would not be looking at a checkout. + - name: Clone repository + uses: actions/checkout@v4 + + - name: Mark the workspace as a safe git directory + shell: bash + run: git config --global --add safe.directory "$GITHUB_WORKSPACE" + + - name: Install Rust + shell: bash + run: | + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \ + | sh -s -- -y --no-modify-path --profile minimal --default-toolchain none + echo "${HOME}/.cargo/bin" >> "${GITHUB_PATH}" + # Root rust-toolchain.toml is the stable pin. cuda-oxide/ selects the + # nightly when a later step sets that working directory. + "${HOME}/.cargo/bin/rustup" show + (cd cuda-oxide && "${HOME}/.cargo/bin/rustup" show) diff --git a/.github/workflows/cargo-deny.yml b/.github/workflows/cargo-deny.yml index b7f2b93320..85803cb758 100644 --- a/.github/workflows/cargo-deny.yml +++ b/.github/workflows/cargo-deny.yml @@ -30,6 +30,11 @@ on: permissions: contents: read +# A container job's default shell is dash; use bash, as on ubuntu-latest. +defaults: + run: + shell: bash + jobs: check: runs-on: linux-amd64-cpu4 diff --git a/.github/workflows/clippy.yml b/.github/workflows/clippy.yml index a6b26f121d..9e85e92980 100644 --- a/.github/workflows/clippy.yml +++ b/.github/workflows/clippy.yml @@ -10,42 +10,33 @@ on: permissions: contents: read +# A container job's default shell is dash. The example loop uses bash +# process substitution. +defaults: + run: + shell: bash + jobs: clippy: runs-on: linux-amd64-cpu4 + # CUDA 13.1.1, the toolkit pin these jobs already used. The devel image + # carries nvcc, cudart, and libcurand. rustup is installed in the + # container; the image itself has no Rust. container: - image: rust:1.98.0-bookworm + image: nvidia/cuda:13.1.1-devel-ubuntu24.04 + env: + DEBIAN_FRONTEND: noninteractive + env: + CUDA_TOOLKIT_PATH: /usr/local/cuda timeout-minutes: 30 steps: - - name: Clone repository - uses: actions/checkout@v4 - - - name: Mark the workspace as a safe git directory - run: git config --global --add safe.directory "$GITHUB_WORKSPACE" - - - name: Install sudo - run: | - if ! command -v sudo >/dev/null 2>&1; then - apt-get update -qq - apt-get install -y -qq --no-install-recommends sudo - fi - - - name: Install CUDA toolkit - uses: Jimver/cuda-toolkit@v0.2.35 - id: cuda-toolkit - with: - cuda: "13.1.1" - method: network - sub-packages: '["nvcc", "cudart-dev"]' - non-cuda-sub-packages: '["libcurand-dev"]' + - uses: ./.github/actions/setup-cuda-container # `--all-targets` covers tests, benches and examples, matching the two # steps below and the `just clippy` recipe. Without it a lint that fires # only in a test target passes here and fails for anyone running the # recipe locally. - name: Run clippy (workspace crates) - env: - CUDA_TOOLKIT_PATH: ${{ steps.cuda-toolkit.outputs.CUDA_PATH }} working-directory: cuda-oxide run: cargo clippy --workspace --all-targets -- -D warnings @@ -67,14 +58,12 @@ jobs: # well-defined `target/` under cuda-oxide. - name: Run clippy (rustc-codegen-cuda) env: - CUDA_TOOLKIT_PATH: ${{ steps.cuda-toolkit.outputs.CUDA_PATH }} CARGO_TARGET_DIR: ${{ runner.temp }}/clippy-shared-target working-directory: cuda-oxide/crates/rustc-codegen-cuda run: cargo clippy --all-targets -- -D warnings - name: Run clippy (rustc-codegen-cuda examples) env: - CUDA_TOOLKIT_PATH: ${{ steps.cuda-toolkit.outputs.CUDA_PATH }} CARGO_TARGET_DIR: ${{ runner.temp }}/clippy-shared-target run: | set -e diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 6a99cffcfd..2c84a2e2c3 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -24,6 +24,11 @@ on: # Each commit on main gets its own group, so it starts at once. Any other ref # still shares one group and cancels the older run. +# A container job's default shell is dash; use bash, as on ubuntu-latest. +defaults: + run: + shell: bash + concurrency: group: ${{ github.workflow }}-${{ github.ref_name }}${{ github.ref_name == 'main' && format('-{0}', github.sha) || '' }} cancel-in-progress: ${{ github.ref_name != 'main' }} diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 8e15875983..6932bd18f0 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -28,6 +28,11 @@ concurrency: permissions: contents: read +# A container job's default shell is dash; these steps use bash. +defaults: + run: + shell: bash + # Gate rustdoc and doctests, which the `unit-tests` job does not cover (it runs # `--lib --tests`, excluding doctests, and never builds docs). Keeps broken # intra-doc links and uncompilable doc examples from accumulating silently. @@ -35,38 +40,22 @@ jobs: rustdoc-and-doctests: name: cargo doc + doctests runs-on: linux-amd64-cpu4 + # `cargo doc` compiles cuda-bindings' build-time bindgen, which needs + # cuda.h. The devel image is CUDA 13.1.1, the same pin the toolkit + # install used, and it has no rustup. container: - image: rust:1.98.0-bookworm + image: nvidia/cuda:13.1.1-devel-ubuntu24.04 + env: + DEBIAN_FRONTEND: noninteractive + env: + CUDA_TOOLKIT_PATH: /usr/local/cuda timeout-minutes: 30 steps: - - name: Clone repository - uses: actions/checkout@v4 - - - name: Mark the workspace as a safe git directory - run: git config --global --add safe.directory "$GITHUB_WORKSPACE" - - - name: Install sudo - run: | - if ! command -v sudo >/dev/null 2>&1; then - apt-get update -qq - apt-get install -y -qq --no-install-recommends sudo - fi - - - name: Install CUDA toolkit - # `cargo doc --workspace` compiles cuda-bindings' build-time bindgen, - # which needs `cuda.h`. Same toolkit pin as the unit-tests job. - uses: Jimver/cuda-toolkit@v0.2.35 - id: cuda-toolkit - with: - cuda: "13.1.1" - method: network - sub-packages: '["nvcc", "cudart-dev"]' - non-cuda-sub-packages: '["libcurand-dev"]' + - uses: ./.github/actions/setup-cuda-container - name: Build docs (warning-free) and run doctests working-directory: cuda-oxide env: - CUDA_TOOLKIT_PATH: ${{ steps.cuda-toolkit.outputs.CUDA_PATH }} RUSTDOCFLAGS: "-D warnings" run: | sysroot="$(rustc --print sysroot)" diff --git a/.github/workflows/examples-compile.yml b/.github/workflows/examples-compile.yml index 2facf8943a..d15818c1a1 100644 --- a/.github/workflows/examples-compile.yml +++ b/.github/workflows/examples-compile.yml @@ -28,12 +28,25 @@ concurrency: permissions: contents: read +# A container job's default shell is dash. The LLVM install and the +# smoketest steps use `set -o pipefail`. +defaults: + run: + shell: bash + jobs: examples-compile: name: compile all examples (device pipeline, GPU-less) runs-on: linux-amd64-cpu4 + # CUDA 13.1.1 devel image: nvcc, cudart, libcurand, and libnvJitLink. + # The materialization gate below needs nvJitLink, which the devel image + # ships. rustup is installed in the container. container: - image: rust:1.98.0-bookworm + image: nvidia/cuda:13.1.1-devel-ubuntu24.04 + env: + DEBIAN_FRONTEND: noninteractive + env: + CUDA_TOOLKIT_PATH: /usr/local/cuda # Every example crate: backend build + shared dep tree + per-example # device codegen. Typically well under an hour with the shared # CARGO_TARGET_DIR; the timeout leaves headroom for cold runners. @@ -41,43 +54,17 @@ jobs: # focused 10-minute materialization gate, plus toolkit/LLVM setup. timeout-minutes: 75 steps: - - name: Clone repository - uses: actions/checkout@v4 - - - name: Mark the workspace as a safe git directory - run: git config --global --add safe.directory "$GITHUB_WORKSPACE" - - - name: Install sudo - run: | - if ! command -v sudo >/dev/null 2>&1; then - apt-get update -qq - apt-get install -y -qq --no-install-recommends sudo - fi + - uses: ./.github/actions/setup-cuda-container # Every example workspace must pin the same dependency versions. The # shared CARGO_TARGET_DIR below keys build caches on the resolved # dependency subtree, so one drifted Cargo.lock silently costs a full # dependency-tree rebuild (bindgen over cuda.h included) for that one # example -- a 15-70s spike in an otherwise ~2s-per-example run. - # Fails fast, before any toolkit install. + # Fails fast, before LLVM and the compile. - name: Example lockfiles resolve identical dependency versions run: bash cuda-oxide/scripts/sync-example-locks.sh --check - # Needed for cuda.h and curand.h: bindgen runs in the shared - # cuda-bindings build script. Same setup as unit-tests.yml / clippy.yml. - - name: Install CUDA toolkit - uses: Jimver/cuda-toolkit@v0.2.35 - id: cuda-toolkit - with: - cuda: "13.1.1" - method: network - sub-packages: '["nvcc", "cudart-dev"]' - # nvJitLink is packaged separately from cuda-nvcc on Linux. Keep it - # in this already-toolkit-backed job so the materialization gate - # below exercises the real public command without adding work to - # the latency-sensitive fmt job. - non-cuda-sub-packages: '["libcurand-dev", "libnvjitlink-dev"]' - # The device pipeline shells out to LLVM's llc/opt for PTX # generation. This pin is the FLOOR GATE: if someone lands IR that # needs a newer llc, this job is where the floor bump becomes @@ -89,20 +76,20 @@ jobs: # that only surfaced once the ptxas verdict gate started assembling # what this job produces. The pipeline now rejects such leaks with a # named error, so an under-floor llc fails loudly here. - # If a future ubuntu-latest codename has no llvm-toolchain-*-22 - # suite, apt-get update fails loudly; recover by pinning runs-on to - # the previous image or bumping the LLVM major here. + # The container is Ubuntu 24.04, so VERSION_CODENAME is noble. + # If apt.llvm.org drops llvm-toolchain-noble-22, this update fails + # loudly; recover by bumping the LLVM major here. - name: Install LLVM 22 (llc/opt for the NVPTX backend) run: | set -euo pipefail source /etc/os-release - sudo install -d -m 0755 /etc/apt/keyrings + install -d -m 0755 /etc/apt/keyrings wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key \ - | sudo tee /etc/apt/keyrings/apt-llvm-org.asc >/dev/null + | tee /etc/apt/keyrings/apt-llvm-org.asc >/dev/null echo "deb [signed-by=/etc/apt/keyrings/apt-llvm-org.asc] https://apt.llvm.org/${VERSION_CODENAME}/ llvm-toolchain-${VERSION_CODENAME}-22 main" \ - | sudo tee /etc/apt/sources.list.d/llvm-22.list - sudo apt-get update - sudo apt-get install -y --no-install-recommends llvm-22 + | tee /etc/apt/sources.list.d/llvm-22.list + apt-get update + apt-get install -y --no-install-recommends llvm-22 llc-22 --version | head -2 opt-22 --version | head -2 @@ -112,7 +99,6 @@ jobs: # logs. A step timeout fails the step, so the upload still runs. timeout-minutes: 50 env: - CUDA_TOOLKIT_PATH: ${{ steps.cuda-toolkit.outputs.CUDA_PATH }} CUDA_OXIDE_LLC: /usr/bin/llc-22 # Each example is its own cargo workspace; without a shared # target dir the dep tree rebuilds ~85 times and blows the @@ -140,7 +126,6 @@ jobs: timeout-minutes: 10 working-directory: cuda-oxide env: - CUDA_TOOLKIT_PATH: ${{ steps.cuda-toolkit.outputs.CUDA_PATH }} CUDA_OXIDE_LLC: /usr/bin/llc-22 CARGO_TARGET_DIR: ${{ runner.temp }}/examples-shared-target CARGO_INCREMENTAL: "0" diff --git a/.github/workflows/fmt.yml b/.github/workflows/fmt.yml index 4f21920576..c3c2d96ec1 100644 --- a/.github/workflows/fmt.yml +++ b/.github/workflows/fmt.yml @@ -10,6 +10,11 @@ on: permissions: contents: read +# A container job's default shell is dash, which rejects `set -o pipefail`. +defaults: + run: + shell: bash + jobs: fmt: runs-on: linux-amd64-cpu4 diff --git a/.github/workflows/status-guard.yml b/.github/workflows/status-guard.yml index aa61d2d38d..7e423bfcee 100644 --- a/.github/workflows/status-guard.yml +++ b/.github/workflows/status-guard.yml @@ -18,6 +18,11 @@ concurrency: permissions: contents: read +# A container job's default shell is dash. The VM jobs already run bash. +defaults: + run: + shell: bash + jobs: error-example-status-guard: name: error-example status guard diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 8a20749cb0..3924a66b3c 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -10,6 +10,11 @@ on: permissions: contents: read +# A container job's default shell is dash, which rejects `set -o pipefail`. +defaults: + run: + shell: bash + jobs: rust-unit-tests: name: cargo test -p ${{ matrix.package }} @@ -20,11 +25,11 @@ jobs: strategy: fail-fast: false matrix: - # Each entry must be runnable on a driverless runner — no live GPU. - # `needs_cuda: true` installs the CUDA toolkit for build- - # time bindgen (every crate that transitively depends on - # `cuda-bindings` needs `cuda.h` to compile). `test_args` overrides - # the default `--all-targets` where a crate needs a narrower set. + # Each entry must be runnable on a driverless runner — no live GPU, + # and this image has no CUDA toolkit. `cargo-oxide` doctor asserts + # that absence. Crates that bindgen against cuda.h run in + # `rust-unit-tests-cuda`. `test_args` overrides the default + # `--all-targets` where a crate needs a narrower set. # # Crates intentionally not in this matrix: # * `fuzzer` — differential-testing infra with no unit tests. @@ -57,9 +62,6 @@ jobs: - package: dialect-mir - package: dialect-nvvm - package: mir-importer - # Includes a real llc -> ptxas spine-kernel validation. - - package: cuda-oxide-codegen - needs_cuda: true - package: mir-lower - package: mir-transforms - package: nvvm-transforms @@ -71,10 +73,6 @@ jobs: - package: oxide-artifacts test_args: --all-targets --features object - package: reserved-oxide-symbols - # launch_requires_unsafe trybuild fixture dev-depends on - # cuda-host/cuda-core, which transitively pull cuda-bindings - - package: cuda-macros - needs_cuda: true # trybuild compile-fail suite for ThreadIndex / DisjointSlice safety - package: cuda-device # Runtime CUDA compiler/linker discovery and file-identity checks; @@ -85,9 +83,6 @@ jobs: # pure and validates options, provenance, and cubin structure; the # live-toolkit smoke test remains explicitly ignored. - package: cuda-artifact-finalizer - # pure-Rust kernel-launch helpers + tcgen05 tiling tables - - package: cuda-host - needs_cuda: true # cargo subcommand wrapper; pure Rust, no CUDA toolkit required. # Running it on a runner with NO toolkit installed is the # regression gate for issue #87. @@ -100,41 +95,7 @@ jobs: - name: Mark the workspace as a safe git directory run: git config --global --add safe.directory "$GITHUB_WORKSPACE" - - name: Install sudo - run: | - if ! command -v sudo >/dev/null 2>&1; then - apt-get update -qq - apt-get install -y -qq --no-install-recommends sudo - fi - - - name: Install CUDA toolkit - if: matrix.needs_cuda - uses: Jimver/cuda-toolkit@v0.2.35 - id: cuda-toolkit - with: - cuda: "13.1.1" - method: network - sub-packages: '["nvcc", "cudart-dev"]' - non-cuda-sub-packages: '["libcurand-dev"]' - - - name: Install LLVM 21 (llc/opt for the NVPTX backend) - if: matrix.package == 'cuda-oxide-codegen' - run: | - set -euo pipefail - source /etc/os-release - sudo install -d -m 0755 /etc/apt/keyrings - wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key \ - | sudo tee /etc/apt/keyrings/apt-llvm-org.asc >/dev/null - echo "deb [signed-by=/etc/apt/keyrings/apt-llvm-org.asc] https://apt.llvm.org/${VERSION_CODENAME}/ llvm-toolchain-${VERSION_CODENAME}-21 main" \ - | sudo tee /etc/apt/sources.list.d/llvm-21.list - sudo apt-get update - sudo apt-get install -y --no-install-recommends llvm-21 - llc-21 --version | head -2 - opt-21 --version | head -2 - - name: Run crate unit tests - env: - CUDA_TOOLKIT_PATH: ${{ steps.cuda-toolkit.outputs.CUDA_PATH }} working-directory: cuda-oxide run: | sysroot="$(rustc --print sysroot)" @@ -160,6 +121,56 @@ jobs: printf '%s\n' "${out}" printf '%s\n' "${out}" | grep -q "CUDA headers (cuda.h)" + # cuda.h crates. A container is chosen per job, so these cannot share the + # rust image above without also handing cargo-oxide a toolkit. + rust-unit-tests-cuda: + name: cargo test -p ${{ matrix.package }} + runs-on: linux-amd64-cpu4 + container: + image: nvidia/cuda:13.1.1-devel-ubuntu24.04 + env: + DEBIAN_FRONTEND: noninteractive + env: + CUDA_TOOLKIT_PATH: /usr/local/cuda + timeout-minutes: 30 + strategy: + fail-fast: false + matrix: + include: + # Includes a real llc -> ptxas spine-kernel validation. + - package: cuda-oxide-codegen + # launch_requires_unsafe trybuild fixture dev-depends on + # cuda-host/cuda-core, which transitively pull cuda-bindings. + - package: cuda-macros + # pure-Rust kernel-launch helpers + tcgen05 tiling tables. + # bindgen still needs cuda.h. + - package: cuda-host + steps: + - uses: ./.github/actions/setup-cuda-container + + - name: Install LLVM 21 (llc/opt for the NVPTX backend) + if: matrix.package == 'cuda-oxide-codegen' + run: | + set -euo pipefail + source /etc/os-release + install -d -m 0755 /etc/apt/keyrings + wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key \ + | tee /etc/apt/keyrings/apt-llvm-org.asc >/dev/null + echo "deb [signed-by=/etc/apt/keyrings/apt-llvm-org.asc] https://apt.llvm.org/${VERSION_CODENAME}/ llvm-toolchain-${VERSION_CODENAME}-21 main" \ + | tee /etc/apt/sources.list.d/llvm-21.list + apt-get update + apt-get install -y --no-install-recommends llvm-21 + llc-21 --version | head -2 + opt-21 --version | head -2 + + - name: Run crate unit tests + working-directory: cuda-oxide + run: | + sysroot="$(rustc --print sysroot)" + export LIBRARY_PATH="${sysroot}/lib:${LIBRARY_PATH:-}" + export LD_LIBRARY_PATH="${sysroot}/lib:${LD_LIBRARY_PATH:-}" + cargo test -p ${{ matrix.package }} --all-targets + rustc-codegen-cuda-unit-tests: name: cargo test --lib (rustc-codegen-cuda) runs-on: linux-amd64-cpu4 From e73b01907c10bb02960c19b1406684b27e3658c5 Mon Sep 17 00:00:00 2001 From: Roman Ivanov Date: Thu, 15 Oct 2026 17:21:07 -0700 Subject: [PATCH 5/6] ci: check out the repo before the CUDA setup action A local action is resolved from the workspace, so the setup action cannot be the first step. Install git, then checkout, then rustup. Signed-off-by: Roman Ivanov --- .github/actions/setup-cuda-container/action.yml | 16 ++++++---------- .github/workflows/clippy.yml | 8 ++++++++ .github/workflows/docs.yml | 8 ++++++++ .github/workflows/examples-compile.yml | 8 ++++++++ .github/workflows/unit-tests.yml | 8 ++++++++ 5 files changed, 38 insertions(+), 10 deletions(-) diff --git a/.github/actions/setup-cuda-container/action.yml b/.github/actions/setup-cuda-container/action.yml index c5536c3cf6..90a95c5a19 100644 --- a/.github/actions/setup-cuda-container/action.yml +++ b/.github/actions/setup-cuda-container/action.yml @@ -3,10 +3,12 @@ --- name: Set up the CUDA devel container description: >- - Installs git and the host packages the nvidia/cuda devel image lacks, checks - out the repository with that git, and installs rustup. The image already - contains the CUDA toolkit. Later steps pick up rust-toolchain.toml from - their working directory. + Installs the host packages the nvidia/cuda devel image lacks, marks the + workspace safe for git, and installs rustup. The caller checks the + repository out first: a local action is resolved from the workspace, so + this action cannot be the job's first step. The image already contains + the CUDA toolkit. Later steps pick up rust-toolchain.toml from their + working directory. runs: using: composite @@ -18,12 +20,6 @@ runs: apt-get install -y -qq --no-install-recommends \ build-essential ca-certificates curl git pkg-config libclang-dev wget - # checkout needs git on PATH. Without it the action downloads a tarball - # and leaves no repository, so the safe-directory step and anything that - # calls git would not be looking at a checkout. - - name: Clone repository - uses: actions/checkout@v4 - - name: Mark the workspace as a safe git directory shell: bash run: git config --global --add safe.directory "$GITHUB_WORKSPACE" diff --git a/.github/workflows/clippy.yml b/.github/workflows/clippy.yml index 9e85e92980..b40aeaa069 100644 --- a/.github/workflows/clippy.yml +++ b/.github/workflows/clippy.yml @@ -30,6 +30,14 @@ jobs: CUDA_TOOLKIT_PATH: /usr/local/cuda timeout-minutes: 30 steps: + # A local action is loaded from the workspace, so checkout comes first. + # The CUDA image has no git; without it checkout stores a tarball. + - name: Install git + run: | + apt-get update -qq + apt-get install -y -qq --no-install-recommends ca-certificates git + - name: Clone repository + uses: actions/checkout@v4 - uses: ./.github/actions/setup-cuda-container # `--all-targets` covers tests, benches and examples, matching the two diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 6932bd18f0..b611f9df43 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -51,6 +51,14 @@ jobs: CUDA_TOOLKIT_PATH: /usr/local/cuda timeout-minutes: 30 steps: + # A local action is loaded from the workspace, so checkout comes first. + # The CUDA image has no git; without it checkout stores a tarball. + - name: Install git + run: | + apt-get update -qq + apt-get install -y -qq --no-install-recommends ca-certificates git + - name: Clone repository + uses: actions/checkout@v4 - uses: ./.github/actions/setup-cuda-container - name: Build docs (warning-free) and run doctests diff --git a/.github/workflows/examples-compile.yml b/.github/workflows/examples-compile.yml index d15818c1a1..40ba2d4645 100644 --- a/.github/workflows/examples-compile.yml +++ b/.github/workflows/examples-compile.yml @@ -54,6 +54,14 @@ jobs: # focused 10-minute materialization gate, plus toolkit/LLVM setup. timeout-minutes: 75 steps: + # A local action is loaded from the workspace, so checkout comes first. + # The CUDA image has no git; without it checkout stores a tarball. + - name: Install git + run: | + apt-get update -qq + apt-get install -y -qq --no-install-recommends ca-certificates git + - name: Clone repository + uses: actions/checkout@v4 - uses: ./.github/actions/setup-cuda-container # Every example workspace must pin the same dependency versions. The diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 3924a66b3c..9ef31fba2e 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -146,6 +146,14 @@ jobs: # bindgen still needs cuda.h. - package: cuda-host steps: + # A local action is loaded from the workspace, so checkout comes first. + # The CUDA image has no git; without it checkout stores a tarball. + - name: Install git + run: | + apt-get update -qq + apt-get install -y -qq --no-install-recommends ca-certificates git + - name: Clone repository + uses: actions/checkout@v4 - uses: ./.github/actions/setup-cuda-container - name: Install LLVM 21 (llc/opt for the NVPTX backend) From 6083b7cb14412d3d5efab3566da9c5d2018e2017 Mon Sep 17 00:00:00 2001 From: Roman Ivanov Date: Thu, 15 Oct 2026 17:30:59 -0700 Subject: [PATCH 6/6] ci: install python3 for the example lockfile check sync-example-locks.sh --check runs under python3. The CUDA devel image does not include it. Signed-off-by: Roman Ivanov --- .github/workflows/examples-compile.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/examples-compile.yml b/.github/workflows/examples-compile.yml index 40ba2d4645..cca315e663 100644 --- a/.github/workflows/examples-compile.yml +++ b/.github/workflows/examples-compile.yml @@ -56,10 +56,12 @@ jobs: steps: # A local action is loaded from the workspace, so checkout comes first. # The CUDA image has no git; without it checkout stores a tarball. - - name: Install git + # sync-example-locks.sh --check is a python3 stdlib script. ubuntu-latest + # had python3; this image does not. + - name: Install git and Python run: | apt-get update -qq - apt-get install -y -qq --no-install-recommends ca-certificates git + apt-get install -y -qq --no-install-recommends ca-certificates git python3 - name: Clone repository uses: actions/checkout@v4 - uses: ./.github/actions/setup-cuda-container