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"),

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions