diff --git a/cuda_pathfinder/cuda/pathfinder/_dynamic_libs/descriptor_catalog.py b/cuda_pathfinder/cuda/pathfinder/_dynamic_libs/descriptor_catalog.py index e39046eec70..a6ccfc44997 100644 --- a/cuda_pathfinder/cuda/pathfinder/_dynamic_libs/descriptor_catalog.py +++ b/cuda_pathfinder/cuda/pathfinder/_dynamic_libs/descriptor_catalog.py @@ -59,6 +59,8 @@ def _ctk_windows_wheel_dirs(cuda13_bin_dir: str, cuda12_dir: str) -> WindowsSear class DescriptorSpec: name: str packaged_with: PackagedWith + # Tabulate oldest version first: the loader reverses these to search + # newest -> oldest (see load_dl_linux._candidate_sonames). linux_sonames: tuple[str, ...] = () windows_dlls: tuple[str, ...] = () supported_windows_arch: tuple[WindowsArch, ...] = () @@ -406,7 +408,7 @@ class DescriptorSpec: DescriptorSpec( name="cufftMp", packaged_with="other", - linux_sonames=("libcufftMp.so.12", "libcufftMp.so.11"), + linux_sonames=("libcufftMp.so.11", "libcufftMp.so.12"), site_packages_linux=("nvidia/cufftmp/cu13/lib", "nvidia/cufftmp/cu12/lib"), dependencies=("nvshmem_host",), requires_rtld_deepbind=True, diff --git a/cuda_pathfinder/tests/test_descriptor_catalog.py b/cuda_pathfinder/tests/test_descriptor_catalog.py index 3b643aa2e77..7f810e7b86d 100644 --- a/cuda_pathfinder/tests/test_descriptor_catalog.py +++ b/cuda_pathfinder/tests/test_descriptor_catalog.py @@ -80,6 +80,22 @@ def test_linux_sonames_look_like_sonames(spec: DescriptorSpec): assert ".so" in soname, f"Unexpected Linux soname format: {soname}" +def _linux_soname_version_key(soname: str) -> tuple[int, ...]: + """Version components of a ``lib.so[.[....]]`` file name.""" + _, _, version_suffix = soname.partition(".so") + return tuple(int(part) for part in version_suffix.split(".") if part) + + +@pytest.mark.parametrize("spec", DESCRIPTOR_CATALOG, ids=lambda s: s.name) +@pytest.mark.agent_authored(model="claude-opus-5") +def test_linux_sonames_are_tabulated_oldest_first(spec: DescriptorSpec): + """``load_dl_linux._candidate_sonames()`` reverses this tuple to search + newest -> oldest, so a newest-first table silently inverts the priority. + """ + versions = [_linux_soname_version_key(soname) for soname in spec.linux_sonames] + assert versions == sorted(versions), f"{spec.name} linux_sonames are not oldest-first: {spec.linux_sonames}" + + @pytest.mark.parametrize("spec", DESCRIPTOR_CATALOG, ids=lambda s: s.name) def test_windows_dlls_look_like_dlls(spec: DescriptorSpec): for dll in spec.windows_dlls: