Skip to content

Commit a00f514

Browse files
committed
anticipate sccache change
1 parent dff01b7 commit a00f514

4 files changed

Lines changed: 148 additions & 39 deletions

File tree

‎cuda_bindings/build_hooks.py‎

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -128,11 +128,12 @@ def _resolve_toolchain_name():
128128

129129

130130
def _with_compiler(command, compiler):
131-
"""Replace leading compiler token(s); keep following flags.
131+
"""Replace the leading compiler on a sysconfig linker command; keep flags.
132132
133-
Conda sysconfig CC/CXX/LDCXXSHARED look like
134-
``g++ -pthread -B .../python_compiler_compat``; only the executable
135-
changes so those flags stay on the compiler and the linker command.
133+
Conda ``LDCXXSHARED`` looks like ``g++ -pthread -B .../python_compiler_compat
134+
-shared ...``. Only the executable changes so those flags stay on the
135+
link line. CC/CXX are not rewritten this way: they may already be a
136+
launcher plus compiler (``sccache cc``).
136137
"""
137138
if not command or not str(command).strip():
138139
return compiler
@@ -143,19 +144,33 @@ def _with_compiler(command, compiler):
143144
return " ".join([compiler, *parts[i:]])
144145

145146

147+
def _with_sccache(current, compiler):
148+
"""Keep a leading sccache token when the toolchain picks a compiler.
149+
150+
CI sets ``CC="sccache cc"`` or ``CC="/host/.../sccache cc"``. An explicit
151+
toolchain then becomes ``CC="sccache clang"`` rather than a bare compiler.
152+
"""
153+
if current:
154+
launcher = current.split()[0]
155+
if os.path.basename(launcher) == "sccache":
156+
return f"{launcher} {compiler}"
157+
return compiler
158+
159+
146160
def _apply_toolchain_env(cc, cxx, explicit):
147161
"""Set CC/CXX/LDCXXSHARED for an explicitly-chosen toolchain.
148162
149163
The default path (CUDA_PYTHON_TOOLCHAIN unset) intentionally
150164
does not touch the env, so an externally-set compiler (e.g.
151165
CC="sccache cc" in CI) keeps working. An explicit CUDA_PYTHON_TOOLCHAIN
152-
override (incl. =gnu) governs the compiler and overrides CC/CXX.
153-
Trailing sysconfig flags on CC/CXX/LDCXXSHARED (rpath, -pthread, -B, ...)
154-
are kept; LDSHARED is left unset so distutils rewrites it from CC.
166+
override (incl. =gnu) sets CC/CXX to the toolchain compiler; an existing
167+
sccache prefix is kept (CC="sccache cc" + llvm -> CC="sccache clang").
168+
Sysconfig extras on LDCXXSHARED (rpath, -pthread, -B, ...) are kept;
169+
LDSHARED is left unset so distutils rewrites it from CC.
155170
"""
156171
if explicit and cc is not None:
157-
os.environ["CC"] = _with_compiler(sysconfig.get_config_var("CC"), cc)
158-
os.environ["CXX"] = _with_compiler(sysconfig.get_config_var("CXX"), cxx)
172+
os.environ["CC"] = _with_sccache(os.environ.get("CC", ""), cc)
173+
os.environ["CXX"] = _with_sccache(os.environ.get("CXX", ""), cxx)
159174
ldcxxshared = sysconfig.get_config_var("LDCXXSHARED") or sysconfig.get_config_var("LDSHARED")
160175
os.environ["LDCXXSHARED"] = _with_compiler(ldcxxshared, cxx) if ldcxxshared else f"{cxx} -shared"
161176

‎cuda_bindings/tests/test_build_hooks.py‎

Lines changed: 53 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,8 @@ def test_llvm_sets_env_and_flags(self, monkeypatch):
113113
name, cc, cxx, cargs, largs = build_hooks._resolve_toolchain()
114114
assert name == "llvm"
115115
assert (cc, cxx) == ("clang", "clang++")
116-
assert os.environ["CC"].split()[0] == "clang"
117-
assert os.environ["CXX"].split()[0] == "clang++"
116+
assert os.environ["CC"] == "clang"
117+
assert os.environ["CXX"] == "clang++"
118118
assert os.environ["LDCXXSHARED"].startswith("clang++")
119119
assert "LDSHARED" not in os.environ
120120
assert "-fuse-ld=lld" in largs
@@ -133,8 +133,8 @@ def test_gnu_sets_env_and_flags(self, monkeypatch):
133133
name, cc, cxx, cargs, largs = build_hooks._resolve_toolchain()
134134
assert name == "gnu"
135135
assert (cc, cxx) == ("gcc", "g++")
136-
assert os.environ["CC"].split()[0] == "gcc"
137-
assert os.environ["CXX"].split()[0] == "g++"
136+
assert os.environ["CC"] == "gcc"
137+
assert os.environ["CXX"] == "g++"
138138
assert os.environ["LDCXXSHARED"].startswith("g++")
139139
assert "LDSHARED" not in os.environ
140140
# gcc-only flags are present (this is the point of P2: explicit gnu must use gcc, not generic cc)
@@ -150,6 +150,31 @@ def test_gnu_keeps_gcc_only_flags(self, monkeypatch):
150150
assert "-fpermissive" in cargs
151151
assert "-fno-var-tracking-assignments" in cargs
152152

153+
@pytest.mark.agent_authored(model="grok-4.6")
154+
def test_llvm_keeps_sccache_prefix(self, monkeypatch):
155+
if sys.platform == "win32":
156+
pytest.skip("llvm only valid on Linux")
157+
monkeypatch.setenv("CUDA_PYTHON_TOOLCHAIN", "llvm")
158+
monkeypatch.setenv("CC", "sccache cc")
159+
monkeypatch.setenv("CXX", "sccache c++")
160+
_name, _cc, _cxx, _cargs, _largs = build_hooks._resolve_toolchain()
161+
assert os.environ["CC"] == "sccache clang"
162+
assert os.environ["CXX"] == "sccache clang++"
163+
assert "LDSHARED" not in os.environ
164+
assert os.environ["LDCXXSHARED"].startswith("clang++")
165+
166+
@pytest.mark.agent_authored(model="grok-4.6")
167+
def test_llvm_keeps_host_sccache_path(self, monkeypatch):
168+
if sys.platform == "win32":
169+
pytest.skip("llvm only valid on Linux")
170+
launcher = "/host/usr/local/bin/sccache"
171+
monkeypatch.setenv("CUDA_PYTHON_TOOLCHAIN", "llvm")
172+
monkeypatch.setenv("CC", f"{launcher} cc")
173+
monkeypatch.setenv("CXX", f"{launcher} c++")
174+
_name, _cc, _cxx, _cargs, _largs = build_hooks._resolve_toolchain()
175+
assert os.environ["CC"] == f"{launcher} clang"
176+
assert os.environ["CXX"] == f"{launcher} clang++"
177+
153178
@pytest.mark.agent_authored(model="grok-4.6")
154179
def test_explicit_toolchain_preserves_sysconfig_ldcxxshared_extras(self, monkeypatch):
155180
if sys.platform == "win32":
@@ -184,8 +209,8 @@ def test_explicit_gnu_keeps_conda_sysconfig_flags(self, monkeypatch):
184209
monkeypatch.setattr(build_hooks.sysconfig, "get_config_var", lambda name: values.get(name))
185210
monkeypatch.setenv("CUDA_PYTHON_TOOLCHAIN", "gnu")
186211
build_hooks._resolve_toolchain()
187-
assert os.environ["CC"] == values["CC"]
188-
assert os.environ["CXX"] == values["CXX"]
212+
assert os.environ["CC"] == "gcc"
213+
assert os.environ["CXX"] == "g++"
189214
assert os.environ["LDCXXSHARED"] == values["LDCXXSHARED"]
190215
assert "LDSHARED" not in os.environ
191216

@@ -204,12 +229,32 @@ def test_explicit_llvm_keeps_conda_sysconfig_flags(self, monkeypatch):
204229
monkeypatch.setattr(build_hooks.sysconfig, "get_config_var", lambda name: values.get(name))
205230
monkeypatch.setenv("CUDA_PYTHON_TOOLCHAIN", "llvm")
206231
build_hooks._resolve_toolchain()
207-
assert os.environ["CC"] == f"clang {prefix}"
208-
assert os.environ["CXX"] == f"clang++ {prefix}"
232+
assert os.environ["CC"] == "clang"
233+
assert os.environ["CXX"] == "clang++"
209234
assert os.environ["LDCXXSHARED"] == f"clang++ {extras}"
210235
assert "LDSHARED" not in os.environ
211236

212237

238+
class TestWithSccache:
239+
"""_with_sccache: keep a leading sccache token, swap the compiler."""
240+
241+
@pytest.mark.agent_authored(model="grok-4.6")
242+
def test_keeps_sccache_and_swaps_compiler(self):
243+
assert build_hooks._with_sccache("sccache cc", "clang") == "sccache clang"
244+
245+
@pytest.mark.agent_authored(model="grok-4.6")
246+
def test_keeps_absolute_sccache_path(self):
247+
assert (
248+
build_hooks._with_sccache("/host/usr/local/bin/sccache cc", "clang") == "/host/usr/local/bin/sccache clang"
249+
)
250+
251+
@pytest.mark.agent_authored(model="grok-4.6")
252+
def test_bare_or_unrelated_cc_returns_compiler(self):
253+
assert build_hooks._with_sccache("", "clang") == "clang"
254+
assert build_hooks._with_sccache("gcc", "clang") == "clang"
255+
assert build_hooks._with_sccache("ccache gcc", "clang") == "clang"
256+
257+
213258
class TestWithCompiler:
214259
"""_with_compiler: replace the compiler executable, keep following flags."""
215260

‎cuda_core/build_hooks.py‎

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -127,11 +127,12 @@ def _resolve_toolchain_name():
127127

128128

129129
def _with_compiler(command, compiler):
130-
"""Replace leading compiler token(s); keep following flags.
130+
"""Replace the leading compiler on a sysconfig linker command; keep flags.
131131
132-
Conda sysconfig CC/CXX/LDCXXSHARED look like
133-
``g++ -pthread -B .../python_compiler_compat``; only the executable
134-
changes so those flags stay on the compiler and the linker command.
132+
Conda ``LDCXXSHARED`` looks like ``g++ -pthread -B .../python_compiler_compat
133+
-shared ...``. Only the executable changes so those flags stay on the
134+
link line. CC/CXX are not rewritten this way: they may already be a
135+
launcher plus compiler (``sccache cc``).
135136
"""
136137
if not command or not str(command).strip():
137138
return compiler
@@ -142,19 +143,33 @@ def _with_compiler(command, compiler):
142143
return " ".join([compiler, *parts[i:]])
143144

