use std::borrow::Borrow;
use std::collections::HashMap;
use std::io;
use std::io::Write as _;
use std::path::Path;
use std::path::PathBuf;
use anyhow::Context as _;
use anyhow::Result;
use fs_err as fs;
use fs_err::File;
#[cfg(unix)]
use fs_err::os::unix::fs::OpenOptionsExt as _;
use tracing::debug;
use crate::BuildArtifact;
use crate::BuildContext;
use crate::ModuleWriter;
use crate::VirtualWriter;
use crate::WheelWriter;
use crate::archive_source::ArchiveSource;
#[cfg(unix)]
use crate::module_writer::default_permission;
use crate::module_writer::write_python_part;
mod bin_binding;
mod cffi_binding;
mod pyo3_binding;
mod uniffi_binding;
pub use bin_binding::BinBindingGenerator;
pub use cffi_binding::CffiBindingGenerator;
pub use pyo3_binding::Pyo3BindingGenerator;
pub use uniffi_binding::UniFfiBindingGenerator;
pub(crate) trait BindingGenerator {
fn generate_bindings(
&mut self,
context: &BuildContext,
artifact: &BuildArtifact,
module: &Path,
) -> Result<GeneratorOutput>;
}
#[derive(Debug)]
pub(crate) enum ArtifactTarget {
Binary(PathBuf),
ExtensionModule(PathBuf),
}
impl ArtifactTarget {
pub(crate) fn path(&self) -> &Path {
match self {
ArtifactTarget::Binary(path) | ArtifactTarget::ExtensionModule(path) => path,
}
}
}
#[derive(Debug)]
pub(crate) struct GeneratorOutput {
artifact_target: ArtifactTarget,
artifact_source_override: Option<PathBuf>,
additional_files: Option<HashMap<PathBuf, ArchiveSource>>,
}
pub fn generate_binding<A>(
writer: &mut VirtualWriter<WheelWriter>,
generator: &mut impl BindingGenerator,
context: &BuildContext,
artifacts: &[A],
) -> Result<()>
where
A: Borrow<BuildArtifact>,
{
if !context.editable {
write_python_part(
writer,
&context.project_layout,
context.pyproject_toml.as_ref(),
)
.context("Failed to add the python module to the package")?;
}
let base_path = context
.project_layout
.python_module
.as_ref()
.map(|python_module| python_module.parent().unwrap().to_path_buf());
let module = match &base_path {
Some(base_path) => context
.project_layout
.rust_module
.strip_prefix(base_path)
.unwrap()
.to_path_buf(),
None => PathBuf::from(&context.project_layout.extension_name),
};
for artifact in artifacts {
let artifact = artifact.borrow();
let GeneratorOutput {
artifact_target,
artifact_source_override,
additional_files,
} = generator.generate_bindings(context, artifact, &module)?;
match (context.editable, &base_path) {
(true, Some(base_path)) => {
let source = artifact_source_override.unwrap_or_else(|| artifact.path.clone());
match artifact_target {
ArtifactTarget::Binary(path) => {
writer.add_file_force(path, source, true)?;
}
ArtifactTarget::ExtensionModule(path) => {
let target = base_path.join(path);
debug!("Removing previously built module {}", target.display());
fs::create_dir_all(target.parent().unwrap())?;
let _ = fs::remove_file(&target);
debug!("Installing {} from {}", target.display(), source.display());
fs::copy(&source, &target).with_context(|| {
format!(
"Failed to copy {} to {}",
source.display(),
target.display(),
)
})?;
}
}
if let Some(additional_files) = additional_files {
for (target, source) in additional_files {
let target = base_path.join(target);
fs::create_dir_all(target.parent().unwrap())?;
debug!("Generating file {}", target.display());
let mut options = File::options();
options.write(true).create(true).truncate(true);
#[cfg(unix)]
{
options.mode(default_permission(source.executable()));
}
let mut file = options.open(&target)?;
match source {
ArchiveSource::Generated(source) => file.write_all(&source.data)?,
ArchiveSource::File(source) => {
let mut source = File::options().read(true).open(source.path)?;
io::copy(&mut source, &mut file)?;
}
}
}
}
}
_ => {
let source = artifact_source_override.unwrap_or_else(|| artifact.path.clone());
debug!(
"Adding to archive {} from {}",
artifact_target.path().display(),
source.display()
);
writer.add_file_force(artifact_target.path(), source, true)?;
if let Some(additional_files) = additional_files {
for (target, source) in additional_files {
debug!("Generating archive entry {}", target.display());
writer.add_entry_force(target, source)?;
}
}
}
}
}
if context.project_layout.python_module.is_none() {
let ext_name = &context.project_layout.extension_name;
let type_stub = context
.project_layout
.rust_module
.join(format!("{ext_name}.pyi"));
if type_stub.exists() {
eprintln!("📖 Found type stub file at {ext_name}.pyi");
writer.add_file(module.join("__init__.pyi"), type_stub, false)?;
writer.add_empty_file(module.join("py.typed"))?;
}
}
Ok(())
}