[pypy-commit] cffi default: Progress.

arigo noreply at buildbot.pypy.org
Sun Jul 15 13:34:13 CEST 2012


Author: Armin Rigo <arigo at tunes.org>
Branch: 
Changeset: r646:5a1dd2f000dc
Date: 2012-07-15 13:33 +0200
http://bitbucket.org/cffi/cffi/changeset/5a1dd2f000dc/

Log:	Progress.

diff --git a/cffi/api.py b/cffi/api.py
--- a/cffi/api.py
+++ b/cffi/api.py
@@ -50,6 +50,7 @@
         self._parsed_types = new.module('parsed_types').__dict__
         self._new_types = new.module('new_types').__dict__
         self._function_caches = []
+        self._cdefsources = []
         if hasattr(backend, 'set_ffi'):
             backend.set_ffi(self)
         #
@@ -65,6 +66,7 @@
                 equiv = 'signed %s'
             lines.append('typedef %s %s;' % (equiv % by_size[size], name))
         self.cdef('\n'.join(lines))
+        del self._cdefsources[:]
         #
         self.NULL = self.cast("void *", 0)
 
@@ -75,6 +77,7 @@
         The types can be used in 'ffi.new()' and other functions.
         """
         self._parser.parse(csource, override=override)
+        self._cdefsources.append(csource)
         if override:
             for cache in self._function_caches:
                 cache.clear()
@@ -226,7 +229,8 @@
         which requires binary compatibility in the signatures.
         """
         from .verifier import Verifier
-        return Verifier(self, source, **kwargs).verify()
+        self.verifier = Verifier(self, source, **kwargs)
+        return self.verifier.load_library()
 
     def _get_errno(self):
         return self._backend.get_errno()
