QuickJS-ng supports cross-compilation to various target platforms. This guide covers the configuration and best practices for cross-compiling the library and tools.
Overview
Cross-compilation is the process of building QuickJS-ng on one platform (the host) to run on a different platform (the target). This is commonly used for:
Building for embedded systems
Creating ARM binaries on x86 machines
Compiling for mobile platforms (iOS, Android)
WebAssembly builds
Windows builds from Linux
Build System Support
Meson Recommended for cross-compilation with excellent toolchain support and automatic handling of native builds
CMake Supported via toolchain files, requires manual configuration of cross-compilation settings
Meson Cross-Compilation
Meson provides the best cross-compilation experience with automatic handling of native vs. target builds.
Native vs. Target Builds
When cross-compiling, Meson automatically builds two versions of certain tools:
Native tools : Built for the host machine (e.g., qjsc to compile JavaScript to bytecode)
Target binaries : Built for the target platform (e.g., qjs runtime for ARM)
This is necessary because the bytecode compiler (qjsc) needs to run on the build machine during compilation.
Cross-Compilation File
Create a cross-compilation file describing your target platform:
ARM64 Linux
ARM32 Linux
Windows (MinGW)
[binaries]
c = 'aarch64-linux-gnu-gcc'
cpp = 'aarch64-linux-gnu-g++'
ar = 'aarch64-linux-gnu-ar'
strip = 'aarch64-linux-gnu-strip'
pkg-config = 'aarch64-linux-gnu-pkg-config'
[host_machine]
system = 'linux'
cpu_family = 'aarch64'
cpu = 'armv8'
endian = 'little'
[binaries]
c = 'arm-linux-gnueabihf-gcc'
cpp = 'arm-linux-gnueabihf-g++'
ar = 'arm-linux-gnueabihf-ar'
strip = 'arm-linux-gnueabihf-strip'
pkg-config = 'arm-linux-gnueabihf-pkg-config'
[host_machine]
system = 'linux'
cpu_family = 'arm'
cpu = 'armv7'
endian = 'little'
[binaries]
c = 'x86_64-w64-mingw32-gcc'
cpp = 'x86_64-w64-mingw32-g++'
ar = 'x86_64-w64-mingw32-ar'
strip = 'x86_64-w64-mingw32-strip'
windres = 'x86_64-w64-mingw32-windres'
[host_machine]
system = 'windows'
cpu_family = 'x86_64'
cpu = 'x86_64'
endian = 'little'
Building with Cross-Compilation
# Set up build with cross-compilation file
meson setup build --cross-file cross-arm64.ini
# Build (Meson automatically handles native vs. target builds)
meson compile -C build
# Install
meson install -C build
How Meson Handles Native Builds
When cross-compiling, Meson automatically:
Detects that a native qjsc is needed to compile JavaScript files
Builds a native version of qjsc and qjs for the host machine
Uses the native tools during the build process
Builds the target binaries for the cross-compilation target
From meson.build:342-401:
if meson.is_cross_build()
# Build native versions of qjsc and qjs
qjs_native_lib = static_library(
'qjs_native' ,
qjs_srcs,
qjs_libc_srcs,
native: true,
)
meson.override_find_program(
'qjsc' ,
executable(
'qjsc_native' ,
qjsc_srcs,
native: true,
),
)
endif
CMake Cross-Compilation
CMake uses toolchain files for cross-compilation:
ARM64 Linux
Windows (MinGW)
# arm64-linux-toolchain.cmake
set ( CMAKE_SYSTEM_NAME Linux)
set ( CMAKE_SYSTEM_PROCESSOR aarch64)
set (CMAKE_C_COMPILER aarch64-linux-gnu-gcc)
set (CMAKE_CXX_COMPILER aarch64-linux-gnu-g++)
set (CMAKE_FIND_ROOT_PATH /usr/aarch64-linux-gnu)
set (CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set (CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set (CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
# mingw-w64-toolchain.cmake
set ( CMAKE_SYSTEM_NAME Windows)
set ( CMAKE_SYSTEM_PROCESSOR x86_64)
set (CMAKE_C_COMPILER x86_64-w64-mingw32-gcc)
set (CMAKE_CXX_COMPILER x86_64-w64-mingw32-g++)
set (CMAKE_RC_COMPILER x86_64-w64-mingw32-windres)
set (CMAKE_FIND_ROOT_PATH /usr/x86_64-w64-mingw32)
set (CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set (CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set (CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
cmake -B build \
-DCMAKE_TOOLCHAIN_FILE=arm64-linux-toolchain.cmake \
-DCMAKE_BUILD_TYPE=Release
cmake --build build -j $( nproc )
When cross-compiling with CMake, you may need to build a native version of qjsc separately if you’re building from source (not using pre-generated files).
Android (NDK)
QuickJS-ng supports Android NDK 26.0.10792818 or later.
# Create cross-file for Android
cat > android-arm64.ini << EOF
[binaries]
c = ' $ANDROID_NDK /toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android33-clang'
cpp = ' $ANDROID_NDK /toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android33-clang++'
ar = ' $ANDROID_NDK /toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ar'
strip = ' $ANDROID_NDK /toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip'
[host_machine]
system = 'android'
cpu_family = 'aarch64'
cpu = 'armv8'
endian = 'little'
EOF
meson setup build --cross-file android-arm64.ini
meson compile -C build
cmake -B build \
-DCMAKE_TOOLCHAIN_FILE= $ANDROID_NDK /build/cmake/android.toolchain.cmake \
-DANDROID_ABI=arm64-v8a \
-DANDROID_PLATFORM=android-33
cmake --build build
iOS
iOS cross-compilation is typically done on macOS using Xcode toolchains.
# Using CMake with iOS toolchain
cmake -B build \
-DCMAKE_SYSTEM_NAME=iOS \
-DCMAKE_OSX_ARCHITECTURES=arm64 \
-DCMAKE_OSX_DEPLOYMENT_TARGET=11.0
cmake --build build
CLI tools (qjs, qjsc) are not installed for iOS, tvOS, or watchOS builds. Only the library is built.
WebAssembly (WASI)
WASI is the WebAssembly System Interface standard.
Using wasi-sdk
WASI Reactor
# Download and extract wasi-sdk
export WASI_SDK_PATH = / path / to / wasi-sdk
cmake -B build \
-DCMAKE_SYSTEM_NAME=WASI \
-DCMAKE_C_COMPILER= $WASI_SDK_PATH /bin/clang \
-DCMAKE_SYSROOT= $WASI_SDK_PATH /share/wasi-sysroot
cmake --build build
# Build WASI reactor (library without _start)
cmake -B build \
-DCMAKE_SYSTEM_NAME=WASI \
-DCMAKE_C_COMPILER= $WASI_SDK_PATH /bin/clang \
-DCMAKE_SYSROOT= $WASI_SDK_PATH /share/wasi-sysroot \
-DQJS_WASI_REACTOR=ON
cmake --build build
This creates qjs.wasm that exports library functions.
Emscripten
Emscripten compiles C/C++ to WebAssembly for web browsers.
# Using emcmake wrapper
emcmake cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build
# Output: qjs_wasm.js and qjs_wasm.wasm
The Emscripten build is configured with:
2MB stack size (default 16KB is too small)
ES6 module export
Export name: getQuickJs
Exported methods: ccall, cwrap
MinGW (Windows on Linux)
meson setup build --cross-file mingw-w64.ini
meson compile -C build
cmake -B build \
-DCMAKE_TOOLCHAIN_FILE=mingw-w64-toolchain.cmake \
-DBUILD_SHARED_LIBS=OFF \
-DQJS_BUILD_CLI_STATIC=ON
cmake --build build
MinGW builds automatically link statically with libgcc when QJS_BUILD_CLI_STATIC is enabled.
Common Issues and Solutions
Issue: qjsc Not Found During Cross-Compilation
Problem : Native qjsc is needed to compile JavaScript files during build.
Solution :
Meson handles this automatically. No action needed.
Build native qjsc first: # Build native qjsc
cmake -B build-native
cmake --build build-native --target qjsc
# Then cross-compile
export PATH = $PWD / build-native : $PATH
cmake -B build --toolchain-file=toolchain.cmake
cmake --build build
Issue: Thread Library Not Found
Problem : Cross-compilation toolchain doesn’t provide pthread library.
Solution : WASI builds automatically skip thread library:
if ( NOT CMAKE_SYSTEM_NAME STREQUAL "WASI" )
list (APPEND qjs_libs ${CMAKE_THREAD_LIBS_INIT} )
endif ()
Issue: Module Loading on Windows
Problem : Windows cannot resolve symbols at runtime like Unix systems.
Solution : QuickJS-ng automatically links modules to the main library on Windows:
if ( WIN32 )
# Explicitly link modules to qjs on Windows
target_link_libraries (qjs_module_lib INTERFACE qjs)
endif ()
Issue: Static vs. Shared Libraries
Problem : Binary modules with static qjs on Windows can cause runtime errors.
Solution : Use shared library builds on Windows when loading binary modules:
cmake -B build -DBUILD_SHARED_LIBS=ON
Testing Cross-Compiled Binaries
Using QEMU
Test ARM binaries on x86 using QEMU:
# Install QEMU user-mode emulation
sudo apt-get install qemu-user-static
# Run ARM64 binary
qemu-aarch64-static -L /usr/aarch64-linux-gnu ./build/qjs --version
# Run ARM32 binary
qemu-arm-static -L /usr/arm-linux-gnueabihf ./build/qjs --version
Using Wine
Test Windows binaries on Linux:
# Install Wine
sudo apt-get install wine64
# Run Windows binary
wine ./build/qjs.exe --version
Using wasmtime
Test WASI binaries:
# Install wasmtime
curl https://wasmtime.dev/install.sh -sSf | bash
# Run WASI binary
wasmtime build/qjs.wasm --version
Advanced Configuration
Custom Sysroot
Specify a custom sysroot for cross-compilation:
cmake -B build \
-DCMAKE_TOOLCHAIN_FILE=toolchain.cmake \
-DCMAKE_SYSROOT=/path/to/sysroot
Add to cross-file: [properties]
sys_root = '/path/to/sysroot'
Cross-Compiling with Dependencies
When using mimalloc or other dependencies:
# Ensure dependencies are built for target platform
meson setup build \
--cross-file cross-arm64.ini \
-Dcli_mimalloc=enabled \
--pkg-config-path=/path/to/arm64/pkgconfig
Multi-Architecture Builds
Build for multiple architectures:
for arch in arm64 armv7 x86_64 ; do
meson setup build- $arch --cross-file cross- $arch .ini
meson compile -C build- $arch
done
Best Practices
Use Meson for cross-compilation - Better automatic handling of native vs. target builds
Test early and often - Use emulators (QEMU, Wine) to test cross-compiled binaries
Static linking for distribution - Use static builds when distributing to avoid dependency issues
Verify toolchain - Ensure cross-compilation toolchain matches target platform exactly
Check platform features - Some platforms have limited features (no threads on WASI)
Use shared libs carefully - Static libraries are safer for cross-compilation
Ubuntu/Debian
# ARM64
sudo apt-get install gcc-aarch64-linux-gnu g++-aarch64-linux-gnu
# ARM32
sudo apt-get install gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf
# MinGW (Windows)
sudo apt-get install mingw-w64
# Android NDK
wget https://dl.google.com/android/repository/android-ndk-r26b-linux.zip
unzip android-ndk-r26b-linux.zip
macOS
# Install via Homebrew
brew install mingw-w64 # For Windows cross-compilation
# Android NDK
brew install --cask android-ndk