mod detection;
pub use detection::{find_bridge, is_generating_import_lib};
use std::{fmt, str::FromStr};
use anyhow::Context;
use serde::Deserialize;
use crate::python_interpreter::{
MAXIMUM_PYPY_MINOR, MAXIMUM_PYTHON_MINOR, MINIMUM_PYPY_MINOR, MINIMUM_PYTHON_MINOR,
};
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum PyO3Crate {
PyO3,
PyO3Ffi,
}
impl PyO3Crate {
pub fn as_str(&self) -> &str {
match self {
PyO3Crate::PyO3 => "pyo3",
PyO3Crate::PyO3Ffi => "pyo3-ffi",
}
}
}
impl fmt::Debug for PyO3Crate {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
impl fmt::Display for PyO3Crate {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
impl FromStr for PyO3Crate {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"pyo3" => Ok(PyO3Crate::PyO3),
"pyo3-ffi" => Ok(PyO3Crate::PyO3Ffi),
_ => anyhow::bail!("unknown binding crate: {}", s),
}
}
}
#[derive(Debug, Clone, Deserialize)]
pub struct PyO3VersionMetadataRaw {
#[serde(rename = "min-version")]
pub min_version: String,
#[serde(rename = "max-version")]
pub max_version: String,
}
#[derive(Debug, Clone, Deserialize)]
pub struct PyO3MetadataRaw {
pub cpython: PyO3VersionMetadataRaw,
pub pypy: PyO3VersionMetadataRaw,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PyO3VersionMetadata {
pub min_minor: usize,
pub max_minor: usize,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PyO3Metadata {
pub cpython: PyO3VersionMetadata,
pub pypy: PyO3VersionMetadata,
}
impl TryFrom<PyO3VersionMetadataRaw> for PyO3VersionMetadata {
type Error = anyhow::Error;
fn try_from(raw: PyO3VersionMetadataRaw) -> Result<Self, Self::Error> {
let min_version = raw
.min_version
.rsplit('.')
.next()
.context("invalid min-version in pyo3-ffi metadata")?
.parse()?;
let max_version = raw
.max_version
.rsplit('.')
.next()
.context("invalid max-version in pyo3-ffi metadata")?
.parse()?;
Ok(Self {
min_minor: min_version,
max_minor: max_version,
})
}
}
impl TryFrom<PyO3MetadataRaw> for PyO3Metadata {
type Error = anyhow::Error;
fn try_from(raw: PyO3MetadataRaw) -> Result<Self, Self::Error> {
Ok(Self {
cpython: PyO3VersionMetadata::try_from(raw.cpython)?,
pypy: PyO3VersionMetadata::try_from(raw.pypy)?,
})
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct StableAbi {
pub kind: StableAbiKind,
pub version: StableAbiVersion,
}
impl StableAbi {
pub fn from_abi3_version(major: u8, minor: u8) -> StableAbi {
StableAbi {
kind: StableAbiKind::Abi3,
version: StableAbiVersion::Version(major, minor),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum StableAbiVersion {
CurrentPython,
Version(u8, u8),
}
impl StableAbiVersion {
pub fn min_version(&self) -> Option<(u8, u8)> {
match self {
StableAbiVersion::CurrentPython => None,
StableAbiVersion::Version(major, minor) => Some((*major, *minor)),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum StableAbiKind {
Abi3,
}
impl fmt::Display for StableAbiKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
StableAbiKind::Abi3 => write!(f, "abi3"),
}
}
}
impl StableAbiKind {
pub fn wheel_tag(&self) -> &str {
match self {
StableAbiKind::Abi3 => "abi3",
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PyO3 {
pub crate_name: PyO3Crate,
pub version: semver::Version,
pub stable_abi: Option<StableAbi>,
pub metadata: Option<PyO3Metadata>,
}
impl PyO3 {
fn minimal_python_minor_version(&self) -> usize {
let major_version = self.version.major;
let minor_version = self.version.minor;
let min_minor = if let Some(metadata) = self.metadata.as_ref() {
metadata.cpython.min_minor
} else if (major_version, minor_version) >= (0, 16) {
7
} else {
MINIMUM_PYTHON_MINOR
};
if let Some(stable_abi) = self.stable_abi.as_ref() {
if let StableAbiVersion::Version(_, abi3_minor) = stable_abi.version {
min_minor.max(abi3_minor as usize)
} else {
min_minor
}
} else {
min_minor
}
}
fn maximum_python_minor_version(&self) -> usize {
if let Some(metadata) = self.metadata.as_ref() {
metadata.cpython.max_minor
} else {
MAXIMUM_PYTHON_MINOR
}
}
fn minimal_pypy_minor_version(&self) -> usize {
let major_version = self.version.major;
let minor_version = self.version.minor;
if let Some(metadata) = self.metadata.as_ref() {
metadata.pypy.min_minor
} else if (major_version, minor_version) >= (0, 23) {
9
} else if (major_version, minor_version) >= (0, 14) {
7
} else {
MINIMUM_PYPY_MINOR
}
}
fn maximum_pypy_minor_version(&self) -> usize {
if let Some(metadata) = self.metadata.as_ref() {
metadata.pypy.max_minor
} else {
MAXIMUM_PYPY_MINOR
}
}
fn supports_free_threaded(&self) -> bool {
let major_version = self.version.major;
let minor_version = self.version.minor;
(major_version, minor_version) >= (0, 23)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum BridgeModel {
Bin(Option<PyO3>),
PyO3(PyO3),
Cffi,
UniFfi,
}
impl BridgeModel {
pub fn pyo3(&self) -> Option<&PyO3> {
match self {
BridgeModel::Bin(Some(bindings)) => Some(bindings),
BridgeModel::PyO3(bindings) => Some(bindings),
_ => None,
}
}
pub fn is_pyo3(&self) -> bool {
matches!(self, BridgeModel::PyO3(_) | BridgeModel::Bin(Some(_)))
}
pub fn is_pyo3_crate(&self, name: PyO3Crate) -> bool {
match self {
BridgeModel::Bin(Some(bindings)) => bindings.crate_name == name,
BridgeModel::PyO3(bindings) => bindings.crate_name == name,
_ => false,
}
}
pub fn is_bin(&self) -> bool {
matches!(self, BridgeModel::Bin(_))
}
pub fn minimal_python_minor_version(&self) -> usize {
match self.pyo3() {
Some(bindings) => bindings.minimal_python_minor_version(),
None => MINIMUM_PYTHON_MINOR,
}
}
pub fn maximum_python_minor_version(&self) -> usize {
match self.pyo3() {
Some(bindings) => bindings.maximum_python_minor_version(),
None => MAXIMUM_PYTHON_MINOR,
}
}
pub fn minimal_pypy_minor_version(&self) -> usize {
match self.pyo3() {
Some(bindings) => bindings.minimal_pypy_minor_version(),
None => MINIMUM_PYPY_MINOR,
}
}
pub fn maximum_pypy_minor_version(&self) -> usize {
use crate::python_interpreter::MAXIMUM_PYPY_MINOR;
match self.pyo3() {
Some(bindings) => bindings.maximum_pypy_minor_version(),
None => MAXIMUM_PYPY_MINOR,
}
}
pub fn is_abi3(&self) -> bool {
self.pyo3()
.and_then(|pyo3| match pyo3.stable_abi {
Some(stable_abi) => match stable_abi.kind {
StableAbiKind::Abi3 => Some(true),
},
None => None,
})
.is_some_and(|x| x)
}
pub fn supports_free_threaded(&self) -> bool {
match self {
BridgeModel::Bin(Some(bindings)) | BridgeModel::PyO3(bindings) => {
bindings.supports_free_threaded()
}
BridgeModel::Bin(None) => true,
BridgeModel::Cffi | BridgeModel::UniFfi => false,
}
}
}
impl fmt::Display for BridgeModel {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
BridgeModel::Bin(Some(bindings)) => write!(f, "{} bin", bindings.crate_name),
BridgeModel::Bin(None) => write!(f, "bin"),
BridgeModel::PyO3(bindings) => write!(f, "{}", bindings.crate_name),
BridgeModel::Cffi => write!(f, "cffi"),
BridgeModel::UniFfi => write!(f, "uniffi"),
}
}
}