--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:
-
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.
-
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.
-
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:
- Centralize target parsing into a single module that exports unified functions like
is_android(target), rust_target_triple(target), clang_target(target)
perry-codegen's resolve_target_triple and perry's rust_target_triple should share the same mapping table to avoid inconsistency
- 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.rs — resolve_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.rs — is_android + clang compile target
L63 — is_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-4759 — default_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
L974 — find_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-1085 — android_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-1309 — find_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-176 — rust_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
L59 — native_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.rs — rust_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"),
--target android-x86_64Cross-Compilation FailsWhat 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 withunknown file typeerrors.Inspecting the generated
.ofiles revealed their magic bytes were64 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:
resolve_target_triplemissingandroid-x86_64mapping (primary cause)perry-codegen'sresolve_target_triple()only recognized"android"→"aarch64-unknown-linux-android". It did not recognize"android-x86_64", returningNoneand causing codegen to fall back to the host target (x86_64-pc-windows-msvc). Clang then generated COFF instead of ELF.Numerous
is_android/ target matches missingandroid-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_64passes"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.libperry_ui_android.amissing x86_64 architecture buildThe dependency library needed to be compiled separately for the
x86_64-linux-androidtarget.After applying all fixes, compilation succeeded, producing
app/libapp.so(51.4MB) correctly placed underapp/src/main/jniLibs/x86_64/.What you expected
Perry should natively support
--target android-x86_64with behavior consistent with--target android(arm64):.ofileslib{stem}.sois placed in the correct pathMinimal reproduction
Using Perry source (0.5.1206), on Windows 11, run:
Project structure and dependencies are the same as in the previous report (includes
perry-ui-androiddependency).Environment
--target android-x86_64Diagnostic output
Linking error (before root cause fix):
Final successful compilation:
Final directory structure:
Anything else
Design Suggestion
Core Problem: Perry's target string to LLVM triple mapping is scattered across multiple crates (
perry-codegen'sresolve_target_triple,perry'srust_target_triple, variousis_androidchecks). Every new target variant requires synchronized changes across many locations.Suggestions:
is_android(target),rust_target_triple(target),clang_target(target)perry-codegen'sresolve_target_tripleandperry'srust_target_tripleshould share the same mapping table to avoid inconsistencyis_androidshould use prefix matching (e.g.,target.starts_with("android")) rather than enumerating every variantlibperry_ui_android.aBuild ScriptAfter fixing all source code, the linker will still report that the x86_64 version of
libperry_ui_android.acannot be found. This dependency library needs to be compiled separately for thex86_64-linux-androidtarget:Files Modified Summary
perry-codegen/src/codegen/helpers.rsandroid-x86_64mapping toresolve_target_tripleperry/src/commands/compile/link/platform_cmd.rsperry/src/commands/compile/link/build_and_run.rsis_android+ clang compile target dynamicperry/src/commands/compile/run_pipeline.rsis_androidlocations +default_output_pathAndroid branchperry/src/commands/compile/library_search.rsperry/src/commands/compile/app_metadata.rsrust_target_triple+ perry.toml section mappingperry/src/commands/compile/post_link.rsperry/src/commands/compile/resolve/native_library.rsperry/src/commands/compile/optimized_libs/driver.rsperry/src/commands/compile/lock_scan.rsperry/src/commands/run/entry.rsrust_target_triplemappingperry-ui-androidbuild scriptlibperry_ui_android.aforx86_64-linux-androidDetailed Code Changes
1.
perry-codegen/src/codegen/helpers.rs—resolve_target_tripleLocation: L518-521
2.
perry/src/commands/compile/link/platform_cmd.rs— Linker target dynamicLocation: L575-595
3.
perry/src/commands/compile/link/build_and_run.rs—is_android+ clang compile targetL63 —
is_android:L829-833 — JNI stub compile target:
4.
perry/src/commands/compile/run_pipeline.rs— 4 locationsL1435 — Link-time Android platform check:
L4394 — JNI stub
is_android:L4754-4759 —
default_output_pathAndroid branch:L4952 — companion .so copy:
5.
perry/src/commands/compile/library_search.rs— 3 locationsL974 —
find_ui_library:L1083-1085 —
android_cross_env:L1308-1309 —
find_geisterhand_ui:6.
perry/src/commands/compile/app_metadata.rs— 2 locationsL18 — perry.toml section mapping:
L175-176 —
rust_target_triple:7.
perry/src/commands/compile/post_link.rs— strip skip conditionL44-45:
8.
perry/src/commands/compile/resolve/native_library.rs— 2 locationsL59 —
native_manifest_target_key:L1318-1319 — native arch mapping:
9.
perry/src/commands/compile/optimized_libs/driver.rs— 3 locationsL749 — cc-rs NDK environment variables:
L780 — TLS global-dynamic:
L1018 — UI crate selection:
10.
perry/src/commands/compile/lock_scan.rs— target key mappingL163:
11.
perry/src/commands/run/entry.rs—rust_target_tripleL34-35: