Mercurial > repos > bcclaywell > argo_navis
comparison venv/lib/python2.7/site-packages/pip/req/req_set.py @ 0:d67268158946 draft
planemo upload commit a3f181f5f126803c654b3a66dd4e83a48f7e203b
| author | bcclaywell |
|---|---|
| date | Mon, 12 Oct 2015 17:43:33 -0400 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| -1:000000000000 | 0:d67268158946 |
|---|---|
| 1 from __future__ import absolute_import | |
| 2 | |
| 3 from collections import defaultdict | |
| 4 import functools | |
| 5 import itertools | |
| 6 import logging | |
| 7 import os | |
| 8 | |
| 9 from pip._vendor import pkg_resources | |
| 10 from pip._vendor import requests | |
| 11 | |
| 12 from pip.download import (url_to_path, unpack_url) | |
| 13 from pip.exceptions import (InstallationError, BestVersionAlreadyInstalled, | |
| 14 DistributionNotFound, PreviousBuildDirError) | |
| 15 from pip.locations import (PIP_DELETE_MARKER_FILENAME, build_prefix) | |
| 16 from pip.req.req_install import InstallRequirement | |
| 17 from pip.utils import (display_path, rmtree, dist_in_usersite, normalize_path) | |
| 18 from pip.utils.logging import indent_log | |
| 19 from pip.vcs import vcs | |
| 20 | |
| 21 | |
| 22 logger = logging.getLogger(__name__) | |
| 23 | |
| 24 | |
| 25 class Requirements(object): | |
| 26 | |
| 27 def __init__(self): | |
| 28 self._keys = [] | |
| 29 self._dict = {} | |
| 30 | |
| 31 def keys(self): | |
| 32 return self._keys | |
| 33 | |
| 34 def values(self): | |
| 35 return [self._dict[key] for key in self._keys] | |
| 36 | |
| 37 def __contains__(self, item): | |
| 38 return item in self._keys | |
| 39 | |
| 40 def __setitem__(self, key, value): | |
| 41 if key not in self._keys: | |
| 42 self._keys.append(key) | |
| 43 self._dict[key] = value | |
| 44 | |
| 45 def __getitem__(self, key): | |
| 46 return self._dict[key] | |
| 47 | |
| 48 def __repr__(self): | |
| 49 values = ['%s: %s' % (repr(k), repr(self[k])) for k in self.keys()] | |
| 50 return 'Requirements({%s})' % ', '.join(values) | |
| 51 | |
| 52 | |
| 53 class DistAbstraction(object): | |
| 54 """Abstracts out the wheel vs non-wheel prepare_files logic. | |
| 55 | |
| 56 The requirements for anything installable are as follows: | |
| 57 - we must be able to determine the requirement name | |
| 58 (or we can't correctly handle the non-upgrade case). | |
| 59 - we must be able to generate a list of run-time dependencies | |
| 60 without installing any additional packages (or we would | |
| 61 have to either burn time by doing temporary isolated installs | |
| 62 or alternatively violate pips 'don't start installing unless | |
| 63 all requirements are available' rule - neither of which are | |
| 64 desirable). | |
| 65 - for packages with setup requirements, we must also be able | |
| 66 to determine their requirements without installing additional | |
| 67 packages (for the same reason as run-time dependencies) | |
| 68 - we must be able to create a Distribution object exposing the | |
| 69 above metadata. | |
| 70 """ | |
| 71 | |
| 72 def __init__(self, req_to_install): | |
| 73 self.req_to_install = req_to_install | |
| 74 | |
| 75 def dist(self, finder): | |
| 76 """Return a setuptools Dist object.""" | |
| 77 raise NotImplementedError(self.dist) | |
| 78 | |
| 79 def prep_for_dist(self): | |
| 80 """Ensure that we can get a Dist for this requirement.""" | |
| 81 raise NotImplementedError(self.dist) | |
| 82 | |
| 83 | |
| 84 def make_abstract_dist(req_to_install): | |
| 85 """Factory to make an abstract dist object. | |
| 86 | |
| 87 Preconditions: Either an editable req with a source_dir, or satisfied_by or | |
| 88 a wheel link, or a non-editable req with a source_dir. | |
| 89 | |
| 90 :return: A concrete DistAbstraction. | |
| 91 """ | |
| 92 if req_to_install.editable: | |
| 93 return IsSDist(req_to_install) | |
| 94 elif req_to_install.link and req_to_install.link.is_wheel: | |
| 95 return IsWheel(req_to_install) | |
| 96 else: | |
| 97 return IsSDist(req_to_install) | |
| 98 | |
| 99 | |
| 100 class IsWheel(DistAbstraction): | |
| 101 | |
| 102 def dist(self, finder): | |
| 103 return list(pkg_resources.find_distributions( | |
| 104 self.req_to_install.source_dir))[0] | |
| 105 | |
| 106 def prep_for_dist(self): | |
| 107 # FIXME:https://github.com/pypa/pip/issues/1112 | |
| 108 pass | |
| 109 | |
| 110 | |
| 111 class IsSDist(DistAbstraction): | |
| 112 | |
| 113 def dist(self, finder): | |
| 114 dist = self.req_to_install.get_dist() | |
| 115 # FIXME: shouldn't be globally added: | |
| 116 if dist.has_metadata('dependency_links.txt'): | |
| 117 finder.add_dependency_links( | |
| 118 dist.get_metadata_lines('dependency_links.txt') | |
| 119 ) | |
| 120 return dist | |
| 121 | |
| 122 def prep_for_dist(self): | |
| 123 self.req_to_install.run_egg_info() | |
| 124 self.req_to_install.assert_source_matches_version() | |
| 125 | |
| 126 | |
| 127 class Installed(DistAbstraction): | |
| 128 | |
| 129 def dist(self, finder): | |
| 130 return self.req_to_install.satisfied_by | |
| 131 | |
| 132 def prep_for_dist(self): | |
| 133 pass | |
| 134 | |
| 135 | |
| 136 class RequirementSet(object): | |
| 137 | |
| 138 def __init__(self, build_dir, src_dir, download_dir, upgrade=False, | |
| 139 ignore_installed=False, as_egg=False, target_dir=None, | |
| 140 ignore_dependencies=False, force_reinstall=False, | |
| 141 use_user_site=False, session=None, pycompile=True, | |
| 142 isolated=False, wheel_download_dir=None): | |
| 143 if session is None: | |
| 144 raise TypeError( | |
| 145 "RequirementSet() missing 1 required keyword argument: " | |
| 146 "'session'" | |
| 147 ) | |
| 148 | |
| 149 self.build_dir = build_dir | |
| 150 self.src_dir = src_dir | |
| 151 # XXX: download_dir and wheel_download_dir overlap semantically and may | |
| 152 # be combinable. | |
| 153 self.download_dir = download_dir | |
| 154 self.upgrade = upgrade | |
| 155 self.ignore_installed = ignore_installed | |
| 156 self.force_reinstall = force_reinstall | |
| 157 self.requirements = Requirements() | |
| 158 # Mapping of alias: real_name | |
| 159 self.requirement_aliases = {} | |
| 160 self.unnamed_requirements = [] | |
| 161 self.ignore_dependencies = ignore_dependencies | |
| 162 self.successfully_downloaded = [] | |
| 163 self.successfully_installed = [] | |
| 164 self.reqs_to_cleanup = [] | |
| 165 self.as_egg = as_egg | |
| 166 self.use_user_site = use_user_site | |
| 167 self.target_dir = target_dir # set from --target option | |
| 168 self.session = session | |
| 169 self.pycompile = pycompile | |
| 170 self.isolated = isolated | |
| 171 if wheel_download_dir: | |
| 172 wheel_download_dir = normalize_path(wheel_download_dir) | |
| 173 self.wheel_download_dir = wheel_download_dir | |
| 174 # Maps from install_req -> dependencies_of_install_req | |
| 175 self._dependencies = defaultdict(list) | |
| 176 | |
| 177 def __str__(self): | |
| 178 reqs = [req for req in self.requirements.values() | |
| 179 if not req.comes_from] | |
| 180 reqs.sort(key=lambda req: req.name.lower()) | |
| 181 return ' '.join([str(req.req) for req in reqs]) | |
| 182 | |
| 183 def __repr__(self): | |
| 184 reqs = [req for req in self.requirements.values()] | |
| 185 reqs.sort(key=lambda req: req.name.lower()) | |
| 186 reqs_str = ', '.join([str(req.req) for req in reqs]) | |
| 187 return ('<%s object; %d requirement(s): %s>' | |
| 188 % (self.__class__.__name__, len(reqs), reqs_str)) | |
| 189 | |
| 190 def add_requirement(self, install_req, parent_req_name=None): | |
| 191 """Add install_req as a requirement to install. | |
| 192 | |
| 193 :param parent_req_name: The name of the requirement that needed this | |
| 194 added. The name is used because when multiple unnamed requirements | |
| 195 resolve to the same name, we could otherwise end up with dependency | |
| 196 links that point outside the Requirements set. parent_req must | |
| 197 already be added. Note that None implies that this is a user | |
| 198 supplied requirement, vs an inferred one. | |
| 199 :return: Additional requirements to scan. That is either [] if | |
| 200 the requirement is not applicable, or [install_req] if the | |
| 201 requirement is applicable and has just been added. | |
| 202 """ | |
| 203 name = install_req.name | |
| 204 if ((not name or not self.has_requirement(name)) and not | |
| 205 install_req.match_markers()): | |
| 206 # Only log if we haven't already got install_req from somewhere. | |
| 207 logger.debug("Ignore %s: markers %r don't match", | |
| 208 install_req.name, install_req.markers) | |
| 209 return [] | |
| 210 | |
| 211 install_req.as_egg = self.as_egg | |
| 212 install_req.use_user_site = self.use_user_site | |
| 213 install_req.target_dir = self.target_dir | |
| 214 install_req.pycompile = self.pycompile | |
| 215 if not name: | |
| 216 # url or path requirement w/o an egg fragment | |
| 217 self.unnamed_requirements.append(install_req) | |
| 218 return [install_req] | |
| 219 else: | |
| 220 if parent_req_name is None and self.has_requirement(name): | |
| 221 raise InstallationError( | |
| 222 'Double requirement given: %s (already in %s, name=%r)' | |
| 223 % (install_req, self.get_requirement(name), name)) | |
| 224 if not self.has_requirement(name): | |
| 225 # Add requirement | |
| 226 self.requirements[name] = install_req | |
| 227 # FIXME: what about other normalizations? E.g., _ vs. -? | |
| 228 if name.lower() != name: | |
| 229 self.requirement_aliases[name.lower()] = name | |
| 230 result = [install_req] | |
| 231 else: | |
| 232 # Canonicalise to the already-added object | |
| 233 install_req = self.get_requirement(name) | |
| 234 # No need to scan, this is a duplicate requirement. | |
| 235 result = [] | |
| 236 if parent_req_name: | |
| 237 parent_req = self.get_requirement(parent_req_name) | |
| 238 self._dependencies[parent_req].append(install_req) | |
| 239 return result | |
| 240 | |
| 241 def has_requirement(self, project_name): | |
| 242 for name in project_name, project_name.lower(): | |
| 243 if name in self.requirements or name in self.requirement_aliases: | |
| 244 return True | |
| 245 return False | |
| 246 | |
| 247 @property | |
| 248 def has_requirements(self): | |
| 249 return list(self.requirements.values()) or self.unnamed_requirements | |
| 250 | |
| 251 @property | |
| 252 def is_download(self): | |
| 253 if self.download_dir: | |
| 254 self.download_dir = os.path.expanduser(self.download_dir) | |
| 255 if os.path.exists(self.download_dir): | |
| 256 return True | |
| 257 else: | |
| 258 logger.critical('Could not find download directory') | |
| 259 raise InstallationError( | |
| 260 "Could not find or access download directory '%s'" | |
| 261 % display_path(self.download_dir)) | |
| 262 return False | |
| 263 | |
| 264 def get_requirement(self, project_name): | |
| 265 for name in project_name, project_name.lower(): | |
| 266 if name in self.requirements: | |
| 267 return self.requirements[name] | |
| 268 if name in self.requirement_aliases: | |
| 269 return self.requirements[self.requirement_aliases[name]] | |
| 270 raise KeyError("No project with the name %r" % project_name) | |
| 271 | |
| 272 def uninstall(self, auto_confirm=False): | |
| 273 for req in self.requirements.values(): | |
| 274 req.uninstall(auto_confirm=auto_confirm) | |
| 275 req.commit_uninstall() | |
| 276 | |
| 277 def _walk_req_to_install(self, handler): | |
| 278 """Call handler for all pending reqs. | |
| 279 | |
| 280 :param handler: Handle a single requirement. Should take a requirement | |
| 281 to install. Can optionally return an iterable of additional | |
| 282 InstallRequirements to cover. | |
| 283 """ | |
| 284 # The list() here is to avoid potential mutate-while-iterating bugs. | |
| 285 discovered_reqs = [] | |
| 286 reqs = itertools.chain( | |
| 287 list(self.unnamed_requirements), list(self.requirements.values()), | |
| 288 discovered_reqs) | |
| 289 for req_to_install in reqs: | |
| 290 more_reqs = handler(req_to_install) | |
| 291 if more_reqs: | |
| 292 discovered_reqs.extend(more_reqs) | |
| 293 | |
| 294 def locate_files(self): | |
| 295 """Remove in 7.0: used by --no-download""" | |
| 296 self._walk_req_to_install(self._locate_file) | |
| 297 | |
| 298 def _locate_file(self, req_to_install): | |
| 299 install_needed = True | |
| 300 if not self.ignore_installed and not req_to_install.editable: | |
| 301 req_to_install.check_if_exists() | |
| 302 if req_to_install.satisfied_by: | |
| 303 if self.upgrade: | |
| 304 # don't uninstall conflict if user install and | |
| 305 # conflict is not user install | |
| 306 if not (self.use_user_site and | |
| 307 not dist_in_usersite( | |
| 308 req_to_install.satisfied_by | |
| 309 )): | |
| 310 req_to_install.conflicts_with = \ | |
| 311 req_to_install.satisfied_by | |
| 312 req_to_install.satisfied_by = None | |
| 313 else: | |
| 314 install_needed = False | |
| 315 logger.info( | |
| 316 'Requirement already satisfied (use --upgrade to ' | |
| 317 'upgrade): %s', | |
| 318 req_to_install, | |
| 319 ) | |
| 320 | |
| 321 if req_to_install.editable: | |
| 322 if req_to_install.source_dir is None: | |
| 323 req_to_install.source_dir = req_to_install.build_location( | |
| 324 self.src_dir | |
| 325 ) | |
| 326 elif install_needed: | |
| 327 req_to_install.source_dir = req_to_install.build_location( | |
| 328 self.build_dir, | |
| 329 ) | |
| 330 | |
| 331 if (req_to_install.source_dir is not None and not | |
| 332 os.path.isdir(req_to_install.source_dir)): | |
| 333 raise InstallationError( | |
| 334 'Could not install requirement %s because source folder %s' | |
| 335 ' does not exist (perhaps --no-download was used without ' | |
| 336 'first running an equivalent install with --no-install?)' % | |
| 337 (req_to_install, req_to_install.source_dir) | |
| 338 ) | |
| 339 | |
| 340 def prepare_files(self, finder): | |
| 341 """ | |
| 342 Prepare process. Create temp directories, download and/or unpack files. | |
| 343 """ | |
| 344 self._walk_req_to_install( | |
| 345 functools.partial(self._prepare_file, finder)) | |
| 346 | |
| 347 def _check_skip_installed(self, req_to_install, finder): | |
| 348 """Check if req_to_install should be skipped. | |
| 349 | |
| 350 This will check if the req is installed, and whether we should upgrade | |
| 351 or reinstall it, taking into account all the relevant user options. | |
| 352 | |
| 353 After calling this req_to_install will only have satisfied_by set to | |
| 354 None if the req_to_install is to be upgraded/reinstalled etc. Any | |
| 355 other value will be a dist recording the current thing installed that | |
| 356 satisfies the requirement. | |
| 357 | |
| 358 Note that for vcs urls and the like we can't assess skipping in this | |
| 359 routine - we simply identify that we need to pull the thing down, | |
| 360 then later on it is pulled down and introspected to assess upgrade/ | |
| 361 reinstalls etc. | |
| 362 | |
| 363 :return: A text reason for why it was skipped, or None. | |
| 364 """ | |
| 365 # Check whether to upgrade/reinstall this req or not. | |
| 366 req_to_install.check_if_exists() | |
| 367 if req_to_install.satisfied_by: | |
| 368 skip_reason = 'satisfied (use --upgrade to upgrade)' | |
| 369 if self.upgrade: | |
| 370 best_installed = False | |
| 371 # For link based requirements we have to pull the | |
| 372 # tree down and inspect to assess the version #, so | |
| 373 # its handled way down. | |
| 374 if not (self.force_reinstall or req_to_install.link): | |
| 375 try: | |
| 376 finder.find_requirement(req_to_install, self.upgrade) | |
| 377 except BestVersionAlreadyInstalled: | |
| 378 skip_reason = 'up-to-date' | |
| 379 best_installed = True | |
| 380 except DistributionNotFound: | |
| 381 # No distribution found, so we squash the | |
| 382 # error - it will be raised later when we | |
| 383 # re-try later to do the install. | |
| 384 # Why don't we just raise here? | |
| 385 pass | |
| 386 | |
| 387 if not best_installed: | |
| 388 # don't uninstall conflict if user install and | |
| 389 # conflict is not user install | |
| 390 if not (self.use_user_site and not | |
| 391 dist_in_usersite(req_to_install.satisfied_by)): | |
| 392 req_to_install.conflicts_with = \ | |
| 393 req_to_install.satisfied_by | |
| 394 req_to_install.satisfied_by = None | |
| 395 return skip_reason | |
| 396 else: | |
| 397 return None | |
| 398 | |
| 399 def _prepare_file(self, finder, req_to_install): | |
| 400 """Prepare a single requirements files. | |
| 401 | |
| 402 :return: A list of addition InstallRequirements to also install. | |
| 403 """ | |
| 404 # Tell user what we are doing for this requirement: | |
| 405 # obtain (editable), skipping, processing (local url), collecting | |
| 406 # (remote url or package name) | |
| 407 if req_to_install.editable: | |
| 408 logger.info('Obtaining %s', req_to_install) | |
| 409 else: | |
| 410 # satisfied_by is only evaluated by calling _check_skip_installed, | |
| 411 # so it must be None here. | |
| 412 assert req_to_install.satisfied_by is None | |
| 413 if not self.ignore_installed: | |
| 414 skip_reason = self._check_skip_installed( | |
| 415 req_to_install, finder) | |
| 416 | |
| 417 if req_to_install.satisfied_by: | |
| 418 assert skip_reason is not None, ( | |
| 419 '_check_skip_installed returned None but ' | |
| 420 'req_to_install.satisfied_by is set to %r' | |
| 421 % (req_to_install.satisfied_by,)) | |
| 422 logger.info( | |
| 423 'Requirement already %s: %s', skip_reason, | |
| 424 req_to_install) | |
| 425 else: | |
| 426 if (req_to_install.link and | |
| 427 req_to_install.link.scheme == 'file'): | |
| 428 path = url_to_path(req_to_install.link.url) | |
| 429 logger.info('Processing %s', display_path(path)) | |
| 430 else: | |
| 431 logger.info('Collecting %s', req_to_install) | |
| 432 | |
| 433 with indent_log(): | |
| 434 # ################################ # | |
| 435 # # vcs update or unpack archive # # | |
| 436 # ################################ # | |
| 437 if req_to_install.editable: | |
| 438 req_to_install.ensure_has_source_dir(self.src_dir) | |
| 439 req_to_install.update_editable(not self.is_download) | |
| 440 abstract_dist = make_abstract_dist(req_to_install) | |
| 441 abstract_dist.prep_for_dist() | |
| 442 if self.is_download: | |
| 443 req_to_install.archive(self.download_dir) | |
| 444 elif req_to_install.satisfied_by: | |
| 445 abstract_dist = Installed(req_to_install) | |
| 446 else: | |
| 447 # @@ if filesystem packages are not marked | |
| 448 # editable in a req, a non deterministic error | |
| 449 # occurs when the script attempts to unpack the | |
| 450 # build directory | |
| 451 req_to_install.ensure_has_source_dir(self.build_dir) | |
| 452 # If a checkout exists, it's unwise to keep going. version | |
| 453 # inconsistencies are logged later, but do not fail the | |
| 454 # installation. | |
| 455 # FIXME: this won't upgrade when there's an existing | |
| 456 # package unpacked in `req_to_install.source_dir` | |
| 457 if os.path.exists( | |
| 458 os.path.join(req_to_install.source_dir, 'setup.py')): | |
| 459 raise PreviousBuildDirError( | |
| 460 "pip can't proceed with requirements '%s' due to a" | |
| 461 " pre-existing build directory (%s). This is " | |
| 462 "likely due to a previous installation that failed" | |
| 463 ". pip is being responsible and not assuming it " | |
| 464 "can delete this. Please delete it and try again." | |
| 465 % (req_to_install, req_to_install.source_dir) | |
| 466 ) | |
| 467 req_to_install.populate_link(finder, self.upgrade) | |
| 468 # We can't hit this spot and have populate_link return None. | |
| 469 # req_to_install.satisfied_by is None here (because we're | |
| 470 # guarded) and upgrade has no impact except when satisfied_by | |
| 471 # is not None. | |
| 472 # Then inside find_requirement existing_applicable -> False | |
| 473 # If no new versions are found, DistributionNotFound is raised, | |
| 474 # otherwise a result is guaranteed. | |
| 475 assert req_to_install.link | |
| 476 try: | |
| 477 if req_to_install.link.is_wheel and \ | |
| 478 self.wheel_download_dir: | |
| 479 # when doing 'pip wheel` | |
| 480 download_dir = self.wheel_download_dir | |
| 481 do_download = True | |
| 482 else: | |
| 483 download_dir = self.download_dir | |
| 484 do_download = self.is_download | |
| 485 unpack_url( | |
| 486 req_to_install.link, req_to_install.source_dir, | |
| 487 download_dir, do_download, session=self.session, | |
| 488 ) | |
| 489 except requests.HTTPError as exc: | |
| 490 logger.critical( | |
| 491 'Could not install requirement %s because ' | |
| 492 'of error %s', | |
| 493 req_to_install, | |
| 494 exc, | |
| 495 ) | |
| 496 raise InstallationError( | |
| 497 'Could not install requirement %s because ' | |
| 498 'of HTTP error %s for URL %s' % | |
| 499 (req_to_install, exc, req_to_install.link) | |
| 500 ) | |
| 501 abstract_dist = make_abstract_dist(req_to_install) | |
| 502 abstract_dist.prep_for_dist() | |
| 503 if self.is_download: | |
| 504 # Make a .zip of the source_dir we already created. | |
| 505 if req_to_install.link.scheme in vcs.all_schemes: | |
| 506 req_to_install.archive(self.download_dir) | |
| 507 # req_to_install.req is only avail after unpack for URL | |
| 508 # pkgs repeat check_if_exists to uninstall-on-upgrade | |
| 509 # (#14) | |
| 510 if not self.ignore_installed: | |
| 511 req_to_install.check_if_exists() | |
| 512 if req_to_install.satisfied_by: | |
| 513 if self.upgrade or self.ignore_installed: | |
| 514 # don't uninstall conflict if user install and | |
| 515 # conflict is not user install | |
| 516 if not (self.use_user_site and not | |
| 517 dist_in_usersite( | |
| 518 req_to_install.satisfied_by)): | |
| 519 req_to_install.conflicts_with = \ | |
| 520 req_to_install.satisfied_by | |
| 521 req_to_install.satisfied_by = None | |
| 522 else: | |
| 523 logger.info( | |
| 524 'Requirement already satisfied (use ' | |
| 525 '--upgrade to upgrade): %s', | |
| 526 req_to_install, | |
| 527 ) | |
| 528 | |
| 529 # ###################### # | |
| 530 # # parse dependencies # # | |
| 531 # ###################### # | |
| 532 dist = abstract_dist.dist(finder) | |
| 533 more_reqs = [] | |
| 534 | |
| 535 def add_req(subreq): | |
| 536 sub_install_req = InstallRequirement( | |
| 537 str(subreq), | |
| 538 req_to_install, | |
| 539 isolated=self.isolated, | |
| 540 ) | |
| 541 more_reqs.extend(self.add_requirement( | |
| 542 sub_install_req, req_to_install.name)) | |
| 543 | |
| 544 # We add req_to_install before its dependencies, so that we | |
| 545 # can refer to it when adding dependencies. | |
| 546 if not self.has_requirement(req_to_install.name): | |
| 547 # 'unnamed' requirements will get added here | |
| 548 self.add_requirement(req_to_install, None) | |
| 549 | |
| 550 if not self.ignore_dependencies: | |
| 551 if (req_to_install.extras): | |
| 552 logger.debug( | |
| 553 "Installing extra requirements: %r", | |
| 554 ','.join(req_to_install.extras), | |
| 555 ) | |
| 556 missing_requested = sorted( | |
| 557 set(req_to_install.extras) - set(dist.extras) | |
| 558 ) | |
| 559 for missing in missing_requested: | |
| 560 logger.warning( | |
| 561 '%s does not provide the extra \'%s\'', | |
| 562 dist, missing | |
| 563 ) | |
| 564 | |
| 565 available_requested = sorted( | |
| 566 set(dist.extras) & set(req_to_install.extras) | |
| 567 ) | |
| 568 for subreq in dist.requires(available_requested): | |
| 569 add_req(subreq) | |
| 570 | |
| 571 # cleanup tmp src | |
| 572 self.reqs_to_cleanup.append(req_to_install) | |
| 573 | |
| 574 if not req_to_install.editable and not req_to_install.satisfied_by: | |
| 575 # XXX: --no-install leads this to report 'Successfully | |
| 576 # downloaded' for only non-editable reqs, even though we took | |
| 577 # action on them. | |
| 578 self.successfully_downloaded.append(req_to_install) | |
| 579 | |
| 580 return more_reqs | |
| 581 | |
| 582 def cleanup_files(self): | |
| 583 """Clean up files, remove builds.""" | |
| 584 logger.debug('Cleaning up...') | |
| 585 with indent_log(): | |
| 586 for req in self.reqs_to_cleanup: | |
| 587 req.remove_temporary_source() | |
| 588 | |
| 589 if self._pip_has_created_build_dir(): | |
| 590 logger.debug('Removing temporary dir %s...', self.build_dir) | |
| 591 rmtree(self.build_dir) | |
| 592 | |
| 593 def _pip_has_created_build_dir(self): | |
| 594 return ( | |
| 595 self.build_dir == build_prefix and | |
| 596 os.path.exists( | |
| 597 os.path.join(self.build_dir, PIP_DELETE_MARKER_FILENAME) | |
| 598 ) | |
| 599 ) | |
| 600 | |
| 601 def _to_install(self): | |
| 602 """Create the installation order. | |
| 603 | |
| 604 The installation order is topological - requirements are installed | |
| 605 before the requiring thing. We break cycles at an arbitrary point, | |
| 606 and make no other guarantees. | |
| 607 """ | |
| 608 # The current implementation, which we may change at any point | |
| 609 # installs the user specified things in the order given, except when | |
| 610 # dependencies must come earlier to achieve topological order. | |
| 611 order = [] | |
| 612 ordered_reqs = set() | |
| 613 | |
| 614 def schedule(req): | |
| 615 if req.satisfied_by or req in ordered_reqs: | |
| 616 return | |
| 617 ordered_reqs.add(req) | |
| 618 for dep in self._dependencies[req]: | |
| 619 schedule(dep) | |
| 620 order.append(req) | |
| 621 for install_req in self.requirements.values(): | |
| 622 schedule(install_req) | |
| 623 return order | |
| 624 | |
| 625 def install(self, install_options, global_options=(), *args, **kwargs): | |
| 626 """ | |
| 627 Install everything in this set (after having downloaded and unpacked | |
| 628 the packages) | |
| 629 """ | |
| 630 to_install = self._to_install() | |
| 631 | |
| 632 # DISTRIBUTE TO SETUPTOOLS UPGRADE HACK (1 of 3 parts) | |
| 633 # move the distribute-0.7.X wrapper to the end because it does not | |
| 634 # install a setuptools package. by moving it to the end, we ensure it's | |
| 635 # setuptools dependency is handled first, which will provide the | |
| 636 # setuptools package | |
| 637 # TODO: take this out later | |
| 638 distribute_req = pkg_resources.Requirement.parse("distribute>=0.7") | |
| 639 for req in to_install: | |
| 640 if (req.name == 'distribute' and | |
| 641 req.installed_version is not None and | |
| 642 req.installed_version in distribute_req): | |
| 643 to_install.remove(req) | |
| 644 to_install.append(req) | |
| 645 | |
| 646 if to_install: | |
| 647 logger.info( | |
| 648 'Installing collected packages: %s', | |
| 649 ', '.join([req.name for req in to_install]), | |
| 650 ) | |
| 651 | |
| 652 with indent_log(): | |
| 653 for requirement in to_install: | |
| 654 | |
| 655 # DISTRIBUTE TO SETUPTOOLS UPGRADE HACK (1 of 3 parts) | |
| 656 # when upgrading from distribute-0.6.X to the new merged | |
| 657 # setuptools in py2, we need to force setuptools to uninstall | |
| 658 # distribute. In py3, which is always using distribute, this | |
| 659 # conversion is already happening in distribute's | |
| 660 # pkg_resources. It's ok *not* to check if setuptools>=0.7 | |
| 661 # because if someone were actually trying to ugrade from | |
| 662 # distribute to setuptools 0.6.X, then all this could do is | |
| 663 # actually help, although that upgade path was certainly never | |
| 664 # "supported" | |
| 665 # TODO: remove this later | |
| 666 if requirement.name == 'setuptools': | |
| 667 try: | |
| 668 # only uninstall distribute<0.7. For >=0.7, setuptools | |
| 669 # will also be present, and that's what we need to | |
| 670 # uninstall | |
| 671 distribute_requirement = \ | |
| 672 pkg_resources.Requirement.parse("distribute<0.7") | |
| 673 existing_distribute = \ | |
| 674 pkg_resources.get_distribution("distribute") | |
| 675 if existing_distribute in distribute_requirement: | |
| 676 requirement.conflicts_with = existing_distribute | |
| 677 except pkg_resources.DistributionNotFound: | |
| 678 # distribute wasn't installed, so nothing to do | |
| 679 pass | |
| 680 | |
| 681 if requirement.conflicts_with: | |
| 682 logger.info( | |
| 683 'Found existing installation: %s', | |
| 684 requirement.conflicts_with, | |
| 685 ) | |
| 686 with indent_log(): | |
| 687 requirement.uninstall(auto_confirm=True) | |
| 688 try: | |
| 689 requirement.install( | |
| 690 install_options, | |
| 691 global_options, | |
| 692 *args, | |
| 693 **kwargs | |
| 694 ) | |
| 695 except: | |
| 696 # if install did not succeed, rollback previous uninstall | |
| 697 if (requirement.conflicts_with and not | |
| 698 requirement.install_succeeded): | |
| 699 requirement.rollback_uninstall() | |
| 700 raise | |
| 701 else: | |
| 702 if (requirement.conflicts_with and | |
| 703 requirement.install_succeeded): | |
| 704 requirement.commit_uninstall() | |
| 705 requirement.remove_temporary_source() | |
| 706 | |
| 707 self.successfully_installed = to_install |
