base_command.py 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. """Base Command class, and related routines"""
  2. import logging
  3. import logging.config
  4. import optparse
  5. import os
  6. import sys
  7. import traceback
  8. from optparse import Values
  9. from typing import Any, List, Optional, Tuple
  10. from pip._internal.cli import cmdoptions
  11. from pip._internal.cli.command_context import CommandContextMixIn
  12. from pip._internal.cli.parser import ConfigOptionParser, UpdatingDefaultsHelpFormatter
  13. from pip._internal.cli.status_codes import (
  14. ERROR,
  15. PREVIOUS_BUILD_DIR_ERROR,
  16. UNKNOWN_ERROR,
  17. VIRTUALENV_NOT_FOUND,
  18. )
  19. from pip._internal.exceptions import (
  20. BadCommand,
  21. CommandError,
  22. InstallationError,
  23. NetworkConnectionError,
  24. PreviousBuildDirError,
  25. UninstallationError,
  26. )
  27. from pip._internal.utils.deprecation import deprecated
  28. from pip._internal.utils.filesystem import check_path_owner
  29. from pip._internal.utils.logging import BrokenStdoutLoggingError, setup_logging
  30. from pip._internal.utils.misc import get_prog, normalize_path
  31. from pip._internal.utils.temp_dir import TempDirectoryTypeRegistry as TempDirRegistry
  32. from pip._internal.utils.temp_dir import global_tempdir_manager, tempdir_registry
  33. from pip._internal.utils.virtualenv import running_under_virtualenv
  34. __all__ = ["Command"]
  35. logger = logging.getLogger(__name__)
  36. class Command(CommandContextMixIn):
  37. usage = None # type: str
  38. ignore_require_venv = False # type: bool
  39. def __init__(self, name, summary, isolated=False):
  40. # type: (str, str, bool) -> None
  41. super().__init__()
  42. self.name = name
  43. self.summary = summary
  44. self.parser = ConfigOptionParser(
  45. usage=self.usage,
  46. prog=f"{get_prog()} {name}",
  47. formatter=UpdatingDefaultsHelpFormatter(),
  48. add_help_option=False,
  49. name=name,
  50. description=self.__doc__,
  51. isolated=isolated,
  52. )
  53. self.tempdir_registry = None # type: Optional[TempDirRegistry]
  54. # Commands should add options to this option group
  55. optgroup_name = f"{self.name.capitalize()} Options"
  56. self.cmd_opts = optparse.OptionGroup(self.parser, optgroup_name)
  57. # Add the general options
  58. gen_opts = cmdoptions.make_option_group(
  59. cmdoptions.general_group,
  60. self.parser,
  61. )
  62. self.parser.add_option_group(gen_opts)
  63. self.add_options()
  64. def add_options(self):
  65. # type: () -> None
  66. pass
  67. def handle_pip_version_check(self, options):
  68. # type: (Values) -> None
  69. """
  70. This is a no-op so that commands by default do not do the pip version
  71. check.
  72. """
  73. # Make sure we do the pip version check if the index_group options
  74. # are present.
  75. assert not hasattr(options, "no_index")
  76. def run(self, options, args):
  77. # type: (Values, List[Any]) -> int
  78. raise NotImplementedError
  79. def parse_args(self, args):
  80. # type: (List[str]) -> Tuple[Any, Any]
  81. # factored out for testability
  82. return self.parser.parse_args(args)
  83. def main(self, args):
  84. # type: (List[str]) -> int
  85. try:
  86. with self.main_context():
  87. return self._main(args)
  88. finally:
  89. logging.shutdown()
  90. def _main(self, args):
  91. # type: (List[str]) -> int
  92. # We must initialize this before the tempdir manager, otherwise the
  93. # configuration would not be accessible by the time we clean up the
  94. # tempdir manager.
  95. self.tempdir_registry = self.enter_context(tempdir_registry())
  96. # Intentionally set as early as possible so globally-managed temporary
  97. # directories are available to the rest of the code.
  98. self.enter_context(global_tempdir_manager())
  99. options, args = self.parse_args(args)
  100. # Set verbosity so that it can be used elsewhere.
  101. self.verbosity = options.verbose - options.quiet
  102. level_number = setup_logging(
  103. verbosity=self.verbosity,
  104. no_color=options.no_color,
  105. user_log_file=options.log,
  106. )
  107. # TODO: Try to get these passing down from the command?
  108. # without resorting to os.environ to hold these.
  109. # This also affects isolated builds and it should.
  110. if options.no_input:
  111. os.environ["PIP_NO_INPUT"] = "1"
  112. if options.exists_action:
  113. os.environ["PIP_EXISTS_ACTION"] = " ".join(options.exists_action)
  114. if options.require_venv and not self.ignore_require_venv:
  115. # If a venv is required check if it can really be found
  116. if not running_under_virtualenv():
  117. logger.critical("Could not find an activated virtualenv (required).")
  118. sys.exit(VIRTUALENV_NOT_FOUND)
  119. if options.cache_dir:
  120. options.cache_dir = normalize_path(options.cache_dir)
  121. if not check_path_owner(options.cache_dir):
  122. logger.warning(
  123. "The directory '%s' or its parent directory is not owned "
  124. "or is not writable by the current user. The cache "
  125. "has been disabled. Check the permissions and owner of "
  126. "that directory. If executing pip with sudo, you should "
  127. "use sudo's -H flag.",
  128. options.cache_dir,
  129. )
  130. options.cache_dir = None
  131. if getattr(options, "build_dir", None):
  132. deprecated(
  133. reason=(
  134. "The -b/--build/--build-dir/--build-directory "
  135. "option is deprecated and has no effect anymore."
  136. ),
  137. replacement=(
  138. "use the TMPDIR/TEMP/TMP environment variable, "
  139. "possibly combined with --no-clean"
  140. ),
  141. gone_in="21.3",
  142. issue=8333,
  143. )
  144. if "2020-resolver" in options.features_enabled:
  145. logger.warning(
  146. "--use-feature=2020-resolver no longer has any effect, "
  147. "since it is now the default dependency resolver in pip. "
  148. "This will become an error in pip 21.0."
  149. )
  150. try:
  151. status = self.run(options, args)
  152. assert isinstance(status, int)
  153. return status
  154. except PreviousBuildDirError as exc:
  155. logger.critical(str(exc))
  156. logger.debug("Exception information:", exc_info=True)
  157. return PREVIOUS_BUILD_DIR_ERROR
  158. except (
  159. InstallationError,
  160. UninstallationError,
  161. BadCommand,
  162. NetworkConnectionError,
  163. ) as exc:
  164. logger.critical(str(exc))
  165. logger.debug("Exception information:", exc_info=True)
  166. return ERROR
  167. except CommandError as exc:
  168. logger.critical("%s", exc)
  169. logger.debug("Exception information:", exc_info=True)
  170. return ERROR
  171. except BrokenStdoutLoggingError:
  172. # Bypass our logger and write any remaining messages to stderr
  173. # because stdout no longer works.
  174. print("ERROR: Pipe to stdout was broken", file=sys.stderr)
  175. if level_number <= logging.DEBUG:
  176. traceback.print_exc(file=sys.stderr)
  177. return ERROR
  178. except KeyboardInterrupt:
  179. logger.critical("Operation cancelled by user")
  180. logger.debug("Exception information:", exc_info=True)
  181. return ERROR
  182. except BaseException:
  183. logger.critical("Exception:", exc_info=True)
  184. return UNKNOWN_ERROR
  185. finally:
  186. self.handle_pip_version_check(options)