#[cfg(feature = "auditwheel")]
use crate::auditwheel::auditwheel_rs;
use crate::compile;
use crate::compile::warn_missing_py_init;
use crate::module_writer::write_python_part;
use crate::module_writer::WheelWriter;
use crate::module_writer::{write_bin, write_bindings_module, write_cffi_module};
use crate::source_distribution::{get_pyproject_toml, source_distribution};
use crate::Manylinux;
use crate::Metadata21;
use crate::PythonInterpreter;
use crate::Target;
use cargo_metadata::Metadata;
use failure::{bail, Context, Error, ResultExt};
use std::collections::HashMap;
use std::fs;
use std::path::{Path, PathBuf};
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum BridgeModel {
Cffi,
Bin,
Bindings(String),
}
impl BridgeModel {
pub fn unwrap_bindings(&self) -> &str {
match self {
BridgeModel::Bindings(value) => &value,
_ => panic!("Expected Bindings"),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ProjectLayout {
PureRust,
Mixed(PathBuf),
}
impl ProjectLayout {
pub fn determine(
project_root: impl AsRef<Path>,
module_name: &str,
) -> Result<ProjectLayout, Error> {
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() {
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))
} else {
Ok(ProjectLayout::PureRust)
}
}
}
pub struct BuildContext {
pub target: Target,
pub bridge: BridgeModel,
pub project_layout: ProjectLayout,
pub metadata21: Metadata21,
pub scripts: HashMap<String, String>,
pub module_name: String,
pub manifest_path: PathBuf,
pub out: PathBuf,
pub release: bool,
pub strip: bool,
pub manylinux: Manylinux,
pub cargo_extra_args: Vec<String>,
pub rustc_extra_args: Vec<String>,
pub interpreter: Vec<PythonInterpreter>,
pub cargo_metadata: Metadata,
}
type BuiltWheelMetadata = (PathBuf, String, Option<PythonInterpreter>);
impl BuildContext {
pub fn build_wheels(&self) -> Result<Vec<BuiltWheelMetadata>, Error> {
fs::create_dir_all(&self.out)
.context("Failed to create the target directory for the wheels")?;
let wheels = match &self.bridge {
BridgeModel::Cffi => vec![(self.build_cffi_wheel()?, "py2.py3".to_string(), None)],
BridgeModel::Bin => vec![(self.build_bin_wheel()?, "py2.py3".to_string(), None)],
BridgeModel::Bindings(_) => self.build_binding_wheels()?,
};
Ok(wheels)
}
pub fn build_source_distribution(&self) -> Result<Option<BuiltWheelMetadata>, Error> {
fs::create_dir_all(&self.out)
.context("Failed to create the target directory for the source distribution")?;
if get_pyproject_toml(self.manifest_path.parent().unwrap()).is_ok() {
let sdist_path = source_distribution(&self.out, &self.metadata21, &self.manifest_path)
.context("Failed to build source distribution")?;
Ok(Some((sdist_path, "source".to_string(), None)))
} else {
Ok(None)
}
}
pub fn build_binding_wheels(
&self,
) -> Result<Vec<(PathBuf, String, Option<PythonInterpreter>)>, Error> {
let mut wheels = Vec::new();
for python_interpreter in &self.interpreter {
let artifact =
self.compile_cdylib(Some(&python_interpreter), Some(&self.module_name))?;
let tag = python_interpreter.get_tag(&self.manylinux);
let mut writer = WheelWriter::new(
&tag,
&self.out,
&self.metadata21,
&self.scripts,
&[tag.clone()],
)?;
write_bindings_module(
&mut writer,
&self.project_layout,
&self.module_name,
&artifact,
python_interpreter,
false,
)
.context("Failed to add the files to the wheel")?;
let wheel_path = writer.finish()?;
println!(
"📦 Built wheel for {} {}.{}{} to {}",
python_interpreter.interpreter,
python_interpreter.major,
python_interpreter.minor,
python_interpreter.abiflags,
wheel_path.display()
);
wheels.push((
wheel_path,
format!("cp{}{}", python_interpreter.major, python_interpreter.minor),
Some(python_interpreter.clone()),
));
}
Ok(wheels)
}
pub fn compile_cdylib(
&self,
python_interpreter: Option<&PythonInterpreter>,
module_name: Option<&str>,
) -> Result<PathBuf, Error> {
let artifacts = compile(&self, python_interpreter, &self.bridge)
.context("Failed to build a native library through cargo")?;
let artifact = artifacts.get("cdylib").cloned().ok_or_else(|| {
Context::new(
"Cargo didn't build a cdylib. Did you miss crate-type = [\"cdylib\"] \
in the lib section of your Cargo.toml?",
)
})?;
#[cfg(feature = "auditwheel")]
{
let target = python_interpreter
.map(|x| &x.target)
.unwrap_or(&self.target);
auditwheel_rs(&artifact, target, &self.manylinux)
.context("Failed to ensure manylinux compliance")?;
}
if let Some(module_name) = module_name {
warn_missing_py_init(&artifact, module_name)
.context("Failed to parse the native library")?;
}
Ok(artifact)
}
pub fn build_cffi_wheel(&self) -> Result<PathBuf, Error> {
let artifact = self.compile_cdylib(None, None)?;
let (tag, tags) = self.target.get_universal_tags(&self.manylinux);
let mut builder =
WheelWriter::new(&tag, &self.out, &self.metadata21, &self.scripts, &tags)?;
write_cffi_module(
&mut builder,
&self.project_layout,
self.manifest_path.parent().unwrap(),
&self.module_name,
&artifact,
&self.interpreter[0].executable,
false,
)?;
let wheel_path = builder.finish()?;
println!("📦 Built wheel to {}", wheel_path.display());
Ok(wheel_path)
}
pub fn build_bin_wheel(&self) -> Result<PathBuf, Error> {
let artifacts = compile(&self, None, &self.bridge)
.context("Failed to build a native library through cargo")?;
let artifact = artifacts
.get("bin")
.cloned()
.ok_or_else(|| Context::new("Cargo didn't build a binary."))?;
#[cfg(feature = "auditwheel")]
auditwheel_rs(&artifact, &self.target, &self.manylinux)
.context("Failed to ensure manylinux compliance")?;
let (tag, tags) = self.target.get_universal_tags(&self.manylinux);
if !self.scripts.is_empty() {
bail!("Defining entrypoints and working with a binary doesn't mix well");
}
let mut builder =
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)
.context("Failed to add the python module to the package")?;
}
ProjectLayout::PureRust => {}
}
let bin_name = artifact
.file_name()
.expect("Couldn't get the filename from the binary produced by cargo");
write_bin(&mut builder, &artifact, &self.metadata21, bin_name)?;
let wheel_path = builder.finish()?;
println!("📦 Built wheel to {}", wheel_path.display());
Ok(wheel_path)
}
}