1# 2# DEPRECATED: implementation for ffi.verify() 3# 4import sys, os, binascii, shutil, io 5from . import __version_verifier_modules__ 6from . import ffiplatform 7from .error import VerificationError 8 9if sys.version_info >= (3, 3): 10 import importlib.machinery 11 def _extension_suffixes(): 12 return importlib.machinery.EXTENSION_SUFFIXES[:] 13else: 14 import imp 15 def _extension_suffixes(): 16 return [suffix for suffix, _, type in imp.get_suffixes() 17 if type == imp.C_EXTENSION] 18 19 20if sys.version_info >= (3,): 21 NativeIO = io.StringIO 22else: 23 class NativeIO(io.BytesIO): 24 def write(self, s): 25 if isinstance(s, unicode): 26 s = s.encode('ascii') 27 super(NativeIO, self).write(s) 28 29 30class Verifier(object): 31 32 def __init__(self, ffi, preamble, tmpdir=None, modulename=None, 33 ext_package=None, tag='', force_generic_engine=False, 34 source_extension='.c', flags=None, relative_to=None, **kwds): 35 if ffi._parser._uses_new_feature: 36 raise VerificationError( 37 "feature not supported with ffi.verify(), but only " 38 "with ffi.set_source(): %s" % (ffi._parser._uses_new_feature,)) 39 self.ffi = ffi 40 self.preamble = preamble 41 if not modulename: 42 flattened_kwds = ffiplatform.flatten(kwds) 43 vengine_class = _locate_engine_class(ffi, force_generic_engine) 44 self._vengine = vengine_class(self) 45 self._vengine.patch_extension_kwds(kwds) 46 self.flags = flags 47 self.kwds = self.make_relative_to(kwds, relative_to) 48 # 49 if modulename: 50 if tag: 51 raise TypeError("can't specify both 'modulename' and 'tag'") 52 else: 53 key = '\x00'.join(['%d.%d' % sys.version_info[:2], 54 __version_verifier_modules__, 55 preamble, flattened_kwds] + 56 ffi._cdefsources) 57 if sys.version_info >= (3,): 58 key = key.encode('utf-8') 59 k1 = hex(binascii.crc32(key[0::2]) & 0xffffffff) 60 k1 = k1.lstrip('0x').rstrip('L') 61 k2 = hex(binascii.crc32(key[1::2]) & 0xffffffff) 62 k2 = k2.lstrip('0').rstrip('L') 63 modulename = '_cffi_%s_%s%s%s' % (tag, self._vengine._class_key, 64 k1, k2) 65 suffix = _get_so_suffixes()[0] 66 self.tmpdir = tmpdir or _caller_dir_pycache() 67 self.sourcefilename = os.path.join(self.tmpdir, modulename + source_extension) 68 self.modulefilename = os.path.join(self.tmpdir, modulename + suffix) 69 self.ext_package = ext_package 70 self._has_source = False 71 self._has_module = False 72 73 def write_source(self, file=None): 74 """Write the C source code. It is produced in 'self.sourcefilename', 75 which can be tweaked beforehand.""" 76 with self.ffi._lock: 77 if self._has_source and file is None: 78 raise VerificationError( 79 "source code already written") 80 self._write_source(file) 81 82 def compile_module(self): 83 """Write the C source code (if not done already) and compile it. 84 This produces a dynamic link library in 'self.modulefilename'.""" 85 with self.ffi._lock: 86 if self._has_module: 87 raise VerificationError("module already compiled") 88 if not self._has_source: 89 self._write_source() 90 self._compile_module() 91 92 def load_library(self): 93 """Get a C module from this Verifier instance. 94 Returns an instance of a FFILibrary class that behaves like the 95 objects returned by ffi.dlopen(), but that delegates all 96 operations to the C module. If necessary, the C code is written 97 and compiled first. 98 """ 99 with self.ffi._lock: 100 if not self._has_module: 101 self._locate_module() 102 if not self._has_module: 103 if not self._has_source: 104 self._write_source() 105 self._compile_module() 106 return self._load_library() 107 108 def get_module_name(self): 109 basename = os.path.basename(self.modulefilename) 110 # kill both the .so extension and the other .'s, as introduced 111 # by Python 3: 'basename.cpython-33m.so' 112 basename = basename.split('.', 1)[0] 113 # and the _d added in Python 2 debug builds --- but try to be 114 # conservative and not kill a legitimate _d 115 if basename.endswith('_d') and hasattr(sys, 'gettotalrefcount'): 116 basename = basename[:-2] 117 return basename 118 119 def get_extension(self): 120 ffiplatform._hack_at_distutils() # backward compatibility hack 121 if not self._has_source: 122 with self.ffi._lock: 123 if not self._has_source: 124 self._write_source() 125 sourcename = ffiplatform.maybe_relative_path(self.sourcefilename) 126 modname = self.get_module_name() 127 return ffiplatform.get_extension(sourcename, modname, **self.kwds) 128 129 def generates_python_module(self): 130 return self._vengine._gen_python_module 131 132 def make_relative_to(self, kwds, relative_to): 133 if relative_to and os.path.dirname(relative_to): 134 dirname = os.path.dirname(relative_to) 135 kwds = kwds.copy() 136 for key in ffiplatform.LIST_OF_FILE_NAMES: 137 if key in kwds: 138 lst = kwds[key] 139 if not isinstance(lst, (list, tuple)): 140 raise TypeError("keyword '%s' should be a list or tuple" 141 % (key,)) 142 lst = [os.path.join(dirname, fn) for fn in lst] 143 kwds[key] = lst 144 return kwds 145 146 # ---------- 147 148 def _locate_module(self): 149 if not os.path.isfile(self.modulefilename): 150 if self.ext_package: 151 try: 152 pkg = __import__(self.ext_package, None, None, ['__doc__']) 153 except ImportError: 154 return # cannot import the package itself, give up 155 # (e.g. it might be called differently before installation) 156 path = pkg.__path__ 157 else: 158 path = None 159 filename = self._vengine.find_module(self.get_module_name(), path, 160 _get_so_suffixes()) 161 if filename is None: 162 return 163 self.modulefilename = filename 164 self._vengine.collect_types() 165 self._has_module = True 166 167 def _write_source_to(self, file): 168 self._vengine._f = file 169 try: 170 self._vengine.write_source_to_f() 171 finally: 172 del self._vengine._f 173 174 def _write_source(self, file=None): 175 if file is not None: 176 self._write_source_to(file) 177 else: 178 # Write our source file to an in memory file. 179 f = NativeIO() 180 self._write_source_to(f) 181 source_data = f.getvalue() 182 183 # Determine if this matches the current file 184 if os.path.exists(self.sourcefilename): 185 with open(self.sourcefilename, "r") as fp: 186 needs_written = not (fp.read() == source_data) 187 else: 188 needs_written = True 189 190 # Actually write the file out if it doesn't match 191 if needs_written: 192 _ensure_dir(self.sourcefilename) 193 with open(self.sourcefilename, "w") as fp: 194 fp.write(source_data) 195 196 # Set this flag 197 self._has_source = True 198 199 def _compile_module(self): 200 # compile this C source 201 tmpdir = os.path.dirname(self.sourcefilename) 202 outputfilename = ffiplatform.compile(tmpdir, self.get_extension()) 203 try: 204 same = ffiplatform.samefile(outputfilename, self.modulefilename) 205 except OSError: 206 same = False 207 if not same: 208 _ensure_dir(self.modulefilename) 209 shutil.move(outputfilename, self.modulefilename) 210 self._has_module = True 211 212 def _load_library(self): 213 assert self._has_module 214 if self.flags is not None: 215 return self._vengine.load_library(self.flags) 216 else: 217 return self._vengine.load_library() 218 219# ____________________________________________________________ 220 221_FORCE_GENERIC_ENGINE = False # for tests 222 223def _locate_engine_class(ffi, force_generic_engine): 224 if _FORCE_GENERIC_ENGINE: 225 force_generic_engine = True 226 if not force_generic_engine: 227 if '__pypy__' in sys.builtin_module_names: 228 force_generic_engine = True 229 else: 230 try: 231 import _cffi_backend 232 except ImportError: 233 _cffi_backend = '?' 234 if ffi._backend is not _cffi_backend: 235 force_generic_engine = True 236 if force_generic_engine: 237 from . import vengine_gen 238 return vengine_gen.VGenericEngine 239 else: 240 from . import vengine_cpy 241 return vengine_cpy.VCPythonEngine 242 243# ____________________________________________________________ 244 245_TMPDIR = None 246 247def _caller_dir_pycache(): 248 if _TMPDIR: 249 return _TMPDIR 250 result = os.environ.get('CFFI_TMPDIR') 251 if result: 252 return result 253 filename = sys._getframe(2).f_code.co_filename 254 return os.path.abspath(os.path.join(os.path.dirname(filename), 255 '__pycache__')) 256 257def set_tmpdir(dirname): 258 """Set the temporary directory to use instead of __pycache__.""" 259 global _TMPDIR 260 _TMPDIR = dirname 261 262def cleanup_tmpdir(tmpdir=None, keep_so=False): 263 """Clean up the temporary directory by removing all files in it 264 called `_cffi_*.{c,so}` as well as the `build` subdirectory.""" 265 tmpdir = tmpdir or _caller_dir_pycache() 266 try: 267 filelist = os.listdir(tmpdir) 268 except OSError: 269 return 270 if keep_so: 271 suffix = '.c' # only remove .c files 272 else: 273 suffix = _get_so_suffixes()[0].lower() 274 for fn in filelist: 275 if fn.lower().startswith('_cffi_') and ( 276 fn.lower().endswith(suffix) or fn.lower().endswith('.c')): 277 try: 278 os.unlink(os.path.join(tmpdir, fn)) 279 except OSError: 280 pass 281 clean_dir = [os.path.join(tmpdir, 'build')] 282 for dir in clean_dir: 283 try: 284 for fn in os.listdir(dir): 285 fn = os.path.join(dir, fn) 286 if os.path.isdir(fn): 287 clean_dir.append(fn) 288 else: 289 os.unlink(fn) 290 except OSError: 291 pass 292 293def _get_so_suffixes(): 294 suffixes = _extension_suffixes() 295 if not suffixes: 296 # bah, no C_EXTENSION available. Occurs on pypy without cpyext 297 if sys.platform == 'win32': 298 suffixes = [".pyd"] 299 else: 300 suffixes = [".so"] 301 302 return suffixes 303 304def _ensure_dir(filename): 305 dirname = os.path.dirname(filename) 306 if dirname and not os.path.isdir(dirname): 307 os.makedirs(dirname) 308