Skip to content

Integrate cffi-buildtool into CFFI itself#241

Open
ngoldbaum wants to merge 8 commits into
python-cffi:mainfrom
ngoldbaum:integrate-buildtool
Open

Integrate cffi-buildtool into CFFI itself#241
ngoldbaum wants to merge 8 commits into
python-cffi:mainfrom
ngoldbaum:integrate-buildtool

Conversation

@ngoldbaum

Copy link
Copy Markdown
Contributor

Closes #47. Revives #76.

This adds a new subpackage for cffi named cffi.buildtool and a new CLI script distributed by CFFI named gen-cffi-src. Also adds new tests and documentation. See the new documentation and tests for usage details.

I realize that this is a big change that makes a number of opinionated choices. I'm very happy to adjust things to suit the maintainers' taste.

My ultimate goal here is to make it possible for projects using CFFI to use arbitrary build tools and remove the perceived tight coupling between CFFI and setuptools/distutils. If there isn't appetite to add the code in cffi-buildtool to CFFI itself, I'd also happily document how to depend on and use cffi-buildtool instead.

See inklesspen/cffi-buildtool#2 where I got @inklesspen's blessing to do this.

The is based on code and documentation that were originally written by Rose Davidson (@inklesspen on GitHub) for the cffi-buildtool project: https://github.com/inklesspen/cffi-buildtool.

This adds code and documentation that was originally written by Rose Davidson
(@inklesspen on GitHub) for the cffi-buildtool project: https://github.com/inklesspen/cffi-buildtool
@inklesspen

Copy link
Copy Markdown
Contributor

Just commenting here to confirm I give my full support for this upstreaming, and after a release is cut which includes these changes, I will mark my existing project as deprecated.

Also I'm available to answer any questions about the original code upon which this is based.

@ngoldbaum ngoldbaum left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some thoughts that occurred to me since yesterday and a typo fix

Comment thread doc/source/buildtool.rst Outdated
Comment thread doc/source/buildtool.rst
Comment thread testing/cffi1/buildtool_example2/src/csrc/square.c Outdated
@mattip

mattip commented Apr 21, 2026

Copy link
Copy Markdown
Contributor

There is a clang-tsan failure

@ngoldbaum

Copy link
Copy Markdown
Contributor Author

@mattip see #242 and #243, the failure is unrelated.

@ngoldbaum

Copy link
Copy Markdown
Contributor Author

Sorry for taking so long to come back to this. I think this is ready for another round of review now.

@mattip mattip left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems very helpful, and will provide a path to move away from distutils/setuptools only cffi support.

Some of my comments might miss the mark. In general I think the concept of "build" is not clear enough. A cffi out-of-line module has three parts: generate C code, compile the C code into a c-extension, and import the module. Does "build" address all three?

An in-line mode module (no compiler used) is different, I assume this does not address the inline-mode usage.

Comment thread doc/source/buildtool.rst Outdated
Comment thread doc/source/buildtool.rst Outdated
.. contents::

CFFI ships a small subpackage, :mod:`cffi.buildtool`, together with a
command-line program, ``gen-cffi-src``. Both produce the same output as

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the command line program installed with an entry point?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes; see the change in pyproject.toml.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On further reflection, I think it simplifies things to make the only UI be python -m cffi.buildtool, so I've removed the gen-cffi-src script and entry point in the latest version of the PR.

Comment thread doc/source/buildtool.rst Outdated
Comment on lines +12 to +14
a CPython extension module. What they add is two convenient front-ends
-- one that executes an existing "build" Python script, and one that
reads a ``cdef`` and C prelude from two files. This tool enables

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand. Can you rework the "They add" with more words?

Suggested change
a CPython extension module. What they add is two convenient front-ends
-- one that executes an existing "build" Python script, and one that
reads a ``cdef`` and C prelude from two files. This tool enables
a CPython extension module. They add is two convenient front-ends
-- one that executes an existing "build" Python script, and one that
reads a ``cdef`` and C prelude from two files. This tool enables

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm applying your suggestion but I'm going to push a subsequent comment that rewords this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This paragraph ended up getting dropped completely. I think the point that cffi.buildtool is only for generating C sources is a clearer now.

Comment thread doc/source/buildtool.rst Outdated
Comment thread doc/source/buildtool.rst Outdated
.. py:function:: find_ffi_in_python_script(pysrc, filename, ffivar)

Execute a Python build script and return the :class:`cffi.FFI`
object it defines. ``pysrc`` is the text of the script,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the build script always compile a c-extension module? Is the lib object also returned somehow?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, the build script doesn't build an extension module at all. It generates the FFI binding object (a cffi.api.FFI instance) which then is used to generate the C extension sources.

I guess calling it a Python build script is confusing. How about Calling it "a script that dynamically defines a set of FFI bindings"?

I'll try to rewrite these docs to make that point clearer.

Comment thread doc/source/buildtool.rst Outdated

