7 from optparse
import SUPPRESS_HELP
, Values
8 from typing
import List
, Optional
10 from pip
._vendor
.rich
import print_json
12 from pip
._internal
.cache
import WheelCache
13 from pip
._internal
.cli
import cmdoptions
14 from pip
._internal
.cli
.cmdoptions
import make_target_python
15 from pip
._internal
.cli
.req_command
import (
20 from pip
._internal
.cli
.status_codes
import ERROR
, SUCCESS
21 from pip
._internal
.exceptions
import CommandError
, InstallationError
22 from pip
._internal
.locations
import get_scheme
23 from pip
._internal
.metadata
import get_environment
24 from pip
._internal
.models
.installation_report
import InstallationReport
25 from pip
._internal
.operations
.build
.build_tracker
import get_build_tracker
26 from pip
._internal
.operations
.check
import ConflictDetails
, check_install_conflicts
27 from pip
._internal
.req
import install_given_reqs
28 from pip
._internal
.req
.req_install
import (
30 check_legacy_setup_py_options
,
32 from pip
._internal
.utils
.compat
import WINDOWS
33 from pip
._internal
.utils
.filesystem
import test_writable_dir
34 from pip
._internal
.utils
.logging
import getLogger
35 from pip
._internal
.utils
.misc
import (
36 check_externally_managed
,
39 protect_pip_from_modification_on_windows
,
42 from pip
._internal
.utils
.temp_dir
import TempDirectory
43 from pip
._internal
.utils
.virtualenv
import (
44 running_under_virtualenv
,
47 from pip
._internal
.wheel_builder
import build
, should_build_for_install_command
49 logger
= getLogger(__name__
)
52 class InstallCommand(RequirementCommand
):
54 Install packages from:
56 - PyPI (and other indexes) using requirement specifiers.
58 - Local project directories.
59 - Local or remote source archives.
61 pip also supports installing from "requirements files", which provide
62 an easy way to specify a whole environment to be installed.
66 %prog [options] <requirement specifier> [package-index-options] ...
67 %prog [options] -r <requirements file> [package-index-options] ...
68 %prog [options] [-e] <vcs project url> ...
69 %prog [options] [-e] <local project path> ...
70 %prog [options] <archive url/path> ..."""
72 def add_options(self
) -> None:
73 self
.cmd_opts
.add_option(cmdoptions
.requirements())
74 self
.cmd_opts
.add_option(cmdoptions
.constraints())
75 self
.cmd_opts
.add_option(cmdoptions
.no_deps())
76 self
.cmd_opts
.add_option(cmdoptions
.pre())
78 self
.cmd_opts
.add_option(cmdoptions
.editable())
79 self
.cmd_opts
.add_option(
85 "Don't actually install anything, just print what would be. "
86 "Can be used in combination with --ignore-installed "
87 "to 'resolve' the requirements."
90 self
.cmd_opts
.add_option(
97 "Install packages into <dir>. "
98 "By default this will not replace existing files/folders in "
99 "<dir>. Use --upgrade to replace existing packages in <dir> "
103 cmdoptions
.add_target_python_options(self
.cmd_opts
)
105 self
.cmd_opts
.add_option(
107 dest
="use_user_site",
110 "Install to the Python user install directory for your "
111 "platform. Typically ~/.local/, or %APPDATA%\\Python on "
112 "Windows. (See the Python documentation for site.USER_BASE "
116 self
.cmd_opts
.add_option(
118 dest
="use_user_site",
119 action
="store_false",
122 self
.cmd_opts
.add_option(
127 help="Install everything relative to this alternate root directory.",
129 self
.cmd_opts
.add_option(
135 "Installation prefix where lib, bin and other top-level "
136 "folders are placed. Note that the resulting installation may "
137 "contain scripts and other resources which reference the "
138 "Python interpreter of pip, and not that of ``--prefix``. "
139 "See also the ``--python`` option if the intention is to "
140 "install packages into another (possibly pip-free) "
145 self
.cmd_opts
.add_option(cmdoptions
.src())
147 self
.cmd_opts
.add_option(
153 "Upgrade all specified packages to the newest available "
154 "version. The handling of dependencies depends on the "
155 "upgrade-strategy used."
159 self
.cmd_opts
.add_option(
160 "--upgrade-strategy",
161 dest
="upgrade_strategy",
162 default
="only-if-needed",
163 choices
=["only-if-needed", "eager"],
165 "Determines how dependency upgrading should be handled "
166 "[default: %default]. "
167 '"eager" - dependencies are upgraded regardless of '
168 "whether the currently installed version satisfies the "
169 "requirements of the upgraded package(s). "
170 '"only-if-needed" - are upgraded only when they do not '
171 "satisfy the requirements of the upgraded package(s)."
175 self
.cmd_opts
.add_option(
177 dest
="force_reinstall",
179 help="Reinstall all packages even if they are already up-to-date.",
182 self
.cmd_opts
.add_option(
184 "--ignore-installed",
185 dest
="ignore_installed",
188 "Ignore the installed packages, overwriting them. "
189 "This can break your system if the existing package "
190 "is of a different version or was installed "
191 "with a different package manager!"
195 self
.cmd_opts
.add_option(cmdoptions
.ignore_requires_python())
196 self
.cmd_opts
.add_option(cmdoptions
.no_build_isolation())
197 self
.cmd_opts
.add_option(cmdoptions
.use_pep517())
198 self
.cmd_opts
.add_option(cmdoptions
.no_use_pep517())
199 self
.cmd_opts
.add_option(cmdoptions
.check_build_deps())
200 self
.cmd_opts
.add_option(cmdoptions
.override_externally_managed())
202 self
.cmd_opts
.add_option(cmdoptions
.config_settings())
203 self
.cmd_opts
.add_option(cmdoptions
.global_options())
205 self
.cmd_opts
.add_option(
210 help="Compile Python source files to bytecode",
213 self
.cmd_opts
.add_option(
215 action
="store_false",
217 help="Do not compile Python source files to bytecode",
220 self
.cmd_opts
.add_option(
221 "--no-warn-script-location",
222 action
="store_false",
223 dest
="warn_script_location",
225 help="Do not warn when installing scripts outside PATH",
227 self
.cmd_opts
.add_option(
228 "--no-warn-conflicts",
229 action
="store_false",
230 dest
="warn_about_conflicts",
232 help="Do not warn about broken dependencies",
234 self
.cmd_opts
.add_option(cmdoptions
.no_binary())
235 self
.cmd_opts
.add_option(cmdoptions
.only_binary())
236 self
.cmd_opts
.add_option(cmdoptions
.prefer_binary())
237 self
.cmd_opts
.add_option(cmdoptions
.require_hashes())
238 self
.cmd_opts
.add_option(cmdoptions
.progress_bar())
239 self
.cmd_opts
.add_option(cmdoptions
.root_user_action())
241 index_opts
= cmdoptions
.make_option_group(
242 cmdoptions
.index_group
,
246 self
.parser
.insert_option_group(0, index_opts
)
247 self
.parser
.insert_option_group(0, self
.cmd_opts
)
249 self
.cmd_opts
.add_option(
251 dest
="json_report_file",
255 "Generate a JSON file describing what pip did to install "
256 "the provided requirements. "
257 "Can be used in combination with --dry-run and --ignore-installed "
258 "to 'resolve' the requirements. "
259 "When - is used as file name it writes to stdout. "
260 "When writing to stdout, please combine with the --quiet option "
261 "to avoid mixing pip logging output with JSON output."
266 def run(self
, options
: Values
, args
: List
[str]) -> int:
267 if options
.use_user_site
and options
.target_dir
is not None:
268 raise CommandError("Can not combine '--user' and '--target'")
270 # Check whether the environment we're installing into is externally
271 # managed, as specified in PEP 668. Specifying --root, --target, or
272 # --prefix disables the check, since there's no reliable way to locate
273 # the EXTERNALLY-MANAGED file for those cases. An exception is also
274 # made specifically for "--dry-run --report" for convenience.
275 installing_into_current_environment
= (
276 not (options
.dry_run
and options
.json_report_file
)
277 and options
.root_path
is None
278 and options
.target_dir
is None
279 and options
.prefix_path
is None
282 installing_into_current_environment
283 and not options
.override_externally_managed
285 check_externally_managed()
287 upgrade_strategy
= "to-satisfy-only"
289 upgrade_strategy
= options
.upgrade_strategy
291 cmdoptions
.check_dist_restriction(options
, check_target
=True)
293 logger
.verbose("Using %s", get_pip_version())
294 options
.use_user_site
= decide_user_install(
295 options
.use_user_site
,
296 prefix_path
=options
.prefix_path
,
297 target_dir
=options
.target_dir
,
298 root_path
=options
.root_path
,
299 isolated_mode
=options
.isolated_mode
,
302 target_temp_dir
: Optional
[TempDirectory
] = None
303 target_temp_dir_path
: Optional
[str] = None
304 if options
.target_dir
:
305 options
.ignore_installed
= True
306 options
.target_dir
= os
.path
.abspath(options
.target_dir
)
309 os
.path
.exists(options
.target_dir
) and
310 not os
.path
.isdir(options
.target_dir
)
314 "Target path exists but is not a directory, will not continue."
317 # Create a target directory for using with the target option
318 target_temp_dir
= TempDirectory(kind
="target")
319 target_temp_dir_path
= target_temp_dir
.path
320 self
.enter_context(target_temp_dir
)
322 global_options
= options
.global_options
or []
324 session
= self
.get_default_session(options
)
326 target_python
= make_target_python(options
)
327 finder
= self
._build
_package
_finder
(
330 target_python
=target_python
,
331 ignore_requires_python
=options
.ignore_requires_python
,
333 build_tracker
= self
.enter_context(get_build_tracker())
335 directory
= TempDirectory(
336 delete
=not options
.no_clean
,
338 globally_managed
=True,
342 reqs
= self
.get_requirements(args
, options
, finder
, session
)
343 check_legacy_setup_py_options(options
, reqs
)
345 wheel_cache
= WheelCache(options
.cache_dir
)
347 # Only when installing is it permitted to use PEP 660.
348 # In other circumstances (pip wheel, pip download) we generate
349 # regular (i.e. non editable) metadata and wheels.
351 req
.permit_editable_wheels
= True
353 preparer
= self
.make_requirement_preparer(
354 temp_build_dir
=directory
,
356 build_tracker
=build_tracker
,
359 use_user_site
=options
.use_user_site
,
360 verbosity
=self
.verbosity
,
362 resolver
= self
.make_resolver(
366 wheel_cache
=wheel_cache
,
367 use_user_site
=options
.use_user_site
,
368 ignore_installed
=options
.ignore_installed
,
369 ignore_requires_python
=options
.ignore_requires_python
,
370 force_reinstall
=options
.force_reinstall
,
371 upgrade_strategy
=upgrade_strategy
,
372 use_pep517
=options
.use_pep517
,
375 self
.trace_basic_info(finder
)
377 requirement_set
= resolver
.resolve(
378 reqs
, check_supported_wheels
=not options
.target_dir
381 if options
.json_report_file
:
382 report
= InstallationReport(requirement_set
.requirements_to_install
)
383 if options
.json_report_file
== "-":
384 print_json(data
=report
.to_dict())
386 with open(options
.json_report_file
, "w", encoding
="utf-8") as f
:
387 json
.dump(report
.to_dict(), f
, indent
=2, ensure_ascii
=False)
390 # In non dry-run mode, the legacy versions and specifiers check
391 # will be done as part of conflict detection.
392 requirement_set
.warn_legacy_versions_and_specifiers()
393 would_install_items
= sorted(
394 (r
.metadata
["name"], r
.metadata
["version"])
395 for r
in requirement_set
.requirements_to_install
397 if would_install_items
:
400 " ".join("-".join(item
) for item
in would_install_items
),
405 pip_req
= requirement_set
.get_requirement("pip")
407 modifying_pip
= False
409 # If we're not replacing an already installed pip,
410 # we're not modifying it.
411 modifying_pip
= pip_req
.satisfied_by
is None
412 protect_pip_from_modification_on_windows(modifying_pip
=modifying_pip
)
416 for r
in requirement_set
.requirements
.values()
417 if should_build_for_install_command(r
)
420 _
, build_failures
= build(
422 wheel_cache
=wheel_cache
,
425 global_options
=global_options
,
429 raise InstallationError(
430 "Could not build wheels for {}, which is required to "
431 "install pyproject.toml-based projects".format(
432 ", ".join(r
.name
for r
in build_failures
) # type: ignore
436 to_install
= resolver
.get_installation_order(requirement_set
)
438 # Check for conflicts in the package set we're installing.
439 conflicts
: Optional
[ConflictDetails
] = None
440 should_warn_about_conflicts
= (
441 not options
.ignore_dependencies
and options
.warn_about_conflicts
443 if should_warn_about_conflicts
:
444 conflicts
= self
._determine
_conflicts
(to_install
)
446 # Don't warn about script install locations if
447 # --target or --prefix has been specified
448 warn_script_location
= options
.warn_script_location
449 if options
.target_dir
or options
.prefix_path
:
450 warn_script_location
= False
452 installed
= install_given_reqs(
455 root
=options
.root_path
,
456 home
=target_temp_dir_path
,
457 prefix
=options
.prefix_path
,
458 warn_script_location
=warn_script_location
,
459 use_user_site
=options
.use_user_site
,
460 pycompile
=options
.compile,
463 lib_locations
= get_lib_location_guesses(
464 user
=options
.use_user_site
,
465 home
=target_temp_dir_path
,
466 root
=options
.root_path
,
467 prefix
=options
.prefix_path
,
468 isolated
=options
.isolated_mode
,
470 env
= get_environment(lib_locations
)
472 installed
.sort(key
=operator
.attrgetter("name"))
474 for result
in installed
:
477 installed_dist
= env
.get_distribution(item
)
478 if installed_dist
is not None:
479 item
= f
"{item}-{installed_dist.version}"
484 if conflicts
is not None:
485 self
._warn
_about
_conflicts
(
487 resolver_variant
=self
.determine_resolver_variant(options
),
490 installed_desc
= " ".join(items
)
493 "Successfully installed %s",
496 except OSError as error
:
497 show_traceback
= self
.verbosity
>= 1
499 message
= create_os_error_message(
502 options
.use_user_site
,
504 logger
.error(message
, exc_info
=show_traceback
) # noqa
508 if options
.target_dir
:
509 assert target_temp_dir
510 self
._handle
_target
_dir
(
511 options
.target_dir
, target_temp_dir
, options
.upgrade
513 if options
.root_user_action
== "warn":
514 warn_if_run_as_root()
517 def _handle_target_dir(
518 self
, target_dir
: str, target_temp_dir
: TempDirectory
, upgrade
: bool
520 ensure_dir(target_dir
)
522 # Checking both purelib and platlib directories for installed
523 # packages to be moved to target directory
526 # Checking both purelib and platlib directories for installed
527 # packages to be moved to target directory
528 scheme
= get_scheme("", home
=target_temp_dir
.path
)
529 purelib_dir
= scheme
.purelib
530 platlib_dir
= scheme
.platlib
531 data_dir
= scheme
.data
533 if os
.path
.exists(purelib_dir
):
534 lib_dir_list
.append(purelib_dir
)
535 if os
.path
.exists(platlib_dir
) and platlib_dir
!= purelib_dir
:
536 lib_dir_list
.append(platlib_dir
)
537 if os
.path
.exists(data_dir
):
538 lib_dir_list
.append(data_dir
)
540 for lib_dir
in lib_dir_list
:
541 for item
in os
.listdir(lib_dir
):
542 if lib_dir
== data_dir
:
543 ddir
= os
.path
.join(data_dir
, item
)
544 if any(s
.startswith(ddir
) for s
in lib_dir_list
[:-1]):
546 target_item_dir
= os
.path
.join(target_dir
, item
)
547 if os
.path
.exists(target_item_dir
):
550 "Target directory %s already exists. Specify "
551 "--upgrade to force replacement.",
555 if os
.path
.islink(target_item_dir
):
557 "Target directory %s already exists and is "
558 "a link. pip will not automatically replace "
559 "links, please remove if replacement is "
564 if os
.path
.isdir(target_item_dir
):
565 shutil
.rmtree(target_item_dir
)
567 os
.remove(target_item_dir
)
569 shutil
.move(os
.path
.join(lib_dir
, item
), target_item_dir
)
571 def _determine_conflicts(
572 self
, to_install
: List
[InstallRequirement
]
573 ) -> Optional
[ConflictDetails
]:
575 return check_install_conflicts(to_install
)
578 "Error while checking for conflicts. Please file an issue on "
579 "pip's issue tracker: https://github.com/pypa/pip/issues/new"
583 def _warn_about_conflicts(
584 self
, conflict_details
: ConflictDetails
, resolver_variant
: str
586 package_set
, (missing
, conflicting
) = conflict_details
587 if not missing
and not conflicting
:
590 parts
: List
[str] = []
591 if resolver_variant
== "legacy":
593 "pip's legacy dependency resolver does not consider dependency "
594 "conflicts when selecting packages. This behaviour is the "
595 "source of the following dependency conflicts."
598 assert resolver_variant
== "2020-resolver"
600 "pip's dependency resolver does not currently take into account "
601 "all the packages that are installed. This behaviour is the "
602 "source of the following dependency conflicts."
605 # NOTE: There is some duplication here, with commands/check.py
606 for project_name
in missing
:
607 version
= package_set
[project_name
][0]
608 for dependency
in missing
[project_name
]:
610 "{name} {version} requires {requirement}, "
611 "which is not installed."
615 requirement
=dependency
[1],
617 parts
.append(message
)
619 for project_name
in conflicting
:
620 version
= package_set
[project_name
][0]
621 for dep_name
, dep_version
, req
in conflicting
[project_name
]:
623 "{name} {version} requires {requirement}, but {you} have "
624 "{dep_name} {dep_version} which is incompatible."
630 dep_version
=dep_version
,
631 you
=("you" if resolver_variant
== "2020-resolver" else "you'll"),
633 parts
.append(message
)
635 logger
.critical("\n".join(parts
))
638 def get_lib_location_guesses(
640 home
: Optional
[str] = None,
641 root
: Optional
[str] = None,
642 isolated
: bool = False,
643 prefix
: Optional
[str] = None,
653 return [scheme
.purelib
, scheme
.platlib
]
656 def site_packages_writable(root
: Optional
[str], isolated
: bool) -> bool:
659 for d
in set(get_lib_location_guesses(root
=root
, isolated
=isolated
))
663 def decide_user_install(
664 use_user_site
: Optional
[bool],
665 prefix_path
: Optional
[str] = None,
666 target_dir
: Optional
[str] = None,
667 root_path
: Optional
[str] = None,
668 isolated_mode
: bool = False,
670 """Determine whether to do a user install based on the input options.
672 If use_user_site is False, no additional checks are done.
673 If use_user_site is True, it is checked for compatibility with other
675 If use_user_site is None, the default behaviour depends on the environment,
676 which is provided by the other arguments.
678 # In some cases (config from tox), use_user_site can be set to an integer
679 # rather than a bool, which 'use_user_site is False' wouldn't catch.
680 if (use_user_site
is not None) and (not use_user_site
):
681 logger
.debug("Non-user install by explicit request")
687 "Can not combine '--user' and '--prefix' as they imply "
688 "different installation locations"
690 if virtualenv_no_global():
691 raise InstallationError(
692 "Can not perform a '--user' install. User site-packages "
693 "are not visible in this virtualenv."
695 logger
.debug("User install by explicit request")
698 # If we are here, user installs have not been explicitly requested/avoided
699 assert use_user_site
is None
701 # user install incompatible with --prefix/--target
702 if prefix_path
or target_dir
:
703 logger
.debug("Non-user install due to --prefix or --target option")
706 # If user installs are not enabled, choose a non-user install
707 if not site
.ENABLE_USER_SITE
:
708 logger
.debug("Non-user install because user site-packages disabled")
711 # If we have permission for a non-user install, do that,
712 # otherwise do a user install.
713 if site_packages_writable(root
=root_path
, isolated
=isolated_mode
):
714 logger
.debug("Non-user install because site-packages writeable")
718 "Defaulting to user installation because normal site-packages "
724 def create_os_error_message(
725 error
: OSError, show_traceback
: bool, using_user_site
: bool
727 """Format an error message for an OSError
729 It may occur anytime during the execution of the install command.
733 # Mention the error if we are not going to show a traceback
734 parts
.append("Could not install packages due to an OSError")
735 if not show_traceback
:
737 parts
.append(str(error
))
741 # Spilt the error indication from a helper message (if any)
744 # Suggest useful actions to the user:
745 # (1) using user site-packages or (2) verifying the permissions
746 if error
.errno
== errno
.EACCES
:
747 user_option_part
= "Consider using the `--user` option"
748 permissions_part
= "Check the permissions"
750 if not running_under_virtualenv() and not using_user_site
:
755 permissions_part
.lower(),
759 parts
.append(permissions_part
)
762 # Suggest the user to enable Long Paths if path length is
766 and error
.errno
== errno
.ENOENT
768 and len(error
.filename
) > 260
771 "HINT: This error might have occurred since "
772 "this system does not have Windows Long Path "
773 "support enabled. You can find information on "
774 "how to enable this at "
775 "https://pip.pypa.io/warnings/enable-long-paths\n"
778 return "".join(parts
).strip() + "\n"