Commits

Armin Rigo committed 3d6567a

Add and document some control over the temporary directory.

  • Participants
  • Parent commits 5ce3f01

Comments (0)

Files changed (5)

File cffi/ffiplatform.py

     cdef, but no verification has been done
     """
 
-_tmpdir = None
-
-def tmpdir():
-    # for now, living in the __pycache__ subdirectory
-    global _tmpdir
-    if _tmpdir is None:
-        try:
-            os.mkdir('__pycache__')
-        except OSError:
-            pass
-        _tmpdir = os.path.abspath('__pycache__')
-    return _tmpdir
-
 
 def get_extension(srcfilename, modname, **kwds):
     from distutils.core import Extension

File cffi/verifier.py

 from . import model, ffiplatform
 from . import __version__
 
+
+_TMPDIR = '__pycache__'
+
+def set_tmpdir(dirname):
+    """Set the temporary directory to use instead of __pycache__."""
+    global _TMPDIR
+    _TMPDIR = dirname
+
+def cleanup_tmpdir(keep_so=False):
+    """Clean up the temporary directory by removing all files in it
+    called `_cffi_*.{c,so}` as well as the `build` subdirectory."""
+    try:
+        filelist = os.listdir(_TMPDIR)
+    except OSError:
+        return
+    if keep_so:
+        suffix = '.c'   # only remove .c files
+    else:
+        suffix = _get_so_suffix().lower()
+    for fn in filelist:
+        if fn.lower().startswith('_cffi_') and (
+                fn.lower().endswith(suffix) or fn.lower().endswith('.c')):
+            try:
+                os.unlink(os.path.join(_TMPDIR, fn))
+            except OSError:
+                pass
+    shutil.rmtree(os.path.join(_TMPDIR, 'build'), ignore_errors=True)
+
+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 _ensure_dir(filename):
+    try:
+        os.makedirs(os.path.dirname(filename))
+    except OSError:
+        pass
+
+# ____________________________________________________________
+
 class Verifier(object):
     _status = '?'
 
         m = hashlib.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__', modulename + '.c')
+        suffix = _get_so_suffix()
+        self.sourcefilename = os.path.join(_TMPDIR, modulename + '.c')
+        self.modulefilename = os.path.join(_TMPDIR, modulename + suffix)
         self._status = 'init'
 
     def write_source(self, file=None):
 
     # ----------
 
-    @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.get_module_name())
-        except ImportError:
-            return
-        if f is not None:
-            f.close()
-        self.modulefilename = filename
+        if not os.path.isfile(self.modulefilename):
+            try:
+                f, filename, descr = imp.find_module(self.get_module_name())
+            except ImportError:
+                return
+            if f is not None:
+                f.close()
+            self.modulefilename = filename
         self._collect_types()
         self._status = 'module'
 
     def _write_source(self, file=None):
         must_close = (file is None)
         if must_close:
+            _ensure_dir(self.sourcefilename)
             file = open(self.sourcefilename, 'w')
         self._f = file
         try:
         except OSError:
             same = False
         if not same:
+            _ensure_dir(self.modulefilename)
             shutil.move(outputfilename, self.modulefilename)
         self._status = 'module'
 

File doc/source/index.rst

 Note that the above example works independently of the exact layout of
 ``struct passwd``.  It requires a C compiler the first time you run it,
 unless the module is distributed and installed according to the
-`Distributing modules using CFFI`_ intructions below.
+`Distributing modules using CFFI`_ intructions below.  See also the
+note about `Cleaning up the __pycache__ directory`_.
 
 You will find a number of larger examples using ``verify()`` in the
 `demo`_ directory.
 for more details about the ``verifier`` object.
 
 
+Cleaning up the __pycache__ directory
+-------------------------------------
+
+During development, every time you call ``verify()`` with different
+strings of C source code (either the ``cdef()`` strings or the string
+passed to ```verify()`` itself), then it will create a new module file
+name, based on the MD5 hash of these strings.  This creates more files
+in the ``__pycache__`` directory.  It is recommended that you clean it
+up from time to time.  A nice way to do that is to add, in your test
+suite, a call to ``cffi.verifier.cleanup_tmpdir()``.
+
+
+
 
 =======================================================
 
 
 - ``get_extension)``: returns a distutils-compatible ``Extension`` instance.
 
+The following are global functions in the ``cffi.verifier`` module:
+
+- ``set_tmpdir(dirname)``: sets the temporary directory to use instead of
+  ``__pycache__``.
+  
+- ``cleanup_tmpdir()``: cleans up the temporary directory by removing all
+  files in it called ``_cffi_*.{c,so}`` as well as the ``build``
+  subdirectory.
+
+
+
+
+=================
 
 Comments and bugs
 =================

File testing/test_verify.py

             return super(FFI, self).verify(
                 *args, extra_compile_args=['-Werror'], **kwds)
 
+def setup_module():
+    import cffi.verifier
+    cffi.verifier.cleanup_tmpdir()
+
 
 def test_missing_function():
     ffi = FFI()
     lib.somenumber = -6
     assert lib.foo() == -42
     assert lib.somenumber == -6
+    lib.somenumber = 2   # reset for the next run, if any
 
 def test_access_address_of_variable():
     # access the address of 'somenumber': need a trick
     assert lib.somenumber == 2
     lib.somenumberptr[0] = 42
     assert lib.somenumber == 42
+    lib.somenumber = 2    # reset for the next run, if any
 
 def test_access_array_variable():
     ffi = FFI()
     assert lib.foo(3) == -42
     assert lib.somenumber[3] == -6
     assert lib.somenumber[4] == 5
+    lib.somenumber[3] = 4    # reset for the next run, if any
 
 def test_access_struct_variable():
     ffi = FFI()
     lib.stuff.x = -6
     assert lib.foo(0) == -42
     assert lib.foo(1) == 35
+    lib.stuff.x = 2      # reset for the next run, if any
 
 def test_access_callback():
     ffi = FFI()
     ffi.cdef("int (*cb)(int);\n"
-             "int foo(int);")
+             "int foo(int);\n"
+             "void reset_cb(void);")
     lib = ffi.verify("""
         static int g(int x) { return x * 7; }
-        static int (*cb)(int) = g;
+        static int (*cb)(int);
         static int foo(int i) { return cb(i) - 1; }
+        static void reset_cb(void) { cb = g; }
     """)
+    lib.reset_cb()
     assert lib.foo(6) == 41
     my_callback = ffi.callback("int(*)(int)", lambda n: n * 222)
     lib.cb = my_callback

File testing/test_verify2.py

+from .test_verify import *
+
+# This test file runs normally after test_verify.  We only clean up the .c
+# sources, to check that it also works when we have only the .so.  The
+# tests should run much faster than test_verify.
+
+def setup_module():
+    import cffi.verifier
+    cffi.verifier.cleanup_tmpdir(keep_so=True)