144145

146+
def _with_sccache(current, compiler):
147+
"""Keep a leading sccache token when the toolchain picks a compiler.
148+
149+
CI sets ``CC="sccache cc"`` or ``CC="/host/.../sccache cc"``. An explicit
150+
toolchain then becomes ``CC="sccache clang"`` rather than a bare compiler.
151+
"""
152+
if current:
153+
launcher = current.split()[0]
154+
if os.path.basename(launcher) == "sccache":
155+
return f"{launcher} {compiler}"
156+
return compiler
157+
158+
145159
def _apply_toolchain_env(cc, cxx, explicit):
146160
"""Set CC/CXX/LDCXXSHARED for an explicitly-chosen toolchain.
147161
148162
The default path (CUDA_PYTHON_TOOLCHAIN unset) intentionally
149163
does not touch the env, so an externally-set compiler (e.g.
150164
CC="sccache cc" in CI) keeps working. An explicit CUDA_PYTHON_TOOLCHAIN
151-
override (incl. =gnu) governs the compiler and overrides CC/CXX.
152-
Trailing sysconfig flags on CC/CXX/LDCXXSHARED (rpath, -pthread, -B, ...)
153-
are kept; LDSHARED is left unset so distutils rewrites it from CC.
165+
override (incl. =gnu) sets CC/CXX to the toolchain compiler; an existing
166+
sccache prefix is kept (CC="sccache cc" + llvm -> CC="sccache clang").
167+
Sysconfig extras on LDCXXSHARED (rpath, -pthread, -B, ...) are kept;
168+
LDSHARED is left unset so distutils rewrites it from CC.
154169
"""
155170
if explicit and cc is not None:
156-
os.environ["CC"] = _with_compiler(sysconfig.get_config_var("CC"), cc)
157-
os.environ["CXX"] = _with_compiler(sysconfig.get_config_var("CXX"), cxx)
171+
os.environ["CC"] = _with_sccache(os.environ.get("CC", ""), cc)
172+
os.environ["CXX"] = _with_sccache(os.environ.get("CXX", ""), cxx)
158173
ldcxxshared = sysconfig.get_config_var("LDCXXSHARED") or sysconfig.get_config_var("LDSHARED")
159174
os.environ["LDCXXSHARED"] = _with_compiler(ldcxxshared, cxx) if ldcxxshared else f"{cxx} -shared"
160175

