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
11 changes: 9 additions & 2 deletions cuda_pathfinder/cuda/pathfinder/_dynamic_libs/search_platform.py
Original file line number Diff line number Diff line change
Expand Up @@ -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<name>.so.<major> (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)
Expand Down Expand Up @@ -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
Expand Down
105 changes: 105 additions & 0 deletions cuda_pathfinder/tests/test_search_steps.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.<major> 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
Expand Down Expand Up @@ -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.<major> 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
Expand Down
Loading