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 8from __future__ import absolute_import 9 10import errno 11import logging 12import operator 13import os 14import shutil 15import site 16from optparse import SUPPRESS_HELP 17 18from pipenv.patched.notpip._vendor import pkg_resources 19from pipenv.patched.notpip._vendor.packaging.utils import canonicalize_name 20 21from pipenv.patched.notpip._internal.cache import WheelCache 22from pipenv.patched.notpip._internal.cli import cmdoptions 23from pipenv.patched.notpip._internal.cli.cmdoptions import make_target_python 24from pipenv.patched.notpip._internal.cli.req_command import RequirementCommand 25from pipenv.patched.notpip._internal.cli.status_codes import ERROR, SUCCESS 26from pipenv.patched.notpip._internal.exceptions import ( 27 CommandError, 28 InstallationError, 29 PreviousBuildDirError, 30) 31from pipenv.patched.notpip._internal.locations import distutils_scheme 32from pipenv.patched.notpip._internal.operations.check import check_install_conflicts 33from pipenv.patched.notpip._internal.req import RequirementSet, install_given_reqs 34from pipenv.patched.notpip._internal.req.req_tracker import get_requirement_tracker 35from pipenv.patched.notpip._internal.utils.deprecation import deprecated 36from pipenv.patched.notpip._internal.utils.distutils_args import parse_distutils_args 37from pipenv.patched.notpip._internal.utils.filesystem import test_writable_dir 38from pipenv.patched.notpip._internal.utils.misc import ( 39 ensure_dir, 40 get_installed_version, 41 protect_pip_from_modification_on_windows, 42 write_output, 43) 44from pipenv.patched.notpip._internal.utils.temp_dir import TempDirectory 45from pipenv.patched.notpip._internal.utils.typing import MYPY_CHECK_RUNNING 46from pipenv.patched.notpip._internal.utils.virtualenv import virtualenv_no_global 47from pipenv.patched.notpip._internal.wheel_builder import build, should_build_for_install_command 48 49if MYPY_CHECK_RUNNING: 50 from optparse import Values 51 from typing import Any, Iterable, List, Optional 52 53 from pipenv.patched.notpip._internal.models.format_control import FormatControl 54 from pipenv.patched.notpip._internal.req.req_install import InstallRequirement 55 from pipenv.patched.notpip._internal.wheel_builder import BinaryAllowedPredicate 56 57 58logger = logging.getLogger(__name__) 59 60 61def 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 74class 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 = getattr(pip_req, "satisfied_by", None) 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 pipenv.patched.notpip 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 542def get_lib_location_guesses(*args, **kwargs): 543 scheme = distutils_scheme('', *args, **kwargs) 544 return [scheme['purelib'], scheme['platlib']] 545 546 547def site_packages_writable(**kwargs): 548 return all( 549 test_writable_dir(d) for d in set(get_lib_location_guesses(**kwargs)) 550 ) 551 552 553def 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 613def 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 668def 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" 702