mirror of
https://github.com/python/cpython.git
synced 2025-11-01 14:11:41 +00:00
Replace os.popen() with subprocess.Popen. Based on patch by Victor Stinner. If the "gcc", "cc" or "objdump" command is not available, the code was supposed to raise an OSError exception. But there was a bug in the code. The shell code returns the exit code 10 if the required command is missing, and the code tries to check for the status 10. The problem is that os.popen() doesn't return the exit code directly, but a status which should be processed by os.WIFEXITED() and os.WEXITSTATUS(). In practice, the exception was never raised. The OSError exception was not documented and ctypes.util.find_library() is expected to return None if the library is not found.
307 lines
10 KiB
Python
307 lines
10 KiB
Python
import os
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
|
|
# find_library(name) returns the pathname of a library, or None.
|
|
if os.name == "nt":
|
|
|
|
def _get_build_version():
|
|
"""Return the version of MSVC that was used to build Python.
|
|
|
|
For Python 2.3 and up, the version number is included in
|
|
sys.version. For earlier versions, assume the compiler is MSVC 6.
|
|
"""
|
|
# This function was copied from Lib/distutils/msvccompiler.py
|
|
prefix = "MSC v."
|
|
i = sys.version.find(prefix)
|
|
if i == -1:
|
|
return 6
|
|
i = i + len(prefix)
|
|
s, rest = sys.version[i:].split(" ", 1)
|
|
majorVersion = int(s[:-2]) - 6
|
|
if majorVersion >= 13:
|
|
majorVersion += 1
|
|
minorVersion = int(s[2:3]) / 10.0
|
|
# I don't think paths are affected by minor version in version 6
|
|
if majorVersion == 6:
|
|
minorVersion = 0
|
|
if majorVersion >= 6:
|
|
return majorVersion + minorVersion
|
|
# else we don't know what version of the compiler this is
|
|
return None
|
|
|
|
def find_msvcrt():
|
|
"""Return the name of the VC runtime dll"""
|
|
version = _get_build_version()
|
|
if version is None:
|
|
# better be safe than sorry
|
|
return None
|
|
if version <= 6:
|
|
clibname = 'msvcrt'
|
|
elif version <= 13:
|
|
clibname = 'msvcr%d' % (version * 10)
|
|
else:
|
|
# CRT is no longer directly loadable. See issue23606 for the
|
|
# discussion about alternative approaches.
|
|
return None
|
|
|
|
# If python was built with in debug mode
|
|
import importlib.machinery
|
|
if '_d.pyd' in importlib.machinery.EXTENSION_SUFFIXES:
|
|
clibname += 'd'
|
|
return clibname+'.dll'
|
|
|
|
def find_library(name):
|
|
if name in ('c', 'm'):
|
|
return find_msvcrt()
|
|
# See MSDN for the REAL search order.
|
|
for directory in os.environ['PATH'].split(os.pathsep):
|
|
fname = os.path.join(directory, name)
|
|
if os.path.isfile(fname):
|
|
return fname
|
|
if fname.lower().endswith(".dll"):
|
|
continue
|
|
fname = fname + ".dll"
|
|
if os.path.isfile(fname):
|
|
return fname
|
|
return None
|
|
|
|
if os.name == "ce":
|
|
# search path according to MSDN:
|
|
# - absolute path specified by filename
|
|
# - The .exe launch directory
|
|
# - the Windows directory
|
|
# - ROM dll files (where are they?)
|
|
# - OEM specified search path: HKLM\Loader\SystemPath
|
|
def find_library(name):
|
|
return name
|
|
|
|
if os.name == "posix" and sys.platform == "darwin":
|
|
from ctypes.macholib.dyld import dyld_find as _dyld_find
|
|
def find_library(name):
|
|
possible = ['lib%s.dylib' % name,
|
|
'%s.dylib' % name,
|
|
'%s.framework/%s' % (name, name)]
|
|
for name in possible:
|
|
try:
|
|
return _dyld_find(name)
|
|
except ValueError:
|
|
continue
|
|
return None
|
|
|
|
elif os.name == "posix":
|
|
# Andreas Degert's find functions, using gcc, /sbin/ldconfig, objdump
|
|
import re, tempfile
|
|
|
|
def _findLib_gcc(name):
|
|
# Run GCC's linker with the -t (aka --trace) option and examine the
|
|
# library name it prints out. The GCC command will fail because we
|
|
# haven't supplied a proper program with main(), but that does not
|
|
# matter.
|
|
expr = os.fsencode(r'[^\(\)\s]*lib%s\.[^\(\)\s]*' % re.escape(name))
|
|
|
|
c_compiler = shutil.which('gcc')
|
|
if not c_compiler:
|
|
c_compiler = shutil.which('cc')
|
|
if not c_compiler:
|
|
# No C compiler available, give up
|
|
return None
|
|
|
|
temp = tempfile.NamedTemporaryFile()
|
|
try:
|
|
args = [c_compiler, '-Wl,-t', '-o', temp.name, '-l' + name]
|
|
|
|
env = dict(os.environ)
|
|
env['LC_ALL'] = 'C'
|
|
env['LANG'] = 'C'
|
|
proc = subprocess.Popen(args,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.STDOUT,
|
|
env=env)
|
|
with proc:
|
|
trace = proc.stdout.read()
|
|
finally:
|
|
try:
|
|
temp.close()
|
|
except FileNotFoundError:
|
|
# Raised if the file was already removed, which is the normal
|
|
# behaviour of GCC if linking fails
|
|
pass
|
|
res = re.search(expr, trace)
|
|
if not res:
|
|
return None
|
|
return os.fsdecode(res.group(0))
|
|
|
|
|
|
if sys.platform == "sunos5":
|
|
# use /usr/ccs/bin/dump on solaris
|
|
def _get_soname(f):
|
|
if not f:
|
|
return None
|
|
|
|
proc = subprocess.Popen(("/usr/ccs/bin/dump", "-Lpv", f),
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.DEVNULL)
|
|
with proc:
|
|
data = proc.stdout.read()
|
|
res = re.search(br'\[.*\]\sSONAME\s+([^\s]+)', data)
|
|
if not res:
|
|
return None
|
|
return os.fsdecode(res.group(1))
|
|
else:
|
|
def _get_soname(f):
|
|
# assuming GNU binutils / ELF
|
|
if not f:
|
|
return None
|
|
objdump = shutil.which('objdump')
|
|
if not objdump:
|
|
# objdump is not available, give up
|
|
return None
|
|
|
|
proc = subprocess.Popen((objdump, '-p', '-j', '.dynamic', f),
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.DEVNULL)
|
|
with proc:
|
|
dump = proc.stdout.read()
|
|
res = re.search(br'\sSONAME\s+([^\s]+)', dump)
|
|
if not res:
|
|
return None
|
|
return os.fsdecode(res.group(1))
|
|
|
|
if sys.platform.startswith(("freebsd", "openbsd", "dragonfly")):
|
|
|
|
def _num_version(libname):
|
|
# "libxyz.so.MAJOR.MINOR" => [ MAJOR, MINOR ]
|
|
parts = libname.split(b".")
|
|
nums = []
|
|
try:
|
|
while parts:
|
|
nums.insert(0, int(parts.pop()))
|
|
except ValueError:
|
|
pass
|
|
return nums or [sys.maxsize]
|
|
|
|
def find_library(name):
|
|
ename = re.escape(name)
|
|
expr = r':-l%s\.\S+ => \S*/(lib%s\.\S+)' % (ename, ename)
|
|
expr = os.fsencode(expr)
|
|
|
|
proc = subprocess.Popen(('/sbin/ldconfig', '-r'),
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.DEVNULL)
|
|
with proc:
|
|
data = proc.stdout.read()
|
|
|
|
res = re.findall(expr, data)
|
|
if not res:
|
|
return _get_soname(_findLib_gcc(name))
|
|
res.sort(key=_num_version)
|
|
return os.fsdecode(res[-1])
|
|
|
|
elif sys.platform == "sunos5":
|
|
|
|
def _findLib_crle(name, is64):
|
|
if not os.path.exists('/usr/bin/crle'):
|
|
return None
|
|
|
|
env = dict(os.environ)
|
|
env['LC_ALL'] = 'C'
|
|
|
|
if is64:
|
|
args = ('/usr/bin/crle', '-64')
|
|
else:
|
|
args = ('/usr/bin/crle',)
|
|
|
|
paths = None
|
|
proc = subprocess.Popen(args,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.DEVNULL,
|
|
env=env)
|
|
with proc:
|
|
for line in proc.stdout:
|
|
line = line.strip()
|
|
if line.startswith(b'Default Library Path (ELF):'):
|
|
paths = os.fsdecode(line).split()[4]
|
|
|
|
if not paths:
|
|
return None
|
|
|
|
for dir in paths.split(":"):
|
|
libfile = os.path.join(dir, "lib%s.so" % name)
|
|
if os.path.exists(libfile):
|
|
return libfile
|
|
|
|
return None
|
|
|
|
def find_library(name, is64 = False):
|
|
return _get_soname(_findLib_crle(name, is64) or _findLib_gcc(name))
|
|
|
|
else:
|
|
|
|
def _findSoname_ldconfig(name):
|
|
import struct
|
|
if struct.calcsize('l') == 4:
|
|
machine = os.uname().machine + '-32'
|
|
else:
|
|
machine = os.uname().machine + '-64'
|
|
mach_map = {
|
|
'x86_64-64': 'libc6,x86-64',
|
|
'ppc64-64': 'libc6,64bit',
|
|
'sparc64-64': 'libc6,64bit',
|
|
's390x-64': 'libc6,64bit',
|
|
'ia64-64': 'libc6,IA-64',
|
|
}
|
|
abi_type = mach_map.get(machine, 'libc6')
|
|
|
|
# XXX assuming GLIBC's ldconfig (with option -p)
|
|
regex = os.fsencode(
|
|
'\s+(lib%s\.[^\s]+)\s+\(%s' % (re.escape(name), abi_type))
|
|
try:
|
|
with subprocess.Popen(['/sbin/ldconfig', '-p'],
|
|
stdin=subprocess.DEVNULL,
|
|
stderr=subprocess.DEVNULL,
|
|
stdout=subprocess.PIPE,
|
|
env={'LC_ALL': 'C', 'LANG': 'C'}) as p:
|
|
res = re.search(regex, p.stdout.read())
|
|
if res:
|
|
return os.fsdecode(res.group(1))
|
|
except OSError:
|
|
pass
|
|
|
|
def find_library(name):
|
|
return _findSoname_ldconfig(name) or _get_soname(_findLib_gcc(name))
|
|
|
|
################################################################
|
|
# test code
|
|
|
|
def test():
|
|
from ctypes import cdll
|
|
if os.name == "nt":
|
|
print(cdll.msvcrt)
|
|
print(cdll.load("msvcrt"))
|
|
print(find_library("msvcrt"))
|
|
|
|
if os.name == "posix":
|
|
# find and load_version
|
|
print(find_library("m"))
|
|
print(find_library("c"))
|
|
print(find_library("bz2"))
|
|
|
|
# getattr
|
|
## print cdll.m
|
|
## print cdll.bz2
|
|
|
|
# load
|
|
if sys.platform == "darwin":
|
|
print(cdll.LoadLibrary("libm.dylib"))
|
|
print(cdll.LoadLibrary("libcrypto.dylib"))
|
|
print(cdll.LoadLibrary("libSystem.dylib"))
|
|
print(cdll.LoadLibrary("System.framework/System"))
|
|
else:
|
|
print(cdll.LoadLibrary("libm.so"))
|
|
print(cdll.LoadLibrary("libcrypt.so"))
|
|
print(find_library("crypt"))
|
|
|
|
if __name__ == "__main__":
|
|
test()
|