1010
1111import configparser
1212import ctypes .util
13+ import importlib
1314import os
1415import sys
1516from pathlib import Path
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
2568def _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