install.py 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810
  1. from __future__ import annotations
  2. import errno
  3. import json
  4. import operator
  5. import os
  6. import shutil
  7. import site
  8. from optparse import SUPPRESS_HELP, Values
  9. from pathlib import Path
  10. from pip._vendor.packaging.utils import canonicalize_name
  11. from pip._vendor.requests.exceptions import InvalidProxyURL
  12. from pip._vendor.rich import print_json
  13. # Eagerly import self_outdated_check to avoid crashes. Otherwise,
  14. # this module would be imported *after* pip was replaced, resulting
  15. # in crashes if the new self_outdated_check module was incompatible
  16. # with the rest of pip that's already imported, or allowing a
  17. # wheel to execute arbitrary code on install by replacing
  18. # self_outdated_check.
  19. import pip._internal.self_outdated_check # noqa: F401
  20. from pip._internal.cache import WheelCache
  21. from pip._internal.cli import cmdoptions
  22. from pip._internal.cli.cmdoptions import make_target_python
  23. from pip._internal.cli.req_command import (
  24. RequirementCommand,
  25. with_cleanup,
  26. )
  27. from pip._internal.cli.status_codes import ERROR, SUCCESS
  28. from pip._internal.exceptions import (
  29. CommandError,
  30. InstallationError,
  31. InstallWheelBuildError,
  32. )
  33. from pip._internal.locations import get_scheme
  34. from pip._internal.metadata import BaseEnvironment, get_environment
  35. from pip._internal.models.installation_report import InstallationReport
  36. from pip._internal.operations.build.build_tracker import get_build_tracker
  37. from pip._internal.operations.check import ConflictDetails, check_install_conflicts
  38. from pip._internal.req import InstallationResult, install_given_reqs
  39. from pip._internal.req.req_install import (
  40. InstallRequirement,
  41. )
  42. from pip._internal.utils.compat import WINDOWS
  43. from pip._internal.utils.filesystem import test_writable_dir
  44. from pip._internal.utils.logging import getLogger
  45. from pip._internal.utils.misc import (
  46. check_externally_managed,
  47. ensure_dir,
  48. get_pip_version,
  49. protect_pip_from_modification_on_windows,
  50. warn_if_run_as_root,
  51. write_output,
  52. )
  53. from pip._internal.utils.temp_dir import TempDirectory
  54. from pip._internal.utils.virtualenv import (
  55. running_under_virtualenv,
  56. virtualenv_no_global,
  57. )
  58. from pip._internal.wheel_builder import build
  59. logger = getLogger(__name__)
  60. class InstallCommand(RequirementCommand):
  61. """
  62. Install packages from:
  63. - PyPI (and other indexes) using requirement specifiers.
  64. - VCS project urls.
  65. - Local project directories.
  66. - Local or remote source archives.
  67. pip also supports installing from "requirements files", which provide
  68. an easy way to specify a whole environment to be installed.
  69. """
  70. usage = """
  71. %prog [options] <requirement specifier> [package-index-options] ...
  72. %prog [options] -r <requirements file> [package-index-options] ...
  73. %prog [options] [-e] <vcs project url> ...
  74. %prog [options] [-e] <local project path> ...
  75. %prog [options] <archive url/path> ..."""
  76. def add_options(self) -> None:
  77. self.cmd_opts.add_option(cmdoptions.requirements())
  78. self.cmd_opts.add_option(cmdoptions.constraints())
  79. self.cmd_opts.add_option(cmdoptions.build_constraints())
  80. self.cmd_opts.add_option(cmdoptions.requirements_from_scripts())
  81. self.cmd_opts.add_option(cmdoptions.no_deps())
  82. self.cmd_opts.add_option(cmdoptions.editable())
  83. self.cmd_opts.add_option(
  84. "--dry-run",
  85. action="store_true",
  86. dest="dry_run",
  87. default=False,
  88. help=(
  89. "Don't actually install anything, just print what would be. "
  90. "Can be used in combination with --ignore-installed "
  91. "to 'resolve' the requirements."
  92. ),
  93. )
  94. self.cmd_opts.add_option(
  95. "-t",
  96. "--target",
  97. dest="target_dir",
  98. metavar="dir",
  99. default=None,
  100. help=(
  101. "Install packages into <dir>. "
  102. "By default this will not replace existing files/folders in "
  103. "<dir>. Use --upgrade to replace existing packages in <dir> "
  104. "with new versions."
  105. ),
  106. )
  107. cmdoptions.add_target_python_options(self.cmd_opts)
  108. self.cmd_opts.add_option(
  109. "--user",
  110. dest="use_user_site",
  111. action="store_true",
  112. help=(
  113. "Install to the Python user install directory for your "
  114. "platform. Typically ~/.local/, or %APPDATA%\\Python on "
  115. "Windows. (See the Python documentation for site.USER_BASE "
  116. "for full details.)"
  117. ),
  118. )
  119. self.cmd_opts.add_option(
  120. "--no-user",
  121. dest="use_user_site",
  122. action="store_false",
  123. help=SUPPRESS_HELP,
  124. )
  125. self.cmd_opts.add_option(
  126. "--root",
  127. dest="root_path",
  128. metavar="dir",
  129. default=None,
  130. help="Install everything relative to this alternate root directory.",
  131. )
  132. self.cmd_opts.add_option(
  133. "--prefix",
  134. dest="prefix_path",
  135. metavar="dir",
  136. default=None,
  137. help=(
  138. "Installation prefix where lib, bin and other top-level "
  139. "folders are placed. Note that the resulting installation may "
  140. "contain scripts and other resources which reference the "
  141. "Python interpreter of pip, and not that of ``--prefix``. "
  142. "See also the ``--python`` option if the intention is to "
  143. "install packages into another (possibly pip-free) "
  144. "environment."
  145. ),
  146. )
  147. self.cmd_opts.add_option(cmdoptions.src())
  148. self.cmd_opts.add_option(
  149. "-U",
  150. "--upgrade",
  151. dest="upgrade",
  152. action="store_true",
  153. help=(
  154. "Upgrade all specified packages to the newest available "
  155. "version. The handling of dependencies depends on the "
  156. "upgrade-strategy used."
  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. self.cmd_opts.add_option(
  175. "--force-reinstall",
  176. dest="force_reinstall",
  177. action="store_true",
  178. help="Reinstall all packages even if they are already up-to-date.",
  179. )
  180. self.cmd_opts.add_option(
  181. "-I",
  182. "--ignore-installed",
  183. dest="ignore_installed",
  184. action="store_true",
  185. help=(
  186. "Ignore the installed packages, overwriting them. "
  187. "This can break your system if the existing package "
  188. "is of a different version or was installed "
  189. "with a different package manager!"
  190. ),
  191. )
  192. self.cmd_opts.add_option(cmdoptions.ignore_requires_python())
  193. self.cmd_opts.add_option(cmdoptions.no_build_isolation())
  194. self.cmd_opts.add_option(cmdoptions.use_pep517())
  195. self.cmd_opts.add_option(cmdoptions.check_build_deps())
  196. self.cmd_opts.add_option(cmdoptions.override_externally_managed())
  197. self.cmd_opts.add_option(cmdoptions.config_settings())
  198. self.cmd_opts.add_option(
  199. "--compile",
  200. action="store_true",
  201. dest="compile",
  202. default=True,
  203. help="Compile Python source files to bytecode",
  204. )
  205. self.cmd_opts.add_option(
  206. "--no-compile",
  207. action="store_false",
  208. dest="compile",
  209. help="Do not compile Python source files to bytecode",
  210. )
  211. self.cmd_opts.add_option(
  212. "--no-warn-script-location",
  213. action="store_false",
  214. dest="warn_script_location",
  215. default=True,
  216. help="Do not warn when installing scripts outside PATH",
  217. )
  218. self.cmd_opts.add_option(
  219. "--no-warn-conflicts",
  220. action="store_false",
  221. dest="warn_about_conflicts",
  222. default=True,
  223. help="Do not warn about broken dependencies",
  224. )
  225. self.cmd_opts.add_option(cmdoptions.require_hashes())
  226. self.cmd_opts.add_option(cmdoptions.progress_bar())
  227. self.cmd_opts.add_option(cmdoptions.root_user_action())
  228. index_opts = cmdoptions.make_option_group(
  229. cmdoptions.index_group,
  230. self.parser,
  231. )
  232. selection_opts = cmdoptions.make_option_group(
  233. cmdoptions.package_selection_group,
  234. self.parser,
  235. )
  236. self.parser.insert_option_group(0, index_opts)
  237. self.parser.insert_option_group(0, selection_opts)
  238. self.parser.insert_option_group(0, self.cmd_opts)
  239. self.cmd_opts.add_option(
  240. "--report",
  241. dest="json_report_file",
  242. metavar="file",
  243. default=None,
  244. help=(
  245. "Generate a JSON file describing what pip did to install "
  246. "the provided requirements. "
  247. "Can be used in combination with --dry-run and --ignore-installed "
  248. "to 'resolve' the requirements. "
  249. "When - is used as file name it writes to stdout. "
  250. "When writing to stdout, please combine with the --quiet option "
  251. "to avoid mixing pip logging output with JSON output."
  252. ),
  253. )
  254. @with_cleanup
  255. def run(self, options: Values, args: list[str]) -> int:
  256. if options.use_user_site and options.target_dir is not None:
  257. raise CommandError("Can not combine '--user' and '--target'")
  258. # Check whether the environment we're installing into is externally
  259. # managed, as specified in PEP 668. Specifying --root, --target, or
  260. # --prefix disables the check, since there's no reliable way to locate
  261. # the EXTERNALLY-MANAGED file for those cases. An exception is also
  262. # made specifically for "--dry-run --report" for convenience.
  263. installing_into_current_environment = (
  264. not (options.dry_run and options.json_report_file)
  265. and options.root_path is None
  266. and options.target_dir is None
  267. and options.prefix_path is None
  268. )
  269. if (
  270. installing_into_current_environment
  271. and not options.override_externally_managed
  272. ):
  273. check_externally_managed()
  274. upgrade_strategy = "to-satisfy-only"
  275. if options.upgrade:
  276. upgrade_strategy = options.upgrade_strategy
  277. cmdoptions.check_build_constraints(options)
  278. cmdoptions.check_dist_restriction(options, check_target=True)
  279. cmdoptions.check_release_control_exclusive(options)
  280. logger.verbose("Using %s", get_pip_version())
  281. options.use_user_site = decide_user_install(
  282. options.use_user_site,
  283. prefix_path=options.prefix_path,
  284. target_dir=options.target_dir,
  285. root_path=options.root_path,
  286. isolated_mode=options.isolated_mode,
  287. )
  288. target_temp_dir: TempDirectory | None = None
  289. target_temp_dir_path: str | None = None
  290. if options.target_dir:
  291. options.ignore_installed = True
  292. options.target_dir = os.path.abspath(options.target_dir)
  293. if (
  294. # fmt: off
  295. os.path.exists(options.target_dir) and
  296. not os.path.isdir(options.target_dir)
  297. # fmt: on
  298. ):
  299. raise CommandError(
  300. "Target path exists but is not a directory, will not continue."
  301. )
  302. # Create a target directory for using with the target option
  303. target_temp_dir = TempDirectory(kind="target")
  304. target_temp_dir_path = target_temp_dir.path
  305. self.enter_context(target_temp_dir)
  306. session = self.get_default_session(options)
  307. target_python = make_target_python(options)
  308. finder = self._build_package_finder(
  309. options=options,
  310. session=session,
  311. target_python=target_python,
  312. ignore_requires_python=options.ignore_requires_python,
  313. )
  314. build_tracker = self.enter_context(get_build_tracker())
  315. directory = TempDirectory(
  316. delete=not options.no_clean,
  317. kind="install",
  318. globally_managed=True,
  319. )
  320. try:
  321. reqs = self.get_requirements(args, options, finder, session)
  322. wheel_cache = WheelCache(options.cache_dir)
  323. # Only when installing is it permitted to use PEP 660.
  324. # In other circumstances (pip wheel, pip download) we generate
  325. # regular (i.e. non editable) metadata and wheels.
  326. for req in reqs:
  327. req.permit_editable_wheels = True
  328. preparer = self.make_requirement_preparer(
  329. temp_build_dir=directory,
  330. options=options,
  331. build_tracker=build_tracker,
  332. session=session,
  333. finder=finder,
  334. use_user_site=options.use_user_site,
  335. verbosity=self.verbosity,
  336. )
  337. resolver = self.make_resolver(
  338. preparer=preparer,
  339. finder=finder,
  340. options=options,
  341. wheel_cache=wheel_cache,
  342. use_user_site=options.use_user_site,
  343. ignore_installed=options.ignore_installed,
  344. ignore_requires_python=options.ignore_requires_python,
  345. force_reinstall=options.force_reinstall,
  346. upgrade_strategy=upgrade_strategy,
  347. py_version_info=options.python_version,
  348. )
  349. self.trace_basic_info(finder)
  350. requirement_set = resolver.resolve(
  351. reqs, check_supported_wheels=not options.target_dir
  352. )
  353. if options.json_report_file:
  354. report = InstallationReport(requirement_set.requirements_to_install)
  355. if options.json_report_file == "-":
  356. print_json(data=report.to_dict())
  357. else:
  358. with open(options.json_report_file, "w", encoding="utf-8") as f:
  359. json.dump(report.to_dict(), f, indent=2, ensure_ascii=False)
  360. if options.dry_run:
  361. would_install_items = sorted(
  362. (r.metadata["name"], r.metadata["version"])
  363. for r in requirement_set.requirements_to_install
  364. )
  365. if would_install_items:
  366. write_output(
  367. "Would install %s",
  368. " ".join("-".join(item) for item in would_install_items),
  369. )
  370. return SUCCESS
  371. # If there is any more preparation to do for the actual installation, do
  372. # so now. This includes actually downloading the files in the case that
  373. # we have been using PEP-658 metadata so far.
  374. preparer.prepare_linked_requirements_more(
  375. requirement_set.requirements.values()
  376. )
  377. try:
  378. pip_req = requirement_set.get_requirement("pip")
  379. except KeyError:
  380. modifying_pip = False
  381. else:
  382. # If we're not replacing an already installed pip,
  383. # we're not modifying it.
  384. modifying_pip = pip_req.satisfied_by is None
  385. protect_pip_from_modification_on_windows(modifying_pip=modifying_pip)
  386. reqs_to_build = [
  387. r for r in requirement_set.requirements_to_install if not r.is_wheel
  388. ]
  389. _, build_failures = build(
  390. reqs_to_build,
  391. wheel_cache=wheel_cache,
  392. verify=True,
  393. )
  394. if build_failures:
  395. raise InstallWheelBuildError(build_failures)
  396. to_install = resolver.get_installation_order(requirement_set)
  397. # Check for conflicts in the package set we're installing.
  398. conflicts: ConflictDetails | None = None
  399. should_warn_about_conflicts = (
  400. not options.ignore_dependencies and options.warn_about_conflicts
  401. )
  402. if should_warn_about_conflicts:
  403. conflicts = self._determine_conflicts(to_install)
  404. # Don't warn about script install locations if
  405. # --target or --prefix has been specified
  406. warn_script_location = options.warn_script_location
  407. if options.target_dir or options.prefix_path:
  408. warn_script_location = False
  409. installed = install_given_reqs(
  410. to_install,
  411. root=options.root_path,
  412. home=target_temp_dir_path,
  413. prefix=options.prefix_path,
  414. warn_script_location=warn_script_location,
  415. use_user_site=options.use_user_site,
  416. pycompile=options.compile,
  417. progress_bar=options.progress_bar,
  418. )
  419. lib_locations = get_lib_location_guesses(
  420. user=options.use_user_site,
  421. home=target_temp_dir_path,
  422. root=options.root_path,
  423. prefix=options.prefix_path,
  424. isolated=options.isolated_mode,
  425. )
  426. env = get_environment(lib_locations)
  427. if conflicts is not None:
  428. self._warn_about_conflicts(
  429. conflicts,
  430. resolver_variant=self.determine_resolver_variant(options),
  431. )
  432. if summary := installed_packages_summary(installed, env):
  433. write_output(summary)
  434. except OSError as error:
  435. show_traceback = self.verbosity >= 1
  436. message = create_os_error_message(
  437. error,
  438. show_traceback,
  439. options.use_user_site,
  440. )
  441. logger.error(message, exc_info=show_traceback)
  442. return ERROR
  443. if options.target_dir:
  444. assert target_temp_dir
  445. self._handle_target_dir(
  446. options.target_dir, target_temp_dir, options.upgrade
  447. )
  448. if options.root_user_action == "warn":
  449. warn_if_run_as_root()
  450. return SUCCESS
  451. def _handle_target_dir(
  452. self, target_dir: str, target_temp_dir: TempDirectory, upgrade: bool
  453. ) -> None:
  454. ensure_dir(target_dir)
  455. # Checking both purelib and platlib directories for installed
  456. # packages to be moved to target directory
  457. lib_dir_list = []
  458. # Checking both purelib and platlib directories for installed
  459. # packages to be moved to target directory
  460. scheme = get_scheme("", home=target_temp_dir.path)
  461. purelib_dir = scheme.purelib
  462. platlib_dir = scheme.platlib
  463. data_dir = scheme.data
  464. if os.path.exists(purelib_dir):
  465. lib_dir_list.append(purelib_dir)
  466. if os.path.exists(platlib_dir) and platlib_dir != purelib_dir:
  467. lib_dir_list.append(platlib_dir)
  468. if os.path.exists(data_dir):
  469. lib_dir_list.append(data_dir)
  470. for lib_dir in lib_dir_list:
  471. for item in os.listdir(lib_dir):
  472. if lib_dir == data_dir:
  473. ddir = os.path.join(data_dir, item)
  474. if any(s.startswith(ddir) for s in lib_dir_list[:-1]):
  475. continue
  476. target_item_dir = os.path.join(target_dir, item)
  477. if os.path.exists(target_item_dir):
  478. if not upgrade:
  479. logger.warning(
  480. "Target directory %s already exists. Specify "
  481. "--upgrade to force replacement.",
  482. target_item_dir,
  483. )
  484. continue
  485. if os.path.islink(target_item_dir):
  486. logger.warning(
  487. "Target directory %s already exists and is "
  488. "a link. pip will not automatically replace "
  489. "links, please remove if replacement is "
  490. "desired.",
  491. target_item_dir,
  492. )
  493. continue
  494. if os.path.isdir(target_item_dir):
  495. shutil.rmtree(target_item_dir)
  496. else:
  497. os.remove(target_item_dir)
  498. shutil.move(os.path.join(lib_dir, item), target_item_dir)
  499. def _determine_conflicts(
  500. self, to_install: list[InstallRequirement]
  501. ) -> ConflictDetails | None:
  502. try:
  503. return check_install_conflicts(to_install)
  504. except Exception:
  505. logger.exception(
  506. "Error while checking for conflicts. Please file an issue on "
  507. "pip's issue tracker: https://github.com/pypa/pip/issues/new"
  508. )
  509. return None
  510. def _warn_about_conflicts(
  511. self, conflict_details: ConflictDetails, resolver_variant: str
  512. ) -> None:
  513. package_set, (missing, conflicting) = conflict_details
  514. if not missing and not conflicting:
  515. return
  516. parts: list[str] = []
  517. if resolver_variant == "legacy":
  518. parts.append(
  519. "pip's legacy dependency resolver does not consider dependency "
  520. "conflicts when selecting packages. This behaviour is the "
  521. "source of the following dependency conflicts."
  522. )
  523. else:
  524. assert resolver_variant == "resolvelib"
  525. parts.append(
  526. "pip's dependency resolver does not currently take into account "
  527. "all the packages that are installed. This behaviour is the "
  528. "source of the following dependency conflicts."
  529. )
  530. # NOTE: There is some duplication here, with commands/check.py
  531. for project_name in missing:
  532. version = package_set[project_name][0]
  533. for dependency in missing[project_name]:
  534. message = (
  535. f"{project_name} {version} requires {dependency[1]}, "
  536. "which is not installed."
  537. )
  538. parts.append(message)
  539. for project_name in conflicting:
  540. version = package_set[project_name][0]
  541. for dep_name, dep_version, req in conflicting[project_name]:
  542. message = (
  543. "{name} {version} requires {requirement}, but {you} have "
  544. "{dep_name} {dep_version} which is incompatible."
  545. ).format(
  546. name=project_name,
  547. version=version,
  548. requirement=req,
  549. dep_name=dep_name,
  550. dep_version=dep_version,
  551. you=("you" if resolver_variant == "resolvelib" else "you'll"),
  552. )
  553. parts.append(message)
  554. logger.critical("\n".join(parts))
  555. def installed_packages_summary(
  556. installed: list[InstallationResult], env: BaseEnvironment
  557. ) -> str:
  558. # Format a summary of installed packages, with extra care to
  559. # display a package name as it was requested by the user.
  560. installed.sort(key=operator.attrgetter("name"))
  561. summary = []
  562. installed_versions = {}
  563. for distribution in env.iter_all_distributions():
  564. installed_versions[distribution.canonical_name] = distribution.version
  565. for package in installed:
  566. display_name = package.name
  567. version = installed_versions.get(canonicalize_name(display_name), None)
  568. if version:
  569. text = f"{display_name}-{version}"
  570. else:
  571. text = display_name
  572. summary.append(text)
  573. if not summary:
  574. return ""
  575. return f"Successfully installed {' '.join(summary)}"
  576. def get_lib_location_guesses(
  577. user: bool = False,
  578. home: str | None = None,
  579. root: str | None = None,
  580. isolated: bool = False,
  581. prefix: str | None = None,
  582. ) -> list[str]:
  583. scheme = get_scheme(
  584. "",
  585. user=user,
  586. home=home,
  587. root=root,
  588. isolated=isolated,
  589. prefix=prefix,
  590. )
  591. return [scheme.purelib, scheme.platlib]
  592. def site_packages_writable(root: str | None, isolated: bool) -> bool:
  593. return all(
  594. test_writable_dir(d)
  595. for d in set(get_lib_location_guesses(root=root, isolated=isolated))
  596. )
  597. def decide_user_install(
  598. use_user_site: bool | None,
  599. prefix_path: str | None = None,
  600. target_dir: str | None = None,
  601. root_path: str | None = None,
  602. isolated_mode: bool = False,
  603. ) -> bool:
  604. """Determine whether to do a user install based on the input options.
  605. If use_user_site is False, no additional checks are done.
  606. If use_user_site is True, it is checked for compatibility with other
  607. options.
  608. If use_user_site is None, the default behaviour depends on the environment,
  609. which is provided by the other arguments.
  610. """
  611. # In some cases (config from tox), use_user_site can be set to an integer
  612. # rather than a bool, which 'use_user_site is False' wouldn't catch.
  613. if (use_user_site is not None) and (not use_user_site):
  614. logger.debug("Non-user install by explicit request")
  615. return False
  616. # If we have been asked for a user install explicitly, check compatibility.
  617. if use_user_site:
  618. if prefix_path:
  619. raise CommandError(
  620. "Can not combine '--user' and '--prefix' as they imply "
  621. "different installation locations"
  622. )
  623. if virtualenv_no_global():
  624. raise InstallationError(
  625. "Can not perform a '--user' install. User site-packages "
  626. "are not visible in this virtualenv."
  627. )
  628. # Catch all remaining cases which honour the site.ENABLE_USER_SITE
  629. # value, such as a plain Python installation (e.g. no virtualenv).
  630. if not site.ENABLE_USER_SITE:
  631. raise InstallationError(
  632. "Can not perform a '--user' install. User site-packages "
  633. "are disabled for this Python."
  634. )
  635. logger.debug("User install by explicit request")
  636. return True
  637. # If we are here, user installs have not been explicitly requested/avoided
  638. assert use_user_site is None
  639. # user install incompatible with --prefix/--target
  640. if prefix_path or target_dir:
  641. logger.debug("Non-user install due to --prefix or --target option")
  642. return False
  643. # If user installs are not enabled, choose a non-user install
  644. if not site.ENABLE_USER_SITE:
  645. logger.debug("Non-user install because user site-packages disabled")
  646. return False
  647. # If we have permission for a non-user install, do that,
  648. # otherwise do a user install.
  649. if site_packages_writable(root=root_path, isolated=isolated_mode):
  650. logger.debug("Non-user install because site-packages writeable")
  651. return False
  652. logger.info(
  653. "Defaulting to user installation because normal site-packages "
  654. "is not writeable"
  655. )
  656. return True
  657. def create_os_error_message(
  658. error: OSError, show_traceback: bool, using_user_site: bool
  659. ) -> str:
  660. """Format an error message for an OSError
  661. It may occur anytime during the execution of the install command.
  662. """
  663. parts = []
  664. # Mention the error if we are not going to show a traceback
  665. parts.append("Could not install packages due to an OSError")
  666. if not show_traceback:
  667. parts.append(": ")
  668. parts.append(str(error))
  669. else:
  670. parts.append(".")
  671. # Spilt the error indication from a helper message (if any)
  672. parts[-1] += "\n"
  673. # Suggest useful actions to the user:
  674. # (1) using user site-packages or (2) verifying the permissions
  675. if error.errno == errno.EACCES:
  676. user_option_part = "Consider using the `--user` option"
  677. permissions_part = "Check the permissions"
  678. if not running_under_virtualenv() and not using_user_site:
  679. parts.extend(
  680. [
  681. user_option_part,
  682. " or ",
  683. permissions_part.lower(),
  684. ]
  685. )
  686. else:
  687. parts.append(permissions_part)
  688. parts.append(".\n")
  689. # Suggest to check "pip config debug" in case of invalid proxy
  690. if type(error) is InvalidProxyURL:
  691. parts.append(
  692. 'Consider checking your local proxy configuration with "pip config debug"'
  693. )
  694. parts.append(".\n")
  695. # On Windows, errors like EINVAL or ENOENT may occur
  696. # if a file or folder name exceeds 255 characters,
  697. # or if the full path exceeds 260 characters and long path support isn't enabled.
  698. # This condition checks for such cases and adds a hint to the error output.
  699. if WINDOWS and error.errno in (errno.EINVAL, errno.ENOENT) and error.filename:
  700. if any(len(part) > 255 for part in Path(error.filename).parts):
  701. parts.append(
  702. "HINT: This error might be caused by a file or folder name exceeding "
  703. "255 characters, which is a Windows limitation even if long paths "
  704. "are enabled.\n "
  705. )
  706. if len(error.filename) > 260:
  707. parts.append(
  708. "HINT: This error might have occurred since "
  709. "this system does not have Windows Long Path "
  710. "support enabled. You can find information on "
  711. "how to enable this at "
  712. "https://pip.pypa.io/warnings/enable-long-paths\n"
  713. )
  714. return "".join(parts).strip() + "\n"