From 9f4557991a38095be42190b63342e5c84d9fcc5f Mon Sep 17 00:00:00 2001 From: LeSingh1 Date: Sat, 8 Aug 2026 18:42:24 -0700 Subject: [PATCH] fix(core): don't abort the build on an empty integer build knob cuda_core/build_hooks.py reads two integer knobs from the environment with a bare int(): COMPILE_FOR_COVERAGE = bool(int(os.environ.get("CUDA_PYTHON_COVERAGE", "0"))) nthreads = int(os.environ.get("CUDA_PYTHON_PARALLEL_LEVEL", os.cpu_count() // 2)) os.environ.get returns the empty string, not the default, when a variable is set but empty -- and `VAR= pip install .` (or an empty Dockerfile ENV, or an unfilled CI job variable) is exactly how these get neutralised. The first line runs at module scope, so the failure lands while pip is still importing the PEP 517 backend: $ CUDA_PYTHON_COVERAGE= pip install ./cuda_core ValueError: invalid literal for int() with base 10: '' cuda_core/setup.py carries the same two expressions verbatim. Add build_hooks.env_int and route all four call sites through it. Unset and empty/whitespace-only both fall back to the default; a value that is set to something non-integer still raises, but names the variable (`environment variable CUDA_PYTHON_COVERAGE='yes' must be an integer`) instead of an anonymous int() failure. Silently ignoring `=yes` would be worse than stopping here -- it would hand back a build with no coverage instrumentation. Also stop assuming os.cpu_count() returns a number. It is documented to return None when the count cannot be determined, which made the nthreads default a TypeError. Fall back to a serial build instead. setup.py already imports build_hooks, so both build entry points now parse these knobs through the same helper rather than duplicating the expression. --- cuda_core/build_hooks.py | 30 +++++++++++++++++-- cuda_core/setup.py | 6 ++-- cuda_core/tests/test_build_hooks.py | 46 +++++++++++++++++++++++++++++ 3 files changed, 78 insertions(+), 4 deletions(-) diff --git a/cuda_core/build_hooks.py b/cuda_core/build_hooks.py index 626d50355ab..09ad41bacfb 100644 --- a/cuda_core/build_hooks.py +++ b/cuda_core/build_hooks.py @@ -26,7 +26,31 @@ build_sdist = _build_meta.build_sdist get_requires_for_build_sdist = _build_meta.get_requires_for_build_sdist -COMPILE_FOR_COVERAGE = bool(int(os.environ.get("CUDA_PYTHON_COVERAGE", "0"))) + +def env_int(name: str, default: int) -> int: + """Read an integer build knob from the environment. + + Unset and empty mean the same thing. ``CUDA_PYTHON_COVERAGE= pip install .`` + (and the equivalent empty value in a Dockerfile ``ENV`` or a CI job spec) is + how a variable gets neutralised, and it must not abort the build -- a bare + ``int()`` here raises while the PEP 517 backend module is still being + imported, so the failure arrives before any build output. + + A value that is set to something non-integer is a typo worth stopping for: + silently ignoring ``CUDA_PYTHON_COVERAGE=yes`` would hand back a build with + no coverage instrumentation. It is reported with the variable's name rather + than as an anonymous ``invalid literal for int()``. + """ + raw = os.environ.get(name, "").strip() + if not raw: + return default + try: + return int(raw) + except ValueError: + raise ValueError(f"environment variable {name}={os.environ[name]!r} must be an integer") from None + + +COMPILE_FOR_COVERAGE = bool(env_int("CUDA_PYTHON_COVERAGE", 0)) # Please keep in sync with the copy in cuda_bindings/build_hooks.py. @@ -219,7 +243,9 @@ def get_sources(mod_name): for mod in module_names() ) - nthreads = int(os.environ.get("CUDA_PYTHON_PARALLEL_LEVEL", os.cpu_count() // 2)) + # ``os.cpu_count()`` is documented to return None when it cannot be + # determined; fall back to a serial build rather than a TypeError. + nthreads = env_int("CUDA_PYTHON_PARALLEL_LEVEL", (os.cpu_count() or 1) // 2) compile_time_env = {"CUDA_CORE_BUILD_MAJOR": int(_determine_cuda_major_version())} compiler_directives = {"embedsignature": True, "warn.deprecated.IF": False, "freethreading_compatible": True} _CythonOptions.warning_errors = True diff --git a/cuda_core/setup.py b/cuda_core/setup.py index e0d745b1f21..8f9a76d3a42 100644 --- a/cuda_core/setup.py +++ b/cuda_core/setup.py @@ -10,8 +10,10 @@ from setuptools.command.build_ext import build_ext as _build_ext from setuptools.command.build_py import build_py as _build_py -nthreads = int(os.environ.get("CUDA_PYTHON_PARALLEL_LEVEL", os.cpu_count() // 2)) -coverage_mode = bool(int(os.environ.get("CUDA_PYTHON_COVERAGE", "0"))) +# Shared with build_hooks so the two build entry points parse these knobs +# identically (see build_hooks.env_int for why a bare int() is not enough). +nthreads = build_hooks.env_int("CUDA_PYTHON_PARALLEL_LEVEL", (os.cpu_count() or 1) // 2) +coverage_mode = bool(build_hooks.env_int("CUDA_PYTHON_COVERAGE", 0)) _ROOT_DIR = Path(__file__).resolve().parent _AOTI_SHIM_DEF_FILE = _ROOT_DIR / "cuda" / "core" / "_include" / "aoti_shim.def" _AOTI_SHIM_LIB_FILE = _ROOT_DIR / "build" / "aoti_shim.lib" diff --git a/cuda_core/tests/test_build_hooks.py b/cuda_core/tests/test_build_hooks.py index c08ad4cd3c5..6bbca879afb 100644 --- a/cuda_core/tests/test_build_hooks.py +++ b/cuda_core/tests/test_build_hooks.py @@ -165,3 +165,49 @@ def test_missing_cuda_path_raises_error(self): pytest.raises(RuntimeError, match="CUDA_PATH or CUDA_HOME"), ): build_hooks._determine_cuda_major_version() + + +class TestEnvInt: + """Integer build knobs read from the environment.""" + + @pytest.mark.agent_authored(model="claude-opus-5") + @pytest.mark.parametrize( + ("value", "expected"), + [ + pytest.param(None, 7, id="unset"), + # `VAR= pip install .` is how a shell neutralizes a variable, and + # the same shape appears in Dockerfile ENV and CI job specs. + pytest.param("", 7, id="empty"), + pytest.param(" ", 7, id="whitespace-only"), + pytest.param("0", 0, id="zero"), + pytest.param("4", 4, id="positive"), + pytest.param(" 4 ", 4, id="padded"), + ], + ) + def test_env_int_reads_the_value_or_falls_back(self, value, expected): + env = {} if value is None else {"CUDA_PYTHON_TEST_KNOB": value} + with mock.patch.dict(os.environ, env, clear=True): + assert build_hooks.env_int("CUDA_PYTHON_TEST_KNOB", 7) == expected + + @pytest.mark.agent_authored(model="claude-opus-5") + def test_env_int_names_the_variable_for_a_non_integer(self): + with ( + mock.patch.dict(os.environ, {"CUDA_PYTHON_TEST_KNOB": "yes"}, clear=True), + pytest.raises(ValueError, match=r"CUDA_PYTHON_TEST_KNOB='yes' must be an integer"), + ): + build_hooks.env_int("CUDA_PYTHON_TEST_KNOB", 7) + + @pytest.mark.agent_authored(model="claude-opus-5") + @pytest.mark.parametrize("value", ["", " "], ids=["empty", "whitespace-only"]) + def test_backend_imports_with_an_empty_coverage_flag(self, value): + """An empty CUDA_PYTHON_COVERAGE must not break the PEP 517 backend. + + COMPILE_FOR_COVERAGE is evaluated at module scope, so a bare int() there + raised while the build backend was still being imported -- pip reported + `ValueError: invalid literal for int() with base 10: ''` before any + build output appeared. + """ + with mock.patch.dict(os.environ, {"CUDA_PYTHON_COVERAGE": value}, clear=True): + reloaded = _load_build_hooks() + + assert reloaded.COMPILE_FOR_COVERAGE is False