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