1 import os
2 import shutil
3 import subprocess
4 import sys
5
6 # find_library(name) returns the pathname of a library, or None.
7 if os.name == "nt":
8
9 def _get_build_version():
10 """Return the version of MSVC that was used to build Python.
11
12 For Python 2.3 and up, the version number is included in
13 sys.version. For earlier versions, assume the compiler is MSVC 6.
14 """
15 # This function was copied from Lib/distutils/msvccompiler.py
16 prefix = "MSC v."
17 i = sys.version.find(prefix)
18 if i == -1:
19 return 6
20 i = i + len(prefix)
21 s, rest = sys.version[i:].split(" ", 1)
22 majorVersion = int(s[:-2]) - 6
23 if majorVersion >= 13:
24 majorVersion += 1
25 minorVersion = int(s[2:3]) / 10.0
26 # I don't think paths are affected by minor version in version 6
27 if majorVersion == 6:
28 minorVersion = 0
29 if majorVersion >= 6:
30 return majorVersion + minorVersion
31 # else we don't know what version of the compiler this is
32 return None
33
34 def find_msvcrt():
35 """Return the name of the VC runtime dll"""
36 version = _get_build_version()
37 if version is None:
38 # better be safe than sorry
39 return None
40 if version <= 6:
41 clibname = 'msvcrt'
42 elif version <= 13:
43 clibname = 'msvcr%d' % (version * 10)
44 else:
45 # CRT is no longer directly loadable. See issue23606 for the
46 # discussion about alternative approaches.
47 return None
48
49 # If python was built with in debug mode
50 import importlib.machinery
51 if '_d.pyd' in importlib.machinery.EXTENSION_SUFFIXES:
52 clibname += 'd'
53 return clibname+'.dll'
54
55 def find_library(name):
56 if name in ('c', 'm'):
57 return find_msvcrt()
58 # See MSDN for the REAL search order.
59 for directory in os.environ['PATH'].split(os.pathsep):
60 fname = os.path.join(directory, name)
61 if os.path.isfile(fname):
62 return fname
63 if fname.lower().endswith(".dll"):
64 continue
65 fname = fname + ".dll"
66 if os.path.isfile(fname):
67 return fname
68 return None
69
70 elif os.name == "posix" and sys.platform == "darwin":
71 from ctypes.macholib.dyld import dyld_find as _dyld_find
72 def find_library(name):
73 possible = ['lib%s.dylib' % name,
74 '%s.dylib' % name,
75 '%s.framework/%s' % (name, name)]
76 for name in possible:
77 try:
78 return _dyld_find(name)
79 except ValueError:
80 continue
81 return None
82
83 elif sys.platform.startswith("aix"):
84 # AIX has two styles of storing shared libraries
85 # GNU auto_tools refer to these as svr4 and aix
86 # svr4 (System V Release 4) is a regular file, often with .so as suffix
87 # AIX style uses an archive (suffix .a) with members (e.g., shr.o, libssl.so)
88 # see issue#26439 and _aix.py for more details
89
90 from ctypes._aix import find_library
91
92 elif os.name == "posix":
93 # Andreas Degert's find functions, using gcc, /sbin/ldconfig, objdump
94 import re, tempfile
95
96 def _is_elf(filename):
97 "Return True if the given file is an ELF file"
98 elf_header = b'\x7fELF'
99 with open(filename, 'br') as thefile:
100 return thefile.read(4) == elf_header
101
102 def _findLib_gcc(name):
103 # Run GCC's linker with the -t (aka --trace) option and examine the
104 # library name it prints out. The GCC command will fail because we
105 # haven't supplied a proper program with main(), but that does not
106 # matter.
107 expr = os.fsencode(r'[^\(\)\s]*lib%s\.[^\(\)\s]*' % re.escape(name))
108
109 c_compiler = shutil.which('gcc')
110 if not c_compiler:
111 c_compiler = shutil.which('cc')
112 if not c_compiler:
113 # No C compiler available, give up
114 return None
115
116 temp = tempfile.NamedTemporaryFile()
117 try:
118 args = [c_compiler, '-Wl,-t', '-o', temp.name, '-l' + name]
119
120 env = dict(os.environ)
121 env['LC_ALL'] = 'C'
122 env['LANG'] = 'C'
123 try:
124 proc = subprocess.Popen(args,
125 stdout=subprocess.PIPE,
126 stderr=subprocess.STDOUT,
127 env=env)
128 except OSError: # E.g. bad executable
129 return None
130 with proc:
131 trace = proc.stdout.read()
132 finally:
133 try:
134 temp.close()
135 except FileNotFoundError:
136 # Raised if the file was already removed, which is the normal
137 # behaviour of GCC if linking fails
138 pass
139 res = re.findall(expr, trace)
140 if not res:
141 return None
142
143 for file in res:
144 # Check if the given file is an elf file: gcc can report
145 # some files that are linker scripts and not actual
146 # shared objects. See bpo-41976 for more details
147 if not _is_elf(file):
148 continue
149 return os.fsdecode(file)
150
151
152 if sys.platform == "sunos5":
153 # use /usr/ccs/bin/dump on solaris
154 def _get_soname(f):
155 if not f:
156 return None
157
158 try:
159 proc = subprocess.Popen(("/usr/ccs/bin/dump", "-Lpv", f),
160 stdout=subprocess.PIPE,
161 stderr=subprocess.DEVNULL)
162 except OSError: # E.g. command not found
163 return None
164 with proc:
165 data = proc.stdout.read()
166 res = re.search(br'\[.*\]\sSONAME\s+([^\s]+)', data)
167 if not res:
168 return None
169 return os.fsdecode(res.group(1))
170 else:
171 def _get_soname(f):
172 # assuming GNU binutils / ELF
173 if not f:
174 return None
175 objdump = shutil.which('objdump')
176 if not objdump:
177 # objdump is not available, give up
178 return None
179
180 try:
181 proc = subprocess.Popen((objdump, '-p', '-j', '.dynamic', f),
182 stdout=subprocess.PIPE,
183 stderr=subprocess.DEVNULL)
184 except OSError: # E.g. bad executable
185 return None
186 with proc:
187 dump = proc.stdout.read()
188 res = re.search(br'\sSONAME\s+([^\s]+)', dump)
189 if not res:
190 return None
191 return os.fsdecode(res.group(1))
192
193 if sys.platform.startswith(("freebsd", "openbsd", "dragonfly")):
194
195 def _num_version(libname):
196 # "libxyz.so.MAJOR.MINOR" => [ MAJOR, MINOR ]
197 parts = libname.split(b".")
198 nums = []
199 try:
200 while parts:
201 nums.insert(0, int(parts.pop()))
202 except ValueError:
203 pass
204 return nums or [sys.maxsize]
205
206 def find_library(name):
207 ename = re.escape(name)
208 expr = r':-l%s\.\S+ => \S*/(lib%s\.\S+)' % (ename, ename)
209 expr = os.fsencode(expr)
210
211 try:
212 proc = subprocess.Popen(('/sbin/ldconfig', '-r'),
213 stdout=subprocess.PIPE,
214 stderr=subprocess.DEVNULL)
215 except OSError: # E.g. command not found
216 data = b''
217 else:
218 with proc:
219 data = proc.stdout.read()
220
221 res = re.findall(expr, data)
222 if not res:
223 return _get_soname(_findLib_gcc(name))
224 res.sort(key=_num_version)
225 return os.fsdecode(res[-1])
226
227 elif sys.platform == "sunos5":
228
229 def _findLib_crle(name, is64):
230 if not os.path.exists('/usr/bin/crle'):
231 return None
232
233 env = dict(os.environ)
234 env['LC_ALL'] = 'C'
235
236 if is64:
237 args = ('/usr/bin/crle', '-64')
238 else:
239 args = ('/usr/bin/crle',)
240
241 paths = None
242 try:
243 proc = subprocess.Popen(args,
244 stdout=subprocess.PIPE,
245 stderr=subprocess.DEVNULL,
246 env=env)
247 except OSError: # E.g. bad executable
248 return None
249 with proc:
250 for line in proc.stdout:
251 line = line.strip()
252 if line.startswith(b'Default Library Path (ELF):'):
253 paths = os.fsdecode(line).split()[4]
254
255 if not paths:
256 return None
257
258 for dir in paths.split(":"):
259 libfile = os.path.join(dir, "lib%s.so" % name)
260 if os.path.exists(libfile):
261 return libfile
262
263 return None
264
265 def find_library(name, is64 = False):
266 return _get_soname(_findLib_crle(name, is64) or _findLib_gcc(name))
267
268 else:
269
270 def _findSoname_ldconfig(name):
271 import struct
272 if struct.calcsize('l') == 4:
273 machine = os.uname().machine + '-32'
274 else:
275 machine = os.uname().machine + '-64'
276 mach_map = {
277 'x86_64-64': 'libc6,x86-64',
278 'ppc64-64': 'libc6,64bit',
279 'sparc64-64': 'libc6,64bit',
280 's390x-64': 'libc6,64bit',
281 'ia64-64': 'libc6,IA-64',
282 }
283 abi_type = mach_map.get(machine, 'libc6')
284
285 # XXX assuming GLIBC's ldconfig (with option -p)
286 regex = r'\s+(lib%s\.[^\s]+)\s+\(%s'
287 regex = os.fsencode(regex % (re.escape(name), abi_type))
288 try:
289 with subprocess.Popen(['/sbin/ldconfig', '-p'],
290 stdin=subprocess.DEVNULL,
291 stderr=subprocess.DEVNULL,
292 stdout=subprocess.PIPE,
293 env={'LC_ALL': 'C', 'LANG': 'C'}) as p:
294 res = re.search(regex, p.stdout.read())
295 if res:
296 return os.fsdecode(res.group(1))
297 except OSError:
298 pass
299
300 def _findLib_ld(name):
301 # See issue #9998 for why this is needed
302 expr = r'[^\(\)\s]*lib%s\.[^\(\)\s]*' % re.escape(name)
303 cmd = ['ld', '-t']
304 libpath = os.environ.get('LD_LIBRARY_PATH')
305 if libpath:
306 for d in libpath.split(':'):
307 cmd.extend(['-L', d])
308 cmd.extend(['-o', os.devnull, '-l%s' % name])
309 result = None
310 try:
311 p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
312 stderr=subprocess.PIPE,
313 universal_newlines=True)
314 out, _ = p.communicate()
315 res = re.findall(expr, os.fsdecode(out))
316 for file in res:
317 # Check if the given file is an elf file: gcc can report
318 # some files that are linker scripts and not actual
319 # shared objects. See bpo-41976 for more details
320 if not _is_elf(file):
321 continue
322 return os.fsdecode(file)
323 except Exception:
324 pass # result will be None
325 return result
326
327 def find_library(name):
328 # See issue #9998
329 return _findSoname_ldconfig(name) or \
330 _get_soname(_findLib_gcc(name)) or _get_soname(_findLib_ld(name))
331
332 ################################################################
333 # test code
334
335 def test():
336 from ctypes import cdll
337 if os.name == "nt":
338 print(cdll.msvcrt)
339 print(cdll.load("msvcrt"))
340 print(find_library("msvcrt"))
341
342 if os.name == "posix":
343 # find and load_version
344 print(find_library("m"))
345 print(find_library("c"))
346 print(find_library("bz2"))
347
348 # load
349 if sys.platform == "darwin":
350 print(cdll.LoadLibrary("libm.dylib"))
351 print(cdll.LoadLibrary("libcrypto.dylib"))
352 print(cdll.LoadLibrary("libSystem.dylib"))
353 print(cdll.LoadLibrary("System.framework/System"))
354 # issue-26439 - fix broken test call for AIX
355 elif sys.platform.startswith("aix"):
356 from ctypes import CDLL
357 if sys.maxsize < 2**32:
358 print(f"Using CDLL(name, os.RTLD_MEMBER): {CDLL('libc.a(shr.o)', os.RTLD_MEMBER)}")
359 print(f"Using cdll.LoadLibrary(): {cdll.LoadLibrary('libc.a(shr.o)')}")
360 # librpm.so is only available as 32-bit shared library
361 print(find_library("rpm"))
362 print(cdll.LoadLibrary("librpm.so"))
363 else:
364 print(f"Using CDLL(name, os.RTLD_MEMBER): {CDLL('libc.a(shr_64.o)', os.RTLD_MEMBER)}")
365 print(f"Using cdll.LoadLibrary(): {cdll.LoadLibrary('libc.a(shr_64.o)')}")
366 print(f"crypt\t:: {find_library('crypt')}")
367 print(f"crypt\t:: {cdll.LoadLibrary(find_library('crypt'))}")
368 print(f"crypto\t:: {find_library('crypto')}")
369 print(f"crypto\t:: {cdll.LoadLibrary(find_library('crypto'))}")
370 else:
371 print(cdll.LoadLibrary("libm.so"))
372 print(cdll.LoadLibrary("libcrypt.so"))
373 print(find_library("crypt"))
374
375 if __name__ == "__main__":
376 test()