Concrete example on how to set the Py_COMPAT_API_VERSION
macro on the command line by passing CFLAGS
flags to setuptools, build and pip.
First, let’s see how build and pip render compiler warnings.
Build an extension using PyImport_ImportModuleNoBlock()
with python3.13 -m build
(unfold to see logs):
python -m build
$ python3.13 -m build
* Creating venv isolated environment...
* Installing packages in isolated environment... (setuptools >= 40.8.0)
* Getting build dependencies for sdist...
running egg_info
creating test_pythoncapi_compat.egg-info
writing test_pythoncapi_compat.egg-info/PKG-INFO
writing dependency_links to test_pythoncapi_compat.egg-info/dependency_links.txt
writing top-level names to test_pythoncapi_compat.egg-info/top_level.txt
writing manifest file 'test_pythoncapi_compat.egg-info/SOURCES.txt'
reading manifest file 'test_pythoncapi_compat.egg-info/SOURCES.txt'
writing manifest file 'test_pythoncapi_compat.egg-info/SOURCES.txt'
* Building sdist...
running sdist
running egg_info
writing test_pythoncapi_compat.egg-info/PKG-INFO
writing dependency_links to test_pythoncapi_compat.egg-info/dependency_links.txt
writing top-level names to test_pythoncapi_compat.egg-info/top_level.txt
reading manifest file 'test_pythoncapi_compat.egg-info/SOURCES.txt'
writing manifest file 'test_pythoncapi_compat.egg-info/SOURCES.txt'
warning: sdist: standard file not found: should have one of README, README.rst, README.txt, README.md
running check
creating test_pythoncapi_compat-0.0.0
creating test_pythoncapi_compat-0.0.0/test_pythoncapi_compat.egg-info
copying files to test_pythoncapi_compat-0.0.0...
copying extension.c -> test_pythoncapi_compat-0.0.0
copying setup.py -> test_pythoncapi_compat-0.0.0
copying test_pythoncapi_compat.egg-info/PKG-INFO -> test_pythoncapi_compat-0.0.0/test_pythoncapi_compat.egg-info
copying test_pythoncapi_compat.egg-info/SOURCES.txt -> test_pythoncapi_compat-0.0.0/test_pythoncapi_compat.egg-info
copying test_pythoncapi_compat.egg-info/dependency_links.txt -> test_pythoncapi_compat-0.0.0/test_pythoncapi_compat.egg-info
copying test_pythoncapi_compat.egg-info/top_level.txt -> test_pythoncapi_compat-0.0.0/test_pythoncapi_compat.egg-info
copying test_pythoncapi_compat.egg-info/SOURCES.txt -> test_pythoncapi_compat-0.0.0/test_pythoncapi_compat.egg-info
Writing test_pythoncapi_compat-0.0.0/setup.cfg
Creating tar archive
removing 'test_pythoncapi_compat-0.0.0' (and everything under it)
* Building wheel from sdist
* Creating venv isolated environment...
* Installing packages in isolated environment... (setuptools >= 40.8.0)
* Getting build dependencies for wheel...
running egg_info
writing test_pythoncapi_compat.egg-info/PKG-INFO
writing dependency_links to test_pythoncapi_compat.egg-info/dependency_links.txt
writing top-level names to test_pythoncapi_compat.egg-info/top_level.txt
reading manifest file 'test_pythoncapi_compat.egg-info/SOURCES.txt'
writing manifest file 'test_pythoncapi_compat.egg-info/SOURCES.txt'
* Installing packages in isolated environment... (wheel)
* Building wheel...
running bdist_wheel
running build
running build_ext
building 'example_python_cext' extension
creating build
creating build/temp.linux-x86_64-cpython-313
gcc -fno-strict-overflow -Wsign-compare -DDYNAMIC_ANNOTATIONS_ENABLED=1 -DNDEBUG -fexceptions -fcf-protection -fexceptions -fcf-protection -fexceptions -fcf-protection -fPIC -I/tmp/build-env-y7i1zt8e/include -I/usr/include/python3.13 -c extension.c -o build/temp.linux-x86_64-cpython-313/extension.o
extension.c: In function ‘get_python_version’:
extension.c:19:5: warning: ‘PyImport_ImportModuleNoBlock’ is deprecated [-Wdeprecated-declarations]
19 | PyObject *mod = PyImport_ImportModuleNoBlock("sys");
| ^~~~~~~~
In file included from /usr/include/python3.13/Python.h:113,
from extension.c:1:
/usr/include/python3.13/import.h:54:44: note: declared here
54 | Py_DEPRECATED(3.13) PyAPI_FUNC(PyObject *) PyImport_ImportModuleNoBlock(
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~
creating build/lib.linux-x86_64-cpython-313
gcc -shared build/temp.linux-x86_64-cpython-313/extension.o -L/usr/lib64 -o build/lib.linux-x86_64-cpython-313/example_python_cext.cpython-313-x86_64-linux-gnu.so
installing to build/bdist.linux-x86_64/wheel
running install
running install_lib
creating build/bdist.linux-x86_64
creating build/bdist.linux-x86_64/wheel
copying build/lib.linux-x86_64-cpython-313/example_python_cext.cpython-313-x86_64-linux-gnu.so -> build/bdist.linux-x86_64/wheel
running install_egg_info
running egg_info
writing test_pythoncapi_compat.egg-info/PKG-INFO
writing dependency_links to test_pythoncapi_compat.egg-info/dependency_links.txt
writing top-level names to test_pythoncapi_compat.egg-info/top_level.txt
reading manifest file 'test_pythoncapi_compat.egg-info/SOURCES.txt'
writing manifest file 'test_pythoncapi_compat.egg-info/SOURCES.txt'
Copying test_pythoncapi_compat.egg-info to build/bdist.linux-x86_64/wheel/test_pythoncapi_compat-0.0.0-py3.13.egg-info
running install_scripts
creating build/bdist.linux-x86_64/wheel/test_pythoncapi_compat-0.0.0.dist-info/WHEEL
creating '/home/vstinner/myprojects/example_python_cext/dist/.tmp-j2nj1ll4/test_pythoncapi_compat-0.0.0-cp313-cp313-linux_x86_64.whl' and adding 'build/bdist.linux-x86_64/wheel' to it
adding 'example_python_cext.cpython-313-x86_64-linux-gnu.so'
adding 'test_pythoncapi_compat-0.0.0.dist-info/METADATA'
adding 'test_pythoncapi_compat-0.0.0.dist-info/WHEEL'
adding 'test_pythoncapi_compat-0.0.0.dist-info/top_level.txt'
adding 'test_pythoncapi_compat-0.0.0.dist-info/RECORD'
removing build/bdist.linux-x86_64/wheel
Successfully built test_pythoncapi_compat-0.0.0.tar.gz and test_pythoncapi_compat-0.0.0-cp313-cp313-linux_x86_64.whl
Did you spot the warning: ‘PyImport_ImportModuleNoBlock’ is deprecated [-Wdeprecated-declarations]
warning in these 89 lines of logs? I hope so, since I asked you to look for compiler warnings
But honestly, it’s easy to miss such warning if you don’t pay attention.
Now if I build and install the same extension with pip (unfold to see logs):
pip install
$ python3.13 -m pip install .
Defaulting to user installation because normal site-packages is not writeable
Processing /home/vstinner/myprojects/example_python_cext
Installing build dependencies ... done
Getting requirements to build wheel ... done
Preparing metadata (pyproject.toml) ... done
Building wheels for collected packages: example_python_cext
Building wheel for example_python_cext (pyproject.toml) ... done
Created wheel for example_python_cext: filename=example_python_cext-0.0.0-cp313-cp313-linux_x86_64.whl size=4037 sha256=0b7abb2627cc515dd7bd31ab7b1832f41f1282030a371b42b21c0d7f2c9984f8
Stored in directory: /tmp/pip-ephem-wheel-cache-5udgoufe/wheels/79/8d/7b/7a75feee85b68ebcdbf357b59ce0b9ce7e5a78ea43b0a7f6bf
Successfully built example_python_cext
Installing collected packages: example_python_cext
Successfully installed example_python_cext-0.0.0
Did you spot the compiler warning this time? Nope, it’s hidden: compiler logs are not written.
Ok, now let’s try again with PEP 743. I set the Py_COMPAT_API_VERSION
macro to Py_COMPAT_API_VERSION_MAX
.
python -m build
logs:
$ CC=clang CFLAGS="-DPy_COMPAT_API_VERSION=Py_COMPAT_API_VERSION_MAX" ../env/bin/python -m build
(...)
extension.c:19:21: error: call to undeclared function 'PyImport_ImportModuleNoBlock'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
19 | PyObject *mod = PyImport_ImportModuleNoBlock("sys");
| ^
extension.c:19:15: error: incompatible integer to pointer conversion initializing 'PyObject *' (aka 'struct _object *') with an expression of type 'int' [-Wint-conversion]
19 | PyObject *mod = PyImport_ImportModuleNoBlock("sys");
| ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2 errors generated.
error: command '/usr/bin/clang' failed with exit code 1
ERROR Backend subprocess exited when trying to invoke build_wheel
Ok, this time, the build fails since the function is no longer defined.
python -m pip install
logs:
$ CC=clang CFLAGS="-DPy_COMPAT_API_VERSION=Py_COMPAT_API_VERSION_MAX" ../env/bin/python -m pip install .
(...)
Building wheels for collected packages: example_python_cext
Building wheel for example_python_cext (pyproject.toml) ... error
error: subprocess-exited-with-error
× Building wheel for example_python_cext (pyproject.toml) did not run successfully.
│ exit code: 1
╰─> [15 lines of output]
running bdist_wheel
running build
running build_ext
building 'example_python_cext' extension
creating build
creating build/temp.linux-x86_64-cpython-313-pydebug
clang -fno-strict-overflow -Wsign-compare -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -g -Og -Wall -DPy_COMPAT_API_VERSION=Py_COMPAT_API_VERSION_MAX -fPIC -I/home/vstinner/myprojects/env/include -I/home/vstinner/python/main/Include -I/home/vstinner/python/main -c extension.c -o build/temp.linux-x86_64-cpython-313-pydebug/extension.o
extension.c:19:21: error: call to undeclared function 'PyImport_ImportModuleNoBlock'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
19 | PyObject *mod = PyImport_ImportModuleNoBlock("sys");
| ^
extension.c:19:15: error: incompatible integer to pointer conversion initializing 'PyObject *' (aka 'struct _object *') with an expression of type 'int' [-Wint-conversion]
19 | PyObject *mod = PyImport_ImportModuleNoBlock("sys");
| ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2 errors generated.
error: command '/usr/bin/clang' failed with exit code 1
[end of output]
note: This error originates from a subprocess, and is likely not a problem with pip.
ERROR: Failed building wheel for example_python_cext
Failed to build example_python_cext
ERROR: Could not build wheels for example_python_cext, which is required to install pyproject.toml-based projects
Same with pip, the build fails as expected.
I cheated by using clang instead of gcc. Apparently, GCC 13 stills treats -Wimplicit-function-declaration
as a warning, but GCC 14 will treat it as an error to prepare the ecosystem for C23. clang is used by default on macOS and FreeBSD. Or you can simply treat this warning as an error.
Later, we can consider adding options to build and pip to set the Py_COMPAT_API_VERSION
macro for us and pass the right compiler flags to treat -Wimplicit-function-declaration
as error.