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:
-
The command line exceeded Windows CreateProcess's 32767 character limit (error: The command line is too long.), causing the compilation to fail immediately.
-
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').
-
The NDK's .cmd wrapper invoked via cmd.exe /c added unnecessary command-line length overhead and introduced potential argument-passing issues.
-
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.
-
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.
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 targetingaarch64-linux-android, the linking phase failed with multiple cascading errors:The command line exceeded Windows
CreateProcess's 32767 character limit (error:The command line is too long.), causing the compilation to fail immediately.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'andno such file or directory: 'd:Code...perry_stdlib.a').The NDK's
.cmdwrapper invoked viacmd.exe /cadded unnecessary command-line length overhead and introduced potential argument-passing issues.The
default_output_pathfunction lacked a special case for Android targets, returning the bare nameapp, which already existed as a directory. This caused the linker to fail with:ld.lld: error: cannot open output file app: Is a directory.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:The project needs enough object files (approximately 154
.ofiles) to trigger the command-line length limit. If minimization is difficult, reproducing with the above command and configuration should suffice.Environment
perry-windows-x86_64.zip; later modified source code from the same version tag and rebuilt)Diagnostic output
Excerpts from error outputs at various stages:
Debug output during the fix (response file successfully enabled):
Final successful compilation output (using the modified version):
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.rsChanged the NDK clang invocation from
aarch64-linux-android24-clang.cmdwrapper to directly callingclang.exe(with--targetspecified), avoiding the overhead ofcmd.exe /c.perry/crates/perry/src/commands/compile/link/mod.rsModified the
quote_response_argfunction: in non-MSVC (i.e., GNU-style) mode, if a path contains backslashes, proactively replace them with forward slashes. Also fixed the@response.rspargument path to use forward slashes, preventing clang/lld from interpreting\as an escape character.perry/crates/perry/src/commands/compile/run_pipeline.rsAdded a special case for the Android target in
default_output_path, returningPathBuf::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.rsFixed
replace_expr!($i f64)toreplace_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.