Skip to content

Commit 54e2999

Browse files
committed
Use meson-python
1 parent 7069412 commit 54e2999

File tree

1 file changed

+138
-44
lines changed

1 file changed

+138
-44
lines changed

Doc/extending/first-extension-module.rst

Lines changed: 138 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,15 @@ Your first C API extension module
1111
This tutorial will take you through creating a simple
1212
Python extension module written in C or C++.
1313

14-
It assumes basic knowledge about Python: you should be able to
14+
We will use the low-level Python C API directly.
15+
For easier ways to create extension modules, see
16+
the :ref:`recommended third party tools <c-api-tools>`.
17+
18+
The tutorial assumes basic knowledge about Python: you should be able to
1519
define functions in Python code before starting to write them in C.
1620
See :ref:`tutorial-index` for an introduction to Python itself.
1721

18-
The tutorial should be useful for anyone who can write a basic C library.
22+
The tutorial should be approachable for anyone who can write a basic C library.
1923
While we will mention several concepts that a C beginner would not be expected
2024
to know, like ``static`` functions or linkage declarations, understanding these
2125
is not necessary for success.
@@ -24,15 +28,15 @@ We will focus on giving you a "feel" of what Python's C API is like.
2428
It will not teach you important concepts, like error handling
2529
and reference counting, which are covered in later chapters.
2630

27-
As you write the code, you will need to compile it.
28-
Prepare to spend some time choosing, installing and configuring a build tool,
29-
since CPython itself does not include one.
30-
3131
We will assume that you use a Unix-like system (including macOS and
3232
Linux), or Windows.
3333
On other systems, you might need to adjust some details -- for example,
3434
a system command name.
3535

36+
You need to have suitable C compiler and Python development headers installed.
37+
On Linux, headers are often in a package like ``python3-dev``
38+
or ``python3-devel``.
39+
3640

3741
.. note::
3842

@@ -84,9 +88,12 @@ We want this function to be callable from Python as follows:
8488
Start with the headers
8589
======================
8690

