Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
N
news
Project
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Sartika Aritonang
news
Commits
3e0a2deb
Commit
3e0a2deb
authored
May 29, 2020
by
Sartika Aritonang
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Upload New File
parent
591771ac
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
459 additions
and
0 deletions
+459
-0
resolver.py
...site-packages/pip/_internal/resolution/legacy/resolver.py
+459
-0
No files found.
stbi/Lib/site-packages/pip/_internal/resolution/legacy/resolver.py
0 → 100644
View file @
3e0a2deb
"""Dependency Resolution
The dependency resolution in pip is performed as follows:
for top-level requirements:
a. only one spec allowed per project, regardless of conflicts or not.
otherwise a "double requirement" exception is raised
b. they override sub-dependency requirements.
for sub-dependencies
a. "first found, wins" (where the order is breadth first)
"""
# The following comment should be removed at some point in the future.
# mypy: strict-optional=False
# mypy: disallow-untyped-defs=False
import
logging
import
sys
from
collections
import
defaultdict
from
itertools
import
chain
from
pip._vendor.packaging
import
specifiers
from
pip._internal.exceptions
import
(
BestVersionAlreadyInstalled
,
DistributionNotFound
,
HashError
,
HashErrors
,
UnsupportedPythonVersion
,
)
from
pip._internal.req.req_set
import
RequirementSet
from
pip._internal.resolution.base
import
BaseResolver
from
pip._internal.utils.compatibility_tags
import
get_supported
from
pip._internal.utils.logging
import
indent_log
from
pip._internal.utils.misc
import
dist_in_usersite
,
normalize_version_info
from
pip._internal.utils.packaging
import
(
check_requires_python
,
get_requires_python
,
)
from
pip._internal.utils.typing
import
MYPY_CHECK_RUNNING
if
MYPY_CHECK_RUNNING
:
from
typing
import
DefaultDict
,
List
,
Optional
,
Set
,
Tuple
from
pip._vendor
import
pkg_resources
from
pip._internal.cache
import
WheelCache
from
pip._internal.distributions
import
AbstractDistribution
from
pip._internal.index.package_finder
import
PackageFinder
from
pip._internal.operations.prepare
import
RequirementPreparer
from
pip._internal.req.req_install
import
InstallRequirement
from
pip._internal.resolution.base
import
InstallRequirementProvider
DiscoveredDependencies
=
DefaultDict
[
str
,
List
[
InstallRequirement
]]
logger
=
logging
.
getLogger
(
__name__
)
def
_check_dist_requires_python
(
dist
,
# type: pkg_resources.Distribution
version_info
,
# type: Tuple[int, int, int]
ignore_requires_python
=
False
,
# type: bool
):
# type: (...) -> None
"""
Check whether the given Python version is compatible with a distribution's
"Requires-Python" value.
:param version_info: A 3-tuple of ints representing the Python
major-minor-micro version to check.
:param ignore_requires_python: Whether to ignore the "Requires-Python"
value if the given Python version isn't compatible.
:raises UnsupportedPythonVersion: When the given Python version isn't
compatible.
"""
requires_python
=
get_requires_python
(
dist
)
try
:
is_compatible
=
check_requires_python
(
requires_python
,
version_info
=
version_info
,
)
except
specifiers
.
InvalidSpecifier
as
exc
:
logger
.
warning
(
"Package
%
r has an invalid Requires-Python:
%
s"
,
dist
.
project_name
,
exc
,
)
return
if
is_compatible
:
return
version
=
'.'
.
join
(
map
(
str
,
version_info
))
if
ignore_requires_python
:
logger
.
debug
(
'Ignoring failed Requires-Python check for package
%
r: '
'
%
s not in
%
r'
,
dist
.
project_name
,
version
,
requires_python
,
)
return
raise
UnsupportedPythonVersion
(
'Package {!r} requires a different Python: {} not in {!r}'
.
format
(
dist
.
project_name
,
version
,
requires_python
,
))
class
Resolver
(
BaseResolver
):
"""Resolves which packages need to be installed/uninstalled to perform
\
the requested operation without breaking the requirements of any package.
"""
_allowed_strategies
=
{
"eager"
,
"only-if-needed"
,
"to-satisfy-only"
}
def
__init__
(
self
,
preparer
,
# type: RequirementPreparer
finder
,
# type: PackageFinder
wheel_cache
,
# type: Optional[WheelCache]
make_install_req
,
# type: InstallRequirementProvider
use_user_site
,
# type: bool
ignore_dependencies
,
# type: bool
ignore_installed
,
# type: bool
ignore_requires_python
,
# type: bool
force_reinstall
,
# type: bool
upgrade_strategy
,
# type: str
py_version_info
=
None
,
# type: Optional[Tuple[int, ...]]
):
# type: (...) -> None
super
(
Resolver
,
self
)
.
__init__
()
assert
upgrade_strategy
in
self
.
_allowed_strategies
if
py_version_info
is
None
:
py_version_info
=
sys
.
version_info
[:
3
]
else
:
py_version_info
=
normalize_version_info
(
py_version_info
)
self
.
_py_version_info
=
py_version_info
self
.
preparer
=
preparer
self
.
finder
=
finder
self
.
wheel_cache
=
wheel_cache
self
.
upgrade_strategy
=
upgrade_strategy
self
.
force_reinstall
=
force_reinstall
self
.
ignore_dependencies
=
ignore_dependencies
self
.
ignore_installed
=
ignore_installed
self
.
ignore_requires_python
=
ignore_requires_python
self
.
use_user_site
=
use_user_site
self
.
_make_install_req
=
make_install_req
self
.
_discovered_dependencies
=
\
defaultdict
(
list
)
# type: DiscoveredDependencies
def
resolve
(
self
,
root_reqs
,
check_supported_wheels
):
# type: (List[InstallRequirement], bool) -> RequirementSet
"""Resolve what operations need to be done
As a side-effect of this method, the packages (and their dependencies)
are downloaded, unpacked and prepared for installation. This
preparation is done by ``pip.operations.prepare``.
Once PyPI has static dependency metadata available, it would be
possible to move the preparation to become a step separated from
dependency resolution.
"""
requirement_set
=
RequirementSet
(
check_supported_wheels
=
check_supported_wheels
)
for
req
in
root_reqs
:
requirement_set
.
add_requirement
(
req
)
# Actually prepare the files, and collect any exceptions. Most hash
# exceptions cannot be checked ahead of time, because
# _populate_link() needs to be called before we can make decisions
# based on link type.
discovered_reqs
=
[]
# type: List[InstallRequirement]
hash_errors
=
HashErrors
()
for
req
in
chain
(
root_reqs
,
discovered_reqs
):
try
:
discovered_reqs
.
extend
(
self
.
_resolve_one
(
requirement_set
,
req
))
except
HashError
as
exc
:
exc
.
req
=
req
hash_errors
.
append
(
exc
)
if
hash_errors
:
raise
hash_errors
return
requirement_set
def
_is_upgrade_allowed
(
self
,
req
):
# type: (InstallRequirement) -> bool
if
self
.
upgrade_strategy
==
"to-satisfy-only"
:
return
False
elif
self
.
upgrade_strategy
==
"eager"
:
return
True
else
:
assert
self
.
upgrade_strategy
==
"only-if-needed"
return
req
.
is_direct
def
_set_req_to_reinstall
(
self
,
req
):
# type: (InstallRequirement) -> None
"""
Set a requirement to be installed.
"""
# Don't uninstall the conflict if doing a user install and the
# conflict is not a user install.
if
not
self
.
use_user_site
or
dist_in_usersite
(
req
.
satisfied_by
):
req
.
should_reinstall
=
True
req
.
satisfied_by
=
None
def
_check_skip_installed
(
self
,
req_to_install
):
# type: (InstallRequirement) -> Optional[str]
"""Check if req_to_install should be skipped.
This will check if the req is installed, and whether we should upgrade
or reinstall it, taking into account all the relevant user options.
After calling this req_to_install will only have satisfied_by set to
None if the req_to_install is to be upgraded/reinstalled etc. Any
other value will be a dist recording the current thing installed that
satisfies the requirement.
Note that for vcs urls and the like we can't assess skipping in this
routine - we simply identify that we need to pull the thing down,
then later on it is pulled down and introspected to assess upgrade/
reinstalls etc.
:return: A text reason for why it was skipped, or None.
"""
if
self
.
ignore_installed
:
return
None
req_to_install
.
check_if_exists
(
self
.
use_user_site
)
if
not
req_to_install
.
satisfied_by
:
return
None
if
self
.
force_reinstall
:
self
.
_set_req_to_reinstall
(
req_to_install
)
return
None
if
not
self
.
_is_upgrade_allowed
(
req_to_install
):
if
self
.
upgrade_strategy
==
"only-if-needed"
:
return
'already satisfied, skipping upgrade'
return
'already satisfied'
# Check for the possibility of an upgrade. For link-based
# requirements we have to pull the tree down and inspect to assess
# the version #, so it's handled way down.
if
not
req_to_install
.
link
:
try
:
self
.
finder
.
find_requirement
(
req_to_install
,
upgrade
=
True
)
except
BestVersionAlreadyInstalled
:
# Then the best version is installed.
return
'already up-to-date'
except
DistributionNotFound
:
# No distribution found, so we squash the error. It will
# be raised later when we re-try later to do the install.
# Why don't we just raise here?
pass
self
.
_set_req_to_reinstall
(
req_to_install
)
return
None
def
_populate_link
(
self
,
req
):
# type: (InstallRequirement) -> None
"""Ensure that if a link can be found for this, that it is found.
Note that req.link may still be None - if the requirement is already
installed and not needed to be upgraded based on the return value of
_is_upgrade_allowed().
If preparer.require_hashes is True, don't use the wheel cache, because
cached wheels, always built locally, have different hashes than the
files downloaded from the index server and thus throw false hash
mismatches. Furthermore, cached wheels at present have undeterministic
contents due to file modification times.
"""
upgrade
=
self
.
_is_upgrade_allowed
(
req
)
if
req
.
link
is
None
:
req
.
link
=
self
.
finder
.
find_requirement
(
req
,
upgrade
)
if
self
.
wheel_cache
is
None
or
self
.
preparer
.
require_hashes
:
return
cache_entry
=
self
.
wheel_cache
.
get_cache_entry
(
link
=
req
.
link
,
package_name
=
req
.
name
,
supported_tags
=
get_supported
(),
)
if
cache_entry
is
not
None
:
logger
.
debug
(
'Using cached wheel link:
%
s'
,
cache_entry
.
link
)
if
req
.
link
is
req
.
original_link
and
cache_entry
.
persistent
:
req
.
original_link_is_in_wheel_cache
=
True
req
.
link
=
cache_entry
.
link
def
_get_abstract_dist_for
(
self
,
req
):
# type: (InstallRequirement) -> AbstractDistribution
"""Takes a InstallRequirement and returns a single AbstractDist
\
representing a prepared variant of the same.
"""
if
req
.
editable
:
return
self
.
preparer
.
prepare_editable_requirement
(
req
)
# satisfied_by is only evaluated by calling _check_skip_installed,
# so it must be None here.
assert
req
.
satisfied_by
is
None
skip_reason
=
self
.
_check_skip_installed
(
req
)
if
req
.
satisfied_by
:
return
self
.
preparer
.
prepare_installed_requirement
(
req
,
skip_reason
)
# We eagerly populate the link, since that's our "legacy" behavior.
self
.
_populate_link
(
req
)
abstract_dist
=
self
.
preparer
.
prepare_linked_requirement
(
req
)
# NOTE
# The following portion is for determining if a certain package is
# going to be re-installed/upgraded or not and reporting to the user.
# This should probably get cleaned up in a future refactor.
# req.req is only avail after unpack for URL
# pkgs repeat check_if_exists to uninstall-on-upgrade
# (#14)
if
not
self
.
ignore_installed
:
req
.
check_if_exists
(
self
.
use_user_site
)
if
req
.
satisfied_by
:
should_modify
=
(
self
.
upgrade_strategy
!=
"to-satisfy-only"
or
self
.
force_reinstall
or
self
.
ignore_installed
or
req
.
link
.
scheme
==
'file'
)
if
should_modify
:
self
.
_set_req_to_reinstall
(
req
)
else
:
logger
.
info
(
'Requirement already satisfied (use --upgrade to upgrade):'
'
%
s'
,
req
,
)
return
abstract_dist
def
_resolve_one
(
self
,
requirement_set
,
# type: RequirementSet
req_to_install
,
# type: InstallRequirement
):
# type: (...) -> List[InstallRequirement]
"""Prepare a single requirements file.
:return: A list of additional InstallRequirements to also install.
"""
# Tell user what we are doing for this requirement:
# obtain (editable), skipping, processing (local url), collecting
# (remote url or package name)
if
req_to_install
.
constraint
or
req_to_install
.
prepared
:
return
[]
req_to_install
.
prepared
=
True
abstract_dist
=
self
.
_get_abstract_dist_for
(
req_to_install
)
# Parse and return dependencies
dist
=
abstract_dist
.
get_pkg_resources_distribution
()
# This will raise UnsupportedPythonVersion if the given Python
# version isn't compatible with the distribution's Requires-Python.
_check_dist_requires_python
(
dist
,
version_info
=
self
.
_py_version_info
,
ignore_requires_python
=
self
.
ignore_requires_python
,
)
more_reqs
=
[]
# type: List[InstallRequirement]
def
add_req
(
subreq
,
extras_requested
):
sub_install_req
=
self
.
_make_install_req
(
str
(
subreq
),
req_to_install
,
)
parent_req_name
=
req_to_install
.
name
to_scan_again
,
add_to_parent
=
requirement_set
.
add_requirement
(
sub_install_req
,
parent_req_name
=
parent_req_name
,
extras_requested
=
extras_requested
,
)
if
parent_req_name
and
add_to_parent
:
self
.
_discovered_dependencies
[
parent_req_name
]
.
append
(
add_to_parent
)
more_reqs
.
extend
(
to_scan_again
)
with
indent_log
():
# We add req_to_install before its dependencies, so that we
# can refer to it when adding dependencies.
if
not
requirement_set
.
has_requirement
(
req_to_install
.
name
):
# 'unnamed' requirements will get added here
# 'unnamed' requirements can only come from being directly
# provided by the user.
assert
req_to_install
.
is_direct
requirement_set
.
add_requirement
(
req_to_install
,
parent_req_name
=
None
,
)
if
not
self
.
ignore_dependencies
:
if
req_to_install
.
extras
:
logger
.
debug
(
"Installing extra requirements:
%
r"
,
','
.
join
(
req_to_install
.
extras
),
)
missing_requested
=
sorted
(
set
(
req_to_install
.
extras
)
-
set
(
dist
.
extras
)
)
for
missing
in
missing_requested
:
logger
.
warning
(
'
%
s does not provide the extra
\'
%
s
\'
'
,
dist
,
missing
)
available_requested
=
sorted
(
set
(
dist
.
extras
)
&
set
(
req_to_install
.
extras
)
)
for
subreq
in
dist
.
requires
(
available_requested
):
add_req
(
subreq
,
extras_requested
=
available_requested
)
if
not
req_to_install
.
editable
and
not
req_to_install
.
satisfied_by
:
# XXX: --no-install leads this to report 'Successfully
# downloaded' for only non-editable reqs, even though we took
# action on them.
req_to_install
.
successfully_downloaded
=
True
return
more_reqs
def
get_installation_order
(
self
,
req_set
):
# type: (RequirementSet) -> List[InstallRequirement]
"""Create the installation order.
The installation order is topological - requirements are installed
before the requiring thing. We break cycles at an arbitrary point,
and make no other guarantees.
"""
# The current implementation, which we may change at any point
# installs the user specified things in the order given, except when
# dependencies must come earlier to achieve topological order.
order
=
[]
ordered_reqs
=
set
()
# type: Set[InstallRequirement]
def
schedule
(
req
):
if
req
.
satisfied_by
or
req
in
ordered_reqs
:
return
if
req
.
constraint
:
return
ordered_reqs
.
add
(
req
)
for
dep
in
self
.
_discovered_dependencies
[
req
.
name
]:
schedule
(
dep
)
order
.
append
(
req
)
for
install_req
in
req_set
.
requirements
.
values
():
schedule
(
install_req
)
return
order
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment