Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ A collection of small, self-contained example packages under `projects/`, each d
Projects fall into distinct families; know which one you're editing before changing build config:

- **Legacy `scikit-build`** (`build-backend = "setuptools.build_meta"`, has `setup.py`): `hello-pure`, `hello-cpp`, `hello-cython`, `hello-pybind11`, `pen2-cython`, `tower-of-babel`. Build requires include `scikit-build`, `cmake`, `ninja` in `[build-system].requires`. Metadata lives in `setup.py`.
- **Modern `scikit-build-core`** (`build-backend = "scikit_build_core.build"`, pyproject-only, no `setup.py`): `core-c-hello`, `core-cython-hello`, `core-pybind11-hello`, `core-nanobind-shared`, `hello-free-threading`, `pi-fortran`, `hello-cmake-package`. Metadata lives in `[project]`; configure via `[tool.scikit-build]`.
- **Modern `scikit-build-core`** (`build-backend = "scikit_build_core.build"`, pyproject-only, no `setup.py`): `core-c-hello`, `core-cffi-hello`, `core-cython-hello`, `core-pybind11-hello`, `core-nanobind-shared`, `hello-free-threading`, `pi-fortran`, `hello-cmake-package`. Metadata lives in `[project]`; configure via `[tool.scikit-build]`.
- **Hatchling + scikit-build-core plugin** (`build-backend = "hatchling.build"`): `hatchling-pybind11-hello`, using `[tool.hatch.build.targets.wheel.hooks.scikit-build]`.

Language bindings vary per project: pure Python, raw C/C++, pybind11, nanobind (`core-nanobind-shared`, which also demonstrates linking to a shared library shipped in the wheel), Cython, Fortran (`pi-fortran`). `tower-of-babel` is the broadest, exercising Boost + Cython static/shared linkage.
Language bindings vary per project: pure Python, raw C/C++, pybind11, nanobind (`core-nanobind-shared`, which also demonstrates linking to a shared library shipped in the wheel), cffi (`core-cffi-hello`, generating the extension source with `cffi-gen-src`), Cython, Fortran (`pi-fortran`). `tower-of-babel` is the broadest, exercising Boost + Cython static/shared linkage.

## Commands

