Skip to content

Commit

Permalink
Add support for packaging type stubs in pure Rust project layout
Browse files Browse the repository at this point in the history
  • Loading branch information
messense committed Jun 20, 2021
1 parent ae529da commit 8442d37
Show file tree
Hide file tree
Showing 5 changed files with 60 additions and 13 deletions.
1 change: 1 addition & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
* Support Aarch64 on FreeBSD in [#571](https://github.com/PyO3/maturin/pull/571)
Expand Down
29 changes: 19 additions & 10 deletions src/build_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,12 @@ impl BridgeModel {
#[derive(Clone, Debug, PartialEq, Eq)]
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
Expand All @@ -73,18 +76,19 @@ impl ProjectLayout {
pub fn determine(project_root: impl AsRef<Path>, module_name: &str) -> Result<ProjectLayout> {
// 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(),
)
};
Expand All @@ -101,13 +105,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,
Expand Down Expand Up @@ -435,7 +444,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
Expand Down
31 changes: 28 additions & 3 deletions src/module_writer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -609,14 +609,25 @@ 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
writer.add_bytes(
&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)?;
}
}
Expand Down Expand Up @@ -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)?;
Expand Down
7 changes: 7 additions & 0 deletions test-crates/pyo3-pure/check_installed/check_installed.py
Original file line number Diff line number Diff line change
@@ -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")
5 changes: 5 additions & 0 deletions test-crates/pyo3-pure/pyo3_pure.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class DummyClass:
@staticmethod
def get_42(self) -> int: ...

fourtytwo: int

0 comments on commit 8442d37

Please sign in to comment.