Development of an internal social media platform with personalised dashboards for students
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

req_install.py 33KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860
  1. from __future__ import absolute_import
  2. import logging
  3. import os
  4. import shutil
  5. import sys
  6. import sysconfig
  7. import zipfile
  8. from distutils.util import change_root
  9. from pip._vendor import pkg_resources, six
  10. from pip._vendor.packaging.requirements import Requirement
  11. from pip._vendor.packaging.utils import canonicalize_name
  12. from pip._vendor.packaging.version import Version
  13. from pip._vendor.packaging.version import parse as parse_version
  14. from pip._vendor.pep517.wrappers import Pep517HookCaller
  15. from pip._internal import wheel
  16. from pip._internal.build_env import NoOpBuildEnvironment
  17. from pip._internal.exceptions import InstallationError
  18. from pip._internal.locations import (
  19. PIP_DELETE_MARKER_FILENAME, running_under_virtualenv,
  20. )
  21. from pip._internal.models.link import Link
  22. from pip._internal.pyproject import load_pyproject_toml
  23. from pip._internal.req.req_uninstall import UninstallPathSet
  24. from pip._internal.utils.compat import native_str
  25. from pip._internal.utils.hashes import Hashes
  26. from pip._internal.utils.logging import indent_log
  27. from pip._internal.utils.misc import (
  28. _make_build_dir, ask_path_exists, backup_dir, call_subprocess,
  29. display_path, dist_in_site_packages, dist_in_usersite, ensure_dir,
  30. get_installed_version, rmtree,
  31. )
  32. from pip._internal.utils.packaging import get_metadata
  33. from pip._internal.utils.setuptools_build import SETUPTOOLS_SHIM
  34. from pip._internal.utils.temp_dir import TempDirectory
  35. from pip._internal.utils.ui import open_spinner
  36. from pip._internal.vcs import vcs
  37. from pip._internal.wheel import move_wheel_files
  38. logger = logging.getLogger(__name__)
  39. class InstallRequirement(object):
  40. """
  41. Represents something that may be installed later on, may have information
  42. about where to fetch the relavant requirement and also contains logic for
  43. installing the said requirement.
  44. """
  45. def __init__(self, req, comes_from, source_dir=None, editable=False,
  46. link=None, update=True, markers=None,
  47. isolated=False, options=None, wheel_cache=None,
  48. constraint=False, extras=()):
  49. assert req is None or isinstance(req, Requirement), req
  50. self.req = req
  51. self.comes_from = comes_from
  52. self.constraint = constraint
  53. if source_dir is not None:
  54. self.source_dir = os.path.normpath(os.path.abspath(source_dir))
  55. else:
  56. self.source_dir = None
  57. self.editable = editable
  58. self._wheel_cache = wheel_cache
  59. if link is not None:
  60. self.link = self.original_link = link
  61. else:
  62. self.link = self.original_link = req and req.url and Link(req.url)
  63. if extras:
  64. self.extras = extras
  65. elif req:
  66. self.extras = {
  67. pkg_resources.safe_extra(extra) for extra in req.extras
  68. }
  69. else:
  70. self.extras = set()
  71. if markers is not None:
  72. self.markers = markers
  73. else:
  74. self.markers = req and req.marker
  75. self._egg_info_path = None
  76. # This holds the pkg_resources.Distribution object if this requirement
  77. # is already available:
  78. self.satisfied_by = None
  79. # This hold the pkg_resources.Distribution object if this requirement
  80. # conflicts with another installed distribution:
  81. self.conflicts_with = None
  82. # Temporary build location
  83. self._temp_build_dir = TempDirectory(kind="req-build")
  84. # Used to store the global directory where the _temp_build_dir should
  85. # have been created. Cf _correct_build_location method.
  86. self._ideal_build_dir = None
  87. # True if the editable should be updated:
  88. self.update = update
  89. # Set to True after successful installation
  90. self.install_succeeded = None
  91. # UninstallPathSet of uninstalled distribution (for possible rollback)
  92. self.uninstalled_pathset = None
  93. self.options = options if options else {}
  94. # Set to True after successful preparation of this requirement
  95. self.prepared = False
  96. self.is_direct = False
  97. self.isolated = isolated
  98. self.build_env = NoOpBuildEnvironment()
  99. # The static build requirements (from pyproject.toml)
  100. self.pyproject_requires = None
  101. # Build requirements that we will check are available
  102. # TODO: We don't do this for --no-build-isolation. Should we?
  103. self.requirements_to_check = []
  104. # The PEP 517 backend we should use to build the project
  105. self.pep517_backend = None
  106. # Are we using PEP 517 for this requirement?
  107. # After pyproject.toml has been loaded, the only valid values are True
  108. # and False. Before loading, None is valid (meaning "use the default").
  109. # Setting an explicit value before loading pyproject.toml is supported,
  110. # but after loading this flag should be treated as read only.
  111. self.use_pep517 = None
  112. def __str__(self):
  113. if self.req:
  114. s = str(self.req)
  115. if self.link:
  116. s += ' from %s' % self.link.url
  117. elif self.link:
  118. s = self.link.url
  119. else:
  120. s = '<InstallRequirement>'
  121. if self.satisfied_by is not None:
  122. s += ' in %s' % display_path(self.satisfied_by.location)
  123. if self.comes_from:
  124. if isinstance(self.comes_from, six.string_types):
  125. comes_from = self.comes_from
  126. else:
  127. comes_from = self.comes_from.from_path()
  128. if comes_from:
  129. s += ' (from %s)' % comes_from
  130. return s
  131. def __repr__(self):
  132. return '<%s object: %s editable=%r>' % (
  133. self.__class__.__name__, str(self), self.editable)
  134. def populate_link(self, finder, upgrade, require_hashes):
  135. """Ensure that if a link can be found for this, that it is found.
  136. Note that self.link may still be None - if Upgrade is False and the
  137. requirement is already installed.
  138. If require_hashes is True, don't use the wheel cache, because cached
  139. wheels, always built locally, have different hashes than the files
  140. downloaded from the index server and thus throw false hash mismatches.
  141. Furthermore, cached wheels at present have undeterministic contents due
  142. to file modification times.
  143. """
  144. if self.link is None:
  145. self.link = finder.find_requirement(self, upgrade)
  146. if self._wheel_cache is not None and not require_hashes:
  147. old_link = self.link
  148. self.link = self._wheel_cache.get(self.link, self.name)
  149. if old_link != self.link:
  150. logger.debug('Using cached wheel link: %s', self.link)
  151. # Things that are valid for all kinds of requirements?
  152. @property
  153. def name(self):
  154. if self.req is None:
  155. return None
  156. return native_str(pkg_resources.safe_name(self.req.name))
  157. @property
  158. def specifier(self):
  159. return self.req.specifier
  160. @property
  161. def is_pinned(self):
  162. """Return whether I am pinned to an exact version.
  163. For example, some-package==1.2 is pinned; some-package>1.2 is not.
  164. """
  165. specifiers = self.specifier
  166. return (len(specifiers) == 1 and
  167. next(iter(specifiers)).operator in {'==', '==='})
  168. @property
  169. def installed_version(self):
  170. return get_installed_version(self.name)
  171. def match_markers(self, extras_requested=None):
  172. if not extras_requested:
  173. # Provide an extra to safely evaluate the markers
  174. # without matching any extra
  175. extras_requested = ('',)
  176. if self.markers is not None:
  177. return any(
  178. self.markers.evaluate({'extra': extra})
  179. for extra in extras_requested)
  180. else:
  181. return True
  182. @property
  183. def has_hash_options(self):
  184. """Return whether any known-good hashes are specified as options.
  185. These activate --require-hashes mode; hashes specified as part of a
  186. URL do not.
  187. """
  188. return bool(self.options.get('hashes', {}))
  189. def hashes(self, trust_internet=True):
  190. """Return a hash-comparer that considers my option- and URL-based
  191. hashes to be known-good.
  192. Hashes in URLs--ones embedded in the requirements file, not ones
  193. downloaded from an index server--are almost peers with ones from
  194. flags. They satisfy --require-hashes (whether it was implicitly or
  195. explicitly activated) but do not activate it. md5 and sha224 are not
  196. allowed in flags, which should nudge people toward good algos. We
  197. always OR all hashes together, even ones from URLs.
  198. :param trust_internet: Whether to trust URL-based (#md5=...) hashes
  199. downloaded from the internet, as by populate_link()
  200. """
  201. good_hashes = self.options.get('hashes', {}).copy()
  202. link = self.link if trust_internet else self.original_link
  203. if link and link.hash:
  204. good_hashes.setdefault(link.hash_name, []).append(link.hash)
  205. return Hashes(good_hashes)
  206. def from_path(self):
  207. """Format a nice indicator to show where this "comes from"
  208. """
  209. if self.req is None:
  210. return None
  211. s = str(self.req)
  212. if self.comes_from:
  213. if isinstance(self.comes_from, six.string_types):
  214. comes_from = self.comes_from
  215. else:
  216. comes_from = self.comes_from.from_path()
  217. if comes_from:
  218. s += '->' + comes_from
  219. return s
  220. def build_location(self, build_dir):
  221. assert build_dir is not None
  222. if self._temp_build_dir.path is not None:
  223. return self._temp_build_dir.path
  224. if self.req is None:
  225. # for requirement via a path to a directory: the name of the
  226. # package is not available yet so we create a temp directory
  227. # Once run_egg_info will have run, we'll be able
  228. # to fix it via _correct_build_location
  229. # Some systems have /tmp as a symlink which confuses custom
  230. # builds (such as numpy). Thus, we ensure that the real path
  231. # is returned.
  232. self._temp_build_dir.create()
  233. self._ideal_build_dir = build_dir
  234. return self._temp_build_dir.path
  235. if self.editable:
  236. name = self.name.lower()
  237. else:
  238. name = self.name
  239. # FIXME: Is there a better place to create the build_dir? (hg and bzr
  240. # need this)
  241. if not os.path.exists(build_dir):
  242. logger.debug('Creating directory %s', build_dir)
  243. _make_build_dir(build_dir)
  244. return os.path.join(build_dir, name)
  245. def _correct_build_location(self):
  246. """Move self._temp_build_dir to self._ideal_build_dir/self.req.name
  247. For some requirements (e.g. a path to a directory), the name of the
  248. package is not available until we run egg_info, so the build_location
  249. will return a temporary directory and store the _ideal_build_dir.
  250. This is only called by self.run_egg_info to fix the temporary build
  251. directory.
  252. """
  253. if self.source_dir is not None:
  254. return
  255. assert self.req is not None
  256. assert self._temp_build_dir.path
  257. assert self._ideal_build_dir.path
  258. old_location = self._temp_build_dir.path
  259. self._temp_build_dir.path = None
  260. new_location = self.build_location(self._ideal_build_dir)
  261. if os.path.exists(new_location):
  262. raise InstallationError(
  263. 'A package already exists in %s; please remove it to continue'
  264. % display_path(new_location))
  265. logger.debug(
  266. 'Moving package %s from %s to new location %s',
  267. self, display_path(old_location), display_path(new_location),
  268. )
  269. shutil.move(old_location, new_location)
  270. self._temp_build_dir.path = new_location
  271. self._ideal_build_dir = None
  272. self.source_dir = os.path.normpath(os.path.abspath(new_location))
  273. self._egg_info_path = None
  274. def remove_temporary_source(self):
  275. """Remove the source files from this requirement, if they are marked
  276. for deletion"""
  277. if self.source_dir and os.path.exists(
  278. os.path.join(self.source_dir, PIP_DELETE_MARKER_FILENAME)):
  279. logger.debug('Removing source in %s', self.source_dir)
  280. rmtree(self.source_dir)
  281. self.source_dir = None
  282. self._temp_build_dir.cleanup()
  283. self.build_env.cleanup()
  284. def check_if_exists(self, use_user_site):
  285. """Find an installed distribution that satisfies or conflicts
  286. with this requirement, and set self.satisfied_by or
  287. self.conflicts_with appropriately.
  288. """
  289. if self.req is None:
  290. return False
  291. try:
  292. # get_distribution() will resolve the entire list of requirements
  293. # anyway, and we've already determined that we need the requirement
  294. # in question, so strip the marker so that we don't try to
  295. # evaluate it.
  296. no_marker = Requirement(str(self.req))
  297. no_marker.marker = None
  298. self.satisfied_by = pkg_resources.get_distribution(str(no_marker))
  299. if self.editable and self.satisfied_by:
  300. self.conflicts_with = self.satisfied_by
  301. # when installing editables, nothing pre-existing should ever
  302. # satisfy
  303. self.satisfied_by = None
  304. return True
  305. except pkg_resources.DistributionNotFound:
  306. return False
  307. except pkg_resources.VersionConflict:
  308. existing_dist = pkg_resources.get_distribution(
  309. self.req.name
  310. )
  311. if use_user_site:
  312. if dist_in_usersite(existing_dist):
  313. self.conflicts_with = existing_dist
  314. elif (running_under_virtualenv() and
  315. dist_in_site_packages(existing_dist)):
  316. raise InstallationError(
  317. "Will not install to the user site because it will "
  318. "lack sys.path precedence to %s in %s" %
  319. (existing_dist.project_name, existing_dist.location)
  320. )
  321. else:
  322. self.conflicts_with = existing_dist
  323. return True
  324. # Things valid for wheels
  325. @property
  326. def is_wheel(self):
  327. return self.link and self.link.is_wheel
  328. def move_wheel_files(self, wheeldir, root=None, home=None, prefix=None,
  329. warn_script_location=True, use_user_site=False,
  330. pycompile=True):
  331. move_wheel_files(
  332. self.name, self.req, wheeldir,
  333. user=use_user_site,
  334. home=home,
  335. root=root,
  336. prefix=prefix,
  337. pycompile=pycompile,
  338. isolated=self.isolated,
  339. warn_script_location=warn_script_location,
  340. )
  341. # Things valid for sdists
  342. @property
  343. def setup_py_dir(self):
  344. return os.path.join(
  345. self.source_dir,
  346. self.link and self.link.subdirectory_fragment or '')
  347. @property
  348. def setup_py(self):
  349. assert self.source_dir, "No source dir for %s" % self
  350. setup_py = os.path.join(self.setup_py_dir, 'setup.py')
  351. # Python2 __file__ should not be unicode
  352. if six.PY2 and isinstance(setup_py, six.text_type):
  353. setup_py = setup_py.encode(sys.getfilesystemencoding())
  354. return setup_py
  355. @property
  356. def pyproject_toml(self):
  357. assert self.source_dir, "No source dir for %s" % self
  358. pp_toml = os.path.join(self.setup_py_dir, 'pyproject.toml')
  359. # Python2 __file__ should not be unicode
  360. if six.PY2 and isinstance(pp_toml, six.text_type):
  361. pp_toml = pp_toml.encode(sys.getfilesystemencoding())
  362. return pp_toml
  363. def load_pyproject_toml(self):
  364. """Load the pyproject.toml file.
  365. After calling this routine, all of the attributes related to PEP 517
  366. processing for this requirement have been set. In particular, the
  367. use_pep517 attribute can be used to determine whether we should
  368. follow the PEP 517 or legacy (setup.py) code path.
  369. """
  370. pep517_data = load_pyproject_toml(
  371. self.use_pep517,
  372. self.pyproject_toml,
  373. self.setup_py,
  374. str(self)
  375. )
  376. if pep517_data is None:
  377. self.use_pep517 = False
  378. else:
  379. self.use_pep517 = True
  380. requires, backend, check = pep517_data
  381. self.requirements_to_check = check
  382. self.pyproject_requires = requires
  383. self.pep517_backend = Pep517HookCaller(self.setup_py_dir, backend)
  384. def run_egg_info(self):
  385. assert self.source_dir
  386. if self.name:
  387. logger.debug(
  388. 'Running setup.py (path:%s) egg_info for package %s',
  389. self.setup_py, self.name,
  390. )
  391. else:
  392. logger.debug(
  393. 'Running setup.py (path:%s) egg_info for package from %s',
  394. self.setup_py, self.link,
  395. )
  396. with indent_log():
  397. script = SETUPTOOLS_SHIM % self.setup_py
  398. base_cmd = [sys.executable, '-c', script]
  399. if self.isolated:
  400. base_cmd += ["--no-user-cfg"]
  401. egg_info_cmd = base_cmd + ['egg_info']
  402. # We can't put the .egg-info files at the root, because then the
  403. # source code will be mistaken for an installed egg, causing
  404. # problems
  405. if self.editable:
  406. egg_base_option = []
  407. else:
  408. egg_info_dir = os.path.join(self.setup_py_dir, 'pip-egg-info')
  409. ensure_dir(egg_info_dir)
  410. egg_base_option = ['--egg-base', 'pip-egg-info']
  411. with self.build_env:
  412. call_subprocess(
  413. egg_info_cmd + egg_base_option,
  414. cwd=self.setup_py_dir,
  415. show_stdout=False,
  416. command_desc='python setup.py egg_info')
  417. if not self.req:
  418. if isinstance(parse_version(self.metadata["Version"]), Version):
  419. op = "=="
  420. else:
  421. op = "==="
  422. self.req = Requirement(
  423. "".join([
  424. self.metadata["Name"],
  425. op,
  426. self.metadata["Version"],
  427. ])
  428. )
  429. self._correct_build_location()
  430. else:
  431. metadata_name = canonicalize_name(self.metadata["Name"])
  432. if canonicalize_name(self.req.name) != metadata_name:
  433. logger.warning(
  434. 'Running setup.py (path:%s) egg_info for package %s '
  435. 'produced metadata for project name %s. Fix your '
  436. '#egg=%s fragments.',
  437. self.setup_py, self.name, metadata_name, self.name
  438. )
  439. self.req = Requirement(metadata_name)
  440. @property
  441. def egg_info_path(self):
  442. if self._egg_info_path is None:
  443. if self.editable:
  444. base = self.source_dir
  445. else:
  446. base = os.path.join(self.setup_py_dir, 'pip-egg-info')
  447. filenames = os.listdir(base)
  448. if self.editable:
  449. filenames = []
  450. for root, dirs, files in os.walk(base):
  451. for dir in vcs.dirnames:
  452. if dir in dirs:
  453. dirs.remove(dir)
  454. # Iterate over a copy of ``dirs``, since mutating
  455. # a list while iterating over it can cause trouble.
  456. # (See https://github.com/pypa/pip/pull/462.)
  457. for dir in list(dirs):
  458. # Don't search in anything that looks like a virtualenv
  459. # environment
  460. if (
  461. os.path.lexists(
  462. os.path.join(root, dir, 'bin', 'python')
  463. ) or
  464. os.path.exists(
  465. os.path.join(
  466. root, dir, 'Scripts', 'Python.exe'
  467. )
  468. )):
  469. dirs.remove(dir)
  470. # Also don't search through tests
  471. elif dir == 'test' or dir == 'tests':
  472. dirs.remove(dir)
  473. filenames.extend([os.path.join(root, dir)
  474. for dir in dirs])
  475. filenames = [f for f in filenames if f.endswith('.egg-info')]
  476. if not filenames:
  477. raise InstallationError(
  478. "Files/directories not found in %s" % base
  479. )
  480. # if we have more than one match, we pick the toplevel one. This
  481. # can easily be the case if there is a dist folder which contains
  482. # an extracted tarball for testing purposes.
  483. if len(filenames) > 1:
  484. filenames.sort(
  485. key=lambda x: x.count(os.path.sep) +
  486. (os.path.altsep and x.count(os.path.altsep) or 0)
  487. )
  488. self._egg_info_path = os.path.join(base, filenames[0])
  489. return self._egg_info_path
  490. @property
  491. def metadata(self):
  492. if not hasattr(self, '_metadata'):
  493. self._metadata = get_metadata(self.get_dist())
  494. return self._metadata
  495. def get_dist(self):
  496. """Return a pkg_resources.Distribution built from self.egg_info_path"""
  497. egg_info = self.egg_info_path.rstrip(os.path.sep)
  498. base_dir = os.path.dirname(egg_info)
  499. metadata = pkg_resources.PathMetadata(base_dir, egg_info)
  500. dist_name = os.path.splitext(os.path.basename(egg_info))[0]
  501. return pkg_resources.Distribution(
  502. os.path.dirname(egg_info),
  503. project_name=dist_name,
  504. metadata=metadata,
  505. )
  506. def assert_source_matches_version(self):
  507. assert self.source_dir
  508. version = self.metadata['version']
  509. if self.req.specifier and version not in self.req.specifier:
  510. logger.warning(
  511. 'Requested %s, but installing version %s',
  512. self,
  513. version,
  514. )
  515. else:
  516. logger.debug(
  517. 'Source in %s has version %s, which satisfies requirement %s',
  518. display_path(self.source_dir),
  519. version,
  520. self,
  521. )
  522. # For both source distributions and editables
  523. def ensure_has_source_dir(self, parent_dir):
  524. """Ensure that a source_dir is set.
  525. This will create a temporary build dir if the name of the requirement
  526. isn't known yet.
  527. :param parent_dir: The ideal pip parent_dir for the source_dir.
  528. Generally src_dir for editables and build_dir for sdists.
  529. :return: self.source_dir
  530. """
  531. if self.source_dir is None:
  532. self.source_dir = self.build_location(parent_dir)
  533. return self.source_dir
  534. # For editable installations
  535. def install_editable(self, install_options,
  536. global_options=(), prefix=None):
  537. logger.info('Running setup.py develop for %s', self.name)
  538. if self.isolated:
  539. global_options = list(global_options) + ["--no-user-cfg"]
  540. if prefix:
  541. prefix_param = ['--prefix={}'.format(prefix)]
  542. install_options = list(install_options) + prefix_param
  543. with indent_log():
  544. # FIXME: should we do --install-headers here too?
  545. with self.build_env:
  546. call_subprocess(
  547. [
  548. sys.executable,
  549. '-c',
  550. SETUPTOOLS_SHIM % self.setup_py
  551. ] +
  552. list(global_options) +
  553. ['develop', '--no-deps'] +
  554. list(install_options),
  555. cwd=self.setup_py_dir,
  556. show_stdout=False,
  557. )
  558. self.install_succeeded = True
  559. def update_editable(self, obtain=True):
  560. if not self.link:
  561. logger.debug(
  562. "Cannot update repository at %s; repository location is "
  563. "unknown",
  564. self.source_dir,
  565. )
  566. return
  567. assert self.editable
  568. assert self.source_dir
  569. if self.link.scheme == 'file':
  570. # Static paths don't get updated
  571. return
  572. assert '+' in self.link.url, "bad url: %r" % self.link.url
  573. if not self.update:
  574. return
  575. vc_type, url = self.link.url.split('+', 1)
  576. backend = vcs.get_backend(vc_type)
  577. if backend:
  578. vcs_backend = backend(self.link.url)
  579. if obtain:
  580. vcs_backend.obtain(self.source_dir)
  581. else:
  582. vcs_backend.export(self.source_dir)
  583. else:
  584. assert 0, (
  585. 'Unexpected version control type (in %s): %s'
  586. % (self.link, vc_type))
  587. # Top-level Actions
  588. def uninstall(self, auto_confirm=False, verbose=False,
  589. use_user_site=False):
  590. """
  591. Uninstall the distribution currently satisfying this requirement.
  592. Prompts before removing or modifying files unless
  593. ``auto_confirm`` is True.
  594. Refuses to delete or modify files outside of ``sys.prefix`` -
  595. thus uninstallation within a virtual environment can only
  596. modify that virtual environment, even if the virtualenv is
  597. linked to global site-packages.
  598. """
  599. if not self.check_if_exists(use_user_site):
  600. logger.warning("Skipping %s as it is not installed.", self.name)
  601. return
  602. dist = self.satisfied_by or self.conflicts_with
  603. uninstalled_pathset = UninstallPathSet.from_dist(dist)
  604. uninstalled_pathset.remove(auto_confirm, verbose)
  605. return uninstalled_pathset
  606. def _clean_zip_name(self, name, prefix): # only used by archive.
  607. assert name.startswith(prefix + os.path.sep), (
  608. "name %r doesn't start with prefix %r" % (name, prefix)
  609. )
  610. name = name[len(prefix) + 1:]
  611. name = name.replace(os.path.sep, '/')
  612. return name
  613. # TODO: Investigate if this should be kept in InstallRequirement
  614. # Seems to be used only when VCS + downloads
  615. def archive(self, build_dir):
  616. assert self.source_dir
  617. create_archive = True
  618. archive_name = '%s-%s.zip' % (self.name, self.metadata["version"])
  619. archive_path = os.path.join(build_dir, archive_name)
  620. if os.path.exists(archive_path):
  621. response = ask_path_exists(
  622. 'The file %s exists. (i)gnore, (w)ipe, (b)ackup, (a)bort ' %
  623. display_path(archive_path), ('i', 'w', 'b', 'a'))
  624. if response == 'i':
  625. create_archive = False
  626. elif response == 'w':
  627. logger.warning('Deleting %s', display_path(archive_path))
  628. os.remove(archive_path)
  629. elif response == 'b':
  630. dest_file = backup_dir(archive_path)
  631. logger.warning(
  632. 'Backing up %s to %s',
  633. display_path(archive_path),
  634. display_path(dest_file),
  635. )
  636. shutil.move(archive_path, dest_file)
  637. elif response == 'a':
  638. sys.exit(-1)
  639. if create_archive:
  640. zip = zipfile.ZipFile(
  641. archive_path, 'w', zipfile.ZIP_DEFLATED,
  642. allowZip64=True
  643. )
  644. dir = os.path.normcase(os.path.abspath(self.setup_py_dir))
  645. for dirpath, dirnames, filenames in os.walk(dir):
  646. if 'pip-egg-info' in dirnames:
  647. dirnames.remove('pip-egg-info')
  648. for dirname in dirnames:
  649. dirname = os.path.join(dirpath, dirname)
  650. name = self._clean_zip_name(dirname, dir)
  651. zipdir = zipfile.ZipInfo(self.name + '/' + name + '/')
  652. zipdir.external_attr = 0x1ED << 16 # 0o755
  653. zip.writestr(zipdir, '')
  654. for filename in filenames:
  655. if filename == PIP_DELETE_MARKER_FILENAME:
  656. continue
  657. filename = os.path.join(dirpath, filename)
  658. name = self._clean_zip_name(filename, dir)
  659. zip.write(filename, self.name + '/' + name)
  660. zip.close()
  661. logger.info('Saved %s', display_path(archive_path))
  662. def install(self, install_options, global_options=None, root=None,
  663. home=None, prefix=None, warn_script_location=True,
  664. use_user_site=False, pycompile=True):
  665. global_options = global_options if global_options is not None else []
  666. if self.editable:
  667. self.install_editable(
  668. install_options, global_options, prefix=prefix,
  669. )
  670. return
  671. if self.is_wheel:
  672. version = wheel.wheel_version(self.source_dir)
  673. wheel.check_compatibility(version, self.name)
  674. self.move_wheel_files(
  675. self.source_dir, root=root, prefix=prefix, home=home,
  676. warn_script_location=warn_script_location,
  677. use_user_site=use_user_site, pycompile=pycompile,
  678. )
  679. self.install_succeeded = True
  680. return
  681. # Extend the list of global and install options passed on to
  682. # the setup.py call with the ones from the requirements file.
  683. # Options specified in requirements file override those
  684. # specified on the command line, since the last option given
  685. # to setup.py is the one that is used.
  686. global_options = list(global_options) + \
  687. self.options.get('global_options', [])
  688. install_options = list(install_options) + \
  689. self.options.get('install_options', [])
  690. if self.isolated:
  691. global_options = global_options + ["--no-user-cfg"]
  692. with TempDirectory(kind="record") as temp_dir:
  693. record_filename = os.path.join(temp_dir.path, 'install-record.txt')
  694. install_args = self.get_install_args(
  695. global_options, record_filename, root, prefix, pycompile,
  696. )
  697. msg = 'Running setup.py install for %s' % (self.name,)
  698. with open_spinner(msg) as spinner:
  699. with indent_log():
  700. with self.build_env:
  701. call_subprocess(
  702. install_args + install_options,
  703. cwd=self.setup_py_dir,
  704. show_stdout=False,
  705. spinner=spinner,
  706. )
  707. if not os.path.exists(record_filename):
  708. logger.debug('Record file %s not found', record_filename)
  709. return
  710. self.install_succeeded = True
  711. def prepend_root(path):
  712. if root is None or not os.path.isabs(path):
  713. return path
  714. else:
  715. return change_root(root, path)
  716. with open(record_filename) as f:
  717. for line in f:
  718. directory = os.path.dirname(line)
  719. if directory.endswith('.egg-info'):
  720. egg_info_dir = prepend_root(directory)
  721. break
  722. else:
  723. logger.warning(
  724. 'Could not find .egg-info directory in install record'
  725. ' for %s',
  726. self,
  727. )
  728. # FIXME: put the record somewhere
  729. # FIXME: should this be an error?
  730. return
  731. new_lines = []
  732. with open(record_filename) as f:
  733. for line in f:
  734. filename = line.strip()
  735. if os.path.isdir(filename):
  736. filename += os.path.sep
  737. new_lines.append(
  738. os.path.relpath(prepend_root(filename), egg_info_dir)
  739. )
  740. new_lines.sort()
  741. ensure_dir(egg_info_dir)
  742. inst_files_path = os.path.join(egg_info_dir, 'installed-files.txt')
  743. with open(inst_files_path, 'w') as f:
  744. f.write('\n'.join(new_lines) + '\n')
  745. def get_install_args(self, global_options, record_filename, root, prefix,
  746. pycompile):
  747. install_args = [sys.executable, "-u"]
  748. install_args.append('-c')
  749. install_args.append(SETUPTOOLS_SHIM % self.setup_py)
  750. install_args += list(global_options) + \
  751. ['install', '--record', record_filename]
  752. install_args += ['--single-version-externally-managed']
  753. if root is not None:
  754. install_args += ['--root', root]
  755. if prefix is not None:
  756. install_args += ['--prefix', prefix]
  757. if pycompile:
  758. install_args += ["--compile"]
  759. else:
  760. install_args += ["--no-compile"]
  761. if running_under_virtualenv():
  762. py_ver_str = 'python' + sysconfig.get_python_version()
  763. install_args += ['--install-headers',
  764. os.path.join(sys.prefix, 'include', 'site',
  765. py_ver_str, self.name)]
  766. return install_args