Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

search in /usr/local/lib and /usr/lib like the system loader #167

Merged
merged 9 commits into from
Nov 16, 2022
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
4 changes: 4 additions & 0 deletions Changelog
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
42 changes: 29 additions & 13 deletions delocate/libsana.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -511,20 +514,33 @@ 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 os.path.exists(rpath_lib):
return realpath(rpath_lib)
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 = list(rpaths)

# these paths are searched by the macos loader in order if the
# library is not in the previous paths.
paths_to_search.extend(_default_paths_to_search)

rel_path = lib_path.split("/", 1)[1]
for prefix_path in paths_to_search:
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)

raise DependencyNotFound(lib_path)

Expand Down Expand Up @@ -561,7 +577,7 @@ def resolve_rpath(lib_path, rpaths):
return lib_path

lib_rpath = lib_path.split("/", 1)[1]
for rpath in rpaths:
for rpath in (*rpaths, *_default_paths_to_search):
rpath_lib = realpath(pjoin(rpath, lib_rpath))
if os.path.exists(rpath_lib):
return rpath_lib
Expand Down
16 changes: 16 additions & 0 deletions delocate/tests/test_libsana.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
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

Expand Down Expand Up @@ -499,6 +500,21 @@ 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() -> None:
# A minimal test of the resolve_dynamic_paths fallthrough
path, lib = split(LIBA)
lib_rpath = pjoin("@rpath", lib)
# 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 resolve_dynamic_paths(lib_rpath, [], path) == realpath(LIBA)


@pytest.mark.xfail(sys.platform != "darwin", reason="otool")
def test_get_dependencies(tmpdir):
# type: (object) -> None
Expand Down