|
| 1 | +# Android shared library build pipeline. |
| 2 | +# Builds Haskell to a shared .so for aarch64-android using nixpkgs |
| 3 | +# cross-compilation (pkgsCross.aarch64-android-prebuilt) instead of |
| 4 | +# haskell.nix, which builds GHC from source without cache hits. |
| 5 | +# |
| 6 | +# Uses Google's prebuilt NDK toolchain via nixpkgs, avoiding the |
| 7 | +# LLVM 19 compiler-rt issue (NixOS/nixpkgs#380604). |
1 | 8 | { sources ? import ../npins }: |
2 | 9 | let |
3 | | - # Import haskell.nix from the armv7a branch (provides Android cross-compiler) |
4 | | - haskellNix = import sources."haskell.nix" {}; |
5 | | - |
6 | | - # Use nixpkgs-2305 which has LLVM 14. nixpkgs-unstable has LLVM 19 whose |
7 | | - # compiler-rt is broken for Android (os_version_check.c needs pthread.h |
8 | | - # which is unavailable in the builtins-only sysroot). |
9 | | - # See: https://github.com/NixOS/nixpkgs/issues/380604 |
10 | | - |
11 | | - # Android API 26 (Android 8.0 Oreo) overlay — re-imports nixpkgs with |
12 | | - # modified crossSystem, same approach as simplex-chat. |
13 | | - android26 = final: prev: { |
14 | | - pkgsCross = prev.pkgsCross // { |
15 | | - aarch64-android = import prev.path { |
16 | | - crossSystem = prev.lib.systems.examples.aarch64-android // { |
17 | | - sdkVer = "26"; |
18 | | - }; |
19 | | - localSystem = prev.buildPlatform; |
20 | | - inherit (prev) config overlays; |
21 | | - }; |
22 | | - }; |
| 10 | + pkgs = import sources.nixpkgs { |
| 11 | + config.allowUnfree = true; |
| 12 | + config.android_sdk.accept_license = true; |
23 | 13 | }; |
24 | 14 |
|
25 | | - pkgs = import haskellNix.sources.nixpkgs-2305 (haskellNix.nixpkgsArgs // { |
26 | | - overlays = haskellNix.nixpkgsArgs.overlays ++ [ android26 ]; |
27 | | - }); |
28 | | - |
29 | | - androidPkgs = pkgs.pkgsCross.aarch64-android; |
30 | | - |
31 | | - # Android doesn't have LANGINFO_CODESET, but nixpkgs autoconf detects it. |
32 | | - # Patch libiconv to undefine it and enable static linking. |
33 | | - androidIconv = (androidPkgs.libiconv.override { |
34 | | - enableStatic = true; |
35 | | - }).overrideAttrs (old: { |
36 | | - postConfigure = '' |
37 | | - echo "#undef HAVE_LANGINFO_CODESET" >> libcharset/config.h |
38 | | - echo "#undef HAVE_LANGINFO_CODESET" >> lib/config.h |
39 | | - ''; |
40 | | - }); |
41 | | - |
42 | | - # Disable fortify hardening (incompatible with Android NDK) and enable static. |
43 | | - androidFFI = androidPkgs.libffi.overrideAttrs (old: { |
44 | | - dontDisableStatic = true; |
45 | | - hardeningDisable = [ "fortify" ]; |
46 | | - }); |
47 | | - |
48 | | - project = import ./project.nix { inherit pkgs; }; |
49 | | - |
50 | | -in { |
51 | | - inherit pkgs androidPkgs androidIconv androidFFI; |
52 | | - lib = project.projectCross.aarch64-android.hsPkgs.haskell-mobile.components.library; |
53 | | - crossLib = project.projectCross.aarch64-android.hsPkgs.haskell-mobile.components.library; |
54 | | - # Expose all cross-compiled haskell packages for linking |
55 | | - hsPkgs = project.projectCross.aarch64-android.hsPkgs; |
| 15 | + # Cross-compilation packages for Android using prebuilt NDK toolchain |
| 16 | + androidPkgs = pkgs.pkgsCross.aarch64-android-prebuilt; |
| 17 | + |
| 18 | + # Cross-GHC: runs on x86_64-linux, produces aarch64-android code |
| 19 | + ghc = androidPkgs.haskellPackages.ghc; |
| 20 | + ghcCmd = "${ghc}/bin/${ghc.targetPrefix}ghc"; |
| 21 | + |
| 22 | + # NDK toolchain for compiling the JNI bridge C code |
| 23 | + androidComposition = pkgs.androidenv.composeAndroidPackages { |
| 24 | + includeNDK = true; |
| 25 | + }; |
| 26 | + ndk = "${androidComposition.ndk-bundle}/libexec/android-sdk/ndk/${androidComposition.ndk-bundle.version}"; |
| 27 | + ndkCc = "${ndk}/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android26-clang"; |
| 28 | + sysroot = "${ndk}/toolchains/llvm/prebuilt/linux-x86_64/sysroot"; |
| 29 | + |
| 30 | +in pkgs.stdenv.mkDerivation { |
| 31 | + pname = "haskell-mobile-android"; |
| 32 | + version = "0.1.0.0"; |
| 33 | + |
| 34 | + src = ../src; |
| 35 | + |
| 36 | + nativeBuildInputs = [ ghc ]; |
| 37 | + buildInputs = [ androidPkgs.libffi androidPkgs.gmp ]; |
| 38 | + |
| 39 | + buildPhase = '' |
| 40 | + # Discover RTS include path for HsFFI.h |
| 41 | + GHC_LIBDIR=$(${ghcCmd} --print-libdir) |
| 42 | + RTS_INCLUDE=$(dirname $(find $GHC_LIBDIR -name "HsFFI.h" | head -1)) |
| 43 | +
|
| 44 | + echo "GHC: ${ghcCmd}" |
| 45 | + echo "GHC libdir: $GHC_LIBDIR" |
| 46 | + echo "RTS include: $RTS_INCLUDE" |
| 47 | +
|
| 48 | + # Step 1: Compile JNI bridge with NDK clang |
| 49 | + ${ndkCc} -c -fPIC \ |
| 50 | + -I${sysroot}/usr/include \ |
| 51 | + -I$RTS_INCLUDE \ |
| 52 | + -o jni_bridge.o \ |
| 53 | + ${../cbits/jni_bridge.c} |
| 54 | +
|
| 55 | + # Step 2: Compile Haskell to shared library with cross-GHC |
| 56 | + ${ghcCmd} -shared -O2 \ |
| 57 | + -o libhaskellmobile.so \ |
| 58 | + HaskellMobile.hs \ |
| 59 | + ${../cbits/android_stubs.c} \ |
| 60 | + -optl-L${androidPkgs.gmp}/lib \ |
| 61 | + -optl-L${androidPkgs.libffi}/lib \ |
| 62 | + -optl-lffi \ |
| 63 | + -optl-Wl,-z,max-page-size=16384 \ |
| 64 | + -optl$(pwd)/jni_bridge.o \ |
| 65 | + -optl-Wl,-u,haskellInit \ |
| 66 | + -optl-Wl,-u,haskellGreet |
| 67 | + ''; |
| 68 | + |
| 69 | + installPhase = '' |
| 70 | + mkdir -p $out/lib/arm64-v8a |
| 71 | + cp libhaskellmobile.so $out/lib/arm64-v8a/ |
| 72 | + ''; |
56 | 73 | } |
0 commit comments