.. _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.