use crate::{BridgeModel, Target};
use anyhow::{Result, bail, ensure};
use super::discovery::InterpreterMetadataMessage;
pub(super) fn fun_with_abiflags(
message: &InterpreterMetadataMessage,
target: &Target,
bridge: &BridgeModel,
) -> Result<String> {
if bridge != &BridgeModel::Cffi
&& target.get_python_os() != message.system
&& !target.cross_compiling()
&& !(target.get_python_os() == "cygwin"
&& message.system.to_lowercase().starts_with("cygwin"))
{
let is_android_compat = target.get_python_os() == "android"
&& message.system == "linux"
&& message.major == 3
&& message.minor <= 12;
if !is_android_compat {
bail!(
"platform.system() in python, {}, and the rust target, {:?}, don't match ಠ_ಠ",
message.system,
target,
)
}
}
if message.major != 3 || message.minor < 7 {
bail!(
"Only python >= 3.7 is supported, while you're using python {}.{}",
message.major,
message.minor
);
}
if matches!(message.interpreter.as_str(), "pypy" | "graalvm" | "graalpy") {
Ok("".to_string())
} else if message.system == "windows" {
match message.abiflags.as_deref() {
Some("") | None => {
if message.minor <= 7 {
Ok("m".to_string())
} else if message.gil_disabled {
ensure!(
message.minor >= 13,
"gil_disabled is only available in python 3.13+ ಠ_ಠ"
);
Ok("t".to_string())
} else {
Ok("".to_string())
}
}
Some(abiflags) => {
if message.minor >= 14 {
Ok(abiflags.to_string())
} else if message.gil_disabled && abiflags == "t" {
Ok(abiflags.to_string())
} else {
bail!(
"Unexpected abiflags '{}' for Python {}.{} on Windows ಠ_ಠ",
abiflags,
message.major,
message.minor
)
}
}
}
} else if let Some(ref abiflags) = message.abiflags {
if message.minor >= 8 {
Ok(abiflags.to_string())
} else if (abiflags != "m") && (abiflags != "dm") {
bail!("A python 3 interpreter on Linux or macOS must have 'm' or 'dm' as abiflags ಠ_ಠ")
} else {
Ok(abiflags.to_string())
}
} else {
bail!("A python 3 interpreter on Linux or macOS must define abiflags in its sysconfig ಠ_ಠ")
}
}
pub(super) fn calculate_abi_tag(ext_suffix: &str) -> Option<String> {
let parts = ext_suffix.split('.').collect::<Vec<_>>();
if parts.len() < 3 {
return None;
}
let soabi = parts[1];
let mut soabi_split = soabi.split('-');
let abi = if soabi.starts_with("cpython") {
format!("cp{}", soabi_split.nth(1).unwrap())
} else if soabi.starts_with("cp") {
soabi_split.next().unwrap().to_string()
} else if soabi.starts_with("pypy") {
soabi_split.take(2).collect::<Vec<_>>().join("-")
} else if soabi.starts_with("graalpy") {
soabi_split.take(3).collect::<Vec<_>>().join("-")
} else if !soabi.is_empty() {
match soabi_split.nth(1) {
Some(abi) => abi.to_string(),
None => return None,
}
} else {
return None;
};
let abi_tag = abi.replace(['.', '-', ' '], "_");
Some(abi_tag)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_calculate_abi_tag() {
let cases = vec![
(".cpython-37m-x86_64-linux-gnu.so", Some("cp37m")),
(".cpython-310-x86_64-linux-gnu.so", Some("cp310")),
(".cpython-310-darwin.so", Some("cp310")),
(".cpython-313t-darwin.so", Some("cp313t")),
(".cp310-win_amd64.pyd", Some("cp310")),
(".cp39-mingw_x86_64.pyd", Some("cp39")),
(".cpython-312-wasm32-wasi.so", Some("cp312")),
(".cpython-38.so", Some("cp38")),
(".pyd", None),
(".so", None),
(".pypy38-pp73-x86_64-linux-gnu.so", Some("pypy38_pp73")),
(
".graalpy-38-native-x86_64-darwin.dylib",
Some("graalpy_38_native"),
),
(".pyston-23-x86_64-linux-gnu.so", Some("23")),
(".nodashes.so", None),
];
for (ext_suffix, expected) in cases {
assert_eq!(calculate_abi_tag(ext_suffix).as_deref(), expected);
}
}
}