mod detection;
pub use detection::{find_bridge, has_windows_import_lib_support, upgrade_bridge_stable_abi};
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,
PythonInterpreter,
};
pub const ABI3T_MINIMUM_PYTHON_MINOR: u8 = 15;
#[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),
}
}
pub fn from_abi3t_version(major: u8, minor: u8) -> StableAbi {
StableAbi {
kind: StableAbiKind::Abi3t,
version: StableAbiVersion::Version(major, minor),
}
}
}
impl std::fmt::Display for StableAbi {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self.kind {
StableAbiKind::Abi3 => write!(f, "abi3"),
StableAbiKind::Abi3t => write!(f, "abi3t"),
}
}
}
#[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,
Abi3t,
}
impl fmt::Display for StableAbiKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
StableAbiKind::Abi3 => write!(f, "abi3"),
StableAbiKind::Abi3t => write!(f, "abi3t"),
}
}
}
impl StableAbiKind {
pub fn wheel_tag(&self) -> &str {
match self {
StableAbiKind::Abi3 => "abi3",
StableAbiKind::Abi3t => "abi3.abi3t",
}
}
}
#[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 has_stable_abi(&self) -> bool {
self.pyo3()
.and_then(|pyo3| pyo3.stable_abi.as_ref())
.is_some()
}
pub fn is_stable_abi_for_interpreter(&self, interpreter: &PythonInterpreter) -> bool {
self.stable_abi_for_interpreter(interpreter).is_some()
}
pub fn stable_abi_for_interpreter(&self, interpreter: &PythonInterpreter) -> Option<StableAbi> {
self.pyo3()?.stable_abi.filter(|stable_abi| {
interpreter.has_stable_api(stable_abi.kind)
&& stable_abi
.version
.min_version()
.is_none_or(|(major, minor)| {
(interpreter.major as u8, interpreter.minor as u8) >= (major, minor)
})
})
}
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"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn stable_abi_kind_display() {
assert_eq!(StableAbiKind::Abi3.to_string(), "abi3");
assert_eq!(StableAbiKind::Abi3t.to_string(), "abi3t");
}
#[test]
fn stable_abi_kind_wheel_tag() {
assert_eq!(StableAbiKind::Abi3.wheel_tag(), "abi3");
assert_eq!(StableAbiKind::Abi3t.wheel_tag(), "abi3.abi3t");
}
#[test]
fn stable_abi_display() {
assert_eq!(StableAbi::from_abi3_version(3, 7).to_string(), "abi3");
assert_eq!(StableAbi::from_abi3t_version(3, 15).to_string(), "abi3t");
}
#[test]
fn stable_abi_constructors() {
let abi3 = StableAbi::from_abi3_version(3, 9);
assert_eq!(abi3.kind, StableAbiKind::Abi3);
assert_eq!(abi3.version, StableAbiVersion::Version(3, 9));
let abi3t = StableAbi::from_abi3t_version(3, 15);
assert_eq!(abi3t.kind, StableAbiKind::Abi3t);
assert_eq!(abi3t.version, StableAbiVersion::Version(3, 15));
}
}