‎cuda_core/tests/test_build_hooks.py‎

Lines changed: 47 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -674,8 +674,8 @@ def test_llvm_sets_env_and_flags(self, monkeypatch):
674674
name, cc, cxx, cargs, largs = build_hooks._resolve_toolchain()
675675
assert name == "llvm"
676676
assert (cc, cxx) == ("clang", "clang++")
677-
assert os.environ["CC"].split()[0] == "clang"
678-
assert os.environ["CXX"].split()[0] == "clang++"
677+
assert os.environ["CC"] == "clang"
678+
assert os.environ["CXX"] == "clang++"
679679
assert os.environ["LDCXXSHARED"].startswith("clang++")
680680
assert "LDSHARED" not in os.environ
681681
assert "-fuse-ld=lld" in largs
@@ -694,24 +694,38 @@ def test_gnu_sets_env_and_flags(self, monkeypatch):
694694
name, cc, cxx, cargs, largs = build_hooks._resolve_toolchain()
695695
assert name == "gnu"
696696
assert (cc, cxx) == ("gcc", "g++")
697-
assert os.environ["CC"].split()[0] == "gcc"
698-
assert os.environ["CXX"].split()[0] == "g++"
697+
assert os.environ["CC"] == "gcc"
698+
assert os.environ["CXX"] == "g++"
699699
assert os.environ["LDCXXSHARED"].startswith("g++")
700700
assert "LDSHARED" not in os.environ
701701
# gcc-only flags are present (this is the point of P2: explicit gnu must use gcc, not generic cc)
702702
assert "-fpermissive" not in cargs # cuda.core gnu flags don't include it; bindings do
703703
assert "-fno-var-tracking-assignments" not in cargs
704704

