1import sys 2import marshal 3import contextlib 4import dis 5 6from setuptools.extern.packaging import version 7 8from ._imp import find_module, PY_COMPILED, PY_FROZEN, PY_SOURCE 9from . import _imp 10 11 12__all__ = [ 13 'Require', 'find_module', 'get_module_constant', 'extract_constant' 14] 15 16 17class Require: 18 """A prerequisite to building or installing a distribution""" 19 20 def __init__( 21 self, name, requested_version, module, homepage='', 22 attribute=None, format=None): 23 24 if format is None and requested_version is not None: 25 format = version.Version 26 27 if format is not None: 28 requested_version = format(requested_version) 29 if attribute is None: 30 attribute = '__version__' 31 32 self.__dict__.update(locals()) 33 del self.self 34 35 def full_name(self): 36 """Return full package/distribution name, w/version""" 37 if self.requested_version is not None: 38 return '%s-%s' % (self.name, self.requested_version) 39 return self.name 40 41 def version_ok(self, version): 42 """Is 'version' sufficiently up-to-date?""" 43 return self.attribute is None or self.format is None or \ 44 str(version) != "unknown" and self.format(version) >= self.requested_version 45 46 def get_version(self, paths=None, default="unknown"): 47 """Get version number of installed module, 'None', or 'default' 48 49 Search 'paths' for module. If not found, return 'None'. If found, 50 return the extracted version attribute, or 'default' if no version 51 attribute was specified, or the value cannot be determined without 52 importing the module. The version is formatted according to the 53 requirement's version format (if any), unless it is 'None' or the 54 supplied 'default'. 55 """ 56 57 if self.attribute is None: 58 try: 59 f, p, i = find_module(self.module, paths) 60 if f: 61 f.close() 62 return default 63 except ImportError: 64 return None 65 66 v = get_module_constant(self.module, self.attribute, default, paths) 67 68 if v is not None and v is not default and self.format is not None: 69 return self.format(v) 70 71 return v 72 73 def is_present(self, paths=None): 74 """Return true if dependency is present on 'paths'""" 75 return self.get_version(paths) is not None 76 77 def is_current(self, paths=None): 78 """Return true if dependency is present and up-to-date on 'paths'""" 79 version = self.get_version(paths) 80 if version is None: 81 return False 82 return self.version_ok(str(version)) 83 84 85def maybe_close(f): 86 @contextlib.contextmanager 87 def empty(): 88 yield 89 return 90 if not f: 91 return empty() 92 93 return contextlib.closing(f) 94 95 96def get_module_constant(module, symbol, default=-1, paths=None): 97 """Find 'module' by searching 'paths', and extract 'symbol' 98 99 Return 'None' if 'module' does not exist on 'paths', or it does not define 100 'symbol'. If the module defines 'symbol' as a constant, return the 101 constant. Otherwise, return 'default'.""" 102 103 try: 104 f, path, (suffix, mode, kind) = info = find_module(module, paths) 105 except ImportError: 106 # Module doesn't exist 107 return None 108 109 with maybe_close(f): 110 if kind == PY_COMPILED: 111 f.read(8) # skip magic & date 112 code = marshal.load(f) 113 elif kind == PY_FROZEN: 114 code = _imp.get_frozen_object(module, paths) 115 elif kind == PY_SOURCE: 116 code = compile(f.read(), path, 'exec') 117 else: 118 # Not something we can parse; we'll have to import it. :( 119 imported = _imp.get_module(module, paths, info) 120 return getattr(imported, symbol, None) 121 122 return extract_constant(code, symbol, default) 123 124 125def extract_constant(code, symbol, default=-1): 126 """Extract the constant value of 'symbol' from 'code' 127 128 If the name 'symbol' is bound to a constant value by the Python code 129 object 'code', return that value. If 'symbol' is bound to an expression, 130 return 'default'. Otherwise, return 'None'. 131 132 Return value is based on the first assignment to 'symbol'. 'symbol' must 133 be a global, or at least a non-"fast" local in the code block. That is, 134 only 'STORE_NAME' and 'STORE_GLOBAL' opcodes are checked, and 'symbol' 135 must be present in 'code.co_names'. 136 """ 137 if symbol not in code.co_names: 138 # name's not there, can't possibly be an assignment 139 return None 140 141 name_idx = list(code.co_names).index(symbol) 142 143 STORE_NAME = 90 144 STORE_GLOBAL = 97 145 LOAD_CONST = 100 146 147 const = default 148 149 for byte_code in dis.Bytecode(code): 150 op = byte_code.opcode 151 arg = byte_code.arg 152 153 if op == LOAD_CONST: 154 const = code.co_consts[arg] 155 elif arg == name_idx and (op == STORE_NAME or op == STORE_GLOBAL): 156 return const 157 else: 158 const = default 159 160 161def _update_globals(): 162 """ 163 Patch the globals to remove the objects not available on some platforms. 164 165 XXX it'd be better to test assertions about bytecode instead. 166 """ 167 168 if not sys.platform.startswith('java') and sys.platform != 'cli': 169 return 170 incompatible = 'extract_constant', 'get_module_constant' 171 for name in incompatible: 172 del globals()[name] 173 __all__.remove(name) 174 175 176_update_globals() 177