Skip to content

Implement Android platform tag support#2900

Merged
messense merged 1 commit intoPyO3:mainfrom
ririv:android-support-v2
Dec 30, 2025
Merged

Implement Android platform tag support#2900
messense merged 1 commit intoPyO3:mainfrom
ririv:android-support-v2

Conversation

@ririv
Copy link
Copy Markdown
Contributor

@ririv ririv commented Dec 26, 2025

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.whl

It must be renamed manually or by setting the _PYTHON_HOST_PLATFORM environment variable to obtain the correct tag name.

Changes

  • Android Platform Tag Support: Implemented the android_{api}_{arch} platform tag generation logic within the Linux match arm of get_platform_tag in src/build_context.rs.
  • Automatic API Level Detection:
    • Implemented a prioritized search for the Android API level:
      1. ANDROID_API_LEVEL environment variable.
      2. linker path resolution via cargo-config2 (handling .cargo/config.toml and CARGO_TARGET_<TRIPLE>_LINKER).
      3. CC_<TARGET> and CC environment variables.
    • Utilizes regex android(\d+) to accurately extract the version number from toolchain paths.
    • Explicit error reporting if no API level can be determined.
  • Strict Architecture Mapping:
    • Maps Rust target architectures to standard Android ABIs: armeabi_v7a, arm64_v8a, x86, and x86_64.
    • Return a bail! error to prevent incorrect tag generation for unsupported architectures.

Copilot AI review requested due to automatic review settings December 26, 2025 07:04
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread src/build_context.rs
Comment on lines +1318 to +1343
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());
}
}
Copy link

Copilot AI Dec 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment thread src/build_context.rs
}

// Search for android(\d+) in clues
let re = Regex::new(r"android(\d+)")?;
Copy link

Copilot AI Dec 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Copilot uses AI. Check for mistakes.
Comment thread src/build_context.rs
Comment on lines +1313 to +1348
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."
);
}
Copy link

Copilot AI Dec 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_LEVEL environment 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

Copilot uses AI. Check for mistakes.
Comment thread src/build_context.rs
Comment on lines +554 to +563
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)
Copy link

Copilot AI Dec 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copilot uses AI. Check for mistakes.
@messense messense merged commit 5ab0f54 into PyO3:main Dec 30, 2025
49 of 51 checks passed
@ririv ririv deleted the android-support-v2 branch January 1, 2026 15:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants