From efda87c20849f2461a2f19d9050e29874a58c522 Mon Sep 17 00:00:00 2001 From: messense Date: Sat, 12 Jun 2021 15:38:17 +0800 Subject: [PATCH] Add support for packaging type stubs in pure Rust project layout --- Changelog.md | 1 + src/build_context.rs | 27 +++++++++++----- src/module_writer.rs | 31 +++++++++++++++++-- .../check_installed/check_installed.py | 7 +++++ test-crates/pyo3-pure/pyo3_pure.pyi | 5 +++ 5 files changed, 60 insertions(+), 11 deletions(-) create mode 100644 test-crates/pyo3-pure/pyo3_pure.pyi diff --git a/Changelog.md b/Changelog.md index dca1c8c96..cb2c55d33 100644 --- a/Changelog.md +++ b/Changelog.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Add PEP 656 musllinux support in [#543](https://github.com/PyO3/maturin/pull/543) * `--manylinux` is now called `--compatibility` and supports musllinux * The pure rust install layout changed from just the shared library to a python module that reexports the shared library. This should have now observable consequences for users of the created wheel expect that `my_project.my_project` is now also importable (and equal to just `my_project`) +* Add support for packaging type stubs in pure Rust project layout in [#567](https://github.com/PyO3/maturin/pull/567) * Support i386 on OpenBSD in [#568](https://github.com/PyO3/maturin/pull/568) * Support Aarch64 on OpenBSD in [#570](https://github.com/PyO3/maturin/pull/570) diff --git a/src/build_context.rs b/src/build_context.rs index c9d238a69..799f047a8 100644 --- a/src/build_context.rs +++ b/src/build_context.rs @@ -56,7 +56,12 @@ pub enum ProjectLayout { /// A rust crate compiled into a shared library with only some glue python for cffi /// /// Contains the the rust extension name - PureRust(String), + PureRust { + /// Contains the canonicialized (i.e. absolute) path to the rust part of the project + rust_module: PathBuf, + /// rust extension name + extension_name: String, + }, /// A python package that is extended by a native rust module. Mixed { /// Contains the canonicialized (i.e. absolute) path to the python part of the project @@ -73,18 +78,19 @@ impl ProjectLayout { pub fn determine(project_root: impl AsRef, module_name: &str) -> Result { // A dot in the module name means the extension module goes into the module folder specified by the path let parts: Vec<&str> = module_name.split('.').collect(); + let project_root = project_root.as_ref(); let (python_module, rust_module, extension_name) = if parts.len() > 1 { - let mut rust_module = project_root.as_ref().to_path_buf(); + let mut rust_module = project_root.to_path_buf(); rust_module.extend(&parts[0..parts.len() - 1]); ( - project_root.as_ref().join(parts[0]), + project_root.join(parts[0]), rust_module, parts[parts.len() - 1].to_string(), ) } else { ( - project_root.as_ref().join(module_name), - project_root.as_ref().join(module_name), + project_root.join(module_name), + project_root.join(module_name), module_name.to_string(), ) }; @@ -101,13 +107,18 @@ impl ProjectLayout { extension_name, }) } else { - Ok(ProjectLayout::PureRust(extension_name)) + Ok(ProjectLayout::PureRust { + rust_module: project_root.to_path_buf(), + extension_name, + }) } } pub fn extension_name(&self) -> &str { match *self { - ProjectLayout::PureRust(ref name) => name, + ProjectLayout::PureRust { + ref extension_name, .. + } => extension_name, ProjectLayout::Mixed { ref extension_name, .. } => extension_name, @@ -435,7 +446,7 @@ impl BuildContext { write_python_part(&mut builder, python_module, extension_name) .context("Failed to add the python module to the package")?; } - ProjectLayout::PureRust(_) => {} + ProjectLayout::PureRust { .. } => {} } // I wouldn't know of any case where this would be the wrong (and neither do diff --git a/src/module_writer.rs b/src/module_writer.rs index 881afa8f7..b410481dc 100644 --- a/src/module_writer.rs +++ b/src/module_writer.rs @@ -609,7 +609,9 @@ pub fn write_bindings_module( let relative = rust_module.strip_prefix(python_module.parent().unwrap())?; writer.add_file_with_permissions(relative.join(&so_filename), &artifact, 0o755)?; } - ProjectLayout::PureRust(_) => { + ProjectLayout::PureRust { + ref rust_module, .. + } => { let module = PathBuf::from(module_name); writer.add_directory(&module)?; // Reexport the shared library as if it were the top level module @@ -617,6 +619,15 @@ pub fn write_bindings_module( &module.join("__init__.py"), format!("from .{} import *\n", module_name).as_bytes(), )?; + let type_stub = rust_module.join(format!("{}.pyi", module_name)); + if type_stub.exists() { + writer.add_bytes( + &module.join("__init__.pyi"), + &fs_err::read(type_stub).context("Failed to read type stub file")?, + )?; + writer.add_bytes(&module.join("py.typed"), b"")?; + println!("📖 Found type stub file at {}.pyi", module_name); + } writer.add_file_with_permissions(&module.join(so_filename), &artifact, 0o755)?; } } @@ -663,11 +674,25 @@ pub fn write_cffi_module( let relative = rust_module.strip_prefix(python_module.parent().unwrap())?; module = relative.join(extension_name); + writer.add_directory(&module)?; + } + ProjectLayout::PureRust { + ref rust_module, .. + } => { + module = PathBuf::from(module_name); + writer.add_directory(&module)?; + let type_stub = rust_module.join(format!("{}.pyi", module_name)); + if type_stub.exists() { + writer.add_bytes( + &module.join("__init__.pyi"), + &fs_err::read(type_stub).context("Failed to read type stub file")?, + )?; + writer.add_bytes(&module.join("py.typed"), b"")?; + println!("📖 Found type stub file at {}.pyi", module_name); + } } - ProjectLayout::PureRust(_) => module = PathBuf::from(module_name), }; - writer.add_directory(&module)?; writer.add_bytes(&module.join("__init__.py"), cffi_init_file().as_bytes())?; writer.add_bytes(&module.join("ffi.py"), cffi_declarations.as_bytes())?; writer.add_file_with_permissions(&module.join("native.so"), &artifact, 0o755)?; diff --git a/test-crates/pyo3-pure/check_installed/check_installed.py b/test-crates/pyo3-pure/check_installed/check_installed.py index 5773f87af..86fe46115 100755 --- a/test-crates/pyo3-pure/check_installed/check_installed.py +++ b/test-crates/pyo3-pure/check_installed/check_installed.py @@ -1,6 +1,13 @@ #!/usr/bin/env python3 +import os import pyo3_pure assert pyo3_pure.DummyClass.get_42() == 42 + +# Check type stub +install_path = os.path.join(os.path.dirname(pyo3_pure.__file__)) +assert os.path.exists(os.path.join(install_path, "__init__.pyi")) +assert os.path.exists(os.path.join(install_path, "py.typed")) + print("SUCCESS") diff --git a/test-crates/pyo3-pure/pyo3_pure.pyi b/test-crates/pyo3-pure/pyo3_pure.pyi new file mode 100644 index 000000000..aad4bb417 --- /dev/null +++ b/test-crates/pyo3-pure/pyo3_pure.pyi @@ -0,0 +1,5 @@ +class DummyClass: + @staticmethod + def get_42(self) -> int: ... + +fourtytwo: int