From 71b9f830a76d1cac4b8b3be225e734d4b381e009 Mon Sep 17 00:00:00 2001 From: Yaksh Bariya Date: Fri, 13 Feb 2026 09:20:47 +0530 Subject: [PATCH 1/5] fix: platform.system() on Android for Python 3.13+ Python from 3.13 onwards supports Android as an official platform. As a part of this official support, there were a bunch of changes including `platform.system()` returning "Android" and `sys.platform` being "android" instead of "Linux" and "linux" on earlier versions. This is a breaking change. Also try to be backwards compatible while running on Android for older python versions in environments like Termux This maturin bug was found as a part of testing of builds of python packages for Termux as part of Python 3.13 migration. Downstream Python distribution update PR: https://github.com/termux/termux-packages/pull/27739 Ideally we would like to ensure that get_python_os() returns "linux" for Python versions <= 3.12, but that'd require additional work in supplying python version to get_python_os(), but that'd complicate some things. This works well enough, and shouldn't be necessary in future when Python >= 3.13 becomes the standard target by most (which should ideally happen soon since upstream Python is supporting Android from this version). My analysis of maturin reveal that get_python_os() returning "android" shouldn't cause any effects on Android targets that do need to be patched. Issue also reported to maturin in https://github.com/PyO3/maturin/issues/2945 Partially based of off work by @robertkirkman in the linked issue. Further fixup was done to ensure suitability for upstream acceptance. Ref: https://github.com/python/cpython/pull/116215 --- src/python_interpreter/config.rs | 2 +- src/python_interpreter/mod.rs | 19 ++++++++++++++----- src/target/mod.rs | 24 ++++++++++++++++++++++-- 3 files changed, 37 insertions(+), 8 deletions(-) diff --git a/src/python_interpreter/config.rs b/src/python_interpreter/config.rs index 6f5f17ac3..e57fe641e 100644 --- a/src/python_interpreter/config.rs +++ b/src/python_interpreter/config.rs @@ -372,7 +372,7 @@ impl InterpreterConfig { }), }; let file_ext = if target.is_windows() { "pyd" } else { "so" }; - let ext_suffix = if target.is_linux() || target.is_macos() || target.is_hurd() { + let ext_suffix = if target.is_linux() || target.is_android() || target.is_macos() || target.is_hurd() { let target_env = target.get_python_target_env(interpreter_kind, (major, minor)); match interpreter_kind { InterpreterKind::CPython => ext_suffix.unwrap_or_else(|| { diff --git a/src/python_interpreter/mod.rs b/src/python_interpreter/mod.rs index 780182617..6400fff22 100644 --- a/src/python_interpreter/mod.rs +++ b/src/python_interpreter/mod.rs @@ -13,6 +13,7 @@ use std::ops::Deref; use std::path::{Path, PathBuf}; use std::process::{Command, Stdio}; use std::str::{self, FromStr}; +use target_lexicon::Environment; use tracing::{debug, instrument}; mod config; @@ -295,11 +296,19 @@ fn fun_with_abiflags( && !(target.get_python_os() == "cygwin" && message.system.to_lowercase().starts_with("cygwin")) { - bail!( - "platform.system() in python, {}, and the rust target, {:?}, don't match ಠ_ಠ", - message.system, - target, - ) + // Python versions <= 3.12 used to report sys.platform as "linux". Only on Python versions + // >= 3.13, sys.platform reports as "android". So maintain backwards compatibility with + // Python 3.12 when compiling on Android environment (for e.g. Termux) + 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 { diff --git a/src/target/mod.rs b/src/target/mod.rs index 8838d84a5..ba0c38110 100644 --- a/src/target/mod.rs +++ b/src/target/mod.rs @@ -44,6 +44,7 @@ pub enum Os { Aix, Hurd, Cygwin, + Android, } impl fmt::Display for Os { @@ -65,6 +66,7 @@ impl fmt::Display for Os { Os::Aix => write!(f, "AIX"), Os::Hurd => write!(f, "Hurd"), Os::Cygwin => write!(f, "Cygwin"), + Os::Android => write!(f, "Android"), } } } @@ -160,6 +162,14 @@ impl Arch { // Returns the set of supported architectures for each operating system fn get_supported_architectures(os: &Os) -> Vec { match os { + Os::Android => vec![ + Arch::Aarch64, + Arch::Armv5teL, + Arch::Armv6L, + Arch::Armv7L, + Arch::X86, + Arch::X86_64, + ], Os::Linux => vec![ Arch::Aarch64, Arch::Armv5teL, @@ -277,7 +287,10 @@ impl Target { .map_err(|_| format_err!("Unknown target triple {}", target_triple))?; let os = match platform.operating_system { - OperatingSystem::Linux => Os::Linux, + 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, @@ -457,6 +470,7 @@ impl Target { /// Returns the name python uses in `sys.platform` for this os pub fn get_python_os(&self) -> &str { match self.os { + Os::Android => "android", Os::Windows => "windows", Os::Linux => "linux", Os::Macos => "darwin", @@ -570,7 +584,8 @@ impl Target { | Os::Wasi | Os::Aix | Os::Hurd - | Os::Cygwin => true, + | Os::Cygwin + | Os::Android => true, } } @@ -670,6 +685,11 @@ impl Target { self.os == Os::Aix } + /// Returns true if we're building a binary for Android + pub fn is_android(&self) -> bool { + self.os == Os::Android + } + /// Returns true if the current platform's target env is Musl #[inline] pub fn is_musl_libc(&self) -> bool { From 63411c89629cc31e4e286a8218bdfd39c30cc65f Mon Sep 17 00:00:00 2001 From: Yaksh Bariya Date: Fri, 13 Feb 2026 09:44:57 +0530 Subject: [PATCH 2/5] fixup! fix: platform.system() on Android for Python 3.13+ --- src/python_interpreter/config.rs | 94 ++++++++++++++++---------------- src/python_interpreter/mod.rs | 3 +- 2 files changed, 50 insertions(+), 47 deletions(-) diff --git a/src/python_interpreter/config.rs b/src/python_interpreter/config.rs index e57fe641e..560c15660 100644 --- a/src/python_interpreter/config.rs +++ b/src/python_interpreter/config.rs @@ -372,57 +372,59 @@ impl InterpreterConfig { }), }; let file_ext = if target.is_windows() { "pyd" } else { "so" }; - let ext_suffix = if target.is_linux() || target.is_android() || target.is_macos() || target.is_hurd() { - let target_env = target.get_python_target_env(interpreter_kind, (major, minor)); - match interpreter_kind { - InterpreterKind::CPython => ext_suffix.unwrap_or_else(|| { - // Eg: .cpython-38-x86_64-linux-gnu.so - format!( - ".cpython-{}-{}-{}-{}.{}", - abi_tag, - target.get_python_ext_arch(interpreter_kind), - target.get_python_os(), - target_env, - file_ext, - ) - }), - InterpreterKind::PyPy => ext_suffix.unwrap_or_else(|| { - // Eg: .pypy38-pp73-x86_64-linux-gnu.so + let ext_suffix = + if target.is_linux() || target.is_android() || target.is_macos() || target.is_hurd() { + let target_env = target.get_python_target_env(interpreter_kind, (major, minor)); + match interpreter_kind { + InterpreterKind::CPython => ext_suffix.unwrap_or_else(|| { + // Eg: .cpython-38-x86_64-linux-gnu.so + format!( + ".cpython-{}-{}-{}-{}.{}", + abi_tag, + target.get_python_ext_arch(interpreter_kind), + target.get_python_os(), + target_env, + file_ext, + ) + }), + InterpreterKind::PyPy => ext_suffix.unwrap_or_else(|| { + // Eg: .pypy38-pp73-x86_64-linux-gnu.so + format!( + ".pypy{}{}-{}-{}-{}-{}.{}", + major, + minor, + abi_tag, + target.get_python_ext_arch(interpreter_kind), + target.get_python_os(), + target_env, + file_ext, + ) + }), + InterpreterKind::GraalPy => ext_suffix.unwrap_or_else(|| { + // e.g. .graalpy230-310-native-x86_64-linux.so + format!( + ".{}-{}-{}.{}", + abi_tag.replace('_', "-"), + target.get_python_ext_arch(interpreter_kind), + target.get_python_os(), + file_ext, + ) + }), + } + } else if target.is_emscripten() && matches!(interpreter_kind, InterpreterKind::CPython) + { + ext_suffix.unwrap_or_else(|| { format!( - ".pypy{}{}-{}-{}-{}-{}.{}", - major, - minor, + ".cpython-{}-{}-{}.{}", abi_tag, target.get_python_ext_arch(interpreter_kind), target.get_python_os(), - target_env, - file_ext, + file_ext ) - }), - InterpreterKind::GraalPy => ext_suffix.unwrap_or_else(|| { - // e.g. .graalpy230-310-native-x86_64-linux.so - format!( - ".{}-{}-{}.{}", - abi_tag.replace('_', "-"), - target.get_python_ext_arch(interpreter_kind), - target.get_python_os(), - file_ext, - ) - }), - } - } else if target.is_emscripten() && matches!(interpreter_kind, InterpreterKind::CPython) { - ext_suffix.unwrap_or_else(|| { - format!( - ".cpython-{}-{}-{}.{}", - abi_tag, - target.get_python_ext_arch(interpreter_kind), - target.get_python_os(), - file_ext - ) - }) - } else { - ext_suffix.context("missing value for ext_suffix")? - }; + }) + } else { + ext_suffix.context("missing value for ext_suffix")? + }; let gil_disabled = build_flags .map(|flags| flags.contains("Py_GIL_DISABLED")) .unwrap_or(false); diff --git a/src/python_interpreter/mod.rs b/src/python_interpreter/mod.rs index 6400fff22..477660cec 100644 --- a/src/python_interpreter/mod.rs +++ b/src/python_interpreter/mod.rs @@ -301,7 +301,8 @@ fn fun_with_abiflags( // Python 3.12 when compiling on Android environment (for e.g. Termux) let is_android_compat = target.get_python_os() == "android" && message.system == "linux" - && message.major == 3 && message.minor <= 12; + && message.major == 3 + && message.minor <= 12; if !is_android_compat { bail!( "platform.system() in python, {}, and the rust target, {:?}, don't match ಠ_ಠ", From b31e7871b13fa4eafc33df8d1eaade94ff2eea64 Mon Sep 17 00:00:00 2001 From: Yaksh Bariya Date: Fri, 13 Feb 2026 09:55:22 +0530 Subject: [PATCH 3/5] fix: pypi tag detection for Android target --- src/target/pypi_tags.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/target/pypi_tags.rs b/src/target/pypi_tags.rs index 9914ad1e7..040f886c0 100644 --- a/src/target/pypi_tags.rs +++ b/src/target/pypi_tags.rs @@ -51,7 +51,7 @@ pub fn is_arch_supported_by_pypi(target: &Target) -> bool { // PyPI allows iOS with arm64 and x86_64 (simulator) matches!(normalized_arch, "arm64" | "x86_64") } - Os::Linux if target.target_triple().contains("android") => { + Os::Android => { // Android target triples map to specific platform tag architectures let android_arch = match arch.as_str() { "armv7l" => "armeabi_v7a", // armv7 little-endian From 941bfff2611fbda2f2e692b55090100bcc34fc33 Mon Sep 17 00:00:00 2001 From: Yaksh Bariya Date: Fri, 13 Feb 2026 10:00:11 +0530 Subject: [PATCH 4/5] squashme!: remove unnecessary use --- src/python_interpreter/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/python_interpreter/mod.rs b/src/python_interpreter/mod.rs index 477660cec..cadb399d8 100644 --- a/src/python_interpreter/mod.rs +++ b/src/python_interpreter/mod.rs @@ -13,7 +13,6 @@ use std::ops::Deref; use std::path::{Path, PathBuf}; use std::process::{Command, Stdio}; use std::str::{self, FromStr}; -use target_lexicon::Environment; use tracing::{debug, instrument}; mod config; From d22af78c413c893df82cbf41fbc45b5548757b63 Mon Sep 17 00:00:00 2001 From: Yaksh Bariya Date: Fri, 13 Feb 2026 12:26:53 +0530 Subject: [PATCH 5/5] chore: fix comments mentioning sys.platform instead of platform.system() Nice catch by Copilot --- src/python_interpreter/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/python_interpreter/mod.rs b/src/python_interpreter/mod.rs index cadb399d8..681b48adb 100644 --- a/src/python_interpreter/mod.rs +++ b/src/python_interpreter/mod.rs @@ -295,8 +295,8 @@ fn fun_with_abiflags( && !(target.get_python_os() == "cygwin" && message.system.to_lowercase().starts_with("cygwin")) { - // Python versions <= 3.12 used to report sys.platform as "linux". Only on Python versions - // >= 3.13, sys.platform reports as "android". So maintain backwards compatibility with + // Python versions <= 3.12 used to report platform.system() as "linux". Only on Python versions + // >= 3.13, platform.system() reports as "android". So maintain backwards compatibility with // Python 3.12 when compiling on Android environment (for e.g. Termux) let is_android_compat = target.get_python_os() == "android" && message.system == "linux"