.. note::

When you drive the build from a build backend, the

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does "drive the build" mean in this context?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean when CFFI is used without CFFI's built-in distutils integration. Specifically via functionality recompiler.py. I'll rephrase this to make that clearer.

Comment thread doc/source/buildtool.rst Outdated

This mode takes the Python build script you would normally run by
hand -- the one the CFFI docs show under "Main mode of usage" -- and
generates the ``.c`` source for you. For example, given this

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it stop after generating the C source, or does it also compile the c-extension module?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It stops after generating the C source; it is the job of a c compiler to compile the c extension module. (Build tools such as meson already have support for this.)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll try to make this whole set of documentation clearer that there are no compilation steps involved - this is all about generating the C source code for a CFFI extension at build time, without relying on setuptools or CFFI's recompiler and setuptools integration.

Comment thread doc/source/cdef.rst Outdated

This covers how to create wrapper modules. See :ref:`buildtool_docs`
for instructions on how to integrate with a Python build backend and
distribute wrapper modules.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is a wrapper module? A definition would be nice.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added.

Comment thread src/cffi/buildtool/__init__.py Outdated
* :func:`make_ffi_from_sources` -- construct an :class:`cffi.FFI`
from a ``cdef`` string and a C source prelude.
* :func:`generate_c_source` -- emit the generated C source for an
:class:`cffi.FFI` as a string.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the documentation it mentioned two modes for the subpackage: (1) generate the c code and (2) compile the c-extension and import the module. Is that mentioned somewhere here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry if the language about build scripts was confusing. There's no compilation happening. The two modes of operation are either based on a Python script that defines the FFI bindings or a pair of text files that define the FFI bindings for the C API to be wrapped.

The second mode is based on the read_sources function that's currently not exposed in the top-level API here. I guess I might as well expose that as well if we're going to expose a Python API here.

I think we could also probably choose not to expose a public Python API here. On reflection a couple months after doing this PR initially, I lean towards not exposing a Python API and only documenting the command-line interface.

Comment thread doc/source/buildtool.rst Outdated
``extra_compile_args=`` etc. arguments you pass to
:meth:`FFI.set_source` are *ignored*. Link and include settings are
the build backend's responsibility; for meson-python you express
them through the ``dependencies:`` / ``include_directories:``

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

extra_compile_args maps to c_args, might want to add that for completeness

Comment thread doc/source/buildtool.rst Outdated
squared_ext_src,
subdir: 'squared',
install: true,
dependencies: [square_dep, py.dependency()],

@rgommers rgommers Jun 8, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Drop py.dependency here, it's never needed inside a py.extension_module call. It may be needed inside a static_library call (as above), if that used CPython C API rather than it being a pure C library (EDIT: but if it were, you'd see it in CI as a build failure).

Comment thread doc/source/cdef.rst Outdated
and *if* the "stuff" part is big enough that import time is a concern,
then rewrite it as described in `the out-of-line but still ABI mode`__
above. Optionally, see also the `setuptools integration`__ paragraph.
above. Optionally, see also the :ref:`build backend and distrubution

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo distrubution (also higher up, search the diff for it)

Comment thread src/cffi/buildtool/_gen.py Outdated
@@ -0,0 +1,58 @@
# Integrated from the cffi-buildtool project by Rose Davidson
# (https://github.com/inklesspen/cffi-buildtool), MIT-licensed.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically this is incorrect, it misses the required attribution. Easiest way to do this correctly is to add the upstream LICENSE file as buildtool/LICENSE in this PR. The main cffi license file already says "Except when otherwise stated (look for LICENSE files in directories or
information at the beginning of each file) ..."
, so nothing else is needed.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I licensed this code under the MIT license because that was the license used for CFFI at the time that I wrote this tool. I will be happy to relicense it under the current "MIT No Attribution" license that is now used by CFFI, if that makes matters at all simpler.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No worries, I think I'll just add the license file you have now to the buildtool subdirectory as Ralf suggested. That seems cleanest to me from an attribution point of view.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the latest version of the PR the whole implementation is in one _buildtool.py file which now includes the original MIT license verbatim in the source header.

@rgommers

rgommers commented Jun 8, 2026

Copy link
Copy Markdown

I had a read through out of interest; overall this LGTM. The CLI taking the two main build modes and source as input, generated C code as output is useful and matches how Cython, f2py and other such codegen/binding tools are integrated into meson-python and scikit-build-core; the pattern should indeed work for other build backends that are able to build extension modules from C code as well.

ngoldbaum and others added 4 commits June 9, 2026 11:42
Thanks for the review comments! I'm going to apply the review suggestions that are pending now, pull, and address the remaining comments.

Co-authored-by: Matti Picus <matti.picus@gmail.com>
@ngoldbaum

Copy link
Copy Markdown
Contributor Author

I think I've responded to all the review comments. I'd appreciate another look :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Document how to use meson to build a cffi cextension

4 participants