Expand All @@ -30,7 +30,7 @@ Development is driven by nox (`noxfile.py`). Use `uvx nox` (uv is the assumed ru

`noxfile.py` defines which projects participate, with platform/version gates that must be respected when adding or moving a project:

- `hello_list` (tested by `test`): the four `hello-*` setuptools projects, plus `hello-cmake-package` and `pi-fortran` (**non-Windows only**), plus `hello-free-threading` (**Python 3.13+ only**).
- `hello_list` (tested by `test`): the four `hello-*` setuptools projects, plus `hello-cmake-package` and `pi-fortran` (**non-Windows only**), plus `core-cffi-hello` (**Python 3.10+ only**, also in `long_hello_list`) and `hello-free-threading` (**Python 3.13+ only**).
- `long_hello_list` (built by `dist`): `hello_list` plus `pen2-cython`, `core-c-hello`, `core-pybind11-hello`, `hatchling-pybind11-hello`.
- `hello-free-threading` and `core-nanobind-shared` are also wheel-built and tested via cibuildwheel in CI (`cibuildwheel` job); `hello-free-threading` only through that job, not nox.

Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ configured via `[tool.scikit-build]`.
| Project | Demonstrates |
| --- | --- |
| [`core-c-hello`](projects/core-c-hello) | Raw C extension |
| [`core-cffi-hello`](projects/core-cffi-hello) | [cffi](https://cffi.readthedocs.io) via the `cffi-gen-src` source generator; requires Python 3.10+ |
| [`core-cython-hello`](projects/core-cython-hello) | Cython via [cython-cmake](https://github.com/scikit-build/cython-cmake) |
| [`core-pybind11-hello`](projects/core-pybind11-hello) | pybind11 binding |
| [`core-nanobind-shared`](projects/core-nanobind-shared) | [nanobind](https://nanobind.readthedocs.io) binding linking a shared library shipped in the wheel |
Expand Down
4 changes: 4 additions & 0 deletions noxfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@
"core-pybind11-hello",
"hatchling-pybind11-hello",
]
if sys.version_info >= (3, 10):
# cffi 2.1 (first release with cffi-gen-src) requires Python 3.10
hello_list.append("core-cffi-hello")
long_hello_list.append("core-cffi-hello")
if sys.version_info >= (3, 13):
hello_list.append("hello-free-threading")

Expand Down
28 changes: 28 additions & 0 deletions projects/core-cffi-hello/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
cmake_minimum_required(VERSION 3.15...4.3)

project(${SKBUILD_PROJECT_NAME} VERSION ${SKBUILD_PROJECT_VERSION} LANGUAGES C)

find_package(Python REQUIRED COMPONENTS Interpreter Development.Module
${SKBUILD_SABI_COMPONENT})

set(HELLO_CDEF "${CMAKE_CURRENT_SOURCE_DIR}/src/hello/_hello.cdef.txt")
set(HELLO_CSRC "${CMAKE_CURRENT_SOURCE_DIR}/src/hello/_hello.csrc.c")
add_custom_command(
OUTPUT _hello.c
COMMAND Python::Interpreter -m cffi.gen_src read-sources hello._hello
"${HELLO_CDEF}" "${HELLO_CSRC}" _hello.c
DEPENDS "${HELLO_CDEF}" "${HELLO_CSRC}"
VERBATIM)

# cffi's generated code targets the limited API unless
# _CFFI_NO_LIMITED_API is defined (or unavailable, like on 3.14t)
if(NOT "${SKBUILD_SABI_COMPONENT}" STREQUAL "")
python_add_library(_hello MODULE USE_SABI "${SKBUILD_SABI_VERSION}" WITH_SOABI
"${CMAKE_CURRENT_BINARY_DIR}/_hello.c" csrc/square.c)
else()
python_add_library(_hello MODULE WITH_SOABI
"${CMAKE_CURRENT_BINARY_DIR}/_hello.c" csrc/square.c)
endif()
target_include_directories(_hello PRIVATE csrc)

install(TARGETS _hello DESTINATION hello)
49 changes: 49 additions & 0 deletions projects/core-cffi-hello/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# core-cffi-hello

A minimal [scikit-build-core](https://scikit-build-core.readthedocs.io) +
[cffi](https://cffi.readthedocs.io) project that generates the extension
source at build time with the
[`cffi-gen-src`](https://cffi.readthedocs.io/en/stable/cffi-gen-src.html) CLI
(new in cffi 2.1). It is the scikit-build-core analog of the meson-python
example in the cffi documentation.

## Layout

- `csrc/`: a plain C library (`square.c`/`square.h`) with no Python or cffi
knowledge.
- `src/hello/_hello.cdef.txt` and `src/hello/_hello.csrc.c`: the FFI
definitions and the C source prelude, read by `cffi-gen-src read-sources`
and turned into `_hello.c`. This replaces the classic builder script (an
`FFI` object with `cdef()` and `set_source()`); if you already have one,
the `exec-python` subcommand runs it directly instead.
- `src/hello/`: the pure Python package, picked up automatically because it
matches the project name. `wheel.exclude` keeps the cdef/csrc build inputs
out of the wheel.

## The parts that trip people up

- **Run the generator from CMake.** An `add_custom_command` invokes
`python -m cffi.gen_src read-sources hello._hello <cdef> <csrc> _hello.c`
(the module form of the `cffi-gen-src` script), so cffi from
`[build-system].requires` is used. The generated file lands in the build
tree and is compiled together with `csrc/square.c` by `python_add_library`.
The module name (`hello._hello`) is a command-line argument here, not
something read from a file β€” it must match where the extension is
installed.
- **Compile/link flags move to CMake.** `cffi-gen-src` ignores the
`libraries=`, `include_dirs=`, `library_dirs=`, and `extra_compile_args=`
arguments of `set_source()`; use `target_include_directories`,
`target_link_libraries`, etc. instead.
- **The limited API / abi3.** cffi's generated code targets the limited API;
on MSVC release builds it defines `Py_LIMITED_API` on its own (unless
`CFFI_NO_LIMITED_API` is defined), which makes `pyconfig.h` auto-link
`python3.lib` β€” and the link fails if CMake linked the version-specific
library instead. This sample leans in: `wheel.py-api = "cp310"` plus
`python_add_library(... USE_SABI 3.10 ...)` produce a single abi3 wheel
for all CPython >= 3.10. If you want per-version wheels instead, define
`CFFI_NO_LIMITED_API` when compiling on MSVC.
- **cffi is a runtime dependency.** The compiled module imports
`_cffi_backend` when loaded, so `cffi` stays in `[project].dependencies`.
- **Python 3.10+.** cffi 2.1 is the first release shipping `cffi-gen-src`
and requires Python 3.10, so `requires-python` (and the gate in the
top-level `noxfile.py`) reflect that.
3 changes: 3 additions & 0 deletions projects/core-cffi-hello/csrc/square.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#include "square.h"

int square(int n) { return n * n; }
3 changes: 3 additions & 0 deletions projects/core-cffi-hello/csrc/square.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#pragma once

int square(int n);
22 changes: 22 additions & 0 deletions projects/core-cffi-hello/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[build-system]
requires = ["scikit-build-core", "cffi>=2.1"]
build-backend = "scikit_build_core.build"

[project]
name = "hello"
version = "1.2.3"
authors = [
{ name = "The scikit-build team" },
]
description = "a minimal example package (cffi cffi-gen-src version)"
# cffi 2.1, the first release with cffi-gen-src, requires Python 3.10
requires-python = ">=3.10"
license = "MIT"
# The generated extension imports _cffi_backend at runtime
dependencies = ["cffi"]

[tool.scikit-build]
# The cdef/csrc files are build-time inputs, not part of the package
wheel.exclude = ["hello/_hello.cdef.txt", "hello/_hello.csrc.c"]
# cffi's generated code is limited-API compatible; ship one abi3 wheel
wheel.py-api = "cp310.cp315t"
7 changes: 7 additions & 0 deletions projects/core-cffi-hello/src/hello/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from ._hello import lib

__all__ = ["square"]


def square(n: int) -> int:
return lib.square(n)
1 change: 1 addition & 0 deletions projects/core-cffi-hello/src/hello/_hello.cdef.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
int square(int n);
1 change: 1 addition & 0 deletions projects/core-cffi-hello/src/hello/_hello.csrc.c
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
#include "square.h"
5 changes: 5 additions & 0 deletions projects/core-cffi-hello/tests/test_hello.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import hello


def test_square():
assert hello.square(4) == 16
Loading