from __future__ import absolute_import
from distutils import sysconfig
from distutils.util import change_root
from email.parser import FeedParser
from pip._vendor import pkg_resources, six
from pip._vendor.packaging import specifiers
from pip._vendor.packaging.markers import Marker
from pip._vendor.packaging.requirements import InvalidRequirement, Requirement
from pip._vendor.packaging.utils import canonicalize_name
from pip._vendor.packaging.version import Version, parse as parse_version
from pip._vendor.six.moves import configparser
from pip.compat import native_str, get_stdlib, WINDOWS
from pip.download import is_url, url_to_path, path_to_url, is_archive_file
from pip.exceptions import (
InstallationError, UninstallationError,
from pip.locations import (
bin_py, running_under_virtualenv, PIP_DELETE_MARKER_FILENAME, bin_user,
display_path, rmtree, ask_path_exists, backup_dir, is_installable_dir,
dist_in_usersite, dist_in_site_packages, egg_link_path,
call_subprocess, read_text_file, FakeFile, _make_build_dir, ensure_dir,
get_installed_version, normalize_path, dist_is_local,
from pip.utils.hashes import Hashes
from pip.utils.deprecation import RemovedInPip10Warning
from pip.utils.logging import indent_log
from pip.utils.setuptools_build import SETUPTOOLS_SHIM
from pip.utils.ui import open_spinner
from pip.req.req_uninstall import UninstallPathSet
from pip.wheel import move_wheel_files, Wheel
logger = logging.getLogger(__name__)
operators = specifiers.Specifier._operators.keys()
m = re.match(r'^(.+)(\[[^\]]+\])$', path)
path_no_extras = m.group(1)
return path_no_extras, extras
def _safe_extras(extras):
return set(pkg_resources.safe_extra(extra) for extra in extras)
class InstallRequirement(object):
def __init__(self, req, comes_from, source_dir=None, editable=False,
link=None, as_egg=False, update=True,
pycompile=True, markers=None, isolated=False, options=None,
wheel_cache=None, constraint=False):
if isinstance(req, six.string_types):
except InvalidRequirement:
add_msg = "It looks like a path. Does it exist ?"
elif '=' in req and not any(op in req for op in operators):
add_msg = "= is not a valid operator. Did you mean == ?"
add_msg = traceback.format_exc()
"Invalid requirement: '%s'\n%s" % (req, add_msg))
self.extras = _safe_extras(req.extras)
self.comes_from = comes_from
self.constraint = constraint
self.source_dir = source_dir
self._wheel_cache = wheel_cache
self.link = self.original_link = link
self.markers = req and req.marker
self._egg_info_path = None
# This holds the pkg_resources.Distribution object if this requirement
# This hold the pkg_resources.Distribution object if this requirement
# conflicts with another installed distribution:
self.conflicts_with = None
# Temporary build location
self._temp_build_dir = None
# Used to store the global directory where the _temp_build_dir should
# have been created. Cf _correct_build_location method.
self._ideal_build_dir = None
# True if the editable should be updated:
# Set to True after successful installation
self.install_succeeded = None
# UninstallPathSet of uninstalled distribution (for possible rollback)
# Set True if a legitimate do-nothing-on-uninstall has happened - e.g.
# system site packages, stdlib packages.
self.nothing_to_uninstall = False
self.use_user_site = False
self.options = options if options else {}
self.pycompile = pycompile
# Set to True after successful preparation of this requirement
def from_editable(cls, editable_req, comes_from=None, default_vcs=None,
isolated=False, options=None, wheel_cache=None,
from pip.index import Link
name, url, extras_override = parse_editable(
editable_req, default_vcs)
if url.startswith('file:'):
source_dir = url_to_path(url)
res = cls(name, comes_from, source_dir=source_dir,
options=options if options else {},
if extras_override is not None:
res.extras = _safe_extras(extras_override)
cls, name, comes_from=None, isolated=False, options=None,
wheel_cache=None, constraint=False):
"""Creates an InstallRequirement from a name, which might be a
requirement, directory containing 'setup.py', filename, or URL.
from pip.index import Link
name, markers = name.split(marker_sep, 1)
markers = markers.strip()
markers = Marker(markers)
path = os.path.normpath(os.path.abspath(name))
p, extras = _strip_extras(path)
(os.path.sep in name or name.startswith('.'))):
if not is_installable_dir(p):
"Directory %r is not installable. File 'setup.py' "
link = Link(path_to_url(p))
if not os.path.isfile(p):
'Requirement %r looks like a filename, but the '
link = Link(path_to_url(p))
# it's a local file, dir, or url
# Handle relative file URLs
if link.scheme == 'file' and re.search(r'\.\./', link.url):
path_to_url(os.path.normpath(os.path.abspath(link.path))))
wheel = Wheel(link.filename) # can raise InvalidWheelFilename
req = "%s==%s" % (wheel.name, wheel.version)
# set the req to the egg fragment. when it's not there, this
# will become an 'unnamed' requirement
# a requirement specifier
options = options if options else {}
res = cls(req, comes_from, link=link, markers=markers,
isolated=isolated, options=options,
wheel_cache=wheel_cache, constraint=constraint)
res.extras = _safe_extras(
Requirement('placeholder' + extras).extras)
s += ' from %s' % self.link.url
s = self.link.url if self.link else None
if self.satisfied_by is not None:
s += ' in %s' % display_path(self.satisfied_by.location)
if isinstance(self.comes_from, six.string_types):
comes_from = self.comes_from
comes_from = self.comes_from.from_path()
s += ' (from %s)' % comes_from
return '<%s object: %s editable=%r>' % (
self.__class__.__name__, str(self), self.editable)
def populate_link(self, finder, upgrade, require_hashes):
"""Ensure that if a link can be found for this, that it is found.
Note that self.link may still be None - if Upgrade is False and the
requirement is already installed.
If 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.
self.link = finder.find_requirement(self, upgrade)
if self._wheel_cache is not None and not require_hashes:
self.link = self._wheel_cache.cached_wheel(self.link, self.name)
if old_link != self.link:
logger.debug('Using cached wheel link: %s', self.link)
return self.req.specifier
"""Return whether I am pinned to an exact version.
For example, some-package==1.2 is pinned; some-package>1.2 is not.
specifiers = self.specifier
return (len(specifiers) == 1 and
next(iter(specifiers)).operator in ('==', '==='))
if isinstance(self.comes_from, six.string_types):
comes_from = self.comes_from
comes_from = self.comes_from.from_path()
def build_location(self, build_dir):
if self._temp_build_dir is not None:
return self._temp_build_dir
# for requirement via a path to a directory: the name of the
# package is not available yet so we create a temp directory
# Once run_egg_info will have run, we'll be able
# to fix it via _correct_build_location
# Some systems have /tmp as a symlink which confuses custom
# builds (such as numpy). Thus, we ensure that the real path
self._temp_build_dir = os.path.realpath(
tempfile.mkdtemp('-build', 'pip-')
self._ideal_build_dir = build_dir
return self._temp_build_dir
# FIXME: Is there a better place to create the build_dir? (hg and bzr
if not os.path.exists(build_dir):
logger.debug('Creating directory %s', build_dir)
_make_build_dir(build_dir)
return os.path.join(build_dir, name)
def _correct_build_location(self):
"""Move self._temp_build_dir to self._ideal_build_dir/self.req.name
For some requirements (e.g. a path to a directory), the name of the
package is not available until we run egg_info, so the build_location
will return a temporary directory and store the _ideal_build_dir.
This is only called by self.egg_info_path to fix the temporary build
if self.source_dir is not None:
assert self.req is not None
assert self._temp_build_dir
assert self._ideal_build_dir
old_location = self._temp_build_dir
self._temp_build_dir = None
new_location = self.build_location(self._ideal_build_dir)
if os.path.exists(new_location):
'A package already exists in %s; please remove it to continue'
% display_path(new_location))
'Moving package %s from %s to new location %s',
self, display_path(old_location), display_path(new_location),
shutil.move(old_location, new_location)
self._temp_build_dir = new_location
self._ideal_build_dir = None
self.source_dir = new_location
self._egg_info_path = None
return native_str(pkg_resources.safe_name(self.req.name))
self.link and self.link.subdirectory_fragment or '')
assert self.source_dir, "No source dir for %s" % self
if get_installed_version('setuptools') is None:
add_msg = "Please install setuptools."
add_msg = traceback.format_exc()
# Setuptools is not available
"Could not import setuptools which is required to "
"install from a source distribution.\n%s" % add_msg
setup_py = os.path.join(self.setup_py_dir, 'setup.py')
# Python2 __file__ should not be unicode
if six.PY2 and isinstance(setup_py, six.text_type):
setup_py = setup_py.encode(sys.getfilesystemencoding())
'Running setup.py (path:%s) egg_info for package %s',
self.setup_py, self.name,
'Running setup.py (path:%s) egg_info for package from %s',
self.setup_py, self.link,
script = SETUPTOOLS_SHIM % self.setup_py
base_cmd = [sys.executable, '-c', script]
base_cmd += ["--no-user-cfg"]
egg_info_cmd = base_cmd + ['egg_info']
# We can't put the .egg-info files at the root, because then the
# source code will be mistaken for an installed egg, causing
egg_info_dir = os.path.join(self.setup_py_dir, 'pip-egg-info')
egg_base_option = ['--egg-base', 'pip-egg-info']
egg_info_cmd + egg_base_option,
command_desc='python setup.py egg_info')
if isinstance(parse_version(self.pkg_info()["Version"]), Version):
self.pkg_info()["Version"],
self._correct_build_location()
metadata_name = canonicalize_name(self.pkg_info()["Name"])
if canonicalize_name(self.req.name) != metadata_name:
'Running setup.py (path:%s) egg_info for package %s '
'produced metadata for project name %s. Fix your '
self.setup_py, self.name, metadata_name, self.name
self.req = Requirement(metadata_name)
def egg_info_data(self, filename):
if self.satisfied_by is not None:
if not self.satisfied_by.has_metadata(filename):
return self.satisfied_by.get_metadata(filename)
filename = self.egg_info_path(filename)
if not os.path.exists(filename):
data = read_text_file(filename)
def egg_info_path(self, filename):
if self._egg_info_path is None:
base = os.path.join(self.setup_py_dir, 'pip-egg-info')
filenames = os.listdir(base)
for root, dirs, files in os.walk(base):
# Iterate over a copy of ``dirs``, since mutating
# a list while iterating over it can cause trouble.
# (See https://github.com/pypa/pip/pull/462.)
# Don't search in anything that looks like a virtualenv
os.path.join(root, dir, 'bin', 'python')