2014-08-31 01:52:28 +02:00
|
|
|
from __future__ import absolute_import
|
|
|
|
|
|
|
|
import logging
|
2017-12-16 14:54:57 +01:00
|
|
|
from collections import OrderedDict
|
2014-09-11 21:28:21 +02:00
|
|
|
|
2017-08-31 17:48:18 +02:00
|
|
|
from pip._internal.exceptions import InstallationError
|
|
|
|
from pip._internal.utils.logging import indent_log
|
|
|
|
from pip._internal.wheel import Wheel
|
2014-01-12 01:50:11 +01:00
|
|
|
|
2014-08-31 01:52:28 +02:00
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
2014-01-12 01:50:11 +01:00
|
|
|
class RequirementSet(object):
|
|
|
|
|
2018-07-24 17:09:32 +02:00
|
|
|
def __init__(self, require_hashes=False, check_supported_wheels=True):
|
2015-04-07 23:47:13 +02:00
|
|
|
"""Create a RequirementSet.
|
|
|
|
"""
|
2014-05-07 01:25:44 +02:00
|
|
|
|
2017-08-16 18:57:11 +02:00
|
|
|
self.requirements = OrderedDict()
|
2017-06-27 18:37:38 +02:00
|
|
|
self.require_hashes = require_hashes
|
2018-07-24 17:09:32 +02:00
|
|
|
self.check_supported_wheels = check_supported_wheels
|
2017-06-27 18:37:38 +02:00
|
|
|
|
2014-01-12 01:50:11 +01:00
|
|
|
# Mapping of alias: real_name
|
|
|
|
self.requirement_aliases = {}
|
|
|
|
self.unnamed_requirements = []
|
|
|
|
self.successfully_downloaded = []
|
|
|
|
self.reqs_to_cleanup = []
|
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
reqs = [req for req in self.requirements.values()
|
|
|
|
if not req.comes_from]
|
|
|
|
reqs.sort(key=lambda req: req.name.lower())
|
|
|
|
return ' '.join([str(req.req) for req in reqs])
|
|
|
|
|
Add RequirementSet.__repr__
This is useful when exploring with pdb -- e.g.:
(Pdb++) requirement_set
<RequirementSet object; 17 requirement(s): coverage==3.7.1, dj-database-url==0.2.2, dj-static==0.0.5, Django==1.6.2, django-nose==1.2, django-toolbelt==0.0.1, flake8==2.1.0, gunicorn==18.0, mccabe==0.2.1, nose==1.3.0, pep8==1.4.6, psycopg2==2.5.2, pyflakes==0.7.3, selenium==2.39.0, South==0.8.4, static==0.4, wsgiref==0.1.2>
2015-03-20 18:55:36 +01:00
|
|
|
def __repr__(self):
|
|
|
|
reqs = [req for req in self.requirements.values()]
|
|
|
|
reqs.sort(key=lambda req: req.name.lower())
|
|
|
|
reqs_str = ', '.join([str(req.req) for req in reqs])
|
|
|
|
return ('<%s object; %d requirement(s): %s>'
|
|
|
|
% (self.__class__.__name__, len(reqs), reqs_str))
|
|
|
|
|
2016-11-02 13:28:17 +01:00
|
|
|
def add_requirement(self, install_req, parent_req_name=None,
|
|
|
|
extras_requested=None):
|
2015-03-25 01:53:10 +01:00
|
|
|
"""Add install_req as a requirement to install.
|
|
|
|
|
|
|
|
:param parent_req_name: The name of the requirement that needed this
|
|
|
|
added. The name is used because when multiple unnamed requirements
|
|
|
|
resolve to the same name, we could otherwise end up with dependency
|
|
|
|
links that point outside the Requirements set. parent_req must
|
|
|
|
already be added. Note that None implies that this is a user
|
|
|
|
supplied requirement, vs an inferred one.
|
2016-11-02 13:28:17 +01:00
|
|
|
:param extras_requested: an iterable of extras used to evaluate the
|
2017-11-20 16:55:28 +01:00
|
|
|
environment markers.
|
2015-03-25 01:53:10 +01:00
|
|
|
:return: Additional requirements to scan. That is either [] if
|
|
|
|
the requirement is not applicable, or [install_req] if the
|
|
|
|
requirement is applicable and has just been added.
|
|
|
|
"""
|
|
|
|
name = install_req.name
|
2016-11-02 13:28:17 +01:00
|
|
|
if not install_req.match_markers(extras_requested):
|
2018-07-27 16:41:03 +02:00
|
|
|
logger.info(
|
|
|
|
"Ignoring %s: markers '%s' don't match your environment",
|
|
|
|
install_req.name, install_req.markers,
|
|
|
|
)
|
2017-12-16 14:54:57 +01:00
|
|
|
return [], None
|
2014-01-14 18:36:47 +01:00
|
|
|
|
2016-03-10 03:55:20 +01:00
|
|
|
# This check has to come after we filter requirements with the
|
|
|
|
# environment markers.
|
|
|
|
if install_req.link and install_req.link.is_wheel:
|
|
|
|
wheel = Wheel(install_req.link.filename)
|
2018-07-24 17:09:32 +02:00
|
|
|
if self.check_supported_wheels and not wheel.supported():
|
2016-03-10 03:55:20 +01:00
|
|
|
raise InstallationError(
|
|
|
|
"%s is not a supported wheel on this platform." %
|
|
|
|
wheel.filename
|
|
|
|
)
|
|
|
|
|
2018-01-20 14:04:42 +01:00
|
|
|
# This next bit is really a sanity check.
|
|
|
|
assert install_req.is_direct == (parent_req_name is None), (
|
|
|
|
"a direct req shouldn't have a parent and also, "
|
|
|
|
"a non direct req should have a parent"
|
|
|
|
)
|
2016-09-18 22:48:01 +02:00
|
|
|
|
2014-01-12 01:50:11 +01:00
|
|
|
if not name:
|
2014-03-26 23:24:19 +01:00
|
|
|
# url or path requirement w/o an egg fragment
|
2014-01-12 01:50:11 +01:00
|
|
|
self.unnamed_requirements.append(install_req)
|
2017-12-16 14:54:57 +01:00
|
|
|
return [install_req], None
|
2018-07-27 14:27:30 +02:00
|
|
|
|
|
|
|
try:
|
|
|
|
existing_req = self.get_requirement(name)
|
|
|
|
except KeyError:
|
|
|
|
existing_req = None
|
2018-07-27 14:34:34 +02:00
|
|
|
|
|
|
|
have_conflicting_requirement = (
|
|
|
|
parent_req_name is None and
|
|
|
|
existing_req and
|
|
|
|
not existing_req.constraint and
|
|
|
|
existing_req.extras == install_req.extras and
|
|
|
|
not existing_req.req.specifier == install_req.req.specifier
|
|
|
|
)
|
|
|
|
if have_conflicting_requirement:
|
2018-07-27 14:27:30 +02:00
|
|
|
raise InstallationError(
|
2018-07-27 16:41:03 +02:00
|
|
|
"Double requirement given: %s (already in %s, name=%r)"
|
|
|
|
% (install_req, existing_req, name)
|
|
|
|
)
|
2018-07-27 14:27:30 +02:00
|
|
|
if not existing_req:
|
|
|
|
# Add requirement
|
|
|
|
self.requirements[name] = install_req
|
|
|
|
# FIXME: what about other normalizations? E.g., _ vs. -?
|
|
|
|
if name.lower() != name:
|
|
|
|
self.requirement_aliases[name.lower()] = name
|
|
|
|
# We'd want to rescan this requirements later
|
|
|
|
return [install_req], install_req
|
2018-07-27 15:01:52 +02:00
|
|
|
|
|
|
|
# Assume there's no need to scan, and that we've already
|
|
|
|
# encountered this for scanning.
|
|
|
|
result = []
|
|
|
|
if not install_req.constraint and existing_req.constraint:
|
2018-07-27 16:37:48 +02:00
|
|
|
does_not_satisfy_constraint = install_req.link and not (
|
|
|
|
existing_req.link and
|
|
|
|
install_req.link.path == existing_req.link.path
|
|
|
|
)
|
|
|
|
if does_not_satisfy_constraint:
|
2018-07-27 15:01:52 +02:00
|
|
|
self.reqs_to_cleanup.append(install_req)
|
|
|
|
raise InstallationError(
|
|
|
|
"Could not satisfy constraints for '%s': "
|
|
|
|
"installation from path or url cannot be "
|
|
|
|
"constrained to a version" % name,
|
|
|
|
)
|
|
|
|
# If we're now installing a constraint, mark the existing
|
|
|
|
# object for real installation.
|
|
|
|
existing_req.constraint = False
|
2018-07-27 16:39:14 +02:00
|
|
|
existing_req.extras = tuple(sorted(
|
|
|
|
set(existing_req.extras) | set(install_req.extras)
|
|
|
|
))
|
2018-07-27 16:41:03 +02:00
|
|
|
logger.debug(
|
|
|
|
"Setting %s extras to: %s",
|
|
|
|
existing_req, existing_req.extras,
|
|
|
|
)
|
2018-07-27 15:01:52 +02:00
|
|
|
# And now we need to scan this.
|
|
|
|
result = [existing_req]
|
2018-07-27 14:27:30 +02:00
|
|
|
|
|
|
|
# We return install_req here to allow for the caller to add it to
|
|
|
|
# the dependency information for the parent package.
|
2018-07-27 15:01:52 +02:00
|
|
|
return result, existing_req
|
2014-01-12 01:50:11 +01:00
|
|
|
|
|
|
|
def has_requirement(self, project_name):
|
2015-06-29 07:40:57 +02:00
|
|
|
name = project_name.lower()
|
2017-06-13 19:48:51 +02:00
|
|
|
if (name in self.requirements and
|
|
|
|
not self.requirements[name].constraint or
|
|
|
|
name in self.requirement_aliases and
|
|
|
|
not self.requirements[self.requirement_aliases[name]].constraint):
|
|
|
|
return True
|
|
|
|
return False
|
2014-01-12 01:50:11 +01:00
|
|
|
|
|
|
|
@property
|
|
|
|
def has_requirements(self):
|
2015-06-02 05:39:10 +02:00
|
|
|
return list(req for req in self.requirements.values() if not
|
|
|
|
req.constraint) or self.unnamed_requirements
|
2014-01-12 01:50:11 +01:00
|
|
|
|
|
|
|
def get_requirement(self, project_name):
|
|
|
|
for name in project_name, project_name.lower():
|
|
|
|
if name in self.requirements:
|
|
|
|
return self.requirements[name]
|
|
|
|
if name in self.requirement_aliases:
|
|
|
|
return self.requirements[self.requirement_aliases[name]]
|
|
|
|
raise KeyError("No project with the name %r" % project_name)
|
|
|
|
|
2014-05-08 18:12:34 +02:00
|
|
|
def cleanup_files(self):
|
2014-01-12 01:50:11 +01:00
|
|
|
"""Clean up files, remove builds."""
|
2014-12-12 18:02:23 +01:00
|
|
|
logger.debug('Cleaning up...')
|
2014-08-31 01:52:28 +02:00
|
|
|
with indent_log():
|
|
|
|
for req in self.reqs_to_cleanup:
|
|
|
|
req.remove_temporary_source()
|