Skip to content
Merged
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
17 changes: 12 additions & 5 deletions cuda_bindings/build_hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -437,21 +437,28 @@ def _resolve_toolchain(debug=False, compile_for_coverage=False):
extra_link_args = []

if name == "msvc":
# c++14: raising to c++17 costs a measured ~15% on launch_{256,512}_args
# from gcc's c++17 variadic-template expansion.
extra_compile_args += ["/std:c++14", "/O2"]
if debug:
raise RuntimeError("Debuggable builds are not supported on Windows.")
else:
# Common Linux compile flags.
extra_compile_args += ["-std=c++14", "-Wno-deprecated-declarations"]
# c++14: raising to c++17 costs a measured ~15% on launch_{256,512}_args
# from gcc's c++17 variadic-template expansion.
extra_compile_args += ["-std=c++14"]
# cudaMemcpy*Array* and cudaGetDriverEntryPoint are deprecated but still
# supported; suppress the resulting warnings so a future -Werror build
# is not broken by Cython-generated calls we cannot control.
extra_compile_args += ["-Wno-deprecated-declarations"]
# Compiler-specific flags.
if name == "gnu":
extra_compile_args += ["-fpermissive", "-fno-var-tracking-assignments"]
elif name == "llvm":
if name == "llvm":
extra_link_args += ["-fuse-ld=lld"]
# Common Linux debug/opt flags.
if debug:
extra_compile_args += ["-g", "-O0", "-D _GLIBCXX_ASSERTIONS"]
else:
extra_compile_args += ["-g0", "-O3"]
extra_compile_args += ["-g0", "-O2"]
extra_link_args += ["-Wl,--strip-all"]

if compile_for_coverage:
Expand Down
63 changes: 56 additions & 7 deletions cuda_bindings/tests/cython/build_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,39 @@
so `cimport cuda.bindings.*` fails to locate the .pxd files. We resolve
the namespace package's source root from `cuda.bindings.__file__` and pass
it via `include_path=` so cythonize finds the .pxd tree on every platform.

