Skip to content

Commit 437f162

Browse files
committed
fix: modernize Rust example, and render into docs
Signed-off-by: Henry Schreiner <[email protected]>
1 parent 4cbdee2 commit 437f162

File tree

4 files changed

+141
-57
lines changed

4 files changed

+141
-57
lines changed

docs/pages/guides/packaging_compiled.md

Lines changed: 112 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,24 @@ parent: Topical Guides
88

99
{% include toc.html %}
1010

11+
<!-- [[[cog
12+
from cog_helpers import code_fence, render_cookie, TOMLMatcher
13+
with render_cookie(backend="skbuild", vcs=False) as skbuild:
14+
skbuild_cmakelists_txt = skbuild.joinpath("CMakeLists.txt").read_text(encoding="utf-8").strip()
15+
skbuild_src_main_cpp = skbuild.joinpath("src/main.cpp").read_text(encoding="utf-8").strip()
16+
skbuild_pyproject = TOMLMatcher.from_file(skbuild / "pyproject.toml")
17+
with render_cookie(backend="mesonpy", vcs=False) as mesonpy:
18+
mesonpy_meson_build = mesonpy.joinpath("meson.build").read_text(encoding="utf-8").strip()
19+
mesonpy_src_main_cpp = mesonpy.joinpath("src/main.cpp").read_text(encoding="utf-8").strip()
20+
mesonpy_pyproject = TOMLMatcher.from_file(mesonpy / "pyproject.toml")
21+
assert "tool.meson-python" not in mesonpy_pyproject
22+
with render_cookie(backend="maturin", vcs=False) as maturin:
23+
maturin_cargo_toml = maturin.joinpath("Cargo.toml").read_text(encoding="utf-8").strip()
24+
maturin_src_lib_rs = maturin.joinpath("src/lib.rs").read_text(encoding="utf-8").strip()
25+
maturin_pyproject = TOMLMatcher.from_file(maturin / "pyproject.toml")
26+
]]] -->
27+
<!-- [[[end]]] -->
28+
1129
# Packaging Compiled Projects
1230

1331
There are a variety of ways to package compiled projects. In the past, the only
@@ -52,27 +70,48 @@ selects the backend:
5270

5371
{% tabs %} {% tab skbc Scikit-build-core %}
5472

73+
<!-- [[[cog
74+
with code_fence("toml"):
75+
print(skbuild_pyproject.get_source("build-system"))
76+
]]] -->
77+
<!-- prettier-ignore-start -->
5578
```toml
5679
[build-system]
57-
requires = ["scikit-build-core"]
80+
requires = ["pybind11", "scikit-build-core>=0.11"]
5881
build-backend = "scikit_build_core.build"
5982
```
83+
<!-- prettier-ignore-end -->
84+
<!-- [[[end]]] -->
6085

6186
{% endtab %} {% tab meson Meson-python %}
6287

88+
<!-- [[[cog
89+
with code_fence("toml"):
90+
print(mesonpy_pyproject.get_source("build-system"))
91+
]]] -->
92+
<!-- prettier-ignore-start -->
6393
```toml
6494
[build-system]
65-
requires = ["meson-python"]
95+
requires = ["meson-python>=0.18", "pybind11"]
6696
build-backend = "mesonpy"
6797
```
98+
<!-- prettier-ignore-end -->
99+
<!-- [[[end]]] -->
68100

69101
{% endtab %} {% tab maturin Maturin %}
70102

103+
<!-- [[[cog
104+
with code_fence("toml"):
105+
print(maturin_pyproject.get_source("build-system"))
106+
]]] -->
107+
<!-- prettier-ignore-start -->
71108
```toml
72109
[build-system]
73-
requires = ["maturin"]
110+
requires = ["maturin>=1.9,<2"]
74111
build-backend = "maturin"
75112
```
113+
<!-- prettier-ignore-end -->
114+
<!-- [[[end]]] -->
76115

77116
{% endtab %} {% endtabs %}
78117

@@ -83,20 +122,49 @@ build-backend = "maturin"
83122
These tools all read the project table. They also have extra configuration
84123
options in `tool.*` settings.
85124

