base_command.py 9.1 KB

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