Skip to content

Commit

Permalink
Support Rust extension as a submodule in mixed Python/Rust projects
Browse files Browse the repository at this point in the history
Co-authored-by: konstin <[email protected]>
  • Loading branch information
messense and konstin committed Apr 21, 2021
1 parent ecffb57 commit 26edb14
Show file tree
Hide file tree
Showing 18 changed files with 501 additions and 37 deletions.
64 changes: 52 additions & 12 deletions src/build_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,27 +55,63 @@ 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
PureRust,
/// A python package that is extended by a native rust module.
///
/// Contains the canonicialized (i.e. absolute) path to the python part of the project
Mixed(PathBuf),
/// Contains the the rust extension name
PureRust(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
python_module: PathBuf,
/// Contains the canonicialized (i.e. absolute) path to the rust part of the project
rust_module: PathBuf,
/// rust extension name
extension_name: String,
},
}

impl ProjectLayout {
/// Checks whether a python module exists besides Cargo.toml with the right name
pub fn determine(project_root: impl AsRef<Path>, module_name: &str) -> Result<ProjectLayout> {
let python_package_dir = project_root.as_ref().join(module_name);
if python_package_dir.is_dir() {
if !python_package_dir.join("__init__.py").is_file() {
// 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 (python_module, rust_module, extension_name) = if parts.len() > 1 {
let mut rust_module = project_root.as_ref().to_path_buf();
rust_module.extend(&parts[0..parts.len() - 1]);
(
project_root.as_ref().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),
module_name.to_string(),
)
};
if python_module.is_dir() {
if !python_module.join("__init__.py").is_file() {
bail!("Found a directory with the module name ({}) next to Cargo.toml, which indicates a mixed python/rust project, but the directory didn't contain an __init__.py file.", module_name)
}

println!("🍹 Building a mixed python/rust project");

Ok(ProjectLayout::Mixed(python_package_dir))
Ok(ProjectLayout::Mixed {
python_module,
rust_module,
extension_name,
})
} else {
Ok(ProjectLayout::PureRust)
Ok(ProjectLayout::PureRust(extension_name))
}
}

pub fn extension_name(&self) -> &str {
match *self {
ProjectLayout::PureRust(ref name) => name,
ProjectLayout::Mixed {
ref extension_name, ..
} => extension_name,
}
}
}
Expand Down Expand Up @@ -398,11 +434,15 @@ impl BuildContext {
WheelWriter::new(&tag, &self.out, &self.metadata21, &self.scripts, &tags)?;

match self.project_layout {
ProjectLayout::Mixed(ref python_module) => {
write_python_part(&mut builder, python_module, &self.module_name)
ProjectLayout::Mixed {
ref python_module,
ref extension_name,
..
} => {
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
8 changes: 5 additions & 3 deletions src/build_options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,9 +117,10 @@ impl BuildOptions {
let manifest_dir = manifest_file.parent().unwrap();
let metadata21 = Metadata21::from_cargo_toml(&cargo_toml, &manifest_dir)
.context("Failed to parse Cargo.toml into python metadata")?;
let extra_metadata = cargo_toml.remaining_core_metadata();
let scripts = cargo_toml.scripts();

let crate_name = cargo_toml.package.name;
let crate_name = &cargo_toml.package.name;

// If the package name contains minuses, you must declare a module with
// underscores as lib name
Expand All @@ -129,8 +130,9 @@ impl BuildOptions {
.and_then(|lib| lib.name.as_ref())
.unwrap_or(&crate_name)
.to_owned();
let extension_name = extra_metadata.name.as_ref().unwrap_or(&module_name);

let project_layout = ProjectLayout::determine(manifest_dir, &module_name)?;
let project_layout = ProjectLayout::determine(manifest_dir, &extension_name)?;

let mut cargo_extra_args = split_extra_args(&self.cargo_extra_args)?;
if let Some(ref target) = self.target {
Expand Down Expand Up @@ -209,7 +211,7 @@ impl BuildOptions {
project_layout,
metadata21,
scripts,
crate_name,
crate_name: crate_name.to_string(),
module_name,
manifest_path: self.manifest_path,
out: wheel_dir,
Expand Down
14 changes: 11 additions & 3 deletions src/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,14 +112,22 @@ impl Metadata21 {
description = None;
description_content_type = None;
};
let name = extra_metadata
.name
.map(|name| {
if let Some(pos) = name.find('.') {
name.split_at(pos).0.to_string()
} else {
name.clone()
}
})
.unwrap_or_else(|| cargo_toml.package.name.clone());

Ok(Metadata21 {
metadata_version: "2.1".to_owned(),

// Mapped from cargo metadata
name: extra_metadata
.name
.unwrap_or_else(|| cargo_toml.package.name.clone()),
name,
version: cargo_toml.package.version.clone(),
summary: cargo_toml.package.description.clone(),
description,
Expand Down
45 changes: 26 additions & 19 deletions src/module_writer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -572,40 +572,42 @@ pub fn write_bindings_module(
target: &Target,
develop: bool,
) -> Result<()> {
let ext_name = project_layout.extension_name();
let so_filename = match python_interpreter {
Some(python_interpreter) => python_interpreter.get_library_name(&module_name),
Some(python_interpreter) => python_interpreter.get_library_name(&ext_name),
// abi3
None => {
if target.is_unix() {
format!("{base}.abi3.so", base = module_name)
format!("{base}.abi3.so", base = ext_name)
} else {
// Apparently there is no tag for abi3 on windows
format!("{base}.pyd", base = module_name)
format!("{base}.pyd", base = ext_name)
}
}
};

match project_layout {
ProjectLayout::Mixed(ref python_module) => {
ProjectLayout::Mixed {
ref python_module,
ref rust_module,
..
} => {
write_python_part(writer, python_module, &module_name)
.context("Failed to add the python module to the package")?;

if develop {
let target = python_module.join(&so_filename);
let target = rust_module.join(&so_filename);
fs::copy(&artifact, &target).context(format!(
"Failed to copy {} to {}",
artifact.display(),
target.display()
))?;
}

writer.add_file_with_permissions(
Path::new(&module_name).join(&so_filename),
&artifact,
0o755,
)?;
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(_) => {
writer.add_file_with_permissions(so_filename, &artifact, 0o755)?;
}
}
Expand All @@ -628,7 +630,11 @@ pub fn write_cffi_module(
let module;

match project_layout {
ProjectLayout::Mixed(ref python_module) => {
ProjectLayout::Mixed {
ref python_module,
ref rust_module,
ref extension_name,
} => {
write_python_part(writer, python_module, &module_name)
.context("Failed to add the python module to the package")?;

Expand All @@ -646,9 +652,10 @@ pub fn write_cffi_module(
File::create(base_path.join("ffi.py"))?.write_all(cffi_declarations.as_bytes())?;
}

module = PathBuf::from(module_name).join(module_name);
let relative = rust_module.strip_prefix(python_module.parent().unwrap())?;
module = relative.join(extension_name);
}
ProjectLayout::PureRust => module = PathBuf::from(module_name),
ProjectLayout::PureRust(_) => module = PathBuf::from(module_name),
};

writer.add_directory(&module)?;
Expand Down Expand Up @@ -690,24 +697,24 @@ pub fn write_python_part(
for absolute in WalkDir::new(&python_module) {
let absolute = absolute?.into_path();

let relaitve = absolute.strip_prefix(python_module.as_ref().parent().unwrap())?;
let relative = absolute.strip_prefix(python_module.as_ref().parent().unwrap())?;

// Ignore the cffi folder from develop, if any
if relaitve.starts_with(module_name.as_ref().join(&module_name)) {
if relative.starts_with(module_name.as_ref().join(&module_name)) {
continue;
}

if absolute.is_dir() {
writer.add_directory(relaitve)?;
writer.add_directory(relative)?;
} else {
// Ignore native libraries from develop, if any
if let Some(extension) = relaitve.extension() {
if let Some(extension) = relative.extension() {
if extension.to_string_lossy() == "so" {
continue;
}
}
writer
.add_file(relaitve, &absolute)
.add_file(relative, &absolute)
.context(format!("File to add file from {}", absolute.display()))?;
}
}
Expand Down
Loading

0 comments on commit 26edb14

Please sign in to comment.