From 0a74acf20bf2cc0fffe91accb12514448153e8b9 Mon Sep 17 00:00:00 2001 From: Phillip Cloud <417981+cpcloud@users.noreply.github.com> Date: Thu, 23 Apr 2026 10:42:36 -0400 Subject: [PATCH 1/2] fix(pathfinder): glob fallback prefers newest lib.so* (#1732) --- .../cuda/pathfinder/_dynamic_libs/search_platform.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/cuda_pathfinder/cuda/pathfinder/_dynamic_libs/search_platform.py b/cuda_pathfinder/cuda/pathfinder/_dynamic_libs/search_platform.py index 95e0f4dd1ee..37fd6eb1700 100644 --- a/cuda_pathfinder/cuda/pathfinder/_dynamic_libs/search_platform.py +++ b/cuda_pathfinder/cuda/pathfinder/_dynamic_libs/search_platform.py @@ -43,10 +43,16 @@ def _find_so_in_rel_dirs( for rel_dir in rel_dirs: sub_dir = tuple(rel_dir.split(os.path.sep)) for abs_dir in find_sub_dirs_all_sitepackages(sub_dir): + # Exact unversioned match first; fall back to versioned names because some + # distros only ship lib.so. (e.g. conda libcupti). Only one match + # is expected in practice. Sort in reverse so the newest-sorting name wins if + # multiple coexist, matching the newest-first bias elsewhere in pathfinder + # (see LinuxSearchPlatform.find_in_lib_dir and load_dl_linux._candidate_sonames). + # Issue #1732 tracks the deferred question of raising on true ambiguity. so_name = os.path.join(abs_dir, so_basename) if os.path.isfile(so_name): return so_name - for so_name in sorted(glob.glob(os.path.join(abs_dir, file_wild))): + for so_name in sorted(glob.glob(os.path.join(abs_dir, file_wild)), reverse=True): if os.path.isfile(so_name): return so_name sub_dirs_searched.append(sub_dir) @@ -150,7 +156,8 @@ def find_in_lib_dir( file_wild = lib_searched_for + "*" # Only one match is expected, but to ensure deterministic behavior in unexpected # situations, and to be internally consistent, we sort in reverse order with the - # intent to return the newest version first. + # intent to return the newest version first. Issue #1732 tracks the deferred + # question of raising on true ambiguity. for so_name in sorted(glob.glob(os.path.join(lib_dir, file_wild)), reverse=True): if os.path.isfile(so_name): return so_name From 344509ffcfb0cd13de89fb1141b4f5471f80ce99 Mon Sep 17 00:00:00 2001 From: Phillip Cloud <417981+cpcloud@users.noreply.github.com> Date: Thu, 23 Apr 2026 10:45:36 -0400 Subject: [PATCH 2/2] test(pathfinder): cover Linux .so glob fallback newest-first policy (#1732) --- cuda_pathfinder/tests/test_search_steps.py | 105 +++++++++++++++++++++ 1 file changed, 105 insertions(+) diff --git a/cuda_pathfinder/tests/test_search_steps.py b/cuda_pathfinder/tests/test_search_steps.py index 5672c7e5049..1b881707dfb 100644 --- a/cuda_pathfinder/tests/test_search_steps.py +++ b/cuda_pathfinder/tests/test_search_steps.py @@ -148,6 +148,63 @@ def test_not_found_appends_error(self, mocker, tmp_path): assert result is None assert any("No such file" in m for m in ctx.error_messages) + # The next three tests cover the Linux glob fallback in + # cuda.pathfinder._dynamic_libs.search_platform._find_so_in_rel_dirs. + # The fallback triggers when the unversioned libfoo.so is absent but + # versioned libfoo.so. files exist (e.g. some conda layouts). + # Issue #1732 tracks the decision to return the newest-sorting match + # deterministically; these tests lock in that policy at the + # site-packages call site. + + def test_glob_fallback_returns_single_versioned_match(self, mocker, tmp_path): + lib_dir = tmp_path / "nvidia" / "cuda_runtime" / "lib" + lib_dir.mkdir(parents=True) + versioned = lib_dir / "libcudart.so.13" + versioned.touch() + + mocker.patch( + f"{_PLAT_MOD}.find_sub_dirs_all_sitepackages", + return_value=[str(lib_dir)], + ) + + result = find_in_site_packages(_ctx(platform=LinuxSearchPlatform())) + assert result is not None + assert result.abs_path == str(versioned) + assert result.found_via == "site-packages" + + def test_glob_fallback_returns_newest_of_multiple_matches(self, mocker, tmp_path): + lib_dir = tmp_path / "nvidia" / "cuda_runtime" / "lib" + lib_dir.mkdir(parents=True) + older = lib_dir / "libcudart.so.12" + newer = lib_dir / "libcudart.so.13" + older.touch() + newer.touch() + + mocker.patch( + f"{_PLAT_MOD}.find_sub_dirs_all_sitepackages", + return_value=[str(lib_dir)], + ) + + result = find_in_site_packages(_ctx(platform=LinuxSearchPlatform())) + assert result is not None + assert result.abs_path == str(newer) + assert result.found_via == "site-packages" + + def test_glob_fallback_zero_matches_returns_none(self, mocker, tmp_path): + lib_dir = tmp_path / "nvidia" / "cuda_runtime" / "lib" + lib_dir.mkdir(parents=True) + (lib_dir / "unrelated.txt").touch() + + mocker.patch( + f"{_PLAT_MOD}.find_sub_dirs_all_sitepackages", + return_value=[str(lib_dir)], + ) + + ctx = _ctx(platform=LinuxSearchPlatform()) + result = find_in_site_packages(ctx) + assert result is None + assert any("No such file" in m and "libcudart.so" in m for m in ctx.error_messages) + # --------------------------------------------------------------------------- # find_in_conda @@ -189,6 +246,54 @@ def test_found_windows(self, mocker, tmp_path): assert result.abs_path == str(dll) assert result.found_via == "conda" + # The next three tests cover the Linux glob fallback in + # cuda.pathfinder._dynamic_libs.search_platform.LinuxSearchPlatform.find_in_lib_dir, + # which is exercised by find_in_conda (and find_in_cuda_path) when the + # resolved lib dir contains only versioned libfoo.so. files. + # Issue #1732 tracks the decision to return the newest-sorting match + # deterministically; these tests lock in that policy at the conda / + # CUDA_PATH call site. + + def test_glob_fallback_returns_single_versioned_match(self, mocker, tmp_path): + lib_dir = tmp_path / "lib" + lib_dir.mkdir() + versioned = lib_dir / "libcudart.so.13" + versioned.touch() + + mocker.patch.dict(os.environ, {"CONDA_PREFIX": str(tmp_path)}) + + result = find_in_conda(_ctx(platform=LinuxSearchPlatform())) + assert result is not None + assert result.abs_path == str(versioned) + assert result.found_via == "conda" + + def test_glob_fallback_returns_newest_of_multiple_matches(self, mocker, tmp_path): + lib_dir = tmp_path / "lib" + lib_dir.mkdir() + older = lib_dir / "libcudart.so.12" + newer = lib_dir / "libcudart.so.13" + older.touch() + newer.touch() + + mocker.patch.dict(os.environ, {"CONDA_PREFIX": str(tmp_path)}) + + result = find_in_conda(_ctx(platform=LinuxSearchPlatform())) + assert result is not None + assert result.abs_path == str(newer) + assert result.found_via == "conda" + + def test_glob_fallback_zero_matches_returns_none(self, mocker, tmp_path): + lib_dir = tmp_path / "lib" + lib_dir.mkdir() + (lib_dir / "unrelated.txt").touch() + + mocker.patch.dict(os.environ, {"CONDA_PREFIX": str(tmp_path)}) + + ctx = _ctx(platform=LinuxSearchPlatform()) + result = find_in_conda(ctx) + assert result is None + assert any("No such file" in m and "libcudart.so" in m for m in ctx.error_messages) + # --------------------------------------------------------------------------- # find_in_cuda_path