Skip to content

Commit

Permalink
First search in installed packages
Browse files Browse the repository at this point in the history
  • Loading branch information
tmi committed Nov 26, 2024
1 parent 39345dd commit bd8d8e5
Show file tree
Hide file tree
Showing 2 changed files with 92 additions and 56 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,6 @@ __pycache__/
.vscode/*.code-snippets

# Ignore code-workspaces
*.code-workspace
*.code-workspace

*swp
144 changes: 89 additions & 55 deletions findlibs/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,45 @@
"win32": ".dll",
}

def _find_in_package(lib_name: str, pkg_name: str) -> str|None:
"""Tries to find the library in a package installed relatively to findlibs, presumably in venv/conda.
This assumes binary package with content in python module `{pkg_name}libs` (mind the missing dot),
which is the case for newly built ecmwf binary-only packages"""
venv_site_packages = Path(__file__) / '..' / '..'
venv_wheel_lib = str((venv_site_packages / f"{pkg_name}libs" / lib_name).resolve())
# TODO instead consider try import pkg_name and search from `pkg_name.__file__ / ..`. That requires
# the pkg_name to be importible tho -- a restriction we may not want to enforce
if os.path.exists(venv_wheel_lib):
return venv_wheel_lib
else:
return None

def _find_in_python(lib_name: str, pkg_name: str) -> str|None:
"""Tries to find the library installed directly to Conda/Python sys.prefix libs"""
roots = [sys.prefix]
if "CONDA_PREFIX" in os.environ:
roots.append(os.environ["CONDA_PREFIX"])

for root in roots:
for lib in ("lib", "lib64"):
fullname = os.path.join(root, lib, lib_name)
if os.path.exists(fullname):
return fullname
return None

def _find_in_home(lib_name: str, pkg_name: str) -> str|None:
env_prefixes = [pkg_name.upper(), pkg_name.lower()]
env_suffixes = ["HOME", "DIR"]
envs = ["{}_{}".format(x, y) for x in env_prefixes for y in env_suffixes]

for env in envs:
if env in os.environ:
home = os.path.expanduser(os.environ[env])
for lib in ("lib", "lib64"):
fullname = os.path.join(home, lib, lib_name)
if os.path.exists(fullname):
return fullname
return None

def _get_paths_from_config():
locations = [
Expand Down Expand Up @@ -71,72 +110,27 @@ def _get_paths_from_config():

return paths


def find(lib_name, pkg_name=None):
"""Returns the path to the selected library, or None if not found.
Arguments
---------
lib_name : str
Library name without the `lib` prefix. The name of the library to
find is formed using ``lib_name`` and a platform specific suffix
(by default ".so"). E.g. when ``lib_name`` is "eccodes" the library
name will be "libeccodes.so" on Linux and "libeccodes.dylib"
on macOS.
pkg_name : str, optional
Package name if it differs from the library name. Defaults to None.
Returns
--------
str or None
Path to selected library
"""
pkg_name = pkg_name or lib_name
extension = EXTENSIONS.get(sys.platform, ".so")
libname = "lib{}{}".format(lib_name, extension)

# sys.prefix/lib, $CONDA_PREFIX/lib has highest priority;
# otherwise, system library may mess up anaconda's virtual environment.

roots = [sys.prefix]
if "CONDA_PREFIX" in os.environ:
roots.append(os.environ["CONDA_PREFIX"])

for root in roots:
for lib in ("lib", "lib64"):
fullname = os.path.join(root, lib, libname)
if os.path.exists(fullname):
return fullname

env_prefixes = [pkg_name.upper(), pkg_name.lower()]
env_suffixes = ["HOME", "DIR"]
envs = ["{}_{}".format(x, y) for x in env_prefixes for y in env_suffixes]

for env in envs:
if env in os.environ:
home = os.path.expanduser(os.environ[env])
for lib in ("lib", "lib64"):
fullname = os.path.join(home, lib, libname)
if os.path.exists(fullname):
return fullname

config_paths = _get_paths_from_config()

for root in config_paths:
def _find_in_config_paths(lib_name: str, pkg_name: str) -> str|None:
paths = _get_paths_from_config()
for root in paths:
for lib in ("lib", "lib64"):
filepath = root / lib / f"lib{lib_name}{extension}"
filepath = root / lib / lib_name
if filepath.exists():
return str(filepath)
return None

def _find_in_ld_path(lib_name: str, pkg_name: str) -> str|None:
for path in (
"LD_LIBRARY_PATH",
"DYLD_LIBRARY_PATH",
):
for home in os.environ.get(path, "").split(":"):
fullname = os.path.join(home, libname)
fullname = os.path.join(home, lib_name)
if os.path.exists(fullname):
return fullname
return None

def _find_in_sys(lib_name: str, pkg_name: str) -> str|None:
for root in (
"/",
"/usr/",
Expand All @@ -146,8 +140,48 @@ def find(lib_name, pkg_name=None):
os.path.expanduser("~/.local"),
):
for lib in ("lib", "lib64"):
fullname = os.path.join(root, lib, libname)
fullname = os.path.join(root, lib, lib_name)
if os.path.exists(fullname):
return fullname
return None

def _find_in_ctypes_util(lib_name: str, pkg_name: str) -> str|None:
return ctypes.util.find_library(lib_name)

def find(lib_name: str, pkg_name: str|None = None) -> str|None:
"""Returns the path to the selected library, or None if not found.
Arguments
---------
lib_name : str
Library name without the `lib` prefix. The name of the library to
find is formed using ``lib_name`` and a platform specific suffix
(by default ".so"). E.g. when ``lib_name`` is "eccodes" the library
name will be "libeccodes.so" on Linux and "libeccodes.dylib"
on macOS.
pkg_name : str, optional
Package name if it differs from the library name. Defaults to None.
Returns
--------
str or None
Path to selected library
"""
pkg_name = pkg_name or lib_name
extension = EXTENSIONS.get(sys.platform, ".so")
lib_name = "lib{}{}".format(lib_name, extension)

sources = [
_find_in_package,
_find_in_python,
_find_in_home,
_find_in_config_paths,
_find_in_ld_path,
_find_in_sys,
_find_in_ctypes_util,
]

for source in sources:
if (result := source(lib_name, pkg_name)):
return result
return None

0 comments on commit bd8d8e5

Please sign in to comment.