Skip to content

--target android-x86_64 Cross-Compilation Fails #5742

Description

@YC-CLT

--target android-x86_64 Cross-Compilation Fails

What happened

On Windows 11 using Perry 0.5.1206 (built from source) to compile a TypeScript application targeting android-x86_64, the linking phase failed with unknown file type errors.

Inspecting the generated .o files revealed their magic bytes were 64 86 28 00 (0x8664 = IMAGE_FILE_MACHINE_AMD64), meaning Windows COFF format was produced instead of the required Android ELF format.

The root causes were:

  1. resolve_target_triple missing android-x86_64 mapping (primary cause)
    perry-codegen's resolve_target_triple() only recognized "android""aarch64-unknown-linux-android". It did not recognize "android-x86_64", returning None and causing codegen to fall back to the host target (x86_64-pc-windows-msvc). Clang then generated COFF instead of ELF.

  2. Numerous is_android / target matches missing android-x86_64 (secondary issues)
    After fixing the root cause, many locations throughout the Perry pipeline check Android via matches!(target, Some("android")). Since --target android-x86_64 passes "android-x86_64", these checks do not match, causing all Android-specific logic (JNI compilation, library search, output path, strip skipping, etc.) to be skipped.

  3. libperry_ui_android.a missing x86_64 architecture build
    The dependency library needed to be compiled separately for the x86_64-linux-android target.

After applying all fixes, compilation succeeded, producing app/libapp.so (51.4MB) correctly placed under app/src/main/jniLibs/x86_64/.

What you expected

Perry should natively support --target android-x86_64 with behavior consistent with --target android (arm64):

  • Codegen produces ELF .o files
  • Linker uses the correct Android x86_64 toolchain
  • JNI stubs are compiled for x86_64
  • Output lib{stem}.so is placed in the correct path

Minimal reproduction

Using Perry source (0.5.1206), on Windows 11, run:

perry compile src/ts/app.ts --target android-x86_64

Project structure and dependencies are the same as in the previous report (includes perry-ui-android dependency).

Environment

Item Version/Value
OS Windows 11 (x86_64)
Perry version 0.5.1206 (source)
Rust 1.96.0 (ac68faa20 2026-05-25)
Android NDK 30.0.14904198
Target x86_64-linux-android / --target android-x86_64

Diagnostic output

Linking error (before root cause fix):

ld.lld: error: //?/D:/CodeFile/AndroidApps/SuperdeerCode/node_modules/.cache/perry/objects/android-x86_64/37ef4a4d82f3ec1c.o: unknown file type
ld.lld: error: //?/D:/CodeFile/AndroidApps/SuperdeerCode/node_modules/.cache/perry/objects/android-x86_64/09da2bd0eb58ffa1.o: unknown file type
ld.lld: error: too many errors emitted stopping now (use --error-limit=0 to see all errors)
clang: error: linker command failed with exit code 1 (use -v to see invocation)

Final successful compilation:

$ .\perry.exe compile src/ts/app.ts --target android-x86_64

Collecting modules...
  i18n: 2 locale(s) [en, zh-Hans] default: en
  Native library: @honeide/editor/perry (76 FFI functions)
Linking native library: \\?\...\x86_64-linux-android\release\libhone_editor_android.so
Copied companion library: libhone_editor_android.so
Wrote executable: app/libapp.so
  Generated res/values-*/strings.xml for 2 locale(s)
Binary size: 51.4MB

Final directory structure:

app/src/main/jniLibs/
├── arm64-v8a/
│   └── ...
└── x86_64/
    ├── libapp.so                     (52.3 MB)
    └── libhone_editor_android.so

Anything else

Design Suggestion

Core Problem: Perry's target string to LLVM triple mapping is scattered across multiple crates (perry-codegen's resolve_target_triple, perry's rust_target_triple, various is_android checks). Every new target variant requires synchronized changes across many locations.

Suggestions:

  1. Centralize target parsing into a single module that exports unified functions like is_android(target), rust_target_triple(target), clang_target(target)
  2. perry-codegen's resolve_target_triple and perry's rust_target_triple should share the same mapping table to avoid inconsistency
  3. Boolean checks like is_android should use prefix matching (e.g., target.starts_with("android")) rather than enumerating every variant

