diff --git a/README.md b/README.md index b4c7c48b..2219acad 100644 --- a/README.md +++ b/README.md @@ -10,83 +10,159 @@ Compile and distribute Python extensions written in Rust as easily as if they were written in C. -## Setup +## Quickstart -For a complete example, see -[html-py-ever](https://github.com/PyO3/setuptools-rust/tree/main/examples/html-py-ever). +The following is a very basic tutorial that shows how to use `setuptools-rust` in `pyproject.toml`. +It assumes that you already have a bunch of Python and Rust files that you want +to distribute. You can see examples for these files in the +[`examples/hello-world`](https://github.com/PyO3/setuptools-rust/tree/main/examples/hello-world) +directory in the [github repository](https://github.com/PyO3/setuptools-rust). +The [PyO3 docs](https://pyo3.rs) have detailed information on how to write Python +modules in Rust. -First, you need to create a bunch of files: - -### setup.py - -```python -from setuptools import setup -from setuptools_rust import Binding, RustExtension - -setup( - name="hello-rust", - version="1.0", - rust_extensions=[RustExtension("hello_rust.hello_rust", binding=Binding.PyO3)], - packages=["hello_rust"], - # rust extensions are not zip safe, just like C-extensions. - zip_safe=False, -) +``` +hello-world +├── python +│ └── hello_world +│ └── __init__.py +└── rust + └── lib.rs ``` -For a complete reference of the options supported by the `RustExtension` class, see the -[API reference](https://setuptools-rust.readthedocs.io/en/latest/reference.html). +Once the implementation files are in place, we need to add a `pyproject.toml` +file that tells anyone that wants to use your project how to build it. +In this file, we use an [array of tables](https://toml.io/en/v1.0.0#array-of-tables) +(TOML jargon equivalent to Python's list of dicts) for ``[[tool.setuptools-rust.ext-modules]]``, +to specify different extension modules written in Rust: -### pyproject.toml ```toml +# pyproject.toml [build-system] -requires = ["setuptools", "wheel", "setuptools-rust"] +requires = ["setuptools", "setuptools-rust"] +build-backend = "setuptools.build_meta" + +[project] +name = "hello-world" +version = "1.0" + +[tool.setuptools.packages] +# Pure Python packages/modules +find = { where = ["python"] } + +[[tool.setuptools-rust.ext-modules]] +# Private Rust extension module to be nested into the Python package +target = "hello_world._lib" # The last part of the name (e.g. "_lib") has to match lib.name in Cargo.toml, + # but you can add a prefix to nest it inside of a Python package. +path = "Cargo.toml" # Default value, can be omitted +binding = "PyO3" # Default value, can be omitted +py-limited-api = "auto" # Default value, can be omitted ``` -### MANIFEST.in +Each extension module should map directly into the corresponding `[lib]` table on the +[Cargo manifest file](https://doc.rust-lang.org/cargo/reference/manifest.html): -This file is required for building source distributions - -```text -include Cargo.toml -recursive-include src * +```toml +# Cargo.toml +[package] +name = "hello-world" +version = "0.1.0" +edition = "2018" + +[dependencies] +pyo3 = { version = "0.19.2", features = ["extension-module"] } + +[lib] +name = "_lib" # private module to be nested into Python package, + # needs to match the name of the function with the `[#pymodule]` attribute +path = "rust/lib.rs" +crate-type = ["cdylib"] # required for shared library for Python to import from. + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +# See also PyO3 docs on writing Cargo.toml files at https://pyo3.rs ``` -## Usage - -You can use same commands as for c-extensions. For example: +You will also need to tell Setuptools that the Rust files are required to build your +project from the [source distribution](https://setuptools.pypa.io/en/latest/userguide/miscellaneous.html). +That can be done either via `MANIFEST.in` (see example below) or via a plugin like +[`setuptools-scm`](https://pypi.org/project/setuptools-scm/). ``` ->>> python ./setup.py develop -running develop -running egg_info -writing hello-rust.egg-info/PKG-INFO -writing top-level names to hello_rust.egg-info/top_level.txt -writing dependency_links to hello_rust.egg-info/dependency_links.txt -reading manifest file 'hello_rust.egg-info/SOURCES.txt' -writing manifest file 'hello_rust.egg-info/SOURCES.txt' -running build_ext -running build_rust -cargo build --manifest-path extensions/Cargo.toml --features python3 - Finished debug [unoptimized + debuginfo] target(s) in 0.0 secs - -Creating /.../lib/python3.6/site-packages/hello_rust.egg-link (link to .) - -Installed hello_rust -Processing dependencies for hello_rust==1.0 -Finished processing dependencies for hello_rust==1.0 +# MANIFEST.in +include Cargo.toml +recursive-include rust *.rs ``` -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). - -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). -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). - -By default, `develop` will create a debug build, while `install` will create a release build. - -## Commands +With these files in place, you can install the project in a virtual environment +for testing and making sure everything is working correctly: + +```powershell +# cd hello-world +python3 -m venv .venv +source .venv/bin/activate # on Linux or macOS +.venv\Scripts\activate # on Windows +python -m pip install -e . +python +>>> import hello_world +# ... try running something from your new extension module ... +# ... better write some tests with pytest ... +``` - - `build` - Standard build command will also build all rust extensions. - - `build_rust` - Command builds all rust extensions. - - `clean` - Standard clean command executes cargo clean for all rust - extensions. +## Next steps and final remarks + +- When you are ready to distribute your project, have a look on + [the notes in the documentation about building wheels](https://setuptools-rust.readthedocs.io/en/latest/building_wheels.html). + +- 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). + 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). + +- You can also use `[[tool.setuptools-rust.bins]]` (instead of `[[tool.setuptools-rust.ext-modules]]`), + 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](https://packaging.python.org/en/latest/glossary/#term-Wheel) + file distributed by the + [package index](https://packaging.python.org/en/latest/glossary/#term-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](https://github.com/PyO3/setuptools-rust/tree/main/examples/hello-world) + example for more insights. + +- For a complete reference of the configuration options, see the + [API reference](https://setuptools-rust.readthedocs.io/en/latest/reference.html). + You can use any parameter defined by the `RustExtension` class with + `[[tool.setuptools-rust.ext-modules]]` and any parameter defined by the + `RustBin` class with `[[tool.setuptools-rust.bins]]`; just remember to replace + underscore characters `_` with dashes `-` in your `pyproject.toml` file. + +- `Cargo.toml` allow only one `[lib]` table per file. + If you require multiple extension modules you will need to write multiple `Cargo.toml` files. + Alternatively you can create a single private Rust top-level module that exposes + multiple submodules (using [PyO3's submodules](https://pyo3.rs/v0.19.2/module#python-submodules)), + which may also reduce the size of the build artifacts. + You can always keep your extension modules private and wrap them in pure Python + to have fine control over the public API. + +- If want to include both `[[tool.setuptools-rust.bins]]` and `[[tool.setuptools-rust.ext-modules]]` + in the same macOS wheel, you might have to manually add an extra `build.rs` file, + see [PyO3/setuptools-rust#351](https://github.com/PyO3/setuptools-rust/pull/351) + for more information about the workaround. + +- For more examples, see: + - [`hello-world`](https://github.com/PyO3/setuptools-rust/tree/main/examples/hello-world): + a more complete version of the code used in this tutorial that mixes both + `[[tool.setuptools-rust.ext-modules]]` and `[[tool.setuptools-rust.bins]]` + in a single distribution. + - [`html-py-ever`](https://github.com/PyO3/setuptools-rust/tree/main/examples/html-py-ever): + a more advanced example that uses Rust crates as dependencies. + - [`rust_with_cffi`](https://github.com/PyO3/setuptools-rust/tree/main/examples/rust_with_cffi): + uses both Rust and [CFFI](https://cffi.readthedocs.io/en/latest/). + - [`namespace_package`](https://github.com/PyO3/setuptools-rust/tree/main/examples/namespace_package): + integrates Rust-written modules into PEP 420 namespace packages. + - [`hello-world-script`](https://github.com/PyO3/setuptools-rust/tree/main/examples/hello-world-script): + uses Rust only for creating binary executables, not library modules. diff --git a/docs/building_wheels.md b/docs/building_wheels.md index 8e7fbd8d..80e04fed 100644 --- a/docs/building_wheels.md +++ b/docs/building_wheels.md @@ -1,8 +1,12 @@ # Building wheels -Because `setuptools-rust` is an extension to `setuptools`, the standard `setup.py bdist_wheel` command is used to build distributable wheels. These wheels can be uploaded to PyPI using standard tools such as [twine](https://github.com/pypa/twine). +Because `setuptools-rust` is an extension to `setuptools`, the standard [`python -m build`](https://pypa-build.readthedocs.io/en/stable/) command +(or [`pip wheel --no-deps . --wheel-dir dist`](https://pip.pypa.io/en/stable/cli/pip_wheel/)) can be used to build distributable wheels. +These wheels can be uploaded to PyPI using standard tools such as [twine](https://github.com/pypa/twine). -`setuptools-rust` supports building for the [PEP 384](https://www.python.org/dev/peps/pep-0384/) "stable" (aka "limited") API when the `--py-limited-api` option is passed to `setup.py bdist_wheel`. If using PyO3 bindings for `RustExtension`, then the correct [`pyo3/abi3`](https://pyo3.rs/v0.14.5/features.html#abi3) sub-feature is automatically enabled. In this way, abi3 wheels can be uploaded to make package distributors' roles easier, and package users installing from source with `python setup.py install` can use optimizations specific to their Python version. +`setuptools-rust` supports building for the [PEP 384](https://www.python.org/dev/peps/pep-0384/) "stable" (aka "limited") API when the `py_limited_api` option is set on the `[bdist_wheel]` section of `setup.cfg`. +If using PyO3 bindings for `RustExtension`, then the correct [`pyo3/abi3`](https://pyo3.rs/v0.14.5/features.html#abi3) sub-feature is automatically enabled. +In this way, abi3 wheels can be uploaded to make package distributors' roles easier, and package users installing from source with `pip install .` can use optimizations specific to their Python version. This chapter of the documentation explains two possible ways to build wheels for multiple Python versions below. @@ -52,7 +56,7 @@ It is possible to use any of the `manylinux` docker images: `manylinux1`, `manyl ### Binary wheels on macOS -For building wheels on macOS it is sufficient to run the `bdist_wheel` command, i.e. `setup.py bdist_wheel`. +For building wheels on macOS it is sufficient to use one of the default `python -m build` or `pip wheel --no-deps . --wheel-dir dist` commands. To build `universal2` wheels set the `ARCHFLAGS` environment variable to contain both `x86_64` and `arm64`, for example `ARCHFLAGS="-arch x86_64 -arch arm64"`. Wheel-building solutions such as [`cibuildwheel`][cibuildwheel] set this environment variable automatically. diff --git a/docs/index.md b/docs/index.md index be05011c..4621934c 100644 --- a/docs/index.md +++ b/docs/index.md @@ -4,6 +4,7 @@ :hidden: README.md + setuppy_tutorial building_wheels reference ``` diff --git a/docs/setuppy_tutorial.md b/docs/setuppy_tutorial.md new file mode 100644 index 00000000..a36267cc --- /dev/null +++ b/docs/setuppy_tutorial.md @@ -0,0 +1,130 @@ +# Usage with `setup.py` + +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`. + + +## Basic implementation files + +Let's start by assuming that you already have a bunch of Python and Rust files[^1] +that you would like to package for distribution in PyPI inside of a project directory +named `hello-world-setuppy`[^2][^3]: + +[^1]: To know more about how to write Rust to be integrated into Python packages, + please have a look on the [PyO3 docs](https://pyo3.rs) +[^2]: You can have a look on the + [examples/hello-world-setuppy](https://github.com/PyO3/setuptools-rust/tree/main/examples/hello-world-setuppy) + directory in the `setuptools-rust` repository. +[^3]: If you are an experienced Python or Rust programmer, you may notice that we + avoid using the `src` directory and explicitly instruct Setuptools and Cargo to + look into the `python` and `rust` directories respectively. + Since both Python and Rust ecosystem will try to claim the `src` directory as + their default, we prefer to be explicit and avoid confusion. + + +``` +hello-world-setuppy +├── Cargo.lock +├── Cargo.toml +├── python +│ └── hello_world +│ └── __init__.py +└── rust + └── lib.rs +``` + +```{literalinclude} ../examples/hello-world-setuppy/python/hello_world/__init__.py + :language: python +``` + +```{literalinclude} ../examples/hello-world-setuppy/rust/lib.rs + :language: rust +``` + +```{literalinclude} ../examples/hello-world-setuppy/Cargo.toml + :language: toml +``` + + +## Adding files to support packaging + +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: + +```{literalinclude} ../examples/hello-world-setuppy/pyproject.toml + :language: toml +``` + +… and a [`setup.py` configuration file](https://setuptools.pypa.io/en/latest/references/keywords.html) +that tells Setuptools how to build the Rust extensions using our `Cargo.toml` and `setuptools-rust`: + +```{literalinclude} ../examples/hello-world-setuppy/setup.py + :language: python +``` + +For a complete reference of the options supported by the `RustExtension` class, see the +[API reference](https://setuptools-rust.readthedocs.io/en/latest/reference.html). + + +We also add a [`MANIFEST.in` file](https://setuptools.pypa.io/en/latest/userguide/miscellaneous.html) +to control which files we want in the source distribution[^4]: + +```{literalinclude} ../examples/hello-world-setuppy/MANIFEST.in +``` + +[^4]: Alternatively you can also use `setuptools-scm` to add all the files under revision control + to the `sdist`, see the [docs](https://pypi.org/project/setuptools-scm/) for more information. + + +## Testing the extension + +With these files in place, you can install the project in a virtual environment +for testing and making sure everything is working correctly: + + +```powershell +# 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 ... +``` + + +## Next steps and final remarks + +- When you are ready to distribute your project, have a look on + [the notes in the documentation about building wheels](https://setuptools-rust.readthedocs.io/en/latest/building_wheels.html). + +- You can also use a [`RustBin`](https://setuptools-rust.readthedocs.io/en/latest/reference.html) object + (instead of a `RustExtension`), 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](https://packaging.python.org/en/latest/glossary/#term-Wheel) + file distributed by the + [package index](https://packaging.python.org/en/latest/glossary/#term-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](https://github.com/PyO3/setuptools-rust/tree/main/examples/hello-world) + example for more insights. + +- If want to include both `RustBin` and `RustExtension` same macOS wheel, you might have + to manually add an extra `build.rs` file, see [PyO3/setuptools-rust#351](https://github.com/PyO3/setuptools-rust/pull/351) + for more information about the workaround. + +- Since the adoption of {pep}`517`, running `python setup.py ...` directly as a CLI tool is + [considered deprecated](https://blog.ganssle.io/articles/2021/10/setup-py-deprecated.html). + Nevertheless, `setup.py` can be safely used as a configuration file + (the same way `conftest.py` is used by `pytest` or `noxfile.py` is used by `nox`). + There is a different mindset that comes with this change, though: + for example, it does not make sense to use `sys.exit(0)` in a `setup.py` file + or use a overarching `try...except...` block to re-run a failed build with different parameters. diff --git a/examples/hello-world-setuppy/Cargo.lock b/examples/hello-world-setuppy/Cargo.lock index e7cecae9..938e39ac 100644 --- a/examples/hello-world-setuppy/Cargo.lock +++ b/examples/hello-world-setuppy/Cargo.lock @@ -2,6 +2,273 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + [[package]] name = "hello-world" version = "0.1.0" +dependencies = [ + "pyo3", + "pyo3-build-config", +] + +[[package]] +name = "indoc" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa799dd5ed20a7e349f3b4639aa80d74549c81716d9ec4f994c9b5815598306" + +[[package]] +name = "libc" +version = "0.2.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" + +[[package]] +name = "lock_api" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "memoffset" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + +[[package]] +name = "proc-macro2" +version = "1.0.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "pyo3" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e681a6cfdc4adcc93b4d3cf993749a4552018ee0a9b65fc0ccfad74352c72a38" +dependencies = [ + "cfg-if", + "indoc", + "libc", + "memoffset", + "parking_lot", + "pyo3-build-config", + "pyo3-ffi", + "pyo3-macros", + "unindent", +] + +[[package]] +name = "pyo3-build-config" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "076c73d0bc438f7a4ef6fdd0c3bb4732149136abd952b110ac93e4edb13a6ba5" +dependencies = [ + "once_cell", + "target-lexicon", +] + +[[package]] +name = "pyo3-ffi" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e53cee42e77ebe256066ba8aa77eff722b3bb91f3419177cf4cd0f304d3284d9" +dependencies = [ + "libc", + "pyo3-build-config", +] + +[[package]] +name = "pyo3-macros" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfeb4c99597e136528c6dd7d5e3de5434d1ceaf487436a3f03b2d56b6fc9efd1" +dependencies = [ + "proc-macro2", + "pyo3-macros-backend", + "quote", + "syn", +] + +[[package]] +name = "pyo3-macros-backend" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "947dc12175c254889edc0c02e399476c2f652b4b9ebd123aa655c224de259536" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "smallvec" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "target-lexicon" +version = "0.12.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d0e916b1148c8e263850e1ebcbd046f333e0683c724876bb0da63ea4373dc8a" + +[[package]] +name = "unicode-ident" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" + +[[package]] +name = "unindent" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1766d682d402817b5ac4490b3c3002d91dfa0d22812f341609f97b08757359c" + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" diff --git a/examples/hello-world-setuppy/Cargo.toml b/examples/hello-world-setuppy/Cargo.toml index 879d064b..23179e31 100644 --- a/examples/hello-world-setuppy/Cargo.toml +++ b/examples/hello-world-setuppy/Cargo.toml @@ -3,14 +3,16 @@ name = "hello-world" version = "0.1.0" edition = "2018" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [dependencies] +pyo3 = { version = "0.19.2", features = ["extension-module"] } -[profile.release-lto] -inherits = "release" -lto = true +[build-dependencies] +pyo3-build-config = "0.19.2" -[[bin]] -name = "hello-world" -path = "rust/main.rs" +[lib] +# See https://github.com/PyO3/pyo3 for details +name = "_lib" # private module to be nested into Python package +path = "rust/lib.rs" +crate-type = ["cdylib"] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/examples/hello-world-setuppy/noxfile.py b/examples/hello-world-setuppy/noxfile.py index e7a54bdc..5bb74a21 100644 --- a/examples/hello-world-setuppy/noxfile.py +++ b/examples/hello-world-setuppy/noxfile.py @@ -11,4 +11,5 @@ def test(session: nox.Session): # Ensure build uses version of setuptools-rust under development session.install("--no-build-isolation", ".") # Test Rust binary - session.run("hello-world", *session.posargs) + session.run("python", "-c", "from hello_world import _lib; print(_lib)") + session.run("python", "-c", "__import__('hello_world').sum_as_string(5, 7)") diff --git a/examples/hello-world-setuppy/python/hello_world/__init__.py b/examples/hello-world-setuppy/python/hello_world/__init__.py index e69de29b..57fe77ed 100644 --- a/examples/hello-world-setuppy/python/hello_world/__init__.py +++ b/examples/hello-world-setuppy/python/hello_world/__init__.py @@ -0,0 +1,7 @@ +from ._lib import sum_as_string + +__all__ = ["sum_as_string"] + +# It is a common practice in Python packaging to keep the extension modules +# private and use Pure Python modules to wrap them. +# This allows you to have a very fine control over the public API. diff --git a/examples/hello-world-setuppy/rust/lib.rs b/examples/hello-world-setuppy/rust/lib.rs new file mode 100644 index 00000000..1f33c46b --- /dev/null +++ b/examples/hello-world-setuppy/rust/lib.rs @@ -0,0 +1,16 @@ +use pyo3::prelude::*; + +/// Formats the sum of two numbers as string. +#[pyfunction] +fn sum_as_string(a: usize, b: usize) -> PyResult { + Ok((a + b).to_string()) +} + +/// A Python module implemented in Rust. The name of this function must match +/// the `lib.name` setting in the `Cargo.toml`, else Python will not be able to +/// import the module. +#[pymodule] +fn _lib(_py: Python<'_>, m: &PyModule) -> PyResult<()> { + m.add_function(wrap_pyfunction!(sum_as_string, m)?)?; + Ok(()) +} diff --git a/examples/hello-world-setuppy/rust/main.rs b/examples/hello-world-setuppy/rust/main.rs deleted file mode 100644 index e7a11a96..00000000 --- a/examples/hello-world-setuppy/rust/main.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - println!("Hello, world!"); -} diff --git a/examples/hello-world-setuppy/setup.py b/examples/hello-world-setuppy/setup.py index 91361eaa..44be5d43 100644 --- a/examples/hello-world-setuppy/setup.py +++ b/examples/hello-world-setuppy/setup.py @@ -1,6 +1,6 @@ from setuptools import find_packages, setup -from setuptools_rust import RustBin +from setuptools_rust import Binding, RustExtension setup( name="hello-world", @@ -8,11 +8,18 @@ packages=find_packages(where="python"), package_dir={"": "python"}, rust_extensions=[ - RustBin( - "hello-world", - args=["--profile", "release-lto"], + RustExtension( + "hello_world._lib", + # ^-- The last part of the name (e.g. "_lib") has to match lib.name + # in Cargo.toml and the function name in the `.rs` file, + # but you can add a prefix to nest it inside of a Python package. + path="Cargo.toml", # Default value, can be omitted + py_limited_api="auto", # Default value, can be omitted + binding=Binding.PyO3, # Default value, can be omitted ) ], # rust extensions are not zip safe, just like C-extensions. - zip_safe=False, + # But `zip_safe=False` is an obsolete config that does not affect how `pip` + # or `importlib.{resources,metadata}` handle the package. ) +# See reference for RustExtension in https://setuptools-rust.readthedocs.io/en/latest/reference.html diff --git a/noxfile.py b/noxfile.py index 209785f9..d0030882 100644 --- a/noxfile.py +++ b/noxfile.py @@ -134,3 +134,9 @@ def test_examples_emscripten(session: nox.Session): def bump_version(session: nox.Session) -> None: session.install("bump2version") session.run("bumpversion", *session.posargs) + + +@nox.session() +def docs(session: nox.Session): + session.install(".", "-r", "docs/requirements.txt") + session.run("python", "-m", "sphinx", "docs", "docs/_build", *session.posargs) diff --git a/setuptools_rust/extension.py b/setuptools_rust/extension.py index 8779b85c..11528895 100644 --- a/setuptools_rust/extension.py +++ b/setuptools_rust/extension.py @@ -102,13 +102,12 @@ class RustExtension: ``setuptools.Extension``, this controls whether the built extension should be considered compatible with the PEP 384 "limited API". - - ``'auto'``: the ``--py-limited-api`` option of - ``setup.py bdist_wheel`` will control whether the extension is + - ``'auto'``: the ``py_limited_api`` option of + ``bdist_wheel`` will control whether the extension is built as a limited api extension. The corresponding ``pyo3/abi3-pyXY`` feature will be set accordingly. This is the recommended setting, as it allows - ``python setup.py install`` to build a version-specific extension - for best performance. + to build a version-specific extension for best performance. - ``True``: the extension is assumed to be compatible with the limited abi. You must ensure this is the case (e.g. by setting