install.py 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. from __future__ import annotations
  2. import inspect
  3. import platform
  4. from collections.abc import Callable
  5. from typing import TYPE_CHECKING, Any, ClassVar
  6. from ..dist import Distribution
  7. from ..warnings import SetuptoolsDeprecationWarning, SetuptoolsWarning
  8. import distutils.command.install as orig
  9. from distutils.errors import DistutilsArgError
  10. if TYPE_CHECKING:
  11. # This is only used for a type-cast, don't import at runtime or it'll cause deprecation warnings
  12. from .easy_install import easy_install as easy_install_cls
  13. else:
  14. easy_install_cls = None
  15. def __getattr__(name: str): # pragma: no cover
  16. if name == "_install":
  17. SetuptoolsDeprecationWarning.emit(
  18. "`setuptools.command._install` was an internal implementation detail "
  19. "that was left in for numpy<1.9 support.",
  20. due_date=(2025, 5, 2), # Originally added on 2024-11-01
  21. )
  22. return orig.install
  23. raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
  24. class install(orig.install):
  25. """Use easy_install to install the package, w/dependencies"""
  26. distribution: Distribution # override distutils.dist.Distribution with setuptools.dist.Distribution
  27. user_options = orig.install.user_options + [
  28. ('old-and-unmanageable', None, "Try not to use this!"),
  29. (
  30. 'single-version-externally-managed',
  31. None,
  32. "used by system package builders to create 'flat' eggs",
  33. ),
  34. ]
  35. boolean_options = orig.install.boolean_options + [
  36. 'old-and-unmanageable',
  37. 'single-version-externally-managed',
  38. ]
  39. # Type the same as distutils.command.install.install.sub_commands
  40. # Must keep the second tuple item potentially None due to invariance
  41. new_commands: ClassVar[list[tuple[str, Callable[[Any], bool] | None]]] = [
  42. ('install_egg_info', lambda self: True),
  43. ('install_scripts', lambda self: True),
  44. ]
  45. _nc = dict(new_commands)
  46. def initialize_options(self):
  47. SetuptoolsDeprecationWarning.emit(
  48. "setup.py install is deprecated.",
  49. """
  50. Please avoid running ``setup.py`` directly.
  51. Instead, use pypa/build, pypa/installer or other
  52. standards-based tools.
  53. """,
  54. see_url="https://blog.ganssle.io/articles/2021/10/setup-py-deprecated.html",
  55. due_date=(2025, 10, 31),
  56. )
  57. super().initialize_options()
  58. self.old_and_unmanageable = None
  59. self.single_version_externally_managed = None
  60. def finalize_options(self) -> None:
  61. super().finalize_options()
  62. if self.root:
  63. self.single_version_externally_managed = True
  64. elif self.single_version_externally_managed:
  65. if not self.root and not self.record:
  66. raise DistutilsArgError(
  67. "You must specify --record or --root when building system packages"
  68. )
  69. def handle_extra_path(self):
  70. if self.root or self.single_version_externally_managed:
  71. # explicit backward-compatibility mode, allow extra_path to work
  72. return orig.install.handle_extra_path(self)
  73. # Ignore extra_path when installing an egg (or being run by another
  74. # command without --root or --single-version-externally-managed
  75. self.path_file = None
  76. self.extra_dirs = ''
  77. return None
  78. @staticmethod
  79. def _called_from_setup(run_frame):
  80. """
  81. Attempt to detect whether run() was called from setup() or by another
  82. command. If called by setup(), the parent caller will be the
  83. 'run_command' method in 'distutils.dist', and *its* caller will be
  84. the 'run_commands' method. If called any other way, the
  85. immediate caller *might* be 'run_command', but it won't have been
  86. called by 'run_commands'. Return True in that case or if a call stack
  87. is unavailable. Return False otherwise.
  88. """
  89. if run_frame is None:
  90. msg = "Call stack not available. bdist_* commands may fail."
  91. SetuptoolsWarning.emit(msg)
  92. if platform.python_implementation() == 'IronPython':
  93. msg = "For best results, pass -X:Frames to enable call stack."
  94. SetuptoolsWarning.emit(msg)
  95. return True
  96. frames = inspect.getouterframes(run_frame)
  97. for frame in frames[2:4]:
  98. (caller,) = frame[:1]
  99. info = inspect.getframeinfo(caller)
  100. caller_module = caller.f_globals.get('__name__', '')
  101. if caller_module == "setuptools.dist" and info.function == "run_command":
  102. # Starting from v61.0.0 setuptools overwrites dist.run_command
  103. continue
  104. return caller_module == 'distutils.dist' and info.function == 'run_commands'
  105. return False
  106. # XXX Python 3.1 doesn't see _nc if this is inside the class
  107. install.sub_commands = [
  108. cmd for cmd in orig.install.sub_commands if cmd[0] not in install._nc
  109. ] + install.new_commands