Skip to content

Commit 6df3f77

Browse files
committed
Respond to code review comments
1 parent dec64ee commit 6df3f77

4 files changed

Lines changed: 128 additions & 113 deletions

File tree

‎doc/source/buildtool.rst‎

Lines changed: 118 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -9,48 +9,53 @@ Building and Distributing CFFI Extensions
99
CFFI ships a command-line tool, invoked as ``python -m
1010
cffi.buildtool``, that produces the same output as
1111
:meth:`FFI.emit_c_code`: a ``.c`` source file ready to be compiled into
12-
a CPython extension module. It adds two convenient front-ends
13-
-- one that executes an existing "build" Python script, and one that
14-
reads a ``cdef`` and C prelude from two files. This tool enables
12+
a CPython extension module. This tool enables
1513
integrating with any build backend, such as `meson-python
1614
<https://meson-python.readthedocs.io/>`_, `scikit-build-core
1715
<https://scikit-build-core.readthedocs.io/>`_, or similar.
1816

19-
The rest of this page uses meson-python in the examples, but any PEP
20-
517 backend that lets you run a helper program during the build can
17+
The rest of this page uses meson-python in the examples, but any `Python build
18+
backend`_ that lets you run a helper program during the build can
2119
drive ``python -m cffi.buildtool`` the same way.
2220

23-
The command line is the buildtool's only public interface; the
24-
implementation inside the ``cffi`` package is private. The buildtool
25-
was vendored with permission from the `cffi-buildtool`_ project by Rose
26-
Davidson (@inklesspen on GitHub).
21+
The only way to use the buildtool functionality is via ``python -m
22+
cffi.buildtool``; the implementation inside the ``cffi`` package is
23+
private.
2724

28-
.. _cffi-buildtool: https://github.com/inklesspen/cffi-buildtool
25+
The implementation is based on the `cffi-buildtool`_ project by Rose Davidson
26+
(`@inklesspen`_ on GitHub). It is included in CFFI with permission of the original
27+
author.
2928

29+
.. _Python build backend: https://packaging.python.org/en/latest/guides/tool-recommendations/#build-backends-for-extension-modules
30+
.. _cffi-buildtool: https://github.com/inklesspen/cffi-buildtool
31+
.. _@inklesspen: https://github.com/inklesspen
3032

3133
The ``python -m cffi.buildtool`` Command-line Tool
3234
==================================================
3335

34-
``python -m cffi.buildtool`` has two subcommands. In both, the final
35-
positional argument is the path to the ``.c`` file to generate.
36+
``python -m cffi.buildtool`` has two subcommands. The first, ``exec-python`` is
37+
most useful if you already have a Python script that sets up an FFI
38+
definition. The second, ``read-sources`` is most useful if you are wrapping
39+
a large API surface and want a more structured way to specify a set of FFI
40+
definitions.
3641

37-
.. note::
42+
``python -m cffi.buildtool exec-python``
43+
----------------------------------------
3844

39-
When you drive the build from a build backend, the
40-
``libraries=``, ``library_dirs=``, ``include_dirs=``,
41-
``extra_compile_args=`` etc. arguments you pass to
42-
:meth:`FFI.set_source` are *ignored*. Link and include settings are
43-
the build backend's responsibility; for meson-python you express
44-
them through the ``dependencies:`` / ``include_directories:``
45-
arguments of ``py.extension_module()``.
45+
This mode takes a Python script that dynamically defines an FFI interface and
46+
accompanying C extension source code. The FFI definition script is the same
47+
script you would normally run by hand -- the one the CFFI docs show under
48+
:ref:`real-example`.
4649

50+
Let's say we want to create an extension module that wraps a single C function named
51+
``square``. The ``square`` function has the following signature:
4752

