entrypoints.py 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384
  1. import itertools
  2. import os
  3. import shutil
  4. import sys
  5. from typing import List, Optional
  6. from pip._internal.cli.main import main
  7. from pip._internal.utils.compat import WINDOWS
  8. _EXECUTABLE_NAMES = [
  9. "pip",
  10. f"pip{sys.version_info.major}",
  11. f"pip{sys.version_info.major}.{sys.version_info.minor}",
  12. ]
  13. if WINDOWS:
  14. _allowed_extensions = {"", ".exe"}
  15. _EXECUTABLE_NAMES = [
  16. "".join(parts)
  17. for parts in itertools.product(_EXECUTABLE_NAMES, _allowed_extensions)
  18. ]
  19. def _wrapper(args: Optional[List[str]] = None) -> int:
  20. """Central wrapper for all old entrypoints.
  21. Historically pip has had several entrypoints defined. Because of issues
  22. arising from PATH, sys.path, multiple Pythons, their interactions, and most
  23. of them having a pip installed, users suffer every time an entrypoint gets
  24. moved.
  25. To alleviate this pain, and provide a mechanism for warning users and
  26. directing them to an appropriate place for help, we now define all of
  27. our old entrypoints as wrappers for the current one.
  28. """
  29. sys.stderr.write(
  30. "WARNING: pip is being invoked by an old script wrapper. This will "
  31. "fail in a future version of pip.\n"
  32. "Please see https://github.com/pypa/pip/issues/5599 for advice on "
  33. "fixing the underlying issue.\n"
  34. "To avoid this problem you can invoke Python with '-m pip' instead of "
  35. "running pip directly.\n"
  36. )
  37. return main(args)
  38. def get_best_invocation_for_this_pip() -> str:
  39. """Try to figure out the best way to invoke pip in the current environment."""
  40. binary_directory = "Scripts" if WINDOWS else "bin"
  41. binary_prefix = os.path.join(sys.prefix, binary_directory)
  42. # Try to use pip[X[.Y]] names, if those executables for this environment are
  43. # the first on PATH with that name.
  44. path_parts = os.path.normcase(os.environ.get("PATH", "")).split(os.pathsep)
  45. exe_are_in_PATH = os.path.normcase(binary_prefix) in path_parts
  46. if exe_are_in_PATH:
  47. for exe_name in _EXECUTABLE_NAMES:
  48. found_executable = shutil.which(exe_name)
  49. binary_executable = os.path.join(binary_prefix, exe_name)
  50. if (
  51. found_executable
  52. and os.path.exists(binary_executable)
  53. and os.path.samefile(
  54. found_executable,
  55. binary_executable,
  56. )
  57. ):
  58. return exe_name
  59. # Use the `-m` invocation, if there's no "nice" invocation.
  60. return f"{get_best_invocation_for_this_python()} -m pip"
  61. def get_best_invocation_for_this_python() -> str:
  62. """Try to figure out the best way to invoke the current Python."""
  63. exe = sys.executable
  64. exe_name = os.path.basename(exe)
  65. # Try to use the basename, if it's the first executable.
  66. found_executable = shutil.which(exe_name)
  67. if found_executable and os.path.samefile(found_executable, exe):
  68. return exe_name
  69. # Use the full executable name, because we couldn't find something simpler.
  70. return exe