Skip to content

Commit

Permalink
search in /usr/local/lib and /usr/lib like the system loader (#167)
Browse files Browse the repository at this point in the history
* search in /usr/local/lib and /usr/lib like the system loader

* fix black and mypy issues

* Fix tests

* isort

* improve test comment

* fix dependencynotfound

* skip test_resolve_dynamic_paths_fallthrough on win

* style fixes

* Add to Changelog
  • Loading branch information
isuruf authored Nov 16, 2022
1 parent 60ab91b commit 653b973
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 13 deletions.
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

0 comments on commit 653b973

Please sign in to comment.