verifier.py 11 KB

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