From a809842cff692dac85a6ce320370b9d4d1737040 Mon Sep 17 00:00:00 2001 From: Matthieu Darbois Date: Tue, 12 Sep 2023 10:58:04 +0200 Subject: [PATCH] chore: rely on packaging to parse wheel filename (#448) Python 3.12 removed the `distutils` module from stdlib. Adherence to this module in auditwheel vendored (wheel) sources shall be removed. Given updating wheel would be a longer task, switch to packaging to parse wheel filename in order to drop some wheel vendored files which are relying on `distutils`. * chore: rely on packaging to parse wheel filename * chore: remove unused files from vendored wheel --- setup.cfg | 1 + src/auditwheel/_vendor/wheel/cli/__init__.py | 88 ------ src/auditwheel/_vendor/wheel/cli/convert.py | 269 ------------------- src/auditwheel/_vendor/wheel/cli/pack.py | 79 ------ src/auditwheel/_vendor/wheel/cli/unpack.py | 25 -- src/auditwheel/_vendor/wheel/util.py | 46 ---- src/auditwheel/_vendor/wheel/wheelfile.py | 169 ------------ src/auditwheel/main_addtag.py | 7 +- src/auditwheel/wheeltools.py | 18 +- 9 files changed, 15 insertions(+), 687 deletions(-) delete mode 100644 src/auditwheel/_vendor/wheel/cli/__init__.py delete mode 100644 src/auditwheel/_vendor/wheel/cli/convert.py delete mode 100644 src/auditwheel/_vendor/wheel/cli/pack.py delete mode 100644 src/auditwheel/_vendor/wheel/cli/unpack.py delete mode 100644 src/auditwheel/_vendor/wheel/util.py delete mode 100644 src/auditwheel/_vendor/wheel/wheelfile.py diff --git a/setup.cfg b/setup.cfg index d7ddbdfa..b9a29c82 100644 --- a/setup.cfg +++ b/setup.cfg @@ -27,6 +27,7 @@ classifier = [options] include_package_data = True install_requires = + packaging>=20.9 pyelftools>=0.24 importlib_metadata; python_version < "3.8" packages = find: diff --git a/src/auditwheel/_vendor/wheel/cli/__init__.py b/src/auditwheel/_vendor/wheel/cli/__init__.py deleted file mode 100644 index 95740bfb..00000000 --- a/src/auditwheel/_vendor/wheel/cli/__init__.py +++ /dev/null @@ -1,88 +0,0 @@ -""" -Wheel command-line utility. -""" - -from __future__ import print_function - -import argparse -import os -import sys - - -def require_pkgresources(name): - try: - import pkg_resources # noqa: F401 - except ImportError: - raise RuntimeError("'{0}' needs pkg_resources (part of setuptools).".format(name)) - - -class WheelError(Exception): - pass - - -def unpack_f(args): - from .unpack import unpack - unpack(args.wheelfile, args.dest) - - -def pack_f(args): - from .pack import pack - pack(args.directory, args.dest_dir, args.build_number) - - -def convert_f(args): - from .convert import convert - convert(args.files, args.dest_dir, args.verbose) - - -def version_f(args): - from .. import __version__ - print("wheel %s" % __version__) - - -def parser(): - p = argparse.ArgumentParser() - s = p.add_subparsers(help="commands") - - unpack_parser = s.add_parser('unpack', help='Unpack wheel') - unpack_parser.add_argument('--dest', '-d', help='Destination directory', - default='.') - unpack_parser.add_argument('wheelfile', help='Wheel file') - unpack_parser.set_defaults(func=unpack_f) - - repack_parser = s.add_parser('pack', help='Repack wheel') - repack_parser.add_argument('directory', help='Root directory of the unpacked wheel') - repack_parser.add_argument('--dest-dir', '-d', default=os.path.curdir, - help="Directory to store the wheel (default %(default)s)") - repack_parser.add_argument('--build-number', help="Build tag to use in the wheel name") - repack_parser.set_defaults(func=pack_f) - - convert_parser = s.add_parser('convert', help='Convert egg or wininst to wheel') - convert_parser.add_argument('files', nargs='*', help='Files to convert') - convert_parser.add_argument('--dest-dir', '-d', default=os.path.curdir, - help="Directory to store wheels (default %(default)s)") - convert_parser.add_argument('--verbose', '-v', action='store_true') - convert_parser.set_defaults(func=convert_f) - - version_parser = s.add_parser('version', help='Print version and exit') - version_parser.set_defaults(func=version_f) - - help_parser = s.add_parser('help', help='Show this help') - help_parser.set_defaults(func=lambda args: p.print_help()) - - return p - - -def main(): - p = parser() - args = p.parse_args() - if not hasattr(args, 'func'): - p.print_help() - else: - try: - args.func(args) - return 0 - except WheelError as e: - print(e, file=sys.stderr) - - return 1 diff --git a/src/auditwheel/_vendor/wheel/cli/convert.py b/src/auditwheel/_vendor/wheel/cli/convert.py deleted file mode 100644 index 154f1b1e..00000000 --- a/src/auditwheel/_vendor/wheel/cli/convert.py +++ /dev/null @@ -1,269 +0,0 @@ -import os.path -import re -import shutil -import sys -import tempfile -import zipfile -from distutils import dist -from glob import iglob - -from ..bdist_wheel import bdist_wheel -from ..wheelfile import WheelFile -from . import WheelError, require_pkgresources - -egg_info_re = re.compile(r''' - (?P.+?)-(?P.+?) - (-(?Ppy\d\.\d+) - (-(?P.+?))? - )?.egg$''', re.VERBOSE) - - -class _bdist_wheel_tag(bdist_wheel): - # allow the client to override the default generated wheel tag - # The default bdist_wheel implementation uses python and abi tags - # of the running python process. This is not suitable for - # generating/repackaging prebuild binaries. - - full_tag_supplied = False - full_tag = None # None or a (pytag, soabitag, plattag) triple - - def get_tag(self): - if self.full_tag_supplied and self.full_tag is not None: - return self.full_tag - else: - return bdist_wheel.get_tag(self) - - -def egg2wheel(egg_path, dest_dir): - filename = os.path.basename(egg_path) - match = egg_info_re.match(filename) - if not match: - raise WheelError('Invalid egg file name: {}'.format(filename)) - - egg_info = match.groupdict() - dir = tempfile.mkdtemp(suffix="_e2w") - if os.path.isfile(egg_path): - # assume we have a bdist_egg otherwise - with zipfile.ZipFile(egg_path) as egg: - egg.extractall(dir) - else: - # support buildout-style installed eggs directories - for pth in os.listdir(egg_path): - src = os.path.join(egg_path, pth) - if os.path.isfile(src): - shutil.copy2(src, dir) - else: - shutil.copytree(src, os.path.join(dir, pth)) - - pyver = egg_info['pyver'] - if pyver: - pyver = egg_info['pyver'] = pyver.replace('.', '') - - arch = (egg_info['arch'] or 'any').replace('.', '_').replace('-', '_') - - # assume all binary eggs are for CPython - abi = 'cp' + pyver[2:] if arch != 'any' else 'none' - - root_is_purelib = egg_info['arch'] is None - if root_is_purelib: - bw = bdist_wheel(dist.Distribution()) - else: - bw = _bdist_wheel_tag(dist.Distribution()) - - bw.root_is_pure = root_is_purelib - bw.python_tag = pyver - bw.plat_name_supplied = True - bw.plat_name = egg_info['arch'] or 'any' - if not root_is_purelib: - bw.full_tag_supplied = True - bw.full_tag = (pyver, abi, arch) - - dist_info_dir = os.path.join(dir, '{name}-{ver}.dist-info'.format(**egg_info)) - bw.egg2dist(os.path.join(dir, 'EGG-INFO'), dist_info_dir) - bw.write_wheelfile(dist_info_dir, generator='egg2wheel') - wheel_name = '{name}-{ver}-{pyver}-{}-{}.whl'.format(abi, arch, **egg_info) - with WheelFile(os.path.join(dest_dir, wheel_name), 'w') as wf: - wf.write_files(dir) - - shutil.rmtree(dir) - - -def parse_wininst_info(wininfo_name, egginfo_name): - """Extract metadata from filenames. - - Extracts the 4 metadataitems needed (name, version, pyversion, arch) from - the installer filename and the name of the egg-info directory embedded in - the zipfile (if any). - - The egginfo filename has the format:: - - name-ver(-pyver)(-arch).egg-info - - The installer filename has the format:: - - name-ver.arch(-pyver).exe - - Some things to note: - - 1. The installer filename is not definitive. An installer can be renamed - and work perfectly well as an installer. So more reliable data should - be used whenever possible. - 2. The egg-info data should be preferred for the name and version, because - these come straight from the distutils metadata, and are mandatory. - 3. The pyver from the egg-info data should be ignored, as it is - constructed from the version of Python used to build the installer, - which is irrelevant - the installer filename is correct here (even to - the point that when it's not there, any version is implied). - 4. The architecture must be taken from the installer filename, as it is - not included in the egg-info data. - 5. Architecture-neutral installers still have an architecture because the - installer format itself (being executable) is architecture-specific. We - should therefore ignore the architecture if the content is pure-python. - """ - - egginfo = None - if egginfo_name: - egginfo = egg_info_re.search(egginfo_name) - if not egginfo: - raise ValueError("Egg info filename %s is not valid" % (egginfo_name,)) - - # Parse the wininst filename - # 1. Distribution name (up to the first '-') - w_name, sep, rest = wininfo_name.partition('-') - if not sep: - raise ValueError("Installer filename %s is not valid" % (wininfo_name,)) - - # Strip '.exe' - rest = rest[:-4] - # 2. Python version (from the last '-', must start with 'py') - rest2, sep, w_pyver = rest.rpartition('-') - if sep and w_pyver.startswith('py'): - rest = rest2 - w_pyver = w_pyver.replace('.', '') - else: - # Not version specific - use py2.py3. While it is possible that - # pure-Python code is not compatible with both Python 2 and 3, there - # is no way of knowing from the wininst format, so we assume the best - # here (the user can always manually rename the wheel to be more - # restrictive if needed). - w_pyver = 'py2.py3' - # 3. Version and architecture - w_ver, sep, w_arch = rest.rpartition('.') - if not sep: - raise ValueError("Installer filename %s is not valid" % (wininfo_name,)) - - if egginfo: - w_name = egginfo.group('name') - w_ver = egginfo.group('ver') - - return {'name': w_name, 'ver': w_ver, 'arch': w_arch, 'pyver': w_pyver} - - -def wininst2wheel(path, dest_dir): - with zipfile.ZipFile(path) as bdw: - # Search for egg-info in the archive - egginfo_name = None - for filename in bdw.namelist(): - if '.egg-info' in filename: - egginfo_name = filename - break - - info = parse_wininst_info(os.path.basename(path), egginfo_name) - - root_is_purelib = True - for zipinfo in bdw.infolist(): - if zipinfo.filename.startswith('PLATLIB'): - root_is_purelib = False - break - if root_is_purelib: - paths = {'purelib': ''} - else: - paths = {'platlib': ''} - - dist_info = "%(name)s-%(ver)s" % info - datadir = "%s.data/" % dist_info - - # rewrite paths to trick ZipFile into extracting an egg - # XXX grab wininst .ini - between .exe, padding, and first zip file. - members = [] - egginfo_name = '' - for zipinfo in bdw.infolist(): - key, basename = zipinfo.filename.split('/', 1) - key = key.lower() - basepath = paths.get(key, None) - if basepath is None: - basepath = datadir + key.lower() + '/' - oldname = zipinfo.filename - newname = basepath + basename - zipinfo.filename = newname - del bdw.NameToInfo[oldname] - bdw.NameToInfo[newname] = zipinfo - # Collect member names, but omit '' (from an entry like "PLATLIB/" - if newname: - members.append(newname) - # Remember egg-info name for the egg2dist call below - if not egginfo_name: - if newname.endswith('.egg-info'): - egginfo_name = newname - elif '.egg-info/' in newname: - egginfo_name, sep, _ = newname.rpartition('/') - dir = tempfile.mkdtemp(suffix="_b2w") - bdw.extractall(dir, members) - - # egg2wheel - abi = 'none' - pyver = info['pyver'] - arch = (info['arch'] or 'any').replace('.', '_').replace('-', '_') - # Wininst installers always have arch even if they are not - # architecture-specific (because the format itself is). - # So, assume the content is architecture-neutral if root is purelib. - if root_is_purelib: - arch = 'any' - # If the installer is architecture-specific, it's almost certainly also - # CPython-specific. - if arch != 'any': - pyver = pyver.replace('py', 'cp') - wheel_name = '-'.join((dist_info, pyver, abi, arch)) - if root_is_purelib: - bw = bdist_wheel(dist.Distribution()) - else: - bw = _bdist_wheel_tag(dist.Distribution()) - - bw.root_is_pure = root_is_purelib - bw.python_tag = pyver - bw.plat_name_supplied = True - bw.plat_name = info['arch'] or 'any' - - if not root_is_purelib: - bw.full_tag_supplied = True - bw.full_tag = (pyver, abi, arch) - - dist_info_dir = os.path.join(dir, '%s.dist-info' % dist_info) - bw.egg2dist(os.path.join(dir, egginfo_name), dist_info_dir) - bw.write_wheelfile(dist_info_dir, generator='wininst2wheel') - - wheel_path = os.path.join(dest_dir, wheel_name) - with WheelFile(wheel_path, 'w') as wf: - wf.write_files(dir) - - shutil.rmtree(dir) - - -def convert(files, dest_dir, verbose): - # Only support wheel convert if pkg_resources is present - require_pkgresources('wheel convert') - - for pat in files: - for installer in iglob(pat): - if os.path.splitext(installer)[1] == '.egg': - conv = egg2wheel - else: - conv = wininst2wheel - - if verbose: - print("{}... ".format(installer)) - sys.stdout.flush() - - conv(installer, dest_dir) - if verbose: - print("OK") diff --git a/src/auditwheel/_vendor/wheel/cli/pack.py b/src/auditwheel/_vendor/wheel/cli/pack.py deleted file mode 100644 index d565ed0e..00000000 --- a/src/auditwheel/_vendor/wheel/cli/pack.py +++ /dev/null @@ -1,79 +0,0 @@ -from __future__ import print_function - -import os.path -import re -import sys - -from . import WheelError -from ..wheelfile import WheelFile - -DIST_INFO_RE = re.compile(r"^(?P(?P.+?)-(?P\d.*?))\.dist-info$") -BUILD_NUM_RE = re.compile(br'Build: (\d\w*)$') - - -def pack(directory, dest_dir, build_number): - """Repack a previously unpacked wheel directory into a new wheel file. - - The .dist-info/WHEEL file must contain one or more tags so that the target - wheel file name can be determined. - - :param directory: The unpacked wheel directory - :param dest_dir: Destination directory (defaults to the current directory) - """ - # Find the .dist-info directory - dist_info_dirs = [fn for fn in os.listdir(directory) - if os.path.isdir(os.path.join(directory, fn)) and DIST_INFO_RE.match(fn)] - if len(dist_info_dirs) > 1: - raise WheelError('Multiple .dist-info directories found in {}'.format(directory)) - elif not dist_info_dirs: - raise WheelError('No .dist-info directories found in {}'.format(directory)) - - # Determine the target wheel filename - dist_info_dir = dist_info_dirs[0] - name_version = DIST_INFO_RE.match(dist_info_dir).group('namever') - - # Read the tags and the existing build number from .dist-info/WHEEL - existing_build_number = None - wheel_file_path = os.path.join(directory, dist_info_dir, 'WHEEL') - with open(wheel_file_path) as f: - tags = [] - for line in f: - if line.startswith('Tag: '): - tags.append(line.split(' ')[1].rstrip()) - elif line.startswith('Build: '): - existing_build_number = line.split(' ')[1].rstrip() - - if not tags: - raise WheelError('No tags present in {}/WHEEL; cannot determine target wheel filename' - .format(dist_info_dir)) - - # Set the wheel file name and add/replace/remove the Build tag in .dist-info/WHEEL - build_number = build_number if build_number is not None else existing_build_number - if build_number is not None: - if build_number: - name_version += '-' + build_number - - if build_number != existing_build_number: - replacement = ('Build: %s\r\n' % build_number).encode('ascii') if build_number else b'' - with open(wheel_file_path, 'rb+') as f: - wheel_file_content = f.read() - if not BUILD_NUM_RE.subn(replacement, wheel_file_content)[1]: - wheel_file_content += replacement - - f.truncate() - f.write(wheel_file_content) - - # Reassemble the tags for the wheel file - impls = sorted({tag.split('-')[0] for tag in tags}) - abivers = sorted({tag.split('-')[1] for tag in tags}) - platforms = sorted({tag.split('-')[2] for tag in tags}) - tagline = '-'.join(['.'.join(impls), '.'.join(abivers), '.'.join(platforms)]) - - # Repack the wheel - wheel_path = os.path.join(dest_dir, '{}-{}.whl'.format(name_version, tagline)) - with WheelFile(wheel_path, 'w') as wf: - print("Repacking wheel as {}...".format(wheel_path), end='') - sys.stdout.flush() - wf.write_files(directory) - - print('OK') diff --git a/src/auditwheel/_vendor/wheel/cli/unpack.py b/src/auditwheel/_vendor/wheel/cli/unpack.py deleted file mode 100644 index 2e9857a3..00000000 --- a/src/auditwheel/_vendor/wheel/cli/unpack.py +++ /dev/null @@ -1,25 +0,0 @@ -from __future__ import print_function - -import os.path -import sys - -from ..wheelfile import WheelFile - - -def unpack(path, dest='.'): - """Unpack a wheel. - - Wheel content will be unpacked to {dest}/{name}-{ver}, where {name} - is the package name and {ver} its version. - - :param path: The path to the wheel. - :param dest: Destination directory (default to current directory). - """ - with WheelFile(path) as wf: - namever = wf.parsed_filename.group('namever') - destination = os.path.join(dest, namever) - print("Unpacking to: {}...".format(destination), end='') - sys.stdout.flush() - wf.extractall(destination) - - print('OK') diff --git a/src/auditwheel/_vendor/wheel/util.py b/src/auditwheel/_vendor/wheel/util.py deleted file mode 100644 index 3ae2b445..00000000 --- a/src/auditwheel/_vendor/wheel/util.py +++ /dev/null @@ -1,46 +0,0 @@ -import base64 -import io -import sys - - -if sys.version_info[0] < 3: - text_type = unicode # noqa: F821 - - StringIO = io.BytesIO - - def native(s, encoding='utf-8'): - if isinstance(s, unicode): # noqa: F821 - return s.encode(encoding) - return s -else: - text_type = str - - StringIO = io.StringIO - - def native(s, encoding='utf-8'): - if isinstance(s, bytes): - return s.decode(encoding) - return s - - -def urlsafe_b64encode(data): - """urlsafe_b64encode without padding""" - return base64.urlsafe_b64encode(data).rstrip(b'=') - - -def urlsafe_b64decode(data): - """urlsafe_b64decode without padding""" - pad = b'=' * (4 - (len(data) & 3)) - return base64.urlsafe_b64decode(data + pad) - - -def as_unicode(s): - if isinstance(s, bytes): - return s.decode('utf-8') - return s - - -def as_bytes(s): - if isinstance(s, text_type): - return s.encode('utf-8') - return s diff --git a/src/auditwheel/_vendor/wheel/wheelfile.py b/src/auditwheel/_vendor/wheel/wheelfile.py deleted file mode 100644 index 5a4b9931..00000000 --- a/src/auditwheel/_vendor/wheel/wheelfile.py +++ /dev/null @@ -1,169 +0,0 @@ -from __future__ import print_function - -import csv -import hashlib -import os.path -import re -import stat -import time -from collections import OrderedDict -from distutils import log as logger -from zipfile import ZIP_DEFLATED, ZipInfo, ZipFile - -from .cli import WheelError -from .util import urlsafe_b64decode, as_unicode, native, urlsafe_b64encode, as_bytes, StringIO - -# Non-greedy matching of an optional build number may be too clever (more -# invalid wheel filenames will match). Separate regex for .dist-info? -WHEEL_INFO_RE = re.compile( - r"""^(?P(?P.+?)-(?P.+?))(-(?P\d[^-]*))? - -(?P.+?)-(?P.+?)-(?P.+?)\.whl$""", - re.VERBOSE) - - -def get_zipinfo_datetime(timestamp=None): - # Some applications need reproducible .whl files, but they can't do this without forcing - # the timestamp of the individual ZipInfo objects. See issue #143. - timestamp = int(os.environ.get('SOURCE_DATE_EPOCH', timestamp or time.time())) - return time.gmtime(timestamp)[0:6] - - -class WheelFile(ZipFile): - """A ZipFile derivative class that also reads SHA-256 hashes from - .dist-info/RECORD and checks any read files against those. - """ - - _default_algorithm = hashlib.sha256 - - def __init__(self, file, mode='r', compression=ZIP_DEFLATED): - basename = os.path.basename(file) - self.parsed_filename = WHEEL_INFO_RE.match(basename) - if not basename.endswith('.whl') or self.parsed_filename is None: - raise WheelError("Bad wheel filename {!r}".format(basename)) - - ZipFile.__init__(self, file, mode, compression=compression, allowZip64=True) - - self.dist_info_path = '{}.dist-info'.format(self.parsed_filename.group('namever')) - self.record_path = self.dist_info_path + '/RECORD' - self._file_hashes = OrderedDict() - self._file_sizes = {} - if mode == 'r': - # Ignore RECORD and any embedded wheel signatures - self._file_hashes[self.record_path] = None, None - self._file_hashes[self.record_path + '.jws'] = None, None - self._file_hashes[self.record_path + '.p7s'] = None, None - - # Fill in the expected hashes by reading them from RECORD - try: - record = self.open(self.record_path) - except KeyError: - raise WheelError('Missing {} file'.format(self.record_path)) - - with record: - for line in record: - line = line.decode('utf-8') - path, hash_sum, size = line.rsplit(u',', 2) - if hash_sum: - algorithm, hash_sum = hash_sum.split(u'=') - try: - hashlib.new(algorithm) - except ValueError: - raise WheelError('Unsupported hash algorithm: {}'.format(algorithm)) - - if algorithm.lower() in {'md5', 'sha1'}: - raise WheelError( - 'Weak hash algorithm ({}) is not permitted by PEP 427' - .format(algorithm)) - - self._file_hashes[path] = ( - algorithm, urlsafe_b64decode(hash_sum.encode('ascii'))) - - def open(self, name_or_info, mode="r", pwd=None): - def _update_crc(newdata, eof=None): - if eof is None: - eof = ef._eof - update_crc_orig(newdata) - else: # Python 2 - update_crc_orig(newdata, eof) - - running_hash.update(newdata) - if eof and running_hash.digest() != expected_hash: - raise WheelError("Hash mismatch for file '{}'".format(native(ef_name))) - - ef_name = as_unicode(name_or_info.filename if isinstance(name_or_info, ZipInfo) - else name_or_info) - if mode == 'r' and not ef_name.endswith('/') and ef_name not in self._file_hashes: - raise WheelError("No hash found for file '{}'".format(native(ef_name))) - - ef = ZipFile.open(self, name_or_info, mode, pwd) - if mode == 'r' and not ef_name.endswith('/'): - algorithm, expected_hash = self._file_hashes[ef_name] - if expected_hash is not None: - # Monkey patch the _update_crc method to also check for the hash from RECORD - running_hash = hashlib.new(algorithm) - update_crc_orig, ef._update_crc = ef._update_crc, _update_crc - - return ef - - def write_files(self, base_dir): - logger.info("creating '%s' and adding '%s' to it", self.filename, base_dir) - deferred = [] - for root, dirnames, filenames in os.walk(base_dir): - # Sort the directory names so that `os.walk` will walk them in a - # defined order on the next iteration. - dirnames.sort() - for name in sorted(filenames): - path = os.path.normpath(os.path.join(root, name)) - if os.path.isfile(path): - arcname = os.path.relpath(path, base_dir).replace(os.path.sep, '/') - if arcname == self.record_path: - pass - elif root.endswith('.dist-info'): - deferred.append((path, arcname)) - else: - self.write(path, arcname) - - deferred.sort() - for path, arcname in deferred: - self.write(path, arcname) - - def write(self, filename, arcname=None, compress_type=None): - with open(filename, 'rb') as f: - st = os.fstat(f.fileno()) - data = f.read() - - zinfo = ZipInfo(arcname or filename, date_time=get_zipinfo_datetime(st.st_mtime)) - zinfo.external_attr = (stat.S_IMODE(st.st_mode) | stat.S_IFMT(st.st_mode)) << 16 - zinfo.compress_type = compress_type or self.compression - self.writestr(zinfo, data, compress_type) - - def writestr(self, zinfo_or_arcname, bytes, compress_type=None): - ZipFile.writestr(self, zinfo_or_arcname, bytes, compress_type) - fname = (zinfo_or_arcname.filename if isinstance(zinfo_or_arcname, ZipInfo) - else zinfo_or_arcname) - logger.info("adding '%s'", fname) - if fname != self.record_path: - hash_ = self._default_algorithm(bytes) - self._file_hashes[fname] = hash_.name, native(urlsafe_b64encode(hash_.digest())) - self._file_sizes[fname] = len(bytes) - - def close(self): - # Write RECORD - if self.fp is not None and self.mode == 'w' and self._file_hashes: - data = StringIO() - writer = csv.writer(data, delimiter=',', quotechar='"', lineterminator='\n') - writer.writerows(( - ( - fname, - algorithm + "=" + hash_, - self._file_sizes[fname] - ) - for fname, (algorithm, hash_) in self._file_hashes.items() - )) - writer.writerow((format(self.record_path), "", "")) - zinfo = ZipInfo(native(self.record_path), date_time=get_zipinfo_datetime()) - zinfo.compress_type = self.compression - zinfo.external_attr = 0o664 << 16 - self.writestr(zinfo, as_bytes(data.getvalue())) - - ZipFile.close(self) diff --git a/src/auditwheel/main_addtag.py b/src/auditwheel/main_addtag.py index 271d0866..53a9fc04 100644 --- a/src/auditwheel/main_addtag.py +++ b/src/auditwheel/main_addtag.py @@ -25,7 +25,8 @@ def configure_parser(sub_parsers): def execute(args, p): import os - from ._vendor.wheel.wheelfile import WHEEL_INFO_RE + from packaging.utils import parse_wheel_filename + from .wheel_abi import NonPlatformWheel, analyze_wheel_abi from .wheeltools import InWheelCtx, WheelToolsError, add_platforms @@ -35,8 +36,8 @@ def execute(args, p): logger.info(NonPlatformWheel.LOG_MESSAGE) return 1 - parsed_fname = WHEEL_INFO_RE.search(basename(args.WHEEL_FILE)) - in_fname_tags = parsed_fname.groupdict()["plat"].split(".") + _, _, _, in_tags = parse_wheel_filename(basename(args.WHEEL_FILE)) + in_fname_tags = {tag.platform for tag in in_tags} logger.info( '%s receives the following tag: "%s".', diff --git a/src/auditwheel/wheeltools.py b/src/auditwheel/wheeltools.py index ab092786..b9f85b31 100644 --- a/src/auditwheel/wheeltools.py +++ b/src/auditwheel/wheeltools.py @@ -21,8 +21,9 @@ from types import TracebackType from typing import Generator, Iterable +from packaging.utils import parse_wheel_filename + from ._vendor.wheel.pkginfo import read_pkg_info, write_pkg_info -from ._vendor.wheel.wheelfile import WHEEL_INFO_RE from .tmpdirs import InTemporaryDirectory from .tools import dir2zip, unique_by_index, zip2dir @@ -223,9 +224,8 @@ def add_platforms( out_dir = "." wheel_fname = basename(wheel_ctx.in_wheel) - parsed_fname = WHEEL_INFO_RE.match(wheel_fname) - fparts = parsed_fname.groupdict() - original_fname_tags = fparts["plat"].split(".") + _, _, _, in_tags = parse_wheel_filename(wheel_fname) + original_fname_tags = sorted({tag.platform for tag in in_tags}) logger.info("Previous filename tags: %s", ", ".join(original_fname_tags)) fname_tags = [tag for tag in original_fname_tags if tag not in remove_platforms] fname_tags = unique_by_index(fname_tags + platforms) @@ -241,10 +241,12 @@ def add_platforms( else: logger.info("No filename tags change needed.") - _, ext = splitext(wheel_fname) - fparts["plat"] = ".".join(fname_tags) - fparts["ext"] = ext - out_wheel_fname = "{namever}-{pyver}-{abi}-{plat}{ext}".format(**fparts) + fparts = { + "prefix": wheel_fname.rsplit("-", maxsplit=1)[0], + "plat": ".".join(fname_tags), + "ext": splitext(wheel_fname)[1], + } + out_wheel_fname = "{prefix}-{plat}{ext}".format(**fparts) out_wheel = pjoin(out_dir, out_wheel_fname) in_info_tags = [tag for name, tag in info.items() if name == "Tag"]