libperry_ui_android.a Build Script

After fixing all source code, the linker will still report that the x86_64 version of libperry_ui_android.a cannot be found. This dependency library needs to be compiled separately for the x86_64-linux-android target:

$env:RUSTC_BOOTSTRAP = "1"
$env:RUSTFLAGS = "-Z tls-model=global-dynamic"
$env:ANDROID_NDK_HOME = "d:\code\AndroidSDK\ndk\30.0.14904198"
$env:CC_x86_64_linux_android = "d:\code\AndroidSDK\ndk\30.0.14904198\toolchains\llvm\prebuilt\windows-x86_64\bin\x86_64-linux-android24-clang.cmd"
$env:CXX_x86_64_linux_android = "d:\code\AndroidSDK\ndk\30.0.14904198\toolchains\llvm\prebuilt\windows-x86_64\bin\x86_64-linux-android24-clang++.cmd"
$env:AR_x86_64_linux_android = "d:\code\AndroidSDK\ndk\30.0.14904198\toolchains\llvm\prebuilt\windows-x86_64\bin\llvm-ar.exe"
$env:CARGO_TARGET_X86_64_LINUX_ANDROID_LINKER = "d:\code\AndroidSDK\ndk\30.0.14904198\toolchains\llvm\prebuilt\windows-x86_64\bin\x86_64-linux-android24-clang.cmd"
cargo build --release -p perry-ui-android --target x86_64-linux-android

Files Modified Summary

File Change Description
perry-codegen/src/codegen/helpers.rs Add android-x86_64 mapping to resolve_target_triple
perry/src/commands/compile/link/platform_cmd.rs Make linker target dynamic by architecture
perry/src/commands/compile/link/build_and_run.rs is_android + clang compile target dynamic
perry/src/commands/compile/run_pipeline.rs 3 is_android locations + default_output_path Android branch
perry/src/commands/compile/library_search.rs 3 target match locations
perry/src/commands/compile/app_metadata.rs rust_target_triple + perry.toml section mapping
perry/src/commands/compile/post_link.rs strip skip condition
perry/src/commands/compile/resolve/native_library.rs 2 target mappings
perry/src/commands/compile/optimized_libs/driver.rs 3 target matches
perry/src/commands/compile/lock_scan.rs target key mapping
perry/src/commands/run/entry.rs rust_target_triple mapping
perry-ui-android build script Build libperry_ui_android.a for x86_64-linux-android

Detailed Code Changes

1. perry-codegen/src/codegen/helpers.rsresolve_target_triple

Location: L518-521

// Before
"android" => {
    Some("aarch64-unknown-linux-android".to_string())
}

// After
"android" | "android-arm64" => {
    Some("aarch64-unknown-linux-android".to_string())
}
"android-x86_64" => Some("x86_64-unknown-linux-android".to_string()),

2. perry/src/commands/compile/link/platform_cmd.rs — Linker target dynamic

Location: L575-595

// Before — hardcoded aarch64
let clang = format!(
    "{}/toolchains/llvm/prebuilt/{}/bin/aarch64-linux-android24-clang{}",
    ndk_home, host_tag,
    if cfg!(target_os = "windows") { ".cmd" } else { "" }
);
// ...
c.arg("-target").arg("aarch64-linux-android24")

// After — use clang.exe directly, target dynamic
let clang = format!(
    "{}/toolchains/llvm/prebuilt/{}/bin/clang{}",
    ndk_home, host_tag,
    if cfg!(target_os = "windows") { ".exe" } else { "" }
);
// ...
c.arg("-target")
    .arg(if target == Some("android-x86_64") {
        "x86_64-linux-android24"
    } else {
        "aarch64-linux-android24"
    })

3. perry/src/commands/compile/link/build_and_run.rsis_android + clang compile target

L63is_android:

// Before
let is_android = matches!(target, Some("android") | Some("wearos"));

// After
let is_android = matches!(target, Some("android") | Some("android-x86_64") | Some("wearos"));

L829-833 — JNI stub compile target:

// Before
.arg("aarch64-linux-android24")

// After
.arg(if target == Some("android-x86_64") {
    "x86_64-linux-android24"
} else {
    "aarch64-linux-android24"
})

