From d6b3bf34e32e2964d130681294c40f7369d9a01a Mon Sep 17 00:00:00 2001 From: 0z5a Date: Wed, 4 Nov 2026 16:02:53 +0100 Subject: [PATCH] fix(cuda.pathfinder): return absolute library paths Convert discovered static and bitcode paths to absolute paths so cached results remain valid after changing directories. Preserve symlink-parent traversal and cover each discovery source. Signed-off-by: 0z5a --- .../_static_libs/find_bitcode_lib.py | 7 ++- .../_static_libs/find_static_lib.py | 7 ++- .../tests/test_find_bitcode_lib.py | 62 +++++++++++++++++++ cuda_pathfinder/tests/test_find_static_lib.py | 60 ++++++++++++++++++ 4 files changed, 130 insertions(+), 6 deletions(-) diff --git a/cuda_pathfinder/cuda/pathfinder/_static_libs/find_bitcode_lib.py b/cuda_pathfinder/cuda/pathfinder/_static_libs/find_bitcode_lib.py index fd9f3cdfe55..a8d3942c166 100644 --- a/cuda_pathfinder/cuda/pathfinder/_static_libs/find_bitcode_lib.py +++ b/cuda_pathfinder/cuda/pathfinder/_static_libs/find_bitcode_lib.py @@ -143,11 +143,12 @@ def locate_bitcode_lib(name: str) -> LocatedBitcodeLib: """ finder = _FindBitcodeLib(name) + # Path.absolute preserves symlink/.. traversal rather than normalizing it. abs_path = finder.try_site_packages() if abs_path is not None: return LocatedBitcodeLib( name=name, - abs_path=str(abs_path), + abs_path=str(abs_path.absolute()), filename=finder.filename, found_via="site-packages", ) @@ -156,7 +157,7 @@ def locate_bitcode_lib(name: str) -> LocatedBitcodeLib: if abs_path is not None: return LocatedBitcodeLib( name=name, - abs_path=str(abs_path), + abs_path=str(abs_path.absolute()), filename=finder.filename, found_via="conda", ) @@ -165,7 +166,7 @@ def locate_bitcode_lib(name: str) -> LocatedBitcodeLib: if abs_path is not None: return LocatedBitcodeLib( name=name, - abs_path=str(abs_path), + abs_path=str(abs_path.absolute()), filename=finder.filename, found_via="CUDA_PATH", ) diff --git a/cuda_pathfinder/cuda/pathfinder/_static_libs/find_static_lib.py b/cuda_pathfinder/cuda/pathfinder/_static_libs/find_static_lib.py index f0f1c85f538..f03971a29e9 100644 --- a/cuda_pathfinder/cuda/pathfinder/_static_libs/find_static_lib.py +++ b/cuda_pathfinder/cuda/pathfinder/_static_libs/find_static_lib.py @@ -142,11 +142,12 @@ def locate_static_lib(name: str) -> LocatedStaticLib: """ finder = _FindStaticLib(name) + # Path.absolute preserves symlink/.. traversal rather than normalizing it. abs_path = finder.try_site_packages() if abs_path is not None: return LocatedStaticLib( name=name, - abs_path=str(abs_path), + abs_path=str(abs_path.absolute()), filename=finder.filename, found_via="site-packages", ) @@ -155,7 +156,7 @@ def locate_static_lib(name: str) -> LocatedStaticLib: if abs_path is not None: return LocatedStaticLib( name=name, - abs_path=str(abs_path), + abs_path=str(abs_path.absolute()), filename=finder.filename, found_via="conda", ) @@ -164,7 +165,7 @@ def locate_static_lib(name: str) -> LocatedStaticLib: if abs_path is not None: return LocatedStaticLib( name=name, - abs_path=str(abs_path), + abs_path=str(abs_path.absolute()), filename=finder.filename, found_via="CUDA_PATH", ) diff --git a/cuda_pathfinder/tests/test_find_bitcode_lib.py b/cuda_pathfinder/tests/test_find_bitcode_lib.py index 6b5f2de49eb..81a6aedf4d0 100644 --- a/cuda_pathfinder/tests/test_find_bitcode_lib.py +++ b/cuda_pathfinder/tests/test_find_bitcode_lib.py @@ -135,6 +135,68 @@ def find_expected_sub_dir(sub_dir): assert located_lib.found_via == "CUDA_PATH" +@pytest.mark.usefixtures("clear_find_bitcode_lib_cache") +@pytest.mark.parametrize("source", ("site-packages", "conda", "CUDA_PATH")) +@pytest.mark.agent_authored(model="gpt-6") +def test_locate_bitcode_lib_returns_absolute_path_for_relative_root(monkeypatch, tmp_path, source): + monkeypatch.chdir(tmp_path) + monkeypatch.delenv("CONDA_PREFIX", raising=False) + monkeypatch.delenv("CUDA_HOME", raising=False) + monkeypatch.delenv("CUDA_PATH", raising=False) + + info = _bitcode_lib_info("device") + root = Path("relative-root") + if source == "site-packages": + lib_dir = root / Path(info["site_packages_dirs"][0]) + monkeypatch.setattr(find_bitcode_lib_module, "find_sub_dirs_all_sitepackages", lambda _sub_dir: [str(lib_dir)]) + else: + monkeypatch.setattr(find_bitcode_lib_module, "find_sub_dirs_all_sitepackages", lambda _sub_dir: []) + if source == "conda": + monkeypatch.setenv("CONDA_PREFIX", str(root)) + lib_dir = _conda_anchor(root) / Path(info["rel_path"]) + else: + monkeypatch.setenv("CUDA_HOME", str(root)) + lib_dir = root / Path(info["rel_path"]) + + expected = tmp_path / lib_dir / info["filename"] + expected.parent.mkdir(parents=True) + expected.touch() + located = locate_bitcode_lib("device") + assert located.found_via == source + assert located.abs_path == str(expected) + assert find_bitcode_lib("device") == str(expected) + + monkeypatch.chdir(tmp_path.parent) + assert Path(located.abs_path).is_file() + assert Path(find_bitcode_lib("device")).is_file() + + +@pytest.mark.skipif(os.name == "nt", reason="POSIX symlink parent traversal") +@pytest.mark.usefixtures("clear_find_bitcode_lib_cache") +@pytest.mark.agent_authored(model="gpt-6") +def test_locate_bitcode_lib_preserves_symlink_parent(monkeypatch, tmp_path): + monkeypatch.chdir(tmp_path) + monkeypatch.delenv("CONDA_PREFIX", raising=False) + monkeypatch.delenv("CUDA_PATH", raising=False) + monkeypatch.setenv("CUDA_HOME", "alias/..") + monkeypatch.setattr(find_bitcode_lib_module, "find_sub_dirs_all_sitepackages", lambda _sub_dir: []) + + real_root = tmp_path / "real" + (real_root / "child").mkdir(parents=True) + (tmp_path / "alias").symlink_to(real_root / "child", target_is_directory=True) + info = _bitcode_lib_info("device") + expected = real_root / info["rel_path"] / info["filename"] + expected.parent.mkdir(parents=True) + expected.touch() + + located = locate_bitcode_lib("device") + assert Path(located.abs_path).is_absolute() + assert Path(located.abs_path).samefile(expected) + cached = find_bitcode_lib("device") + monkeypatch.chdir(tmp_path.parent) + assert Path(cached).samefile(expected) + + @pytest.mark.usefixtures("clear_find_bitcode_lib_cache") def test_find_bitcode_lib_not_found_error_includes_cuda_home_directory_listing(monkeypatch, tmp_path): cuda_home = tmp_path / "cuda-home" diff --git a/cuda_pathfinder/tests/test_find_static_lib.py b/cuda_pathfinder/tests/test_find_static_lib.py index cf0e62dc8a2..c5f0832afff 100644 --- a/cuda_pathfinder/tests/test_find_static_lib.py +++ b/cuda_pathfinder/tests/test_find_static_lib.py @@ -117,6 +117,66 @@ def test_locate_static_lib_search_order(monkeypatch, tmp_path): assert located_lib.found_via == "CUDA_PATH" +@pytest.mark.usefixtures("clear_find_static_lib_cache") +@pytest.mark.parametrize("source", ("site-packages", "conda", "CUDA_PATH")) +@pytest.mark.agent_authored(model="gpt-6") +def test_locate_static_lib_returns_absolute_path_for_relative_root(monkeypatch, tmp_path, source): + monkeypatch.chdir(tmp_path) + monkeypatch.delenv("CONDA_PREFIX", raising=False) + monkeypatch.delenv("CUDA_HOME", raising=False) + monkeypatch.delenv("CUDA_PATH", raising=False) + + root = Path("relative-root") + if source == "site-packages": + lib_dir = root / Path(CUDADEVRT_INFO["site_packages_dirs"][0]) + monkeypatch.setattr(find_static_lib_module, "find_sub_dirs_all_sitepackages", lambda _sub_dir: [str(lib_dir)]) + else: + monkeypatch.setattr(find_static_lib_module, "find_sub_dirs_all_sitepackages", lambda _sub_dir: []) + if source == "conda": + monkeypatch.setenv("CONDA_PREFIX", str(root)) + lib_dir = _conda_anchor(root) / Path(CUDADEVRT_INFO["conda_rel_paths"][0]) + else: + monkeypatch.setenv("CUDA_HOME", str(root)) + lib_dir = root / Path(CUDADEVRT_INFO["ctk_rel_paths"][0]) + + expected = tmp_path / lib_dir / CUDADEVRT_INFO["filename"] + expected.parent.mkdir(parents=True) + expected.touch() + located = locate_static_lib("cudadevrt") + assert located.found_via == source + assert located.abs_path == str(expected) + assert find_static_lib("cudadevrt") == str(expected) + + monkeypatch.chdir(tmp_path.parent) + assert Path(located.abs_path).is_file() + assert Path(find_static_lib("cudadevrt")).is_file() + + +@pytest.mark.skipif(os.name == "nt", reason="POSIX symlink parent traversal") +@pytest.mark.usefixtures("clear_find_static_lib_cache") +@pytest.mark.agent_authored(model="gpt-6") +def test_locate_static_lib_preserves_symlink_parent(monkeypatch, tmp_path): + monkeypatch.chdir(tmp_path) + monkeypatch.delenv("CONDA_PREFIX", raising=False) + monkeypatch.delenv("CUDA_PATH", raising=False) + monkeypatch.setenv("CUDA_HOME", "alias/..") + monkeypatch.setattr(find_static_lib_module, "find_sub_dirs_all_sitepackages", lambda _sub_dir: []) + + real_root = tmp_path / "real" + (real_root / "child").mkdir(parents=True) + (tmp_path / "alias").symlink_to(real_root / "child", target_is_directory=True) + expected = real_root / Path(CUDADEVRT_INFO["ctk_rel_paths"][0]) / CUDADEVRT_INFO["filename"] + expected.parent.mkdir(parents=True) + expected.touch() + + located = locate_static_lib("cudadevrt") + assert Path(located.abs_path).is_absolute() + assert Path(located.abs_path).samefile(expected) + cached = find_static_lib("cudadevrt") + monkeypatch.chdir(tmp_path.parent) + assert Path(cached).samefile(expected) + + @pytest.mark.usefixtures("clear_find_static_lib_cache") def test_locate_static_lib_conda_rel_path_fallback(monkeypatch, tmp_path): filename = CUDADEVRT_INFO["filename"]