summaryrefslogtreecommitdiffhomepage
path: root/android
diff options
context:
space:
mode:
authorAlbin <albin@mullvad.net>2025-07-15 15:38:33 +0200
committerAlbin <albin@mullvad.net>2025-07-15 15:38:33 +0200
commit592636ad50e93c1990d7b08321378ea19a4c33e4 (patch)
tree4314baf1bff9867ab1eb605f8e24f8bc70a25d38 /android
parenta3ec4507f0e0dcd35e1940e897eb1d3a51151940 (diff)
parent3771dff76a96665db0f93c0ec8af5e944ec429ec (diff)
downloadmullvadvpn-592636ad50e93c1990d7b08321378ea19a4c33e4.tar.xz
mullvadvpn-592636ad50e93c1990d7b08321378ea19a4c33e4.zip
Merge branch 'add-initial-android-devshell'
Diffstat (limited to 'android')
-rw-r--r--android/BuildInstructions.md16
-rw-r--r--android/flake.lock193
-rw-r--r--android/flake.nix104
-rw-r--r--android/lib/daemon-grpc/build.gradle.kts17
-rw-r--r--android/nix/env-vars.nix84
5 files changed, 412 insertions, 2 deletions
diff --git a/android/BuildInstructions.md b/android/BuildInstructions.md
index b287342fd4..a58a993b49 100644
--- a/android/BuildInstructions.md
+++ b/android/BuildInstructions.md
@@ -177,6 +177,22 @@ Run the following command to build a debug build:
../android/build.sh --app-bundle
```
+## Build using nix devshell
+1. Install the nix package manager by following the [official instructions](https://nixos.org/download/).
+2. Enable the experimental `nix-command` and `flake` features by following [these instructions](https://nixos.wiki/wiki/flakes).
+3. Launch a devshell (in `<repository>/android`) by running:
+ ```bash
+ nix develop
+ ```
+4. Build the app as usual by running for example:
+ ```bash
+ ./build.sh --dev-build
+ ```
+ or
+ ```bash
+ ./gradlew assembleOssProdDebug
+ ```
+
## Configure signing key
1. Create a directory to store the signing key, keystore and its configuration:
```
diff --git a/android/flake.lock b/android/flake.lock
new file mode 100644
index 0000000000..a9ee199ccc
--- /dev/null
+++ b/android/flake.lock
@@ -0,0 +1,193 @@
+{
+ "nodes": {
+ "android-nixpkgs": {
+ "inputs": {
+ "devshell": "devshell",
+ "flake-utils": "flake-utils",
+ "nixpkgs": [
+ "nixpkgs"
+ ]
+ },
+ "locked": {
+ "lastModified": 1752524595,
+ "narHash": "sha256-BQw78e4yNYtzn0+CIvNCyWuh6Q1FHY8hebn+jv4kC6E=",
+ "owner": "tadfisher",
+ "repo": "android-nixpkgs",
+ "rev": "5b4a9a71371280b3d95263bf0fc8dcffedec0df8",
+ "type": "github"
+ },
+ "original": {
+ "owner": "tadfisher",
+ "repo": "android-nixpkgs",
+ "type": "github"
+ }
+ },
+ "devshell": {
+ "inputs": {
+ "nixpkgs": [
+ "android-nixpkgs",
+ "nixpkgs"
+ ]
+ },
+ "locked": {
+ "lastModified": 1741473158,
+ "narHash": "sha256-kWNaq6wQUbUMlPgw8Y+9/9wP0F8SHkjy24/mN3UAppg=",
+ "owner": "numtide",
+ "repo": "devshell",
+ "rev": "7c9e793ebe66bcba8292989a68c0419b737a22a0",
+ "type": "github"
+ },
+ "original": {
+ "owner": "numtide",
+ "repo": "devshell",
+ "type": "github"
+ }
+ },
+ "devshell_2": {
+ "inputs": {
+ "nixpkgs": "nixpkgs"
+ },
+ "locked": {
+ "lastModified": 1741473158,
+ "narHash": "sha256-kWNaq6wQUbUMlPgw8Y+9/9wP0F8SHkjy24/mN3UAppg=",
+ "owner": "numtide",
+ "repo": "devshell",
+ "rev": "7c9e793ebe66bcba8292989a68c0419b737a22a0",
+ "type": "github"
+ },
+ "original": {
+ "owner": "numtide",
+ "repo": "devshell",
+ "type": "github"
+ }
+ },
+ "flake-utils": {
+ "inputs": {
+ "systems": "systems"
+ },
+ "locked": {
+ "lastModified": 1731533236,
+ "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
+ "type": "github"
+ },
+ "original": {
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "type": "github"
+ }
+ },
+ "flake-utils_2": {
+ "inputs": {
+ "systems": "systems_2"
+ },
+ "locked": {
+ "lastModified": 1731533236,
+ "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
+ "type": "github"
+ },
+ "original": {
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "type": "github"
+ }
+ },
+ "nixpkgs": {
+ "locked": {
+ "lastModified": 1722073938,
+ "narHash": "sha256-OpX0StkL8vpXyWOGUD6G+MA26wAXK6SpT94kLJXo6B4=",
+ "owner": "NixOS",
+ "repo": "nixpkgs",
+ "rev": "e36e9f57337d0ff0cf77aceb58af4c805472bfae",
+ "type": "github"
+ },
+ "original": {
+ "owner": "NixOS",
+ "ref": "nixpkgs-unstable",
+ "repo": "nixpkgs",
+ "type": "github"
+ }
+ },
+ "nixpkgs_2": {
+ "locked": {
+ "lastModified": 1752480373,
+ "narHash": "sha256-JHQbm+OcGp32wAsXTE/FLYGNpb+4GLi5oTvCxwSoBOA=",
+ "owner": "NixOS",
+ "repo": "nixpkgs",
+ "rev": "62e0f05ede1da0d54515d4ea8ce9c733f12d9f08",
+ "type": "github"
+ },
+ "original": {
+ "id": "nixpkgs",
+ "ref": "nixos-unstable",
+ "type": "indirect"
+ }
+ },
+ "root": {
+ "inputs": {
+ "android-nixpkgs": "android-nixpkgs",
+ "devshell": "devshell_2",
+ "flake-utils": "flake-utils_2",
+ "nixpkgs": "nixpkgs_2",
+ "rust-overlay": "rust-overlay"
+ }
+ },
+ "rust-overlay": {
+ "inputs": {
+ "nixpkgs": [
+ "nixpkgs"
+ ]
+ },
+ "locked": {
+ "lastModified": 1752547600,
+ "narHash": "sha256-0vUE42ji4mcCvQO8CI0Oy8LmC6u2G4qpYldZbZ26MLc=",
+ "owner": "oxalica",
+ "repo": "rust-overlay",
+ "rev": "9127ca1f5a785b23a2fc1c74551a27d3e8b9a28b",
+ "type": "github"
+ },
+ "original": {
+ "owner": "oxalica",
+ "repo": "rust-overlay",
+ "type": "github"
+ }
+ },
+ "systems": {
+ "locked": {
+ "lastModified": 1681028828,
+ "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
+ "owner": "nix-systems",
+ "repo": "default",
+ "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nix-systems",
+ "repo": "default",
+ "type": "github"
+ }
+ },
+ "systems_2": {
+ "locked": {
+ "lastModified": 1681028828,
+ "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
+ "owner": "nix-systems",
+ "repo": "default",
+ "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nix-systems",
+ "repo": "default",
+ "type": "github"
+ }
+ }
+ },
+ "root": "root",
+ "version": 7
+}
diff --git a/android/flake.nix b/android/flake.nix
new file mode 100644
index 0000000000..0d75da2038
--- /dev/null
+++ b/android/flake.nix
@@ -0,0 +1,104 @@
+{
+ description = "Mullvad Android app build flake";
+
+ inputs = {
+ # Unstable is currently needed for protoc-gen-grpc-java.
+ # We should switch to a stable channel once it's avaiable on those.
+ nixpkgs.url = "nixpkgs/nixos-unstable";
+ devshell.url = "github:numtide/devshell";
+ flake-utils.url = "github:numtide/flake-utils";
+ rust-overlay = {
+ url = "github:oxalica/rust-overlay";
+ inputs.nixpkgs.follows = "nixpkgs";
+ };
+ android-nixpkgs = {
+ url = "github:tadfisher/android-nixpkgs";
+ inputs.nixpkgs.follows = "nixpkgs";
+ };
+ };
+
+ outputs = {
+ self,
+ nixpkgs,
+ android-nixpkgs,
+ rust-overlay,
+ flake-utils,
+ devshell,
+ }:
+ flake-utils.lib.eachDefaultSystem (system: let
+ pkgs = import nixpkgs {
+ inherit system;
+ overlays = [
+ (import rust-overlay)
+ devshell.overlays.default
+ ];
+ };
+
+ rust-toolchain = (pkgs.buildPackages.rust-bin.fromRustupToolchainFile
+ ../rust-toolchain.toml).override {
+ extensions = ["rust-analyzer"];
+ targets = [
+ "aarch64-linux-android"
+ "armv7-linux-androideabi"
+ "x86_64-linux-android"
+ "i686-linux-android"
+ ];
+ };
+
+ patchedGo_1_21_3 =
+ (import (fetchTarball {
+ url = "https://github.com/NixOS/nixpkgs/archive/b392079f5fd051926a834c878d27ceec4f139dce.tar.gz";
+ sha256 = "16dkk98fs9pw2amz0vpjsc7ks85cw3hc5rlpbp27llq6x7lwpjaz";
+ }) {inherit system;}).go_1_21.overrideAttrs (oldAttrs: {
+ patches = (oldAttrs.patches or []) ++ [./docker/goruntime-boottime-over-monotonic.diff];
+ });
+
+ versions =
+ (builtins.fromTOML (
+ builtins.concatStringsSep "\n" (
+ builtins.filter (line: !(builtins.match "^[[:space:]]*#" line != null))
+ (nixpkgs.lib.splitString "\n" (builtins.readFile ./gradle/libs.versions.toml))
+ )
+ )).versions;
+
+ compileSdkVersion = versions."compile-sdk";
+ buildToolsVersion = versions."build-tools";
+ minSdkVersion = versions."min-sdk";
+ ndkVersion = versions.ndk;
+
+ android-sdk = android-nixpkgs.sdk.${system} (sdkPkgs:
+ with sdkPkgs; [
+ (builtins.getAttr "platforms-android-${compileSdkVersion}" sdkPkgs)
+ (builtins.getAttr "build-tools-${builtins.replaceStrings ["."] ["-"] buildToolsVersion}" sdkPkgs)
+ (builtins.getAttr "ndk-${builtins.replaceStrings ["."] ["-"] ndkVersion}" sdkPkgs)
+ cmdline-tools-latest
+ platform-tools
+ ]);
+ in {
+ overlay = final: prev: {
+ inherit (self.packages.${system}) android-sdk;
+ };
+
+ packages = {
+ inherit android-sdk;
+ };
+
+ devShells.default = pkgs.devshell.mkShell {
+ name = "mullvad-android-devshell";
+ packages = [
+ android-sdk
+ rust-toolchain
+ patchedGo_1_21_3
+ pkgs.protoc-gen-grpc-java
+ pkgs.gcc
+ pkgs.gnumake
+ pkgs.protobuf
+ pkgs.jdk17
+ pkgs.python3Full
+ ];
+ env = import ./nix/env-vars.nix {
+ inherit pkgs android-sdk buildToolsVersion ndkVersion minSdkVersion;
+ };
+ };
+ });
+}
diff --git a/android/lib/daemon-grpc/build.gradle.kts b/android/lib/daemon-grpc/build.gradle.kts
index d4a6d0e7a2..cadd126830 100644
--- a/android/lib/daemon-grpc/build.gradle.kts
+++ b/android/lib/daemon-grpc/build.gradle.kts
@@ -44,8 +44,21 @@ android {
protobuf {
protoc { artifact = libs.plugins.protobuf.protoc.get().toString() }
plugins {
- create("java") { artifact = libs.plugins.grpc.protoc.gen.grpc.java.get().toString() }
- create("grpc") { artifact = libs.plugins.grpc.protoc.gen.grpc.java.get().toString() }
+ val grpcPluginPath = System.getenv("PROTOC_GEN_GRPC_JAVA_PLUGIN")
+ create("java") {
+ if (grpcPluginPath != null) {
+ path = grpcPluginPath
+ } else {
+ artifact = libs.plugins.grpc.protoc.gen.grpc.java.get().toString()
+ }
+ }
+ create("grpc") {
+ if (grpcPluginPath != null) {
+ path = grpcPluginPath
+ } else {
+ artifact = libs.plugins.grpc.protoc.gen.grpc.java.get().toString()
+ }
+ }
create("grpckt") { artifact = libs.plugins.grpc.protoc.gen.grpc.kotlin.get().toString() }
}
generateProtoTasks {
diff --git a/android/nix/env-vars.nix b/android/nix/env-vars.nix
new file mode 100644
index 0000000000..e9f5d6aa62
--- /dev/null
+++ b/android/nix/env-vars.nix
@@ -0,0 +1,84 @@
+{
+ pkgs,
+ android-sdk,
+ buildToolsVersion,
+ ndkVersion,
+ minSdkVersion,
+}: [
+ {
+ name = "JAVA_HOME";
+ value = "${pkgs.jdk17}";
+ }
+ {
+ name = "PROTOC_GEN_GRPC_JAVA_PLUGIN";
+ prefix = "${pkgs.protoc-gen-grpc-java}/bin/protoc-gen-grpc-java";
+ }
+ {
+ name = "GRADLE_OPTS";
+ value = "-Dorg.gradle.project.android.aapt2FromMavenOverride=${android-sdk}/share/android-sdk/build-tools/${buildToolsVersion}/aapt2";
+ }
+ {
+ name = "ANDROID_HOME";
+ value = "${android-sdk}/share/android-sdk";
+ }
+ {
+ name = "ANDROID_SDK_ROOT";
+ value = "${android-sdk}/share/android-sdk";
+ }
+ {
+ name = "ANDROID_NDK_ROOT";
+ value = "${android-sdk}/share/android-sdk/ndk/${ndkVersion}";
+ }
+ {
+ name = "NDK_TOOLCHAIN_DIR";
+ value = "${android-sdk}/share/android-sdk/ndk/${ndkVersion}/toolchains/llvm/prebuilt/linux-x86_64/bin";
+ }
+ {
+ name = "AR_aarch64_linux_android";
+ value = "$NDK_TOOLCHAIN_DIR/llvm-ar";
+ }
+ {
+ name = "CC_aarch64_linux_android";
+ value = "$NDK_TOOLCHAIN_DIR/aarch64-linux-android${minSdkVersion}-clang";
+ }
+ {
+ name = "CARGO_TARGET_aarch64_LINUX_ANDROID_LINKER";
+ value = "$NDK_TOOLCHAIN_DIR/aarch64-linux-android${minSdkVersion}-clang";
+ }
+ {
+ name = "AR_armv7_linux_androideabi";
+ value = "$NDK_TOOLCHAIN_DIR/llvm-ar";
+ }
+ {
+ name = "CC_armv7_linux_androideabi";
+ value = "$NDK_TOOLCHAIN_DIR/armv7-linux-androideabi${minSdkVersion}-clang";
+ }
+ {
+ name = "CARGO_TARGET_armv7_LINUX_ANDROID_LINKER";
+ value = "$NDK_TOOLCHAIN_DIR/armv7-linux-androideabi${minSdkVersion}-clang";
+ }
+ {
+ name = "AR_x86_64_linux_android";
+ value = "$NDK_TOOLCHAIN_DIR/llvm-ar";
+ }
+ {
+ name = "CC_x86_64_linux_android";
+ value = "$NDK_TOOLCHAIN_DIR/x86_64-linux-android${minSdkVersion}-clang";
+ }
+ {
+ name = "CARGO_TARGET_x86_64_LINUX_ANDROID_LINKER";
+ value = "$NDK_TOOLCHAIN_DIR/x86_64-linux-android${minSdkVersion}-clang";
+ }
+ {
+ name = "AR_i686_linux_android";
+ value = "$NDK_TOOLCHAIN_DIR/llvm-ar";
+ }
+ {
+ name = "CC_i686_linux_android";
+ value = "$NDK_TOOLCHAIN_DIR/i686-linux-android${minSdkVersion}-clang";
+ }
+ {
+ name = "CARGO_TARGET_i686_LINUX_ANDROID_LINKER";
+ value = "$NDK_TOOLCHAIN_DIR/i686-linux-android${minSdkVersion}-clang";
+ }
+]