Skip to content

Commit fdd4763

Browse files
committed
First search in installed packages
1 parent 39345dd commit fdd4763

2 files changed

Lines changed: 96 additions & 56 deletions

File tree

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,6 @@ __pycache__/
2222
.vscode/*.code-snippets
2323

2424
# Ignore code-workspaces
25-
*.code-workspace
25+
*.code-workspace
26+
27+
*swp

findlibs/__init__.py

Lines changed: 93 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
import configparser
1212
import ctypes.util
13+
import importlib
1314
import os
1415
import sys
1516
from pathlib import Path
@@ -21,6 +22,48 @@
2122
"win32": ".dll",
2223
}
2324

25+
def _find_in_package(lib_name: str, pkg_name: str) -> str|None:
26+
"""Tries to find the library in an installed python module `{pgk_name}libs`.
27+
This assumes convention used by newly built binary-only ecmwf packages, eg, eckit shared libs
28+
ending up in "eckitlib" python module."""
29+
# NOTE we could have searched for relative location wrt __file__ -- but that breaks eg
30+
# editable installs of findlibs, conda-venv combinations, etc. The price we pay is that
31+
# the binary packages have to be importible, ie, the default output of auditwheel wont work
32+
try:
33+
module = importlib.import_module(pkg_name + "libs")
34+
venv_wheel_lib = str((Path(module.__file__) / '..' / lib_name).resolve())
35+
if os.path.exists(venv_wheel_lib):
36+
return venv_wheel_lib
37+
except ImportError:
38+
pass
39+
return None
40+
41+
def _find_in_python(lib_name: str, pkg_name: str) -> str|None:
42+
"""Tries to find the library installed directly to Conda/Python sys.prefix libs"""
43+
roots = [sys.prefix]
44+
if "CONDA_PREFIX" in os.environ:
45+
roots.append(os.environ["CONDA_PREFIX"])
46+
47+
for root in roots:
48+
for lib in ("lib", "lib64"):
49+
fullname = os.path.join(root, lib, lib_name)
50+
if os.path.exists(fullname):
51+
return fullname
52+
return None
53+
54+
def _find_in_home(lib_name: str, pkg_name: str) -> str|None:
55+
env_prefixes = [pkg_name.upper(), pkg_name.lower()]
56+
env_suffixes = ["HOME", "DIR"]
57+
envs = ["{}_{}".format(x, y) for x in env_prefixes for y in env_suffixes]
58+
59+
for env in envs:
60+
if env in os.environ:
61+
home = os.path.expanduser(os.environ[env])
62+
for lib in ("lib", "lib64"):
63+
fullname = os.path.join(home, lib, lib_name)
64+
if os.path.exists(fullname):
65+
return fullname
66+
return None
2467

2568
def _get_paths_from_config():
2669
locations = [
@@ -71,72 +114,27 @@ def _get_paths_from_config():
71114

72115
return paths
73116

74-
75-
def find(lib_name, pkg_name=None):
76-
"""Returns the path to the selected library, or None if not found.
77-
78-
Arguments
79-
---------
80-
lib_name : str
81-
Library name without the `lib` prefix. The name of the library to
82-
find is formed using ``lib_name`` and a platform specific suffix
83-
(by default ".so"). E.g. when ``lib_name`` is "eccodes" the library
84-
name will be "libeccodes.so" on Linux and "libeccodes.dylib"
85-
on macOS.
86-
pkg_name : str, optional
87-
Package name if it differs from the library name. Defaults to None.
88-
89-
Returns
90-
--------
91-
str or None
92-
Path to selected library
93-
"""
94-
pkg_name = pkg_name or lib_name
95-
extension = EXTENSIONS.get(sys.platform, ".so")
96-
libname = "lib{}{}".format(lib_name, extension)
97-
98-
# sys.prefix/lib, $CONDA_PREFIX/lib has highest priority;
99-
# otherwise, system library may mess up anaconda's virtual environment.
100-
101-
roots = [sys.prefix]
102-
if "CONDA_PREFIX" in os.environ:
103-
roots.append(os.environ["CONDA_PREFIX"])
104-
105-
for root in roots:
106-
for lib in ("lib", "lib64"):
107-
fullname = os.path.join(root, lib, libname)
108-
if os.path.exists(fullname):
109-
return fullname
110-
111-
env_prefixes = [pkg_name.upper(), pkg_name.lower()]
112-
env_suffixes = ["HOME", "DIR"]
113-
envs = ["{}_{}".format(x, y) for x in env_prefixes for y in env_suffixes]
114-
115-
for env in envs:
116-
if env in os.environ:
117-
home = os.path.expanduser(os.environ[env])
118-
for lib in ("lib", "lib64"):
119-
fullname = os.path.join(home, lib, libname)
120-
if os.path.exists(fullname):
121-
return fullname
122-
123-
config_paths = _get_paths_from_config()
124-
125-
for root in config_paths:
117+
def _find_in_config_paths(lib_name: str, pkg_name: str) -> str|None:
118+
paths = _get_paths_from_config()
119+
for root in paths:
126120
for lib in ("lib", "lib64"):
127-
filepath = root / lib / f"lib{lib_name}{extension}"
121+
filepath = root / lib / lib_name
128122
if filepath.exists():
129123
return str(filepath)
124+
return None
130125

126+
def _find_in_ld_path(lib_name: str, pkg_name: str) -> str|None:
131127
for path in (
132128
"LD_LIBRARY_PATH",
133129
"DYLD_LIBRARY_PATH",
134130
):
135131
for home in os.environ.get(path, "").split(":"):
136-
fullname = os.path.join(home, libname)
132+
fullname = os.path.join(home, lib_name)
137133
if os.path.exists(fullname):
138134
return fullname
135+
return None
139136

137+
def _find_in_sys(lib_name: str, pkg_name: str) -> str|None:
140138
for root in (
141139
"/",
142140
"/usr/",
@@ -146,8 +144,48 @@ def find(lib_name, pkg_name=None):
146144
os.path.expanduser("~/.local"),
147145
):
148146
for lib in ("lib", "lib64"):
149-
fullname = os.path.join(root, lib, libname)
147+
fullname = os.path.join(root, lib, lib_name)
150148
if os.path.exists(fullname):
151149
return fullname
150+
return None
152151

152+
def _find_in_ctypes_util(lib_name: str, pkg_name: str) -> str|None:
153153
return ctypes.util.find_library(lib_name)
154+
155+
def find(lib_name: str, pkg_name: str|None = None) -> str|None:
156+
"""Returns the path to the selected library, or None if not found.
157+
158+
Arguments
159+
---------
160+
lib_name : str
161+
Library name without the `lib` prefix. The name of the library to
162+
find is formed using ``lib_name`` and a platform specific suffix
163+
(by default ".so"). E.g. when ``lib_name`` is "eccodes" the library
164+
name will be "libeccodes.so" on Linux and "libeccodes.dylib"
165+
on macOS.
166+
pkg_name : str, optional
167+
Package name if it differs from the library name. Defaults to None.
168+
169+
Returns
170+
--------
171+
str or None
172+
Path to selected library
173+
"""
174+
pkg_name = pkg_name or lib_name
175+
extension = EXTENSIONS.get(sys.platform, ".so")
176+
lib_name = "lib{}{}".format(lib_name, extension)
177+
178+
sources = [
179+
_find_in_package,
180+
_find_in_python,
181+
_find_in_home,
182+
_find_in_config_paths,
183+
_find_in_ld_path,
184+
_find_in_sys,
185+
_find_in_ctypes_util,
186+
]
187+
188+
for source in sources:
189+
if (result := source(lib_name, pkg_name)):
190+
return result
191+
return None

0 commit comments

Comments
 (0)