Implement Android platform tag support#2900
Conversation
There was a problem hiding this comment.
Pull request overview
This PR implements proper Android platform tag support in Maturin to comply with PEP 738's packaging specification for Android wheels. Previously, Maturin incorrectly generated Linux tags for Android builds.
Key Changes:
- Android-specific platform tag generation with
android_{api}_{arch}format - Automatic API level detection with multi-source fallback strategy
- Strict architecture validation for Android ABIs
| let mut clues = Vec::new(); | ||
|
|
||
| // 1. Linker from cargo-config2 | ||
| if let Some(manifest_dir) = manifest_path.parent() { | ||
| if let Ok(config) = cargo_config2::Config::load_with_cwd(manifest_dir) { | ||
| if let Ok(Some(linker)) = config.linker(target_triple) { | ||
| clues.push(linker.to_string_lossy().into_owned()); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // 2. CC env vars | ||
| if let Ok(cc) = env::var(format!("CC_{}", target_triple.replace('-', "_"))) { | ||
| clues.push(cc); | ||
| } | ||
| if let Ok(cc) = env::var("CC") { | ||
| clues.push(cc); | ||
| } | ||
|
|
||
| // Search for android(\d+) in clues | ||
| let re = Regex::new(r"android(\d+)")?; | ||
| for clue in clues { | ||
| if let Some(caps) = re.captures(&clue) { | ||
| return Ok(caps[1].to_string()); | ||
| } | ||
| } |
There was a problem hiding this comment.
The clues vector is used to collect potential sources for the API level, but if all collection operations fail silently (e.g., manifest_path.parent() returns None, config loading fails, environment variables are not set), the function will always bail with a generic error. Consider adding debug logging to help users understand which detection methods were attempted and why they failed. This would make debugging configuration issues much easier.
| } | ||
|
|
||
| // Search for android(\d+) in clues | ||
| let re = Regex::new(r"android(\d+)")?; |
There was a problem hiding this comment.
The regex pattern is compiled on every call to find_android_api_level. This function is called during wheel tag generation, and compiling a regex repeatedly is inefficient. Consider using once_cell::sync::Lazy to compile the regex once and reuse it, similar to the pattern used elsewhere in the codebase (e.g., IS_LIBPYTHON in src/auditwheel/audit.rs).
| fn find_android_api_level(target_triple: &str, manifest_path: &Path) -> Result<String> { | ||
| if let Ok(val) = env::var("ANDROID_API_LEVEL") { | ||
| return Ok(val); | ||
| } | ||
|
|
||
| let mut clues = Vec::new(); | ||
|
|
||
| // 1. Linker from cargo-config2 | ||
| if let Some(manifest_dir) = manifest_path.parent() { | ||
| if let Ok(config) = cargo_config2::Config::load_with_cwd(manifest_dir) { | ||
| if let Ok(Some(linker)) = config.linker(target_triple) { | ||
| clues.push(linker.to_string_lossy().into_owned()); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // 2. CC env vars | ||
| if let Ok(cc) = env::var(format!("CC_{}", target_triple.replace('-', "_"))) { | ||
| clues.push(cc); | ||
| } | ||
| if let Ok(cc) = env::var("CC") { | ||
| clues.push(cc); | ||
| } | ||
|
|
||
| // Search for android(\d+) in clues | ||
| let re = Regex::new(r"android(\d+)")?; | ||
| for clue in clues { | ||
| if let Some(caps) = re.captures(&clue) { | ||
| return Ok(caps[1].to_string()); | ||
| } | ||
| } | ||
|
|
||
| bail!( | ||
| "Failed to determine Android API level. Please set the ANDROID_API_LEVEL environment variable." | ||
| ); | ||
| } |
There was a problem hiding this comment.
The new find_android_api_level function lacks test coverage. Given that the codebase has comprehensive tests (as seen in the mod tests section of this file), this function should include tests for:
- API level detection from
ANDROID_API_LEVELenvironment variable - API level extraction from linker paths
- API level extraction from CC environment variables
- Error handling when no API level can be determined
- Regex matching behavior with various toolchain path formats
| if target.target_triple().contains("android") { | ||
| let android_arch = match arch.as_str() { | ||
| "armv7l" => "armeabi_v7a", | ||
| "aarch64" => "arm64_v8a", | ||
| "i686" => "x86", | ||
| "x86_64" => "x86_64", | ||
| _ => bail!("Unsupported Android architecture: {}", arch), | ||
| }; | ||
| let api_level = find_android_api_level(target.target_triple(), &self.manifest_path)?; | ||
| format!("android_{}_{}", api_level, android_arch) |
There was a problem hiding this comment.
The Android platform tag generation logic lacks test coverage. The existing test module in this file includes tests for other platform tag generation (e.g., test_macosx_deployment_target, test_iphoneos_deployment_target). Consider adding similar tests for Android platform tags that verify:
- Correct tag format generation for different architectures (armv7l, aarch64, i686, x86_64)
- Integration with API level detection
- Error handling for unsupported architectures
Motivation
Enhancement #2803
#2825 fixed a bug so that Maturin can now compile a wheel for Android.
However, the generated wheel's tag name is incorrect. This is because Maturin lacks support for Android tags and treats Android as Linux, generating a Linux tag.
Python's Android wheel tags are strictly defined. See PEP738
This is Log. https://github.com/ririv/android-wheels/actions/runs/20456436296/job/58779454736
At the end of the wheel building process, we can see:
Successfully built XXXXX-linux_aarch64.whlIt must be renamed manually or by setting the
_PYTHON_HOST_PLATFORMenvironment variable to obtain the correct tag name.Changes
android_{api}_{arch}platform tag generation logic within the Linux match arm ofget_platform_taginsrc/build_context.rs.ANDROID_API_LEVELenvironment variable.linkerpath resolution viacargo-config2(handling.cargo/config.tomlandCARGO_TARGET_<TRIPLE>_LINKER).CC_<TARGET>andCCenvironment variables.android(\d+)to accurately extract the version number from toolchain paths.armeabi_v7a,arm64_v8a,x86, andx86_64.bail!error to prevent incorrect tag generation for unsupported architectures.