125+
{% tabs %} {% tab skbc Scikit-build-core %}
126+
86127
<!-- [[[cog
87-
from cog_helpers import code_fence, render_cookie
88-
with render_cookie(backend="skbuild") as skbuild:
89-
skbuild_cmakelists_txt = skbuild.joinpath("CMakeLists.txt").read_text(encoding="utf-8").strip()
90-
skbuild_src_main_cpp = skbuild.joinpath("src/main.cpp").read_text(encoding="utf-8").strip()
91-
with render_cookie(backend="mesonpy") as mesonpy:
92-
mesonpy_meson_build = mesonpy.joinpath("meson.build").read_text(encoding="utf-8").strip()
93-
mesonpy_src_main_cpp = mesonpy.joinpath("src/main.cpp").read_text(encoding="utf-8").strip()
94-
with render_cookie(backend="maturin") as maturin:
95-
maturin_cargo_toml = maturin.joinpath("Cargo.toml").read_text(encoding="utf-8").strip()
96-
maturin_src_lib_rs = maturin.joinpath("src/lib.rs").read_text(encoding="utf-8").strip()
128+
with code_fence("toml"):
129+
print(skbuild_pyproject.get_source("tool.scikit-build"))
97130
]]] -->
131+
<!-- prettier-ignore-start -->
132+
```toml
133+
[tool.scikit-build]
134+
minimum-version = "build-system.requires"
135+
build-dir = "build/{wheel_tag}"
136+
```
137+
<!-- prettier-ignore-end -->
98138
<!-- [[[end]]] -->
99139

140+
These options are not required, but can improve your experience.
141+
142+
{% endtab %} {% tab meson Meson-python %}
143+
144+
No `tool.meson-python` configuration required for this example.
145+
146+
{% endtab %} {% tab maturin Maturin %}
147+
148+
<!-- [[[cog
149+
with code_fence("toml"):
150+
print(maturin_pyproject.get_source("tool.maturin"))
151+
]]] -->
152+
<!-- prettier-ignore-start -->
153+
```toml
154+
[tool.maturin]
155+
module-name = "package._core"
156+
python-packages = ["package"]
157+
python-source = "src"
158+
sdist-generator = "git" # default is cargo
159+
```
160+
<!-- prettier-ignore-end -->
161+
<!-- [[[end]]] -->
162+
163+
Maturin assumes you follow Rust's package structure, so we need a little bit of
164+
configuration here to follow the convention of the other tools here.
165+
166+
{% endtab %} {% endtabs %}
167+
100168
## Backend specific files
101169

102170
{% tabs %} {% tab skbc Scikit-build-core %}
@@ -180,18 +248,18 @@ with code_fence("toml"):
180248
[package]
181249
name = "package"
182250
version = "0.1.0"
183-
edition = "2018"
251+
edition = "2021"
184252

185253
[lib]
186254
name = "_core"
187255
# "cdylib" is necessary to produce a shared library for Python to import from.
188256
crate-type = ["cdylib"]
189257

190258
[dependencies]
191-
rand = "0.8.3"
259+
rand = "0.9.2"
192260

193261
[dependencies.pyo3]
194-
version = "0.19.1"
262+
version = "0.27.2"
195263
# "extension-module" tells pyo3 we want to build an extension module (skips linking against libpython.so)
196264
# "abi3-py310" tells pyo3 (and maturin) to build using the stable ABI with minimum Python version 3.10
197265
features = ["extension-module", "abi3-py310"]
@@ -302,26 +370,29 @@ with code_fence("rs"):
302370
```rs
303371
use pyo3::prelude::*;
304372

305-
#[pyfunction]
306-
fn add(x: i64, y: i64) -> i64 {
307-
x + y
308-
}
309-
310-
#[pyfunction]
311-
fn subtract(x: i64, y: i64) -> i64 {
312-
x - y
313-
}
314-
315373
/// A Python module implemented in Rust. The name of this function must match
316374
/// the `lib.name` setting in the `Cargo.toml`, else Python will not be able to
317375
/// import the module.
318376
#[pymodule]
319-
fn _core(_py: Python, m: &PyModule) -> PyResult<()> {
320-
m.add_function(wrap_pyfunction!(add, m)?)?;
321-
m.add_function(wrap_pyfunction!(subtract, m)?)?;
322-
m.add("__version__", env!("CARGO_PKG_VERSION"))?;
377+
mod _core {
378+
use super::*;
379+
380+
#[pyfunction]
381+
fn add(x: i64, y: i64) -> i64 {
382+
x + y
383+
}
384+
385+
#[pyfunction]
386+
fn subtract(x: i64, y: i64) -> i64 {
387+
x - y
388+
}
389+
323390

324-
Ok(())
391+
#[pymodule_init]
392+
fn pymodule_init(m: &Bound<'_, PyModule>) -> PyResult<()> {
393+
m.add("__version__", env!("CARGO_PKG_VERSION"))?;
394+
Ok(())
395+
}
325396
}
326397
```
327398
<!-- prettier-ignore-end -->
@@ -332,7 +403,8 @@ fn _core(_py: Python, m: &PyModule) -> PyResult<()> {
332403
## Package structure
333404

334405
The recommendation (followed above) is to have source code in `/src`, and the
335-
Python package files in `/src/<package>`.
406+
Python package files in `/src/<package>`. The compiled files also can go in
407+
`/src`.
336408

337409
## Versioning
338410

@@ -348,25 +420,26 @@ though the defaults are reasonable.
348420

349421
Unlike pure Python, you'll need to build redistributable wheels for each
350422
platform and supported Python version if you want to avoid compilation on the
351-
user's system. See [the CI page on wheels][gha_wheels] for a suggested workflow.
423+
user's system using cibuildwheel. See [the CI page on wheels][gha_wheels] for a
424+
suggested workflow.
352425

353426
## Special considerations
354427

355428
### NumPy
356429

357430
Modern versions of NumPy (1.25+) allow you to target older versions when
358-
building, which is _highly_ recommended, and this will become required in NumPy
359-
2.0. Now you add:
431+
building, which is _highly_ recommended, and this became required in NumPy 2.0.
432+
Now you add:
360433

361434
```cpp
362435
#define NPY_TARGET_VERSION NPY_1_22_API_VERSION
363436
```
364437
365438
(Where that number is whatever version you support as a minimum) then make sure
366-
you build with NumPy 1.25+ (or 2.0+ when it comes out). Before 1.25, it was
367-
necessary to actually pin the oldest NumPy you supported (the
368-
`oldest-supported-numpy` package is the easiest method). If you support Python <
369-
3.9, you'll have to use the old method for those versions.
439+
you build with NumPy 1.25+ (or 2.0+). Before 1.25, it was necessary to actually
440+
pin the oldest NumPy you supported (the `oldest-supported-numpy` package is the
441+
easiest method). If you support Python < 3.9, you'll have to use the old method
442+
for those versions.
370443
371444
If using pybind11, you don't need NumPy at build-time in the first place.
372445

helpers/cog_helpers.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,14 @@ def get_source(self, dotted_name: str, /) -> str:
7373
)
7474
return tomlkit.dumps(toml).strip()
7575

