123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691 |
- # The following comment should be removed at some point in the future.
- # It's included for now because without it InstallCommand.run() has a
- # couple errors where we have to know req.name is str rather than
- # Optional[str] for the InstallRequirement req.
- # mypy: strict-optional=False
- # mypy: disallow-untyped-defs=False
- from __future__ import absolute_import
- import errno
- import logging
- import operator
- import os
- import shutil
- import site
- 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.deprecation import deprecated
- 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,
- 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 Any, Iterable, List, Optional
- from pip._internal.models.format_control import FormatControl
- 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 __init__(self, *args, **kw):
- super(InstallCommand, self).__init__(*args, **kw)
- cmd_opts = self.cmd_opts
- cmd_opts.add_option(cmdoptions.requirements())
- cmd_opts.add_option(cmdoptions.constraints())
- cmd_opts.add_option(cmdoptions.no_deps())
- cmd_opts.add_option(cmdoptions.pre())
- cmd_opts.add_option(cmdoptions.editable())
- 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(cmd_opts)
- 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.)")
- cmd_opts.add_option(
- '--no-user',
- dest='use_user_site',
- action='store_false',
- help=SUPPRESS_HELP)
- cmd_opts.add_option(
- '--root',
- dest='root_path',
- metavar='dir',
- default=None,
- help="Install everything relative to this alternate root "
- "directory.")
- 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")
- cmd_opts.add_option(cmdoptions.build_dir())
- cmd_opts.add_option(cmdoptions.src())
- 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.'
- )
- 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).'
- )
- cmd_opts.add_option(
- '--force-reinstall',
- dest='force_reinstall',
- action='store_true',
- help='Reinstall all packages even if they are already '
- 'up-to-date.')
- 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!'
- )
- cmd_opts.add_option(cmdoptions.ignore_requires_python())
- cmd_opts.add_option(cmdoptions.no_build_isolation())
- cmd_opts.add_option(cmdoptions.use_pep517())
- cmd_opts.add_option(cmdoptions.no_use_pep517())
- cmd_opts.add_option(cmdoptions.install_options())
- cmd_opts.add_option(cmdoptions.global_options())
- cmd_opts.add_option(
- "--compile",
- action="store_true",
- dest="compile",
- default=True,
- help="Compile Python source files to bytecode",
- )
- cmd_opts.add_option(
- "--no-compile",
- action="store_false",
- dest="compile",
- help="Do not compile Python source files to bytecode",
- )
- 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",
- )
- cmd_opts.add_option(
- "--no-warn-conflicts",
- action="store_false",
- dest="warn_about_conflicts",
- default=True,
- help="Do not warn about broken dependencies",
- )
- cmd_opts.add_option(cmdoptions.no_binary())
- cmd_opts.add_option(cmdoptions.only_binary())
- cmd_opts.add_option(cmdoptions.prefer_binary())
- cmd_opts.add_option(cmdoptions.require_hashes())
- 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, cmd_opts)
- @with_cleanup
- def run(self, options, args):
- # type: (Values, List[Any]) -> 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)
- 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 []
- 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
- 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,
- check_supported_wheels=not options.target_dir,
- )
- warn_deprecated_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 = None
- 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.
- # We don't care about failures building legacy
- # requirements, as we'll fall through to a direct
- # install for those.
- pep517_build_failures = [
- r for r in build_failures if r.use_pep517
- ]
- if pep517_build_failures:
- raise InstallationError(
- "Could not build wheels for {} which use"
- " PEP 517 and cannot be installed directly".format(
- ", ".join(r.name for r in pep517_build_failures)))
- to_install = resolver.get_installation_order(
- requirement_set
- )
- # Consistency Checking of the package set we're installing.
- should_warn_about_conflicts = (
- not options.ignore_dependencies and
- options.warn_about_conflicts
- )
- if should_warn_about_conflicts:
- self._warn_about_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,
- pycompile=options.compile,
- warn_script_location=warn_script_location,
- use_user_site=options.use_user_site,
- )
- 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)
- 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)
- return ERROR
- if options.target_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):
- ensure_dir(target_dir)
- # Checking both purelib and platlib directories for installed
- # packages to be moved to target directory
- lib_dir_list = []
- with target_temp_dir:
- # 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 _warn_about_conflicts(self, to_install):
- try:
- package_set, _dep_info = check_install_conflicts(to_install)
- except Exception:
- logger.error("Error checking for conflicts.", exc_info=True)
- return
- missing, conflicting = _dep_info
- # NOTE: There is some duplication here from pip check
- for project_name in missing:
- version = package_set[project_name][0]
- for dependency in missing[project_name]:
- logger.critical(
- "%s %s requires %s, which is not installed.",
- project_name, version, dependency[1],
- )
- for project_name in conflicting:
- version = package_set[project_name][0]
- for dep_name, dep_version, req in conflicting[project_name]:
- logger.critical(
- "%s %s has requirement %s, but you'll have %s %s which is "
- "incompatible.",
- project_name, version, req, dep_name, dep_version,
- )
- def get_lib_location_guesses(*args, **kwargs):
- scheme = distutils_scheme('', *args, **kwargs)
- return [scheme['purelib'], scheme['platlib']]
- def site_packages_writable(**kwargs):
- return all(
- test_writable_dir(d) for d in set(get_lib_location_guesses(**kwargs))
- )
- 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 warn_deprecated_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
- deprecated(
- reason=(
- "Location-changing options found in --install-option: {}. "
- "This configuration may cause unexpected behavior and is "
- "unsupported.".format(
- "; ".join(offenders)
- )
- ),
- replacement=(
- "using pip-level options like --user, --prefix, --root, and "
- "--target"
- ),
- gone_in="20.2",
- issue=7309,
- )
- def create_env_error_message(error, show_traceback, using_user_site):
- """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"
|