debug.py 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. import importlib.resources
  2. import locale
  3. import logging
  4. import os
  5. import sys
  6. from optparse import Values
  7. from types import ModuleType
  8. from typing import Any, Dict, List, Optional
  9. import pip._vendor
  10. from pip._vendor.certifi import where
  11. from pip._vendor.packaging.version import parse as parse_version
  12. from pip._internal.cli import cmdoptions
  13. from pip._internal.cli.base_command import Command
  14. from pip._internal.cli.cmdoptions import make_target_python
  15. from pip._internal.cli.status_codes import SUCCESS
  16. from pip._internal.configuration import Configuration
  17. from pip._internal.metadata import get_environment
  18. from pip._internal.utils.logging import indent_log
  19. from pip._internal.utils.misc import get_pip_version
  20. logger = logging.getLogger(__name__)
  21. def show_value(name: str, value: Any) -> None:
  22. logger.info("%s: %s", name, value)
  23. def show_sys_implementation() -> None:
  24. logger.info("sys.implementation:")
  25. implementation_name = sys.implementation.name
  26. with indent_log():
  27. show_value("name", implementation_name)
  28. def create_vendor_txt_map() -> Dict[str, str]:
  29. with importlib.resources.open_text("pip._vendor", "vendor.txt") as f:
  30. # Purge non version specifying lines.
  31. # Also, remove any space prefix or suffixes (including comments).
  32. lines = [
  33. line.strip().split(" ", 1)[0] for line in f.readlines() if "==" in line
  34. ]
  35. # Transform into "module" -> version dict.
  36. return dict(line.split("==", 1) for line in lines)
  37. def get_module_from_module_name(module_name: str) -> ModuleType:
  38. # Module name can be uppercase in vendor.txt for some reason...
  39. module_name = module_name.lower().replace("-", "_")
  40. # PATCH: setuptools is actually only pkg_resources.
  41. if module_name == "setuptools":
  42. module_name = "pkg_resources"
  43. __import__(f"pip._vendor.{module_name}", globals(), locals(), level=0)
  44. return getattr(pip._vendor, module_name)
  45. def get_vendor_version_from_module(module_name: str) -> Optional[str]:
  46. module = get_module_from_module_name(module_name)
  47. version = getattr(module, "__version__", None)
  48. if not version:
  49. # Try to find version in debundled module info.
  50. assert module.__file__ is not None
  51. env = get_environment([os.path.dirname(module.__file__)])
  52. dist = env.get_distribution(module_name)
  53. if dist:
  54. version = str(dist.version)
  55. return version
  56. def show_actual_vendor_versions(vendor_txt_versions: Dict[str, str]) -> None:
  57. """Log the actual version and print extra info if there is
  58. a conflict or if the actual version could not be imported.
  59. """
  60. for module_name, expected_version in vendor_txt_versions.items():
  61. extra_message = ""
  62. actual_version = get_vendor_version_from_module(module_name)
  63. if not actual_version:
  64. extra_message = (
  65. " (Unable to locate actual module version, using"
  66. " vendor.txt specified version)"
  67. )
  68. actual_version = expected_version
  69. elif parse_version(actual_version) != parse_version(expected_version):
  70. extra_message = (
  71. " (CONFLICT: vendor.txt suggests version should"
  72. " be {})".format(expected_version)
  73. )
  74. logger.info("%s==%s%s", module_name, actual_version, extra_message)
  75. def show_vendor_versions() -> None:
  76. logger.info("vendored library versions:")
  77. vendor_txt_versions = create_vendor_txt_map()
  78. with indent_log():
  79. show_actual_vendor_versions(vendor_txt_versions)
  80. def show_tags(options: Values) -> None:
  81. tag_limit = 10
  82. target_python = make_target_python(options)
  83. tags = target_python.get_tags()
  84. # Display the target options that were explicitly provided.
  85. formatted_target = target_python.format_given()
  86. suffix = ""
  87. if formatted_target:
  88. suffix = f" (target: {formatted_target})"
  89. msg = "Compatible tags: {}{}".format(len(tags), suffix)
  90. logger.info(msg)
  91. if options.verbose < 1 and len(tags) > tag_limit:
  92. tags_limited = True
  93. tags = tags[:tag_limit]
  94. else:
  95. tags_limited = False
  96. with indent_log():
  97. for tag in tags:
  98. logger.info(str(tag))
  99. if tags_limited:
  100. msg = (
  101. "...\n[First {tag_limit} tags shown. Pass --verbose to show all.]"
  102. ).format(tag_limit=tag_limit)
  103. logger.info(msg)
  104. def ca_bundle_info(config: Configuration) -> str:
  105. levels = set()
  106. for key, _ in config.items():
  107. levels.add(key.split(".")[0])
  108. if not levels:
  109. return "Not specified"
  110. levels_that_override_global = ["install", "wheel", "download"]
  111. global_overriding_level = [
  112. level for level in levels if level in levels_that_override_global
  113. ]
  114. if not global_overriding_level:
  115. return "global"
  116. if "global" in levels:
  117. levels.remove("global")
  118. return ", ".join(levels)
  119. class DebugCommand(Command):
  120. """
  121. Display debug information.
  122. """
  123. usage = """
  124. %prog <options>"""
  125. ignore_require_venv = True
  126. def add_options(self) -> None:
  127. cmdoptions.add_target_python_options(self.cmd_opts)
  128. self.parser.insert_option_group(0, self.cmd_opts)
  129. self.parser.config.load()
  130. def run(self, options: Values, args: List[str]) -> int:
  131. logger.warning(
  132. "This command is only meant for debugging. "
  133. "Do not use this with automation for parsing and getting these "
  134. "details, since the output and options of this command may "
  135. "change without notice."
  136. )
  137. show_value("pip version", get_pip_version())
  138. show_value("sys.version", sys.version)
  139. show_value("sys.executable", sys.executable)
  140. show_value("sys.getdefaultencoding", sys.getdefaultencoding())
  141. show_value("sys.getfilesystemencoding", sys.getfilesystemencoding())
  142. show_value(
  143. "locale.getpreferredencoding",
  144. locale.getpreferredencoding(),
  145. )
  146. show_value("sys.platform", sys.platform)
  147. show_sys_implementation()
  148. show_value("'cert' config value", ca_bundle_info(self.parser.config))
  149. show_value("REQUESTS_CA_BUNDLE", os.environ.get("REQUESTS_CA_BUNDLE"))
  150. show_value("CURL_CA_BUNDLE", os.environ.get("CURL_CA_BUNDLE"))
  151. show_value("pip._vendor.certifi.where()", where())
  152. show_value("pip._vendor.DEBUNDLED", pip._vendor.DEBUNDLED)
  153. show_vendor_versions()
  154. show_tags(options)
  155. return SUCCESS