install.py 29 KB

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