Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
wheel.py263 linesDownload Raw Back to setuptools
1"""Wheels support."""2 3import contextlib4import email5import functools6import itertools7import os8import posixpath9import re10import zipfile11from collections.abc import Iterator12 13from packaging.requirements import Requirement14from packaging.tags import sys_tags15from packaging.utils import canonicalize_name16from packaging.version import Version as parse_version17 18import setuptools19from setuptools.archive_util import _unpack_zipfile_obj20from setuptools.command.egg_info import _egg_basename, write_requirements21 22from ._discovery import extras_from_deps23from ._importlib import metadata24from .unicode_utils import _read_utf8_with_fallback25 26from distutils.util import get_platform27 28WHEEL_NAME = re.compile(29    r"""^(?P<project_name>.+?)-(?P<version>\d.*?)30    ((-(?P<build>\d.*?))?-(?P<py_version>.+?)-(?P<abi>.+?)-(?P<platform>.+?)31    )\.whl$""",32    re.VERBOSE,33).match34 35NAMESPACE_PACKAGE_INIT = "__import__('pkg_resources').declare_namespace(__name__)\n"36 37 38@functools.cache39def _get_supported_tags():40    # We calculate the supported tags only once, otherwise calling41    # this method on thousands of wheels takes seconds instead of42    # milliseconds.43    return {(t.interpreter, t.abi, t.platform) for t in sys_tags()}44 45 46def unpack(src_dir, dst_dir) -> None:47    """Move everything under `src_dir` to `dst_dir`, and delete the former."""48    for dirpath, dirnames, filenames in os.walk(src_dir):49        subdir = os.path.relpath(dirpath, src_dir)50        for f in filenames:51            src = os.path.join(dirpath, f)52            dst = os.path.join(dst_dir, subdir, f)53            os.renames(src, dst)54        for n, d in reversed(list(enumerate(dirnames))):55            src = os.path.join(dirpath, d)56            dst = os.path.join(dst_dir, subdir, d)57            if not os.path.exists(dst):58                # Directory does not exist in destination,59                # rename it and prune it from os.walk list.60                os.renames(src, dst)61                del dirnames[n]62    # Cleanup.63    for dirpath, dirnames, filenames in os.walk(src_dir, topdown=True):64        assert not filenames65        os.rmdir(dirpath)66 67 68@contextlib.contextmanager69def disable_info_traces() -> Iterator[None]:70    """71    Temporarily disable info traces.72    """73    from distutils import log74 75    saved = log.set_threshold(log.WARN)76    try:77        yield78    finally:79        log.set_threshold(saved)80 81 82class Wheel:83    def __init__(self, filename) -> None:84        match = WHEEL_NAME(os.path.basename(filename))85        if match is None:86            raise ValueError(f'invalid wheel name: {filename!r}')87        self.filename = filename88        for k, v in match.groupdict().items():89            setattr(self, k, v)90 91    def tags(self):92        """List tags (py_version, abi, platform) supported by this wheel."""93        return itertools.product(94            self.py_version.split('.'),95            self.abi.split('.'),96            self.platform.split('.'),97        )98 99    def is_compatible(self):100        """Is the wheel compatible with the current platform?"""101        return next((True for t in self.tags() if t in _get_supported_tags()), False)102 103    def egg_name(self):104        return (105            _egg_basename(106                self.project_name,107                self.version,108                platform=(None if self.platform == 'any' else get_platform()),109            )110            + ".egg"111        )112 113    def get_dist_info(self, zf):114        # find the correct name of the .dist-info dir in the wheel file115        for member in zf.namelist():116            dirname = posixpath.dirname(member)117            if dirname.endswith('.dist-info') and canonicalize_name(dirname).startswith(118                canonicalize_name(self.project_name)119            ):120                return dirname121        raise ValueError("unsupported wheel format. .dist-info not found")122 123    def install_as_egg(self, destination_eggdir) -> None:124        """Install wheel as an egg directory."""125        with zipfile.ZipFile(self.filename) as zf:126            self._install_as_egg(destination_eggdir, zf)127 128    def _install_as_egg(self, destination_eggdir, zf):129        dist_basename = f'{self.project_name}-{self.version}'130        dist_info = self.get_dist_info(zf)131        dist_data = f'{dist_basename}.data'132        egg_info = os.path.join(destination_eggdir, 'EGG-INFO')133 134        self._convert_metadata(zf, destination_eggdir, dist_info, egg_info)135        self._move_data_entries(destination_eggdir, dist_data)136        self._fix_namespace_packages(egg_info, destination_eggdir)137 138    @staticmethod139    def _convert_metadata(zf, destination_eggdir, dist_info, egg_info):140        def get_metadata(name):141            with zf.open(posixpath.join(dist_info, name)) as fp:142                value = fp.read().decode('utf-8')143                return email.parser.Parser().parsestr(value)144 145        wheel_metadata = get_metadata('WHEEL')146        # Check wheel format version is supported.147        wheel_version = parse_version(wheel_metadata.get('Wheel-Version'))148        wheel_v1 = parse_version('1.0') <= wheel_version < parse_version('2.0dev0')149        if not wheel_v1:150            raise ValueError(f'unsupported wheel format version: {wheel_version}')151        # Extract to target directory.152        _unpack_zipfile_obj(zf, destination_eggdir)153        dist_info = os.path.join(destination_eggdir, dist_info)154        install_requires, extras_require = Wheel._convert_requires(155            destination_eggdir, dist_info156        )157        os.rename(dist_info, egg_info)158        os.rename(159            os.path.join(egg_info, 'METADATA'),160            os.path.join(egg_info, 'PKG-INFO'),161        )162        setup_dist = setuptools.Distribution(163            attrs=dict(164                install_requires=install_requires,165                extras_require=extras_require,166            ),167        )168        with disable_info_traces():169            write_requirements(170                setup_dist.get_command_obj('egg_info'),171                None,172                os.path.join(egg_info, 'requires.txt'),173            )174 175    @staticmethod176    def _convert_requires(destination_eggdir, dist_info):177        md = metadata.Distribution.at(dist_info).metadata178        deps = md.get_all('Requires-Dist') or []179        reqs = list(map(Requirement, deps))180 181        extras = extras_from_deps(deps)182 183        # Note: Evaluate and strip markers now,184        # as it's difficult to convert back from the syntax:185        # foobar; "linux" in sys_platform and extra == 'test'186        def raw_req(req):187            req = Requirement(str(req))188            req.marker = None189            return str(req)190 191        def eval(req, **env):192            return not req.marker or req.marker.evaluate(env)193 194        def for_extra(req):195            try:196                markers = req.marker._markers197            except AttributeError:198                markers = ()199            return set(200                marker[2].value201                for marker in markers202                if isinstance(marker, tuple) and marker[0].value == 'extra'203            )204 205        install_requires = list(206            map(raw_req, filter(eval, itertools.filterfalse(for_extra, reqs)))207        )208        extras_require = {209            extra: list(210                map(211                    raw_req,212                    (req for req in reqs if for_extra(req) and eval(req, extra=extra)),213                )214            )215            for extra in extras216        }217        return install_requires, extras_require218 219    @staticmethod220    def _move_data_entries(destination_eggdir, dist_data):221        """Move data entries to their correct location."""222        dist_data = os.path.join(destination_eggdir, dist_data)223        dist_data_scripts = os.path.join(dist_data, 'scripts')224        if os.path.exists(dist_data_scripts):225            egg_info_scripts = os.path.join(destination_eggdir, 'EGG-INFO', 'scripts')226            os.mkdir(egg_info_scripts)227            for entry in os.listdir(dist_data_scripts):228                # Remove bytecode, as it's not properly handled229                # during easy_install scripts install phase.230                if entry.endswith('.pyc'):231                    os.unlink(os.path.join(dist_data_scripts, entry))232                else:233                    os.rename(234                        os.path.join(dist_data_scripts, entry),235                        os.path.join(egg_info_scripts, entry),236                    )237            os.rmdir(dist_data_scripts)238        for subdir in filter(239            os.path.exists,240            (241                os.path.join(dist_data, d)242                for d in ('data', 'headers', 'purelib', 'platlib')243            ),244        ):245            unpack(subdir, destination_eggdir)246        if os.path.exists(dist_data):247            os.rmdir(dist_data)248 249    @staticmethod250    def _fix_namespace_packages(egg_info, destination_eggdir):251        namespace_packages = os.path.join(egg_info, 'namespace_packages.txt')252        if os.path.exists(namespace_packages):253            namespace_packages = _read_utf8_with_fallback(namespace_packages).split()254 255            for mod in namespace_packages:256                mod_dir = os.path.join(destination_eggdir, *mod.split('.'))257                mod_init = os.path.join(mod_dir, '__init__.py')258                if not os.path.exists(mod_dir):259                    os.mkdir(mod_dir)260                if not os.path.exists(mod_init):261                    with open(mod_init, 'w', encoding="utf-8") as fp:262                        fp.write(NAMESPACE_PACKAGE_INIT)263 
codekingpro/portable-devtools · Team Ai