When CUDA_PYTHON_CYTHON_CACHE_DIR is set, cythonize uses the same cache
namespacing and include-path aliasing as cuda_bindings/build_hooks.py.
"""

from __future__ import annotations

import importlib.util
import os
import sys
from pathlib import Path

import Cython
from Cython.Build import cythonize
from setuptools import setup

import cuda.bindings

_COMPILER_DIRECTIVES = {"freethreading_compatible": True}


def _load_build_hooks():
# PEP 517 backend, not an installed module. Load by path so we do not put
# cuda_bindings/ on sys.path (that would shadow the installed package).
build_hooks_path = Path(__file__).resolve().parents[2] / "build_hooks.py"
spec = importlib.util.spec_from_file_location("cuda_bindings_build_hooks", build_hooks_path)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module


build_hooks = _load_build_hooks()


def _bindings_source_root() -> Path:
# cuda.bindings.__file__ -> .../<root>/cuda/bindings/__init__.py
Expand All @@ -32,6 +52,41 @@ def _bindings_source_root() -> Path:
return root


def _cythonize_tests(pyx_files):
cache_path = build_hooks._cython_cache_path(
"cuda-bindings-cython-tests",
compiler_directives=_COMPILER_DIRECTIVES,
language_level=3,
cplus=True,
)
cythonize_kwargs = {
"language_level": 3,
"nthreads": 1,
"compiler_directives": _COMPILER_DIRECTIVES,
"cache": cache_path,
}
if cache_path is None:
return cythonize(
pyx_files,
include_path=[str(_bindings_source_root())],
**cythonize_kwargs,
)

# Distinct alias names so a concurrent package build's .cython-stdlib /
# .cython-bindings symlinks are not replaced. Relative aliases resolve
# next to build_hooks.py (package root).
stdlib_target = Path(Cython.__file__).parent / "Includes"
with (
build_hooks._stable_cython_alias(stdlib_target, Path(".cython-stdlib-tests")) as rel_stdlib,
build_hooks._stable_cython_alias(_bindings_source_root(), Path(".cython-bindings-tests")) as rel_bindings,
):
return cythonize(
pyx_files,
include_path=[".", rel_bindings, rel_stdlib],
**cythonize_kwargs,
)


def main() -> None:
script_dir = Path(__file__).resolve().parent
# Avoid appending the absolute checkout path under build/temp: the
Expand All @@ -41,13 +96,7 @@ def main() -> None:
if not pyx_files:
raise SystemExit(f"no test_*.pyx files under {script_dir}")

ext_modules = cythonize(
pyx_files,
language_level=3,
nthreads=1,
include_path=[str(_bindings_source_root())],
compiler_directives={"freethreading_compatible": True},
)
ext_modules = _cythonize_tests(pyx_files)

# pytest imports each extension by bare module name (see test_cython.py),
# so build in-place next to its .pyx regardless of the invoking cwd.
Expand Down
36 changes: 26 additions & 10 deletions cuda_bindings/tests/test_build_hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,9 +148,8 @@ def test_gnu_sets_env_and_flags(self, monkeypatch):
assert os.environ["CXX"] == "g++"
assert os.environ["LDCXXSHARED"] == "g++ -shared -Wl,-O1"
assert "LDSHARED" not in os.environ
# gcc-only flags are present (this is the point of P2: explicit gnu must use gcc, not generic cc)
assert "-fpermissive" in cargs
assert "-fno-var-tracking-assignments" in cargs
assert "-fpermissive" not in cargs
assert "-fno-var-tracking-assignments" not in cargs

@pytest.mark.agent_authored(model="grok-4.6")
def test_llvm_keeps_sccache_prefix(self, monkeypatch):
Expand All @@ -167,14 +166,31 @@ def test_llvm_keeps_sccache_prefix(self, monkeypatch):
# The launcher prefixes CC/CXX only; the shared linker command is the bare compiler.
assert os.environ["LDCXXSHARED"] == "clang++ -shared -Wl,-O1"

@pytest.mark.agent_authored(model="glm-5.2")
def test_gnu_keeps_gcc_only_flags(self, monkeypatch):
@pytest.mark.agent_authored(model="claude-sonnet-4-6")
def test_linux_opt_flag_set(self, monkeypatch):
"""Linux opt build: -std=c++14, -O2, -Wno-deprecated-declarations; no -O3 or gnu-only flags."""
if sys.platform == "win32":
pytest.skip("gnu only valid on Linux")
monkeypatch.setenv("CUDA_PYTHON_TOOLCHAIN", "gnu")
_name, _cc, _cxx, cargs, _largs = build_hooks._resolve_toolchain()
assert "-fpermissive" in cargs
assert "-fno-var-tracking-assignments" in cargs
pytest.skip("Linux flags only")
monkeypatch.delenv("CUDA_PYTHON_TOOLCHAIN", raising=False)
_name, _cc, _cxx, cargs, largs = build_hooks._resolve_toolchain(debug=False)
assert "-std=c++14" in cargs
assert "-Wno-deprecated-declarations" in cargs
assert "-g0" in cargs
assert "-O2" in cargs
assert "-O3" not in cargs
assert "-fpermissive" not in cargs
assert "-fno-var-tracking-assignments" not in cargs
assert "-Wl,--strip-all" in largs
Comment on lines +176 to +183

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not opposed to this kind of test, but it seems unnecessary


@pytest.mark.agent_authored(model="claude-sonnet-4-6")
def test_msvc_opt_flag_set(self, monkeypatch):
"""MSVC opt build emits /std:c++14 and /O2."""
if sys.platform != "win32":
pytest.skip("MSVC flags only on Windows")
monkeypatch.delenv("CUDA_PYTHON_TOOLCHAIN", raising=False)
_name, _cc, _cxx, cargs, _largs = build_hooks._resolve_toolchain(debug=False)
assert "/std:c++14" in cargs
assert "/O2" in cargs

@pytest.mark.agent_authored(model="claude-sonnet-5.5")
def test_explicit_toolchain_prefers_env_ldcxxshared(self, monkeypatch):
Expand Down
4 changes: 3 additions & 1 deletion cuda_core/build_hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -484,11 +484,13 @@ def _resolve_toolchain(debug=False, compile_for_coverage=False):
extra_link_args = []

if name == "msvc":
extra_compile_args += ["/std:c++17"]
# c++17: required by structured bindings and if constexpr in cuda/core/_cpp/.
extra_compile_args += ["/std:c++17", "/O2"]
if debug:
raise RuntimeError("Debuggable builds are not supported on Windows.")
else:
# Common Linux compile flags.
# c++17: required by structured bindings and if constexpr in cuda/core/_cpp/.
extra_compile_args += ["-std=c++17"]
# Compiler-specific flags.
if name == "llvm":
Expand Down
69 changes: 59 additions & 10 deletions cuda_core/tests/cython/build_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,39 @@
so `cimport cuda.bindings.*` fails to locate the .pxd files. We resolve
the namespace package's source root from `cuda.bindings.__file__` and pass
it via `include_path=` so cythonize finds the .pxd tree on every platform.

When CUDA_PYTHON_CYTHON_CACHE_DIR is set, cythonize uses the same cache
namespacing and include-path aliasing as cuda_core/build_hooks.py.
"""

from __future__ import annotations

import importlib.util
import os
import sys
from pathlib import Path

import Cython
from Cython.Build import cythonize
from setuptools import setup

import cuda.bindings

_COMPILER_DIRECTIVES = {"freethreading_compatible": True}


def _load_build_hooks():
# PEP 517 backend, not an installed module. Load by path so we do not put
# cuda_core/ on sys.path (that would shadow the installed package).
build_hooks_path = Path(__file__).resolve().parents[2] / "build_hooks.py"
spec = importlib.util.spec_from_file_location("cuda_core_build_hooks", build_hooks_path)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module


build_hooks = _load_build_hooks()