48-
``python -m cffi.buildtool exec-python``
49-
----------------------------------------
53+
.. code-block:: C
54+
55+
int square(int n);
5056
51-
This mode takes the Python build script you would normally run by
52-
hand -- the one the CFFI docs show under "Main mode of usage" -- and
53-
generates the ``.c`` source for you. For example, given this
57+
Let's also say this function definition is exposed inside a header named
58+
`square.h`. We could create a set of FFI bindings for this function given this
5459
``_squared_build.py``::
5560

5661
from cffi import FFI
@@ -64,33 +69,53 @@ generates the ``.c`` source for you. For example, given this
6469
'#include "square.h"',
6570
)
6671

67-
if __name__ == "__main__":
68-
ffibuilder.compile(verbose=True)
69-
70-
you run:
72+
To generate the source code for the C extension, you would run:
7173

7274
.. code-block:: console
7375
7476
$ python -m cffi.buildtool exec-python _squared_build.py _squared.c
7577
76-
The script is executed with ``__name__`` set to ``"cffi.buildtool"``,
77-
so the trailing ``if __name__ == "__main__":`` block is skipped: the
78-
tool only generates the C source and never compiles it.
78+
Many CFFI build scripts have an ``if __name__ == "__main__"`` section
79+
that triggers a compilation step. This is not needed for a
80+
``cffi.buildtool`` script, which does not generate compiled artifacts,
81+
only C source code. It is up to your build-backend of choice
82+
(e.g. meson-python) to run a C compiler and build compiled artifacts.
83+
If the script does have such a section it is harmless: the script is
84+
executed with ``__name__`` set to ``"cffi.buildtool"``, so the block is
85+
skipped and an existing build script works unchanged.
86+
87+
If the :class:`cffi.FFI` is bound to a name other than ``ffibuilder``, pass
88+
``--ffi-var``. To make that concrete, let's say your build script creates an FFI
89+
object named ``make_ffi``::
90+
91+
from cffi import FFI
7992

80-
If the :class:`cffi.FFI` is bound to a name other than ``ffibuilder``,
81-
pass ``--ffi-var``:
93+
make_ffi = FFI()
94+
95+
In that case, you would pass ``--ffi-var=make_ffi`` to ``cffi.buildtool``:
8296

8397
.. code-block:: console
8498
8599
$ python -m cffi.buildtool exec-python --ffi-var=make_ffi _squared_build.py _squared.c
86100
101+
.. note::
102+
103+
CFFI's setuptools integration supports passing ``libraries=``,
104+
``library_dirs=``, ``include_dirs=``, and ``extra_compile_args=``
105+
arguments to :meth:`FFI.set_source`. When using `cffi.buildtool`,
106+
these arguments are *ignored*. Link and include settings are the
107+
build backend's responsibility; for meson-python you would express
108+
them through the ``dependencies``, ``include_directories``, and
109+
``c_args`` arguments of ``py.extension_module()``.
110+
87111
``python -m cffi.buildtool read-sources``
88112
-----------------------------------------
89113

90-
For larger modules, keeping the ``cdef`` and the C source prelude in
91-
separate files tends to be easier to work with -- your editor
92-
treats them as plain C, and presubmit tooling doesn't have to parse
93-
them out of a string literal.
114+
For larger modules, keeping the FFI definition and any necessary C
115+
source prelude in separate files tends to be easier to work with --
116+
you can configure your editor to treat them as plain C, and write
117+
presubmit tooling that parses the FFI definition directly without
118+
extracting it from a Python script.
94119

95120
Given ``squared.cdef.txt``:
96121

@@ -104,15 +129,17 @@ and ``squared.csrc.c``:
104129
105130
#include "square.h"
106131
107-
you run:
132+
you would run the following command to generate a CFFI extension:
108133

109134
.. code-block:: console
110135
111136
$ python -m cffi.buildtool read-sources squared._squared squared.cdef.txt squared.csrc.c _squared.c
112137
113-
The first positional argument is the fully qualified module name that
114-
will be embedded in the generated source (equivalent to the first
115-
argument to :meth:`FFI.set_source`).
138+
With all other details left exactly the same as the ``exec-python`` example.
139+
140+
The first positional argument passed to the ``read-sources`` command is the
141+
fully qualified module name that will be embedded in the generated C source
142+
code (equivalent to the first argument to :meth:`FFI.set_source`).
116143

117144

118145
A Worked Example Using ``meson-python``
@@ -135,78 +162,36 @@ Project layout:
135162
136163
``pyproject.toml``:
137164

138-
.. code-block:: toml
139-
140-
[build-system]
141-
build-backend = 'mesonpy'
142-
requires = ['meson-python', 'cffi']
143-
144-
[project]
145-
name = 'squared'
146-
version = '0.1.0'
147-
requires-python = '>=3.9'
148-
dependencies = ['cffi']
165+
.. literalinclude:: ../../testing/cffi1/buildtool_examples/build_script_example/pyproject.toml
166+
:language: toml
149167

150168
``meson.build``:
151169

152-
.. code-block:: meson
153-
154-
project(
155-
'squared',
156-
'c',
157-
version: '0.1.0',
158-
)
159-
160-
py = import('python').find_installation(pure: false)
161-
162-
install_subdir('src/squared', install_dir: py.get_install_dir())
170+
.. literalinclude:: ../../testing/cffi1/buildtool_examples/build_script_example/meson.build
171+
:language: meson
163172

164-
square_lib = static_library(
165-
'square',
166-
'src/csrc/square.c',
167-
include_directories: include_directories('src/csrc'),
168-
)
169-
square_dep = declare_dependency(
170-
link_with: square_lib,
171-
include_directories: include_directories('src/csrc'),
172-
)
173-
174-
squared_ext_src = custom_target(
175-
'squared-cffi-src',
176-
command: [
177-
py,
178-
'-m', 'cffi.buildtool',
179-
'exec-python',
180-
'@INPUT@',
181-
'@OUTPUT@',
182-
],
183-
output: '_squared.c',
184-
input: ['src/squared/_squared_build.py'],
185-
)
173+
``src/squared/__init__.py``:
186174

187-
py.extension_module(
188-
'_squared',
189-
squared_ext_src,
190-
subdir: 'squared',
191-
install: true,
192-
dependencies: [square_dep, py.dependency()],
193-
)
175+
.. literalinclude:: ../../testing/cffi1/buildtool_examples/build_script_example/src/squared/__init__.py
176+
:language: python
194177

195-
``src/squared/__init__.py``:
178+
``src/squared/_squared_build.py``:
196179

197-
.. code-block:: python
180+
.. literalinclude:: ../../testing/cffi1/buildtool_examples/build_script_example/src/squared/_squared_build.py
181+
:language: python
198182

199-
from ._squared import ffi, lib
183+
``src/csrc/square.h``:
200184

185+
.. literalinclude:: ../../testing/cffi1/buildtool_examples/build_script_example/src/csrc/square.h
186+
:language: C
201187

202-
def squared(n):
203-
return lib.square(n)
188+
``src/csrc/square.c``:
204189

205-
``src/squared/_squared_build.py``, ``src/csrc/square.h`` and
206-
``src/csrc/square.c`` contain the snippets shown above.
190+
.. literalinclude:: ../../testing/cffi1/buildtool_examples/build_script_example/src/csrc/square.c
191+
:language: C
207192

208-
Build and install the project with any PEP 517 front-end. For
209-
example:
193+
Build and install the project with any Python build front-end. For
194+
example, with `pip`, in the root `squared` directory:
210195

211196
.. code-block:: console
212197
@@ -215,8 +200,35 @@ example:
215200
49
216201
217202
To switch this project to ``read-sources`` mode, replace
218-
``_squared_build.py`` with two files (``_squared.cdef.txt`` and
219-
``_squared.csrc.c``), then change the ``custom_target`` command to:
203+
``_squared_build.py`` with two files, so that the project layout
204+
becomes:
205+
206+
.. code-block:: text
207+
208+
squared/
209+
├── pyproject.toml
210+
├── meson.build
211+
└── src/
212+
├── squared/
213+
│ ├── __init__.py
214+
│ ├── squared.cdef.txt
215+
│ └── squared.csrc.c
216+
└── csrc/
217+
├── square.h
218+
└── square.c
219+
220+
The first new file, ``squared.cdef.txt``, contains the FFI definition:
221+
222+
.. literalinclude:: ../../testing/cffi1/buildtool_examples/cdef_example/src/squared/squared.cdef.txt
223+
:language: python
224+
225+
and the second, ``squared.csrc.c``, contains the C source prelude:
226+
227+
.. literalinclude:: ../../testing/cffi1/buildtool_examples/cdef_example/src/squared/squared.csrc.c
228+
:language: python
229+
230+
then change two spots in the ``meson.build`` file. First, update the ``custom_target``
231+
``command`` to call ``python -m cffi.buildtool read-sources`` with two input arguments:
220232

221233
.. code-block:: meson
222234
@@ -230,11 +242,11 @@ To switch this project to ``read-sources`` mode, replace
230242
'@OUTPUT@',
231243
],
232244
233-
and list both files under ``input:``:
245+
and then list both of the FFI specification files under ``input``:
234246

235247
.. code-block:: meson
236248
237-
input: ['src/squared/_squared.cdef.txt', '_squared.csrc.c']
249+
input: ['src/squared/squared.cdef.txt', 'src/squared/squared.csrc.c']
238250
239251
Distributing CFFI Extensions using Setuptools
240252
=============================================

‎doc/source/cdef.rst‎

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,12 @@ Preparing Wrapper Modules
66

77
.. note::
88

9-
This covers how to create wrapper modules. See :ref:`buildtool_docs`
10-
for instructions on how to integrate with a Python build backend and
11-
distribute wrapper modules.
9+
A *wrapper module* is a Python module that uses CFFI to expose
10+
functions and data from a C library, so that the rest of your
11+
program can import it and call the library through the ``ffi`` and
12+
``lib`` objects. This page covers how to create wrapper modules.
13+
See :ref:`buildtool_docs` for instructions on how to integrate with
14+
a Python build backend and distribute wrapper modules.
1215

1316
There are three or four different ways to use CFFI in a project.
1417
In order of complexity:
@@ -855,7 +858,7 @@ steps.
855858
856859
and *if* the "stuff" part is big enough that import time is a concern,
857860
then rewrite it as described in `the out-of-line but still ABI mode`__
858-
above. Optionally, see also the :ref:`build backend and distrubution
861+
above. Optionally, see also the :ref:`build backend and distribution
859862
<buildtool_docs>` documentation.
860863

861864
.. __: out-of-line-abi_
@@ -874,7 +877,7 @@ above. Optionally, see also the :ref:`build backend and distrubution
874877
then you should really rewrite it as described in `the out-of-line, API
875878
mode`__ above. It avoids a number of issues that have caused
876879
``ffi.verify()`` to grow a number of extra arguments over time. Then
877-
see the :ref:`build backend and distrubution <buildtool_docs>`
880+
see the :ref:`build backend and distribution <buildtool_docs>`
878881
documentation. Also, remember to remove the ``ext_package=".."`` from
879882
your ``setup.py``, which was sometimes needed with ``verify()`` but is
880883
just creating confusion with ``set_source()``.

‎testing/cffi1/buildtool_examples/build_script_example/meson.build‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,5 +37,5 @@ py.extension_module(
3737
squared_ext_src,
3838
subdir: 'squared',
3939
install: true,
40-
dependencies: [square_dep, py.dependency()],
40+
dependencies: [square_dep],
4141
)

‎testing/cffi1/buildtool_examples/cdef_example/meson.build‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,5 +39,5 @@ py.extension_module(
3939
squared_ext_src,
4040
subdir: 'squared',
4141
install: true,
42-
dependencies: [square_dep, py.dependency()],
42+
dependencies: [square_dep],
4343
)

0 commit comments

Comments
 (0)