monkey.py 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. """
  2. Monkey patching of distutils.
  3. """
  4. import sys
  5. import distutils.filelist
  6. import platform
  7. import types
  8. import functools
  9. from importlib import import_module
  10. import inspect
  11. from setuptools.extern import six
  12. import setuptools
  13. __all__ = []
  14. """
  15. Everything is private. Contact the project team
  16. if you think you need this functionality.
  17. """
  18. def _get_mro(cls):
  19. """
  20. Returns the bases classes for cls sorted by the MRO.
  21. Works around an issue on Jython where inspect.getmro will not return all
  22. base classes if multiple classes share the same name. Instead, this
  23. function will return a tuple containing the class itself, and the contents
  24. of cls.__bases__. See https://github.com/pypa/setuptools/issues/1024.
  25. """
  26. if platform.python_implementation() == "Jython":
  27. return (cls,) + cls.__bases__
  28. return inspect.getmro(cls)
  29. def get_unpatched(item):
  30. lookup = (
  31. get_unpatched_class if isinstance(item, six.class_types) else
  32. get_unpatched_function if isinstance(item, types.FunctionType) else
  33. lambda item: None
  34. )
  35. return lookup(item)
  36. def get_unpatched_class(cls):
  37. """Protect against re-patching the distutils if reloaded
  38. Also ensures that no other distutils extension monkeypatched the distutils
  39. first.
  40. """
  41. external_bases = (
  42. cls
  43. for cls in _get_mro(cls)
  44. if not cls.__module__.startswith('setuptools')
  45. )
  46. base = next(external_bases)
  47. if not base.__module__.startswith('distutils'):
  48. msg = "distutils has already been patched by %r" % cls
  49. raise AssertionError(msg)
  50. return base
  51. def patch_all():
  52. # we can't patch distutils.cmd, alas
  53. distutils.core.Command = setuptools.Command
  54. has_issue_12885 = sys.version_info <= (3, 5, 3)
  55. if has_issue_12885:
  56. # fix findall bug in distutils (http://bugs.python.org/issue12885)
  57. distutils.filelist.findall = setuptools.findall
  58. needs_warehouse = (
  59. sys.version_info < (2, 7, 13)
  60. or
  61. (3, 0) < sys.version_info < (3, 3, 7)
  62. or
  63. (3, 4) < sys.version_info < (3, 4, 6)
  64. or
  65. (3, 5) < sys.version_info <= (3, 5, 3)
  66. )
  67. if needs_warehouse:
  68. warehouse = 'https://upload.pypi.org/legacy/'
  69. distutils.config.PyPIRCCommand.DEFAULT_REPOSITORY = warehouse
  70. _patch_distribution_metadata_write_pkg_file()
  71. _patch_distribution_metadata_write_pkg_info()
  72. # Install Distribution throughout the distutils
  73. for module in distutils.dist, distutils.core, distutils.cmd:
  74. module.Distribution = setuptools.dist.Distribution
  75. # Install the patched Extension
  76. distutils.core.Extension = setuptools.extension.Extension
  77. distutils.extension.Extension = setuptools.extension.Extension
  78. if 'distutils.command.build_ext' in sys.modules:
  79. sys.modules['distutils.command.build_ext'].Extension = (
  80. setuptools.extension.Extension
  81. )
  82. patch_for_msvc_specialized_compiler()
  83. def _patch_distribution_metadata_write_pkg_file():
  84. """Patch write_pkg_file to also write Requires-Python/Requires-External"""
  85. distutils.dist.DistributionMetadata.write_pkg_file = (
  86. setuptools.dist.write_pkg_file
  87. )
  88. def _patch_distribution_metadata_write_pkg_info():
  89. """
  90. Workaround issue #197 - Python 3 prior to 3.2.2 uses an environment-local
  91. encoding to save the pkg_info. Monkey-patch its write_pkg_info method to
  92. correct this undesirable behavior.
  93. """
  94. environment_local = (3,) <= sys.version_info[:3] < (3, 2, 2)
  95. if not environment_local:
  96. return
  97. distutils.dist.DistributionMetadata.write_pkg_info = (
  98. setuptools.dist.write_pkg_info
  99. )
  100. def patch_func(replacement, target_mod, func_name):
  101. """
  102. Patch func_name in target_mod with replacement
  103. Important - original must be resolved by name to avoid
  104. patching an already patched function.
  105. """
  106. original = getattr(target_mod, func_name)
  107. # set the 'unpatched' attribute on the replacement to
  108. # point to the original.
  109. vars(replacement).setdefault('unpatched', original)
  110. # replace the function in the original module
  111. setattr(target_mod, func_name, replacement)
  112. def get_unpatched_function(candidate):
  113. return getattr(candidate, 'unpatched')
  114. def patch_for_msvc_specialized_compiler():
  115. """
  116. Patch functions in distutils to use standalone Microsoft Visual C++
  117. compilers.
  118. """
  119. # import late to avoid circular imports on Python < 3.5
  120. msvc = import_module('setuptools.msvc')
  121. if platform.system() != 'Windows':
  122. # Compilers only availables on Microsoft Windows
  123. return
  124. def patch_params(mod_name, func_name):
  125. """
  126. Prepare the parameters for patch_func to patch indicated function.
  127. """
  128. repl_prefix = 'msvc9_' if 'msvc9' in mod_name else 'msvc14_'
  129. repl_name = repl_prefix + func_name.lstrip('_')
  130. repl = getattr(msvc, repl_name)
  131. mod = import_module(mod_name)
  132. if not hasattr(mod, func_name):
  133. raise ImportError(func_name)
  134. return repl, mod, func_name
  135. # Python 2.7 to 3.4
  136. msvc9 = functools.partial(patch_params, 'distutils.msvc9compiler')
  137. # Python 3.5+
  138. msvc14 = functools.partial(patch_params, 'distutils._msvccompiler')
  139. try:
  140. # Patch distutils.msvc9compiler
  141. patch_func(*msvc9('find_vcvarsall'))
  142. patch_func(*msvc9('query_vcvarsall'))
  143. except ImportError:
  144. pass
  145. try:
  146. # Patch distutils._msvccompiler._get_vc_env
  147. patch_func(*msvc14('_get_vc_env'))
  148. except ImportError:
  149. pass
  150. try:
  151. # Patch distutils._msvccompiler.gen_lib_options for Numpy
  152. patch_func(*msvc14('gen_lib_options'))
  153. except ImportError:
  154. pass