705-
@pytest.mark.agent_authored(model="glm-5.2")
706-
def test_llvm_overrides_external_cc(self, monkeypatch):
705+
@pytest.mark.agent_authored(model="grok-4.6")
706+
def test_llvm_keeps_sccache_prefix(self, monkeypatch):
707707
if sys.platform == "win32":
708708
pytest.skip("llvm only valid on Linux")
709-
# An explicit non-default toolchain governs the compiler, so a stale
710-
# external CC (e.g. "sccache cc") is replaced, not kept.
711709
monkeypatch.setenv("CUDA_PYTHON_TOOLCHAIN", "llvm")
712710
monkeypatch.setenv("CC", "sccache cc")
711+
monkeypatch.setenv("CXX", "sccache c++")
712+
_name, _cc, _cxx, _cargs, _largs = build_hooks._resolve_toolchain()
713+
assert os.environ["CC"] == "sccache clang"
714+
assert os.environ["CXX"] == "sccache clang++"
715+
assert "LDSHARED" not in os.environ
716+
assert os.environ["LDCXXSHARED"].startswith("clang++")
717+
718+
@pytest.mark.agent_authored(model="grok-4.6")
719+
def test_llvm_keeps_host_sccache_path(self, monkeypatch):
720+
if sys.platform == "win32":
721+
pytest.skip("llvm only valid on Linux")
722+
launcher = "/host/usr/local/bin/sccache"
723+
monkeypatch.setenv("CUDA_PYTHON_TOOLCHAIN", "llvm")
724+
monkeypatch.setenv("CC", f"{launcher} cc")
725+
monkeypatch.setenv("CXX", f"{launcher} c++")
713726
_name, _cc, _cxx, _cargs, _largs = build_hooks._resolve_toolchain()
714-
assert os.environ["CC"].split()[0] == "clang"
727+
assert os.environ["CC"] == f"{launcher} clang"
728+
assert os.environ["CXX"] == f"{launcher} clang++"
715729

