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