comparison env/lib/python3.9/site-packages/pip/_internal/commands/install.py @ 0:4f3585e2f14b draft default tip

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