Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
allendowney
GitHub Repository: allendowney/cpython
Path: blob/main/Lib/ctypes/util.py
12 views
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()
377
378