.. _cffi_gen_src_docs: ======================================== Generating C Sources for CFFI Extensions ======================================== .. contents:: CFFI ships a command-line tool, ``cffi-gen-src``, that produces the same output as :meth:`FFI.emit_c_code`: a ``.c`` source file ready to be compiled into a CPython c-extension module. This is useful for projects that want CFFI to generate source code for a later compilation step without depending on setuptools. Installing CFFI installs the ``cffi-gen-src`` script; running ``python -m cffi.gen_src`` invokes the same command line and behaves identically. Use the former where a script with a Python shebang makes more sense (e.g. cross-compiling) and use the latter when the console script is not on PATH but a Python interpreter is. In a packaged project, ``cffi-gen-src`` is usually called by a script or rule executed by a build backend, and the build backend then consumes the generated C file. Any `Python build backend`_ that can run a source generator, such as `meson-python `_, `scikit-build-core `_, or similar, can use ``cffi-gen-src`` the same way. The rest of this page uses meson-python in the examples. The command line is the only supported interface; the Python API inside ``cffi._cffi_gen_src`` is private. The implementation is based on the `cffi-buildtool`_ project by Rose Davidson (`@inklesspen`_ on GitHub). It is included in CFFI with permission of the original author. .. _Python build backend: https://packaging.python.org/en/latest/guides/tool-recommendations/#build-backends-for-extension-modules .. _cffi-buildtool: https://github.com/inklesspen/cffi-buildtool .. _@inklesspen: https://github.com/inklesspen The ``cffi-gen-src`` Command-line Tool ====================================== ``cffi-gen-src`` has two subcommands. The first, ``exec-python`` is most useful if you already have a Python script that sets up an FFI definition. The second, ``read-sources`` is most useful if you are wrapping a large API surface and want a more structured way to specify a set of FFI definitions. ``cffi-gen-src exec-python`` ---------------------------- This mode executes a Python script that creates a :class:`cffi.FFI` object as a module-level global and calls :meth:`FFI.set_source` on it, then emits the generated C source. By default ``cffi-gen-src`` looks for a global named ``ffibuilder``; pass ``--ffi-var`` if the object has another name. Apart from being run by the tool instead of by hand, it is the same script the CFFI docs show under :ref:`real-example`. Let's say we want to create an extension module that wraps a single C function named ``square``. The ``square`` function has the following signature: .. code-block:: C int square(int n); Let's also say this function definition is exposed inside a header named `square.h`. We could create a set of FFI bindings for this function given this ``_squared_build.py``:: from cffi import FFI ffibuilder = FFI() ffibuilder.cdef("int square(int n);") ffibuilder.set_source( "squared._squared", '#include "square.h"', ) To generate the source code for the C extension, you would run: .. code-block:: console $ cffi-gen-src exec-python _squared_build.py _squared.c Many CFFI FFI definition scripts have an ``if __name__ == "__main__"`` section that triggers a compilation step. This is not needed for a script run by ``cffi-gen-src``, which only writes the generated C source. If the script does have such a section it is harmless: the script is executed with ``__name__`` set to ``"cffi.gen_src"``, so the block is skipped and an existing FFI definition script works unchanged. If the :class:`cffi.FFI` is bound to a name other than ``ffibuilder``, pass ``--ffi-var``. To make that concrete, let's say your FFI definition script creates an FFI object named ``make_ffi``:: from cffi import FFI make_ffi = FFI() In that case, you would pass ``--ffi-var=make_ffi`` to ``cffi-gen-src``: .. code-block:: console $ cffi-gen-src exec-python --ffi-var=make_ffi _squared_build.py _squared.c .. note:: ``cffi-gen-src`` emits C source for out-of-line API mode modules. It cannot emit source for ABI mode modules where :meth:`FFI.set_source` is called with ``None``. .. note:: CFFI's setuptools integration supports passing ``libraries=``, ``library_dirs=``, ``include_dirs=``, and ``extra_compile_args=`` arguments to :meth:`FFI.set_source`. When using ``cffi-gen-src``, these arguments are *ignored*. Link and include settings are the responsibility of the compilation step that happens after generating the C extension sources. If you are using meson-python following the examples in this section, you would express them through the ``dependencies``, ``include_directories``, and ``c_args`` arguments of ``py.extension_module()``. ``cffi-gen-src read-sources`` ----------------------------- For larger modules, keeping the FFI definition and any necessary C source prelude in separate files tends to be easier to work with -- you can configure your editor to treat them as plain C, and write presubmit tooling that parses the FFI definition directly without extracting it from a Python script. Given ``squared.cdef.txt``: .. code-block:: C int square(int n); and ``squared.csrc.c``: .. code-block:: C #include "square.h" you would run the following command to generate the C source code for a CFFI extension: .. code-block:: console $ cffi-gen-src read-sources squared._squared squared.cdef.txt squared.csrc.c _squared.c With all other details left exactly the same as the ``exec-python`` example. The first positional argument passed to the ``read-sources`` command is the fully qualified module name that will be embedded in the generated C source code (equivalent to the first argument to :meth:`FFI.set_source`). A Worked Example Using ``meson-python`` ======================================= Project layout: .. code-block:: text squared/ ├── pyproject.toml ├── meson.build └── src/ ├── squared/ │ ├── __init__.py │ └── _squared_build.py └── csrc/ ├── square.h └── square.c ``pyproject.toml``: .. literalinclude:: ../../testing/cffi1/cffi_gen_src_examples/exec_python_example/pyproject.toml :language: toml ``meson.build``: .. literalinclude:: ../../testing/cffi1/cffi_gen_src_examples/exec_python_example/meson.build :language: meson ``src/squared/__init__.py``: .. literalinclude:: ../../testing/cffi1/cffi_gen_src_examples/exec_python_example/src/squared/__init__.py :language: python ``src/squared/_squared_build.py``: .. literalinclude:: ../../testing/cffi1/cffi_gen_src_examples/exec_python_example/src/squared/_squared_build.py :language: python ``src/csrc/square.h``: .. literalinclude:: ../../testing/cffi1/cffi_gen_src_examples/exec_python_example/src/csrc/square.h :language: C ``src/csrc/square.c``: .. literalinclude:: ../../testing/cffi1/cffi_gen_src_examples/exec_python_example/src/csrc/square.c :language: C Build and install the project with any Python build front-end. For example, with `pip`, in the root `squared` directory: .. code-block:: console $ python -m pip install . $ python -c "from squared import squared; print(squared(7))" 49 To switch this project to ``read-sources`` mode, replace ``_squared_build.py`` with two files, so that the project layout becomes: .. code-block:: text squared/ ├── pyproject.toml ├── meson.build └── src/ ├── squared/ │ ├── __init__.py │ ├── squared.cdef.txt │ └── squared.csrc.c └── csrc/ ├── square.h └── square.c The first new file, ``squared.cdef.txt``, contains the FFI definition: .. literalinclude:: ../../testing/cffi1/cffi_gen_src_examples/read_sources_example/src/squared/squared.cdef.txt :language: python and the second, ``squared.csrc.c``, contains the C source prelude: .. literalinclude:: ../../testing/cffi1/cffi_gen_src_examples/read_sources_example/src/squared/squared.csrc.c :language: python then change two spots in the ``meson.build`` file. First, update the ``custom_target`` ``command`` to call ``cffi-gen-src read-sources`` with two input arguments: .. code-block:: meson command: [ cffi_gen_src, 'read-sources', 'squared._squared', '@INPUT0@', '@INPUT1@', '@OUTPUT@', ], and then list both of the FFI specification files under ``input``: .. code-block:: meson input: ['src/squared/squared.cdef.txt', 'src/squared/squared.csrc.c'] Distributing CFFI Extensions using Setuptools ============================================= .. _distutils-setuptools: You can (but don't have to) use CFFI's **Distutils** or **Setuptools integration** when writing a ``setup.py``. For Distutils (only in out-of-line API mode; deprecated since Python 3.10): .. code-block:: python # setup.py (requires CFFI to be installed first) from distutils.core import setup import foo_build # possibly with sys.path tricks to find it setup( ..., ext_modules=[foo_build.ffibuilder.distutils_extension()], ) For Setuptools (out-of-line only, but works in ABI or API mode; recommended): .. code-block:: python # setup.py (with automatic dependency tracking) from setuptools import setup setup( ..., setup_requires=["cffi>=1.0.0"], cffi_modules=["package/foo_build.py:ffibuilder"], install_requires=["cffi>=1.0.0"], ) Note again that the ``foo_build.py`` example contains the following lines, which mean that the ``ffibuilder`` is not actually compiled when ``package.foo_build`` is merely imported---it will be compiled independently by the Setuptools logic, using compilation parameters provided by Setuptools: .. code-block:: python if __name__ == "__main__": # not when running with setuptools ffibuilder.compile(verbose=True) * Note that some bundler tools that try to find all modules used by a project, like PyInstaller, will miss ``_cffi_backend`` in the out-of-line mode because your program contains no explicit ``import cffi`` or ``import _cffi_backend``. You need to add ``_cffi_backend`` explicitly (as a "hidden import" in PyInstaller, but it can also be done more generally by adding the line ``import _cffi_backend`` in your main program). Note that CFFI actually contains two different ``FFI`` classes. The page `Using the ffi/lib objects`_ describes the common functionality. It is what you get in the ``from package._foo import ffi`` lines above. On the other hand, the extended ``FFI`` class is the one you get from ``import cffi; ffi_or_ffibuilder = cffi.FFI()``. It has the same functionality (for in-line use), but also the extra methods described below (to prepare the FFI). NOTE: We use the name ``ffibuilder`` instead of ``ffi`` in the out-of-line context, when the code is about producing a ``_foo.so`` file; this is an attempt to distinguish it from the different ``ffi`` object that you get by later saying ``from _foo import ffi``. .. _`Using the ffi/lib objects`: using.html The reason for this split of functionality is that a regular program using CFFI out-of-line does not need to import the ``cffi`` pure Python package at all. (Internally it still needs ``_cffi_backend``, a C extension module that comes with CFFI; this is why CFFI is also listed in ``install_requires=..`` above. In the future this might be split into a different PyPI package that only installs ``_cffi_backend``.) Note that a few small differences do exist: notably, ``from _foo import ffi`` returns an object of a type written in C, which does not let you add random attributes to it (nor does it have all the underscore-prefixed internal attributes of the Python version). 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)``). The same is true for ``lib.__class__``, ``lib.__all__`` and ``lib.__name__`` added in successive versions.