-
Notifications
You must be signed in to change notification settings - Fork 306
Build cuda.core RDC test fixtures without Bash #2335
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| # SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
| """Build the relocatable-device-code fixtures used by cuda.core tests.""" | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import os | ||
| import subprocess | ||
| import tempfile | ||
| from pathlib import Path | ||
|
|
||
|
|
||
| def _run(command: list[str]) -> None: | ||
| print(f"+ {subprocess.list2cmdline(command)}") | ||
| result = subprocess.run(command) # noqa: S603 | ||
| if result.returncode != 0: | ||
| raise SystemExit(result.returncode) | ||
|
|
||
|
|
||
| def main() -> None: | ||
| script_dir = Path(__file__).resolve().parent | ||
| source_path = script_dir / "saxpy.cu" | ||
| final_object_path = script_dir / "saxpy.o" | ||
| final_library_path = script_dir / ("saxpy.lib" if os.name == "nt" else "saxpy.a") | ||
|
|
||
| nvcc_extra_flags = ["-std=c++17"] | ||
| if os.name == "nt": | ||
| nvcc_extra_flags.extend(["-Xcompiler", "/Zc:preprocessor"]) | ||
|
|
||
| with tempfile.TemporaryDirectory(prefix="build_test_binaries-", dir=script_dir) as temp_dir: | ||
| temp_dir_path = Path(temp_dir) | ||
| object_path = temp_dir_path / final_object_path.name | ||
| library_path = temp_dir_path / final_library_path.name | ||
|
|
||
| _run( | ||
| [ | ||
| "nvcc", | ||
| "-dc", | ||
| *nvcc_extra_flags, | ||
| "-arch=all-major", | ||
| "-o", | ||
| str(object_path), | ||
| str(source_path), | ||
| ] | ||
| ) | ||
| _run(["nvcc", "-lib", "-o", str(library_path), str(object_path)]) | ||
|
|
||
| object_path.replace(final_object_path) | ||
| library_path.replace(final_library_path) | ||
|
|
||
| for path in (final_object_path, final_library_path): | ||
| print(f"{path}: {path.stat().st_size} bytes") | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| main() | ||
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,6 +5,7 @@ | |
| import os | ||
| import pickle | ||
| import subprocess | ||
| import sys | ||
| import warnings | ||
| from pathlib import Path | ||
|
|
||
|
|
@@ -16,7 +17,6 @@ | |
| from cuda.core._program import _can_load_generated_ptx | ||
| from cuda.core._utils.cuda_utils import CUDAError, driver, handle_return | ||
| from cuda.core._utils.version import binding_version, driver_version | ||
| from cuda.pathfinder import find_nvidia_binary_utility | ||
|
|
||
| try: | ||
| import numba | ||
|
|
@@ -193,22 +193,77 @@ def _read_saxpy_rdc(kind: str) -> bytes: | |
| raise ValueError(f"unknown saxpy RDC kind: {kind!r}") | ||
|
|
||
| if not rdc_path.is_file(): | ||
| nvcc_path = find_nvidia_binary_utility("nvcc") | ||
| if nvcc_path is None: | ||
| pytest.skip( | ||
| f"{rdc_path.name} not found at {rdc_path} and nvcc is unavailable. " | ||
| "In CI this is downloaded from the build stage." | ||
| ) | ||
| env = os.environ.copy() | ||
| env["NVCC"] = nvcc_path | ||
| subprocess.run( # noqa: S603 | ||
| ["bash", str(binaries_dir / "build_test_binaries.sh")], # noqa: S607 | ||
| check=True, | ||
| env=env, | ||
| ) | ||
| _build_saxpy_rdc(binaries_dir) | ||
| return rdc_path.read_bytes() | ||
|
|
||
|
|
||
| def _subprocess_output(result: subprocess.CompletedProcess[str]) -> str: | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could we delete this function _subprocess_output and just default to system stdout/stderr behaviors?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would prefer to keep this helper. We intentionally capture stdout/stderr here because we need to inspect the For unexpected failures, On success we still print the captured output back to stdout/stderr, so the normal diagnostic trail is preserved. |
||
| sections = [] | ||
| if result.stdout: | ||
| sections.append(f"stdout:\n{result.stdout.rstrip()}") | ||
| if result.stderr: | ||
| sections.append(f"stderr:\n{result.stderr.rstrip()}") | ||
| return "\n".join(sections) or "<no output>" | ||
|
|
||
|
|
||
| def _host_compiler_is_unavailable(output: str) -> bool: | ||
| normalized = output.lower() | ||
| # Keep these patterns narrow. Unknown nvcc failures should fail the test and | ||
| # expose their diagnostics, not be silently reclassified as environment skips. | ||
| windows_compiler_missing = ( | ||
| "cannot find compiler" in normalized and "cl.exe" in normalized and "in path" in normalized | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is "cannot find compiler" keyword stable enough and will not be modified in future release of nvcc logging? I got the following from agent and it works on linux but not sure on windows. for line in p.stdout.splitlines():
if line.startswith("#$"):
cmd = line[3:].strip().split()
if cmd:
return cmd[0] # gcc / g++ / clang++ / cl.exe
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good question. I do not want to rely on this message being stable forever. The intent is almost the opposite: keep the recognized skip cases narrow. If The |
||
| ) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. On windows is "cl.exe" the only host compiler available? What if nvcc is using another host compiler?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For the path this PR exercises, yes, the Windows host compiler we expect is MSVC If someone has a custom Windows In other words, this PR does intentionally not try to generalize all possible Windows host compiler configurations. It makes the supported test contract (see ctk-next PR 436)explicit: |
||
| linux_compiler_missing = ( | ||
| "no such file or directory" in normalized and "failed to preprocess host compiler properties" in normalized | ||
| ) | ||
| return windows_compiler_missing or linux_compiler_missing | ||
|
|
||
|
|
||
| def _build_saxpy_rdc(binaries_dir: Path) -> None: | ||
| try: | ||
| version_result = subprocess.run( | ||
| ["nvcc", "--version"], # noqa: S607 - PATH lookup is the behavior under test. | ||
| capture_output=True, | ||
| text=True, | ||
| errors="replace", | ||
| ) | ||
| except FileNotFoundError: | ||
| pytest.skip("RDC test fixtures are absent and nvcc is not available on PATH") | ||
|
|
||
| if version_result.returncode != 0: | ||
| pytest.fail( | ||
| f"nvcc --version failed with exit code {version_result.returncode}\n{_subprocess_output(version_result)}", | ||
| pytrace=False, | ||
| ) | ||
|
|
||
| print(version_result.stdout, end="") | ||
| if version_result.stderr: | ||
| print(version_result.stderr, end="", file=sys.stderr) | ||
|
|
||
| builder_path = binaries_dir / "build_test_binaries.py" | ||
| build_result = subprocess.run( # noqa: S603 | ||
| [sys.executable, str(builder_path)], | ||
| capture_output=True, | ||
| text=True, | ||
| errors="replace", | ||
| ) | ||
| if build_result.returncode == 0: | ||
| print(build_result.stdout, end="") | ||
| if build_result.stderr: | ||
| print(build_result.stderr, end="", file=sys.stderr) | ||
| return | ||
|
|
||
| output = _subprocess_output(build_result) | ||
| if _host_compiler_is_unavailable(output): | ||
| print(output, file=sys.stderr) | ||
| pytest.skip("nvcc is available, but its host compiler is not configured") | ||
|
|
||
| pytest.fail( | ||
| f"RDC test fixture build failed with exit code {build_result.returncode}\n{output}", | ||
| pytrace=False, | ||
| ) | ||
|
|
||
|
|
||
| def test_get_kernel(init_cuda): | ||
| kernel = """extern "C" __global__ void ABC() { }""" | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Your previous PR uses NVCC environmental variable and pathfinder instead of bare nvcc command. Do we consider that here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, this was considered. Now that the builder is Python instead of Bash, a full
nvcc.exepath would be much less problematic than it was in the earlier Bash-based version.I still prefer bare
nvcchere because it keeps the test contract simple and explicit: the test environment should put the intended CUDA toolkit'sbindirectory onPATH, and the host compiler environment should already be configured. That is easy to satisfy in CI, runbooks, and local development.Using pathfinder or an
NVCCoverride inside this unit test would add a second toolkit-selection mechanism. That can create mismatches where the test discovers onenvcc, whileCUDA_PATH,PATH, runtime libraries, or the surrounding runbook point at another toolkit. For this fixture build, relying on the environment's normal executable lookup is simpler and easier to reason about.