Author: Armin Rigo <[email protected]>
Branch: static-callback-embedding
Changeset: r2499:2075cb3f448f
Date: 2015-12-29 14:33 +0100
http://bitbucket.org/cffi/cffi/changeset/2075cb3f448f/
Log: hg merge default
diff --git a/c/_cffi_backend.c b/c/_cffi_backend.c
--- a/c/_cffi_backend.c
+++ b/c/_cffi_backend.c
@@ -266,18 +266,7 @@
/* whenever running Python code, the errno is saved in this thread-local
variable */
#ifndef MS_WIN32
-# ifdef USE__THREAD
-/* This macro ^^^ is defined by setup.py if it finds that it is
- syntactically valid to use "__thread" with this C compiler. */
-static __thread int cffi_saved_errno = 0;
-static void save_errno(void) { cffi_saved_errno = errno; }
-static void restore_errno(void) { errno = cffi_saved_errno; }
-static void init_errno(void) { }
-# else
-# include "misc_thread.h"
-# endif
-# define save_errno_only save_errno
-# define restore_errno_only restore_errno
+# include "misc_thread_posix.h"
#endif
#include "minibuffer.h"
@@ -290,8 +279,11 @@
# include "wchar_helper.h"
#endif
-typedef PyObject *const cffi_allocator_t[3];
-static cffi_allocator_t default_allocator = { NULL, NULL, NULL };
+typedef struct _cffi_allocator_s {
+ PyObject *ca_alloc, *ca_free;
+ int ca_dont_clear;
+} cffi_allocator_t;
+static const cffi_allocator_t default_allocator = { NULL, NULL, 0 };
static PyObject *FFIError;
static PyObject *unique_cache;
@@ -3030,21 +3022,18 @@
static CDataObject *allocate_with_allocator(Py_ssize_t basesize,
Py_ssize_t datasize,
CTypeDescrObject *ct,
- cffi_allocator_t allocator)
+ const cffi_allocator_t *allocator)
{
CDataObject *cd;
- PyObject *my_alloc = allocator[0];
- PyObject *my_free = allocator[1];
- PyObject *dont_clear_after_alloc = allocator[2];
-
- if (my_alloc == NULL) { /* alloc */
+
+ if (allocator->ca_alloc == NULL) {
cd = allocate_owning_object(basesize + datasize, ct);
if (cd == NULL)
return NULL;
cd->c_data = ((char *)cd) + basesize;
}
else {
- PyObject *res = PyObject_CallFunction(my_alloc, "n", datasize);
+ PyObject *res = PyObject_CallFunction(allocator->ca_alloc, "n",
datasize);
if (res == NULL)
return NULL;
@@ -3069,16 +3058,16 @@
return NULL;
}
- cd = allocate_gcp_object(cd, ct, my_free);
+ cd = allocate_gcp_object(cd, ct, allocator->ca_free);
Py_DECREF(res);
}
- if (dont_clear_after_alloc == NULL)
+ if (!allocator->ca_dont_clear)
memset(cd->c_data, 0, datasize);
return cd;
}
static PyObject *direct_newp(CTypeDescrObject *ct, PyObject *init,
- cffi_allocator_t allocator)
+ const cffi_allocator_t *allocator)
{
CTypeDescrObject *ctitem;
CDataObject *cd;
@@ -3183,7 +3172,7 @@
PyObject *init = Py_None;
if (!PyArg_ParseTuple(args, "O!|O:newp", &CTypeDescr_Type, &ct, &init))
return NULL;
- return direct_newp(ct, init, default_allocator);
+ return direct_newp(ct, init, &default_allocator);
}
static int
@@ -4659,7 +4648,9 @@
if (cif_descr != NULL) {
/* exchange data size */
- cif_descr->exchange_size = exchange_offset;
+ /* we also align it to the next multiple of 8, in an attempt to
+ work around bugs(?) of libffi like #241 */
+ cif_descr->exchange_size = ALIGN_ARG(exchange_offset);
}
return 0;
}
@@ -5101,15 +5092,9 @@
{
save_errno();
{
-#ifdef WITH_THREAD
- PyGILState_STATE state = PyGILState_Ensure();
-#endif
-
+ PyGILState_STATE state = gil_ensure();
general_invoke_callback(1, result, (char *)args, userdata);
-
-#ifdef WITH_THREAD
- PyGILState_Release(state);
-#endif
+ gil_release(state);
}
restore_errno();
}
@@ -6515,7 +6500,7 @@
if (v == NULL || PyModule_AddObject(m, "_C_API", v) < 0)
INITERROR;
- v = PyText_FromString("1.3.1");
+ v = PyText_FromString("1.4.2");
if (v == NULL || PyModule_AddObject(m, "__version__", v) < 0)
INITERROR;
@@ -6542,7 +6527,7 @@
INITERROR;
}
- init_errno();
+ init_cffi_tls();
if (PyErr_Occurred())
INITERROR;
diff --git a/c/call_python.c b/c/call_python.c
--- a/c/call_python.c
+++ b/c/call_python.c
@@ -24,9 +24,6 @@
static PyObject *_ffi_def_extern_decorator(PyObject *outer_args, PyObject *fn)
{
-#if PY_MAJOR_VERSION >= 3
-# error review!
-#endif
char *s;
PyObject *error, *onerror, *infotuple, *old1;
int index, err;
@@ -43,10 +40,10 @@
return NULL;
if (s == NULL) {
- PyObject *name = PyObject_GetAttrString(fn, "__name__");
+ name = PyObject_GetAttrString(fn, "__name__");
if (name == NULL)
return NULL;
- s = PyString_AsString(name);
+ s = PyText_AsUTF8(name);
if (s == NULL) {
Py_DECREF(name);
return NULL;
@@ -203,9 +200,7 @@
err = 1;
}
else {
-#ifdef WITH_THREAD
- PyGILState_STATE state = PyGILState_Ensure();
-#endif
+ PyGILState_STATE state = gil_ensure();
if (externpy->reserved1 != PyThreadState_GET()->interp->modules) {
/* Update the (reserved1, reserved2) cache. This will fail
if we didn't call @ffi.def_extern() in this particular
@@ -215,9 +210,7 @@
if (!err) {
general_invoke_callback(0, args, args, externpy->reserved2);
}
-#ifdef WITH_THREAD
- PyGILState_Release(state);
-#endif
+ gil_release(state);
}
if (err) {
static const char *msg[2] = {
diff --git a/c/cffi1_module.c b/c/cffi1_module.c
--- a/c/cffi1_module.c
+++ b/c/cffi1_module.c
@@ -22,7 +22,7 @@
static int init_ffi_lib(PyObject *m)
{
PyObject *x;
- int i;
+ int i, res;
static char init_done = 0;
if (PyType_Ready(&FFI_Type) < 0)
@@ -48,11 +48,13 @@
for (i = 0; all_dlopen_flags[i].name != NULL; i++) {
x = PyInt_FromLong(all_dlopen_flags[i].value);
- if (x == NULL || PyDict_SetItemString(FFI_Type.tp_dict,
- all_dlopen_flags[i].name,
- x) < 0)
+ if (x == NULL)
return -1;
+ res = PyDict_SetItemString(FFI_Type.tp_dict,
+ all_dlopen_flags[i].name, x);
Py_DECREF(x);
+ if (res < 0)
+ return -1;
}
init_done = 1;
}
diff --git a/c/commontypes.c b/c/commontypes.c
--- a/c/commontypes.c
+++ b/c/commontypes.c
@@ -199,7 +199,8 @@
static PyObject *b__get_common_types(PyObject *self, PyObject *arg)
{
- int i, err;
+ int err;
+ size_t i;
for (i = 0; i < num_common_simple_types; i++) {
const char *s = common_simple_types[i];
PyObject *o = PyText_FromString(s + strlen(s) + 1);
diff --git a/c/ffi_obj.c b/c/ffi_obj.c
--- a/c/ffi_obj.c
+++ b/c/ffi_obj.c
@@ -335,7 +335,7 @@
"pointer to the memory somewhere else, e.g. into another structure.");
static PyObject *_ffi_new(FFIObject *self, PyObject *args, PyObject *kwds,
- cffi_allocator_t allocator)
+ const cffi_allocator_t *allocator)
{
CTypeDescrObject *ct;
PyObject *arg, *init = Py_None;
@@ -353,15 +353,22 @@
static PyObject *ffi_new(FFIObject *self, PyObject *args, PyObject *kwds)
{
- return _ffi_new(self, args, kwds, default_allocator);
+ return _ffi_new(self, args, kwds, &default_allocator);
}
static PyObject *_ffi_new_with_allocator(PyObject *allocator, PyObject *args,
PyObject *kwds)
{
+ cffi_allocator_t alloc1;
+ PyObject *my_alloc, *my_free;
+ my_alloc = PyTuple_GET_ITEM(allocator, 1);
+ my_free = PyTuple_GET_ITEM(allocator, 2);
+ alloc1.ca_alloc = (my_alloc == Py_None ? NULL : my_alloc);
+ alloc1.ca_free = (my_free == Py_None ? NULL : my_free);
+ alloc1.ca_dont_clear = (PyTuple_GET_ITEM(allocator, 3) == Py_False);
+
return _ffi_new((FFIObject *)PyTuple_GET_ITEM(allocator, 0),
- args, kwds,
- &PyTuple_GET_ITEM(allocator, 1));
+ args, kwds, &alloc1);
}
PyDoc_STRVAR(ffi_new_allocator_doc,
@@ -396,27 +403,14 @@
return NULL;
}
- allocator = PyTuple_New(4);
+ allocator = PyTuple_Pack(4,
+ (PyObject *)self,
+ my_alloc,
+ my_free,
+ PyBool_FromLong(should_clear_after_alloc));
if (allocator == NULL)
return NULL;
- Py_INCREF(self);
- PyTuple_SET_ITEM(allocator, 0, (PyObject *)self);
-
- if (my_alloc != Py_None) {
- Py_INCREF(my_alloc);
- PyTuple_SET_ITEM(allocator, 1, my_alloc);
- }
- if (my_free != Py_None) {
- Py_INCREF(my_free);
- PyTuple_SET_ITEM(allocator, 2, my_free);
- }
- if (!should_clear_after_alloc) {
- PyObject *my_true = Py_True;
- Py_INCREF(my_true);
- PyTuple_SET_ITEM(allocator, 3, my_true); /* dont_clear_after_alloc */
- }
-
{
static PyMethodDef md = {"allocator",
(PyCFunction)_ffi_new_with_allocator,
@@ -896,7 +890,14 @@
#endif
PyDoc_STRVAR(ffi_init_once_doc,
- "XXX document me");
+"init_once(function, tag): run function() once. More precisely,\n"
+"'function()' is called the first time we see a given 'tag'.\n"
+"\n"
+"The return value of function() is remembered and returned by the current\n"
+"and all future init_once() with the same tag. If init_once() is called\n"
+"from multiple threads in parallel, all calls block until the execution\n"
+"of function() is done. If function() raises an exception, it is\n"
+"propagated and nothing is cached.");
#if PY_MAJOR_VERSION < 3
/* PyCapsule_New is redefined to be PyCObject_FromVoidPtr in _cffi_backend,
diff --git a/c/lib_obj.c b/c/lib_obj.c
--- a/c/lib_obj.c
+++ b/c/lib_obj.c
@@ -459,6 +459,7 @@
static PyObject *lib_getattr(LibObject *lib, PyObject *name)
{
+ char *p;
PyObject *x;
LIB_GET_OR_CACHE_ADDR(x, lib, name, goto missing);
@@ -469,16 +470,25 @@
return x;
missing:
- if (strcmp(PyText_AsUTF8(name), "__all__") == 0) {
+ p = PyText_AsUTF8(name);
+ if (p == NULL)
+ return NULL;
+ if (strcmp(p, "__all__") == 0) {
PyErr_Clear();
return _lib_dir1(lib, 1);
}
- if (strcmp(PyText_AsUTF8(name), "__dict__") == 0) {
+ if (strcmp(p, "__dict__") == 0) {
PyErr_Clear();
return _lib_dict(lib);
}
+ if (strcmp(p, "__class__") == 0) {
+ PyErr_Clear();
+ x = (PyObject *)Py_TYPE(lib);
+ Py_INCREF(x);
+ return x;
+ }
/* this hack is for Python 3.5 */
- if (strcmp(PyText_AsUTF8(name), "__name__") == 0) {
+ if (strcmp(p, "__name__") == 0) {
PyErr_Clear();
return lib_repr(lib);
}
diff --git a/c/misc_thread.h b/c/misc_thread.h
deleted file mode 100644
--- a/c/misc_thread.h
+++ /dev/null
@@ -1,23 +0,0 @@
-#include <pthread.h>
-
-/* This is only included if GCC doesn't support "__thread" global variables.
- * See USE__THREAD in _ffi_backend.c.
- */
-
-static pthread_key_t cffi_tls_key;
-
-static void init_errno(void)
-{
- (void) pthread_key_create(&cffi_tls_key, NULL);
-}
-
-static void save_errno(void)
-{
- intptr_t value = errno;
- (void) pthread_setspecific(cffi_tls_key, (void *)value);
-}
-
-static void restore_errno(void) {
- intptr_t value = (intptr_t)pthread_getspecific(cffi_tls_key);
- errno = value;
-}
diff --git a/c/misc_thread_posix.h b/c/misc_thread_posix.h
new file mode 100644
--- /dev/null
+++ b/c/misc_thread_posix.h
@@ -0,0 +1,186 @@
+/*
+ Logic for a better replacement of PyGILState_Ensure().
+
+ This version is ready to handle the case of a non-Python-started
+ thread in which we do a large number of calls to CFFI callbacks. If
+ we were to rely on PyGILState_Ensure() for that, we would constantly
+ be creating and destroying PyThreadStates---it is slow, and
+ PyThreadState_Delete() will actually walk the list of all thread
+ states, making it O(n). :-(
+
+ This version only creates one PyThreadState object the first time we
+ see a given thread, and keep it alive until the thread is really
+ shut down, using a destructor on the tls key.
+*/
+
+#ifdef WITH_THREAD
+#include <pthread.h>
+
+
+static pthread_key_t cffi_tls_key;
+
+struct cffi_tls_s {
+ /* The locally-made thread state. This is only non-null in case
+ we build the thread state here. It remains null if this thread
+ had already a thread state provided by CPython. */
+ PyThreadState *local_thread_state;
+
+ /* The saved errno. If the C compiler supports '__thread', then
+ we use that instead; this value is not used at all in this case. */
+ int saved_errno;
+};
+
+static void _tls_destructor(void *p)
+{
+ struct cffi_tls_s *tls = (struct cffi_tls_s *)p;
+
+ if (tls->local_thread_state != NULL) {
+ /* We need to re-acquire the GIL temporarily to free the
+ thread state. I hope it is not a problem to do it in
+ a thread-local destructor.
+ */
+ PyEval_RestoreThread(tls->local_thread_state);
+ PyThreadState_DeleteCurrent();
+ }
+ free(tls);
+}
+
+static void init_cffi_tls(void)
+{
+ if (pthread_key_create(&cffi_tls_key, _tls_destructor) != 0)
+ PyErr_SetString(PyExc_OSError, "pthread_key_create() failed");
+}
+
+static struct cffi_tls_s *_make_cffi_tls(void)
+{
+ void *p = calloc(1, sizeof(struct cffi_tls_s));
+ if (p == NULL)
+ return NULL;
+ if (pthread_setspecific(cffi_tls_key, p) != 0) {
+ free(p);
+ return NULL;
+ }
+ return p;
+}
+
+static struct cffi_tls_s *get_cffi_tls(void)
+{
+ void *p = pthread_getspecific(cffi_tls_key);
+ if (p == NULL)
+ p = _make_cffi_tls();
+ return (struct cffi_tls_s *)p;
+}
+
+
+/* USE__THREAD is defined by setup.py if it finds that it is
+ syntactically valid to use "__thread" with this C compiler. */
+#ifdef USE__THREAD
+
+static __thread int cffi_saved_errno = 0;
+static void save_errno(void) { cffi_saved_errno = errno; }
+static void restore_errno(void) { errno = cffi_saved_errno; }
+
+#else
+
+static void save_errno(void)
+{
+ int saved = errno;
+ struct cffi_tls_s *tls = get_cffi_tls();
+ if (tls != NULL)
+ tls->saved_errno = saved;
+}
+
+static void restore_errno(void)
+{
+ struct cffi_tls_s *tls = get_cffi_tls();
+ if (tls != NULL)
+ errno = tls->saved_errno;
+}
+
+#endif
+
+
+/* Seems that CPython 3.5.1 made our job harder. Did not find out how
+ to do that without these hacks. We can't use PyThreadState_GET(),
+ because that calls PyThreadState_Get() which fails an assert if the
+ result is NULL. */
+#if PY_MAJOR_VERSION >= 3 && !defined(_Py_atomic_load_relaxed)
+ /* this was abruptly un-defined in 3.5.1 */
+void *volatile _PyThreadState_Current;
+ /* XXX simple volatile access is assumed atomic */
+# define _Py_atomic_load_relaxed(pp) (*(pp))
+#endif
+
+
+static PyThreadState *get_current_ts(void)
+{
+#if PY_MAJOR_VERSION >= 3
+ return (PyThreadState*)_Py_atomic_load_relaxed(&_PyThreadState_Current);
+#else
+ return _PyThreadState_Current;
+#endif
+}
+
+static PyGILState_STATE gil_ensure(void)
+{
+ /* Called at the start of a callback. Replacement for
+ PyGILState_Ensure().
+ */
+ PyGILState_STATE result;
+ struct cffi_tls_s *tls;
+ PyThreadState *ts = PyGILState_GetThisThreadState();
+
+ if (ts != NULL) {
+ ts->gilstate_counter++;
+ if (ts != get_current_ts()) {
+ /* common case: 'ts' is our non-current thread state and
+ we have to make it current and acquire the GIL */
+ PyEval_RestoreThread(ts);
+ return PyGILState_UNLOCKED;
+ }
+ else {
+ return PyGILState_LOCKED;
+ }
+ }
+ else {
+ /* no thread state here so far. */
+ result = PyGILState_Ensure();
+ assert(result == PyGILState_UNLOCKED);
+
+ ts = PyGILState_GetThisThreadState();
+ assert(ts != NULL);
+ assert(ts == get_current_ts());
+ assert(ts->gilstate_counter >= 1);
+
+ /* Save the now-current thread state inside our 'local_thread_state'
+ field, to be removed at thread shutdown */
+ tls = get_cffi_tls();
+ if (tls != NULL) {
+ tls->local_thread_state = ts;
+ ts->gilstate_counter++;
+ }
+
+ return result;
+ }
+}
+
+static void gil_release(PyGILState_STATE oldstate)
+{
+ PyGILState_Release(oldstate);
+}
+
+
+#else /* !WITH_THREAD */
+
+static int cffi_saved_errno = 0;
+static void save_errno(void) { cffi_saved_errno = errno; }
+static void restore_errno(void) { errno = cffi_saved_errno; }
+
+static PyGILState_STATE gil_ensure(void) { return -1; }
+static void gil_release(PyGILState_STATE oldstate) { }
+
+#endif /* !WITH_THREAD */
+
+
+#define save_errno_only save_errno
+#define restore_errno_only restore_errno
diff --git a/c/misc_win32.h b/c/misc_win32.h
--- a/c/misc_win32.h
+++ b/c/misc_win32.h
@@ -10,7 +10,7 @@
static DWORD cffi_tls_index = TLS_OUT_OF_INDEXES;
-static void init_errno(void)
+static void init_cffi_tls(void)
{
if (cffi_tls_index == TLS_OUT_OF_INDEXES) {
cffi_tls_index = TlsAlloc();
@@ -182,6 +182,17 @@
}
#endif
+
+#ifdef WITH_THREAD
+/* XXX should port the code from misc_thread_posix.h */
+static PyGILState_STATE gil_ensure(void) { return PyGILState_Ensure(); }
+static void gil_release(PyGILState_STATE oldst) { PyGILState_Release(oldst); }
+#else
+static PyGILState_STATE gil_ensure(void) { return -1; }
+static void gil_release(PyGILState_STATE oldstate) { }
+#endif
+
+
/************************************************************/
/* Emulate dlopen()&co. from the Windows API */
diff --git a/c/test_c.py b/c/test_c.py
--- a/c/test_c.py
+++ b/c/test_c.py
@@ -12,7 +12,7 @@
# ____________________________________________________________
import sys
-assert __version__ == "1.3.1", ("This test_c.py file is for testing a version"
+assert __version__ == "1.4.2", ("This test_c.py file is for testing a version"
" of cffi that differs from the one that we"
" get from 'import _cffi_backend'")
if sys.version_info < (3,):
diff --git a/cffi/__init__.py b/cffi/__init__.py
--- a/cffi/__init__.py
+++ b/cffi/__init__.py
@@ -4,8 +4,8 @@
from .api import FFI, CDefError, FFIError
from .ffiplatform import VerificationError, VerificationMissing
-__version__ = "1.3.1"
-__version_info__ = (1, 3, 1)
+__version__ = "1.4.2"
+__version_info__ = (1, 4, 2)
# The verifier module file names are based on the CRC32 of a string that
# contains the following version number. It may be older than __version__
diff --git a/cffi/api.py b/cffi/api.py
--- a/cffi/api.py
+++ b/cffi/api.py
@@ -73,6 +73,7 @@
self._included_ffis = []
self._windows_unicode = None
self._init_once_cache = {}
+ self._cdef_version = None
self._embedding_init_code = None
if hasattr(backend, 'set_ffi'):
backend.set_ffi(self)
@@ -106,6 +107,7 @@
raise TypeError("cdef() argument must be a string")
csource = csource.encode('ascii')
with self._lock:
+ self._cdef_version = object()
self._parser.parse(csource, override=override, packed=packed,
dllexport=dllexport)
self._cdefsources.append(csource)
@@ -592,14 +594,15 @@
recompile(self, module_name, source,
c_file=filename, call_c_compiler=False, **kwds)
- def compile(self, tmpdir='.'):
+ def compile(self, tmpdir='.', verbose=0):
from .recompiler import recompile
#
if not hasattr(self, '_assigned_source'):
raise ValueError("set_source() must be called before compile()")
module_name, source, source_extension, kwds = self._assigned_source
return recompile(self, module_name, source, tmpdir=tmpdir,
- source_extension=source_extension, **kwds)
+ source_extension=source_extension,
+ compiler_verbose=verbose, **kwds)
def init_once(self, func, tag):
# Read _init_once_cache[tag], which is either (False, lock) if
@@ -660,70 +663,70 @@
import os
backend = ffi._backend
backendlib = _load_backend_lib(backend, libname, flags)
- copied_enums = []
#
- def make_accessor_locked(name):
+ def accessor_function(name):
key = 'function ' + name
- if key in ffi._parser._declarations:
- tp, _ = ffi._parser._declarations[key]
- BType = ffi._get_cached_btype(tp)
- try:
- value = backendlib.load_function(BType, name)
- except KeyError as e:
- raise AttributeError('%s: %s' % (name, e))
- library.__dict__[name] = value
+ tp, _ = ffi._parser._declarations[key]
+ BType = ffi._get_cached_btype(tp)
+ try:
+ value = backendlib.load_function(BType, name)
+ except KeyError as e:
+ raise AttributeError('%s: %s' % (name, e))
+ library.__dict__[name] = value
+ #
+ def accessor_variable(name):
+ key = 'variable ' + name
+ tp, _ = ffi._parser._declarations[key]
+ BType = ffi._get_cached_btype(tp)
+ read_variable = backendlib.read_variable
+ write_variable = backendlib.write_variable
+ setattr(FFILibrary, name, property(
+ lambda self: read_variable(BType, name),
+ lambda self, value: write_variable(BType, name, value)))
+ #
+ def accessor_constant(name):
+ raise NotImplementedError("non-integer constant '%s' cannot be "
+ "accessed from a dlopen() library" % (name,))
+ #
+ def accessor_int_constant(name):
+ library.__dict__[name] = ffi._parser._int_constants[name]
+ #
+ accessors = {}
+ accessors_version = [False]
+ #
+ def update_accessors():
+ if accessors_version[0] is ffi._cdef_version:
return
#
- key = 'variable ' + name
- if key in ffi._parser._declarations:
- tp, _ = ffi._parser._declarations[key]
- BType = ffi._get_cached_btype(tp)
- read_variable = backendlib.read_variable
- write_variable = backendlib.write_variable
- setattr(FFILibrary, name, property(
- lambda self: read_variable(BType, name),
- lambda self, value: write_variable(BType, name, value)))
- return
- #
- if not copied_enums:
- from . import model
- error = None
- for key, (tp, _) in ffi._parser._declarations.items():
- if not isinstance(tp, model.EnumType):
- continue
- try:
- tp.check_not_partial()
- except Exception as e:
- error = e
- continue
- for enumname, enumval in zip(tp.enumerators, tp.enumvalues):
- if enumname not in library.__dict__:
- library.__dict__[enumname] = enumval
- if error is not None:
- if name in library.__dict__:
- return # ignore error, about a different enum
- raise error
-
- for key, val in ffi._parser._int_constants.items():
- if key not in library.__dict__:
- library.__dict__[key] = val
-
- copied_enums.append(True)
- if name in library.__dict__:
- return
- #
- key = 'constant ' + name
- if key in ffi._parser._declarations:
- raise NotImplementedError("fetching a non-integer constant "
- "after dlopen()")
- #
- raise AttributeError(name)
+ from . import model
+ for key, (tp, _) in ffi._parser._declarations.items():
+ if not isinstance(tp, model.EnumType):
+ tag, name = key.split(' ', 1)
+ if tag == 'function':
+ accessors[name] = accessor_function
+ elif tag == 'variable':
+ accessors[name] = accessor_variable
+ elif tag == 'constant':
+ accessors[name] = accessor_constant
+ else:
+ for i, enumname in enumerate(tp.enumerators):
+ def accessor_enum(name, tp=tp, i=i):
+ tp.check_not_partial()
+ library.__dict__[name] = tp.enumvalues[i]
+ accessors[enumname] = accessor_enum
+ for name in ffi._parser._int_constants:
+ accessors.setdefault(name, accessor_int_constant)
+ accessors_version[0] = ffi._cdef_version
#
def make_accessor(name):
with ffi._lock:
if name in library.__dict__ or name in FFILibrary.__dict__:
return # added by another thread while waiting for the lock
- make_accessor_locked(name)
+ if name not in accessors:
+ update_accessors()
+ if name not in accessors:
+ raise AttributeError(name)
+ accessors[name](name)
#
class FFILibrary(object):
def __getattr__(self, name):
@@ -737,6 +740,10 @@
setattr(self, name, value)
else:
property.__set__(self, value)
+ def __dir__(self):
+ with ffi._lock:
+ update_accessors()
+ return accessors.keys()
#
if libname is not None:
try:
diff --git a/cffi/ffiplatform.py b/cffi/ffiplatform.py
--- a/cffi/ffiplatform.py
+++ b/cffi/ffiplatform.py
@@ -17,15 +17,16 @@
def get_extension(srcfilename, modname, sources=(), **kwds):
from distutils.core import Extension
allsources = [srcfilename]
- allsources.extend(sources)
+ for src in sources:
+ allsources.append(os.path.normpath(src))
return Extension(name=modname, sources=allsources, **kwds)
-def compile(tmpdir, ext):
+def compile(tmpdir, ext, compiler_verbose=0):
"""Compile a C extension module using distutils."""
saved_environ = os.environ.copy()
try:
- outputfilename = _build(tmpdir, ext)
+ outputfilename = _build(tmpdir, ext, compiler_verbose)
outputfilename = os.path.abspath(outputfilename)
finally:
# workaround for a distutils bugs where some env vars can
@@ -35,10 +36,10 @@
os.environ[key] = value
return outputfilename
-def _build(tmpdir, ext):
+def _build(tmpdir, ext, compiler_verbose=0):
# XXX compact but horrible :-(
from distutils.core import Distribution
- import distutils.errors
+ import distutils.errors, distutils.log
#
dist = Distribution({'ext_modules': [ext]})
dist.parse_config_files()
@@ -48,7 +49,12 @@
options['build_temp'] = ('ffiplatform', tmpdir)
#
try:
- dist.run_command('build_ext')
+ old_level = distutils.log.set_threshold(0) or 0
+ try:
+ distutils.log.set_verbosity(compiler_verbose)
+ dist.run_command('build_ext')
+ finally:
+ distutils.log.set_threshold(old_level)
except (distutils.errors.CompileError,
distutils.errors.LinkError) as e:
raise VerificationError('%s: %s' % (e.__class__.__name__, e))
diff --git a/cffi/recompiler.py b/cffi/recompiler.py
--- a/cffi/recompiler.py
+++ b/cffi/recompiler.py
@@ -1354,7 +1354,8 @@
return os.path.join(outputdir, *parts), parts
def recompile(ffi, module_name, preamble, tmpdir='.', call_c_compiler=True,
- c_file=None, source_extension='.c', extradir=None, **kwds):
+ c_file=None, source_extension='.c', extradir=None,
+ compiler_verbose=1, **kwds):
if not isinstance(module_name, str):
module_name = module_name.encode('ascii')
if ffi._windows_unicode:
@@ -1374,7 +1375,7 @@
cwd = os.getcwd()
try:
os.chdir(tmpdir)
- outputfilename = ffiplatform.compile('.', ext)
+ outputfilename = ffiplatform.compile('.', ext,
compiler_verbose)
finally:
os.chdir(cwd)
return outputfilename
diff --git a/doc/source/cdef.rst b/doc/source/cdef.rst
--- a/doc/source/cdef.rst
+++ b/doc/source/cdef.rst
@@ -62,7 +62,7 @@
ffi.cdef("C-like declarations with '...'")
if __name__ == "__main__":
- ffi.compile()
+ ffi.compile(verbose=True)
Running ``python foo_build.py`` produces a file ``_foo.c`` and
invokes the C compiler to turn it into a file ``_foo.so`` (or
@@ -134,7 +134,8 @@
Similarly, the ``lib`` objects returned by the C version are read-only,
apart from writes to global variables. Also, ``lib.__dict__`` does
not work before version 1.2 or if ``lib`` happens to declare a name
-called ``__dict__`` (use instead ``dir(lib)``).
+called ``__dict__`` (use instead ``dir(lib)``). The same is true
+for ``lib.__class__`` before version 1.4.
ffi.cdef(): declaring types and functions
@@ -504,7 +505,8 @@
the mtime to be updated anyway, delete the file before calling the
functions.
-**ffi.compile(tmpdir='.'):** explicitly generate the .py or .c file,
+**ffi.compile(tmpdir='.', verbose=False):**
+explicitly generate the .py or .c file,
and (if .c) compile it. The output file is (or are) put in the
directory given by ``tmpdir``. In the examples given here, we use
``if __name__ == "__main__": ffi.compile()`` in the build scripts---if
@@ -513,6 +515,11 @@
to ``set_source()``, then a corresponding subdirectory of the ``tmpdir``
is used.)
+*New in version 1.4:* ``verbose`` argument. If True, it prints the
+usual distutils output, including the command lines that call the
+compiler. (This parameter might be changed to True by default in a
+future release.)
+
**ffi.emit_python_code(filename):** generate the given .py file (same
as ``ffi.compile()`` for ABI mode, with an explicitly-named file to
write). If you choose, you can include this .py file pre-packaged in
diff --git a/doc/source/conf.py b/doc/source/conf.py
--- a/doc/source/conf.py
+++ b/doc/source/conf.py
@@ -45,9 +45,9 @@
# built documents.
#
# The short X.Y version.
-version = '1.3'
+version = '1.4'
# The full version, including alpha/beta/rc tags.
-release = '1.3.1'
+release = '1.4.2'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
diff --git a/doc/source/installation.rst b/doc/source/installation.rst
--- a/doc/source/installation.rst
+++ b/doc/source/installation.rst
@@ -51,11 +51,11 @@
Download and Installation:
-* http://pypi.python.org/packages/source/c/cffi/cffi-1.3.1.tar.gz
+* http://pypi.python.org/packages/source/c/cffi/cffi-1.4.2.tar.gz
- - MD5: ...
+ - MD5: 81357fe5042d00650b85b728cc181df2
- - SHA: ...
+ - SHA: 76cff6f1ff5bfb2b9c6c8e2cfa8bf90b5c944394
* Or grab the most current version from the `Bitbucket page`_:
``hg clone https://bitbucket.org/cffi/cffi``
diff --git a/doc/source/using.rst b/doc/source/using.rst
--- a/doc/source/using.rst
+++ b/doc/source/using.rst
@@ -433,11 +433,11 @@
out-of-line API mode. The next section about Callbacks_ describes the
ABI-mode solution.
-This is *new in version 1.4.* Use Callbacks_ if backward compatibility
-is an issue. (The original callbacks are slower to invoke and have
-the same issue as libffi's callbacks; notably, see the warning__.
-The new style described in the present section does not use libffi's
-callbacks at all.)
+This is *new in version 1.4.* Use old-style Callbacks_ if backward
+compatibility is an issue. (The original callbacks are slower to
+invoke and have the same issue as libffi's callbacks; notably, see the
+warning__. The new style described in the present section does not
+use libffi's callbacks at all.)
.. __: Callbacks_
@@ -459,33 +459,35 @@
from _my_example import ffi, lib
@ffi.def_extern()
- def my_callback(fooptr, value):
+ def my_callback(x, y):
return 42
-You can get a ``<cdata>`` pointer-to-function object from
+You obtain a ``<cdata>`` pointer-to-function object by getting
``lib.my_callback``. This ``<cdata>`` can be passed to C code and
then works like a callback: when the C code calls this function
pointer, the Python function ``my_callback`` is called. (You need
to pass ``lib.my_callback`` to C code, and not ``my_callback``: the
-latter is just a plain Python function that cannot be passed to C.)
+latter is just the Python function above, which cannot be passed to C.)
CFFI implements this by defining ``my_callback`` as a static C
function, written after the ``set_source()`` code. The ``<cdata>``
then points to this function. What this function does is invoke the
-Python function object that was dynamically attached by
+Python function object that is, at runtime, attached with
``@ffi.def_extern()``.
-Each function from the cdef with ``extern "Python"`` turns into only
-one C function. To support some corner cases, it is possible to
-redefine the attached Python function by calling ``@ffi.def_extern()``
-again---but this is not recommended! Better write the Python function
-more flexibly in the first place. Calling ``@ffi.def_extern()`` again
-changes the C logic to call the new Python function; the old Python
-function is not callable any more and the C function pointer you get
-from ``lib.my_function`` is always the same.
+The ``@ffi.def_extern()`` decorator should be applied to a global
+function, once. This is because each function from the cdef with
+``extern "Python"`` turns into only one C function. To support some
+corner cases, it is possible to redefine the attached Python function
+by calling ``@ffi.def_extern()`` again---but this is not recommended!
+Better write the single global Python function more flexibly in the
+first place. Calling ``@ffi.def_extern()`` again changes the C logic
+to call the new Python function; the old Python function is not
+callable any more and the C function pointer you get from
+``lib.my_function`` is always the same.
-Extern "Python" and "void *" arguments
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Extern "Python" and ``void *`` arguments
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
As described just before, you cannot use ``extern "Python"`` to make a
variable number of C function pointers. However, achieving that
@@ -567,11 +569,13 @@
In case you want to access some ``extern "Python"`` function directly
from the C code written in ``set_source()``, you need to write a
forward static declaration. The real implementation of this function
-is added by CFFI *after* the C code---this is needed because even the
+is added by CFFI *after* the C code---this is needed because the
declaration might use types defined by ``set_source()``
(e.g. ``event_t`` above, from the ``#include``), so it cannot be
generated before.
+::
+
ffi.set_source("_demo_cffi", """
#include <the_event_library.h>
@@ -589,11 +593,12 @@
int my_algo(int);
""")
ffi.set_source("_example_cffi", """
- static int f(int);
+ static int f(int); /* the forward declaration */
+
static int my_algo(int n) {
int i, sum = 0;
for (i = 0; i < n; i++)
- sum += f(i);
+ sum += f(i); /* call f() here */
return sum;
}
""")
@@ -705,7 +710,28 @@
keep this object alive for as long as the callback may be invoked.
The easiest way to do that is to always use ``@ffi.callback()`` at
module-level only, and to pass "context" information around with
-`ffi.new_handle()`_, if possible.
+`ffi.new_handle()`_, if possible. Example:
+
+.. code-block:: python
+
+ # a good way to use this decorator is once at global level
+ @ffi.callback("int(int, void *)")
+ def my_global_callback(x, handle):
+ return ffi.from_handle(handle).some_method(x)
+
+
+ class Foo(object):
+
+ def __init__(self):
+ handle = ffi.new_handle(self)
+ self._handle = handle # must be kept alive
+ lib.register_stuff_with_callback_and_voidp_arg(my_global_callback,
handle)
+
+ def some_method(self, x):
+ ...
+
+(See also the section about `extern "Python"`_ above, where the same
+general style is used.)
Note that callbacks of a variadic function type are not supported. A
workaround is to add custom C code. In the following example, a
@@ -797,7 +823,7 @@
hurt) to say ``WINAPI`` or ``__stdcall`` when declaring a plain
function in the ``cdef()``. (The difference can still be seen if you
take explicitly a pointer to this function with ``ffi.addressof()``,
-or if the function is ``CFFI_CALL_PYTHON``.)
+or if the function is ``extern "Python"``.)
These calling convention specifiers are accepted but ignored on any
platform other than 32-bit Windows.
@@ -1139,6 +1165,58 @@
returned by ``alloc()`` is assumed to be already cleared (or you are
fine with garbage); otherwise CFFI will clear it.
+.. _initonce:
+
+**ffi.init_once(function, tag)**: run ``function()`` once. The
+``tag`` should be a primitive object, like a string, that identifies
+the function: ``function()`` is only called the first time we see the
+``tag``. The return value of ``function()`` is remembered and
+returned by the current and all future ``init_once()`` with the same
+tag. If ``init_once()`` is called from multiple threads in parallel,
+all calls block until the execution of ``function()`` is done. If
+``function()`` raises an exception, it is propagated and nothing is
+cached (i.e. ``function()`` will be called again, in case we catch the
+exception and try ``init_once()`` again). *New in version 1.4.*
+
+Example::
+
+ from _xyz_cffi import ffi, lib
+
+ def initlib():
+ lib.init_my_library()
+
+ def make_new_foo():
+ ffi.init_once(initlib, "init")
+ return lib.make_foo()
+
+``init_once()`` is optimized to run very quickly if ``function()`` has
+already been called. (On PyPy, the cost is zero---the JIT usually
+removes everything in the machine code it produces.)
+
+*Note:* one motivation__ for ``init_once()`` is the CPython notion of
+"subinterpreters" in the embedded case. If you are using the
+out-of-line API mode, ``function()`` is called only once even in the
+presence of multiple subinterpreters, and its return value is shared
+among all subinterpreters. The goal is to mimic the way traditional
+CPython C extension modules have their init code executed only once in
+total even if there are subinterpreters. In the example above, the C
+function ``init_my_library()`` is called once in total, not once per
+subinterpreter. For this reason, avoid Python-level side-effects in
+``function()`` (as they will only be applied in the first
+subinterpreter to run); instead, return a value, as in the following
+example::
+
+ def init_get_max():
+ return lib.initialize_once_and_get_some_maximum_number()
+
+ def process(i):
+ if i > ffi.init_once(init_get_max, "max"):
+ raise IndexError("index too large!")
+ ...
+
+.. __: https://bitbucket.org/cffi/cffi/issues/233/
+
+
.. _`Preparing and Distributing modules`: cdef.html#loading-libraries
@@ -1233,8 +1311,8 @@
function with a ``char *`` argument to which you pass a Python
string will not actually modify the array of characters passed in,
and so passes directly a pointer inside the Python string object.
- (PyPy might in the future do the same, but it is harder because a
- string object can move in memory when the GC runs.)
+ (PyPy might in the future do the same, but it is harder because
+ strings are not naturally zero-terminated in PyPy.)
`(**)` C function calls are done with the GIL released.
diff --git a/doc/source/whatsnew.rst b/doc/source/whatsnew.rst
--- a/doc/source/whatsnew.rst
+++ b/doc/source/whatsnew.rst
@@ -3,18 +3,61 @@
======================
+v1.4.2
+======
+
+Nothing changed from v1.4.1.
+
+
+v1.4.1
+======
+
+* Fix the compilation failure of cffi on CPython 3.5.0. (3.5.1 works;
+ some detail changed that makes some underscore-starting macros
+ disappear from view of extension modules, and I worked around it,
+ thinking it changed in all 3.5 versions---but no: it was only in
+ 3.5.1.)
+
+
v1.4.0
======
+* A `better way to do callbacks`__ has been added (faster and more
+ portable, and usually cleaner). It is a mechanism for the
+ out-of-line API mode that replaces the dynamic creation of callback
+ objects (i.e. C functions that invoke Python) with the static
+ declaration in ``cdef()`` of which callbacks are needed. This is
+ more C-like, in that you have to structure your code around the idea
+ that you get a fixed number of function pointers, instead of
+ creating them on-the-fly.
+
+* ``ffi.compile()`` now takes an optional ``verbose`` argument. When
+ ``True``, distutils prints the calls to the compiler.
+
+* ``ffi.compile()`` used to fail if given ``sources`` with a path that
+ includes ``".."``. Fixed.
+
+* ``ffi.init_once()`` added. See docs__.
+
+* ``dir(lib)`` now works on libs returned by ``ffi.dlopen()`` too.
+
+* Cleaned up and modernized the content of the ``demo`` subdirectory
+ in the sources (thanks matti!).
+
* ``ffi.new_handle()`` is now guaranteed to return unique ``void *``
values, even if called twice on the same object. Previously, in
- that case, CPython (but not PyPy) would return two ``cdata`` objects
- with the same ``void *`` value. This change is useful to add and
- remove handles from a global dict or set without worrying about
- duplicates.
+ that case, CPython would return two ``cdata`` objects with the same
+ ``void *`` value. This change is useful to add and remove handles
+ from a global dict (or set) without worrying about duplicates.
+ It already used to work like that on PyPy.
+ *This change can break code that used to work on CPython by relying
+ on the object to be kept alive by other means than keeping the
+ result of ffi.new_handle() alive.* (The corresponding `warning in
+ the docs`__ of ``ffi.new_handle()`` has been here since v0.8!)
-* ``ffi.init_once()`` XXX
- https://bitbucket.org/cffi/cffi/issues/233/
+.. __: using.html#extern-python
+.. __: using.html#initonce
+.. __: using.html#ffi-new-handle
v1.3.1
diff --git a/setup.py b/setup.py
--- a/setup.py
+++ b/setup.py
@@ -144,7 +144,7 @@
`Mailing list <https://groups.google.com/forum/#!forum/python-cffi>`_
""",
- version='1.3.1',
+ version='1.4.2',
packages=['cffi'] if cpython else [],
package_data={'cffi': ['_cffi_include.h', 'parse_c_type.h']}
if cpython else {},
diff --git a/testing/cffi0/backend_tests.py b/testing/cffi0/backend_tests.py
--- a/testing/cffi0/backend_tests.py
+++ b/testing/cffi0/backend_tests.py
@@ -1352,8 +1352,8 @@
ffi = FFI(backend=self.Backend())
ffi.cdef("enum foo;")
from cffi import __version_info__
- if __version_info__ < (1, 4):
- py.test.skip("re-enable me in version 1.4")
+ if __version_info__ < (1, 5):
+ py.test.skip("re-enable me in version 1.5")
e = py.test.raises(CDefError, ffi.cast, "enum foo", -1)
assert str(e.value) == (
"'enum foo' has no values explicitly defined: refusing to guess "
@@ -1826,7 +1826,12 @@
assert seen == [1, 1]
def test_init_once_multithread(self):
- import thread, time
+ import sys, time
+ if sys.version_info < (3,):
+ import thread
+ else:
+ import _thread as thread
+ #
def do_init():
seen.append('init!')
time.sleep(1)
diff --git a/testing/cffi0/test_function.py b/testing/cffi0/test_function.py
--- a/testing/cffi0/test_function.py
+++ b/testing/cffi0/test_function.py
@@ -464,10 +464,22 @@
ffi = FFI(backend=self.Backend())
ffi.cdef("double __stdcall sin(double x);") # stdcall ignored
m = ffi.dlopen(lib_m)
- if (sys.platform == 'win32' and sys.maxsize < 2**32 and
+ if (sys.platform == 'win32' and sys.maxsize < 2**32 and
self.Backend is not CTypesBackend):
assert "double(__stdcall *)(double)" in str(ffi.typeof(m.sin))
else:
assert "double(*)(double)" in str(ffi.typeof(m.sin))
x = m.sin(1.23)
assert x == math.sin(1.23)
+
+ def test_dir_on_dlopen_lib(self):
+ ffi = FFI(backend=self.Backend())
+ ffi.cdef("""
+ typedef enum { MYE1, MYE2 } myenum_t;
+ double myfunc(double);
+ double myvar;
+ const double myconst;
+ #define MYFOO 42
+ """)
+ m = ffi.dlopen(lib_m)
+ assert dir(m) == ['MYE1', 'MYE2', 'MYFOO', 'myconst', 'myfunc',
'myvar']
diff --git a/testing/cffi1/test_ffi_obj.py b/testing/cffi1/test_ffi_obj.py
--- a/testing/cffi1/test_ffi_obj.py
+++ b/testing/cffi1/test_ffi_obj.py
@@ -7,6 +7,7 @@
p = ffi.new("int *")
p[0] = -42
assert p[0] == -42
+ assert type(ffi) is ffi.__class__ is _cffi1_backend.FFI
def test_ffi_subclass():
class FOO(_cffi1_backend.FFI):
@@ -16,6 +17,7 @@
assert foo.x == 42
p = foo.new("int *")
assert p[0] == 0
+ assert type(foo) is foo.__class__ is FOO
def test_ffi_no_argument():
py.test.raises(TypeError, _cffi1_backend.FFI, 42)
@@ -437,13 +439,18 @@
assert seen == [1, 1]
def test_init_once_multithread():
- import thread, time
+ if sys.version_info < (3,):
+ import thread
+ else:
+ import _thread as thread
+ import time
+ #
def do_init():
- print 'init!'
+ print('init!')
seen.append('init!')
time.sleep(1)
seen.append('init done')
- print 'init done'
+ print('init done')
return 7
ffi = _cffi1_backend.FFI()
seen = []
@@ -454,3 +461,37 @@
thread.start_new_thread(f, ())
time.sleep(1.5)
assert seen == ['init!', 'init done'] + 6 * [7]
+
+def test_init_once_failure():
+ def do_init():
+ seen.append(1)
+ raise ValueError
+ ffi = _cffi1_backend.FFI()
+ seen = []
+ for i in range(5):
+ py.test.raises(ValueError, ffi.init_once, do_init, "tag")
+ assert seen == [1] * (i + 1)
+
+def test_init_once_multithread_failure():
+ if sys.version_info < (3,):
+ import thread
+ else:
+ import _thread as thread
+ import time
+ def do_init():
+ seen.append('init!')
+ time.sleep(1)
+ seen.append('oops')
+ raise ValueError
+ ffi = _cffi1_backend.FFI()
+ seen = []
+ for i in range(3):
+ def f():
+ py.test.raises(ValueError, ffi.init_once, do_init, "tag")
+ thread.start_new_thread(f, ())
+ i = 0
+ while len(seen) < 6:
+ i += 1
+ assert i < 20
+ time.sleep(0.51)
+ assert seen == ['init!', 'oops'] * 3
diff --git a/testing/cffi1/test_recompiler.py b/testing/cffi1/test_recompiler.py
--- a/testing/cffi1/test_recompiler.py
+++ b/testing/cffi1/test_recompiler.py
@@ -3,7 +3,7 @@
from cffi import FFI, VerificationError, FFIError
from cffi import recompiler
from testing.udir import udir
-from testing.support import u
+from testing.support import u, long
from testing.support import FdWriteCapture, StdErrCapture
@@ -948,6 +948,19 @@
""", sources=[str(extra_c_source)])
assert lib.external_foo == 42
+def test_dotdot_in_source_file_names():
+ extra_c_source = udir.join(
+ 'extra_test_dotdot_in_source_file_names.c')
+ extra_c_source.write('const int external_foo = 42;\n')
+ ffi = FFI()
+ ffi.cdef("const int external_foo;")
+ lib = verify(ffi, 'test_dotdot_in_source_file_names', """
+ extern const int external_foo;
+ """, sources=[os.path.join(os.path.dirname(str(extra_c_source)),
+ 'foobar', '..',
+ os.path.basename(str(extra_c_source)))])
+ assert lib.external_foo == 42
+
def test_call_with_incomplete_structs():
ffi = FFI()
ffi.cdef("typedef struct {...;} foo_t; "
@@ -1143,6 +1156,7 @@
assert hasattr(lib, '__dict__')
assert lib.__all__ == ['MYFOO', 'mybar'] # but not 'myvar'
assert lib.__name__ == repr(lib)
+ assert lib.__class__ is type(lib)
def test_macro_var_callback():
ffi = FFI()
@@ -1502,8 +1516,8 @@
res = lib.bar(4, 5)
assert res == 0
assert f.getvalue() == (
- "extern \"Python\": function bar() called, but no code was attached "
- "to it yet with @ffi.def_extern(). Returning 0.\n")
+ b"extern \"Python\": function bar() called, but no code was attached "
+ b"to it yet with @ffi.def_extern(). Returning 0.\n")
@ffi.def_extern("bar")
def my_bar(x, y):
@@ -1520,10 +1534,10 @@
baz1 = ffi.def_extern()(baz)
assert baz1 is baz
seen = []
- baz(40L, 4L)
- res = lib.baz(50L, 8L)
+ baz(long(40), long(4))
+ res = lib.baz(long(50), long(8))
assert res is None
- assert seen == [("Baz", 40L, 4L), ("Baz", 50, 8)]
+ assert seen == [("Baz", 40, 4), ("Baz", 50, 8)]
assert type(seen[0][1]) is type(seen[0][2]) is long
assert type(seen[1][1]) is type(seen[1][2]) is int
diff --git a/testing/support.py b/testing/support.py
--- a/testing/support.py
+++ b/testing/support.py
@@ -8,6 +8,7 @@
return eval('u'+repr(other).replace(r'\\u', r'\u')
.replace(r'\\U', r'\U'))
u = U()
+ long = long # for further "from testing.support import long"
assert u+'a\x00b' == eval(r"u'a\x00b'")
assert u+'a\u1234b' == eval(r"u'a\u1234b'")
assert u+'a\U00012345b' == eval(r"u'a\U00012345b'")
@@ -22,9 +23,12 @@
class StdErrCapture(object):
"""Capture writes to sys.stderr (not to the underlying file descriptor)."""
def __enter__(self):
- import StringIO
+ try:
+ from StringIO import StringIO
+ except ImportError:
+ from io import StringIO
self.old_stderr = sys.stderr
- sys.stderr = f = StringIO.StringIO()
+ sys.stderr = f = StringIO()
return f
def __exit__(self, *args):
sys.stderr = self.old_stderr
@@ -35,6 +39,9 @@
to the Posix manual."""
def __init__(self, capture_fd=2): # stderr by default
+ if sys.platform == 'win32':
+ import py
+ py.test.skip("seems not to work, too bad")
self.capture_fd = capture_fd
def __enter__(self):
_______________________________________________
pypy-commit mailing list
[email protected]
https://mail.python.org/mailman/listinfo/pypy-commit