4. perry/src/commands/compile/run_pipeline.rs — 4 locations

L1435 — Link-time Android platform check:

// Before
| Some("android")
| Some("wearos")

// After
| Some("android")
| Some("android-x86_64")
| Some("wearos")

L4394 — JNI stub is_android:

// Before
let is_android = matches!(target.as_deref(), Some("android") | Some("wearos"));

// After
let is_android = matches!(target.as_deref(), Some("android") | Some("android-x86_64") | Some("wearos"));

L4754-4759default_output_path Android branch:

// Before — no Android branch, falls to else { PathBuf::from(stem) }

// After
} else if matches!(target, Some("android") | Some("android-x86_64")) {
    PathBuf::from(format!("{}/lib{}.so", stem, stem))

L4952 — companion .so copy:

// Before
let is_android = matches!(target.as_deref(), Some("android") | Some("wearos"));

// After
let is_android = matches!(target.as_deref(), Some("android") | Some("android-x86_64") | Some("wearos"));

5. perry/src/commands/compile/library_search.rs — 3 locations

L974find_ui_library:

// Before
Some("android") | Some("wearos") => "libperry_ui_android.a",

// After
Some("android") | Some("android-x86_64") | Some("wearos") => "libperry_ui_android.a",

L1083-1085android_cross_env:

// Before — arm64 only
let (triple, clang_target) = ("aarch64-linux-android", "aarch64-linux-android24");

// After — branch by architecture
let (triple, clang_target) = match target {
    Some("android-x86_64") => ("x86_64-linux-android", "x86_64-linux-android24"),
    _ => ("aarch64-linux-android", "aarch64-linux-android24"),
};

L1308-1309find_geisterhand_ui:

// Before
} else if matches!(target, Some("android") | Some("wearos")) {

// After
} else if matches!(target, Some("android") | Some("android-x86_64") | Some("wearos")) {

6. perry/src/commands/compile/app_metadata.rs — 2 locations

L18 — perry.toml section mapping:

// Before
Some("android") | Some("wearos") => Some("android"),

// After
Some("android") | Some("android-x86_64") => Some("android"),
Some("wearos") => Some("android"),

L175-176rust_target_triple:

// Before
Some("android") => Some("aarch64-linux-android"),

// After
Some("android") => Some("aarch64-linux-android"),
Some("android-x86_64") => Some("x86_64-linux-android"),

7. perry/src/commands/compile/post_link.rs — strip skip condition

L44-45:

// Before
|| target == Some("android")

// After
|| target == Some("android")
|| target == Some("android-x86_64")

8. perry/src/commands/compile/resolve/native_library.rs — 2 locations

L59native_manifest_target_key:

// Before
Some("android") | Some("wearos") => "android",

// After
Some("android") | Some("android-x86_64") | Some("wearos") => "android",

L1318-1319 — native arch mapping:

// Before
Some("android") | Some("wearos") => Some("arm64"),

// After
Some("android") | Some("wearos") => Some("arm64"),
Some("android-x86_64") => Some("x64"),

9. perry/src/commands/compile/optimized_libs/driver.rs — 3 locations

L749 — cc-rs NDK environment variables:

// Before
Some("android") | Some("wearos")

// After
Some("android") | Some("android-x86_64") | Some("wearos")

L780 — TLS global-dynamic:

// Before
Some("android") | Some("wearos")

// After
Some("android") | Some("android-x86_64") | Some("wearos")

L1018 — UI crate selection:

// Before
Some("android") | Some("wearos") => "perry-ui-android",

// After
Some("android") | Some("android-x86_64") | Some("wearos") => "perry-ui-android",

10. perry/src/commands/compile/lock_scan.rs — target key mapping

L163:

// Before
Some("android") | Some("wearos") => "android".to_string(),

// After
Some("android") | Some("android-x86_64") | Some("wearos") => "android".to_string(),

11. perry/src/commands/run/entry.rsrust_target_triple

L34-35:

// Before
Some("android") => Some("aarch64-linux-android"),

// After
Some("android") => Some("aarch64-linux-android"),
Some("android-x86_64") => Some("x86_64-linux-android"),

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugConfirmed defect or regressiontriagedMaintainer reviewed; type, scope, and next step are clear

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions