@@ -9,48 +9,53 @@ Building and Distributing CFFI Extensions
99CFFI ships a command-line tool, invoked as ``python -m
1010cffi.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
1513integrating 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
2119drive ``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
3133The ``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
95120Given ``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
118145A 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=============================================
0 commit comments