install.py 27 KB

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