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