| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213 | """Wheels support."""from distutils.util import get_platformfrom distutils import logimport emailimport itertoolsimport osimport posixpathimport reimport zipfileimport pkg_resourcesimport setuptoolsfrom pkg_resources import parse_versionfrom setuptools.extern.packaging.tags import sys_tagsfrom setuptools.extern.packaging.utils import canonicalize_namefrom setuptools.command.egg_info import write_requirementsWHEEL_NAME = re.compile(    r"""^(?P<project_name>.+?)-(?P<version>\d.*?)    ((-(?P<build>\d.*?))?-(?P<py_version>.+?)-(?P<abi>.+?)-(?P<platform>.+?)    )\.whl$""",    re.VERBOSE).matchNAMESPACE_PACKAGE_INIT = \    "__import__('pkg_resources').declare_namespace(__name__)\n"def unpack(src_dir, dst_dir):    '''Move everything under `src_dir` to `dst_dir`, and delete the former.'''    for dirpath, dirnames, filenames in os.walk(src_dir):        subdir = os.path.relpath(dirpath, src_dir)        for f in filenames:            src = os.path.join(dirpath, f)            dst = os.path.join(dst_dir, subdir, f)            os.renames(src, dst)        for n, d in reversed(list(enumerate(dirnames))):            src = os.path.join(dirpath, d)            dst = os.path.join(dst_dir, subdir, d)            if not os.path.exists(dst):                # Directory does not exist in destination,                # rename it and prune it from os.walk list.                os.renames(src, dst)                del dirnames[n]    # Cleanup.    for dirpath, dirnames, filenames in os.walk(src_dir, topdown=True):        assert not filenames        os.rmdir(dirpath)class Wheel:    def __init__(self, filename):        match = WHEEL_NAME(os.path.basename(filename))        if match is None:            raise ValueError('invalid wheel name: %r' % filename)        self.filename = filename        for k, v in match.groupdict().items():            setattr(self, k, v)    def tags(self):        '''List tags (py_version, abi, platform) supported by this wheel.'''        return itertools.product(            self.py_version.split('.'),            self.abi.split('.'),            self.platform.split('.'),        )    def is_compatible(self):        '''Is the wheel is compatible with the current platform?'''        supported_tags = set(            (t.interpreter, t.abi, t.platform) for t in sys_tags())        return next((True for t in self.tags() if t in supported_tags), False)    def egg_name(self):        return pkg_resources.Distribution(            project_name=self.project_name, version=self.version,            platform=(None if self.platform == 'any' else get_platform()),        ).egg_name() + '.egg'    def get_dist_info(self, zf):        # find the correct name of the .dist-info dir in the wheel file        for member in zf.namelist():            dirname = posixpath.dirname(member)            if (dirname.endswith('.dist-info') and                    canonicalize_name(dirname).startswith(                        canonicalize_name(self.project_name))):                return dirname        raise ValueError("unsupported wheel format. .dist-info not found")    def install_as_egg(self, destination_eggdir):        '''Install wheel as an egg directory.'''        with zipfile.ZipFile(self.filename) as zf:            self._install_as_egg(destination_eggdir, zf)    def _install_as_egg(self, destination_eggdir, zf):        dist_basename = '%s-%s' % (self.project_name, self.version)        dist_info = self.get_dist_info(zf)        dist_data = '%s.data' % dist_basename        egg_info = os.path.join(destination_eggdir, 'EGG-INFO')        self._convert_metadata(zf, destination_eggdir, dist_info, egg_info)        self._move_data_entries(destination_eggdir, dist_data)        self._fix_namespace_packages(egg_info, destination_eggdir)    @staticmethod    def _convert_metadata(zf, destination_eggdir, dist_info, egg_info):        def get_metadata(name):            with zf.open(posixpath.join(dist_info, name)) as fp:                value = fp.read().decode('utf-8')                return email.parser.Parser().parsestr(value)        wheel_metadata = get_metadata('WHEEL')        # Check wheel format version is supported.        wheel_version = parse_version(wheel_metadata.get('Wheel-Version'))        wheel_v1 = (            parse_version('1.0') <= wheel_version < parse_version('2.0dev0')        )        if not wheel_v1:            raise ValueError(                'unsupported wheel format version: %s' % wheel_version)        # Extract to target directory.        os.mkdir(destination_eggdir)        zf.extractall(destination_eggdir)        # Convert metadata.        dist_info = os.path.join(destination_eggdir, dist_info)        dist = pkg_resources.Distribution.from_location(            destination_eggdir, dist_info,            metadata=pkg_resources.PathMetadata(destination_eggdir, dist_info),        )        # Note: Evaluate and strip markers now,        # as it's difficult to convert back from the syntax:        # foobar; "linux" in sys_platform and extra == 'test'        def raw_req(req):            req.marker = None            return str(req)        install_requires = list(sorted(map(raw_req, dist.requires())))        extras_require = {            extra: sorted(                req                for req in map(raw_req, dist.requires((extra,)))                if req not in install_requires            )            for extra in dist.extras        }        os.rename(dist_info, egg_info)        os.rename(            os.path.join(egg_info, 'METADATA'),            os.path.join(egg_info, 'PKG-INFO'),        )        setup_dist = setuptools.Distribution(            attrs=dict(                install_requires=install_requires,                extras_require=extras_require,            ),        )        # Temporarily disable info traces.        log_threshold = log._global_log.threshold        log.set_threshold(log.WARN)        try:            write_requirements(                setup_dist.get_command_obj('egg_info'),                None,                os.path.join(egg_info, 'requires.txt'),            )        finally:            log.set_threshold(log_threshold)    @staticmethod    def _move_data_entries(destination_eggdir, dist_data):        """Move data entries to their correct location."""        dist_data = os.path.join(destination_eggdir, dist_data)        dist_data_scripts = os.path.join(dist_data, 'scripts')        if os.path.exists(dist_data_scripts):            egg_info_scripts = os.path.join(                destination_eggdir, 'EGG-INFO', 'scripts')            os.mkdir(egg_info_scripts)            for entry in os.listdir(dist_data_scripts):                # Remove bytecode, as it's not properly handled                # during easy_install scripts install phase.                if entry.endswith('.pyc'):                    os.unlink(os.path.join(dist_data_scripts, entry))                else:                    os.rename(                        os.path.join(dist_data_scripts, entry),                        os.path.join(egg_info_scripts, entry),                    )            os.rmdir(dist_data_scripts)        for subdir in filter(os.path.exists, (            os.path.join(dist_data, d)            for d in ('data', 'headers', 'purelib', 'platlib')        )):            unpack(subdir, destination_eggdir)        if os.path.exists(dist_data):            os.rmdir(dist_data)    @staticmethod    def _fix_namespace_packages(egg_info, destination_eggdir):        namespace_packages = os.path.join(            egg_info, 'namespace_packages.txt')        if os.path.exists(namespace_packages):            with open(namespace_packages) as fp:                namespace_packages = fp.read().split()            for mod in namespace_packages:                mod_dir = os.path.join(destination_eggdir, *mod.split('.'))                mod_init = os.path.join(mod_dir, '__init__.py')                if not os.path.exists(mod_dir):                    os.mkdir(mod_dir)                if not os.path.exists(mod_init):                    with open(mod_init, 'w') as fp:                        fp.write(NAMESPACE_PACKAGE_INIT)
 |