diff --git a/flake.nix b/flake.nix index 6f3490691d..4706619461 100644 --- a/flake.nix +++ b/flake.nix @@ -429,9 +429,8 @@ }).simplex-chat.components.library { kind = "android"; # Link the threaded RTS only at final link, outside TH/iserv. - linkOptions = [ - "-optl-lffi" - ] ++ map (sym: "-optl-Wl,-u,${sym}") chatExports; + # GHC 9.14's RTS package declares its bundled libffi archive. + linkOptions = map (sym: "-optl-Wl,-u,${sym}") chatExports; postInstall = '' set -x ${pkgs.tree}/bin/tree $out @@ -503,8 +502,8 @@ }).simplex-chat.components.library { kind = "android"; # Link the threaded RTS only at final link, outside TH/iserv. + # GHC 9.14's RTS package declares its bundled libffi archive. linkOptions = [ - "-optl-lffi" "-optl-Wl,-z,max-page-size=16384" ] ++ map (sym: "-optl-Wl,-u,${sym}") chatExports; postInstall = '' diff --git a/scripts/nix/android-emulator-overlay.nix b/scripts/nix/android-emulator-overlay.nix index 4763bec50c..a53d0a8273 100644 --- a/scripts/nix/android-emulator-overlay.nix +++ b/scripts/nix/android-emulator-overlay.nix @@ -2,9 +2,12 @@ final: prev: let lib = prev.lib; - active = prev.stdenv.hostPlatform.isAndroid - && prev.stdenv.hostPlatform.isAarch32 - && prev.stdenv.buildPlatform.isLinux; + androidArm32 = prev.stdenv.hostPlatform.isAndroid + && prev.stdenv.hostPlatform.isAarch32; + supportedEmulator = androidArm32 + && prev.stdenv.buildPlatform.isLinux + && lib.versionAtLeast prev.pkgsBuildBuild.qemu.version "9.1" + && lib.versionOlder prev.pkgsBuildBuild.qemu.version "12"; # Keep this outside the native compiler package sets. Only Android ARM32 # Template Haskell and test wrappers need the guest personality fallback. emulator = import ./android-arm32-qemu.nix { @@ -13,7 +16,7 @@ let }; exes = prev.haskell-nix.iserv-proxy-exes.ghc914-sh; mkTemplateHaskell = interpreterExes: - { inherit (prev.haskell-nix) + { inherit (final.haskell-nix) emulatorSystemFeatures emulatorNativeBuilderPackages; } // import (haskellNix + "/overlays/linux-cross.nix") { inherit (prev.stdenv) hostPlatform buildPlatform; @@ -25,8 +28,18 @@ let inherit (interpreterExes) iserv-proxy iserv-proxy-interpreter iserv-proxy-interpreter-prof; }; -in lib.optionalAttrs active { +in lib.optionalAttrs androidArm32 { haskell-nix = prev.haskell-nix // { + # Static-library symbol corrections do not depend on the emulator version. + cabalProject' = projectModule: prev.haskell-nix.cabalProject' ( + [ ./android-iserv-project.nix ] + ++ (if builtins.isList projectModule then projectModule else [ projectModule ]) + ); + } // lib.optionalAttrs supportedEmulator { + # Use the patched ARM32 emulator on local Linux builders too. This is the + # supported haskell.nix opt-out from its native-builder scheduling marker; + # other target package sets keep their existing restrictions. + emulatorSystemFeatures = []; templateHaskell = prev.haskell-nix.templateHaskell // { ghc914-sh = mkTemplateHaskell exes // { evalWith = builtins.mapAttrs (_: mkTemplateHaskell) exes.evalWith; diff --git a/scripts/nix/android-iserv-gnu-iconv.patch b/scripts/nix/android-iserv-gnu-iconv.patch new file mode 100644 index 0000000000..077ab8fa73 --- /dev/null +++ b/scripts/nix/android-iserv-gnu-iconv.patch @@ -0,0 +1,30 @@ +--- a/cbits/symbols.armv7a-android.c ++++ b/cbits/symbols.armv7a-android.c +@@ -5,6 +5,14 @@ + #include + #include + #include ++ ++// Match GNU libiconv public prototypes without importing system headers ++// that conflict with the legacy untyped declarations below. iconv_t is void*. ++extern void *libiconv_open(const char *tocode, const char *fromcode); ++extern size_t libiconv(void *cd, char **inbuf, size_t *inbytesleft, ++ char **outbuf, size_t *outbytesleft); ++extern int libiconv_close(void *cd); ++extern const char *locale_charset(void); + + extern void __aeabi_idiv(void); + extern void __aeabi_idivmod(void); +@@ -394,6 +402,12 @@ + + RtsSymbolVal my_iserv_syms[] = { + // { "__sync_val_compare_and_swap_1", (void*)(&___sync_val_compare_and_swap_1), STRENGTH_NORMAL, 1 }, ++ // API 26 uses GNU libiconv. Static Bionic dlsym cannot expose these ++ // definitions to loaded Haskell objects; register their real addresses. ++ SYM(libiconv_open), ++ SYM(libiconv), ++ SYM(libiconv_close), ++ SYM(locale_charset), + SYM(strlen), + SYM(__aeabi_idiv), + SYM(__aeabi_idivmod), diff --git a/scripts/nix/android-iserv-project.nix b/scripts/nix/android-iserv-project.nix new file mode 100644 index 0000000000..8220d8437c --- /dev/null +++ b/scripts/nix/android-iserv-project.nix @@ -0,0 +1,20 @@ +{ config, lib, pkgs, ... }: +lib.mkIf ( + pkgs.stdenv.hostPlatform.isAndroid + && pkgs.stdenv.hostPlatform.isAarch32 + && config.compiler-nix-name == "ghc914-sh" + && config.name == "iserv-proxy" +) { + # Patch before Cabal planning: v2 hashes must describe the same source it + # later packages. Bionic's real static libc routines must win at startup. + src = lib.mkForce (pkgs.pkgsBuildBuild.applyPatches { + name = "android-arm32-iserv-proxy-source"; + src = pkgs.haskell-nix.sources.iserv-proxy; + patches = [ + ./android-iserv-static-libc.patch + # The API26 project declares GNU libiconv for ghc-internal. Its real + # definitions need explicit registration in the static interpreter. + ./android-iserv-gnu-iconv.patch + ]; + }); +} diff --git a/scripts/nix/android-iserv-static-libc.patch b/scripts/nix/android-iserv-static-libc.patch new file mode 100644 index 0000000000..31c1467e0f --- /dev/null +++ b/scripts/nix/android-iserv-static-libc.patch @@ -0,0 +1,41 @@ +--- a/cbits/symbols.armv7a-android.c ++++ b/cbits/symbols.armv7a-android.c +@@ -353,34 +353,10 @@ + MISSING_FUN(__loader_add_thread_local_dtor) + MISSING_FUN(__loader_remove_thread_local_dtor) + MISSING_FUN(__clang_call_terminate) +-// Symbols absent from the armv7a Android NDK's bionic / libc++ as +-// shipped with our current toolchain. The dynamic linker would +-// otherwise fail to load this library when it sees the unresolved +-// references via `SYM(...)` below. These stubs are never expected +-// to be invoked at runtime; if a real call ever lands here we +-// `exit(1)` with a diagnostic so the caller is visible instead of +-// silently corrupting state. +-MISSING_FUN(__arm_fadvise64_64) +-MISSING_FUN(__cxa_guard_abort) +-MISSING_FUN(__cxa_guard_acquire) +-MISSING_FUN(__cxa_guard_release) +-MISSING_FUN(__cxa_thread_finalize) +-MISSING_FUN(__fsetxattr) +-MISSING_FUN(__libc_stdio_cleanup) +-MISSING_FUN(_Z21__libc_shared_globalsv) +-MISSING_FUN(getentropy) +-MISSING_FUN(posix_spawn_file_actions_addchdir_np) +-MISSING_FUN(posix_spawn_file_actions_addclose) +-MISSING_FUN(posix_spawn_file_actions_adddup2) +-MISSING_FUN(posix_spawn_file_actions_addopen) +-MISSING_FUN(posix_spawn_file_actions_destroy) +-MISSING_FUN(posix_spawn_file_actions_init) +-MISSING_FUN(posix_spawnattr_destroy) +-MISSING_FUN(posix_spawnattr_init) +-MISSING_FUN(posix_spawnattr_setflags) +-MISSING_FUN(posix_spawnattr_setsigdefault) +-MISSING_FUN(posix_spawnp) +-MISSING_FUN(statx) ++// Bionic defines these functions in its static libc.a, including private ++// startup routines absent from the API-specific shared libc.so stubs. Keep ++// the extern/SYM references below so GHCi uses the same real libc state; ++// defining MISSING_FUN replacements here would shadow those implementations. + + typedef void SymbolAddr; + typedef char SymbolName;