12 from distutils
.util
import get_platform
15 from setuptools
.extern
.packaging
.version
import Version
as parse_version
16 from setuptools
.extern
.packaging
.tags
import sys_tags
17 from setuptools
.extern
.packaging
.utils
import canonicalize_name
18 from setuptools
.command
.egg_info
import write_requirements
, _egg_basename
19 from setuptools
.archive_util
import _unpack_zipfile_obj
22 WHEEL_NAME
= re
.compile(
23 r
"""^(?P<project_name>.+?)-(?P<version>\d.*?)
24 ((-(?P<build>\d.*?))?-(?P<py_version>.+?)-(?P<abi>.+?)-(?P<platform>.+?)
29 NAMESPACE_PACKAGE_INIT
= "__import__('pkg_resources').declare_namespace(__name__)\n"
32 @functools.lru_cache(maxsize
=None)
33 def _get_supported_tags():
34 # We calculate the supported tags only once, otherwise calling
35 # this method on thousands of wheels takes seconds instead of
37 return {(t.interpreter, t.abi, t.platform) for t in sys_tags()}
40 def unpack(src_dir
, dst_dir
):
41 '''Move everything under `src_dir` to `dst_dir`, and delete the former.'''
42 for dirpath
, dirnames
, filenames
in os
.walk(src_dir
):
43 subdir
= os
.path
.relpath(dirpath
, src_dir
)
45 src
= os
.path
.join(dirpath
, f
)
46 dst
= os
.path
.join(dst_dir
, subdir
, f
)
48 for n
, d
in reversed(list(enumerate(dirnames
))):
49 src
= os
.path
.join(dirpath
, d
)
50 dst
= os
.path
.join(dst_dir
, subdir
, d
)
51 if not os
.path
.exists(dst
):
52 # Directory does not exist in destination,
53 # rename it and prune it from os.walk list.
57 for dirpath
, dirnames
, filenames
in os
.walk(src_dir
, topdown
=True):
62 @contextlib.contextmanager
63 def disable_info_traces():
65 Temporarily disable info traces.
67 from distutils
import log
69 saved
= log
.set_threshold(log
.WARN
)
73 log
.set_threshold(saved
)
77 def __init__(self
, filename
):
78 match
= WHEEL_NAME(os
.path
.basename(filename
))
80 raise ValueError('invalid wheel name: %r' % filename
)
81 self
.filename
= filename
82 for k
, v
in match
.groupdict().items():
86 '''List tags (py_version, abi, platform) supported by this wheel.'''
87 return itertools
.product(
88 self
.py_version
.split('.'),
90 self
.platform
.split('.'),
93 def is_compatible(self
):
94 '''Is the wheel compatible with the current platform?'''
95 return next((True for t
in self
.tags() if t
in _get_supported_tags()), False)
102 platform
=(None if self
.platform
== 'any' else get_platform()),
107 def get_dist_info(self
, zf
):
108 # find the correct name of the .dist-info dir in the wheel file
109 for member
in zf
.namelist():
110 dirname
= posixpath
.dirname(member
)
111 if dirname
.endswith('.dist-info') and canonicalize_name(dirname
).startswith(
112 canonicalize_name(self
.project_name
)
115 raise ValueError("unsupported wheel format. .dist-info not found")
117 def install_as_egg(self
, destination_eggdir
):
118 '''Install wheel as an egg directory.'''
119 with zipfile
.ZipFile(self
.filename
) as zf
:
120 self
._install
_as
_egg
(destination_eggdir
, zf
)
122 def _install_as_egg(self
, destination_eggdir
, zf
):
123 dist_basename
= '%s-%s' % (self
.project_name
, self
.version
)
124 dist_info
= self
.get_dist_info(zf
)
125 dist_data
= '%s.data' % dist_basename
126 egg_info
= os
.path
.join(destination_eggdir
, 'EGG-INFO')
128 self
._convert
_metadata
(zf
, destination_eggdir
, dist_info
, egg_info
)
129 self
._move
_data
_entries
(destination_eggdir
, dist_data
)
130 self
._fix
_namespace
_packages
(egg_info
, destination_eggdir
)
133 def _convert_metadata(zf
, destination_eggdir
, dist_info
, egg_info
):
136 def get_metadata(name
):
137 with zf
.open(posixpath
.join(dist_info
, name
)) as fp
:
138 value
= fp
.read().decode('utf-8')
139 return email
.parser
.Parser().parsestr(value
)
141 wheel_metadata
= get_metadata('WHEEL')
142 # Check wheel format version is supported.
143 wheel_version
= parse_version(wheel_metadata
.get('Wheel-Version'))
144 wheel_v1
= parse_version('1.0') <= wheel_version
< parse_version('2.0dev0')
146 raise ValueError('unsupported wheel format version: %s' % wheel_version
)
147 # Extract to target directory.
148 _unpack_zipfile_obj(zf
, destination_eggdir
)
150 dist_info
= os
.path
.join(destination_eggdir
, dist_info
)
151 dist
= pkg_resources
.Distribution
.from_location(
154 metadata
=pkg_resources
.PathMetadata(destination_eggdir
, dist_info
),
157 # Note: Evaluate and strip markers now,
158 # as it's difficult to convert back from the syntax:
159 # foobar; "linux" in sys_platform and extra == 'test'
164 install_requires
= list(map(raw_req
, dist
.requires()))
168 for req
in map(raw_req
, dist
.requires((extra
,)))
169 if req
not in install_requires
171 for extra
in dist
.extras
173 os
.rename(dist_info
, egg_info
)
175 os
.path
.join(egg_info
, 'METADATA'),
176 os
.path
.join(egg_info
, 'PKG-INFO'),
178 setup_dist
= setuptools
.Distribution(
180 install_requires
=install_requires
,
181 extras_require
=extras_require
,
184 with disable_info_traces():
186 setup_dist
.get_command_obj('egg_info'),
188 os
.path
.join(egg_info
, 'requires.txt'),
192 def _move_data_entries(destination_eggdir
, dist_data
):
193 """Move data entries to their correct location."""
194 dist_data
= os
.path
.join(destination_eggdir
, dist_data
)
195 dist_data_scripts
= os
.path
.join(dist_data
, 'scripts')
196 if os
.path
.exists(dist_data_scripts
):
197 egg_info_scripts
= os
.path
.join(destination_eggdir
, 'EGG-INFO', 'scripts')
198 os
.mkdir(egg_info_scripts
)
199 for entry
in os
.listdir(dist_data_scripts
):
200 # Remove bytecode, as it's not properly handled
201 # during easy_install scripts install phase.
202 if entry
.endswith('.pyc'):
203 os
.unlink(os
.path
.join(dist_data_scripts
, entry
))
206 os
.path
.join(dist_data_scripts
, entry
),
207 os
.path
.join(egg_info_scripts
, entry
),
209 os
.rmdir(dist_data_scripts
)
210 for subdir
in filter(
213 os
.path
.join(dist_data
, d
)
214 for d
in ('data', 'headers', 'purelib', 'platlib')
217 unpack(subdir
, destination_eggdir
)
218 if os
.path
.exists(dist_data
):
222 def _fix_namespace_packages(egg_info
, destination_eggdir
):
223 namespace_packages
= os
.path
.join(egg_info
, 'namespace_packages.txt')
224 if os
.path
.exists(namespace_packages
):
225 with open(namespace_packages
) as fp
:
226 namespace_packages
= fp
.read().split()
227 for mod
in namespace_packages
:
228 mod_dir
= os
.path
.join(destination_eggdir
, *mod
.split('.'))
229 mod_init
= os
.path
.join(mod_dir
, '__init__.py')
230 if not os
.path
.exists(mod_dir
):
232 if not os
.path
.exists(mod_init
):
233 with open(mod_init
, 'w') as fp
:
234 fp
.write(NAMESPACE_PACKAGE_INIT
)