From e663e699cbb5b077a41f1702d328ff4ad3c913bc Mon Sep 17 00:00:00 2001 From: Isuru Fernando Date: Wed, 9 Nov 2022 17:03:05 -0600 Subject: [PATCH 1/9] search in /usr/local/lib and /usr/lib like the system loader --- delocate/libsana.py | 30 +++++++++++++++++++----------- delocate/tests/test_libsana.py | 10 ++++++++++ 2 files changed, 29 insertions(+), 11 deletions(-) diff --git a/delocate/libsana.py b/delocate/libsana.py index 0fa30be8..08ebad1e 100644 --- a/delocate/libsana.py +++ b/delocate/libsana.py @@ -511,20 +511,28 @@ def resolve_dynamic_paths(lib_path, rpaths, loader_path, executable_path=None): """ if executable_path is None: executable_path = dirname(sys.executable) - if lib_path.startswith("@loader_path/"): - return realpath(pjoin(loader_path, lib_path.split("/", 1)[1])) - if lib_path.startswith("@executable_path/"): - return realpath(pjoin(executable_path, lib_path.split("/", 1)[1])) - if not lib_path.startswith("@rpath/"): + + if not lib_path.startswith(("@rpath/", "@loader_path/", "@executable_path/")): return realpath(lib_path) - lib_rpath = lib_path.split("/", 1)[1] - for rpath in rpaths: - rpath_lib = resolve_dynamic_paths( - pjoin(rpath, lib_rpath), (), loader_path, executable_path + if lib_path.startswith("@loader_path/"): + paths_to_search = [loader_path] + elif lib_path.startswith("@executable_path/"): + paths_to_search = [executable_path] + elif lib_path.startswith("@rpath/"): + paths_to_search = rpaths.copy() + + # these paths are searched by the macos loader in order if the + # library is not in the previous paths. + paths_to_search.extend(["/usr/local/lib", "/usr/lib"]) + + rel_path = lib_path.split("/", 1)[1] + for prefix_path in paths_to_search: + abs_path = resolve_dynamic_paths( + pjoin(prefix_path, rel_path), (), loader_path, executable_path ) - if os.path.exists(rpath_lib): - return realpath(rpath_lib) + if os.path.exists(abs_path): + return realpath(abs_path) raise DependencyNotFound(lib_path) diff --git a/delocate/tests/test_libsana.py b/delocate/tests/test_libsana.py index 0f2920bd..8a5020fe 100644 --- a/delocate/tests/test_libsana.py +++ b/delocate/tests/test_libsana.py @@ -499,6 +499,16 @@ def test_resolve_rpath(): assert_equal(resolve_rpath(lib_rpath, []), lib_rpath) +def test_resolve_dynamic_paths_fallthrough(): + # type: () -> None + if not os.path.exists("/usr/lib/libstdc++.6.dylib"): + pytest.skip("Needs /usr/lib/libstdc++.6.dylib") + # A minimal test of the resolve_dynamic_paths_fallthrough + lib_rpath = pjoin("@rpath", "libstdc++.6.dylib") + # Should find /usr/lib/libstdc++.6.dylib + assert_equal(resolve_rpath(lib_rpath, []), "/usr/lib/libstdc++.6.dylib") + + @pytest.mark.xfail(sys.platform != "darwin", reason="otool") def test_get_dependencies(tmpdir): # type: (object) -> None From b0efac740fc7e97832a1f11d8e2a94688c3dc88c Mon Sep 17 00:00:00 2001 From: Isuru Fernando Date: Wed, 9 Nov 2022 17:06:20 -0600 Subject: [PATCH 2/9] fix black and mypy issues --- delocate/libsana.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/delocate/libsana.py b/delocate/libsana.py index 08ebad1e..f9db6acf 100644 --- a/delocate/libsana.py +++ b/delocate/libsana.py @@ -512,7 +512,9 @@ def resolve_dynamic_paths(lib_path, rpaths, loader_path, executable_path=None): if executable_path is None: executable_path = dirname(sys.executable) - if not lib_path.startswith(("@rpath/", "@loader_path/", "@executable_path/")): + if not lib_path.startswith( + ("@rpath/", "@loader_path/", "@executable_path/") + ): return realpath(lib_path) if lib_path.startswith("@loader_path/"): @@ -520,7 +522,7 @@ def resolve_dynamic_paths(lib_path, rpaths, loader_path, executable_path=None): elif lib_path.startswith("@executable_path/"): paths_to_search = [executable_path] elif lib_path.startswith("@rpath/"): - paths_to_search = rpaths.copy() + paths_to_search = list(rpaths) # these paths are searched by the macos loader in order if the # library is not in the previous paths. From 4975f16123537c4d059433ac2715797d7cc58888 Mon Sep 17 00:00:00 2001 From: Isuru Fernando Date: Wed, 9 Nov 2022 17:31:26 -0600 Subject: [PATCH 3/9] Fix tests --- delocate/libsana.py | 7 +++++-- delocate/tests/test_libsana.py | 15 +++++++++------ 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/delocate/libsana.py b/delocate/libsana.py index f9db6acf..755cbe6c 100644 --- a/delocate/libsana.py +++ b/delocate/libsana.py @@ -473,6 +473,9 @@ def tree_libs( return lib_dict +_default_paths_to_search = ("/usr/local/lib", "/usr/lib") + + def resolve_dynamic_paths(lib_path, rpaths, loader_path, executable_path=None): # type: (Text, Iterable[Text], Text, Optional[Text]) -> Text """Return `lib_path` with any special runtime linking names resolved. @@ -526,7 +529,7 @@ def resolve_dynamic_paths(lib_path, rpaths, loader_path, executable_path=None): # these paths are searched by the macos loader in order if the # library is not in the previous paths. - paths_to_search.extend(["/usr/local/lib", "/usr/lib"]) + paths_to_search.extend(_default_paths_to_search) rel_path = lib_path.split("/", 1)[1] for prefix_path in paths_to_search: @@ -571,7 +574,7 @@ def resolve_rpath(lib_path, rpaths): return lib_path lib_rpath = lib_path.split("/", 1)[1] - for rpath in rpaths: + for rpath in tuple(rpaths) + _default_paths_to_search: rpath_lib = realpath(pjoin(rpath, lib_rpath)) if os.path.exists(rpath_lib): return rpath_lib diff --git a/delocate/tests/test_libsana.py b/delocate/tests/test_libsana.py index 8a5020fe..a09ab377 100644 --- a/delocate/tests/test_libsana.py +++ b/delocate/tests/test_libsana.py @@ -13,6 +13,7 @@ from typing import Dict, Iterable, Text import pytest +from unittest import mock from ..delocating import DelocationError, filter_system_libs from ..libsana import ( @@ -501,12 +502,14 @@ def test_resolve_rpath(): def test_resolve_dynamic_paths_fallthrough(): # type: () -> None - if not os.path.exists("/usr/lib/libstdc++.6.dylib"): - pytest.skip("Needs /usr/lib/libstdc++.6.dylib") - # A minimal test of the resolve_dynamic_paths_fallthrough - lib_rpath = pjoin("@rpath", "libstdc++.6.dylib") - # Should find /usr/lib/libstdc++.6.dylib - assert_equal(resolve_rpath(lib_rpath, []), "/usr/lib/libstdc++.6.dylib") + # A minimal test of the resolve_dynamic_paths fallthrough + path, lib = split(LIBA) + lib_rpath = pjoin("@rpath", lib) + # Should return the given parameter as is since it can't be found + with pytest.raises(DependencyNotFound): + resolve_dynamic_paths(lib_rpath, [], path) + with mock.patch("delocate.libsana._default_paths_to_search", (path,)): + assert_equal(resolve_dynamic_paths(lib_rpath, [], path), realpath(LIBA)) @pytest.mark.xfail(sys.platform != "darwin", reason="otool") From 775324b85f560867e91d028d88ccb4e309f87dcb Mon Sep 17 00:00:00 2001 From: Isuru Fernando Date: Wed, 9 Nov 2022 17:32:31 -0600 Subject: [PATCH 4/9] isort --- delocate/tests/test_libsana.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/delocate/tests/test_libsana.py b/delocate/tests/test_libsana.py index a09ab377..5f1a698a 100644 --- a/delocate/tests/test_libsana.py +++ b/delocate/tests/test_libsana.py @@ -11,9 +11,9 @@ from os.path import join as pjoin from os.path import realpath, relpath, split from typing import Dict, Iterable, Text +from unittest import mock import pytest -from unittest import mock from ..delocating import DelocationError, filter_system_libs from ..libsana import ( From 6824e9fabd777a4b8397800e5431c060dc72725e Mon Sep 17 00:00:00 2001 From: Isuru Fernando Date: Wed, 9 Nov 2022 17:36:27 -0600 Subject: [PATCH 5/9] improve test comment --- delocate/tests/test_libsana.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/delocate/tests/test_libsana.py b/delocate/tests/test_libsana.py index 5f1a698a..80dadd2f 100644 --- a/delocate/tests/test_libsana.py +++ b/delocate/tests/test_libsana.py @@ -505,9 +505,12 @@ def test_resolve_dynamic_paths_fallthrough(): # A minimal test of the resolve_dynamic_paths fallthrough path, lib = split(LIBA) lib_rpath = pjoin("@rpath", lib) - # Should return the given parameter as is since it can't be found + # Should fail as rpath is not given and the library cannot be found + # in default paths to search with pytest.raises(DependencyNotFound): resolve_dynamic_paths(lib_rpath, [], path) + # Since the library is in the default paths to search, this should + # return the full path to the library with mock.patch("delocate.libsana._default_paths_to_search", (path,)): assert_equal(resolve_dynamic_paths(lib_rpath, [], path), realpath(LIBA)) From 957548cee42a358ff8c036a01fc9ec8d6cba96c7 Mon Sep 17 00:00:00 2001 From: Isuru Fernando Date: Thu, 10 Nov 2022 02:17:42 +0200 Subject: [PATCH 6/9] fix dependencynotfound --- delocate/libsana.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/delocate/libsana.py b/delocate/libsana.py index 755cbe6c..454fba96 100644 --- a/delocate/libsana.py +++ b/delocate/libsana.py @@ -533,9 +533,12 @@ def resolve_dynamic_paths(lib_path, rpaths, loader_path, executable_path=None): rel_path = lib_path.split("/", 1)[1] for prefix_path in paths_to_search: - abs_path = resolve_dynamic_paths( - pjoin(prefix_path, rel_path), (), loader_path, executable_path - ) + try: + abs_path = resolve_dynamic_paths( + pjoin(prefix_path, rel_path), (), loader_path, executable_path + ) + except DependencyNotFound: + continue if os.path.exists(abs_path): return realpath(abs_path) From c7257d5a17ae0f3d6c361f1b29278f44968cd59e Mon Sep 17 00:00:00 2001 From: Isuru Fernando Date: Wed, 9 Nov 2022 18:25:36 -0600 Subject: [PATCH 7/9] skip test_resolve_dynamic_paths_fallthrough on win --- delocate/tests/test_libsana.py | 1 + 1 file changed, 1 insertion(+) diff --git a/delocate/tests/test_libsana.py b/delocate/tests/test_libsana.py index 80dadd2f..5a4e41b4 100644 --- a/delocate/tests/test_libsana.py +++ b/delocate/tests/test_libsana.py @@ -500,6 +500,7 @@ def test_resolve_rpath(): assert_equal(resolve_rpath(lib_rpath, []), lib_rpath) +@pytest.mark.xfail(sys.platform == "win32", reason="Needs Unix linkage.") def test_resolve_dynamic_paths_fallthrough(): # type: () -> None # A minimal test of the resolve_dynamic_paths fallthrough From aa34bcc9e98f72707b1b1ac25ac69b1869441bae Mon Sep 17 00:00:00 2001 From: Isuru Fernando Date: Wed, 9 Nov 2022 21:52:36 -0600 Subject: [PATCH 8/9] style fixes --- delocate/libsana.py | 2 +- delocate/tests/test_libsana.py | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/delocate/libsana.py b/delocate/libsana.py index 454fba96..2b1af695 100644 --- a/delocate/libsana.py +++ b/delocate/libsana.py @@ -577,7 +577,7 @@ def resolve_rpath(lib_path, rpaths): return lib_path lib_rpath = lib_path.split("/", 1)[1] - for rpath in tuple(rpaths) + _default_paths_to_search: + for rpath in (*rpaths, *_default_paths_to_search): rpath_lib = realpath(pjoin(rpath, lib_rpath)) if os.path.exists(rpath_lib): return rpath_lib diff --git a/delocate/tests/test_libsana.py b/delocate/tests/test_libsana.py index 5a4e41b4..c4946fec 100644 --- a/delocate/tests/test_libsana.py +++ b/delocate/tests/test_libsana.py @@ -501,8 +501,7 @@ def test_resolve_rpath(): @pytest.mark.xfail(sys.platform == "win32", reason="Needs Unix linkage.") -def test_resolve_dynamic_paths_fallthrough(): - # type: () -> None +def test_resolve_dynamic_paths_fallthrough() -> None: # A minimal test of the resolve_dynamic_paths fallthrough path, lib = split(LIBA) lib_rpath = pjoin("@rpath", lib) @@ -513,7 +512,7 @@ def test_resolve_dynamic_paths_fallthrough(): # Since the library is in the default paths to search, this should # return the full path to the library with mock.patch("delocate.libsana._default_paths_to_search", (path,)): - assert_equal(resolve_dynamic_paths(lib_rpath, [], path), realpath(LIBA)) + assert resolve_dynamic_paths(lib_rpath, [], path) == realpath(LIBA) @pytest.mark.xfail(sys.platform != "darwin", reason="otool") From 55fb9456a3b9705b1a3a060092a36c1871b939ff Mon Sep 17 00:00:00 2001 From: Isuru Fernando Date: Wed, 9 Nov 2022 21:55:29 -0600 Subject: [PATCH 9/9] Add to Changelog --- Changelog | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Changelog b/Changelog index 0deb14f4..1df57cbb 100644 --- a/Changelog +++ b/Changelog @@ -21,6 +21,10 @@ Releases * [Unreleased] + * Dependency paths with ``@rpath``, ``@loader_path`` and ``@executable_path`` + will now look at ``/usr/local/lib`` and ``/usr/lib`` after the + rpaths, loader path and executable path are searched respectively. + * [0.10.3] - 2022-11-04 * Support for Python 3.6 has been dropped.