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, 4) < sys.version_info < (3, 4, 6) 79 or 80 (3, 5) < sys.version_info <= (3, 5, 3) 81 ) 82 83 if needs_warehouse: 84 warehouse = 'https://upload.pypi.org/legacy/' 85 distutils.config.PyPIRCCommand.DEFAULT_REPOSITORY = warehouse 86 87 _patch_distribution_metadata() 88 89 # Install Distribution throughout the distutils 90 for module in distutils.dist, distutils.core, distutils.cmd: 91 module.Distribution = setuptools.dist.Distribution 92 93 # Install the patched Extension 94 distutils.core.Extension = setuptools.extension.Extension 95 distutils.extension.Extension = setuptools.extension.Extension 96 if 'distutils.command.build_ext' in sys.modules: 97 sys.modules['distutils.command.build_ext'].Extension = ( 98 setuptools.extension.Extension 99 ) 100 101 patch_for_msvc_specialized_compiler() 102 103 104def _patch_distribution_metadata(): 105 """Patch write_pkg_file and read_pkg_file for higher metadata standards""" 106 for attr in ('write_pkg_file', 'read_pkg_file', 'get_metadata_version'): 107 new_val = getattr(setuptools.dist, attr) 108 setattr(distutils.dist.DistributionMetadata, attr, new_val) 109 110 111def patch_func(replacement, target_mod, func_name): 112 """ 113 Patch func_name in target_mod with replacement 114 115 Important - original must be resolved by name to avoid 116 patching an already patched function. 117 """ 118 original = getattr(target_mod, func_name) 119 120 # set the 'unpatched' attribute on the replacement to 121 # point to the original. 122 vars(replacement).setdefault('unpatched', original) 123 124 # replace the function in the original module 125 setattr(target_mod, func_name, replacement) 126 127 128def get_unpatched_function(candidate): 129 return getattr(candidate, 'unpatched') 130 131 132def patch_for_msvc_specialized_compiler(): 133 """ 134 Patch functions in distutils to use standalone Microsoft Visual C++ 135 compilers. 136 """ 137 # import late to avoid circular imports on Python < 3.5 138 msvc = import_module('setuptools.msvc') 139 140 if platform.system() != 'Windows': 141 # Compilers only availables on Microsoft Windows 142 return 143 144 def patch_params(mod_name, func_name): 145 """ 146 Prepare the parameters for patch_func to patch indicated function. 147 """ 148 repl_prefix = 'msvc9_' if 'msvc9' in mod_name else 'msvc14_' 149 repl_name = repl_prefix + func_name.lstrip('_') 150 repl = getattr(msvc, repl_name) 151 mod = import_module(mod_name) 152 if not hasattr(mod, func_name): 153 raise ImportError(func_name) 154 return repl, mod, func_name 155 156 # Python 2.7 to 3.4 157 msvc9 = functools.partial(patch_params, 'distutils.msvc9compiler') 158 159 # Python 3.5+ 160 msvc14 = functools.partial(patch_params, 'distutils._msvccompiler') 161 162 try: 163 # Patch distutils.msvc9compiler 164 patch_func(*msvc9('find_vcvarsall')) 165 patch_func(*msvc9('query_vcvarsall')) 166 except ImportError: 167 pass 168 169 try: 170 # Patch distutils._msvccompiler._get_vc_env 171 patch_func(*msvc14('_get_vc_env')) 172 except ImportError: 173 pass 174 175 try: 176 # Patch distutils._msvccompiler.gen_lib_options for Numpy 177 patch_func(*msvc14('gen_lib_options')) 178 except ImportError: 179 pass 180