Mercurial > repos > guerler > springsuite
comparison planemo/lib/python3.7/site-packages/pip/_internal/legacy_resolve.py @ 1:56ad4e20f292 draft
"planemo upload commit 6eee67778febed82ddd413c3ca40b3183a3898f1"
| author | guerler |
|---|---|
| date | Fri, 31 Jul 2020 00:32:28 -0400 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| 0:d30785e31577 | 1:56ad4e20f292 |
|---|---|
| 1 """Dependency Resolution | |
| 2 | |
| 3 The dependency resolution in pip is performed as follows: | |
| 4 | |
| 5 for top-level requirements: | |
| 6 a. only one spec allowed per project, regardless of conflicts or not. | |
| 7 otherwise a "double requirement" exception is raised | |
| 8 b. they override sub-dependency requirements. | |
| 9 for sub-dependencies | |
| 10 a. "first found, wins" (where the order is breadth first) | |
| 11 """ | |
| 12 | |
| 13 import logging | |
| 14 import sys | |
| 15 from collections import defaultdict | |
| 16 from itertools import chain | |
| 17 | |
| 18 from pip._vendor.packaging import specifiers | |
| 19 | |
| 20 from pip._internal.exceptions import ( | |
| 21 BestVersionAlreadyInstalled, DistributionNotFound, HashError, HashErrors, | |
| 22 UnsupportedPythonVersion, | |
| 23 ) | |
| 24 from pip._internal.req.constructors import install_req_from_req_string | |
| 25 from pip._internal.utils.logging import indent_log | |
| 26 from pip._internal.utils.misc import ( | |
| 27 dist_in_usersite, ensure_dir, normalize_version_info, | |
| 28 ) | |
| 29 from pip._internal.utils.packaging import ( | |
| 30 check_requires_python, get_requires_python, | |
| 31 ) | |
| 32 from pip._internal.utils.typing import MYPY_CHECK_RUNNING | |
| 33 | |
| 34 if MYPY_CHECK_RUNNING: | |
| 35 from typing import DefaultDict, List, Optional, Set, Tuple | |
| 36 from pip._vendor import pkg_resources | |
| 37 | |
| 38 from pip._internal.cache import WheelCache | |
| 39 from pip._internal.distributions import AbstractDistribution | |
| 40 from pip._internal.download import PipSession | |
| 41 from pip._internal.index import PackageFinder | |
| 42 from pip._internal.operations.prepare import RequirementPreparer | |
| 43 from pip._internal.req.req_install import InstallRequirement | |
| 44 from pip._internal.req.req_set import RequirementSet | |
| 45 | |
| 46 logger = logging.getLogger(__name__) | |
| 47 | |
| 48 | |
| 49 def _check_dist_requires_python( | |
| 50 dist, # type: pkg_resources.Distribution | |
| 51 version_info, # type: Tuple[int, int, int] | |
| 52 ignore_requires_python=False, # type: bool | |
| 53 ): | |
| 54 # type: (...) -> None | |
| 55 """ | |
| 56 Check whether the given Python version is compatible with a distribution's | |
| 57 "Requires-Python" value. | |
| 58 | |
| 59 :param version_info: A 3-tuple of ints representing the Python | |
| 60 major-minor-micro version to check. | |
| 61 :param ignore_requires_python: Whether to ignore the "Requires-Python" | |
| 62 value if the given Python version isn't compatible. | |
| 63 | |
| 64 :raises UnsupportedPythonVersion: When the given Python version isn't | |
| 65 compatible. | |
| 66 """ | |
| 67 requires_python = get_requires_python(dist) | |
| 68 try: | |
| 69 is_compatible = check_requires_python( | |
| 70 requires_python, version_info=version_info, | |
| 71 ) | |
| 72 except specifiers.InvalidSpecifier as exc: | |
| 73 logger.warning( | |
| 74 "Package %r has an invalid Requires-Python: %s", | |
| 75 dist.project_name, exc, | |
| 76 ) | |
| 77 return | |
| 78 | |
| 79 if is_compatible: | |
| 80 return | |
| 81 | |
| 82 version = '.'.join(map(str, version_info)) | |
| 83 if ignore_requires_python: | |
| 84 logger.debug( | |
| 85 'Ignoring failed Requires-Python check for package %r: ' | |
| 86 '%s not in %r', | |
| 87 dist.project_name, version, requires_python, | |
| 88 ) | |
| 89 return | |
| 90 | |
| 91 raise UnsupportedPythonVersion( | |
| 92 'Package {!r} requires a different Python: {} not in {!r}'.format( | |
| 93 dist.project_name, version, requires_python, | |
| 94 )) | |
| 95 | |
| 96 | |
| 97 class Resolver(object): | |
| 98 """Resolves which packages need to be installed/uninstalled to perform \ | |
| 99 the requested operation without breaking the requirements of any package. | |
| 100 """ | |
| 101 | |
| 102 _allowed_strategies = {"eager", "only-if-needed", "to-satisfy-only"} | |
| 103 | |
| 104 def __init__( | |
| 105 self, | |
| 106 preparer, # type: RequirementPreparer | |
| 107 session, # type: PipSession | |
| 108 finder, # type: PackageFinder | |
| 109 wheel_cache, # type: Optional[WheelCache] | |
| 110 use_user_site, # type: bool | |
| 111 ignore_dependencies, # type: bool | |
| 112 ignore_installed, # type: bool | |
| 113 ignore_requires_python, # type: bool | |
| 114 force_reinstall, # type: bool | |
| 115 isolated, # type: bool | |
| 116 upgrade_strategy, # type: str | |
| 117 use_pep517=None, # type: Optional[bool] | |
| 118 py_version_info=None, # type: Optional[Tuple[int, ...]] | |
| 119 ): | |
| 120 # type: (...) -> None | |
| 121 super(Resolver, self).__init__() | |
| 122 assert upgrade_strategy in self._allowed_strategies | |
| 123 | |
| 124 if py_version_info is None: | |
| 125 py_version_info = sys.version_info[:3] | |
| 126 else: | |
| 127 py_version_info = normalize_version_info(py_version_info) | |
| 128 | |
| 129 self._py_version_info = py_version_info | |
| 130 | |
| 131 self.preparer = preparer | |
| 132 self.finder = finder | |
| 133 self.session = session | |
| 134 | |
| 135 # NOTE: This would eventually be replaced with a cache that can give | |
| 136 # information about both sdist and wheels transparently. | |
| 137 self.wheel_cache = wheel_cache | |
| 138 | |
| 139 # This is set in resolve | |
| 140 self.require_hashes = None # type: Optional[bool] | |
| 141 | |
| 142 self.upgrade_strategy = upgrade_strategy | |
| 143 self.force_reinstall = force_reinstall | |
| 144 self.isolated = isolated | |
| 145 self.ignore_dependencies = ignore_dependencies | |
| 146 self.ignore_installed = ignore_installed | |
| 147 self.ignore_requires_python = ignore_requires_python | |
| 148 self.use_user_site = use_user_site | |
| 149 self.use_pep517 = use_pep517 | |
| 150 | |
| 151 self._discovered_dependencies = \ | |
| 152 defaultdict(list) # type: DefaultDict[str, List] | |
| 153 | |
| 154 def resolve(self, requirement_set): | |
| 155 # type: (RequirementSet) -> None | |
| 156 """Resolve what operations need to be done | |
| 157 | |
| 158 As a side-effect of this method, the packages (and their dependencies) | |
| 159 are downloaded, unpacked and prepared for installation. This | |
| 160 preparation is done by ``pip.operations.prepare``. | |
| 161 | |
| 162 Once PyPI has static dependency metadata available, it would be | |
| 163 possible to move the preparation to become a step separated from | |
| 164 dependency resolution. | |
| 165 """ | |
| 166 # make the wheelhouse | |
| 167 if self.preparer.wheel_download_dir: | |
| 168 ensure_dir(self.preparer.wheel_download_dir) | |
| 169 | |
| 170 # If any top-level requirement has a hash specified, enter | |
| 171 # hash-checking mode, which requires hashes from all. | |
| 172 root_reqs = ( | |
| 173 requirement_set.unnamed_requirements + | |
| 174 list(requirement_set.requirements.values()) | |
| 175 ) | |
| 176 self.require_hashes = ( | |
| 177 requirement_set.require_hashes or | |
| 178 any(req.has_hash_options for req in root_reqs) | |
| 179 ) | |
| 180 | |
| 181 # Display where finder is looking for packages | |
| 182 search_scope = self.finder.search_scope | |
| 183 locations = search_scope.get_formatted_locations() | |
| 184 if locations: | |
| 185 logger.info(locations) | |
| 186 | |
| 187 # Actually prepare the files, and collect any exceptions. Most hash | |
| 188 # exceptions cannot be checked ahead of time, because | |
| 189 # req.populate_link() needs to be called before we can make decisions | |
| 190 # based on link type. | |
| 191 discovered_reqs = [] # type: List[InstallRequirement] | |
| 192 hash_errors = HashErrors() | |
| 193 for req in chain(root_reqs, discovered_reqs): | |
| 194 try: | |
| 195 discovered_reqs.extend( | |
| 196 self._resolve_one(requirement_set, req) | |
| 197 ) | |
| 198 except HashError as exc: | |
| 199 exc.req = req | |
| 200 hash_errors.append(exc) | |
| 201 | |
| 202 if hash_errors: | |
| 203 raise hash_errors | |
| 204 | |
| 205 def _is_upgrade_allowed(self, req): | |
| 206 # type: (InstallRequirement) -> bool | |
| 207 if self.upgrade_strategy == "to-satisfy-only": | |
| 208 return False | |
| 209 elif self.upgrade_strategy == "eager": | |
| 210 return True | |
| 211 else: | |
| 212 assert self.upgrade_strategy == "only-if-needed" | |
| 213 return req.is_direct | |
| 214 | |
| 215 def _set_req_to_reinstall(self, req): | |
| 216 # type: (InstallRequirement) -> None | |
| 217 """ | |
| 218 Set a requirement to be installed. | |
| 219 """ | |
| 220 # Don't uninstall the conflict if doing a user install and the | |
| 221 # conflict is not a user install. | |
| 222 if not self.use_user_site or dist_in_usersite(req.satisfied_by): | |
| 223 req.conflicts_with = req.satisfied_by | |
| 224 req.satisfied_by = None | |
| 225 | |
| 226 # XXX: Stop passing requirement_set for options | |
| 227 def _check_skip_installed(self, req_to_install): | |
| 228 # type: (InstallRequirement) -> Optional[str] | |
| 229 """Check if req_to_install should be skipped. | |
| 230 | |
| 231 This will check if the req is installed, and whether we should upgrade | |
| 232 or reinstall it, taking into account all the relevant user options. | |
| 233 | |
| 234 After calling this req_to_install will only have satisfied_by set to | |
| 235 None if the req_to_install is to be upgraded/reinstalled etc. Any | |
| 236 other value will be a dist recording the current thing installed that | |
| 237 satisfies the requirement. | |
| 238 | |
| 239 Note that for vcs urls and the like we can't assess skipping in this | |
| 240 routine - we simply identify that we need to pull the thing down, | |
| 241 then later on it is pulled down and introspected to assess upgrade/ | |
| 242 reinstalls etc. | |
| 243 | |
| 244 :return: A text reason for why it was skipped, or None. | |
| 245 """ | |
| 246 if self.ignore_installed: | |
| 247 return None | |
| 248 | |
| 249 req_to_install.check_if_exists(self.use_user_site) | |
| 250 if not req_to_install.satisfied_by: | |
| 251 return None | |
| 252 | |
| 253 if self.force_reinstall: | |
| 254 self._set_req_to_reinstall(req_to_install) | |
| 255 return None | |
| 256 | |
| 257 if not self._is_upgrade_allowed(req_to_install): | |
| 258 if self.upgrade_strategy == "only-if-needed": | |
| 259 return 'already satisfied, skipping upgrade' | |
| 260 return 'already satisfied' | |
| 261 | |
| 262 # Check for the possibility of an upgrade. For link-based | |
| 263 # requirements we have to pull the tree down and inspect to assess | |
| 264 # the version #, so it's handled way down. | |
| 265 if not req_to_install.link: | |
| 266 try: | |
| 267 self.finder.find_requirement(req_to_install, upgrade=True) | |
| 268 except BestVersionAlreadyInstalled: | |
| 269 # Then the best version is installed. | |
| 270 return 'already up-to-date' | |
| 271 except DistributionNotFound: | |
| 272 # No distribution found, so we squash the error. It will | |
| 273 # be raised later when we re-try later to do the install. | |
| 274 # Why don't we just raise here? | |
| 275 pass | |
| 276 | |
| 277 self._set_req_to_reinstall(req_to_install) | |
| 278 return None | |
| 279 | |
| 280 def _get_abstract_dist_for(self, req): | |
| 281 # type: (InstallRequirement) -> AbstractDistribution | |
| 282 """Takes a InstallRequirement and returns a single AbstractDist \ | |
| 283 representing a prepared variant of the same. | |
| 284 """ | |
| 285 assert self.require_hashes is not None, ( | |
| 286 "require_hashes should have been set in Resolver.resolve()" | |
| 287 ) | |
| 288 | |
| 289 if req.editable: | |
| 290 return self.preparer.prepare_editable_requirement( | |
| 291 req, self.require_hashes, self.use_user_site, self.finder, | |
| 292 ) | |
| 293 | |
| 294 # satisfied_by is only evaluated by calling _check_skip_installed, | |
| 295 # so it must be None here. | |
| 296 assert req.satisfied_by is None | |
| 297 skip_reason = self._check_skip_installed(req) | |
| 298 | |
| 299 if req.satisfied_by: | |
| 300 return self.preparer.prepare_installed_requirement( | |
| 301 req, self.require_hashes, skip_reason | |
| 302 ) | |
| 303 | |
| 304 upgrade_allowed = self._is_upgrade_allowed(req) | |
| 305 abstract_dist = self.preparer.prepare_linked_requirement( | |
| 306 req, self.session, self.finder, upgrade_allowed, | |
| 307 self.require_hashes | |
| 308 ) | |
| 309 | |
| 310 # NOTE | |
| 311 # The following portion is for determining if a certain package is | |
| 312 # going to be re-installed/upgraded or not and reporting to the user. | |
| 313 # This should probably get cleaned up in a future refactor. | |
| 314 | |
| 315 # req.req is only avail after unpack for URL | |
| 316 # pkgs repeat check_if_exists to uninstall-on-upgrade | |
| 317 # (#14) | |
| 318 if not self.ignore_installed: | |
| 319 req.check_if_exists(self.use_user_site) | |
| 320 | |
| 321 if req.satisfied_by: | |
| 322 should_modify = ( | |
| 323 self.upgrade_strategy != "to-satisfy-only" or | |
| 324 self.force_reinstall or | |
| 325 self.ignore_installed or | |
| 326 req.link.scheme == 'file' | |
| 327 ) | |
| 328 if should_modify: | |
| 329 self._set_req_to_reinstall(req) | |
| 330 else: | |
| 331 logger.info( | |
| 332 'Requirement already satisfied (use --upgrade to upgrade):' | |
| 333 ' %s', req, | |
| 334 ) | |
| 335 | |
| 336 return abstract_dist | |
| 337 | |
| 338 def _resolve_one( | |
| 339 self, | |
| 340 requirement_set, # type: RequirementSet | |
| 341 req_to_install # type: InstallRequirement | |
| 342 ): | |
| 343 # type: (...) -> List[InstallRequirement] | |
| 344 """Prepare a single requirements file. | |
| 345 | |
| 346 :return: A list of additional InstallRequirements to also install. | |
| 347 """ | |
| 348 # Tell user what we are doing for this requirement: | |
| 349 # obtain (editable), skipping, processing (local url), collecting | |
| 350 # (remote url or package name) | |
| 351 if req_to_install.constraint or req_to_install.prepared: | |
| 352 return [] | |
| 353 | |
| 354 req_to_install.prepared = True | |
| 355 | |
| 356 # register tmp src for cleanup in case something goes wrong | |
| 357 requirement_set.reqs_to_cleanup.append(req_to_install) | |
| 358 | |
| 359 abstract_dist = self._get_abstract_dist_for(req_to_install) | |
| 360 | |
| 361 # Parse and return dependencies | |
| 362 dist = abstract_dist.get_pkg_resources_distribution() | |
| 363 # This will raise UnsupportedPythonVersion if the given Python | |
| 364 # version isn't compatible with the distribution's Requires-Python. | |
| 365 _check_dist_requires_python( | |
| 366 dist, version_info=self._py_version_info, | |
| 367 ignore_requires_python=self.ignore_requires_python, | |
| 368 ) | |
| 369 | |
| 370 more_reqs = [] # type: List[InstallRequirement] | |
| 371 | |
| 372 def add_req(subreq, extras_requested): | |
| 373 sub_install_req = install_req_from_req_string( | |
| 374 str(subreq), | |
| 375 req_to_install, | |
| 376 isolated=self.isolated, | |
| 377 wheel_cache=self.wheel_cache, | |
| 378 use_pep517=self.use_pep517 | |
| 379 ) | |
| 380 parent_req_name = req_to_install.name | |
| 381 to_scan_again, add_to_parent = requirement_set.add_requirement( | |
| 382 sub_install_req, | |
| 383 parent_req_name=parent_req_name, | |
| 384 extras_requested=extras_requested, | |
| 385 ) | |
| 386 if parent_req_name and add_to_parent: | |
| 387 self._discovered_dependencies[parent_req_name].append( | |
| 388 add_to_parent | |
| 389 ) | |
| 390 more_reqs.extend(to_scan_again) | |
| 391 | |
| 392 with indent_log(): | |
| 393 # We add req_to_install before its dependencies, so that we | |
| 394 # can refer to it when adding dependencies. | |
| 395 if not requirement_set.has_requirement(req_to_install.name): | |
| 396 # 'unnamed' requirements will get added here | |
| 397 req_to_install.is_direct = True | |
| 398 requirement_set.add_requirement( | |
| 399 req_to_install, parent_req_name=None, | |
| 400 ) | |
| 401 | |
| 402 if not self.ignore_dependencies: | |
| 403 if req_to_install.extras: | |
| 404 logger.debug( | |
| 405 "Installing extra requirements: %r", | |
| 406 ','.join(req_to_install.extras), | |
| 407 ) | |
| 408 missing_requested = sorted( | |
| 409 set(req_to_install.extras) - set(dist.extras) | |
| 410 ) | |
| 411 for missing in missing_requested: | |
| 412 logger.warning( | |
| 413 '%s does not provide the extra \'%s\'', | |
| 414 dist, missing | |
| 415 ) | |
| 416 | |
| 417 available_requested = sorted( | |
| 418 set(dist.extras) & set(req_to_install.extras) | |
| 419 ) | |
| 420 for subreq in dist.requires(available_requested): | |
| 421 add_req(subreq, extras_requested=available_requested) | |
| 422 | |
| 423 if not req_to_install.editable and not req_to_install.satisfied_by: | |
| 424 # XXX: --no-install leads this to report 'Successfully | |
| 425 # downloaded' for only non-editable reqs, even though we took | |
| 426 # action on them. | |
| 427 requirement_set.successfully_downloaded.append(req_to_install) | |
| 428 | |
| 429 return more_reqs | |
| 430 | |
| 431 def get_installation_order(self, req_set): | |
| 432 # type: (RequirementSet) -> List[InstallRequirement] | |
| 433 """Create the installation order. | |
| 434 | |
| 435 The installation order is topological - requirements are installed | |
| 436 before the requiring thing. We break cycles at an arbitrary point, | |
| 437 and make no other guarantees. | |
| 438 """ | |
| 439 # The current implementation, which we may change at any point | |
| 440 # installs the user specified things in the order given, except when | |
| 441 # dependencies must come earlier to achieve topological order. | |
| 442 order = [] | |
| 443 ordered_reqs = set() # type: Set[InstallRequirement] | |
| 444 | |
| 445 def schedule(req): | |
| 446 if req.satisfied_by or req in ordered_reqs: | |
| 447 return | |
| 448 if req.constraint: | |
| 449 return | |
| 450 ordered_reqs.add(req) | |
| 451 for dep in self._discovered_dependencies[req.name]: | |
| 452 schedule(dep) | |
| 453 order.append(req) | |
| 454 | |
| 455 for install_req in req_set.requirements.values(): | |
| 456 schedule(install_req) | |
| 457 return order |
