1import os 2import shutil 3import subprocess 4import sys 5 6# find_library(name) returns the pathname of a library, or None. 7if 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 70elif os.name == "posix" and sys.platform in {"darwin", "ios", "tvos", "watchos"}: 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 83elif 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 92elif sys.platform == "android": 93 def find_library(name): 94 directory = "/system/lib" 95 if "64" in os.uname().machine: 96 directory += "64" 97 98 fname = f"{directory}/lib{name}.so" 99 return fname if os.path.isfile(fname) else None 100 101elif os.name == "posix": 102 # Andreas Degert's find functions, using gcc, /sbin/ldconfig, objdump 103 import re, tempfile 104 105 def _is_elf(filename): 106 "Return True if the given file is an ELF file" 107 elf_header = b'\x7fELF' 108 try: 109 with open(filename, 'br') as thefile: 110 return thefile.read(4) == elf_header 111 except FileNotFoundError: 112 return False 113 114 def _findLib_gcc(name): 115 # Run GCC's linker with the -t (aka --trace) option and examine the 116 # library name it prints out. The GCC command will fail because we 117 # haven't supplied a proper program with main(), but that does not 118 # matter. 119 expr = os.fsencode(r'[^\(\)\s]*lib%s\.[^\(\)\s]*' % re.escape(name)) 120 121 c_compiler = shutil.which('gcc') 122 if not c_compiler: 123 c_compiler = shutil.which('cc') 124 if not c_compiler: 125 # No C compiler available, give up 126 return None 127 128 temp = tempfile.NamedTemporaryFile() 129 try: 130 args = [c_compiler, '-Wl,-t', '-o', temp.name, '-l' + name] 131 132 env = dict(os.environ) 133 env['LC_ALL'] = 'C' 134 env['LANG'] = 'C' 135 try: 136 proc = subprocess.Popen(args, 137 stdout=subprocess.PIPE, 138 stderr=subprocess.STDOUT, 139 env=env) 140 except OSError: # E.g. bad executable 141 return None 142 with proc: 143 trace = proc.stdout.read() 144 finally: 145 try: 146 temp.close() 147 except FileNotFoundError: 148 # Raised if the file was already removed, which is the normal 149 # behaviour of GCC if linking fails 150 pass 151 res = re.findall(expr, trace) 152 if not res: 153 return None 154 155 for file in res: 156 # Check if the given file is an elf file: gcc can report 157 # some files that are linker scripts and not actual 158 # shared objects. See bpo-41976 for more details 159 if not _is_elf(file): 160 continue 161 return os.fsdecode(file) 162 163 164 if sys.platform == "sunos5": 165 # use /usr/ccs/bin/dump on solaris 166 def _get_soname(f): 167 if not f: 168 return None 169 170 try: 171 proc = subprocess.Popen(("/usr/ccs/bin/dump", "-Lpv", f), 172 stdout=subprocess.PIPE, 173 stderr=subprocess.DEVNULL) 174 except OSError: # E.g. command not found 175 return None 176 with proc: 177 data = proc.stdout.read() 178 res = re.search(br'\[.*\]\sSONAME\s+([^\s]+)', data) 179 if not res: 180 return None 181 return os.fsdecode(res.group(1)) 182 else: 183 def _get_soname(f): 184 # assuming GNU binutils / ELF 185 if not f: 186 return None 187 objdump = shutil.which('objdump') 188 if not objdump: 189 # objdump is not available, give up 190 return None 191 192 try: 193 proc = subprocess.Popen((objdump, '-p', '-j', '.dynamic', f), 194 stdout=subprocess.PIPE, 195 stderr=subprocess.DEVNULL) 196 except OSError: # E.g. bad executable 197 return None 198 with proc: 199 dump = proc.stdout.read() 200 res = re.search(br'\sSONAME\s+([^\s]+)', dump) 201 if not res: 202 return None 203 return os.fsdecode(res.group(1)) 204 205 if sys.platform.startswith(("freebsd", "openbsd", "dragonfly")): 206 207 def _num_version(libname): 208 # "libxyz.so.MAJOR.MINOR" => [ MAJOR, MINOR ] 209 parts = libname.split(b".") 210 nums = [] 211 try: 212 while parts: 213 nums.insert(0, int(parts.pop())) 214 except ValueError: 215 pass 216 return nums or [sys.maxsize] 217 218 def find_library(name): 219 ename = re.escape(name) 220 expr = r':-l%s\.\S+ => \S*/(lib%s\.\S+)' % (ename, ename) 221 expr = os.fsencode(expr) 222 223 try: 224 proc = subprocess.Popen(('/sbin/ldconfig', '-r'), 225 stdout=subprocess.PIPE, 226 stderr=subprocess.DEVNULL) 227 except OSError: # E.g. command not found 228 data = b'' 229 else: 230 with proc: 231 data = proc.stdout.read() 232 233 res = re.findall(expr, data) 234 if not res: 235 return _get_soname(_findLib_gcc(name)) 236 res.sort(key=_num_version) 237 return os.fsdecode(res[-1]) 238 239 elif sys.platform == "sunos5": 240 241 def _findLib_crle(name, is64): 242 if not os.path.exists('/usr/bin/crle'): 243 return None 244 245 env = dict(os.environ) 246 env['LC_ALL'] = 'C' 247 248 if is64: 249 args = ('/usr/bin/crle', '-64') 250 else: 251 args = ('/usr/bin/crle',) 252 253 paths = None 254 try: 255 proc = subprocess.Popen(args, 256 stdout=subprocess.PIPE, 257 stderr=subprocess.DEVNULL, 258 env=env) 259 except OSError: # E.g. bad executable 260 return None 261 with proc: 262 for line in proc.stdout: 263 line = line.strip() 264 if line.startswith(b'Default Library Path (ELF):'): 265 paths = os.fsdecode(line).split()[4] 266 267 if not paths: 268 return None 269 270 for dir in paths.split(":"): 271 libfile = os.path.join(dir, "lib%s.so" % name) 272 if os.path.exists(libfile): 273 return libfile 274 275 return None 276 277 def find_library(name, is64 = False): 278 return _get_soname(_findLib_crle(name, is64) or _findLib_gcc(name)) 279 280 else: 281 282 def _findSoname_ldconfig(name): 283 import struct 284 if struct.calcsize('l') == 4: 285 machine = os.uname().machine + '-32' 286 else: 287 machine = os.uname().machine + '-64' 288 mach_map = { 289 'x86_64-64': 'libc6,x86-64', 290 'ppc64-64': 'libc6,64bit', 291 'sparc64-64': 'libc6,64bit', 292 's390x-64': 'libc6,64bit', 293 'ia64-64': 'libc6,IA-64', 294 } 295 abi_type = mach_map.get(machine, 'libc6') 296 297 # XXX assuming GLIBC's ldconfig (with option -p) 298 regex = r'\s+(lib%s\.[^\s]+)\s+\(%s' 299 regex = os.fsencode(regex % (re.escape(name), abi_type)) 300 try: 301 with subprocess.Popen(['/sbin/ldconfig', '-p'], 302 stdin=subprocess.DEVNULL, 303 stderr=subprocess.DEVNULL, 304 stdout=subprocess.PIPE, 305 env={'LC_ALL': 'C', 'LANG': 'C'}) as p: 306 res = re.search(regex, p.stdout.read()) 307 if res: 308 return os.fsdecode(res.group(1)) 309 except OSError: 310 pass 311 312 def _findLib_ld(name): 313 # See issue #9998 for why this is needed 314 expr = r'[^\(\)\s]*lib%s\.[^\(\)\s]*' % re.escape(name) 315 cmd = ['ld', '-t'] 316 libpath = os.environ.get('LD_LIBRARY_PATH') 317 if libpath: 318 for d in libpath.split(':'): 319 cmd.extend(['-L', d]) 320 cmd.extend(['-o', os.devnull, '-l%s' % name]) 321 result = None 322 try: 323 p = subprocess.Popen(cmd, stdout=subprocess.PIPE, 324 stderr=subprocess.PIPE, 325 universal_newlines=True) 326 out, _ = p.communicate() 327 res = re.findall(expr, os.fsdecode(out)) 328 for file in res: 329 # Check if the given file is an elf file: gcc can report 330 # some files that are linker scripts and not actual 331 # shared objects. See bpo-41976 for more details 332 if not _is_elf(file): 333 continue 334 return os.fsdecode(file) 335 except Exception: 336 pass # result will be None 337 return result 338 339 def find_library(name): 340 # See issue #9998 341 return _findSoname_ldconfig(name) or \ 342 _get_soname(_findLib_gcc(name)) or _get_soname(_findLib_ld(name)) 343 344################################################################ 345# test code 346 347def test(): 348 from ctypes import cdll 349 if os.name == "nt": 350 print(cdll.msvcrt) 351 print(cdll.load("msvcrt")) 352 print(find_library("msvcrt")) 353 354 if os.name == "posix": 355 # find and load_version 356 print(find_library("m")) 357 print(find_library("c")) 358 print(find_library("bz2")) 359 360 # load 361 if sys.platform == "darwin": 362 print(cdll.LoadLibrary("libm.dylib")) 363 print(cdll.LoadLibrary("libcrypto.dylib")) 364 print(cdll.LoadLibrary("libSystem.dylib")) 365 print(cdll.LoadLibrary("System.framework/System")) 366 # issue-26439 - fix broken test call for AIX 367 elif sys.platform.startswith("aix"): 368 from ctypes import CDLL 369 if sys.maxsize < 2**32: 370 print(f"Using CDLL(name, os.RTLD_MEMBER): {CDLL('libc.a(shr.o)', os.RTLD_MEMBER)}") 371 print(f"Using cdll.LoadLibrary(): {cdll.LoadLibrary('libc.a(shr.o)')}") 372 # librpm.so is only available as 32-bit shared library 373 print(find_library("rpm")) 374 print(cdll.LoadLibrary("librpm.so")) 375 else: 376 print(f"Using CDLL(name, os.RTLD_MEMBER): {CDLL('libc.a(shr_64.o)', os.RTLD_MEMBER)}") 377 print(f"Using cdll.LoadLibrary(): {cdll.LoadLibrary('libc.a(shr_64.o)')}") 378 print(f"crypt\t:: {find_library('crypt')}") 379 print(f"crypt\t:: {cdll.LoadLibrary(find_library('crypt'))}") 380 print(f"crypto\t:: {find_library('crypto')}") 381 print(f"crypto\t:: {cdll.LoadLibrary(find_library('crypto'))}") 382 else: 383 print(cdll.LoadLibrary("libm.so")) 384 print(cdll.LoadLibrary("libcrypt.so")) 385 print(find_library("crypt")) 386 387if __name__ == "__main__": 388 test() 389