| Viewing file:  install.py (28.05 KB)      -rw-r--r-- Select action/file-type:
 
  (+) |  (+) |  (+) | Code (+) | Session (+) |  (+) | SDB (+) |  (+) |  (+) |  (+) |  (+) |  (+) | 
 
from __future__ import absolute_import
 import errno
 import logging
 import operator
 import os
 import shutil
 import site
 import sys
 from os import path
 from optparse import SUPPRESS_HELP
 
 from pip._vendor import pkg_resources
 from pip._vendor.packaging.utils import canonicalize_name
 
 from pip._internal.cache import WheelCache
 from pip._internal.cli import cmdoptions
 from pip._internal.cli.cmdoptions import make_target_python
 from pip._internal.cli.req_command import RequirementCommand, with_cleanup
 from pip._internal.cli.status_codes import ERROR, SUCCESS
 from pip._internal.exceptions import CommandError, InstallationError
 from pip._internal.locations import distutils_scheme
 from pip._internal.operations.check import check_install_conflicts
 from pip._internal.req import install_given_reqs
 from pip._internal.req.req_tracker import get_requirement_tracker
 from pip._internal.utils.datetime import today_is_later_than
 from pip._internal.utils.distutils_args import parse_distutils_args
 from pip._internal.utils.filesystem import test_writable_dir
 from pip._internal.utils.misc import (
 ensure_dir,
 get_installed_version,
 get_pip_version,
 protect_pip_from_modification_on_windows,
 write_output,
 )
 from pip._internal.utils.temp_dir import TempDirectory
 from pip._internal.utils.typing import MYPY_CHECK_RUNNING
 from pip._internal.utils.virtualenv import virtualenv_no_global
 from pip._internal.wheel_builder import build, should_build_for_install_command
 
 if MYPY_CHECK_RUNNING:
 from optparse import Values
 from typing import Iterable, List, Optional
 
 from pip._internal.models.format_control import FormatControl
 from pip._internal.operations.check import ConflictDetails
 from pip._internal.req.req_install import InstallRequirement
 from pip._internal.wheel_builder import BinaryAllowedPredicate
 
 
 logger = logging.getLogger(__name__)
 
 
 def get_check_binary_allowed(format_control):
 # type: (FormatControl) -> BinaryAllowedPredicate
 def check_binary_allowed(req):
 # type: (InstallRequirement) -> bool
 if req.use_pep517:
 return True
 canonical_name = canonicalize_name(req.name)
 allowed_formats = format_control.get_allowed_formats(canonical_name)
 return "binary" in allowed_formats
 
 return check_binary_allowed
 
 
 class InstallCommand(RequirementCommand):
 """
 Install packages from:
 
 - PyPI (and other indexes) using requirement specifiers.
 - VCS project urls.
 - Local project directories.
 - Local or remote source archives.
 
 pip also supports installing from "requirements files", which provide
 an easy way to specify a whole environment to be installed.
 """
 
 usage = """
 %prog [options] <requirement specifier> [package-index-options] ...
 %prog [options] -r <requirements file> [package-index-options] ...
 %prog [options] [-e] <vcs project url> ...
 %prog [options] [-e] <local project path> ...
 %prog [options] <archive url/path> ..."""
 
 def add_options(self):
 # type: () -> None
 self.cmd_opts.add_option(cmdoptions.requirements())
 self.cmd_opts.add_option(cmdoptions.constraints())
 self.cmd_opts.add_option(cmdoptions.no_deps())
 self.cmd_opts.add_option(cmdoptions.pre())
 
 self.cmd_opts.add_option(cmdoptions.editable())
 self.cmd_opts.add_option(
 '-t', '--target',
 dest='target_dir',
 metavar='dir',
 default=None,
 help='Install packages into <dir>. '
 'By default this will not replace existing files/folders in '
 '<dir>. Use --upgrade to replace existing packages in <dir> '
 'with new versions.'
 )
 cmdoptions.add_target_python_options(self.cmd_opts)
 
 self.cmd_opts.add_option(
 '--user',
 dest='use_user_site',
 action='store_true',
 help="Install to the Python user install directory for your "
 "platform. Typically ~/.local/, or %APPDATA%\\Python on "
 "Windows. (See the Python documentation for site.USER_BASE "
 "for full details.)")
 self.cmd_opts.add_option(
 '--no-user',
 dest='use_user_site',
 action='store_false',
 help=SUPPRESS_HELP)
 self.cmd_opts.add_option(
 '--root',
 dest='root_path',
 metavar='dir',
 default=None,
 help="Install everything relative to this alternate root "
 "directory.")
 self.cmd_opts.add_option(
 '--prefix',
 dest='prefix_path',
 metavar='dir',
 default=None,
 help="Installation prefix where lib, bin and other top-level "
 "folders are placed")
 
 self.cmd_opts.add_option(cmdoptions.build_dir())
 
 self.cmd_opts.add_option(cmdoptions.src())
 
 self.cmd_opts.add_option(
 '-U', '--upgrade',
 dest='upgrade',
 action='store_true',
 help='Upgrade all specified packages to the newest available '
 'version. The handling of dependencies depends on the '
 'upgrade-strategy used.'
 )
 
 self.cmd_opts.add_option(
 '--upgrade-strategy',
 dest='upgrade_strategy',
 default='only-if-needed',
 choices=['only-if-needed', 'eager'],
 help='Determines how dependency upgrading should be handled '
 '[default: %default]. '
 '"eager" - dependencies are upgraded regardless of '
 'whether the currently installed version satisfies the '
 'requirements of the upgraded package(s). '
 '"only-if-needed" -  are upgraded only when they do not '
 'satisfy the requirements of the upgraded package(s).'
 )
 
 self.cmd_opts.add_option(
 '--force-reinstall',
 dest='force_reinstall',
 action='store_true',
 help='Reinstall all packages even if they are already '
 'up-to-date.')
 
 self.cmd_opts.add_option(
 '-I', '--ignore-installed',
 dest='ignore_installed',
 action='store_true',
 help='Ignore the installed packages, overwriting them. '
 'This can break your system if the existing package '
 'is of a different version or was installed '
 'with a different package manager!'
 )
 
 self.cmd_opts.add_option(cmdoptions.ignore_requires_python())
 self.cmd_opts.add_option(cmdoptions.no_build_isolation())
 self.cmd_opts.add_option(cmdoptions.use_pep517())
 self.cmd_opts.add_option(cmdoptions.no_use_pep517())
 
 self.cmd_opts.add_option(cmdoptions.install_options())
 self.cmd_opts.add_option(cmdoptions.global_options())
 
 self.cmd_opts.add_option(
 "--compile",
 action="store_true",
 dest="compile",
 default=True,
 help="Compile Python source files to bytecode",
 )
 
 self.cmd_opts.add_option(
 "--no-compile",
 action="store_false",
 dest="compile",
 help="Do not compile Python source files to bytecode",
 )
 
 self.cmd_opts.add_option(
 "--no-warn-script-location",
 action="store_false",
 dest="warn_script_location",
 default=True,
 help="Do not warn when installing scripts outside PATH",
 )
 self.cmd_opts.add_option(
 "--no-warn-conflicts",
 action="store_false",
 dest="warn_about_conflicts",
 default=True,
 help="Do not warn about broken dependencies",
 )
 
 self.cmd_opts.add_option(cmdoptions.no_binary())
 self.cmd_opts.add_option(cmdoptions.only_binary())
 self.cmd_opts.add_option(cmdoptions.prefer_binary())
 self.cmd_opts.add_option(cmdoptions.require_hashes())
 self.cmd_opts.add_option(cmdoptions.progress_bar())
 
 index_opts = cmdoptions.make_option_group(
 cmdoptions.index_group,
 self.parser,
 )
 
 self.parser.insert_option_group(0, index_opts)
 self.parser.insert_option_group(0, self.cmd_opts)
 
 @with_cleanup
 def run(self, options, args):
 # type: (Values, List[str]) -> int
 if options.use_user_site and options.target_dir is not None:
 raise CommandError("Can not combine '--user' and '--target'")
 
 cmdoptions.check_install_build_global(options)
 
 def is_venv():
 return (hasattr(sys, 'real_prefix') or
 (hasattr(sys, 'base_prefix') and
 sys.base_prefix != sys.prefix))
 
 # Check whether we have root privileges and aren't in venv/virtualenv
 if os.getuid() == 0 and not is_venv() and not options.root_path:
 command = path.basename(sys.argv[0])
 if command == "__main__.py":
 command = path.basename(sys.executable) + " -m pip"
 logger.warning(
 "Running pip install with root privileges is "
 "generally not a good idea. Try `%s install --user` instead."
 % command
 )
 
 upgrade_strategy = "to-satisfy-only"
 if options.upgrade:
 upgrade_strategy = options.upgrade_strategy
 
 cmdoptions.check_dist_restriction(options, check_target=True)
 
 install_options = options.install_options or []
 
 logger.debug("Using %s", get_pip_version())
 options.use_user_site = decide_user_install(
 options.use_user_site,
 prefix_path=options.prefix_path,
 target_dir=options.target_dir,
 root_path=options.root_path,
 isolated_mode=options.isolated_mode,
 )
 
 target_temp_dir = None  # type: Optional[TempDirectory]
 target_temp_dir_path = None  # type: Optional[str]
 if options.target_dir:
 options.ignore_installed = True
 options.target_dir = os.path.abspath(options.target_dir)
 if (os.path.exists(options.target_dir) and not
 os.path.isdir(options.target_dir)):
 raise CommandError(
 "Target path exists but is not a directory, will not "
 "continue."
 )
 
 # Create a target directory for using with the target option
 target_temp_dir = TempDirectory(kind="target")
 target_temp_dir_path = target_temp_dir.path
 self.enter_context(target_temp_dir)
 
 global_options = options.global_options or []
 
 session = self.get_default_session(options)
 
 target_python = make_target_python(options)
 finder = self._build_package_finder(
 options=options,
 session=session,
 target_python=target_python,
 ignore_requires_python=options.ignore_requires_python,
 )
 build_delete = (not (options.no_clean or options.build_dir))
 wheel_cache = WheelCache(options.cache_dir, options.format_control)
 
 req_tracker = self.enter_context(get_requirement_tracker())
 
 directory = TempDirectory(
 options.build_dir,
 delete=build_delete,
 kind="install",
 globally_managed=True,
 )
 
 try:
 reqs = self.get_requirements(args, options, finder, session)
 
 reject_location_related_install_options(
 reqs, options.install_options
 )
 
 preparer = self.make_requirement_preparer(
 temp_build_dir=directory,
 options=options,
 req_tracker=req_tracker,
 session=session,
 finder=finder,
 use_user_site=options.use_user_site,
 )
 resolver = self.make_resolver(
 preparer=preparer,
 finder=finder,
 options=options,
 wheel_cache=wheel_cache,
 use_user_site=options.use_user_site,
 ignore_installed=options.ignore_installed,
 ignore_requires_python=options.ignore_requires_python,
 force_reinstall=options.force_reinstall,
 upgrade_strategy=upgrade_strategy,
 use_pep517=options.use_pep517,
 )
 
 self.trace_basic_info(finder)
 
 requirement_set = resolver.resolve(
 reqs, check_supported_wheels=not options.target_dir
 )
 
 try:
 pip_req = requirement_set.get_requirement("pip")
 except KeyError:
 modifying_pip = False
 else:
 # If we're not replacing an already installed pip,
 # we're not modifying it.
 modifying_pip = pip_req.satisfied_by is None
 protect_pip_from_modification_on_windows(
 modifying_pip=modifying_pip
 )
 
 check_binary_allowed = get_check_binary_allowed(
 finder.format_control
 )
 
 reqs_to_build = [
 r for r in requirement_set.requirements.values()
 if should_build_for_install_command(
 r, check_binary_allowed
 )
 ]
 
 _, build_failures = build(
 reqs_to_build,
 wheel_cache=wheel_cache,
 build_options=[],
 global_options=[],
 )
 
 # If we're using PEP 517, we cannot do a direct install
 # so we fail here.
 pep517_build_failure_names = [
 r.name   # type: ignore
 for r in build_failures if r.use_pep517
 ]  # type: List[str]
 if pep517_build_failure_names:
 raise InstallationError(
 "Could not build wheels for {} which use"
 " PEP 517 and cannot be installed directly".format(
 ", ".join(pep517_build_failure_names)
 )
 )
 
 # For now, we just warn about failures building legacy
 # requirements, as we'll fall through to a direct
 # install for those.
 for r in build_failures:
 if not r.use_pep517:
 r.legacy_install_reason = 8368
 
 to_install = resolver.get_installation_order(
 requirement_set
 )
 
 # Check for conflicts in the package set we're installing.
 conflicts = None  # type: Optional[ConflictDetails]
 should_warn_about_conflicts = (
 not options.ignore_dependencies and
 options.warn_about_conflicts
 )
 if should_warn_about_conflicts:
 conflicts = self._determine_conflicts(to_install)
 
 # Don't warn about script install locations if
 # --target has been specified
 warn_script_location = options.warn_script_location
 if options.target_dir:
 warn_script_location = False
 
 installed = install_given_reqs(
 to_install,
 install_options,
 global_options,
 root=options.root_path,
 home=target_temp_dir_path,
 prefix=options.prefix_path,
 warn_script_location=warn_script_location,
 use_user_site=options.use_user_site,
 pycompile=options.compile,
 )
 
 lib_locations = get_lib_location_guesses(
 user=options.use_user_site,
 home=target_temp_dir_path,
 root=options.root_path,
 prefix=options.prefix_path,
 isolated=options.isolated_mode,
 )
 working_set = pkg_resources.WorkingSet(lib_locations)
 
 installed.sort(key=operator.attrgetter('name'))
 items = []
 for result in installed:
 item = result.name
 try:
 installed_version = get_installed_version(
 result.name, working_set=working_set
 )
 if installed_version:
 item += '-' + installed_version
 except Exception:
 pass
 items.append(item)
 
 if conflicts is not None:
 self._warn_about_conflicts(
 conflicts,
 new_resolver='2020-resolver' in options.features_enabled,
 )
 
 installed_desc = ' '.join(items)
 if installed_desc:
 write_output(
 'Successfully installed %s', installed_desc,
 )
 except EnvironmentError as error:
 show_traceback = (self.verbosity >= 1)
 
 message = create_env_error_message(
 error, show_traceback, options.use_user_site,
 )
 logger.error(message, exc_info=show_traceback)  # noqa
 
 return ERROR
 
 if options.target_dir:
 assert target_temp_dir
 self._handle_target_dir(
 options.target_dir, target_temp_dir, options.upgrade
 )
 
 return SUCCESS
 
 def _handle_target_dir(self, target_dir, target_temp_dir, upgrade):
 # type: (str, TempDirectory, bool) -> None
 ensure_dir(target_dir)
 
 # Checking both purelib and platlib directories for installed
 # packages to be moved to target directory
 lib_dir_list = []
 
 # Checking both purelib and platlib directories for installed
 # packages to be moved to target directory
 scheme = distutils_scheme('', home=target_temp_dir.path)
 purelib_dir = scheme['purelib']
 platlib_dir = scheme['platlib']
 data_dir = scheme['data']
 
 if os.path.exists(purelib_dir):
 lib_dir_list.append(purelib_dir)
 if os.path.exists(platlib_dir) and platlib_dir != purelib_dir:
 lib_dir_list.append(platlib_dir)
 if os.path.exists(data_dir):
 lib_dir_list.append(data_dir)
 
 for lib_dir in lib_dir_list:
 for item in os.listdir(lib_dir):
 if lib_dir == data_dir:
 ddir = os.path.join(data_dir, item)
 if any(s.startswith(ddir) for s in lib_dir_list[:-1]):
 continue
 target_item_dir = os.path.join(target_dir, item)
 if os.path.exists(target_item_dir):
 if not upgrade:
 logger.warning(
 'Target directory %s already exists. Specify '
 '--upgrade to force replacement.',
 target_item_dir
 )
 continue
 if os.path.islink(target_item_dir):
 logger.warning(
 'Target directory %s already exists and is '
 'a link. pip will not automatically replace '
 'links, please remove if replacement is '
 'desired.',
 target_item_dir
 )
 continue
 if os.path.isdir(target_item_dir):
 shutil.rmtree(target_item_dir)
 else:
 os.remove(target_item_dir)
 
 shutil.move(
 os.path.join(lib_dir, item),
 target_item_dir
 )
 
 def _determine_conflicts(self, to_install):
 # type: (List[InstallRequirement]) -> Optional[ConflictDetails]
 try:
 return check_install_conflicts(to_install)
 except Exception:
 logger.exception(
 "Error while checking for conflicts. Please file an issue on "
 "pip's issue tracker: https://github.com/pypa/pip/issues/new"
 )
 return None
 
 def _warn_about_conflicts(self, conflict_details, new_resolver):
 # type: (ConflictDetails, bool) -> None
 package_set, (missing, conflicting) = conflict_details
 if not missing and not conflicting:
 return
 
 parts = []  # type: List[str]
 if not new_resolver:
 parts.append(
 "After October 2020 you may experience errors when installing "
 "or updating packages. This is because pip will change the "
 "way that it resolves dependency conflicts.\n"
 )
 parts.append(
 "We recommend you use --use-feature=2020-resolver to test "
 "your packages with the new resolver before it becomes the "
 "default.\n"
 )
 elif not today_is_later_than(year=2020, month=7, day=31):
 # NOTE: trailing newlines here are intentional
 parts.append(
 "Pip will install or upgrade your package(s) and its "
 "dependencies without taking into account other packages you "
 "already have installed. This may cause an uncaught "
 "dependency conflict.\n"
 )
 form_link = "https://forms.gle/cWKMoDs8sUVE29hz9"
 parts.append(
 "If you would like pip to take your other packages into "
 "account, please tell us here: {}\n".format(form_link)
 )
 
 # NOTE: There is some duplication here, with commands/check.py
 for project_name in missing:
 version = package_set[project_name][0]
 for dependency in missing[project_name]:
 message = (
 "{name} {version} requires {requirement}, "
 "which is not installed."
 ).format(
 name=project_name,
 version=version,
 requirement=dependency[1],
 )
 parts.append(message)
 
 for project_name in conflicting:
 version = package_set[project_name][0]
 for dep_name, dep_version, req in conflicting[project_name]:
 message = (
 "{name} {version} requires {requirement}, but you'll have "
 "{dep_name} {dep_version} which is incompatible."
 ).format(
 name=project_name,
 version=version,
 requirement=req,
 dep_name=dep_name,
 dep_version=dep_version,
 )
 parts.append(message)
 
 logger.critical("\n".join(parts))
 
 
 def get_lib_location_guesses(
 user=False,  # type: bool
 home=None,  # type: Optional[str]
 root=None,  # type: Optional[str]
 isolated=False,  # type: bool
 prefix=None  # type: Optional[str]
 ):
 # type:(...) -> List[str]
 scheme = distutils_scheme('', user=user, home=home, root=root,
 isolated=isolated, prefix=prefix)
 return [scheme['purelib'], scheme['platlib']]
 
 
 def site_packages_writable(root, isolated):
 # type: (Optional[str], bool) -> bool
 return all(
 test_writable_dir(d) for d in set(
 get_lib_location_guesses(root=root, isolated=isolated))
 )
 
 
 def decide_user_install(
 use_user_site,  # type: Optional[bool]
 prefix_path=None,  # type: Optional[str]
 target_dir=None,  # type: Optional[str]
 root_path=None,  # type: Optional[str]
 isolated_mode=False,  # type: bool
 ):
 # type: (...) -> bool
 """Determine whether to do a user install based on the input options.
 
 If use_user_site is False, no additional checks are done.
 If use_user_site is True, it is checked for compatibility with other
 options.
 If use_user_site is None, the default behaviour depends on the environment,
 which is provided by the other arguments.
 """
 # In some cases (config from tox), use_user_site can be set to an integer
 # rather than a bool, which 'use_user_site is False' wouldn't catch.
 if (use_user_site is not None) and (not use_user_site):
 logger.debug("Non-user install by explicit request")
 return False
 
 if use_user_site:
 if prefix_path:
 raise CommandError(
 "Can not combine '--user' and '--prefix' as they imply "
 "different installation locations"
 )
 if virtualenv_no_global():
 raise InstallationError(
 "Can not perform a '--user' install. User site-packages "
 "are not visible in this virtualenv."
 )
 logger.debug("User install by explicit request")
 return True
 
 # If we are here, user installs have not been explicitly requested/avoided
 assert use_user_site is None
 
 # user install incompatible with --prefix/--target
 if prefix_path or target_dir:
 logger.debug("Non-user install due to --prefix or --target option")
 return False
 
 # If user installs are not enabled, choose a non-user install
 if not site.ENABLE_USER_SITE:
 logger.debug("Non-user install because user site-packages disabled")
 return False
 
 # If we have permission for a non-user install, do that,
 # otherwise do a user install.
 if site_packages_writable(root=root_path, isolated=isolated_mode):
 logger.debug("Non-user install because site-packages writeable")
 return False
 
 logger.info("Defaulting to user installation because normal site-packages "
 "is not writeable")
 return True
 
 
 def reject_location_related_install_options(requirements, options):
 # type: (List[InstallRequirement], Optional[List[str]]) -> None
 """If any location-changing --install-option arguments were passed for
 requirements or on the command-line, then show a deprecation warning.
 """
 def format_options(option_names):
 # type: (Iterable[str]) -> List[str]
 return ["--{}".format(name.replace("_", "-")) for name in option_names]
 
 offenders = []
 
 for requirement in requirements:
 install_options = requirement.install_options
 location_options = parse_distutils_args(install_options)
 if location_options:
 offenders.append(
 "{!r} from {}".format(
 format_options(location_options.keys()), requirement
 )
 )
 
 if options:
 location_options = parse_distutils_args(options)
 if location_options:
 offenders.append(
 "{!r} from command line".format(
 format_options(location_options.keys())
 )
 )
 
 if not offenders:
 return
 
 raise CommandError(
 "Location-changing options found in --install-option: {}."
 " This is unsupported, use pip-level options like --user,"
 " --prefix, --root, and --target instead.".format(
 "; ".join(offenders)
 )
 )
 
 
 def create_env_error_message(error, show_traceback, using_user_site):
 # type: (EnvironmentError, bool, bool) -> str
 """Format an error message for an EnvironmentError
 
 It may occur anytime during the execution of the install command.
 """
 parts = []
 
 # Mention the error if we are not going to show a traceback
 parts.append("Could not install packages due to an EnvironmentError")
 if not show_traceback:
 parts.append(": ")
 parts.append(str(error))
 else:
 parts.append(".")
 
 # Spilt the error indication from a helper message (if any)
 parts[-1] += "\n"
 
 # Suggest useful actions to the user:
 #  (1) using user site-packages or (2) verifying the permissions
 if error.errno == errno.EACCES:
 user_option_part = "Consider using the `--user` option"
 permissions_part = "Check the permissions"
 
 if not using_user_site:
 parts.extend([
 user_option_part, " or ",
 permissions_part.lower(),
 ])
 else:
 parts.append(permissions_part)
 parts.append(".\n")
 
 return "".join(parts).strip() + "\n"
 
 |