From da61693797420c5c16bcc754d758948025bd5be7 Mon Sep 17 00:00:00 2001 From: Solidor777 Date: Fri, 8 May 2026 02:39:23 -0700 Subject: [PATCH 1/2] joltc-sys: native Android cross-compile + host-vs-target /EHsc fix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two coupled bugs surfaced when cross-compiling joltc-sys to Android (aarch64-linux-android via NDK r26d clang from a Windows host): 1. `cfg!(windows)` is a HOST-OS check inside build.rs. When targeting Android from Windows, the conditional always fires and adds the MSVC-only `/EHsc` flag to the cmake config — NDK clang then receives `/EHsc` as a path argument and errors out ("error: no such file or directory: '/EHsc'"). Fix: read `CARGO_CFG_TARGET_OS` and only add `/EHsc` when the actual target is windows. 2. Building for Android requires three CMake-side hints that joltc-sys does not currently set: `CMAKE_TOOLCHAIN_FILE` (NDK's `android.toolchain.cmake`), `ANDROID_ABI` (string the toolchain reads — env var alone is ignored), and the generator (Ninja, since cmake-rs defaults to MSBuild on Windows hosts). Fix: when `target_os == "android"`, derive ANDROID_ABI from `CARGO_CFG_TARGET_ARCH` (aarch64 → arm64-v8a, arm → armeabi-v7a, etc.), look up the NDK at `ANDROID_NDK_HOME` / `ANDROID_NDK_ROOT` / `ANDROID_NDK`, and force Ninja. End-user contract after this change: set ANDROID_NDK_HOME, run `cargo ndk --target aarch64-linux-android check -p joltc-sys`. The standard cargo-ndk + nttld/setup-ndk@v1 flow Just Works. Validated locally on Windows-host → aarch64-linux-android with NDK r26d (full JoltPhysics + JoltC compiles in ~20s). 32-bit ARM (armv7-linux-androideabi) hits an unrelated upstream JoltC LAYOUT_COMPATIBLE static_assert (`JPC_*Settings` 16-byte alignment vs `JPH::*Settings` 8-byte alignment on 32-bit ARM) that this PR does not address. --- crates/joltc-sys/build.rs | 53 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/crates/joltc-sys/build.rs b/crates/joltc-sys/build.rs index dc88c67..beaf6dd 100644 --- a/crates/joltc-sys/build.rs +++ b/crates/joltc-sys/build.rs @@ -28,13 +28,64 @@ fn build_joltc() { // features just based on opt-level. config.profile("Release"); + // Read the actual cross-compile target rather than the build host so + // the rest of this fn can dispatch correctly. `cfg!(...)` would + // evaluate at build-script-compile time on the HOST. + let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap_or_default(); + let target_arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap_or_default(); + // Jolt fails to compile via the cmake crate without specifying exception // handling behavior under MSVC. I'm not sure that this is the correct // exception handling mode. - if cfg!(windows) { + // + // The original `cfg!(windows)` is a HOST-OS check (build.rs runs on + // the host, so it is always true on Windows). When cross-compiling + // to Android from a Windows host, NDK clang receives `/EHsc` as a + // path argument and errors. Switch to the target check so the flag + // only applies to actual Windows targets. + if target_os == "windows" { config.cxxflag("/EHsc"); } + // Native Android cross-compile setup. Without this, building + // joltc-sys for Android via the standard + // `cargo ndk --target check` flow fails because + // (a) cmake-rs defaults to MSBuild on Windows hosts which can't + // target Android; (b) the NDK's `android.toolchain.cmake` requires + // `ANDROID_ABI` to be set as a CMake variable (env var is + // ignored); (c) `ANDROID_NDK_HOME` is the canonical env name set + // by `nttld/setup-ndk` GitHub Action + cargo-ndk's local + // workflow. + // + // We translate cargo's target arch to NDK's ABI string and route + // CMake through the NDK toolchain file. `ANDROID_PLATFORM=android-21` + // is the same baseline most cargo-ndk users target. + if target_os == "android" { + let android_ndk_home = env::var("ANDROID_NDK_HOME") + .or_else(|_| env::var("ANDROID_NDK_ROOT")) + .or_else(|_| env::var("ANDROID_NDK")) + .expect( + "Android cross-compile requires ANDROID_NDK_HOME (or ANDROID_NDK_ROOT / \ + ANDROID_NDK) to point at the NDK install. Install via \ + `nttld/setup-ndk@v1` in CI or the Android SDK manager locally.", + ); + let toolchain_file = format!("{android_ndk_home}/build/cmake/android.toolchain.cmake"); + config.define("CMAKE_TOOLCHAIN_FILE", &toolchain_file); + let android_abi = match target_arch.as_str() { + "aarch64" => "arm64-v8a", + "arm" => "armeabi-v7a", + "x86" => "x86", + "x86_64" => "x86_64", + _ => panic!("unsupported Android target arch: {target_arch}"), + }; + config.define("ANDROID_ABI", android_abi); + config.define("ANDROID_PLATFORM", "android-21"); + // cmake-rs defaults to the host-OS generator (MSBuild on Win); + // the NDK toolchain only supports Ninja / Makefiles. Force + // Ninja explicitly. + config.generator("Ninja"); + } + // Having IPO/LTO turned on breaks lld on Windows. config.define("INTERPROCEDURAL_OPTIMIZATION", "OFF"); From 678a47c5437471151b7e04039a020a8b534d474f Mon Sep 17 00:00:00 2001 From: Solidor777 Date: Fri, 8 May 2026 14:19:46 -0700 Subject: [PATCH 2/2] joltc-sys: native iOS cross-compile MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds an iOS branch alongside the Android branch from #12. Covers both `aarch64-apple-ios` (device) and `aarch64-apple-ios-sim` (simulator on Apple Silicon hosts); `x86_64-apple-ios` is the legacy Intel-Mac simulator path. From a macOS host: ```sh cargo check --target aarch64-apple-ios -p joltc-sys cargo check --target aarch64-apple-ios-sim -p joltc-sys ``` Both succeed with no extra env-var setup. ## Two coupled fixes 1. **`build_joltc()` iOS branch.** Sets `CMAKE_SYSTEM_NAME=iOS`, `CMAKE_OSX_SYSROOT` (`iphoneos` for device vs `iphonesimulator` for simulator, distinguished via `CARGO_CFG_TARGET_ABI=sim`), `CMAKE_OSX_ARCHITECTURES` from `target_arch`, and `CMAKE_OSX_DEPLOYMENT_TARGET=12.0`. Forces Ninja so the build is generator-agnostic on macos CI runners. 2. **`generate_bindings()` clang-target translation.** bindgen passes rustc's TARGET to clang verbatim. rustc's iOS-simulator triple `aarch64-apple-ios-sim` is invalid clang syntax — clang errors with `version 'sim' in target triple is invalid`. Translate to clang's recognised form (`arm64-apple-ios-simulator` for sim, `arm64-apple-ios` for device) and supply the matching SDK sysroot via `xcrun --show-sdk-path` so iOS system headers (stdint.h, etc.) resolve. ## Validation - `cargo check --target aarch64-apple-ios -p joltc-sys` on macOS host (Xcode 16.4, iPhoneOS 18.5 SDK): succeeds. - `cargo check --target aarch64-apple-ios-sim -p joltc-sys`: succeeds. - Downstream Titan engine's `titan-jolt-3d` builds against both iOS targets in CI on `macos-latest`. - Desktop targets unaffected (iOS branch gated on `target_os == "ios"`). ## Stacking note This PR is stacked on #12 (`android-cross-compile-and-target-os-fix`) to share the host-vs-target awareness scaffolding. Either PR can merge first — the iOS branch is a strict additive layer. --- crates/joltc-sys/build.rs | 73 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/crates/joltc-sys/build.rs b/crates/joltc-sys/build.rs index beaf6dd..48c5ecb 100644 --- a/crates/joltc-sys/build.rs +++ b/crates/joltc-sys/build.rs @@ -86,6 +86,43 @@ fn build_joltc() { config.generator("Ninja"); } + // Native iOS cross-compile setup. cargo-side targets are + // `aarch64-apple-ios` (device) and `aarch64-apple-ios-sim` + // (simulator on Apple Silicon hosts); `x86_64-apple-ios` is the + // legacy Intel-Mac simulator path. CMake distinguishes device vs + // simulator via `CMAKE_OSX_SYSROOT` (`iphoneos` vs + // `iphonesimulator`); cargo-side the simulator is identified by + // `CARGO_CFG_TARGET_ABI=sim`. Without this branch, CMake builds + // against the macOS SDK and the resulting static libs fail at + // link time with `undefined symbol` for every iOS-specific + // runtime symbol cargo expects to find. + if target_os == "ios" { + config.define("CMAKE_SYSTEM_NAME", "iOS"); + let osx_arch = match target_arch.as_str() { + "aarch64" => "arm64", + "x86_64" => "x86_64", + _ => panic!("unsupported iOS target arch: {target_arch}"), + }; + config.define("CMAKE_OSX_ARCHITECTURES", osx_arch); + let target_abi = env::var("CARGO_CFG_TARGET_ABI").unwrap_or_default(); + let sysroot = if target_abi == "sim" || target_arch == "x86_64" { + "iphonesimulator" + } else { + "iphoneos" + }; + config.define("CMAKE_OSX_SYSROOT", sysroot); + // iOS 12 is the floor for arm64-only devices (post iPhone 5s) + // and aligns with Apple's current minimum-supported deployment + // target tier; consumers can override via cmake's standard + // env var if they need a different floor. + config.define("CMAKE_OSX_DEPLOYMENT_TARGET", "12.0"); + // Force Ninja so the build is generator-agnostic on macos + // CI runners (which have both Xcode and Ninja). Xcode generator + // works too but is slower and pulls in Xcode-specific scheme + // noise we don't need for a static lib. + config.generator("Ninja"); + } + // Having IPO/LTO turned on breaks lld on Windows. config.define("INTERPROCEDURAL_OPTIMIZATION", "OFF"); @@ -161,6 +198,10 @@ fn build_flags() -> Vec<(&'static str, &'static str)> { } fn generate_bindings(flags: &[(&'static str, &'static str)]) -> anyhow::Result<()> { + let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap_or_default(); + let target_arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap_or_default(); + let target_abi = env::var("CARGO_CFG_TARGET_ABI").unwrap_or_default(); + let mut builder = bindgen::Builder::default() .parse_callbacks(Box::new(bindgen::CargoCallbacks::new())) .header("JoltC/JoltC/JoltC.h") @@ -169,6 +210,38 @@ fn generate_bindings(flags: &[(&'static str, &'static str)]) -> anyhow::Result<( .default_enum_style(bindgen::EnumVariation::Consts) .prepend_enum_name(false); + // bindgen's default behaviour passes `TARGET` (rustc's triple) to + // clang. rustc's iOS-simulator triple `aarch64-apple-ios-sim` is + // invalid clang syntax — clang errors with `version 'sim' in + // target triple is invalid`. Translate to clang's recognised form + // (`arm64-apple-ios-simulator` for sim, `arm64-apple-ios` for + // device) and supply the matching SDK sysroot via `xcrun` so iOS + // system headers (stdint.h, etc.) resolve correctly. macOS host + // is the only supported iOS-host today (Apple SDKs are gated to + // that platform). + if target_os == "ios" { + let clang_target = match (target_arch.as_str(), target_abi.as_str()) { + ("aarch64", "sim") => "arm64-apple-ios-simulator", + ("aarch64", _) => "arm64-apple-ios", + ("x86_64", _) => "x86_64-apple-ios-simulator", + _ => panic!("unsupported iOS arch/abi: {target_arch}/{target_abi}"), + }; + builder = builder.clang_arg(format!("--target={clang_target}")); + + let sdk_name = if target_abi == "sim" || target_arch == "x86_64" { + "iphonesimulator" + } else { + "iphoneos" + }; + let sdk_output = std::process::Command::new("xcrun") + .args(["--sdk", sdk_name, "--show-sdk-path"]) + .output() + .with_context(|| format!("failed to invoke `xcrun --sdk {sdk_name}`"))?; + let sdk_path = String::from_utf8(sdk_output.stdout).context("xcrun output is not utf-8")?; + let sdk_path = sdk_path.trim(); + builder = builder.clang_arg(format!("-isysroot{sdk_path}")); + } + for (key, value) in flags { builder = builder.clang_arg(format!("-D{key}={value}")); }