PageRenderTime 48ms CodeModel.GetById 24ms RepoModel.GetById 1ms app.codeStats 0ms

/env/lib/python3.3/site-packages/pip/wheel.py

https://github.com/wantsomechocolate/MosaicMaker
Python | 335 lines | 334 code | 0 blank | 1 comment | 1 complexity | 4bdf34b9cb1f9f6ba95f4a3647d45cae MD5 | raw file
  1. """
  2. Support for installing and building the "wheel" binary package format.
  3. """
  4. from __future__ import with_statement
  5. import csv
  6. import functools
  7. import hashlib
  8. import os
  9. import pkg_resources
  10. import re
  11. import shutil
  12. import sys
  13. from base64 import urlsafe_b64encode
  14. from pip.locations import distutils_scheme
  15. from pip.log import logger
  16. from pip import pep425tags
  17. from pip.util import call_subprocess, normalize_path, make_path_relative
  18. wheel_ext = '.whl'
  19. # don't use pkg_resources.Requirement.parse, to avoid the override in distribute,
  20. # that converts 'setuptools' to 'distribute'.
  21. setuptools_requirement = list(pkg_resources.parse_requirements("setuptools>=0.8"))[0]
  22. def wheel_setuptools_support():
  23. """
  24. Return True if we have a setuptools that supports wheel.
  25. """
  26. fulfilled = False
  27. try:
  28. installed_setuptools = pkg_resources.get_distribution('setuptools')
  29. if installed_setuptools in setuptools_requirement:
  30. fulfilled = True
  31. except pkg_resources.DistributionNotFound:
  32. pass
  33. if not fulfilled:
  34. logger.warn("%s is required for wheel installs." % setuptools_requirement)
  35. return fulfilled
  36. def rehash(path, algo='sha256', blocksize=1<<20):
  37. """Return (hash, length) for path using hashlib.new(algo)"""
  38. h = hashlib.new(algo)
  39. length = 0
  40. with open(path) as f:
  41. block = f.read(blocksize)
  42. while block:
  43. length += len(block)
  44. h.update(block)
  45. block = f.read(blocksize)
  46. digest = 'sha256='+urlsafe_b64encode(h.digest()).decode('latin1').rstrip('=')
  47. return (digest, length)
  48. try:
  49. unicode
  50. def binary(s):
  51. if isinstance(s, unicode):
  52. return s.encode('ascii')
  53. return s
  54. except NameError:
  55. def binary(s):
  56. if isinstance(s, str):
  57. return s.encode('ascii')
  58. def open_for_csv(name, mode):
  59. if sys.version_info[0] < 3:
  60. nl = {}
  61. bin = 'b'
  62. else:
  63. nl = { 'newline': '' }
  64. bin = ''
  65. return open(name, mode + bin, **nl)
  66. def fix_script(path):
  67. """Replace #!python with #!/path/to/python
  68. Return True if file was changed."""
  69. # XXX RECORD hashes will need to be updated
  70. if os.path.isfile(path):
  71. script = open(path, 'rb')
  72. try:
  73. firstline = script.readline()
  74. if not firstline.startswith(binary('#!python')):
  75. return False
  76. exename = sys.executable.encode(sys.getfilesystemencoding())
  77. firstline = binary('#!') + exename + binary(os.linesep)
  78. rest = script.read()
  79. finally:
  80. script.close()
  81. script = open(path, 'wb')
  82. try:
  83. script.write(firstline)
  84. script.write(rest)
  85. finally:
  86. script.close()
  87. return True
  88. dist_info_re = re.compile(r"""^(?P<namever>(?P<name>.+?)(-(?P<ver>\d.+?))?)
  89. \.dist-info$""", re.VERBOSE)
  90. def root_is_purelib(name, wheeldir):
  91. """
  92. Return True if the extracted wheel in wheeldir should go into purelib.
  93. """
  94. name_folded = name.replace("-", "_")
  95. for item in os.listdir(wheeldir):
  96. match = dist_info_re.match(item)
  97. if match and match.group('name') == name_folded:
  98. with open(os.path.join(wheeldir, item, 'WHEEL')) as wheel:
  99. for line in wheel:
  100. line = line.lower().rstrip()
  101. if line == "root-is-purelib: true":
  102. return True
  103. return False
  104. def move_wheel_files(name, req, wheeldir, user=False, home=None):
  105. """Install a wheel"""
  106. scheme = distutils_scheme(name, user=user, home=home)
  107. if root_is_purelib(name, wheeldir):
  108. lib_dir = scheme['purelib']
  109. else:
  110. lib_dir = scheme['platlib']
  111. info_dir = []
  112. data_dirs = []
  113. source = wheeldir.rstrip(os.path.sep) + os.path.sep
  114. installed = {}
  115. changed = set()
  116. def normpath(src, p):
  117. return make_path_relative(src, p).replace(os.path.sep, '/')
  118. def record_installed(srcfile, destfile, modified=False):
  119. """Map archive RECORD paths to installation RECORD paths."""
  120. oldpath = normpath(srcfile, wheeldir)
  121. newpath = normpath(destfile, lib_dir)
  122. installed[oldpath] = newpath
  123. if modified:
  124. changed.add(destfile)
  125. def clobber(source, dest, is_base, fixer=None):
  126. if not os.path.exists(dest): # common for the 'include' path
  127. os.makedirs(dest)
  128. for dir, subdirs, files in os.walk(source):
  129. basedir = dir[len(source):].lstrip(os.path.sep)
  130. if is_base and basedir.split(os.path.sep, 1)[0].endswith('.data'):
  131. continue
  132. for s in subdirs:
  133. destsubdir = os.path.join(dest, basedir, s)
  134. if is_base and basedir == '' and destsubdir.endswith('.data'):
  135. data_dirs.append(s)
  136. continue
  137. elif (is_base
  138. and s.endswith('.dist-info')
  139. # is self.req.project_name case preserving?
  140. and s.lower().startswith(req.project_name.replace('-', '_').lower())):
  141. assert not info_dir, 'Multiple .dist-info directories'
  142. info_dir.append(destsubdir)
  143. if not os.path.exists(destsubdir):
  144. os.makedirs(destsubdir)
  145. for f in files:
  146. srcfile = os.path.join(dir, f)
  147. destfile = os.path.join(dest, basedir, f)
  148. shutil.move(srcfile, destfile)
  149. changed = False
  150. if fixer:
  151. changed = fixer(destfile)
  152. record_installed(srcfile, destfile, changed)
  153. clobber(source, lib_dir, True)
  154. assert info_dir, "%s .dist-info directory not found" % req
  155. for datadir in data_dirs:
  156. fixer = None
  157. for subdir in os.listdir(os.path.join(wheeldir, datadir)):
  158. fixer = None
  159. if subdir == 'scripts':
  160. fixer = fix_script
  161. source = os.path.join(wheeldir, datadir, subdir)
  162. dest = scheme[subdir]
  163. clobber(source, dest, False, fixer=fixer)
  164. record = os.path.join(info_dir[0], 'RECORD')
  165. temp_record = os.path.join(info_dir[0], 'RECORD.pip')
  166. with open_for_csv(record, 'r') as record_in:
  167. with open_for_csv(temp_record, 'w+') as record_out:
  168. reader = csv.reader(record_in)
  169. writer = csv.writer(record_out)
  170. for row in reader:
  171. row[0] = installed.pop(row[0], row[0])
  172. if row[0] in changed:
  173. row[1], row[2] = rehash(row[0])
  174. writer.writerow(row)
  175. for f in installed:
  176. writer.writerow((installed[f], '', ''))
  177. shutil.move(temp_record, record)
  178. def _unique(fn):
  179. @functools.wraps(fn)
  180. def unique(*args, **kw):
  181. seen = set()
  182. for item in fn(*args, **kw):
  183. if item not in seen:
  184. seen.add(item)
  185. yield item
  186. return unique
  187. # TODO: this goes somewhere besides the wheel module
  188. @_unique
  189. def uninstallation_paths(dist):
  190. """
  191. Yield all the uninstallation paths for dist based on RECORD-without-.pyc
  192. Yield paths to all the files in RECORD. For each .py file in RECORD, add
  193. the .pyc in the same directory.
  194. UninstallPathSet.add() takes care of the __pycache__ .pyc.
  195. """
  196. from pip.req import FakeFile # circular import
  197. r = csv.reader(FakeFile(dist.get_metadata_lines('RECORD')))
  198. for row in r:
  199. path = os.path.join(dist.location, row[0])
  200. yield path
  201. if path.endswith('.py'):
  202. dn, fn = os.path.split(path)
  203. base = fn[:-3]
  204. path = os.path.join(dn, base+'.pyc')
  205. yield path
  206. class Wheel(object):
  207. """A wheel file"""
  208. # TODO: maybe move the install code into this class
  209. wheel_file_re = re.compile(
  210. r"""^(?P<namever>(?P<name>.+?)(-(?P<ver>\d.+?))?)
  211. ((-(?P<build>\d.*?))?-(?P<pyver>.+?)-(?P<abi>.+?)-(?P<plat>.+?)
  212. \.whl|\.dist-info)$""",
  213. re.VERBOSE)
  214. def __init__(self, filename):
  215. wheel_info = self.wheel_file_re.match(filename)
  216. self.filename = filename
  217. self.name = wheel_info.group('name').replace('_', '-')
  218. self.version = wheel_info.group('ver')
  219. self.pyversions = wheel_info.group('pyver').split('.')
  220. self.abis = wheel_info.group('abi').split('.')
  221. self.plats = wheel_info.group('plat').split('.')
  222. # All the tag combinations from this file
  223. self.file_tags = set((x, y, z) for x in self.pyversions for y
  224. in self.abis for z in self.plats)
  225. def support_index_min(self, tags=None):
  226. """
  227. Return the lowest index that a file_tag achieves in the supported_tags list
  228. e.g. if there are 8 supported tags, and one of the file tags is first in the
  229. list, then return 0.
  230. """
  231. if tags is None: # for mock
  232. tags = pep425tags.supported_tags
  233. indexes = [tags.index(c) for c in self.file_tags if c in tags]
  234. return min(indexes) if indexes else None
  235. def supported(self, tags=None):
  236. """Is this wheel supported on this system?"""
  237. if tags is None: # for mock
  238. tags = pep425tags.supported_tags
  239. return bool(set(tags).intersection(self.file_tags))
  240. class WheelBuilder(object):
  241. """Build wheels from a RequirementSet."""
  242. def __init__(self, requirement_set, finder, wheel_dir, build_options=[], global_options=[]):
  243. self.requirement_set = requirement_set
  244. self.finder = finder
  245. self.wheel_dir = normalize_path(wheel_dir)
  246. self.build_options = build_options
  247. self.global_options = global_options
  248. def _build_one(self, req):
  249. """Build one wheel."""
  250. base_args = [
  251. sys.executable, '-c',
  252. "import setuptools;__file__=%r;"\
  253. "exec(compile(open(__file__).read().replace('\\r\\n', '\\n'), __file__, 'exec'))" % req.setup_py] + \
  254. list(self.global_options)
  255. logger.notify('Running setup.py bdist_wheel for %s' % req.name)
  256. logger.notify('Destination directory: %s' % self.wheel_dir)
  257. wheel_args = base_args + ['bdist_wheel', '-d', self.wheel_dir] + self.build_options
  258. try:
  259. call_subprocess(wheel_args, cwd=req.source_dir, show_stdout=False)
  260. return True
  261. except:
  262. logger.error('Failed building wheel for %s' % req.name)
  263. return False
  264. def build(self):
  265. """Build wheels."""
  266. #unpack and constructs req set
  267. self.requirement_set.prepare_files(self.finder)
  268. reqset = self.requirement_set.requirements.values()
  269. #make the wheelhouse
  270. if not os.path.exists(self.wheel_dir):
  271. os.makedirs(self.wheel_dir)
  272. #build the wheels
  273. logger.notify('Building wheels for collected packages: %s' % ', '.join([req.name for req in reqset]))
  274. logger.indent += 2
  275. build_success, build_failure = [], []
  276. for req in reqset:
  277. if req.is_wheel:
  278. logger.notify("Skipping building wheel: %s", req.url)
  279. continue
  280. if self._build_one(req):
  281. build_success.append(req)
  282. else:
  283. build_failure.append(req)
  284. logger.indent -= 2
  285. #notify sucess/failure
  286. if build_success:
  287. logger.notify('Successfully built %s' % ' '.join([req.name for req in build_success]))
  288. if build_failure:
  289. logger.notify('Failed to build %s' % ' '.join([req.name for req in build_failure]))