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