716730
@pytest.mark.agent_authored(model="grok-4.6")
717731
def test_explicit_toolchain_preserves_sysconfig_ldcxxshared_extras(self, monkeypatch):
@@ -747,8 +761,8 @@ def test_explicit_gnu_keeps_conda_sysconfig_flags(self, monkeypatch):
747761
monkeypatch.setattr(build_hooks.sysconfig, "get_config_var", lambda name: values.get(name))
748762
monkeypatch.setenv("CUDA_PYTHON_TOOLCHAIN", "gnu")
749763
build_hooks._resolve_toolchain()
750-
assert os.environ["CC"] == values["CC"]
751-
assert os.environ["CXX"] == values["CXX"]
764+
assert os.environ["CC"] == "gcc"
765+
assert os.environ["CXX"] == "g++"
752766
assert os.environ["LDCXXSHARED"] == values["LDCXXSHARED"]
753767
assert "LDSHARED" not in os.environ
754768

@@ -767,12 +781,32 @@ def test_explicit_llvm_keeps_conda_sysconfig_flags(self, monkeypatch):
767781
monkeypatch.setattr(build_hooks.sysconfig, "get_config_var", lambda name: values.get(name))
768782
monkeypatch.setenv("CUDA_PYTHON_TOOLCHAIN", "llvm")
769783
build_hooks._resolve_toolchain()
770-
assert os.environ["CC"] == f"clang {prefix}"
771-
assert os.environ["CXX"] == f"clang++ {prefix}"
784+
assert os.environ["CC"] == "clang"
785+
assert os.environ["CXX"] == "clang++"
772786
assert os.environ["LDCXXSHARED"] == f"clang++ {extras}"
773787
assert "LDSHARED" not in os.environ
774788

775789

790+
class TestWithSccache:
791+
"""_with_sccache: keep a leading sccache token, swap the compiler."""
792+
793+
@pytest.mark.agent_authored(model="grok-4.6")
794+
def test_keeps_sccache_and_swaps_compiler(self):
795+
assert build_hooks._with_sccache("sccache cc", "clang") == "sccache clang"
796+
797+
@pytest.mark.agent_authored(model="grok-4.6")
798+
def test_keeps_absolute_sccache_path(self):
799+
assert (
800+
build_hooks._with_sccache("/host/usr/local/bin/sccache cc", "clang") == "/host/usr/local/bin/sccache clang"
801+
)
802+
803+
@pytest.mark.agent_authored(model="grok-4.6")
804+
def test_bare_or_unrelated_cc_returns_compiler(self):
805+
assert build_hooks._with_sccache("", "clang") == "clang"
806+
assert build_hooks._with_sccache("gcc", "clang") == "clang"
807+
assert build_hooks._with_sccache("ccache gcc", "clang") == "clang"
808+
809+
776810
class TestWithCompiler:
777811
"""_with_compiler: replace the compiler executable, keep following flags."""
778812

0 commit comments

Comments
 (0)