87-
Begin by creating a file named :file:`spammodule.c`. [#why-spammodule]_
91+
Begin by creating a directory for this tutorial, and switching to it
92+
on command line.
93+
Then, create a file named :file:`spammodule.c` in your directory.
94+
[#why-spammodule]_
8895

89-
We'll start by including two headers: :file:`Python.h` to pull in
96+
In this file, we'll include two headers: :file:`Python.h` to pull in
9097
all declarations of the Python C API, and :file:`stdlib.h` for the
9198
:c:func:`system` function. [#stdlib-h]_
9299

@@ -102,38 +109,76 @@ On some systems, Python may define some pre-processor definitions
102109
that affect the standard headers.
103110

104111

105-
Warming up your build tool
106-
==========================
112+
Running your build tool
113+
=======================
107114

108115
With only the includes in place, your extension won't do anything.
109116
Still, it's a good time to try compiling and importing it.
110117
This will ensure that your build tool works, so that you can make
111118
and test incremental changes as you follow the rest of the text.
112119

113-
Choose a build tool for native C extensions from
114-
`the list <https://packaging.python.org/en/latest/guides/tool-recommendations/#build-backends-for-extension-modules>`_
115-
of recommendations in the Python Packaging User Guide. [#compile-directly]_
116-
Follow the tool's documentation to compile and install :file:`spammodule.c`
117-
as a C extension module.
120+
CPython itself does not come with a tool to build extension modules;
121+
it is recommended to use a third-party project for this.
122+
In this tutorial, we'll use `meson-python`_.
123+
(If you want to use another one, see :ref:`first-extension-other-tools`.)
124+
125+
.. at the time of writing, meson-python has the least overhead for a
126+
simple extension using PyModExport.
127+
Change this if another tool makes things easier.
128+
129+
``meson-python`` requires defining a "project" using two extra files.
130+
131+
First, add ``pyproject.toml`` with these contents:
118132

119-
.. note:: Workaround for missing ``PyInit``
133+
.. code-block:: toml
120134
121-
If your build tool output complains about missing ``PyInit_spam``,
122-
add the following function to your module for now:
135+
[build-system]
136+
build-backend = 'mesonpy'
137+
requires = ['meson-python']
123138
124-
.. code-block:: c
139+
[project]
140+
name = 'spammodule'
141+
version = '0'
125142
126-
// A workaround
127-
void *PyInit_spam(void) { return NULL; }
143+
Then, create ``meson.build`` containing the following:
144+
145+
.. code-block:: meson
146+
147+
project('purelib-and-platlib', 'c')
148+
149+
py = import('python').find_installation(pure: false)
150+
151+
py.extension_module(
152+
'spam', # name of the importable Python module
153+
'spammodule.c', # the C source file
154+
install: true,
155+
)
156+
157+
.. note::
128158

129-
This is a shim for an old-style :ref:`initialization function <extension-export-hook>`,
130-
which was required in extension modules for CPython 3.14 and below.
131-
Current CPython will not call it, but some build tools may still assume that
132-
all extension modules need to define it.
159+
See `meson-python documentation <meson-python>`_ for details on
160+
configuration.
133161

134-
If you use this workaround, you will get the exception
135-
``SystemError: initialization of spam failed without raising an exception``
136-
instead of an :py:exc:`ImportError` in the next step.
162+
Now, build install the *project in the current directory* (``.``) via ``pip``:
163+
164+
.. code-block:: sh
165+
166+
python -m pip install .
167+
168+
.. tip::
169+
170+
If you don't have ``pip`` installed, run ``python -m ensurepip``,
171+
preferably in a :mod:`virtual environment <venv>`.
172+
You can also use another tool that can build and install
173+
``pyproject.toml``-based projects, like
174+
`uv <https://docs.astral.sh/uv/>`_ (``uv pip install .``).
175+
176+
.. _meson-python: https://mesonbuild.com/meson-python/
177+
.. _virtual environment: https://packaging.python.org/en/latest/guides/installing-using-pip-and-virtual-environments/#create-and-use-virtual-environments
178+
179+
Note that you will need to run this command again every time you change your
180+
extension.
181+
Unlike Python, C has an explicit compilation step.
137182

138183
When your extension is compiled and installed, start Python and try to
139184
import it.
@@ -522,6 +567,71 @@ Here is the entire source file, for your convenience:
522567
:start-at: ///
523568

524569

570+
.. _first-extension-other-tools:
571+
572+
Appendix: Other build tools
573+
===========================
574+
575+
You should be able to follow this tutorial -- except the
576+
*Running your build tool* section itself -- with a build tool other
577+
than ``meson-python``.
578+
579+
The Python Packaging User Guide has a `list of recommended tools <https://packaging.python.org/en/latest/guides/tool-recommendations/#build-backends-for-extension-modules>`_;
580+
be sure to choose one for the C language.
581+
582+
583+
Workaround for missing PyInit function
584+
--------------------------------------
585+
586+
If your build tool output complains about missing ``PyInit_spam``,
587+
add the following function to your module for now:
588+
589+
.. code-block:: c
590+
591+
// A workaround
592+
void *PyInit_spam(void) { return NULL; }
593+
594+
This is a shim for an old-style :ref:`initialization function <extension-export-hook>`,
595+
which was required in extension modules for CPython 3.14 and below.
596+
Current CPython will not call it, but some build tools may still assume that
597+
all extension modules need to define it.
598+
599+
If you use this workaround, you will get the exception
600+
``SystemError: initialization of spam failed without raising an exception``
601+
instead of
602+
``ImportError: dynamic module does not define module export function``.
603+
604+
605+
Compiling directly
606+
------------------
607+
608+
Using a third-party build tool is heavily recommended,
609+
as it will take care of various details of your platform and Python
610+
installation, of naming the resulting extension, and, later, of distributing
611+
your work.
612+
613+
If you are building an extension for as *specific* system, or for yourself
614+
only, you might instead want to run your compiler directly.
615+
The way to do this is system-specific; be prepared for issues you will need
616+
to solve yourself.
617+
618+
Linux
619+
^^^^^
620+
621+
On Linux, the Python development package may include a ``python3-config``
622+
command that prints out the required compiler flags.
623+
If you use it, check that it corresponds to the CPython interpreter you'll use
624+
to load the module.
625+
Then, start with the following command:
626+
627+
.. code-block:: sh
628+
629+
gcc --shared $(python3-config --cflags --ldflags) spammodule.c -o spam.so
630+
631+
This should generate a ``spam.so`` file that you need to put in a directory
632+
on :py:attr:`sys.path`.
633+
634+
525635
.. rubric:: Footnotes
526636

527637
.. [#why-spam] ``spam`` is the favorite food of Monty Python fans...
@@ -537,22 +647,6 @@ Here is the entire source file, for your convenience:
537647
:ref:`several other standard headers <capi-system-includes>` for its own use
538648
or for backwards compatibility.
539649
However, it is good practice to explicitly include what you need.
540-
.. [#compile-directly] Using a third-party build tool is heavily recommended,
541-
as it will take
542-
care of various details of your platform and Python installation,
543-
of naming the resulting extension, and, later, of distributing your work.
544-
545-
If you don't want to use a tool, you can try to run your compiler directly.
546-
The following command should work on Linux systems that include a
547-
correctly configured ``python-config`` command that corresponds to the
548-
CPython interpreter you use.
549-
It should generate a ``spam.so`` file that you need to put in a directory
550-
on :py:attr:`sys.path`.
551-
552-
.. code-block:: sh
553-
554-
gcc --shared $(python-config --cflags --ldflags) spammodule.c -o spam.so
555-
556650
.. [#why-pymethoddef] The :c:type:`!PyMethodDef` structure is also used
557651
to create methods of classes, so there's no separate
558652
":c:type:`!PyFunctionDef`".

0 commit comments

Comments
 (0)