wheel.py 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. '''Wheels support.'''
  2. from distutils.util import get_platform
  3. import email
  4. import itertools
  5. import os
  6. import re
  7. import zipfile
  8. from pkg_resources import Distribution, PathMetadata, parse_version
  9. from setuptools.extern.six import PY3
  10. from setuptools import Distribution as SetuptoolsDistribution
  11. from setuptools import pep425tags
  12. from setuptools.command.egg_info import write_requirements
  13. WHEEL_NAME = re.compile(
  14. r"""^(?P<project_name>.+?)-(?P<version>\d.*?)
  15. ((-(?P<build>\d.*?))?-(?P<py_version>.+?)-(?P<abi>.+?)-(?P<platform>.+?)
  16. )\.whl$""",
  17. re.VERBOSE).match
  18. NAMESPACE_PACKAGE_INIT = '''\
  19. try:
  20. __import__('pkg_resources').declare_namespace(__name__)
  21. except ImportError:
  22. __path__ = __import__('pkgutil').extend_path(__path__, __name__)
  23. '''
  24. def unpack(src_dir, dst_dir):
  25. '''Move everything under `src_dir` to `dst_dir`, and delete the former.'''
  26. for dirpath, dirnames, filenames in os.walk(src_dir):
  27. subdir = os.path.relpath(dirpath, src_dir)
  28. for f in filenames:
  29. src = os.path.join(dirpath, f)
  30. dst = os.path.join(dst_dir, subdir, f)
  31. os.renames(src, dst)
  32. for n, d in reversed(list(enumerate(dirnames))):
  33. src = os.path.join(dirpath, d)
  34. dst = os.path.join(dst_dir, subdir, d)
  35. if not os.path.exists(dst):
  36. # Directory does not exist in destination,
  37. # rename it and prune it from os.walk list.
  38. os.renames(src, dst)
  39. del dirnames[n]
  40. # Cleanup.
  41. for dirpath, dirnames, filenames in os.walk(src_dir, topdown=True):
  42. assert not filenames
  43. os.rmdir(dirpath)
  44. class Wheel(object):
  45. def __init__(self, filename):
  46. match = WHEEL_NAME(os.path.basename(filename))
  47. if match is None:
  48. raise ValueError('invalid wheel name: %r' % filename)
  49. self.filename = filename
  50. for k, v in match.groupdict().items():
  51. setattr(self, k, v)
  52. def tags(self):
  53. '''List tags (py_version, abi, platform) supported by this wheel.'''
  54. return itertools.product(self.py_version.split('.'),
  55. self.abi.split('.'),
  56. self.platform.split('.'))
  57. def is_compatible(self):
  58. '''Is the wheel is compatible with the current platform?'''
  59. supported_tags = pep425tags.get_supported()
  60. return next((True for t in self.tags() if t in supported_tags), False)
  61. def egg_name(self):
  62. return Distribution(
  63. project_name=self.project_name, version=self.version,
  64. platform=(None if self.platform == 'any' else get_platform()),
  65. ).egg_name() + '.egg'
  66. def install_as_egg(self, destination_eggdir):
  67. '''Install wheel as an egg directory.'''
  68. with zipfile.ZipFile(self.filename) as zf:
  69. dist_basename = '%s-%s' % (self.project_name, self.version)
  70. dist_info = '%s.dist-info' % dist_basename
  71. dist_data = '%s.data' % dist_basename
  72. def get_metadata(name):
  73. with zf.open('%s/%s' % (dist_info, name)) as fp:
  74. value = fp.read().decode('utf-8') if PY3 else fp.read()
  75. return email.parser.Parser().parsestr(value)
  76. wheel_metadata = get_metadata('WHEEL')
  77. dist_metadata = get_metadata('METADATA')
  78. # Check wheel format version is supported.
  79. wheel_version = parse_version(wheel_metadata.get('Wheel-Version'))
  80. if not parse_version('1.0') <= wheel_version < parse_version('2.0dev0'):
  81. raise ValueError('unsupported wheel format version: %s' % wheel_version)
  82. # Extract to target directory.
  83. os.mkdir(destination_eggdir)
  84. zf.extractall(destination_eggdir)
  85. # Convert metadata.
  86. dist_info = os.path.join(destination_eggdir, dist_info)
  87. dist = Distribution.from_location(
  88. destination_eggdir, dist_info,
  89. metadata=PathMetadata(destination_eggdir, dist_info)
  90. )
  91. # Note: we need to evaluate and strip markers now,
  92. # as we can't easily convert back from the syntax:
  93. # foobar; "linux" in sys_platform and extra == 'test'
  94. def raw_req(req):
  95. req.marker = None
  96. return str(req)
  97. install_requires = list(sorted(map(raw_req, dist.requires())))
  98. extras_require = {
  99. extra: list(sorted(
  100. req
  101. for req in map(raw_req, dist.requires((extra,)))
  102. if req not in install_requires
  103. ))
  104. for extra in dist.extras
  105. }
  106. egg_info = os.path.join(destination_eggdir, 'EGG-INFO')
  107. os.rename(dist_info, egg_info)
  108. os.rename(os.path.join(egg_info, 'METADATA'),
  109. os.path.join(egg_info, 'PKG-INFO'))
  110. setup_dist = SetuptoolsDistribution(attrs=dict(
  111. install_requires=install_requires,
  112. extras_require=extras_require,
  113. ))
  114. write_requirements(setup_dist.get_command_obj('egg_info'),
  115. None, os.path.join(egg_info, 'requires.txt'))
  116. # Move data entries to their correct location.
  117. dist_data = os.path.join(destination_eggdir, dist_data)
  118. dist_data_scripts = os.path.join(dist_data, 'scripts')
  119. if os.path.exists(dist_data_scripts):
  120. egg_info_scripts = os.path.join(destination_eggdir,
  121. 'EGG-INFO', 'scripts')
  122. os.mkdir(egg_info_scripts)
  123. for entry in os.listdir(dist_data_scripts):
  124. # Remove bytecode, as it's not properly handled
  125. # during easy_install scripts install phase.
  126. if entry.endswith('.pyc'):
  127. os.unlink(os.path.join(dist_data_scripts, entry))
  128. else:
  129. os.rename(os.path.join(dist_data_scripts, entry),
  130. os.path.join(egg_info_scripts, entry))
  131. os.rmdir(dist_data_scripts)
  132. for subdir in filter(os.path.exists, (
  133. os.path.join(dist_data, d)
  134. for d in ('data', 'headers', 'purelib', 'platlib')
  135. )):
  136. unpack(subdir, destination_eggdir)
  137. if os.path.exists(dist_data):
  138. os.rmdir(dist_data)
  139. # Fix namespace packages.
  140. namespace_packages = os.path.join(egg_info, 'namespace_packages.txt')
  141. if os.path.exists(namespace_packages):
  142. with open(namespace_packages) as fp:
  143. namespace_packages = fp.read().split()
  144. for mod in namespace_packages:
  145. mod_dir = os.path.join(destination_eggdir, *mod.split('.'))
  146. mod_init = os.path.join(mod_dir, '__init__.py')
  147. if os.path.exists(mod_dir) and not os.path.exists(mod_init):
  148. with open(mod_init, 'w') as fp:
  149. fp.write(NAMESPACE_PACKAGE_INIT)