I'd like to propose some changes to CFFI that create a some more conceptual separation between the ABI (in-line/out-line) and API access modes.
I can never remember how to set up API vs ABI access to a library in CFFI and have to check the cookbook every time. It's a great library but the current interface does not seem intuitive, at least not to me. It's possible to get an API when you wanted an ABI if you're not paying.
Part of the problem in my opinion is that the FFI object has 4 jobs. It creates both types of ABI access, API access, and also provides methods to marshal data between C and Python.
Requiring a C compiler is pretty major decision for a package maintainer. It creates a lot of extra work and has big implications for testing, containers, and such. (Is the package going to be installed in Docker containers? Can I get add a working compiler to that container?) manylinux1 has some some potential to improve this, but all the same, I think it makes sense for CFFI's users to clearly indicate "yes, I want an API / ABI".
I think this can be done with a few fairly simple changes:
1) Move the filename of the output module from
.compile(). All of the other inputs to
.set_source() are inputs for a C compiler, so I think this is a more logical location. Unsurprisingly
.set_source() does not do any work, it just stores it for compile.
ABIBuilder classes. Deprecate
cffi.FFI as a FFI builder, and give it the data marshalling job.
.bytecompile(), as a way of indicating to the user that this function does not invoke the C compiler, but rather creates compiles the cdefs to a binary format and saves this as a Python module. Of course this is abuse of terminology to some extent – perhaps
.serialize() instead would also emphasize that this stores data for quicker access.
ABIBuilder.set_source(). Right now, the presence of C source in the second parameter is the magic that determines whether CFFI builds an API or ABI.
Simple example (ABI level, in-line)
from cffi import ABIBuilder abi = ABIBuilder() abi.cdef(""" int printf(const char *format, ...); // copy-pasted from the man page """) lib = abi.dlopen(None)
Real example (API level, out-of-line)
from cffi import APIBuilder apibuilder = APIBuilder() apibuilder.set_source(""" /* passed to C compiler */ """, libraries=) apibuilder.cdef(""" int getpid(void); """) if __name__ == '__main__': apibuilder.compile("_example", verbose=True)
ABI, out of line
from cffi import ABIBuilder abi = ABIBuilder() abi.cdef(""" int printf(const char *format, ...); // copy-pasted from the man page """) abi.bytecompile("_example", verbose=True)