python (3.11.7)

(root)/
lib/
python3.11/
site-packages/
pip/
_internal/
commands/
install.py
       1  import errno
       2  import json
       3  import operator
       4  import os
       5  import shutil
       6  import site
       7  from optparse import SUPPRESS_HELP, Values
       8  from typing import List, Optional
       9  
      10  from pip._vendor.rich import print_json
      11  
      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 (
      16      RequirementCommand,
      17      warn_if_run_as_root,
      18      with_cleanup,
      19  )
      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 (
      29      InstallRequirement,
      30      check_legacy_setup_py_options,
      31  )
      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,
      37      ensure_dir,
      38      get_pip_version,
      39      protect_pip_from_modification_on_windows,
      40      write_output,
      41  )
      42  from pip._internal.utils.temp_dir import TempDirectory
      43  from pip._internal.utils.virtualenv import (
      44      running_under_virtualenv,
      45      virtualenv_no_global,
      46  )
      47  from pip._internal.wheel_builder import build, should_build_for_install_command
      48  
      49  logger = getLogger(__name__)
      50  
      51  
      52  class ESC[4;38;5;81mInstallCommand(ESC[4;38;5;149mRequirementCommand):
      53      """
      54      Install packages from:
      55  
      56      - PyPI (and other indexes) using requirement specifiers.
      57      - VCS project urls.
      58      - Local project directories.
      59      - Local or remote source archives.
      60  
      61      pip also supports installing from "requirements files", which provide
      62      an easy way to specify a whole environment to be installed.
      63      """
      64  
      65      usage = """
      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> ..."""
      71  
      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())
      77  
      78          self.cmd_opts.add_option(cmdoptions.editable())
      79          self.cmd_opts.add_option(
      80              "--dry-run",
      81              action="store_true",
      82              dest="dry_run",
      83              default=False,
      84              help=(
      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."
      88              ),
      89          )
      90          self.cmd_opts.add_option(
      91              "-t",
      92              "--target",
      93              dest="target_dir",
      94              metavar="dir",
      95              default=None,
      96              help=(
      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> "
     100                  "with new versions."
     101              ),
     102          )
     103          cmdoptions.add_target_python_options(self.cmd_opts)
     104  
     105          self.cmd_opts.add_option(
     106              "--user",
     107              dest="use_user_site",
     108              action="store_true",
     109              help=(
     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 "
     113                  "for full details.)"
     114              ),
     115          )
     116          self.cmd_opts.add_option(
     117              "--no-user",
     118              dest="use_user_site",
     119              action="store_false",
     120              help=SUPPRESS_HELP,
     121          )
     122          self.cmd_opts.add_option(
     123              "--root",
     124              dest="root_path",
     125              metavar="dir",
     126              default=None,
     127              help="Install everything relative to this alternate root directory.",
     128          )
     129          self.cmd_opts.add_option(
     130              "--prefix",
     131              dest="prefix_path",
     132              metavar="dir",
     133              default=None,
     134              help=(
     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) "
     141                  "environment."
     142              ),
     143          )
     144  
     145          self.cmd_opts.add_option(cmdoptions.src())
     146  
     147          self.cmd_opts.add_option(
     148              "-U",
     149              "--upgrade",
     150              dest="upgrade",
     151              action="store_true",
     152              help=(
     153                  "Upgrade all specified packages to the newest available "
     154                  "version. The handling of dependencies depends on the "
     155                  "upgrade-strategy used."
     156              ),
     157          )
     158  
     159          self.cmd_opts.add_option(
     160              "--upgrade-strategy",
     161              dest="upgrade_strategy",
     162              default="only-if-needed",
     163              choices=["only-if-needed", "eager"],
     164              help=(
     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)."
     172              ),
     173          )
     174  
     175          self.cmd_opts.add_option(
     176              "--force-reinstall",
     177              dest="force_reinstall",
     178              action="store_true",
     179              help="Reinstall all packages even if they are already up-to-date.",
     180          )
     181  
     182          self.cmd_opts.add_option(
     183              "-I",
     184              "--ignore-installed",
     185              dest="ignore_installed",
     186              action="store_true",
     187              help=(
     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!"
     192              ),
     193          )
     194  
     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())
     201  
     202          self.cmd_opts.add_option(cmdoptions.config_settings())
     203          self.cmd_opts.add_option(cmdoptions.global_options())
     204  
     205          self.cmd_opts.add_option(
     206              "--compile",
     207              action="store_true",
     208              dest="compile",
     209              default=True,
     210              help="Compile Python source files to bytecode",
     211          )
     212  
     213          self.cmd_opts.add_option(
     214              "--no-compile",
     215              action="store_false",
     216              dest="compile",
     217              help="Do not compile Python source files to bytecode",
     218          )
     219  
     220          self.cmd_opts.add_option(
     221              "--no-warn-script-location",
     222              action="store_false",
     223              dest="warn_script_location",
     224              default=True,
     225              help="Do not warn when installing scripts outside PATH",
     226          )
     227          self.cmd_opts.add_option(
     228              "--no-warn-conflicts",
     229              action="store_false",
     230              dest="warn_about_conflicts",
     231              default=True,
     232              help="Do not warn about broken dependencies",
     233          )
     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())
     240  
     241          index_opts = cmdoptions.make_option_group(
     242              cmdoptions.index_group,
     243              self.parser,
     244          )
     245  
     246          self.parser.insert_option_group(0, index_opts)
     247          self.parser.insert_option_group(0, self.cmd_opts)
     248  
     249          self.cmd_opts.add_option(
     250              "--report",
     251              dest="json_report_file",
     252              metavar="file",
     253              default=None,
     254              help=(
     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."
     262              ),
     263          )
     264  
     265      @with_cleanup
     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'")
     269  
     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
     280          )
     281          if (
     282              installing_into_current_environment
     283              and not options.override_externally_managed
     284          ):
     285              check_externally_managed()
     286  
     287          upgrade_strategy = "to-satisfy-only"
     288          if options.upgrade:
     289              upgrade_strategy = options.upgrade_strategy
     290  
     291          cmdoptions.check_dist_restriction(options, check_target=True)
     292  
     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,
     300          )
     301  
     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)
     307              if (
     308                  # fmt: off
     309                  os.path.exists(options.target_dir) and
     310                  not os.path.isdir(options.target_dir)
     311                  # fmt: on
     312              ):
     313                  raise CommandError(
     314                      "Target path exists but is not a directory, will not continue."
     315                  )
     316  
     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)
     321  
     322          global_options = options.global_options or []
     323  
     324          session = self.get_default_session(options)
     325  
     326          target_python = make_target_python(options)
     327          finder = self._build_package_finder(
     328              options=options,
     329              session=session,
     330              target_python=target_python,
     331              ignore_requires_python=options.ignore_requires_python,
     332          )
     333          build_tracker = self.enter_context(get_build_tracker())
     334  
     335          directory = TempDirectory(
     336              delete=not options.no_clean,
     337              kind="install",
     338              globally_managed=True,
     339          )
     340  
     341          try:
     342              reqs = self.get_requirements(args, options, finder, session)
     343              check_legacy_setup_py_options(options, reqs)
     344  
     345              wheel_cache = WheelCache(options.cache_dir)
     346  
     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.
     350              for req in reqs:
     351                  req.permit_editable_wheels = True
     352  
     353              preparer = self.make_requirement_preparer(
     354                  temp_build_dir=directory,
     355                  options=options,
     356                  build_tracker=build_tracker,
     357                  session=session,
     358                  finder=finder,
     359                  use_user_site=options.use_user_site,
     360                  verbosity=self.verbosity,
     361              )
     362              resolver = self.make_resolver(
     363                  preparer=preparer,
     364                  finder=finder,
     365                  options=options,
     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,
     373              )
     374  
     375              self.trace_basic_info(finder)
     376  
     377              requirement_set = resolver.resolve(
     378                  reqs, check_supported_wheels=not options.target_dir
     379              )
     380  
     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())
     385                  else:
     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)
     388  
     389              if options.dry_run:
     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
     396                  )
     397                  if would_install_items:
     398                      write_output(
     399                          "Would install %s",
     400                          " ".join("-".join(item) for item in would_install_items),
     401                      )
     402                  return SUCCESS
     403  
     404              try:
     405                  pip_req = requirement_set.get_requirement("pip")
     406              except KeyError:
     407                  modifying_pip = False
     408              else:
     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)
     413  
     414              reqs_to_build = [
     415                  r
     416                  for r in requirement_set.requirements.values()
     417                  if should_build_for_install_command(r)
     418              ]
     419  
     420              _, build_failures = build(
     421                  reqs_to_build,
     422                  wheel_cache=wheel_cache,
     423                  verify=True,
     424                  build_options=[],
     425                  global_options=global_options,
     426              )
     427  
     428              if build_failures:
     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
     433                      )
     434                  )
     435  
     436              to_install = resolver.get_installation_order(requirement_set)
     437  
     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
     442              )
     443              if should_warn_about_conflicts:
     444                  conflicts = self._determine_conflicts(to_install)
     445  
     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
     451  
     452              installed = install_given_reqs(
     453                  to_install,
     454                  global_options,
     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,
     461              )
     462  
     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,
     469              )
     470              env = get_environment(lib_locations)
     471  
     472              installed.sort(key=operator.attrgetter("name"))
     473              items = []
     474              for result in installed:
     475                  item = result.name
     476                  try:
     477                      installed_dist = env.get_distribution(item)
     478                      if installed_dist is not None:
     479                          item = f"{item}-{installed_dist.version}"
     480                  except Exception:
     481                      pass
     482                  items.append(item)
     483  
     484              if conflicts is not None:
     485                  self._warn_about_conflicts(
     486                      conflicts,
     487                      resolver_variant=self.determine_resolver_variant(options),
     488                  )
     489  
     490              installed_desc = " ".join(items)
     491              if installed_desc:
     492                  write_output(
     493                      "Successfully installed %s",
     494                      installed_desc,
     495                  )
     496          except OSError as error:
     497              show_traceback = self.verbosity >= 1
     498  
     499              message = create_os_error_message(
     500                  error,
     501                  show_traceback,
     502                  options.use_user_site,
     503              )
     504              logger.error(message, exc_info=show_traceback)  # noqa
     505  
     506              return ERROR
     507  
     508          if options.target_dir:
     509              assert target_temp_dir
     510              self._handle_target_dir(
     511                  options.target_dir, target_temp_dir, options.upgrade
     512              )
     513          if options.root_user_action == "warn":
     514              warn_if_run_as_root()
     515          return SUCCESS
     516  
     517      def _handle_target_dir(
     518          self, target_dir: str, target_temp_dir: TempDirectory, upgrade: bool
     519      ) -> None:
     520          ensure_dir(target_dir)
     521  
     522          # Checking both purelib and platlib directories for installed
     523          # packages to be moved to target directory
     524          lib_dir_list = []
     525  
     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
     532  
     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)
     539  
     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]):
     545                          continue
     546                  target_item_dir = os.path.join(target_dir, item)
     547                  if os.path.exists(target_item_dir):
     548                      if not upgrade:
     549                          logger.warning(
     550                              "Target directory %s already exists. Specify "
     551                              "--upgrade to force replacement.",
     552                              target_item_dir,
     553                          )
     554                          continue
     555                      if os.path.islink(target_item_dir):
     556                          logger.warning(
     557                              "Target directory %s already exists and is "
     558                              "a link. pip will not automatically replace "
     559                              "links, please remove if replacement is "
     560                              "desired.",
     561                              target_item_dir,
     562                          )
     563                          continue
     564                      if os.path.isdir(target_item_dir):
     565                          shutil.rmtree(target_item_dir)
     566                      else:
     567                          os.remove(target_item_dir)
     568  
     569                  shutil.move(os.path.join(lib_dir, item), target_item_dir)
     570  
     571      def _determine_conflicts(
     572          self, to_install: List[InstallRequirement]
     573      ) -> Optional[ConflictDetails]:
     574          try:
     575              return check_install_conflicts(to_install)
     576          except Exception:
     577              logger.exception(
     578                  "Error while checking for conflicts. Please file an issue on "
     579                  "pip's issue tracker: https://github.com/pypa/pip/issues/new"
     580              )
     581              return None
     582  
     583      def _warn_about_conflicts(
     584          self, conflict_details: ConflictDetails, resolver_variant: str
     585      ) -> None:
     586          package_set, (missing, conflicting) = conflict_details
     587          if not missing and not conflicting:
     588              return
     589  
     590          parts: List[str] = []
     591          if resolver_variant == "legacy":
     592              parts.append(
     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."
     596              )
     597          else:
     598              assert resolver_variant == "2020-resolver"
     599              parts.append(
     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."
     603              )
     604  
     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]:
     609                  message = (
     610                      "{name} {version} requires {requirement}, "
     611                      "which is not installed."
     612                  ).format(
     613                      name=project_name,
     614                      version=version,
     615                      requirement=dependency[1],
     616                  )
     617                  parts.append(message)
     618  
     619          for project_name in conflicting:
     620              version = package_set[project_name][0]
     621              for dep_name, dep_version, req in conflicting[project_name]:
     622                  message = (
     623                      "{name} {version} requires {requirement}, but {you} have "
     624                      "{dep_name} {dep_version} which is incompatible."
     625                  ).format(
     626                      name=project_name,
     627                      version=version,
     628                      requirement=req,
     629                      dep_name=dep_name,
     630                      dep_version=dep_version,
     631                      you=("you" if resolver_variant == "2020-resolver" else "you'll"),
     632                  )
     633                  parts.append(message)
     634  
     635          logger.critical("\n".join(parts))
     636  
     637  
     638  def get_lib_location_guesses(
     639      user: bool = False,
     640      home: Optional[str] = None,
     641      root: Optional[str] = None,
     642      isolated: bool = False,
     643      prefix: Optional[str] = None,
     644  ) -> List[str]:
     645      scheme = get_scheme(
     646          "",
     647          user=user,
     648          home=home,
     649          root=root,
     650          isolated=isolated,
     651          prefix=prefix,
     652      )
     653      return [scheme.purelib, scheme.platlib]
     654  
     655  
     656  def site_packages_writable(root: Optional[str], isolated: bool) -> bool:
     657      return all(
     658          test_writable_dir(d)
     659          for d in set(get_lib_location_guesses(root=root, isolated=isolated))
     660      )
     661  
     662  
     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,
     669  ) -> bool:
     670      """Determine whether to do a user install based on the input options.
     671  
     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
     674      options.
     675      If use_user_site is None, the default behaviour depends on the environment,
     676      which is provided by the other arguments.
     677      """
     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")
     682          return False
     683  
     684      if use_user_site:
     685          if prefix_path:
     686              raise CommandError(
     687                  "Can not combine '--user' and '--prefix' as they imply "
     688                  "different installation locations"
     689              )
     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."
     694              )
     695          logger.debug("User install by explicit request")
     696          return True
     697  
     698      # If we are here, user installs have not been explicitly requested/avoided
     699      assert use_user_site is None
     700  
     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")
     704          return False
     705  
     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")
     709          return False
     710  
     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")
     715          return False
     716  
     717      logger.info(
     718          "Defaulting to user installation because normal site-packages "
     719          "is not writeable"
     720      )
     721      return True
     722  
     723  
     724  def create_os_error_message(
     725      error: OSError, show_traceback: bool, using_user_site: bool
     726  ) -> str:
     727      """Format an error message for an OSError
     728  
     729      It may occur anytime during the execution of the install command.
     730      """
     731      parts = []
     732  
     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:
     736          parts.append(": ")
     737          parts.append(str(error))
     738      else:
     739          parts.append(".")
     740  
     741      # Spilt the error indication from a helper message (if any)
     742      parts[-1] += "\n"
     743  
     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"
     749  
     750          if not running_under_virtualenv() and not using_user_site:
     751              parts.extend(
     752                  [
     753                      user_option_part,
     754                      " or ",
     755                      permissions_part.lower(),
     756                  ]
     757              )
     758          else:
     759              parts.append(permissions_part)
     760          parts.append(".\n")
     761  
     762      # Suggest the user to enable Long Paths if path length is
     763      # more than 260
     764      if (
     765          WINDOWS
     766          and error.errno == errno.ENOENT
     767          and error.filename
     768          and len(error.filename) > 260
     769      ):
     770          parts.append(
     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"
     776          )
     777  
     778      return "".join(parts).strip() + "\n"