You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

306 lines
11 KiB

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