install.py 31 KB

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