Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
reflex-frp
GitHub Repository: reflex-frp/reflex-platform
Path: blob/develop/android/impl.nix
1 views
env: with env;
let overrideAndroidCabal = package: overrideCabal package (drv: {
  # -Wl,--unresolved-symbols=ignore-in-object-files
  preConfigure = (drv.preConfigure or "") + ''
        export NIX_CFLAGS_LINK="-no-pie -v"
        sed -i 's%^executable *\(.*\)$%executable lib\1.so\n    cc-options: -no-pie -shared -fPIC\n    ld-options: -no-pie -shared -Wl,--gc-sections,--version-script=${./haskellActivity.version},-u,Java_systems_obsidian_HaskellActivity_haskellStartMain,-u,hs_main\n    ghc-options: -fPIC -shared -no-pie -threaded -no-hs-main -lHSrts_thr -lffi -lm -llog%i' *.cabal
      '';
      # -no-hs-main
      configureFlags = (drv.configureFlags or []) ++ [
        "--enable-shared"
      ];
      });
    /*  
    overrideAndroidCabal = package: overrideCabal package (drv: {
      preConfigure = ''
        export NIX_CFLAGS_COMPILE=""
        export NIX_CFLAGS_LINK="-v -no-pie"
      '';    
      });
    */
    androidenv = nixpkgs.androidenv;
    #TODO: Keep the signing key for dev mode more consistent, e.g. in ~/.config/reflex-platform, so that the app can be reinstalled in-place
    addDeployScript = src: nixpkgs.runCommand "android-app" {
      inherit src;
      buildCommand = ''
        mkdir -p "$out/bin"
        cp -r "$src"/* "$out"
        substitute ${./deploy.sh} $out/bin/deploy \
          --subst-var-by coreutils ${nixpkgs.coreutils} \
          --subst-var-by adb ${androidenv.androidPkgs_9_0.platform-tools} \
          --subst-var-by java ${nixpkgs.openjdk17_headless} \
          --subst-var-by out $out
        chmod +x "$out/bin/deploy"
      '';
      buildInputs = [ androidenv.androidPkgs_9_0.androidsdk ];
    } "";
    buildGradleApp = import ./build-gradle-app.nix {
      inherit (nixpkgs) stdenv lib gnumake gawk file runCommand
      which fetchurl buildEnv;
      inherit androidenv;
      gradle = nixpkgs.gradle.override {
        java = nixpkgs.buildPackages.openjdk11_headless;
      };
      jdk = nixpkgs.buildPackages.openjdk17_headless;
    };
    inherit (nixpkgs.lib) splitString escapeShellArg mapAttrs attrNames concatStrings optionalString;
in {
  buildApp = args: with args; addDeployScript (buildGradleApp {
    inherit acceptAndroidSdkLicenses mavenDeps;
    buildDirectory = "./.";
    inherit gradleTask;
    keyAlias = releaseKey.keyAlias or null;
    keyAliasPassword = releaseKey.keyPassword or null;
    keyStore = releaseKey.storeFile or null;
    keyStorePassword = releaseKey.storePassword or null;
    name = applicationId;
    platformVersions = [ "30" ];
    release = false;
    src =
      let splitApplicationId = splitString "." applicationId;
          appSOs = mapAttrs (abiVersion: { myNixpkgs, myHaskellPackages }: {
            hsApp = overrideAndroidCabal (package myHaskellPackages);
            sharedLibs = runtimeSharedLibs myNixpkgs ++ [ "${myNixpkgs.libffi}/lib/libffi.so" ];
          }) ({
            "arm64-v8a" = {
              myNixpkgs = nixpkgsCross.android.aarch64;
              myHaskellPackages = ghcAndroidAarch64;
            };
          } // (if ghcAndroidAarch32.ghc.version != "8.6.5" then { } else {
            "armeabi-v7a" = {
              myNixpkgs = nixpkgsCross.android.aarch32;
              myHaskellPackages = ghcAndroidAarch32;
            };
          }));
          abiVersions = attrNames appSOs;
      in nixpkgs.runCommand "android-app" {
        buildGradle = builtins.toFile "build.gradle" (import ./build.gradle.nix {
          inherit applicationId version additionalDependencies releaseKey universalApk;
          googleServicesClasspath = optionalString (googleServicesJson != null)
            "classpath 'com.google.gms:google-services:4.3.3'";
          googleServicesPlugin = optionalString (googleServicesJson != null)
            "apply plugin: 'com.google.gms.google-services'";
        });
        androidManifestXml = builtins.toFile "AndroidManifest.xml" (import ./AndroidManifest.xml.nix {
          inherit applicationId version iconPath intentFilters services permissions activityAttributes usesCleartextTraffic;
        });
        stringsXml = builtins.toFile "strings.xml" (import ./strings.xml.nix {
          inherit displayName;
        });
        applicationMk = builtins.toFile "Application.mk" (import ./Application.mk.nix {
          inherit nixpkgs abiVersions;
        });
        javaSrc = nixpkgs.buildEnv {
          name = applicationId + "-java";
          paths = javaSources
            # Sets up the main Activity using [android-activity](https://hackage.haskell.org/package/android-activity)
            (ghcAndroidAarch64.android-activity.src + "/java") #TODO: Use output, not src
            # Sets up the main webview using [reflex-dom](https://github.com/reflex-frp/reflex-dom/blob/develop/reflex-dom/java/org/reflexfrp/reflexdom/MainWidget.java)
            (ghcAndroidAarch64.reflex-dom.src + "/java");
        };
        src = ./src;
        nativeBuildInputs = [ nixpkgs.rsync ];
        unpackPhase = "";
      } (''
          cp -r --no-preserve=mode "$src" "$out"
          mkdir -p "$out/src/main"
          cp -r --no-preserve=mode "$javaSrc" "$out/src/main/java"
          ln -s "$buildGradle" "$out/build.gradle"
          ln -s "$androidManifestXml" "$out/AndroidManifest.xml"
          mkdir -p "$out/res/values"
          ln -s "$stringsXml" "$out/res/values/strings.xml"
          mkdir -p "$out/jni"
          ln -s "$applicationMk" "$out/jni/Application.mk"
          ${optionalString (googleServicesJson != null) ''cp '${googleServicesJson}' "$out/google-services.json"''}
        '' + concatStrings (builtins.map (arch:
          let
            inherit (appSOs.${arch}) hsApp sharedLibs;
            sharedLibsCmd = concatStrings (map (libPath: ''
              local lib="${libPath}"
              if [ ! -f "$lib" ] ; then
                >&2 echo 'Error: library $lib not found'
                exit 1
              fi
              cp --no-preserve=mode "$lib" "$ARCH_LIB"
              '') sharedLibs);
          in ''
            {
              ARCH_LIB=$out/lib/${arch}
              mkdir -p $ARCH_LIB

              local exe="${hsApp}/bin/lib${executableName}.so"
              if [ ! -f "$exe" ] ; then
                >&2 echo 'Error: executable "${executableName}" not found'
                exit 1
              fi
              cp --no-preserve=mode "$exe" "$ARCH_LIB/libHaskellActivity.so"

              '' + sharedLibsCmd + ''
            }
        '') abiVersions) + ''
          rsync -r --chmod=+w "${assets}"/ "$out/assets/"
          rsync -r --chmod=+w "${resources}"/ "$out/res/"
          [ -d "$out/assets" ]
          [ -d "$out/res" ]
        '');
    useGooglePlayServices = true; # TODO: Should this be enabled by default?
    useGoogleAPIs = true; #TODO: Should this be enabled by default?

    # We use the NDK build process
    useNDK = true;
  });

  intentFilterXml = args: with args; ''
    <intent-filter android:autoVerify="true">
      <action android:name="android.intent.action.VIEW" />
      <category android:name="android.intent.category.DEFAULT" />
      <category android:name="android.intent.category.BROWSABLE" />
      <data android:scheme="${scheme}"
            android:host="${host}"
            ${ optionalString (port != null) ''android:port="${toString port}"'' }
            android:pathPrefix="${pathPrefix}" />
    </intent-filter>
  '';
}