While pyproject.toml-based configuration will be enough for most projects,
sometimes you may need to use custom logic and imperative programming during the build.
For those scenarios, setuptools also allows you to specify project configuration
via setup.py in addition to pyproject.toml.
The following is a very basic tutorial that shows how to use setuptools-rust in
your setup.py.
Let's start by assuming that you already have a bunch of Python and Rust files1
that you would like to package for distribution in PyPI inside of a project directory
named hello-world-setuppy23:
hello-world-setuppy
├── Cargo.lock
├── Cargo.toml
├── python
│ └── hello_world
│ └── __init__.py
└── rust
└── lib.rs
:language: python
:language: rust
:language: toml
Now we start by adding a pyproject.toml which tells anyone that wants to use
our project to use setuptools and setuptools-rust to build it:
:language: toml
… and a setup.py configuration file
that tells Setuptools how to build the Rust extensions using our Cargo.toml and setuptools-rust:
:language: python
For a complete reference of the options supported by the RustExtension class, see the
API reference.
We also add a MANIFEST.in file
to control which files we want in the source distribution4:
With these files in place, you can install the project in a virtual environment for testing and making sure everything is working correctly:
# cd hello-world-setuppy
python3 -m venv .venv
source .venv/bin/activate # on Linux or macOS
.venv\Scripts\activate # on Windows
python -m pip install -e .
python -c 'import hello_world; print(hello_world.sum_as_string(5, 7))' # => 12
# ... better write some tests with pytest ...-
When you are ready to distribute your project, have a look on the notes in the documentation about building wheels.
-
You can also use a
RustBinobject (instead of aRustExtension), if you want to distribute a binary executable written in Rust (instead of a library that can be imported by the Python runtime). Note however that distributing both library and executable (or multiple executables), may significantly increase the size of the wheel file distributed by the package index and therefore increase build, download and installation times. Another approach is to use a Python entry-point that calls the Rust implementation (exposed via PyO3 bindings). See the hello-world example for more insights. -
If want to include both
RustBinandRustExtensionsame macOS wheel, you might have to manually add an extrabuild.rsfile, see PyO3/setuptools-rust#351 for more information about the workaround. -
If your Rust extension generates files as part of its
build.rsbuild script that you want to be present in your Python wheel, you can use thegenerated_filesargument ofRustExtensionto define which files should be copied across, and into which locations in the Python package. -
Since the adoption of {pep}
517, runningpython setup.py ...directly as a CLI tool is considered deprecated. Nevertheless,setup.pycan be safely used as a configuration file (the same wayconftest.pyis used bypytestornoxfile.pyis used bynox). There is a different mindset that comes with this change, though: for example, it does not make sense to usesys.exit(0)in asetup.pyfile or use a overarchingtry...except...block to re-run a failed build with different parameters.
Footnotes
-
To know more about how to write Rust to be integrated into Python packages, please have a look on the PyO3 docs ↩
-
You can have a look on the examples/hello-world-setuppy directory in the
setuptools-rustrepository. ↩ -
If you are an experienced Python or Rust programmer, you may notice that we avoid using the
srcdirectory and explicitly instruct Setuptools and Cargo to look into thepythonandrustdirectories respectively. Since both Python and Rust ecosystem will try to claim thesrcdirectory as their default, we prefer to be explicit and avoid confusion. ↩ -
Alternatively you can also use
setuptools-scmto add all the files under revision control to thesdist, see the docs for more information. ↩