# -*- coding: utf-8 -*- # # Copyright (C) 2013-2023 Vinay Sajip. # Licensed to the Python Software Foundation under a contributor agreement. # See LICENSE.txt and CONTRIBUTORS.txt. # from __future__ import unicode_literals
import base64 import codecs import datetime from email import message_from_file import hashlib import json import logging import os import posixpath import re import shutil import sys import tempfile import zipfile
from . import __version__, DistlibException from .compat import sysconfig, ZipFile, fsdecode, text_type, filter from .database import InstalledDistribution from .metadata import Metadata, WHEEL_METADATA_FILENAME, LEGACY_METADATA_FILENAME from .util import (FileOperator, convert_path, CSVReader, CSVWriter, Cache, cached_property, get_cache_base,
read_exports, tempdir, get_platform) from .version import NormalizedVersion, UnsupportedVersionError
logger = logging.getLogger(__name__)
cache = None# created when needed
if hasattr(sys, 'pypy_version_info'): # pragma: no cover
IMP_PREFIX = 'pp' elif sys.platform.startswith('java'): # pragma: no cover
IMP_PREFIX = 'jy' elif sys.platform == 'cli': # pragma: no cover
IMP_PREFIX = 'ip' else:
IMP_PREFIX = 'cp'
ABI = sysconfig.get_config_var('SOABI') if ABI and ABI.startswith('cpython-'):
ABI = ABI.replace('cpython-', 'cp').split('-')[0] else:
def _derive_abi():
parts = ['cp', VER_SUFFIX] if sysconfig.get_config_var('Py_DEBUG'):
parts.append('d') if IMP_PREFIX == 'cp':
vi = sys.version_info[:2] if vi < (3, 8):
wpm = sysconfig.get_config_var('WITH_PYMALLOC') if wpm isNone:
wpm = True if wpm:
parts.append('m') if vi < (3, 3):
us = sysconfig.get_config_var('Py_UNICODE_SIZE') if us == 4or (us isNoneand sys.maxunicode == 0x10FFFF):
parts.append('u') return''.join(parts)
def remove(self, pathname):
extensions = self.impure_wheels.pop(pathname) for k, v in extensions: if k in self.libs: del self.libs[k]
def find_module(self, fullname, path=None): if fullname in self.libs:
result = self else:
result = None return result
def load_module(self, fullname): if fullname in sys.modules:
result = sys.modules[fullname] else: if fullname notin self.libs: raise ImportError('unable to find extension for %s' % fullname)
result = _load_dynamic(fullname, self.libs[fullname])
result.__loader__ = self
parts = fullname.rsplit('.', 1) if len(parts) > 1:
result.__package__ = parts[0] return result
_hook = Mounter()
class Wheel(object): """ Class to build and install from Wheel files (PEP 427). """
wheel_version = (1, 1)
hash_kind = 'sha256'
def __init__(self, filename=None, sign=False, verify=False): """
Initialise an instance using a (valid) filename. """
self.sign = sign
self.should_verify = verify
self.buildver = ''
self.pyver = [PYVER]
self.abi = ['none']
self.arch = ['any']
self.dirname = os.getcwd() if filename isNone:
self.name = 'dummy'
self.version = '0.1'
self._filename = self.filename else:
m = NAME_VERSION_RE.match(filename) if m:
info = m.groupdict('')
self.name = info['nm'] # Reinstate the local version separator
self.version = info['vn'].replace('_', '-')
self.buildver = info['bn']
self._filename = self.filename else:
dirname, filename = os.path.split(filename)
m = FILENAME_RE.match(filename) ifnot m: raise DistlibException('Invalid name or ' 'filename: %r' % filename) if dirname:
self.dirname = os.path.abspath(dirname)
self._filename = filename
info = m.groupdict('')
self.name = info['nm']
self.version = info['vn']
self.buildver = info['bn']
self.pyver = info['py'].split('.')
self.abi = info['bi'].split('.')
self.arch = info['ar'].split('.')
@property def filename(self): """
Build andreturn a filename from the various components. """ if self.buildver:
buildver = '-' + self.buildver else:
buildver = ''
pyver = '.'.join(self.pyver)
abi = '.'.join(self.abi)
arch = '.'.join(self.arch) # replace - with _ as a local version separator
version = self.version.replace('-', '_') return'%s-%s%s-%s-%s-%s.whl' % (self.name, version, buildver, pyver, abi, arch)
@cached_property def info(self):
pathname = os.path.join(self.dirname, self.filename) with ZipFile(pathname, 'r') as zf:
result = self.get_wheel_metadata(zf) return result
def process_shebang(self, data):
m = SHEBANG_RE.match(data) if m:
end = m.end()
shebang, data_after_shebang = data[:end], data[end:] # Preserve any arguments after the interpreter if b'pythonw'in shebang.lower():
shebang_python = SHEBANG_PYTHONW else:
shebang_python = SHEBANG_PYTHON
m = SHEBANG_DETAIL_RE.match(shebang) if m:
args = b' ' + m.groups()[-1] else:
args = b''
shebang = shebang_python + args
data = shebang + data_after_shebang else:
cr = data.find(b'\r')
lf = data.find(b'\n') if cr < 0or cr > lf:
term = b'\n' else: if data[cr:cr + 2] == b'\r\n':
term = b'\r\n' else:
term = b'\r'
data = SHEBANG_PYTHON + term + data return data
def get_hash(self, data, hash_kind=None): if hash_kind isNone:
hash_kind = self.hash_kind try:
hasher = getattr(hashlib, hash_kind) except AttributeError: raise DistlibException('Unsupported hash algorithm: %r' % hash_kind)
result = hasher(data).digest()
result = base64.urlsafe_b64encode(result).rstrip(b'=').decode('ascii') return hash_kind, result
def write_record(self, records, record_path, archive_record_path):
records = list(records) # make a copy, as mutated
records.append((archive_record_path, '', '')) with CSVWriter(record_path) as writer: for row in records:
writer.writerow(row)
def write_records(self, info, libdir, archive_paths):
records = []
distinfo, info_dir = info # hasher = getattr(hashlib, self.hash_kind) for ap, p in archive_paths: with open(p, 'rb') as f:
data = f.read()
digest = '%s=%s' % self.get_hash(data)
size = os.path.getsize(p)
records.append((ap, digest, size))
p = os.path.join(distinfo, 'RECORD')
ap = to_posix(os.path.join(info_dir, 'RECORD'))
self.write_record(records, p, ap)
archive_paths.append((ap, p))
def build_zip(self, pathname, archive_paths): with ZipFile(pathname, 'w', zipfile.ZIP_DEFLATED) as zf: for ap, p in archive_paths:
logger.debug('Wrote %s to %s in wheel', p, ap)
zf.write(p, ap)
def build(self, paths, tags=None, wheel_version=None): """
Build a wheel from files in specified paths, and use any specified tags
when determining the name of the wheel. """ if tags isNone:
tags = {}
# First, stuff which is not in site-packages for key in ('data', 'headers', 'scripts'): if key notin paths: continue
path = paths[key] if os.path.isdir(path): for root, dirs, files in os.walk(path): for fn in files:
p = fsdecode(os.path.join(root, fn))
rp = os.path.relpath(p, path)
ap = to_posix(os.path.join(data_dir, key, rp))
archive_paths.append((ap, p)) if key == 'scripts'andnot p.endswith('.exe'): with open(p, 'rb') as f:
data = f.read()
data = self.process_shebang(data) with open(p, 'wb') as f:
f.write(data)
# Now, stuff which is in site-packages, other than the # distinfo stuff.
path = libdir
distinfo = None for root, dirs, files in os.walk(path): if root == path: # At the top level only, save distinfo for later # and skip it for now for i, dn in enumerate(dirs):
dn = fsdecode(dn) if dn.endswith('.dist-info'):
distinfo = os.path.join(root, dn) del dirs[i] break assert distinfo, '.dist-info directory expected, not found'
for fn in files: # comment out next suite to leave .pyc files in if fsdecode(fn).endswith(('.pyc', '.pyo')): continue
p = os.path.join(root, fn)
rp = to_posix(os.path.relpath(p, path))
archive_paths.append((rp, p))
# Now distinfo. Assumed to be flat, i.e. os.listdir is enough.
files = os.listdir(distinfo) for fn in files: if fn notin ('RECORD', 'INSTALLER', 'SHARED', 'WHEEL'):
p = fsdecode(os.path.join(distinfo, fn))
ap = to_posix(os.path.join(info_dir, fn))
archive_paths.append((ap, p))
wheel_metadata = [ 'Wheel-Version: %d.%d' % (wheel_version or self.wheel_version), 'Generator: distlib %s' % __version__, 'Root-Is-Purelib: %s' % is_pure,
] for pyver, abi, arch in self.tags:
wheel_metadata.append('Tag: %s-%s-%s' % (pyver, abi, arch))
p = os.path.join(distinfo, 'WHEEL') with open(p, 'w') as f:
f.write('\n'.join(wheel_metadata))
ap = to_posix(os.path.join(info_dir, 'WHEEL'))
archive_paths.append((ap, p))
# sort the entries by archive path. Not needed by any spec, but it # keeps the archive listing and RECORD tidier than they would otherwise # be. Use the number of path segments to keep directory entries together, # and keep the dist-info stuff at the end. def sorter(t):
ap = t[0]
n = ap.count('/') if'.dist-info'in ap:
n += 10000 return (n, ap)
archive_paths = sorted(archive_paths, key=sorter)
# Now, at last, RECORD. # Paths in here are archive paths - nothing else makes sense.
self.write_records((distinfo, info_dir), libdir, archive_paths) # Now, ready to build the zip file
pathname = os.path.join(self.dirname, self.filename)
self.build_zip(pathname, archive_paths) return pathname
def install(self, paths, maker, **kwargs): """
Install a wheel to the specified paths. If kwarg ``warner`` is
specified, it should be a callable, which will be called with two
tuples indicating the wheel version of this software and the wheel
version in the file, if there is a discrepancy in the versions.
This can be used to issue any warnings to raise any exceptions. If kwarg ``lib_only`` isTrue, only the purelib/platlib files are
installed, and the headers, scripts, data and dist-info metadata are not written. If kwarg ``bytecode_hashed_invalidation`` isTrue, written
bytecode will try to use file-hash based invalidation (PEP-552) on
supported interpreter versions (CPython 3.7+).
The return value is a :class:`InstalledDistribution` instance unless
``options.lib_only`` isTrue, in which case the return value is ``None``. """
with ZipFile(pathname, 'r') as zf: with zf.open(wheel_metadata_name) as bwf:
wf = wrapper(bwf)
message = message_from_file(wf)
wv = message['Wheel-Version'].split('.', 1)
file_version = tuple([int(i) for i in wv]) if (file_version != self.wheel_version) and warner:
warner(self.wheel_version, file_version)
# make a new instance rather than a copy of maker's, # as we mutate it
fileop = FileOperator(dry_run=dry_run)
fileop.record = True# so we can rollback if needed
bc = not sys.dont_write_bytecode # Double negatives. Lovely!
outfiles = [] # for RECORD writing
# for script copying/shebang processing
workdir = tempfile.mkdtemp() # set target dir later # we default add_launchers to False, as the # Python Launcher should be used instead
maker.source_dir = workdir
maker.target_dir = None try: for zinfo in zf.infolist():
arcname = zinfo.filename if isinstance(arcname, text_type):
u_arcname = arcname else:
u_arcname = arcname.decode('utf-8') if self.skip_entry(u_arcname): continue
row = records[u_arcname] if row[2] and str(zinfo.file_size) != row[2]: raise DistlibException('size mismatch for ' '%s' % u_arcname) if row[1]:
kind, value = row[1].split('=', 1) with zf.open(arcname) as bf:
data = bf.read()
_, digest = self.get_hash(data, kind) if digest != value: raise DistlibException('digest mismatch for ' '%s' % arcname)
if lib_only and u_arcname.startswith((info_pfx, data_pfx)):
logger.debug('lib_only: skipping %s', u_arcname) continue
is_script = (u_arcname.startswith(script_pfx) andnot u_arcname.endswith('.exe'))
if u_arcname.startswith(data_pfx):
_, where, rp = u_arcname.split('/', 2)
outfile = os.path.join(paths[where], convert_path(rp)) else: # meant for site-packages. if u_arcname in (wheel_metadata_name, record_name): continue
outfile = os.path.join(libdir, convert_path(u_arcname)) ifnot is_script: with zf.open(arcname) as bf:
fileop.copy_stream(bf, outfile) # Issue #147: permission bits aren't preserved. Using # zf.extract(zinfo, libdir) should have worked, but didn't, # see https://www.thetopsites.net/article/53834422.shtml # So ... manually preserve permission bits as given in zinfo if os.name == 'posix': # just set the normal permission bits
os.chmod(outfile, (zinfo.external_attr >> 16) & 0x1FF)
outfiles.append(outfile) # Double check the digest of the written file ifnot dry_run and row[1]: with open(outfile, 'rb') as bf:
data = bf.read()
_, newdigest = self.get_hash(data, kind) if newdigest != digest: raise DistlibException('digest mismatch ' 'on write for ' '%s' % outfile) if bc and outfile.endswith('.py'): try:
pyc = fileop.byte_compile(outfile, hashed_invalidation=bc_hashed_invalidation)
outfiles.append(pyc) except Exception: # Don't give up if byte-compilation fails, # but log it and perhaps warn the user
logger.warning('Byte-compilation failed', exc_info=True) else:
fn = os.path.basename(convert_path(arcname))
workname = os.path.join(workdir, fn) with zf.open(arcname) as bf:
fileop.copy_stream(bf, workname)
# Try to get pydist.json so we can see if there are # any commands to generate. If this fails (e.g. because # of a legacy wheel), log a warning but don't give up.
commands = None
file_version = self.info['Wheel-Version'] if file_version == '1.0': # Use legacy info
ep = posixpath.join(info_dir, 'entry_points.txt') try: with zf.open(ep) as bwf:
epdata = read_exports(bwf)
commands = {} for key in ('console', 'gui'):
k = '%s_scripts' % key if k in epdata:
commands['wrap_%s' % key] = d = {} for v in epdata[k].values():
s = '%s:%s' % (v.prefix, v.suffix) if v.flags:
s += ' [%s]' % ','.join(v.flags)
d[v.name] = s except Exception:
logger.warning('Unable to read legacy script ' 'metadata, so cannot generate ' 'scripts') else: try: with zf.open(metadata_name) as bwf:
wf = wrapper(bwf)
commands = json.load(wf).get('extensions') if commands:
commands = commands.get('python.commands') except Exception:
logger.warning('Unable to read JSON metadata, so ' 'cannot generate scripts') if commands:
console_scripts = commands.get('wrap_console', {})
gui_scripts = commands.get('wrap_gui', {}) if console_scripts or gui_scripts:
script_dir = paths.get('scripts', '') ifnot os.path.isdir(script_dir): raise ValueError('Valid script path not ' 'specified')
maker.target_dir = script_dir for k, v in console_scripts.items():
script = '%s = %s' % (k, v)
filenames = maker.make(script)
fileop.set_executable_mode(filenames)
if gui_scripts:
options = {'gui': True} for k, v in gui_scripts.items():
script = '%s = %s' % (k, v)
filenames = maker.make(script, options)
fileop.set_executable_mode(filenames)
p = os.path.join(libdir, info_dir)
dist = InstalledDistribution(p)
# Write SHARED
paths = dict(paths) # don't change passed in dict del paths['purelib'] del paths['platlib']
paths['lib'] = libdir
p = dist.write_shared_locations(paths, dry_run) if p:
outfiles.append(p)
# Write RECORD
dist.write_installed_files(outfiles, paths['prefix'], dry_run) return dist except Exception: # pragma: no cover
logger.exception('installation failed.')
fileop.rollback() raise finally:
shutil.rmtree(workdir)
def _get_dylib_cache(self): global cache if cache isNone: # Use native string to avoid issues on 2.x: see Python #20140.
base = os.path.join(get_cache_base(), str('dylib-cache'), '%s.%s' % sys.version_info[:2])
cache = Cache(base) return cache
def _get_extensions(self):
pathname = os.path.join(self.dirname, self.filename)
name_ver = '%s-%s' % (self.name, self.version)
info_dir = '%s.dist-info' % name_ver
arcname = posixpath.join(info_dir, 'EXTENSIONS')
wrapper = codecs.getreader('utf-8')
result = [] with ZipFile(pathname, 'r') as zf: try: with zf.open(arcname) as bf:
wf = wrapper(bf)
extensions = json.load(wf)
cache = self._get_dylib_cache()
prefix = cache.prefix_to_dir(self.filename, use_abspath=False)
cache_base = os.path.join(cache.base, prefix) ifnot os.path.isdir(cache_base):
os.makedirs(cache_base) for name, relpath in extensions.items():
dest = os.path.join(cache_base, convert_path(relpath)) ifnot os.path.exists(dest):
extract = True else:
file_time = os.stat(dest).st_mtime
file_time = datetime.datetime.fromtimestamp(file_time)
info = zf.getinfo(relpath)
wheel_time = datetime.datetime(*info.date_time)
extract = wheel_time > file_time if extract:
zf.extract(relpath, cache_base)
result.append((name, dest)) except KeyError: pass return result
def is_compatible(self): """
Determine if a wheel is compatible with the running system. """ return is_compatible(self)
def is_mountable(self): """
Determine if a wheel is asserted as mountable by its metadata. """ returnTrue# for now - metadata details TBD
def mount(self, append=False):
pathname = os.path.abspath(os.path.join(self.dirname, self.filename)) ifnot self.is_compatible():
msg = 'Wheel %s not compatible with this Python.' % pathname raise DistlibException(msg) ifnot self.is_mountable():
msg = 'Wheel %s is marked as not mountable.' % pathname raise DistlibException(msg) if pathname in sys.path:
logger.debug('%s already in path', pathname) else: if append:
sys.path.append(pathname) else:
sys.path.insert(0, pathname)
extensions = self._get_extensions() if extensions: if _hook notin sys.meta_path:
sys.meta_path.append(_hook)
_hook.add(pathname, extensions)
def unmount(self):
pathname = os.path.abspath(os.path.join(self.dirname, self.filename)) if pathname notin sys.path:
logger.debug('%s not in path', pathname) else:
sys.path.remove(pathname) if pathname in _hook.impure_wheels:
_hook.remove(pathname) ifnot _hook.impure_wheels: if _hook in sys.meta_path:
sys.meta_path.remove(_hook)
with ZipFile(pathname, 'r') as zf: with zf.open(wheel_metadata_name) as bwf:
wf = wrapper(bwf)
message_from_file(wf) # wv = message['Wheel-Version'].split('.', 1) # file_version = tuple([int(i) for i in wv]) # TODO version verification
records = {} with zf.open(record_name) as bf: with CSVReader(stream=bf) as reader: for row in reader:
p = row[0]
records[p] = row
for zinfo in zf.infolist():
arcname = zinfo.filename if isinstance(arcname, text_type):
u_arcname = arcname else:
u_arcname = arcname.decode('utf-8') # See issue #115: some wheels have .. in their entries, but # in the filename ... e.g. __main__..py ! So the check is # updated to look for .. in the directory portions
p = u_arcname.split('/') if'..'in p: raise DistlibException('invalid entry in ' 'wheel: %r' % u_arcname)
if self.skip_entry(u_arcname): continue
row = records[u_arcname] if row[2] and str(zinfo.file_size) != row[2]: raise DistlibException('size mismatch for ' '%s' % u_arcname) if row[1]:
kind, value = row[1].split('=', 1) with zf.open(arcname) as bf:
data = bf.read()
_, digest = self.get_hash(data, kind) if digest != value: raise DistlibException('digest mismatch for ' '%s' % arcname)
def update(self, modifier, dest_dir=None, **kwargs): """
Update the contents of a wheel in a generic way. The modifier should
be a callable which expects a dictionary argument: its keys are
archive-entry paths, and its values are absolute filesystem paths
where the contents the corresponding archive entries can be found. The
modifier is free to change the contents of the files pointed to, add
new entries and remove entries, before returning. This method will
extract the entire contents of the wheel to a temporary location, call
the modifier, and then use the passed (and possibly updated)
dictionary to write a new wheel. If ``dest_dir`` is specified, the new
wheel is written there -- otherwise, the original wheel is overwritten.
The modifier should returnTrueif it updated the wheel, elseFalse.
This method returns the same value the modifier returns. """
def get_version(path_map, info_dir):
version = path = None
key = '%s/%s' % (info_dir, LEGACY_METADATA_FILENAME) if key notin path_map:
key = '%s/PKG-INFO' % info_dir if key in path_map:
path = path_map[key]
version = Metadata(path=path).version return version, path
def update_version(version, path):
updated = None try:
NormalizedVersion(version)
i = version.find('-') if i < 0:
updated = '%s+1' % version else:
parts = [int(s) for s in version[i + 1:].split('.')]
parts[-1] += 1
updated = '%s+%s' % (version[:i], '.'.join(str(i) for i in parts)) except UnsupportedVersionError:
logger.debug('Cannot update non-compliant (PEP-440) ' 'version %r', version) if updated:
md = Metadata(path=path)
md.version = updated
legacy = path.endswith(LEGACY_METADATA_FILENAME)
md.write(path=path, legacy=legacy)
logger.debug('Version updated from %r to %r', version, updated)
pathname = os.path.join(self.dirname, self.filename)
name_ver = '%s-%s' % (self.name, self.version)
info_dir = '%s.dist-info' % name_ver
record_name = posixpath.join(info_dir, 'RECORD') with tempdir() as workdir: with ZipFile(pathname, 'r') as zf:
path_map = {} for zinfo in zf.infolist():
arcname = zinfo.filename if isinstance(arcname, text_type):
u_arcname = arcname else:
u_arcname = arcname.decode('utf-8') if u_arcname == record_name: continue if'..'in u_arcname: raise DistlibException('invalid entry in ' 'wheel: %r' % u_arcname)
zf.extract(zinfo, workdir)
path = os.path.join(workdir, convert_path(u_arcname))
path_map[u_arcname] = path
# Remember the version.
original_version, _ = get_version(path_map, info_dir) # Files extracted. Call the modifier.
modified = modifier(path_map, **kwargs) if modified: # Something changed - need to build a new wheel.
current_version, path = get_version(path_map, info_dir) if current_version and (current_version == original_version): # Add or update local version to signify changes.
update_version(current_version, path) # Decide where the new wheel goes. if dest_dir isNone:
fd, newpath = tempfile.mkstemp(suffix='.whl', prefix='wheel-update-', dir=workdir)
os.close(fd) else: ifnot os.path.isdir(dest_dir): raise DistlibException('Not a directory: %r' % dest_dir)
newpath = os.path.join(dest_dir, self.filename)
archive_paths = list(path_map.items())
distinfo = os.path.join(workdir, info_dir)
info = distinfo, info_dir
self.write_records(info, workdir, archive_paths)
self.build_zip(newpath, archive_paths) if dest_dir isNone:
shutil.copyfile(newpath, pathname) return modified
def _get_glibc_version(): import platform
ver = platform.libc_ver()
result = [] if ver[0] == 'glibc': for s in ver[1].split('.'):
result.append(int(s) if s.isdigit() else0)
result = tuple(result) return result
def compatible_tags(): """ Return (pyver, abi, arch) tuples compatible with this Python. """ class _Version: def __init__(self, major, minor):
self.major = major
self.major_minor = (major, minor)
self.string = ''.join((str(major), str(minor)))
def __str__(self): return self.string
versions = [
_Version(sys.version_info.major, minor_version) for minor_version in range(sys.version_info.minor, -1, -1)
]
abis = [] for suffix in _get_suffixes(): if suffix.startswith('.abi'):
abis.append(suffix.split('.', 2)[1])
abis.sort() if ABI != 'none':
abis.insert(0, ABI)
abis.append('none')
result = []
arches = [ARCH] if sys.platform == 'darwin':
m = re.match(r'(\w+)_(\d+)_(\d+)_(\w+)$', ARCH) if m:
name, major, minor, arch = m.groups()
minor = int(minor)
matches = [arch] if arch in ('i386', 'ppc'):
matches.append('fat') if arch in ('i386', 'ppc', 'x86_64'):
matches.append('fat3') if arch in ('ppc64', 'x86_64'):
matches.append('fat64') if arch in ('i386', 'x86_64'):
matches.append('intel') if arch in ('i386', 'x86_64', 'intel', 'ppc', 'ppc64'):
matches.append('universal') while minor >= 0: for match in matches:
s = '%s_%s_%s_%s' % (name, major, minor, match) if s != ARCH: # already there
arches.append(s)
minor -= 1
# Most specific - our Python version, ABI and arch for i, version_object in enumerate(versions):
version = str(version_object)
add_abis = []
if i == 0:
add_abis = abis
if IMP_PREFIX == 'cp'and version_object.major_minor >= (3, 2):
limited_api_abi = 'abi' + str(version_object.major) if limited_api_abi notin add_abis:
add_abis.append(limited_api_abi)
for abi in add_abis: for arch in arches:
result.append((''.join((IMP_PREFIX, version)), abi, arch)) # manylinux if abi != 'none'and sys.platform.startswith('linux'):
arch = arch.replace('linux_', '')
parts = _get_glibc_version() if len(parts) == 2: if parts >= (2, 5):
result.append((''.join((IMP_PREFIX, version)), abi, 'manylinux1_%s' % arch)) if parts >= (2, 12):
result.append((''.join((IMP_PREFIX, version)), abi, 'manylinux2010_%s' % arch)) if parts >= (2, 17):
result.append((''.join((IMP_PREFIX, version)), abi, 'manylinux2014_%s' % arch))
result.append((''.join(
(IMP_PREFIX, version)), abi, 'manylinux_%s_%s_%s' % (parts[0], parts[1], arch)))
# where no ABI / arch dependency, but IMP_PREFIX dependency for i, version_object in enumerate(versions):
version = str(version_object)
result.append((''.join((IMP_PREFIX, version)), 'none', 'any')) if i == 0:
result.append((''.join((IMP_PREFIX, version[0])), 'none', 'any'))
# no IMP_PREFIX, ABI or arch dependency for i, version_object in enumerate(versions):
version = str(version_object)
result.append((''.join(('py', version)), 'none', 'any')) if i == 0:
result.append((''.join(('py', version[0])), 'none', 'any'))
return set(result)
COMPATIBLE_TAGS = compatible_tags()
del compatible_tags
def is_compatible(wheel, tags=None): ifnot isinstance(wheel, Wheel):
wheel = Wheel(wheel) # assume it's a filename
result = False if tags isNone:
tags = COMPATIBLE_TAGS for ver, abi, arch in tags: if ver in wheel.pyver and abi in wheel.abi and arch in wheel.arch:
result = True break return result
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.