Skip to content

Commit 0d0bc80

Browse files
committed
pathfinder: check required dependencies in locate_nvidia_dynamic_lib
load_nvidia_dynamic_lib fails when a required dependency cannot be found, so locate_nvidia_dynamic_lib now does too, at the same point in the search. Also document that the native loader search result is cached for the process.
1 parent 5538503 commit 0d0bc80

2 files changed

Lines changed: 68 additions & 1 deletion

File tree

‎cuda_pathfinder/cuda/pathfinder/_dynamic_libs/find_nvidia_dynamic_lib.py‎

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,12 @@ def _locate_lib(libname: str) -> FindResult:
5454

5555
ctx = SearchContext(desc)
5656
find = run_find_steps(ctx, EARLY_FIND_STEPS)
57+
58+
# load_nvidia_dynamic_lib loads the required dependencies at this point and
59+
# fails if one of them cannot be found.
60+
for dep in desc.dependencies:
61+
locate_nvidia_dynamic_lib(dep)
62+
5763
if find is not None:
5864
return find
5965

@@ -80,6 +86,9 @@ def locate_nvidia_dynamic_lib(libname: str) -> LocatedDynamicLib:
8086
process. Use it where a path is needed but the library must not be loaded
8187
into the current process, for example to pass a library to a build system.
8288
89+
Like :func:`load_nvidia_dynamic_lib`, it fails if a library that ``libname``
90+
requires cannot be found. Optional dependencies are not checked.
91+
8392
Args:
8493
libname (str): The short name of the library to locate (e.g.,
8594
``"cudart"``, ``"nvvm"``, etc.).
@@ -114,7 +123,12 @@ def locate_nvidia_dynamic_lib(libname: str) -> LocatedDynamicLib:
114123
The result can differ from what :func:`load_nvidia_dynamic_lib` returns
115124
in the current process when another component has already loaded the
116125
library, or influences the native loader search (for example through
117-
its ``RPATH``).
126+
its ``RPATH``). A file that is found but fails to load is still
127+
returned, since nothing is loaded.
128+
129+
The result of the native loader search (step 3) is cached for the life
130+
of the process and shared with the CTK root canary probe, because each
131+
search starts a Python subprocess.
118132
"""
119133
check_libname(libname)
120134
find = _locate_lib(libname)

‎cuda_pathfinder/tests/test_find_nvidia_dynamic_lib.py‎

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,59 @@ def test_driver_lib_not_found_raises(mocker):
178178
locate_nvidia_dynamic_lib("cuda")
179179

180180

181+
def _patch_early_find_by_libname(mocker, tmp_path, found):
182+
"""Early find steps hit for the libnames in ``found``; everything else misses."""
183+
184+
def run_find_steps(ctx, steps):
185+
if steps is EARLY_FIND_STEPS and ctx.libname in found:
186+
return FindResult(str(tmp_path / f"lib{ctx.libname}.so"), "site-packages")
187+
return None
188+
189+
mocker.patch(f"{_MODULE}.run_find_steps", side_effect=run_find_steps)
190+
mocker.patch(f"{_MODULE}._resolve_system_loaded_abs_path_in_subprocess", return_value=None)
191+
mocker.patch(f"{_MODULE}._try_ctk_root_canary", return_value=None)
192+
193+
194+
@pytest.mark.usefixtures("no_in_process_loading")
195+
@pytest.mark.agent_authored(model="claude-opus-5-5")
196+
def test_missing_required_dependency_raises(mocker, tmp_path):
197+
_patch_early_find_by_libname(mocker, tmp_path, {"cublas"})
198+
199+
with pytest.raises(DynamicLibNotFoundError, match="cublasLt"):
200+
locate_nvidia_dynamic_lib("cublas")
201+
202+
203+
@pytest.mark.usefixtures("no_in_process_loading")
204+
@pytest.mark.agent_authored(model="claude-opus-5-5")
205+
def test_required_dependencies_are_checked_transitively(mocker, tmp_path):
206+
# cusolver requires cublas, which requires cublasLt.
207+
_patch_early_find_by_libname(mocker, tmp_path, {"cusolver", "nvJitLink", "cusparse", "cublas"})
208+
209+
with pytest.raises(DynamicLibNotFoundError, match="cublasLt"):
210+
locate_nvidia_dynamic_lib("cusolver")
211+
212+
213+
@pytest.mark.usefixtures("no_in_process_loading")
214+
@pytest.mark.agent_authored(model="claude-opus-5-5")
215+
def test_present_dependencies_do_not_change_the_result(mocker, tmp_path):
216+
_patch_early_find_by_libname(mocker, tmp_path, {"cublas", "cublasLt"})
217+
218+
located = locate_nvidia_dynamic_lib("cublas")
219+
220+
assert located.abs_path == str(tmp_path / "libcublas.so")
221+
assert located.found_via == "site-packages"
222+
223+
224+
@pytest.mark.usefixtures("no_in_process_loading")
225+
@pytest.mark.agent_authored(model="claude-opus-5-5")
226+
def test_optional_dependencies_are_not_required(mocker, tmp_path):
227+
desc = load_mod.LIB_DESCRIPTORS["cudss"]
228+
assert desc.optional_dependencies
229+
_patch_early_find_by_libname(mocker, tmp_path, {"cudss", *desc.dependencies})
230+
231+
assert locate_nvidia_dynamic_lib("cudss").abs_path == str(tmp_path / "libcudss.so")
232+
233+
181234
@pytest.mark.agent_authored(model="claude-opus-5-5")
182235
def test_find_is_cached(mocker, tmp_path):
183236
lib = tmp_path / "libcudart.so.13"

0 commit comments

Comments
 (0)