diff --git a/cffi/ffiplatform.py b/cffi/ffiplatform.py
--- a/cffi/ffiplatform.py
+++ b/cffi/ffiplatform.py
@@ -10,15 +10,8 @@
     cdef, but no verification has been done
     """
 
-_file_counter = 0
 _tmpdir = None
 
-def undercffi_module_name():
-    global _file_counter
-    modname = '_cffi_%d' % _file_counter
-    _file_counter += 1
-    return modname
-
 def tmpdir():
     # for now, living in the __pycache__ subdirectory
     global _tmpdir
@@ -31,14 +24,14 @@
     return _tmpdir
 
 
-def compile(tmpdir, modname, **kwds):
+def compile(tmpdir, srcfilename, modname, **kwds):
     """Compile a C extension module using distutils."""
 
     saved_environ = os.environ.copy()
     saved_path = os.getcwd()
     try:
         os.chdir(tmpdir)
-        outputfilename = _build(modname, kwds)
+        outputfilename = _build(srcfilename, modname, kwds)
         outputfilename = os.path.abspath(outputfilename)
     finally:
         os.chdir(saved_path)
@@ -49,12 +42,12 @@
                 os.environ[key] = value
     return outputfilename
 
-def _build(modname, kwds):
+def _build(srcfilename, modname, kwds):
     # XXX compact but horrible :-(
     from distutils.core import Distribution, Extension
     import distutils.errors
     #
-    ext = Extension(name=modname, sources=[modname + '.c'], **kwds)
+    ext = Extension(name=modname, sources=[srcfilename], **kwds)
     dist = Distribution({'ext_modules': [ext]})
     options = dist.get_option_dict('build_ext')
     options['force'] = ('ffiplatform', True)
diff --git a/cffi/verifier.py b/cffi/verifier.py
--- a/cffi/verifier.py
+++ b/cffi/verifier.py
@@ -1,15 +1,86 @@
-import os
+import sys, os, md5, imp, shutil
 from . import model, ffiplatform
+from . import __version__
 
 class Verifier(object):
+    _status = '?'
 
     def __init__(self, ffi, preamble, **kwds):
+        import _cffi_backend
+        if ffi._backend is not _cffi_backend:
+            raise NotImplementedError(
+                "verify() is only available for the _cffi_backend")
+        #
         self.ffi = ffi
         self.preamble = preamble
         self.kwds = kwds
         self._typesdict = {}
         self._need_size = set()
         self._need_size_order = []
+        #
+        m = md5.md5('\x00'.join([sys.version[:3], __version__, preamble] +
+                                ffi._cdefsources))
+        modulename = '_cffi_%s' % m.hexdigest()
+        suffix = self._get_so_suffix()
+        self.modulefilename = os.path.join('__pycache__', modulename + suffix)
+        self.sourcefilename = os.path.join('__pycache__', m.hexdigest() + '.c')
+        self._status = 'init'
+
+    def write_source(self, file=None):
+        """Write the C source code.  It is produced in 'self.sourcefilename',
+        which can be tweaked beforehand."""
+        if self._status == 'init':
+            self._write_source(file)
+        else:
+            raise ffiplatform.VerificationError("source code already written")
+
+    def compile_module(self):
+        """Write the C source code (if not done already) and compile it.
+        This produces a dynamic link library in 'self.modulefilename'."""
+        if self._status == 'init':
+            self._write_source()
+        if self._status == 'source':
+            self._compile_module()
+        else:
+            raise ffiplatform.VerificationError("module already compiled")
+
+    def load_library(self):
+        """Get a C module from this Verifier instance.
+        Returns an instance of a FFILibrary class that behaves like the
+        objects returned by ffi.dlopen(), but that delegates all
+        operations to the C module.  If necessary, the C code is written
+        and compiled first.
+        """
+        if self._status == 'init':       # source code not written yet
+            self._locate_module()
+        if self._status == 'init':
+            self._write_source()
+        if self._status == 'source':
+            self._compile_module()
+        assert self._status == 'module'
+        return self._load_library()
+
+    def getmodulename(self):
+        return os.path.splitext(os.path.basename(self.modulefilename))[0]
+
+    # ----------
+
+    @staticmethod
+    def _get_so_suffix():
+        for suffix, mode, type in imp.get_suffixes():
+            if type == imp.C_EXTENSION:
+                return suffix
+        raise ffiplatform.VerificationError("no C_EXTENSION available")
+
+    def _locate_module(self):
+        try:
+            f, filename, descr = imp.find_module(self.getmodulename())
+        except ImportError:
+            return
+        if f is not None:
+            f.close()
+        self.modulefilename = filename
+        self._status = 'module'
 
     def _prnt(self, what=''):
         print >> self._f, what
@@ -25,21 +96,20 @@
             self._typesdict[type] = num
             return num
 
-    def verify(self):
-        """Produce an extension module, compile it and import it.
-        Then make a fresh FFILibrary class, of which we will return
-        an instance.  Finally, we copy all the API elements from
-        the module to the class or the instance as needed.
-        """
-        import _cffi_backend
-        if self.ffi._backend is not _cffi_backend:
-            raise NotImplementedError(
-                "verify() is only available for the _cffi_backend")
+    def _write_source(self, file=None):
+        must_close = (file is None)
+        if must_close:
+            file = open(self.sourcefilename, 'w')
+        self._f = file
+        try:
+            self._write_source_to_f()
+        finally:
+            del self._f
+            if must_close:
+                file.close()
+        self._status = 'source'
 
-        modname = ffiplatform.undercffi_module_name()
-        tmpdir = ffiplatform.tmpdir()
-        filebase = os.path.join(tmpdir, modname)
-
+    def _write_source_to_f(self):
         # The new module will have a _cffi_setup() function that receives
         # objects from the ffi world, and that calls some setup code in
         # the module.  This setup code is split in several independent
@@ -48,55 +118,66 @@
         # 'chained_list_constants' attribute contains the head of this
         # chained list, as a string that gives the call to do, if any.
         self._chained_list_constants = '0'
+        #
+        prnt = self._prnt
+        # first paste some standard set of lines that are mostly '#define'
+        prnt(cffimod_header)
+        prnt()
+        # then paste the C source given by the user, verbatim.
+        prnt(self.preamble)
+        prnt()
+        #
+        # call generate_cpy_xxx_decl(), for every xxx found from
+        # ffi._parser._declarations.  This generates all the functions.
+        self._generate("decl")
+        #
+        # implement the function _cffi_setup_custom() as calling the
+        # head of the chained list.
+        self._generate_setup_custom()
+        prnt()
+        #
+        # produce the method table, including the entries for the
+        # generated Python->C function wrappers, which are done
+        # by generate_cpy_function_method().
+        prnt('static PyMethodDef _cffi_methods[] = {')
+        self._generate("method")
+        prnt('  {"_cffi_setup", _cffi_setup, METH_VARARGS},')
+        prnt('  {NULL, NULL}    /* Sentinel */')
+        prnt('};')
+        prnt()
+        #
+        # standard init.
+        modname = self.getmodulename()
+        prnt('PyMODINIT_FUNC')
+        prnt('init%s(void)' % modname)
+        prnt('{')
+        prnt('  Py_InitModule("%s", _cffi_methods);' % modname)
+        prnt('  _cffi_init();')
+        prnt('}')
 
-        with open(filebase + '.c', 'w') as f:
-            self._f = f
-            prnt = self._prnt
-            # first paste some standard set of lines that are mostly '#define'
-            prnt(cffimod_header)
-            prnt()
-            # then paste the C source given by the user, verbatim.
-            prnt(self.preamble)
-            prnt()
-            #
-            # call generate_cpy_xxx_decl(), for every xxx found from
-            # ffi._parser._declarations.  This generates all the functions.
-            self._generate("decl")
-            #
-            # implement the function _cffi_setup_custom() as calling the
-            # head of the chained list.
-            self._generate_setup_custom()
-            prnt()
-            #
-            # produce the method table, including the entries for the
-            # generated Python->C function wrappers, which are done
-            # by generate_cpy_function_method().
-            prnt('static PyMethodDef _cffi_methods[] = {')
-            self._generate("method")
-            prnt('  {"_cffi_setup", _cffi_setup, METH_VARARGS},')
-            prnt('  {NULL, NULL}    /* Sentinel */')
-            prnt('};')
-            prnt()
-            #
-            # standard init.
-            prnt('PyMODINIT_FUNC')
-            prnt('init%s(void)' % modname)
-            prnt('{')
-            prnt('  Py_InitModule("%s", _cffi_methods);' % modname)
-            prnt('  _cffi_init();')
-            prnt('}')
-            #
-            del self._f
+    def _compile_module(self):
+        # compile this C source
+        tmpdir = os.path.dirname(self.sourcefilename)
+        sourcename = os.path.basename(self.sourcefilename)
+        modname = self.getmodulename()
+        outputfilename = ffiplatform.compile(tmpdir, sourcename,
+                                             modname, **self.kwds)
+        try:
+            same = os.path.samefile(outputfilename, self.modulefilename)
+        except OSError:
+            same = False
+        if not same:
+            shutil.move(outputfilename, self.modulefilename)
+        self._status = 'module'
 
-        # compile this C source
-        outputfilename = ffiplatform.compile(tmpdir, modname, **self.kwds)
-        #
+    def _load_library(self):
+        # XXX review all usages of 'self' here!
         # import it as a new extension module
-        import imp
         try:
-            module = imp.load_dynamic(modname, outputfilename)
+            module = imp.load_dynamic(self.getmodulename(), self.modulefilename)
         except ImportError, e:
-            raise ffiplatform.VerificationError(str(e))
+            error = "importing %r: %s" % (self.modulefilename, e)
+            raise ffiplatform.VerificationError(error)
         #
         # call loading_cpy_struct() to get the struct layout inferred by
         # the C compiler
diff --git a/testing/test_zdistutils.py b/testing/test_zdistutils.py
--- a/testing/test_zdistutils.py
+++ b/testing/test_zdistutils.py
@@ -42,8 +42,8 @@
     csrc = '/*hi there!*/\n#include <math.h>\n'
     v = Verifier(ffi, csrc)
     v.compile_module()
-    assert v.modulename.startswith('_cffi_')
-    mod = imp.load_dynamic(v.modulename, v.modulefilename)
+    assert v.getmodulename().startswith('_cffi_')
+    mod = imp.load_dynamic(v.getmodulename(), v.modulefilename)
     assert hasattr(mod, '_cffi_setup')
 
 def test_compile_module_explicit_filename():
@@ -51,11 +51,11 @@
     ffi.cdef("double sin(double x);")
     csrc = '/*hi there!2*/\n#include <math.h>\n'
     v = Verifier(ffi, csrc)
-    v.modulefilename = filename = str(udir.join('compile_module.so'))
+    v.modulefilename = filename = str(udir.join('test_compile_module.so'))
     v.compile_module()
     assert filename == v.modulefilename
-    assert v.modulename.startswith('_cffi_')
-    mod = imp.load_dynamic(v.modulename, v.modulefilename)
+    assert v.getmodulename() == 'test_compile_module'
+    mod = imp.load_dynamic(v.getmodulename(), v.modulefilename)
     assert hasattr(mod, '_cffi_setup')
 
 def test_name_from_md5_of_cdef():
@@ -64,7 +64,7 @@
         ffi = FFI()
         ffi.cdef("%s sin(double x);" % csrc)
         v = Verifier(ffi, "#include <math.h>")
-        names.append(v.modulename)
+        names.append(v.getmodulename())
     assert names[0] == names[1] != names[2]
 
 def test_name_from_md5_of_csrc():
@@ -73,7 +73,7 @@
         ffi = FFI()
         ffi.cdef("double sin(double x);")
         v = Verifier(ffi, csrc)
-        names.append(v.modulename)
+        names.append(v.getmodulename())
     assert names[0] == names[1] != names[2]
 
 def test_load_library():
@@ -119,5 +119,5 @@
     ext = v.get_extension()
     assert str(ext.__class__) == 'distutils.extension.Extension'
     assert ext.sources == [v.sourcefilename]
-    assert ext.name == v.modulename
+    assert ext.name == v.getmodulename()
     assert ext.define_macros == [('TEST_EXTENSION_OBJECT', '1')]


More information about the pypy-commit mailing list