use crate::auditwheel::{AuditWheelMode, PlatformTag};
use crate::build_context::BuildContextBuilder;
pub use crate::cargo_options::{CargoOptions, TargetTriple};
use crate::compression::CompressionOptions;
use serde::{Deserialize, Serialize};
use std::ops::{Deref, DerefMut};
use std::path::PathBuf;
use tracing::instrument;
#[derive(Debug, Default, Serialize, Deserialize, clap::Parser, Clone, Eq, PartialEq)]
#[serde(default)]
pub struct PythonOptions {
#[arg(short, long, num_args = 0.., action = clap::ArgAction::Append)]
pub interpreter: Vec<PathBuf>,
#[arg(short = 'f', long, conflicts_with = "interpreter")]
pub find_interpreter: bool,
#[arg(short, long, value_parser = ["pyo3", "pyo3-ffi", "cffi", "uniffi", "bin"])]
pub bindings: Option<String>,
}
#[derive(Debug, Default, Serialize, Deserialize, clap::Parser, Clone, Eq, PartialEq)]
#[serde(default)]
pub struct PlatformOptions {
#[arg(
id = "compatibility",
long = "compatibility",
alias = "manylinux",
num_args = 0..,
action = clap::ArgAction::Append
)]
pub platform_tag: Vec<PlatformTag>,
#[arg(long, conflicts_with = "skip_auditwheel")]
pub auditwheel: Option<AuditWheelMode>,
#[arg(long, hide = true)]
pub skip_auditwheel: bool,
#[cfg(feature = "zig")]
#[arg(long)]
pub zig: bool,
}
#[derive(Debug, Default, Serialize, Deserialize, clap::Parser, Clone, Eq, PartialEq)]
#[serde(default)]
pub struct OutputOptions {
#[arg(short, long)]
pub out: Option<PathBuf>,
#[arg(long)]
pub include_debuginfo: bool,
#[arg(long = "sbom-include", num_args = 1.., action = clap::ArgAction::Append)]
pub sbom_include: Vec<PathBuf>,
}
#[derive(Debug, Default, Serialize, Deserialize, clap::Parser, Clone, Eq, PartialEq)]
#[serde(default)]
pub struct BuildOptions {
#[command(flatten)]
pub python: PythonOptions,
#[command(flatten)]
pub platform: PlatformOptions,
#[command(flatten)]
pub output: OutputOptions,
#[command(flatten)]
pub cargo: CargoOptions,
#[command(flatten)]
pub compression: CompressionOptions,
#[arg(long)]
pub generate_stubs: bool,
}
impl Deref for BuildOptions {
type Target = CargoOptions;
fn deref(&self) -> &Self::Target {
&self.cargo
}
}
impl DerefMut for BuildOptions {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.cargo
}
}
impl BuildOptions {
#[instrument(skip_all)]
pub fn into_build_context(self) -> BuildContextBuilder {
BuildContextBuilder::new(self)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::bridge::{PyO3, PyO3Crate, StableAbi, find_bridge};
use crate::python_interpreter::InterpreterResolver;
use crate::test_utils::test_crate_path;
use crate::{BridgeModel, Target};
use cargo_metadata::MetadataCommand;
use insta::assert_snapshot;
use pretty_assertions::assert_eq;
#[test]
fn test_find_bridge_pyo3() {
let pyo3_mixed = MetadataCommand::new()
.manifest_path(test_crate_path("pyo3-mixed").join("Cargo.toml"))
.exec()
.unwrap();
assert!(matches!(
find_bridge(&pyo3_mixed, None, None),
Ok(BridgeModel::PyO3 { .. })
));
assert!(matches!(
find_bridge(&pyo3_mixed, Some("pyo3"), None),
Ok(BridgeModel::PyO3 { .. })
));
}
#[test]
fn test_find_bridge_pyo3_abi3() {
use crate::bridge::{PyO3Metadata, PyO3VersionMetadata};
let pyo3_pure = MetadataCommand::new()
.manifest_path(test_crate_path("pyo3-pure").join("Cargo.toml"))
.exec()
.unwrap();
let bridge = BridgeModel::PyO3(PyO3 {
crate_name: PyO3Crate::PyO3,
version: semver::Version::new(0, 28, 2),
stable_abi: Some(StableAbi::from_abi3_version(3, 7)),
metadata: Some(PyO3Metadata {
cpython: PyO3VersionMetadata {
min_minor: 7,
max_minor: 15,
},
pypy: PyO3VersionMetadata {
min_minor: 11,
max_minor: 11,
},
}),
});
assert_eq!(find_bridge(&pyo3_pure, None, None).unwrap(), bridge);
assert_eq!(find_bridge(&pyo3_pure, Some("pyo3"), None).unwrap(), bridge);
}
#[test]
fn test_find_bridge_pyo3_feature() {
let pyo3_pure = MetadataCommand::new()
.manifest_path(test_crate_path("pyo3-feature").join("Cargo.toml"))
.exec()
.unwrap();
assert!(find_bridge(&pyo3_pure, None, None).is_err());
let pyo3_pure = MetadataCommand::new()
.manifest_path(test_crate_path("pyo3-feature").join("Cargo.toml"))
.other_options(vec!["--features=pyo3".to_string()])
.exec()
.unwrap();
assert!(matches!(
find_bridge(&pyo3_pure, None, None).unwrap(),
BridgeModel::PyO3 { .. }
));
}
#[test]
fn test_find_bridge_cffi() {
let cffi_pure = MetadataCommand::new()
.manifest_path(test_crate_path("cffi-pure").join("Cargo.toml"))
.exec()
.unwrap();
assert_eq!(
find_bridge(&cffi_pure, Some("cffi"), None).unwrap(),
BridgeModel::Cffi
);
assert_eq!(
find_bridge(&cffi_pure, None, None).unwrap(),
BridgeModel::Cffi
);
assert!(find_bridge(&cffi_pure, Some("pyo3"), None).is_err());
}
#[test]
fn test_find_bridge_bin() {
let hello_world = MetadataCommand::new()
.manifest_path(test_crate_path("hello-world").join("Cargo.toml"))
.exec()
.unwrap();
assert_eq!(
find_bridge(&hello_world, Some("bin"), None).unwrap(),
BridgeModel::Bin(None)
);
assert_eq!(
find_bridge(&hello_world, None, None).unwrap(),
BridgeModel::Bin(None)
);
assert!(find_bridge(&hello_world, Some("pyo3"), None).is_err());
let pyo3_bin = MetadataCommand::new()
.manifest_path(test_crate_path("pyo3-bin").join("Cargo.toml"))
.exec()
.unwrap();
assert!(matches!(
find_bridge(&pyo3_bin, Some("bin"), None).unwrap(),
BridgeModel::Bin(Some(_))
));
assert!(matches!(
find_bridge(&pyo3_bin, None, None).unwrap(),
BridgeModel::Bin(Some(_))
));
}
#[test]
fn test_find_single_python_interpreter_not_found() {
let target = Target::from_resolved_target_triple("x86_64-unknown-linux-gnu").unwrap();
let bridge = BridgeModel::Cffi;
let interpreter = vec![PathBuf::from("nonexistent-python-xyz")];
let resolver = InterpreterResolver::new(&target, &bridge, None, &interpreter, false, false);
let result = resolver.resolve();
let err_msg = result.unwrap_err().to_string();
assert_snapshot!(err_msg, @"Failed to find a python interpreter from `nonexistent-python-xyz`");
}
}