|
10 | 10 | Compile and distribute Python extensions written in Rust as easily as if |
11 | 11 | they were written in C. |
12 | 12 |
|
13 | | -## Setup |
| 13 | +## Quickstart |
14 | 14 |
|
15 | | -For a complete example, see |
16 | | -[html-py-ever](https://github.com/PyO3/setuptools-rust/tree/main/examples/html-py-ever). |
| 15 | +The following is a very basic tutorial that shows how to use `setuptools-rust` in `pyproject.toml`. |
| 16 | +It assumes that you already have a bunch of Python and Rust files that you want |
| 17 | +to distribute. You can see examples for these files in the |
| 18 | +[`examples/hello-world`](https://github.com/PyO3/setuptools-rust/tree/main/examples/hello-world) |
| 19 | +directory in the [github repository](https://github.com/PyO3/setuptools-rust). |
| 20 | +The [PyO3 docs](https://pyo3.rs) have detailed information on how to write Python |
| 21 | +modules in Rust. |
17 | 22 |
|
18 | | -First, you need to create a bunch of files: |
19 | | - |
20 | | -### setup.py |
21 | | - |
22 | | -```python |
23 | | -from setuptools import setup |
24 | | -from setuptools_rust import Binding, RustExtension |
25 | | - |
26 | | -setup( |
27 | | - name="hello-rust", |
28 | | - version="1.0", |
29 | | - rust_extensions=[RustExtension("hello_rust.hello_rust", binding=Binding.PyO3)], |
30 | | - packages=["hello_rust"], |
31 | | - # rust extensions are not zip safe, just like C-extensions. |
32 | | - zip_safe=False, |
33 | | -) |
| 23 | +``` |
| 24 | +hello-world |
| 25 | +├── python |
| 26 | +│ └── hello_world |
| 27 | +│ └── __init__.py |
| 28 | +└── rust |
| 29 | + └── lib.rs |
34 | 30 | ``` |
35 | 31 |
|
36 | | -For a complete reference of the options supported by the `RustExtension` class, see the |
37 | | -[API reference](https://setuptools-rust.readthedocs.io/en/latest/reference.html). |
| 32 | +Once the implementation files are in place, we need to add a `pyproject.toml` |
| 33 | +file that tells anyone that wants to use your project how to build it. |
| 34 | +In this file, we use an [array of tables](https://toml.io/en/v1.0.0#array-of-tables) |
| 35 | +(TOML jargon equivalent to Python's list of dicts) for ``[[tool.setuptools-rust.ext-modules]]``, |
| 36 | +to specify different extension modules written in Rust: |
38 | 37 |
|
39 | | -### pyproject.toml |
40 | 38 |
|
41 | 39 | ```toml |
| 40 | +# pyproject.toml |
42 | 41 | [build-system] |
43 | | -requires = ["setuptools", "wheel", "setuptools-rust"] |
| 42 | +requires = ["setuptools", "setuptools-rust"] |
| 43 | +build-backend = "setuptools.build_meta" |
| 44 | + |
| 45 | +[project] |
| 46 | +name = "hello-world" |
| 47 | +version = "1.0" |
| 48 | + |
| 49 | +[tool.setuptools.packages] |
| 50 | +# Pure Python packages/modules |
| 51 | +find = { where = ["python"] } |
| 52 | + |
| 53 | +[[tool.setuptools-rust.ext-modules]] |
| 54 | +# Private Rust extension module to be nested into the Python package |
| 55 | +target = "hello_world._lib" # The last part of the name (e.g. "_lib") has to match lib.name in Cargo.toml, |
| 56 | + # but you can add a prefix to nest it inside of a Python package. |
| 57 | +path = "Cargo.toml" # Default value, can be omitted |
| 58 | +binding = "PyO3" # Default value, can be omitted |
| 59 | +py-limited-api = "auto" # Default value, can be omitted |
44 | 60 | ``` |
45 | 61 |
|
46 | | -### MANIFEST.in |
| 62 | +Each extension module should map directly into the corresponding `[lib]` table on the |
| 63 | +[Cargo manifest file](https://doc.rust-lang.org/cargo/reference/manifest.html): |
47 | 64 |
|
48 | | -This file is required for building source distributions |
49 | | - |
50 | | -```text |
51 | | -include Cargo.toml |
52 | | -recursive-include src * |
| 65 | +```toml |
| 66 | +# Cargo.toml |
| 67 | +[package] |
| 68 | +name = "hello-world" |
| 69 | +version = "0.1.0" |
| 70 | +edition = "2018" |
| 71 | + |
| 72 | +[dependencies] |
| 73 | +pyo3 = { version = "0.19.2", features = ["extension-module"] } |
| 74 | + |
| 75 | +[lib] |
| 76 | +name = "_lib" # private module to be nested into Python package, |
| 77 | + # needs to match the name of the function with the `[#pymodule]` attribute |
| 78 | +path = "rust/lib.rs" |
| 79 | +crate-type = ["cdylib"] # required for shared library for Python to import from. |
| 80 | + |
| 81 | +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html |
| 82 | +# See also PyO3 docs on writing Cargo.toml files at https://pyo3.rs |
53 | 83 | ``` |
54 | 84 |
|
55 | | -## Usage |
56 | | - |
57 | | -You can use same commands as for c-extensions. For example: |
| 85 | +You will also need to tell Setuptools that the Rust files are required to build your |
| 86 | +project from the [source distribution](https://setuptools.pypa.io/en/latest/userguide/miscellaneous.html). |
| 87 | +That can be done either via `MANIFEST.in` (see example below) or via a plugin like |
| 88 | +[`setuptools-scm`](https://pypi.org/project/setuptools-scm/). |
58 | 89 |
|
59 | 90 | ``` |
60 | | ->>> python ./setup.py develop |
61 | | -running develop |
62 | | -running egg_info |
63 | | -writing hello-rust.egg-info/PKG-INFO |
64 | | -writing top-level names to hello_rust.egg-info/top_level.txt |
65 | | -writing dependency_links to hello_rust.egg-info/dependency_links.txt |
66 | | -reading manifest file 'hello_rust.egg-info/SOURCES.txt' |
67 | | -writing manifest file 'hello_rust.egg-info/SOURCES.txt' |
68 | | -running build_ext |
69 | | -running build_rust |
70 | | -cargo build --manifest-path extensions/Cargo.toml --features python3 |
71 | | - Finished debug [unoptimized + debuginfo] target(s) in 0.0 secs |
72 | | -
|
73 | | -Creating /.../lib/python3.6/site-packages/hello_rust.egg-link (link to .) |
74 | | -
|
75 | | -Installed hello_rust |
76 | | -Processing dependencies for hello_rust==1.0 |
77 | | -Finished processing dependencies for hello_rust==1.0 |
| 91 | +# MANIFEST.in |
| 92 | +include Cargo.toml |
| 93 | +recursive-include rust *.rs |
78 | 94 | ``` |
79 | 95 |
|
80 | | -Or you can use commands like `bdist_wheel` (after installing `wheel`). See also [the notes in the documentation about building wheels](https://setuptools-rust.readthedocs.io/en/latest/building_wheels.html). |
81 | | - |
82 | | -Cross-compiling is also supported, using one of [`crossenv`](https://github.com/benfogle/crossenv), [`cross`](https://github.com/rust-embedded/cross) or [`cargo-zigbuild`](https://github.com/messense/cargo-zigbuild). |
83 | | -For examples see the `test-crossenv` and `test-cross` and `test-zigbuild` Github actions jobs in [`ci.yml`](https://github.com/PyO3/setuptools-rust/blob/main/.github/workflows/ci.yml). |
84 | | - |
85 | | -By default, `develop` will create a debug build, while `install` will create a release build. |
86 | | - |
87 | | -## Commands |
| 96 | +With these files in place, you can install the project in a virtual environment |
| 97 | +for testing and making sure everything is working correctly: |
| 98 | + |
| 99 | +```powershell |
| 100 | +# cd hello-world |
| 101 | +python3 -m venv .venv |
| 102 | +source .venv/bin/activate # on Linux or macOS |
| 103 | +.venv\Scripts\activate # on Windows |
| 104 | +python -m pip install -e . |
| 105 | +python |
| 106 | +>>> import hello_world |
| 107 | +# ... try running something from your new extension module ... |
| 108 | +# ... better write some tests with pytest ... |
| 109 | +``` |
88 | 110 |
|
89 | | - - `build` - Standard build command will also build all rust extensions. |
90 | | - - `build_rust` - Command builds all rust extensions. |
91 | | - - `clean` - Standard clean command executes cargo clean for all rust |
92 | | - extensions. |
| 111 | +## Next steps and final remarks |
| 112 | + |
| 113 | +- When you are ready to distribute your project, have a look on |
| 114 | + [the notes in the documentation about building wheels](https://setuptools-rust.readthedocs.io/en/latest/building_wheels.html). |
| 115 | + |
| 116 | +- Cross-compiling is also supported, using one of |
| 117 | + [`crossenv`](https://github.com/benfogle/crossenv), |
| 118 | + [`cross`](https://github.com/rust-embedded/cross) or |
| 119 | + [`cargo-zigbuild`](https://github.com/messense/cargo-zigbuild). |
| 120 | + For examples see the `test-crossenv` and `test-cross` and `test-zigbuild` Github actions jobs in |
| 121 | + [`ci.yml`](https://github.com/PyO3/setuptools-rust/blob/main/.github/workflows/ci.yml). |
| 122 | + |
| 123 | +- You can also use `[[tool.setuptools-rust.bins]]` (instead of `[[tool.setuptools-rust.ext-modules]]`), |
| 124 | + if you want to distribute a binary executable written in Rust (instead of a library that can be imported by the Python runtime). |
| 125 | + Note however that distributing both library and executable (or multiple executables), |
| 126 | + may significantly increase the size of the |
| 127 | + [wheel](https://packaging.python.org/en/latest/glossary/#term-Wheel) |
| 128 | + file distributed by the |
| 129 | + [package index](https://packaging.python.org/en/latest/glossary/#term-Package-Index) |
| 130 | + and therefore increase build, download and installation times. |
| 131 | + Another approach is to use a Python entry-point that calls the Rust |
| 132 | + implementation (exposed via PyO3 bindings). |
| 133 | + See the [hello-world](https://github.com/PyO3/setuptools-rust/tree/main/examples/hello-world) |
| 134 | + example for more insights. |
| 135 | + |
| 136 | +- For a complete reference of the configuration options, see the |
| 137 | + [API reference](https://setuptools-rust.readthedocs.io/en/latest/reference.html). |
| 138 | + You can use any parameter defined by the `RustExtension` class with |
| 139 | + `[[tool.setuptools-rust.ext-modules]]` and any parameter defined by the |
| 140 | + `RustBin` class with `[[tool.setuptools-rust.bins]]`; just remember to replace |
| 141 | + underscore characters `_` with dashes `-` in your `pyproject.toml` file. |
| 142 | + |
| 143 | +- `Cargo.toml` allow only one `[lib]` table per file. |
| 144 | + If you require multiple extension modules you will need to write multiple `Cargo.toml` files. |
| 145 | + Alternatively you can create a single private Rust top-level module that exposes |
| 146 | + multiple submodules (using [PyO3's submodules](https://pyo3.rs/v0.19.2/module#python-submodules)), |
| 147 | + which may also reduce the size of the build artifacts. |
| 148 | + You can always keep your extension modules private and wrap them in pure Python |
| 149 | + to have fine control over the public API. |
| 150 | + |
| 151 | +- If want to include both `[[tool.setuptools-rust.bins]]` and `[[tool.setuptools-rust.ext-modules]]` |
| 152 | + in the same macOS wheel, you might have to manually add an extra `build.rs` file, |
| 153 | + see [PyO3/setuptools-rust#351](https://github.com/PyO3/setuptools-rust/pull/351) |
| 154 | + for more information about the workaround. |
| 155 | + |
| 156 | +- For more examples, see: |
| 157 | + - [`hello-world`](https://github.com/PyO3/setuptools-rust/tree/main/examples/hello-world): |
| 158 | + a more complete version of the code used in this tutorial that mixes both |
| 159 | + `[[tool.setuptools-rust.ext-modules]]` and `[[tool.setuptools-rust.bins]]` |
| 160 | + in a single distribution. |
| 161 | + - [`html-py-ever`](https://github.com/PyO3/setuptools-rust/tree/main/examples/html-py-ever): |
| 162 | + a more advanced example that uses Rust crates as dependencies. |
| 163 | + - [`rust_with_cffi`](https://github.com/PyO3/setuptools-rust/tree/main/examples/rust_with_cffi): |
| 164 | + uses both Rust and [CFFI](https://cffi.readthedocs.io/en/latest/). |
| 165 | + - [`namespace_package`](https://github.com/PyO3/setuptools-rust/tree/main/examples/namespace_package): |
| 166 | + integrates Rust-written modules into PEP 420 namespace packages. |
| 167 | + - [`hello-world-script`](https://github.com/PyO3/setuptools-rust/tree/main/examples/hello-world-script): |
| 168 | + uses Rust only for creating binary executables, not library modules. |
0 commit comments