Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions .github/actions/setup-cuda-container/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# 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 the host packages the nvidia/cuda devel image lacks, marks the
workspace safe for git, and installs rustup. Callers use the $/ form so
this runs before checkout and git is already installed. 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
# python3 is for sync-example-locks.sh. The image has neither git nor
# python3; git has to be present before actions/checkout.
apt-get install -y -qq --no-install-recommends \
build-essential ca-certificates curl git pkg-config libclang-dev \
python3 wget

- 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
env:
RUSTUP_VERSION: "1.29.2"
# SHA-256 of rustup-init for x86_64-unknown-linux-gnu, published next to
# the binary as .../rustup/archive/1.29.2/x86_64-unknown-linux-gnu/rustup-init.sha256.
# Bump both values together.
RUSTUP_INIT_SHA256: "5be191e8c70a5007074254cb74d78dbfdd25cd5a61c27de7b8e5410122ec24f4"
run: |
set -euo pipefail
[ "$(uname -m)" = "x86_64" ] || { echo "the rustup-init pin is for x86_64"; exit 1; }
init="${RUNNER_TEMP}/rustup-init"
curl --proto '=https' --tlsv1.2 -sSfo "${init}" \
"https://static.rust-lang.org/rustup/archive/${RUSTUP_VERSION}/x86_64-unknown-linux-gnu/rustup-init"
echo "${RUSTUP_INIT_SHA256} ${init}" | sha256sum -c -
chmod +x "${init}"
"${init}" -y --no-modify-path --profile minimal --default-toolchain none
rm -f "${init}"
echo "${HOME}/.cargo/bin" >> "${GITHUB_PATH}"
# $/ runs this before the workspace checkout. The action download is
# the same commit, and rustup finds rust-toolchain.toml by walking up
# from the working directory. The root file is the stable pin;
# cuda-oxide/ selects the nightly.
repo="${GITHUB_ACTION_PATH}/../../.."
(cd "${repo}" && "${HOME}/.cargo/bin/rustup" show)
(cd "${repo}/cuda-oxide" && "${HOME}/.cargo/bin/rustup" show)
76 changes: 50 additions & 26 deletions .github/workflows/book.yml
Original file line number Diff line number Diff line change
@@ -1,27 +1,15 @@
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/<n>, 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]
paths:
- 'cuda-oxide/cuda-oxide-book/**'
- 'cutile-rs/cutile-book/**'
- 'cutile-rs/scripts/build_versioned_book.sh'
- 'docs/**'
- '.github/workflows/book.yml'
# No workflow-level paths: filter; the changes job below gates the jobs.
branches:
- main
- "pull-request/[0-9]+"
workflow_dispatch:

# Only checkout rights by default. The Pages deployment permissions live on
Expand All @@ -39,11 +27,47 @@ 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:
# Path gate. copy-pr-bot creates pull-request/<n> at an existing SHA, and
# GitHub often treats that push as having no path changes, so a workflow-level
# `paths:` filter would skip the whole run. This job diffs against main
# instead, as cutile-rs.yml does, and every job below runs when no file diff
# is detected. Other events (schedule, workflow_dispatch) skip the gate.
changes:
if: github.event_name == 'push'
runs-on: linux-amd64-cpu4
timeout-minutes: 5
permissions:
contents: read
outputs:
relevant: ${{ steps.changes.outputs.relevant }}
any_changed: ${{ steps.changes.outputs.any_changed }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: dorny/paths-filter@0e4a8c6effa4802afeda77dc8d303f8176d7dfad # v3.0.4
id: changes
with:
filters: |
relevant:
- 'cuda-oxide/cuda-oxide-book/**'
- 'cutile-rs/cutile-book/**'
- 'cutile-rs/scripts/build_versioned_book.sh'
- 'docs/**'
- '.github/workflows/book.yml'
# `*` covers a root file, `**` every nested path. Both false means
# no file diff was detected.
any_changed:
- '*'
- '**'

build:
runs-on: ubuntu-latest
needs: changes
if: ${{ !cancelled() && (github.event_name != 'push' || needs.changes.outputs.relevant == 'true' || needs.changes.outputs.any_changed != 'true') }}
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.
Expand Down Expand Up @@ -76,17 +100,17 @@ 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,
# so uploading it there produces an artifact nothing can ever read. A PR's
# 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
Expand All @@ -96,7 +120,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
Expand Down
37 changes: 31 additions & 6 deletions .github/workflows/cargo-deny.yml
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -30,14 +30,24 @@ 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: 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

Expand Down Expand Up @@ -94,25 +104,35 @@ 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
run: bash cuda-oxide/scripts/check-dependency-licenses.sh

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

Expand All @@ -125,12 +145,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.
Expand Down
10 changes: 4 additions & 6 deletions .github/workflows/ci-status.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
75 changes: 54 additions & 21 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,16 @@ 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/<n>, 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]
paths:
- 'cuda-oxide/**'
- 'cuda-bindings/**'
- 'cuda-core/**'
- 'cuda-core-derive/**'
- 'cuda-async/**'
- 'Cargo.toml'
- 'Cargo.lock'
- '.github/workflows/**'
# No workflow-level paths: filter; the changes job below gates the jobs.
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.
Expand All @@ -44,14 +31,60 @@ permissions:
contents: read

jobs:
# Path gate. copy-pr-bot creates pull-request/<n> at an existing SHA, and
# GitHub often treats that push as having no path changes, so a workflow-level
# `paths:` filter would skip the whole run. This job diffs against main
# instead, as cutile-rs.yml does, and every job below runs when no file diff
# is detected. Other events (schedule, workflow_dispatch) skip the gate.
changes:
if: github.event_name == 'push'
runs-on: linux-amd64-cpu4
timeout-minutes: 5
permissions:
contents: read
outputs:
relevant: ${{ steps.changes.outputs.relevant }}
any_changed: ${{ steps.changes.outputs.any_changed }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: dorny/paths-filter@0e4a8c6effa4802afeda77dc8d303f8176d7dfad # v3.0.4
id: changes
with:
filters: |
relevant:
- 'cuda-oxide/**'
- 'cuda-bindings/**'
- 'cuda-core/**'
- 'cuda-core-derive/**'
- 'cuda-async/**'
- 'Cargo.toml'
- 'Cargo.lock'
- '.github/workflows/**'
- '.github/actions/setup-cuda-container/**'
# `*` covers a root file, `**` every nested path. Both false means
# no file diff was detected.
any_changed:
- '*'
- '**'

fmt:
needs: changes
if: ${{ !cancelled() && (github.event_name != 'push' || needs.changes.outputs.relevant == 'true' || needs.changes.outputs.any_changed != 'true') }}
uses: ./.github/workflows/fmt.yml

clippy:
needs: changes
if: ${{ !cancelled() && (github.event_name != 'push' || needs.changes.outputs.relevant == 'true' || needs.changes.outputs.any_changed != 'true') }}
uses: ./.github/workflows/clippy.yml

unit-tests:
needs: changes
if: ${{ !cancelled() && (github.event_name != 'push' || needs.changes.outputs.relevant == 'true' || needs.changes.outputs.any_changed != 'true') }}
uses: ./.github/workflows/unit-tests.yml

cargo-deny:
needs: changes
if: ${{ !cancelled() && (github.event_name != 'push' || needs.changes.outputs.relevant == 'true' || needs.changes.outputs.any_changed != 'true') }}
uses: ./.github/workflows/cargo-deny.yml
Loading
Loading