From a19ea149ad2fff5f793bb4508af8b927843eacb7 Mon Sep 17 00:00:00 2001 From: Vojta Tuma Date: Tue, 26 Nov 2024 09:48:19 +0100 Subject: [PATCH] First search in installed packages --- .gitignore | 4 +- findlibs/__init__.py | 149 +++++++++++++++++++++++++++---------------- 2 files changed, 97 insertions(+), 56 deletions(-) diff --git a/.gitignore b/.gitignore index 152ddae..869b8ae 100644 --- a/.gitignore +++ b/.gitignore @@ -22,4 +22,6 @@ __pycache__/ .vscode/*.code-snippets # Ignore code-workspaces -*.code-workspace \ No newline at end of file +*.code-workspace + +*swp diff --git a/findlibs/__init__.py b/findlibs/__init__.py index 519ced2..1b1dc79 100644 --- a/findlibs/__init__.py +++ b/findlibs/__init__.py @@ -10,6 +10,7 @@ import configparser import ctypes.util +import importlib import os import sys from pathlib import Path @@ -21,6 +22,49 @@ "win32": ".dll", } +def _find_in_package(lib_name: str, pkg_name: str) -> str|None: + """Tries to find the library in an installed python module `{pgk_name}libs`. + This assumes convention used by newly built binary-only ecmwf packages, eg, eckit shared libs + ending up in "eckitlib" python module.""" + # NOTE we could have searched for relative location wrt __file__ -- but that breaks eg + # editable installs of findlibs, conda-venv combinations, etc. The price we pay is that + # the binary packages have to be importible, ie, the default output of auditwheel wont work + try: + module = importlib.import_module(pkg_name + "libs") + venv_wheel_lib = str((Path(module.__file__) / '..' / lib_name).resolve()) + if os.path.exists(venv_wheel_lib): + return venv_wheel_lib + except ImportError: + print("fail") + pass + 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 = [ @@ -71,72 +115,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/", @@ -146,8 +145,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