76+
def __contains__(self, dotted_name: str, /) -> bool:
77+
names = dotted_name.split(".")
78+
try:
79+
functools.reduce(lambda d, k: d[k], names, self.toml)
80+
except KeyError:
81+
return False
82+
return True
83+
7684

7785
@contextlib.contextmanager
7886
def code_fence(lang: str, /, *, width: int = 3) -> Generator[None, None, None]:
Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,26 @@
11
use pyo3::prelude::*;
22

3-
#[pyfunction]
4-
fn add(x: i64, y: i64) -> i64 {
5-
x + y
6-
}
7-
8-
#[pyfunction]
9-
fn subtract(x: i64, y: i64) -> i64 {
10-
x - y
11-
}
12-
133
/// A Python module implemented in Rust. The name of this function must match
144
/// the `lib.name` setting in the `Cargo.toml`, else Python will not be able to
155
/// import the module.
166
#[pymodule]
17-
fn _core(_py: Python, m: &PyModule) -> PyResult<()> {
18-
m.add_function(wrap_pyfunction!(add, m)?)?;
19-
m.add_function(wrap_pyfunction!(subtract, m)?)?;
20-
m.add("__version__", env!("CARGO_PKG_VERSION"))?;
7+
mod _core {
8+
use super::*;
9+
10+
#[pyfunction]
11+
fn add(x: i64, y: i64) -> i64 {
12+
x + y
13+
}
14+
15+
#[pyfunction]
16+
fn subtract(x: i64, y: i64) -> i64 {
17+
x - y
18+
}
19+
2120

22-
Ok(())
21+
#[pymodule_init]
22+
fn pymodule_init(m: &Bound<'_, PyModule>) -> PyResult<()> {
23+
m.add("__version__", env!("CARGO_PKG_VERSION"))?;
24+
Ok(())
25+
}
2326
}
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
[package]
22
name = "{{ cookiecutter.__project_slug }}"
33
version = "0.1.0"
4-
edition = "2018"
4+
edition = "2021"
55

66
[lib]
77
name = "_core"
88
# "cdylib" is necessary to produce a shared library for Python to import from.
99
crate-type = ["cdylib"]
1010

1111
[dependencies]
12-
rand = "0.8.3"
12+
rand = "0.9.2"
1313

1414
[dependencies.pyo3]
15-
version = "0.19.1"
15+
version = "0.27.2"
1616
# "extension-module" tells pyo3 we want to build an extension module (skips linking against libpython.so)
1717
# "abi3-py310" tells pyo3 (and maturin) to build using the stable ABI with minimum Python version 3.10
1818
features = ["extension-module", "abi3-py310"]

0 commit comments

Comments
 (0)