def _bindings_source_root() -> Path:
# cuda.bindings.__file__ -> .../<root>/cuda/bindings/__init__.py
Expand All @@ -32,26 +52,55 @@ def _bindings_source_root() -> Path:
return root


def _cythonize_tests(pyx_files):
cache_path = build_hooks._cython_cache_path(
"cuda-core-cython-tests",
compiler_directives=_COMPILER_DIRECTIVES,
language_level=3,
cplus=True,
)
cythonize_kwargs = {
"language_level": 3,
"compiler_directives": _COMPILER_DIRECTIVES,
"cache": cache_path,
}
if cache_path is None:
return cythonize(
pyx_files,
include_path=[str(_bindings_source_root())],
**cythonize_kwargs,
)

# Distinct alias names so a concurrent package build's .cython-stdlib /
# .cython-bindings symlinks are not replaced. Relative aliases resolve
# next to build_hooks.py (package root).
stdlib_target = Path(Cython.__file__).parent / "Includes"
with (
build_hooks._stable_cython_alias(stdlib_target, Path(".cython-stdlib-tests")) as rel_stdlib,
build_hooks._stable_cython_alias(_bindings_source_root(), Path(".cython-bindings-tests")) as rel_bindings,
):
return cythonize(
pyx_files,
include_path=[".", rel_bindings, rel_stdlib],
**cythonize_kwargs,
)


def main() -> None:
script_dir = Path(__file__).resolve().parent
pyx_files = sorted(str(p) for p in script_dir.glob("test_*.pyx"))
pyx_files = sorted(p.name for p in script_dir.glob("test_*.pyx"))
if not pyx_files:
raise SystemExit(f"no test_*.pyx files under {script_dir}")

ext_modules = cythonize(
pyx_files,
language_level=3,
include_path=[str(_bindings_source_root())],
compiler_directives={"freethreading_compatible": True},
)

# `build_ext --inplace` places the compiled .so relative to the current
# working directory, but pixi runs this task from the project root. pytest
# imports each extension by bare module name (see test_cython.py), which
# only resolves when the .so sits in tests/cython (the dir pytest puts on
# sys.path). chdir here so the .so lands next to its .pyx regardless of the
# invoking cwd.
# sys.path). chdir before cythonize so alias relpaths and the .so location
# are stable regardless of the invoking cwd.
os.chdir(script_dir)
ext_modules = _cythonize_tests(pyx_files)

sys.argv = [sys.argv[0], "build_ext", "--inplace"]
setup(name="cuda_core_cython_tests", ext_modules=ext_modules)

Expand Down
28 changes: 26 additions & 2 deletions cuda_core/tests/test_build_hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -976,8 +976,8 @@ def test_gnu_sets_env_and_flags(self, monkeypatch):
assert os.environ["CXX"] == "g++"
assert os.environ["LDCXXSHARED"] == "g++ -shared -Wl,-O1"
assert "LDSHARED" not in os.environ
# gcc-only flags are present (this is the point of P2: explicit gnu must use gcc, not generic cc)
assert "-fpermissive" not in cargs # cuda.core gnu flags don't include it; bindings do
# Neither cuda.core nor cuda.bindings sets these gcc-only flags.
assert "-fpermissive" not in cargs
assert "-fno-var-tracking-assignments" not in cargs

@pytest.mark.agent_authored(model="grok-4.6")
Expand Down Expand Up @@ -1018,6 +1018,30 @@ def test_explicit_toolchain_falls_back_to_ldshared_then_shared_flag(self, monkey
build_hooks._resolve_toolchain()
assert os.environ["LDCXXSHARED"] == "clang++ -shared"

@pytest.mark.agent_authored(model="claude-sonnet-4-6")
def test_linux_opt_flag_set(self, monkeypatch):
"""Linux opt build: -std=c++17, -O2; no gnu-only flags."""
if sys.platform == "win32":
pytest.skip("Linux flags only")
monkeypatch.delenv("CUDA_PYTHON_TOOLCHAIN", raising=False)
_name, _cc, _cxx, cargs, largs = build_hooks._resolve_toolchain(debug=False)
assert "-std=c++17" in cargs
assert "-g0" in cargs
assert "-O2" in cargs
assert "-fpermissive" not in cargs
assert "-fno-var-tracking-assignments" not in cargs
assert "-Wl,--strip-all" in largs

@pytest.mark.agent_authored(model="claude-sonnet-4-6")
def test_msvc_opt_flag_set(self, monkeypatch):
"""MSVC opt build emits /std:c++17 and /O2."""
if sys.platform != "win32":
pytest.skip("MSVC flags only on Windows")
monkeypatch.delenv("CUDA_PYTHON_TOOLCHAIN", raising=False)
_name, _cc, _cxx, cargs, _largs = build_hooks._resolve_toolchain(debug=False)
assert "/std:c++17" in cargs
assert "/O2" in cargs


class TestWithSccache:
"""_with_sccache: keep a leading sccache token, swap the compiler."""
Expand Down
Loading