comparison lib/python3.8/site-packages/pip/_internal/commands/install.py @ 0:9e54283cc701 draft

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