install.py 28 KB

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