Skip to content
Snippets Groups Projects
flake.nix 16.4 KiB
Newer Older
{
  description = "The PrivateStorage Mobile application";
  inputs = {
    nixpkgs = {
      url = "github:NixOS/nixpkgs?ref=nixos-22.05";
    };
    flake-utils.url = "github:numtide/flake-utils";
    pypi-deps-db = {
      flake = false;
      url = "github:DavHau/pypi-deps-db";
    };
    mach-nix-flake = {
      flake = true;
      url = "github:DavHau/mach-nix";
      inputs = {
        nixpkgs.follows = "nixpkgs";
        flake-utils.follows = "flake-utils";
        pypi-deps-db.follows = "pypi-deps-db";
    buildozerSrc = {
      flake = false;
      url = "github:PrivateStorageio/buildozer?ref=build-without-installing";
    };
    pythonForAndroidSrc = {
      flake = false;
      url = "github:PrivateStorageio/python-for-android?ref=work-on-nixos";
    };
  outputs = { self, nixpkgs, flake-utils, mach-nix-flake, buildozerSrc, pythonForAndroidSrc, ... }:
    flake-utils.lib.eachSystem [
      "x86_64-linux"
    ] (system: let

      mach-nix = mach-nix-flake.lib.${system};
Jean-Paul Calderone's avatar
Jean-Paul Calderone committed
      pkgs = import nixpkgs {
        inherit system;
        config = {
          allowUnfree = true;
          android_sdk.accept_license = true;
        };
Jean-Paul Calderone's avatar
Jean-Paul Calderone committed
      };
      python-for-android = mach-nix.buildPythonPackage {
        src = pythonForAndroidSrc;
        requirementsExtra = ''
        sh>=1.10
        '';
      };
      buildozer = mach-nix.buildPythonPackage {
        src = buildozerSrc;
        packagesExtra = [ ];
      };

      kivy-env = mach-nix.mkPython {
        python = "python39";
        requirements = ''
          kivy
          Pillow
          cython
        '';
        packagesExtra = [ python-for-android buildozer ];

      # THESE MUST AGREE WITH app/app/build.gradle
      #
      # If they do not you will get failures that look something like:

      # FAILURE: Build failed with an exception.
      #
      # * What went wrong:
      # Could not determine the dependencies of task ':app:compileReleaseJavaWithJavac'.
      # > Failed to install the following SDK components:
      #       platforms;android-32 Android SDK Platform 32
      #   The SDK directory is not writable (/nix/store/46214a16f22rd7q8vkrhsa907ra0405l-androidsdk/libexec/android-sdk)

      platformVersion = "31";
      buildToolsVersion = "31.0.0";
      # Check out pkgs/development/mobile/androidenv/repo.json for
      # nixpkgs-supported versions.
      ndkVersion = "23.0.7123448-rc1";
      androidComposition = pkgs.callPackage ./android.nix {
        inherit cmakeVersion buildToolsVersion platformVersion ndkVersion;
      getBuildInputs = getInputs: xs: builtins.foldl' (accum: drv: (getInputs drv) ++ accum) [] xs;
      # Make sure gradle uses the aapt2 from our Android SDK.  Otherwise it
      # downloads one from the internet that doesn't work because it
      # specifies an interpreter (ld.so) that nothing has ensured will
      # exist.
      #
      # Without this you get a stack of build errors that look a bit like:
      #
      # Execution failed for task ':app:processDebugResources'.
      # > Could not resolve all files for configuration ':app:debugRuntimeClasspath'.
      #    > Failed to transform appcompat-1.3.0.aar (androidx.appcompat:appcompat:1.3.0) to match attributes {artifactType=android-compiled-dependencies-resources, org.gradle.category=library, org.gradle.dependency.bundling=external, org.gradle.libraryelements=aar, org.gradle.status=release, org.gradle.usage=java-runtime}.
      #       > Execution failed for AarResourcesCompilerTransform: /home/exarkun/.gradle/caches/transforms-3/f5908003b5fc10a9cf0fd543db10ae32/transformed/appcompat-1.3.0.
      #          > AAPT2 aapt2-7.2.1-7984345-linux Daemon #1: Daemon startup failed
      #            This should not happen under normal circumstances, please file an issue if it does.
      ANDROIDSDK = "${androidComposition.androidsdk}/libexec/android-sdk";
      gradle-wrapper = pkgs.writeShellApplication {
        name = "gradle";
        text = ''
        ${pkgs.gradle}/bin/gradle -Pandroid.aapt2FromMavenOverride=${ANDROIDSDK}/build-tools/${buildToolsVersion}/aapt2 "$@"
      # packages.host-python = (import nixpkgs { system = "aarch64-linux-android"; }).python3Minimal.override { self = self.packages.host-python; };

      packages.apk = pkgs.androidenv.buildApp {
        name = "PrivateStorage Mobile";
        src = ./app;
        release = false;

        platformVersions = [ platformVersion ];
      apps.emulate = {
        type = "app";
        program = let
          emulator = pkgs.androidenv.emulateApp {
            name = "PrivateStorage Mobile";
            inherit platformVersion;
            abiVersion = "x86_64";
            systemImageType = "default";
            package = "MyApp";
            activity = "MainActivity";
          };
        in
          "${emulator}/bin/run-test-emulator";
      };
      devShells.gradle = pkgs.mkShell rec {
        name = "nix-native";
        version = "2022.11.09";

        inherit ANDROIDSDK;
        ANDROIDNDK = "${ANDROIDSDK}/ndk-bundle";

        ANDROID_SDK_ROOT = ANDROIDSDK;
        ANDROID_NDK_ROOT = ANDROIDNDK;

        # Deprecated but ... :shrug:
        ANDROID_HOME = ANDROIDSDK;

        ANDROID_PLATFORM_VERSION = "${platformVersion}";

        # https://nix.dev/tutorials/cross-compilation

          (python310.withPackages (ps: with ps; [ build ]))

        shellHook = ''
        # Tell the tools a third time where the SDK and NDK are to be found.
        cat >app/local.properties <<EOF
        # THIS FILE IS AUTOMATICALLY GENERATED
        # DO NOT EDIT
        # YOUR CHANGES WILL BE DESTROYED

        sdk.dir=${ANDROIDSDK}
      };

      devShells.default = self.devShells.${system}.gradle;

      # https://github.com/NixOS/nixpkgs/blob/master/doc/languages-frameworks/android.section.md
      # is a good reference for this stuff.
      devShells.kivy = pkgs.mkShell rec {
        # The docs talk about ANDROID_SDK_ROOT but apparently it has been
        # renamed yet again.
        ANDROIDSDK = "${androidComposition.androidsdk}/libexec/android-sdk";
        ANDROIDNDK = "${ANDROIDSDK}/ndk-bundle";

        shellHook = ''
        export PATH="$(echo "$ANDROIDSDK/cmake/${cmakeVersion}".*/bin):$PATH"
        '';

        buildInputs = with pkgs; [
          android-studio

          # Kivy and python-for-android depend on:
Jean-Paul Calderone's avatar
Jean-Paul Calderone committed
          # https://github.com/kivy/python-for-android/blob/v2020.04.29/doc/source/troubleshooting.rst#errors-related-to-java-version

          # python-for-android recipes all want to download and unpack their
          # own sources!  aaaaaa
          unzip
        ] ++

        # Also we need build dependencies for all of the things it is going to
        # build!
        (getBuildInputs (drv: drv.buildInputs) [ openssl libffi python39 ]);

        nativeBuildInputs =
          with pkgs; getBuildInputs (drv: drv.nativeBuildInputs) [ openssl libffi python39 ];
    }) // {

      # Python cross-compilation notes.
      #
      # You can get a lot of cross-compiled stuff out of nixpkgs beneath
      # `pkgsCross`.  For example,
      #
      #    pkgsCross.aarch64-android.python3Minimal
      #
      # is just what the name says.  This builds fine with only one small
      # change against current nixpkgs.  However, its extension modules have a
      # harder time.  For example,
      #
      #    pkgsCross.aarch64-android.python3Minimal.pkgs.zfec
      #
      # fails to build because:
      #
      #     running bdist_wheel
      #     Traceback (most recent call last):
      #       File "/build/wheel-0.37.1-source/nix_run_setup", line 8, in <module>
      #         exec(compile(getattr(tokenize, 'open', open)(__file__).read().replace('\\r\\n', '\\n'), __file__, 'exec'))
      #       File "setup.py", line 4, in <module>
      #         setup(maintainer=u'Alex Grönholm')
      #       File "/nix/store/azdg3l16q2d03g8drgr94bg1069mfzpq-python3.10-bootstrapped-pip-22.2.2/lib/python3.10/site-packages/setuptools/__init__.py", line 87, in setup
      #         return distutils.core.setup(**attrs)
      #       File "/nix/store/azdg3l16q2d03g8drgr94bg1069mfzpq-python3.10-bootstrapped-pip-22.2.2/lib/python3.10/site-packages/setuptools/_distutils/core.py", line 185, in setup
      #         return run_commands(dist)
      #       File "/nix/store/azdg3l16q2d03g8drgr94bg1069mfzpq-python3.10-bootstrapped-pip-22.2.2/lib/python3.10/site-packages/setuptools/_distutils/core.py", line 201, in run_commands
      #         dist.run_commands()
      #       File "/nix/store/azdg3l16q2d03g8drgr94bg1069mfzpq-python3.10-bootstrapped-pip-22.2.2/lib/python3.10/site-packages/setuptools/_distutils/dist.py", line 973, in run_commands
      #         self.run_command(cmd)
      #       File "/nix/store/azdg3l16q2d03g8drgr94bg1069mfzpq-python3.10-bootstrapped-pip-22.2.2/lib/python3.10/site-packages/setuptools/dist.py", line 1217, in run_command
      #         super().run_command(command)
      #       File "/nix/store/azdg3l16q2d03g8drgr94bg1069mfzpq-python3.10-bootstrapped-pip-22.2.2/lib/python3.10/site-packages/setuptools/_distutils/dist.py", line 992, in run_command
      #         cmd_obj.run()
      #       File "/nix/store/azdg3l16q2d03g8drgr94bg1069mfzpq-python3.10-bootstrapped-pip-22.2.2/lib/python3.10/site-packages/wheel/bdist_wheel.py", line 295, in run
      #         build_ext = self.reinitialize_command('build_ext')
      #       File "/nix/store/azdg3l16q2d03g8drgr94bg1069mfzpq-python3.10-bootstrapped-pip-22.2.2/lib/python3.10/site-packages/setuptools/__init__.py", line 200, in reinitialize_command
      #         cmd = _Command.reinitialize_command(self, command, reinit_subcommands)
      #       File "/nix/store/azdg3l16q2d03g8drgr94bg1069mfzpq-python3.10-bootstrapped-pip-22.2.2/lib/python3.10/site-packages/setuptools/_distutils/cmd.py", line 312, in reinitialize_command
      #         return self.distribution.reinitialize_command(command, reinit_subcommands)
      #       File "/nix/store/azdg3l16q2d03g8drgr94bg1069mfzpq-python3.10-bootstrapped-pip-22.2.2/lib/python3.10/site-packages/setuptools/_distutils/dist.py", line 945, in reinitialize_command
      #         command = self.get_command_obj(command_name)
      #       File "/nix/store/azdg3l16q2d03g8drgr94bg1069mfzpq-python3.10-bootstrapped-pip-22.2.2/lib/python3.10/site-packages/setuptools/_distutils/dist.py", line 864, in get_command_obj
      #         klass = self.get_command_class(command)
      #       File "/nix/store/azdg3l16q2d03g8drgr94bg1069mfzpq-python3.10-bootstrapped-pip-22.2.2/lib/python3.10/site-packages/setuptools/dist.py", line 954, in get_command_class
      #         self.cmdclass[command] = cmdclass = ep.load()
      #       File "/nix/store/537cvh8mqdaglc0an8ndmzszllh1lwac-python3-minimal-3.10.8/lib/python3.10/importlib/metadata/__init__.py", line 171, in load
      #         module = import_module(match.group('module'))
      #       File "/nix/store/537cvh8mqdaglc0an8ndmzszllh1lwac-python3-minimal-3.10.8/lib/python3.10/importlib/__init__.py", line 126, in import_module
      #         return _bootstrap._gcd_import(name[level:], package, level)
      #       File "<frozen importlib._bootstrap>", line 1050, in _gcd_import
      #       File "<frozen importlib._bootstrap>", line 1027, in _find_and_load
      #       File "<frozen importlib._bootstrap>", line 1006, in _find_and_load_unlocked
      #       File "<frozen importlib._bootstrap>", line 688, in _load_unlocked
      #       File "<frozen importlib._bootstrap_external>", line 883, in exec_module
      #       File "<frozen importlib._bootstrap>", line 241, in _call_with_frames_removed
      #       File "/nix/store/azdg3l16q2d03g8drgr94bg1069mfzpq-python3.10-bootstrapped-pip-22.2.2/lib/python3.10/site-packages/setuptools/command/build_ext.py", line 26, in <module>
      #         get_config_var("LDSHARED")
      #       File "/nix/store/azdg3l16q2d03g8drgr94bg1069mfzpq-python3.10-bootstrapped-pip-22.2.2/lib/python3.10/site-packages/setuptools/_distutils/sysconfig.py", line 574, in get_config_var
      #         return get_config_vars().get(name)
      #       File "/nix/store/azdg3l16q2d03g8drgr94bg1069mfzpq-python3.10-bootstrapped-pip-22.2.2/lib/python3.10/site-packages/setuptools/_distutils/sysconfig.py", line 553, in get_config_vars
      #         _config_vars = sysconfig.get_config_vars().copy()
      #       File "/nix/store/537cvh8mqdaglc0an8ndmzszllh1lwac-python3-minimal-3.10.8/lib/python3.10/sysconfig.py", line 615, in get_config_vars
      #         _init_posix(_CONFIG_VARS)
      #       File "/nix/store/537cvh8mqdaglc0an8ndmzszllh1lwac-python3-minimal-3.10.8/lib/python3.10/sysconfig.py", line 477, in _init_posix
      #         _temp = __import__(name, globals(), locals(), ['build_time_vars'], 0)
      #     ModuleNotFoundError: No module named '_sysconfigdata__linux_aarch64-linux-android'
      #
      # Notice that the *build* Python
      # (537cvh8mqdaglc0an8ndmzszllh1lwac-python3-minimal-3.10.8) is trying to
      # load sysconfigdata for the *host* Python - presumably so it can
      # compile the extension correctly.  However, the host Python
      # sysconfigdata module appears to be unavailable to the build Python.
      #
      # Why?  The nixpkgs CPython expression writes the sysconfigdata module
      # for both Pythons (build and host).  It has ``sysconfigdataHook`` that
      # goes to a lot of trouble to compute the right parameters for the host
      # Python and write the data to a properly named source file.
      #
      # However, the build Python and host Pythons each only get one of these
      # modules.  For the build Python it is:
      #
      #   _sysconfigdata__linux_x86_64-linux-gnu.py
      #
      # and the host Python gets:
      #
      #   _sysconfigdata__linux_.py
      #
      # Each of these contains information which is apparently correct for
      # their respective runtime systems.  Then, the nixpkgs CPython
      # expression for building the extension module uses "bootstrap pip"
      # which will respect PYTHONPATH for module search.
      #
      # (Q) Does something put the host Python's _sysconfigdata__linux_.py on
      # the build Python's PYTHONPATH?  What?  How?
      #
      # (Q)

      #
      # And now for something completely different.
      #
      # https://crossenv.readthedocs.io/en/latest/ is a tool specifically for
      # this.  It assumes you can cross-compile CPython on your own, though,
      # and then helps you use it to cross-compile extension modules.  Maybe
      # our nixpkgs-cross-compiled CPython would be a good complement?
      #


      # packages.host-python = pkgsCross.aarch64-android.python3Minimal.withPackages (ps: [
      #   # The nixpkgs zfec has some extra dependencies that we might not
      #   # really care about.  This one is leaner.  Mostly only relevant
      #   # because we have to cross-compile anything here.
      #   (ps.callPackage ./zfec.nix {})
      # ]);

      # packages.aarch64-android.host-python310 =
      #   let
      #     pkgs = import nixpkgs {
      #       system = "x86_64-linux";
      #       config = {
      #         allowUnfree = true;
      #         android_sdk.accept_license = true;
      #       };
      #     };
      #   in
      #     (pkgs.pkgsCross.aarch64-android.python310.override {
      #       # Turn off all the things.  Each one might be broken for
      #       # cross-compilation and even if not we don't need them for this build
      #       # Python.  We just need it to report the right platform info.
      #       mimetypesSupport = false;
      #       x11Support = false;
      #       bluezSupport = false;
      #       sqlite = null;
      #       openssl = null;
      #       tzdata = null;
      #       readline = null;
      #     }).overrideAttrs (old: {
      #       # Turn off even more things which aren't parameters to the Python
      #       # expressionThey .

      #       # These drag in an aarch64-android bash which fails to build.
      #       preFixup = null;
      #       postPatch = null;

      #       # This drags in a -lcrypt dependency which isn't satisfied by Google
      #       # Android Bionic.
      #       LIBS = null;
      #     });
    };