Skip to content

Windows Cross-Compilation to Android: Linker errors and fixes #5740

Description

@YC-CLT

Windows Cross-Compilation to Android Linker Failures

What happened

On Windows 11 using Perry 0.5.1206 (official release binary perry-windows-x86_64.zip) to compile a TypeScript application targeting aarch64-linux-android, the linking phase failed with multiple cascading errors:

  1. The command line exceeded Windows CreateProcess's 32767 character limit (error: The command line is too long.), causing the compilation to fail immediately.

  2. After enabling response files, backslashes \ in paths were interpreted as escape characters by clang/lld, causing object files to not be found (example errors: no such file or directory: '\?D:...o' and no such file or directory: 'd:Code...perry_stdlib.a').

  3. The NDK's .cmd wrapper invoked via cmd.exe /c added unnecessary command-line length overhead and introduced potential argument-passing issues.

  4. The default_output_path function lacked a special case for Android targets, returning the bare name app, which already existed as a directory. This caused the linker to fail with: ld.lld: error: cannot open output file app: Is a directory.

  5. A Rust 1.96 macro compatibility issue: replace_expr!($i f64) was missing a comma, causing a compilation error (no rules expected f64).

I subsequently modified the Perry source code (based on the 0.5.1206 tag) to fix the above issues, rebuilt my own Perry binary, and ultimately compiled successfully, producing app/libapp.so.

What you expected

Perry should successfully cross-compile Android dynamic libraries on Windows out-of-the-box. The output filename should be lib<project>.so (e.g., libapp.so), and the linking process should not be blocked by command-line length limits or path escaping issues, without requiring manual build script modifications.

Minimal reproduction

Using the official Perry release perry-windows-x86_64.zip (or building from source), on Windows 11, run:

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

The project needs enough object files (approximately 154 .o files) to trigger the command-line length limit. If minimization is difficult, reproducing with the above command and configuration should suffice.

Environment

  • Perry version: 0.5.1206 (initially used official release binary perry-windows-x86_64.zip; later modified source code from the same version tag and rebuilt)
  • Host OS: Windows 11 (x86_64)
  • Target: aarch64-linux-android
  • Rust toolchain (when building from source): 1.96.0 (ac68faa20 2026-05-25)
  • Android NDK: 30.0.14904198
  • Installed via: Initially downloaded release zip, later modified and compiled from source

Diagnostic output

Excerpts from error outputs at various stages:

Error: linking with `cmd.exe /c ...` failed: The command line is too long.
error: could not compile `app` (bin "app") due to previous error
clang: error: no such file or directory: '\?D:Code...o'
clang: error: no such file or directory: 'd:Code...perry_stdlib.a'
ld.lld: error: cannot open output file app: Is a directory
error: no rules expected `f64`
   --> crates/perry-runtime/src/abi_trampoline.rs:234:53

Debug output during the fix (response file successfully enabled):

DEBUG response_file: cfg_windows=true is_windows=false use_response_file=true
DEBUG response_file: msvc_quoting=false arg_count=154
DEBUG response_file: CREATED at D:\Temp\perry-link-9856.rsp

Final successful compilation output (using the modified version):

Copied companion library: libhone_editor_android.so
Wrote executable: app/libapp.so
Binary size: 50.6MB

Anything else

The fixes involved the following source files (based on Perry source tree 0.5.1206):

  • perry/crates/perry/src/commands/compile/link/platform_cmd.rs
    Changed the NDK clang invocation from aarch64-linux-android24-clang.cmd wrapper to directly calling clang.exe (with --target specified), avoiding the overhead of cmd.exe /c.

  • perry/crates/perry/src/commands/compile/link/mod.rs
    Modified the quote_response_arg function: in non-MSVC (i.e., GNU-style) mode, if a path contains backslashes, proactively replace them with forward slashes. Also fixed the @response.rsp argument path to use forward slashes, preventing clang/lld from interpreting \ as an escape character.

  • perry/crates/perry/src/commands/compile/run_pipeline.rs
    Added a special case for the Android target in default_output_path, returning PathBuf::from(format!("{}/lib{}.so", stem, stem)), ensuring the output file is placed in a subdirectory named after the project (e.g., app/libapp.so).

  • perry/crates/perry-runtime/src/abi_trampoline.rs
    Fixed replace_expr!($i f64) to replace_expr!($i, f64), resolving the Rust 1.96 macro parsing compatibility issue.

All modifications were made to the source code, and a custom Perry binary was rebuilt. The compilation pipeline then completed successfully, producing a 50.6 MB binary.

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