use crate::PlatformTag;
use crate::build_options::TargetTriple;
use crate::cross_compile::is_cross_compiling;
use crate::python_interpreter::InterpreterKind;
use crate::python_interpreter::InterpreterKind::{CPython, GraalPy, PyPy};
use anyhow::{Result, anyhow, bail, format_err};
use platform_info::*;
use rustc_version::VersionMeta;
use serde::Deserialize;
use std::env;
use std::fmt;
use std::path::Path;
use std::path::PathBuf;
use std::process::Command;
use std::str;
use std::str::FromStr;
use target_lexicon::{Architecture, Environment, Triple};
use tracing::error;
mod legacy_py;
mod platform_tag;
mod pypi_tags;
pub use platform_tag::get_platform_tag;
pub(crate) use platform_tag::rustc_macosx_target_version;
pub use pypi_tags::{is_arch_supported_by_pypi, validate_wheel_filename_for_pypi};
pub(crate) const RUST_1_64_0: semver::Version = semver::Version::new(1, 64, 0);
pub(crate) const RUST_1_93_0: semver::Version = semver::Version::new(1, 93, 0);
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Os {
Linux,
Windows,
Macos,
Ios,
FreeBsd,
NetBsd,
OpenBsd,
Dragonfly,
Solaris,
Illumos,
Haiku,
Emscripten,
Wasi,
Aix,
Hurd,
Cygwin,
Android,
}
impl fmt::Display for Os {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Os::Linux => write!(f, "Linux"),
Os::Windows => write!(f, "Windows"),
Os::Macos => write!(f, "macOS"),
Os::Ios => write!(f, "iOS"),
Os::FreeBsd => write!(f, "FreeBSD"),
Os::NetBsd => write!(f, "NetBSD"),
Os::OpenBsd => write!(f, "OpenBSD"),
Os::Dragonfly => write!(f, "DragonFly"),
Os::Solaris => write!(f, "Solaris"),
Os::Illumos => write!(f, "Illumos"),
Os::Haiku => write!(f, "Haiku"),
Os::Emscripten => write!(f, "Emscripten"),
Os::Wasi => write!(f, "Wasi"),
Os::Aix => write!(f, "AIX"),
Os::Hurd => write!(f, "Hurd"),
Os::Cygwin => write!(f, "Cygwin"),
Os::Android => write!(f, "Android"),
}
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Arch {
Aarch64,
Armv5teL,
Armv6L,
Armv7L,
#[serde(alias = "ppc")]
Powerpc,
#[serde(alias = "ppc64le")]
Powerpc64Le,
#[serde(alias = "ppc64")]
Powerpc64,
#[serde(alias = "i686")]
X86,
X86_64,
S390X,
Wasm32,
Riscv32,
Riscv64,
Mips64el,
Mips64,
Mipsel,
Mips,
Sparc64,
Sparcv9,
LoongArch64,
}
impl Arch {
pub fn as_str(&self) -> &str {
match *self {
Arch::Aarch64 => "aarch64",
Arch::Armv5teL => "armv5tel",
Arch::Armv6L => "armv6l",
Arch::Armv7L => "armv7l",
Arch::Powerpc => "ppc",
Arch::Powerpc64Le => "ppc64le",
Arch::Powerpc64 => "ppc64",
Arch::X86 => "i686",
Arch::X86_64 => "x86_64",
Arch::S390X => "s390x",
Arch::Wasm32 => "wasm32",
Arch::Riscv32 => "riscv32",
Arch::Riscv64 => "riscv64",
Arch::Mips64el => "mips64el",
Arch::Mips64 => "mips64",
Arch::Mipsel => "mipsel",
Arch::Mips => "mips",
Arch::Sparc64 => "sparc64",
Arch::Sparcv9 => "sparcv9",
Arch::LoongArch64 => "loongarch64",
}
}
}
impl fmt::Display for Arch {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl Arch {
pub fn machine(&self) -> &'static str {
match self {
Arch::Aarch64 => "arm64",
Arch::Armv5teL | Arch::Armv6L | Arch::Armv7L => "arm",
Arch::Powerpc | Arch::Powerpc64Le | Arch::Powerpc64 => "powerpc",
Arch::X86 => "i386",
Arch::X86_64 => "amd64",
Arch::Riscv32 | Arch::Riscv64 => "riscv",
Arch::Mips64el | Arch::Mips64 | Arch::Mipsel | Arch::Mips => "mips",
Arch::Sparc64 => "sparc64",
Arch::Sparcv9 => "sparcv9",
Arch::Wasm32 => "wasm32",
Arch::S390X => "s390x",
Arch::LoongArch64 => "loongarch64",
}
}
}
fn get_supported_architectures(os: &Os) -> &'static [Arch] {
match os {
Os::Android => &[
Arch::Aarch64,
Arch::Armv5teL,
Arch::Armv6L,
Arch::Armv7L,
Arch::X86,
Arch::X86_64,
],
Os::Linux => &[
Arch::Aarch64,
Arch::Armv5teL,
Arch::Armv6L,
Arch::Armv7L,
Arch::Powerpc,
Arch::Powerpc64,
Arch::Powerpc64Le,
Arch::S390X,
Arch::X86,
Arch::X86_64,
Arch::Riscv32,
Arch::Riscv64,
Arch::Mips64el,
Arch::Mips64,
Arch::Mipsel,
Arch::Mips,
Arch::Sparc64,
Arch::LoongArch64,
],
Os::Windows => &[Arch::X86, Arch::X86_64, Arch::Aarch64],
Os::Macos => &[Arch::Aarch64, Arch::X86_64],
Os::Ios => &[Arch::Aarch64, Arch::X86_64],
Os::FreeBsd | Os::NetBsd => &[
Arch::Aarch64,
Arch::Armv6L,
Arch::Armv7L,
Arch::Powerpc,
Arch::Powerpc64,
Arch::Powerpc64Le,
Arch::X86,
Arch::X86_64,
Arch::Riscv32,
Arch::Riscv64,
Arch::Mips64el,
Arch::Mipsel,
Arch::Sparc64,
],
Os::OpenBsd => &[
Arch::X86,
Arch::X86_64,
Arch::Aarch64,
Arch::Armv7L,
Arch::Powerpc,
Arch::Powerpc64,
Arch::Powerpc64Le,
Arch::Riscv32,
Arch::Riscv64,
Arch::Sparc64,
],
Os::Dragonfly => &[Arch::X86_64],
Os::Illumos => &[Arch::X86_64],
Os::Haiku => &[Arch::X86_64],
Os::Solaris => &[Arch::X86_64, Arch::Sparc64, Arch::Sparcv9],
Os::Emscripten | Os::Wasi => &[Arch::Wasm32],
Os::Aix => &[Arch::Powerpc64],
Os::Hurd => &[Arch::X86, Arch::X86_64],
Os::Cygwin => &[Arch::X86, Arch::X86_64],
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Target {
os: Os,
arch: Arch,
env: Environment,
triple: String,
cross_compiling: bool,
pub(crate) rustc_version: VersionMeta,
pub(crate) user_specified: bool,
}
impl fmt::Display for Target {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.triple)
}
}
impl Target {
pub fn from_target_triple(target_triple: Option<&TargetTriple>) -> Result<Self> {
let rustc_version = rustc_version_meta()?;
let triple = match &target_triple {
None => rustc_version.host.to_string(),
Some(TargetTriple::Universal2) => "aarch64-apple-darwin".to_string(),
Some(TargetTriple::Regular(target_triple)) => target_triple.clone(),
};
Self::from_triple(rustc_version, &triple, target_triple.is_some())
}
pub fn from_resolved_target_triple(target_triple: &str) -> Result<Self> {
let rustc_version = rustc_version_meta()?;
Self::from_triple(rustc_version, target_triple, true)
}
fn from_triple(
rustc_version: VersionMeta,
target_triple: &str,
user_specified: bool,
) -> Result<Self> {
use target_lexicon::{
ArmArchitecture, Mips32Architecture, Mips64Architecture, OperatingSystem,
};
let platform = Triple::from_str(target_triple)
.map_err(|_| format_err!("Unknown target triple {}", target_triple))?;
let os = match platform.operating_system {
OperatingSystem::Linux => match platform.environment {
Environment::Android | Environment::Androideabi => Os::Android,
_ => Os::Linux,
},
OperatingSystem::Windows => Os::Windows,
OperatingSystem::MacOSX(_) | OperatingSystem::Darwin(_) => Os::Macos,
OperatingSystem::IOS(_) => Os::Ios,
OperatingSystem::Netbsd => Os::NetBsd,
OperatingSystem::Freebsd => Os::FreeBsd,
OperatingSystem::Openbsd => Os::OpenBsd,
OperatingSystem::Dragonfly => Os::Dragonfly,
OperatingSystem::Solaris => Os::Solaris,
OperatingSystem::Illumos => Os::Illumos,
OperatingSystem::Haiku => Os::Haiku,
OperatingSystem::Emscripten => Os::Emscripten,
OperatingSystem::Wasi | OperatingSystem::WasiP1 | OperatingSystem::WasiP2 => Os::Wasi,
OperatingSystem::Aix => Os::Aix,
OperatingSystem::Hurd => Os::Hurd,
OperatingSystem::Cygwin => Os::Cygwin,
unsupported => bail!("The operating system {:?} is not supported", unsupported),
};
let arch = match platform.architecture {
Architecture::X86_64 | Architecture::X86_64h => Arch::X86_64,
Architecture::X86_32(_) => Arch::X86,
Architecture::Arm(arm_arch) => match arm_arch {
ArmArchitecture::Armv5te => Arch::Armv5teL,
ArmArchitecture::Arm | ArmArchitecture::Armv6 => Arch::Armv6L,
ArmArchitecture::Armv7 => Arch::Armv7L,
_ => bail!("The architecture {} is not supported", arm_arch),
},
Architecture::Aarch64(_) => Arch::Aarch64,
Architecture::Powerpc => Arch::Powerpc,
Architecture::Powerpc64 => Arch::Powerpc64,
Architecture::Powerpc64le => Arch::Powerpc64Le,
Architecture::S390x => Arch::S390X,
Architecture::Wasm32 => Arch::Wasm32,
Architecture::Riscv32(_) => Arch::Riscv32,
Architecture::Riscv64(_) => Arch::Riscv64,
Architecture::Mips64(mips64_arch) => match mips64_arch {
Mips64Architecture::Mips64el => Arch::Mips64el,
Mips64Architecture::Mips64 => Arch::Mips64,
_ => bail!("The architecture {} is not supported", mips64_arch),
},
Architecture::Mips32(mips32_arch) => match mips32_arch {
Mips32Architecture::Mipsel => Arch::Mipsel,
Mips32Architecture::Mips => Arch::Mips,
_ => bail!("The architecture {} is not supported", mips32_arch),
},
Architecture::Sparc64 => Arch::Sparc64,
Architecture::Sparcv9 => Arch::Sparcv9,
Architecture::LoongArch64 => Arch::LoongArch64,
unsupported => bail!("The architecture {} is not supported", unsupported),
};
if !get_supported_architectures(&os).contains(&arch) {
bail!("{} is not supported on {}", arch, os);
}
let mut target = Target {
os,
arch,
env: platform.environment,
triple: target_triple.to_string(),
rustc_version,
user_specified,
cross_compiling: false,
};
target.cross_compiling = is_cross_compiling(&target)?;
Ok(target)
}
pub fn get_platform_arch(&self) -> Result<String> {
if self.cross_compiling {
return Ok(self.arch.to_string());
}
let machine = PlatformInfo::new().map(|info| info.machine().to_string_lossy().into_owned());
let arch = match machine {
Ok(machine) => {
let linux32 = (machine == "x86_64" && self.arch != Arch::X86_64)
|| (machine == "aarch64" && self.arch != Arch::Aarch64);
if linux32 {
self.arch.to_string()
} else {
machine
}
}
Err(err) => {
error!("Failed to get machine architecture: {}", err);
self.arch.to_string()
}
};
Ok(arch)
}
pub fn get_platform_release(&self) -> Result<String> {
let os = self.os.to_string();
let os_version = env::var(format!("MATURIN_{}_VERSION", os.to_ascii_uppercase()));
let release = match os_version {
Ok(os_ver) => os_ver,
Err(_) => {
let info = PlatformInfo::new()
.map_err(|e| anyhow!("Failed to fetch platform information: {e}"))?;
info.release().to_string_lossy().into_owned()
}
};
let release = release.replace(['.', '-'], "_");
Ok(release)
}
pub fn get_python_arch(&self) -> &str {
match self.arch {
Arch::Aarch64 => "aarch64",
Arch::Armv5teL => "armv5tel",
Arch::Armv6L => "armv6l",
Arch::Armv7L => "armv7l",
Arch::Powerpc => "ppc",
Arch::Powerpc64Le => "powerpc64le",
Arch::Powerpc64 => "powerpc64",
Arch::X86 => "i386",
Arch::X86_64 => "x86_64",
Arch::S390X => "s390x",
Arch::Wasm32 => "wasm32",
Arch::Riscv32 => "riscv32",
Arch::Riscv64 => "riscv64",
Arch::Mips64el | Arch::Mips64 => "mips64",
Arch::Mipsel | Arch::Mips => "mips",
Arch::Sparc64 => "sparc64",
Arch::Sparcv9 => "sparcv9",
Arch::LoongArch64 => "loongarch64",
}
}
pub fn get_python_ext_arch(&self, python_impl: InterpreterKind) -> &str {
if matches!(
self.target_arch(),
Arch::Armv5teL | Arch::Armv6L | Arch::Armv7L
) {
"arm"
} else if matches!(self.target_arch(), Arch::Powerpc64Le)
&& python_impl == InterpreterKind::PyPy
{
"ppc_64"
} else if matches!(self.target_arch(), Arch::X86) && python_impl == InterpreterKind::PyPy {
"x86"
} else if matches!(self.target_arch(), Arch::Powerpc) {
"powerpc"
} else {
self.get_python_arch()
}
}
pub fn get_python_target_env(
&self,
python_impl: InterpreterKind,
python_version: (usize, usize),
) -> String {
match python_impl {
CPython => {
if matches!(self.target_arch(), Arch::Mips64 | Arch::Mips64el) && self.is_linux() {
"gnuabi64".to_string()
} else if python_version >= (3, 11) {
self.target_env().to_string()
} else {
self.target_env().to_string().replace("musl", "gnu")
}
}
PyPy | GraalPy => "gnu".to_string(),
}
}
pub fn get_python_os(&self) -> &str {
match self.os {
Os::Android => "android",
Os::Windows => "windows",
Os::Linux => "linux",
Os::Macos => "darwin",
Os::Ios => "ios",
Os::FreeBsd => "freebsd",
Os::NetBsd => "netbsd",
Os::OpenBsd => "openbsd",
Os::Dragonfly => "dragonfly",
Os::Solaris => "sunos",
Os::Illumos => "sunos",
Os::Haiku => "haiku",
Os::Emscripten => "emscripten",
Os::Wasi => "wasi",
Os::Aix => "aix",
Os::Hurd => "gnu",
Os::Cygwin => "cygwin",
}
}
pub fn get_minimum_manylinux_tag(&self) -> PlatformTag {
match self.arch {
Arch::Aarch64 | Arch::Armv7L | Arch::Powerpc64 | Arch::Powerpc64Le | Arch::S390X => {
PlatformTag::manylinux2014()
}
Arch::X86 | Arch::X86_64 => {
if self.rustc_version.semver >= RUST_1_64_0 {
PlatformTag::manylinux2014()
} else {
PlatformTag::manylinux2010()
}
}
Arch::Riscv64 => PlatformTag::Manylinux {
major: 2,
minor: 31,
},
Arch::LoongArch64 => PlatformTag::Manylinux {
major: 2,
minor: 36,
},
Arch::Armv5teL
| Arch::Armv6L
| Arch::Wasm32
| Arch::Riscv32
| Arch::Mips64el
| Arch::Mips64
| Arch::Mipsel
| Arch::Mips
| Arch::Powerpc
| Arch::Sparc64
| Arch::Sparcv9 => PlatformTag::Linux,
}
}
pub fn pointer_width(&self) -> usize {
match self.arch {
Arch::Aarch64
| Arch::Powerpc64
| Arch::Powerpc64Le
| Arch::X86_64
| Arch::S390X
| Arch::Riscv64
| Arch::Mips64el
| Arch::Mips64
| Arch::Sparc64
| Arch::Sparcv9
| Arch::LoongArch64 => 64,
Arch::Armv5teL
| Arch::Armv6L
| Arch::Armv7L
| Arch::X86
| Arch::Wasm32
| Arch::Mipsel
| Arch::Mips
| Arch::Riscv32
| Arch::Powerpc => 32,
}
}
#[inline]
pub fn target_triple(&self) -> &str {
&self.triple
}
#[inline]
pub fn host_triple(&self) -> &str {
&self.rustc_version.host
}
pub fn is_unix(&self) -> bool {
match self.os {
Os::Windows => false,
Os::Linux
| Os::Macos
| Os::Ios
| Os::FreeBsd
| Os::NetBsd
| Os::OpenBsd
| Os::Dragonfly
| Os::Solaris
| Os::Illumos
| Os::Haiku
| Os::Emscripten
| Os::Wasi
| Os::Aix
| Os::Hurd
| Os::Cygwin
| Os::Android => true,
}
}
#[inline]
pub fn target_os(&self) -> Os {
self.os
}
#[inline]
pub fn target_arch(&self) -> Arch {
self.arch
}
#[inline]
pub fn target_env(&self) -> Environment {
self.env
}
#[inline]
pub fn is_linux(&self) -> bool {
self.os == Os::Linux
}
#[inline]
pub fn is_freebsd(&self) -> bool {
self.os == Os::FreeBsd
}
#[inline]
pub fn is_macos(&self) -> bool {
self.os == Os::Macos
}
#[inline]
pub fn is_ios(&self) -> bool {
self.os == Os::Ios
}
#[inline]
pub fn is_windows(&self) -> bool {
self.os == Os::Windows
}
#[inline]
pub fn is_msvc(&self) -> bool {
self.env == Environment::Msvc
}
#[inline]
pub fn is_cygwin(&self) -> bool {
self.os == Os::Cygwin
}
#[inline]
pub fn is_illumos(&self) -> bool {
self.os == Os::Illumos
}
#[inline]
pub fn is_haiku(&self) -> bool {
self.os == Os::Haiku
}
#[inline]
pub fn is_emscripten(&self) -> bool {
self.os == Os::Emscripten
}
#[inline]
pub fn is_wasi(&self) -> bool {
self.os == Os::Wasi
}
#[inline]
pub fn is_hurd(&self) -> bool {
self.os == Os::Hurd
}
#[inline]
pub fn is_aix(&self) -> bool {
self.os == Os::Aix
}
pub fn is_android(&self) -> bool {
self.os == Os::Android
}
#[inline]
pub fn is_musl_libc(&self) -> bool {
matches!(
self.env,
Environment::Musl
| Environment::Musleabi
| Environment::Musleabihf
| Environment::Muslabi64
)
}
#[inline]
pub fn cross_compiling(&self) -> bool {
self.cross_compiling
}
pub fn get_venv_python(&self, venv_base: impl AsRef<Path>) -> PathBuf {
let python = if self.is_windows() {
"python.exe"
} else {
"python"
};
self.get_venv_bin_dir(venv_base).join(python)
}
pub fn get_venv_bin_dir(&self, venv_base: impl AsRef<Path>) -> PathBuf {
let venv = venv_base.as_ref();
if self.is_windows() {
let bin_dir = venv.join("Scripts");
if bin_dir.join("python.exe").exists() {
return bin_dir;
}
let bin_dir = venv.join("bin");
if bin_dir.join("python.exe").exists() {
return bin_dir;
}
venv.to_path_buf()
} else {
venv.join("bin")
}
}
pub fn get_python(&self) -> PathBuf {
if let Some(venv) = env::var_os("VIRTUAL_ENV") {
let venv_python = self.get_venv_python(&venv);
if venv_python.exists() {
return venv_python;
}
}
if let Some(python_location) = env::var_os("pythonLocation") {
let python_location = Path::new(&python_location);
let python = if self.is_windows() {
python_location.join("python.exe")
} else {
python_location.join("bin").join("python3")
};
if python.exists() {
return python;
}
}
if self.is_windows() {
PathBuf::from("python.exe")
} else {
PathBuf::from("python3")
}
}
}
fn rustc_version_meta() -> Result<VersionMeta> {
let meta = rustc_version::version_meta().map_err(|err| match err {
rustc_version::Error::CouldNotExecuteCommand(e)
if e.kind() == std::io::ErrorKind::NotFound =>
{
anyhow!(
"rustc, the rust compiler, is not installed or not in PATH. \
This package requires Rust and Cargo to compile extensions. \
Install it through the system's package manager or via https://rustup.rs/.",
)
}
err => anyhow!(err).context("Failed to run rustc to get the host target"),
})?;
Ok(meta)
}
pub(crate) fn detect_arch_from_python(python: &PathBuf, target: &Target) -> Option<TargetTriple> {
match Command::new(python)
.arg("-c")
.arg("import sysconfig; print(sysconfig.get_platform(), end='')")
.output()
{
Ok(output) if output.status.success() => {
let platform = String::from_utf8_lossy(&output.stdout);
if platform.contains("macos") {
if platform.contains("x86_64") && target.target_arch() != Arch::X86_64 {
return Some(TargetTriple::Regular("x86_64-apple-darwin".to_string()));
} else if platform.contains("arm64") && target.target_arch() != Arch::Aarch64 {
return Some(TargetTriple::Regular("aarch64-apple-darwin".to_string()));
}
} else if platform.contains("win") {
if platform.contains("amd64") && target.target_arch() != Arch::X86_64 {
return Some(TargetTriple::Regular("x86_64-pc-windows-msvc".to_string()));
} else if platform.contains("arm64") && target.target_arch() != Arch::Aarch64 {
return Some(TargetTriple::Regular("aarch64-pc-windows-msvc".to_string()));
}
}
}
_ => eprintln!("⚠️ Warning: Failed to determine python platform"),
}
None
}
pub(crate) fn detect_target_from_cross_python(python: &PathBuf) -> Option<TargetTriple> {
match Command::new(python)
.arg("-c")
.arg("import sys, sysconfig; print(sysconfig.get_platform(), end='') if getattr(sys, 'cross_compiling', False) else ''")
.output()
{
Ok(output) if output.status.success() => {
let platform = String::from_utf8_lossy(&output.stdout);
if platform.ends_with("-arm64-iphoneos") {
return Some(TargetTriple::Regular("aarch64-apple-ios".to_string()));
} else if platform.ends_with("-arm64-iphonesimulator") {
return Some(TargetTriple::Regular("aarch64-apple-ios-sim".to_string()));
} else if platform.ends_with("-x86_64-iphonesimulator") {
return Some(TargetTriple::Regular("x86_64-apple-ios".to_string()));
}
}
_ => eprintln!("⚠️ Warning: Failed to determine python platform"),
}
None
}