diff --git a/.github/workflows/test-wheel-linux.yml b/.github/workflows/test-wheel-linux.yml index c2c7523b68c..4b35c689a05 100644 --- a/.github/workflows/test-wheel-linux.yml +++ b/.github/workflows/test-wheel-linux.yml @@ -361,13 +361,13 @@ jobs: host-platform: ${{ inputs.host-platform }} cuda-version: ${{ matrix.CUDA_VER }} - - name: Set up latest cuda_sanitizer_api + - name: Set up cuda_sanitizer_api if: ${{ (env.TEST_BINDINGS == 'true' || env.TEST_CORE == 'true') && env.SETUP_SANITIZER == '1' }} uses: ./.github/actions/fetch_ctk continue-on-error: false with: host-platform: ${{ inputs.host-platform }} - cuda-version: ${{ env.LATEST_CUDA_VERSION }} + cuda-version: ${{ matrix.CUDA_VER }} cuda-components: "cuda_sanitizer_api" - name: Set up compute-sanitizer diff --git a/ci/tools/compute_ci_plan.py b/ci/tools/compute_ci_plan.py index 51493044569..1ab4b2961b9 100644 --- a/ci/tools/compute_ci_plan.py +++ b/ci/tools/compute_ci_plan.py @@ -56,7 +56,6 @@ ".github/workflows/test-wheel-linux.yml": "linux", ".github/workflows/test-wheel-windows.yml": "windows", "ci/tools/configure_driver_mode.ps1": "windows", - "ci/tools/guess_latest.sh": "linux", "ci/tools/install_gpu_driver.ps1": "windows", "ci/tools/install_gpu_driver.sh": "linux", "ci/tools/setup-sanitizer": "linux", diff --git a/ci/tools/env-vars b/ci/tools/env-vars index 262de423065..9946ad549de 100755 --- a/ci/tools/env-vars +++ b/ci/tools/env-vars @@ -119,7 +119,6 @@ elif [[ "${1}" == "test" ]]; then # We only test compute-sanitizer on python 3.12 arbitrarily; we don't need to use sanitizer on the entire matrix # Only local ctk installs have compute-sanitizer; there is no wheel for it if [[ "${PY_VER}" == "3.12" && "${CUDA_VER}" != "11.8.0" && "${LOCAL_CTK}" == 1 && "${HOST_PLATFORM}" == linux* ]]; then - echo "LATEST_CUDA_VERSION=$(bash ci/tools/guess_latest.sh $TEST_CUDA_MAJOR)" >> $GITHUB_ENV SETUP_SANITIZER=1 else SETUP_SANITIZER=0 diff --git a/ci/tools/fetch_ctk_redistrib.py b/ci/tools/fetch_ctk_redistrib.py index 1a75ed471a8..6217dad42da 100644 --- a/ci/tools/fetch_ctk_redistrib.py +++ b/ci/tools/fetch_ctk_redistrib.py @@ -29,10 +29,11 @@ # Known-good sha256 of the redistrib metadata JSON, keyed by CUDA version. # Per-archive verification is only trustworthy if the JSON that carries the -# archive hashes is itself trusted, so we pin the metadata too. A version that -# is absent here is not blocked -- failing closed would break CI on every new -# CTK -- but `verify-metadata` emits a warning; add the version's hash below as -# it enters the matrix to pin it. Regenerate with: +# archive hashes is itself trusted, so we pin the metadata too. `verify-metadata` +# fails on a public-server version that is absent here, so add a version's hash +# below in the same change that adds it to ci/versions.yml or a LOCAL_CTK row of +# ci/test-matrix.yml; tests/test_fetch_ctk_redistrib.py enforces this. +# Regenerate with: # curl -fsSL \ # https://developer.download.nvidia.com/compute/cuda/redist/redistrib_.json \ # | sha256sum @@ -41,6 +42,7 @@ "12.9.1": "8335301010b0023ee1ff61eb11e2600ca62002d76780de4089011ad77e0c7630", "13.0.2": "fce66717a81c510ffeb89ecc3e79849ab34af3b80139f750876d9033e31d71c2", "13.4.2": "015b97aa3977d4d0bade1631b226b6d4cbe48751cc841aad9193295df7f9047a", + "13.5.0": "401529e951b845788177f999c0756c520c4d0db1cd4cd05c0a295e331884b596", } @@ -154,8 +156,8 @@ def verify_metadata_sha256(*, cuda_version: str, metadata_path: str, metadata_so manifest under the same version number, so it is never checked against them; it only gets a warning. - Returns a process exit code: 1 on a mismatch (fail closed), 0 when the hash - matches, the version is not pinned yet, or the source is custom (warn only). + Returns a process exit code: 1 on a mismatch or an unpinned public version + (fail closed), 0 when the hash matches or the source is custom (warn only). """ actual = compute_file_sha256(metadata_path) if metadata_source == "custom": @@ -168,11 +170,11 @@ def verify_metadata_sha256(*, cuda_version: str, metadata_path: str, metadata_so expected = KNOWN_REDISTRIB_SHA256.get(cuda_version) if expected is None: print( - f"WARNING: no pinned redistrib sha256 for CUDA {cuda_version!r}; got {actual}. " + f"ERROR: no pinned redistrib sha256 for CUDA {cuda_version!r}; got {actual}. " f"Add it to KNOWN_REDISTRIB_SHA256 in {Path(__file__).name} to pin it.", file=sys.stderr, ) - return 0 + return 1 if actual != expected: print( f"ERROR: redistrib metadata sha256 mismatch for CUDA {cuda_version!r}: expected {expected}, got {actual}", @@ -207,7 +209,6 @@ def parse_args(argv: list[str] | None = None) -> argparse.Namespace: verify_parser.add_argument("--cuda-version", required=True) verify_parser.add_argument("--metadata-path", required=True) verify_parser.add_argument("--metadata-source", choices=("public", "custom"), default="public") - return parser.parse_args(argv) diff --git a/ci/tools/guess_latest.sh b/ci/tools/guess_latest.sh deleted file mode 100644 index f9d383f2094..00000000000 --- a/ci/tools/guess_latest.sh +++ /dev/null @@ -1,42 +0,0 @@ -#!/bin/bash -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# -# SPDX-License-Identifier: Apache-2.0 - -# URL to search -URL="https://developer.download.nvidia.com/compute/cuda/redist/" - -# Ensure exactly one argument is provided -if [ "$#" -ne 1 ]; then - echo "Usage: $0 " - exit 1 -fi - -# Accept major version as the first argument -MAJOR_VERSION="$1" - -# Fetch the directory listing and extract the latest version number -get_latest_version() { - # Get the HTML content of the page - local html_content=$(wget -q -O - "$URL") - - # Extract links matching the pattern redistrib_?.?.?.json - local files=$(echo "$html_content" | grep -oP "redistrib_${MAJOR_VERSION}\.[0-9]+\.[0-9]+\.json" | cut -d'"' -f2) - - # If files were found, extract the version numbers and find the latest - if [ -n "$files" ]; then - # Extract just the version numbers using regex - local versions=$(echo "$files" | grep -oP "redistrib_\K${MAJOR_VERSION}\.[0-9]+\.[0-9]+(?=\.json)") - - # Sort the versions and get the latest - local latest_version=$(echo "$versions" | sort -V | tail -n 1) - echo "$latest_version" - else - echo "No files matching the pattern were found." - return 1 - fi -} - -# Call the function and store the result -latest_version=$(get_latest_version) -echo $latest_version diff --git a/ci/tools/tests/test_compute_ci_plan.py b/ci/tools/tests/test_compute_ci_plan.py index b87326420ee..c08eddb6dcd 100644 --- a/ci/tools/tests/test_compute_ci_plan.py +++ b/ci/tools/tests/test_compute_ci_plan.py @@ -85,7 +85,6 @@ def test_test_infrastructure_platforms(self) -> None: ".github/workflows/test-wheel-linux.yml": {"linux"}, ".github/workflows/test-wheel-windows.yml": {"windows"}, "ci/tools/configure_driver_mode.ps1": {"windows"}, - "ci/tools/guess_latest.sh": {"linux"}, "ci/tools/install_gpu_driver.ps1": {"windows"}, "ci/tools/install_gpu_driver.sh": {"linux"}, "ci/tools/setup-sanitizer": {"linux"}, diff --git a/ci/tools/tests/test_fetch_ctk_redistrib.py b/ci/tools/tests/test_fetch_ctk_redistrib.py index dad75da1a1b..03f9a29df50 100644 --- a/ci/tools/tests/test_fetch_ctk_redistrib.py +++ b/ci/tools/tests/test_fetch_ctk_redistrib.py @@ -4,13 +4,16 @@ from __future__ import annotations import json +import re import sys from pathlib import Path import pytest +import yaml sys.path.insert(0, str(Path(__file__).parent.parent)) from fetch_ctk_redistrib import ( + KNOWN_REDISTRIB_SHA256, compute_file_sha256, get_component_relative_path, get_component_sha256, @@ -18,6 +21,9 @@ verify_metadata_sha256, ) +CI_DIR = Path(__file__).resolve().parents[2] +SHA256_RE = re.compile(r"^[0-9a-f]{64}$") + # Minimal redistrib-shaped metadata. "cccl" exercises the cuda_cccl -> cccl # alias; "no_sha" lacks the sha256 field. FAKE_METADATA = { @@ -101,14 +107,37 @@ def test_verify_metadata_mismatch_fails_closed(tmp_path, monkeypatch): assert verify_metadata_sha256(cuda_version="1.2.3", metadata_path=str(f)) == 1 -@pytest.mark.agent_authored(model="claude-opus-4.8") -def test_verify_metadata_unknown_version_warns(tmp_path, capsys): +@pytest.mark.agent_authored(model="claude-opus-5.5") +def test_verify_metadata_unknown_version_fails_closed(tmp_path, capsys): f = tmp_path / "redistrib.json" f.write_bytes(b"whatever") - # An unpinned version must not fail closed (it would break CI on every new - # CTK), but it must emit a warning. - assert verify_metadata_sha256(cuda_version="99.9.9", metadata_path=str(f)) == 0 - assert "WARNING" in capsys.readouterr().err + assert verify_metadata_sha256(cuda_version="99.9.9", metadata_path=str(f)) == 1 + assert "ERROR" in capsys.readouterr().err + + +@pytest.mark.agent_authored(model="claude-opus-5.5") +def test_known_redistrib_sha256_covers_ci_cuda_versions(): + # Every CUDA version CI fetches from the public server must be pinned, so a + # version bump fails here instead of in the fetch_ctk step. + versions = yaml.safe_load((CI_DIR / "versions.yml").read_text(encoding="utf-8")) + matrix = yaml.safe_load((CI_DIR / "test-matrix.yml").read_text(encoding="utf-8")) + required = {str(entry["version"]) for entry in versions["cuda"].values()} + required |= { + str(row["CUDA_VER"]) + for platform in ("linux", "windows") + for rows in matrix[platform].values() + for row in rows + if str(row.get("LOCAL_CTK")) == "1" + } + assert required + missing = sorted(required - KNOWN_REDISTRIB_SHA256.keys()) + assert not missing, ( + f"CUDA versions used by CI have no pinned redistrib manifest hash: {missing}. " + "Add them to KNOWN_REDISTRIB_SHA256 in ci/tools/fetch_ctk_redistrib.py; compute each hash with " + "`curl -fsSL https://developer.download.nvidia.com/compute/cuda/redist/redistrib_.json | sha256sum`." + ) + for version, sha256 in KNOWN_REDISTRIB_SHA256.items(): + assert SHA256_RE.fullmatch(sha256), f"invalid pinned sha256 for CUDA {version}" @pytest.mark.agent_authored(model="claude-opus-4.8") diff --git a/ci/versions.yml b/ci/versions.yml index 2a48f83497c..de50338604d 100644 --- a/ci/versions.yml +++ b/ci/versions.yml @@ -6,6 +6,9 @@ backport_branch: "12.9.x" # keep in sync with target-branch in .github/dependab # This controls the version of the upstream CTK to install to build the wheels. # It is not the version of cuda_bindings/cuda_python being released, which is # solely determined by git tags via setuptools_scm. +# Each version here, and in every LOCAL_CTK: '1' row of ci/test-matrix.yml, +# needs a pinned manifest hash in KNOWN_REDISTRIB_SHA256 in +# ci/tools/fetch_ctk_redistrib.py; fetch_ctk fails without one. cuda: build: version: "13.5.0"