Skip to content

Commit 3f146ee

Browse files
committed
Add Android reproducible build tooling
1 parent 830238c commit 3f146ee

19 files changed

Lines changed: 1140 additions & 8 deletions
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
name: Android Reproducible Build
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
variant:
7+
description: Release APK variant to verify
8+
required: true
9+
default: universal
10+
type: choice
11+
options:
12+
- fdroid
13+
- universal
14+
pull_request:
15+
branches: ["main"]
16+
paths:
17+
- "android/app/build.gradle.kts"
18+
- "android/**/*.gradle.kts"
19+
- "android/app/proguard*.pro"
20+
- "android/fdroid-build/**"
21+
- "android/reproducible/**"
22+
- "android/gradle/**"
23+
- "shell.nix"
24+
- ".github/workflows/android-reproducible.yml"
25+
push:
26+
paths:
27+
- "android/app/build.gradle.kts"
28+
- "android/**/*.gradle.kts"
29+
- "android/app/proguard*.pro"
30+
- "android/fdroid-build/**"
31+
- "android/reproducible/**"
32+
- "android/gradle/**"
33+
- "shell.nix"
34+
- ".github/workflows/android-reproducible.yml"
35+
36+
concurrency:
37+
group: ${{ github.workflow }}-${{ github.ref }}
38+
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
39+
40+
jobs:
41+
verify:
42+
name: Verify ${{ github.event.inputs.variant || 'universal' }} APK
43+
runs-on: ubuntu-latest
44+
timeout-minutes: 180
45+
env:
46+
GPR_USERNAME: ${{ github.actor }}
47+
GPR_TOKEN: ${{ github.token }}
48+
REPRO_VARIANT: ${{ github.event.inputs.variant || 'universal' }}
49+
steps:
50+
- name: Checkout
51+
uses: actions/checkout@v5
52+
with:
53+
submodules: recursive
54+
55+
- name: Install Nix
56+
uses: cachix/install-nix-action@v31
57+
58+
- name: Verify reproducible APK
59+
run: |
60+
nix-shell shell.nix --run \
61+
"android/reproducible/verify.sh --source . --variant $REPRO_VARIANT --work-dir $RUNNER_TEMP/gem-repro --keep"
62+
63+
- name: Upload verifier artifacts
64+
if: failure()
65+
uses: actions/upload-artifact@v4
66+
with:
67+
name: android-reproducible-${{ env.REPRO_VARIANT }}
68+
path: |
69+
${{ runner.temp }}/gem-repro/build-one/*.apk
70+
${{ runner.temp }}/gem-repro/build-two/*.apk
71+
${{ runner.temp }}/gem-repro/build-one/build.log
72+
${{ runner.temp }}/gem-repro/build-two/build.log
73+
${{ runner.temp }}/gem-repro/build-one/*mapping*.txt
74+
${{ runner.temp }}/gem-repro/build-two/*mapping*.txt
75+
${{ runner.temp }}/gem-repro/build-one/*configuration*.txt
76+
${{ runner.temp }}/gem-repro/build-two/*configuration*.txt
77+
${{ runner.temp }}/gem-repro/*.html

‎.gitignore‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,5 @@ core/.env
77
core/.vscode/
88
core/gemstone/src/.build/
99
*.code-workspace
10+
.gradle-reproducible/
11+
.cargo-reproducible/

‎README.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ Gem Wallet is an open-source mobile wallet for iOS and Android. This repository
1717

1818
🤖 [Android on Google Play](https://play.google.com/store/apps/details?id=com.gemwallet.android&utm_campaign=github&utm_source=referral&utm_medium=github)
1919

20+
🔁 [Android reproducible builds](docs/android-reproducible-builds.md)
21+
2022
## Features
2123

2224
- Open source, self-custodial wallet with multi-chain support

‎android/app/build.gradle.kts‎

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@ plugins {
88
id("kotlinx-serialization")
99
id("com.google.devtools.ksp")
1010
id("androidx.room")
11-
id("com.google.gms.google-services")
11+
}
12+
13+
if (System.getenv("FDROID_BUILD") != "true") {
14+
apply(plugin = "com.google.gms.google-services")
1215
}
1316

1417
repositories {
@@ -57,6 +60,10 @@ android {
5760

5861
create("fdroid") {
5962
dimension = channelDimension
63+
ndk {
64+
abiFilters.add("armeabi-v7a")
65+
abiFilters.add("arm64-v8a")
66+
}
6067
buildConfigField("String", "UPDATE_URL", "\"\"")
6168
}
6269
create("huawei") {
@@ -138,7 +145,17 @@ android {
138145
}
139146

140147
getByName("release") {
141-
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
148+
val reproducibleBuild = System.getenv("ANDROID_REPRODUCIBLE_BUILD") == "true"
149+
val releaseProguardFiles = mutableListOf<Any>(
150+
getDefaultProguardFile("proguard-android-optimize.txt"),
151+
"proguard-rules.pro",
152+
)
153+
if (reproducibleBuild) {
154+
releaseProguardFiles.add("proguard-reproducible-rules.pro")
155+
} else if (System.getenv("FDROID_BUILD") != "true") {
156+
releaseProguardFiles.add("proguard-stacktrace-rules.pro")
157+
}
158+
proguardFiles(*releaseProguardFiles.toTypedArray())
142159
isMinifyEnabled = true
143160
isShrinkResources = true
144161
isDebuggable = false
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
-dontoptimize

‎android/app/proguard-rules.pro‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@
1212
# public *;
1313
#}
1414

15-
# Preserve stack traces and fix R8 non-deterministic map-id for reproducible builds.
16-
-keepattributes SourceFile,LineNumberTable
15+
# Keep SourceFile stable for stack traces and avoid R8 path-derived SourceFile names.
16+
# Store releases add LineNumberTable in proguard-stacktrace-rules.pro.
17+
-keepattributes SourceFile
1718
-renamesourcefileattribute SourceFile
1819

1920
-verbose
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Preserve source line numbers for store releases.
2+
#
3+
# Reproducible/F-Droid builds intentionally skip LineNumberTable because R8 9.2.14
4+
# produced non-deterministic outline and residual-signature position metadata across
5+
# two clean Linux builds.
6+
-keepattributes LineNumberTable

‎android/build.gradle.kts‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ buildscript {
33
gradlePluginPortal()
44
google()
55
mavenCentral()
6-
mavenLocal()
6+
if (System.getenv("FDROID_BUILD") != "true") {
7+
mavenLocal()
8+
}
79
}
810
dependencies {
911
classpath(libs.gradle)
@@ -33,7 +35,9 @@ allprojects {
3335
}
3436
google()
3537
mavenCentral()
36-
mavenLocal()
38+
if (System.getenv("FDROID_BUILD") != "true") {
39+
mavenLocal()
40+
}
3741
maven { url = uri("https://jitpack.io") }
3842
}
3943

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[target.aarch64-linux-android]
2+
ar = "{NDK_PATH}/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ar"
3+
linker = "{NDK_PATH}/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android28-clang"
4+
5+
[target.armv7-linux-androideabi]
6+
ar = "{NDK_PATH}/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-ar"
7+
linker = "{NDK_PATH}/toolchains/llvm/prebuilt/linux-x86_64/bin/armv7a-linux-androideabi28-clang"

‎android/fdroid-build/env.sh‎

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5+
repo_root="$(cd "$script_dir/../.." && pwd)"
6+
7+
# shellcheck source=/dev/null
8+
source "$HOME/.cargo/env"
9+
10+
export FDROID_BUILD=true
11+
export ANDROID_REPRODUCIBLE_BUILD=true
12+
export SKIP_SIGN=true
13+
export CARGO_NET_GIT_FETCH_WITH_CLI=true
14+
export NDK_TOOLCHAIN_DIR="$NDK_PATH/toolchains/llvm/prebuilt/linux-x86_64/bin"
15+
16+
export AR_aarch64_linux_android="$NDK_TOOLCHAIN_DIR/llvm-ar"
17+
export AR_armv7_linux_androideabi="$NDK_TOOLCHAIN_DIR/llvm-ar"
18+
19+
export CC_aarch64_linux_android="$NDK_TOOLCHAIN_DIR/aarch64-linux-android28-clang"
20+
export CC_armv7_linux_androideabi="$NDK_TOOLCHAIN_DIR/armv7a-linux-androideabi28-clang"
21+
22+
export CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER="$CC_aarch64_linux_android"
23+
export CARGO_TARGET_ARMV7_LINUX_ANDROIDEABI_LINKER="$CC_armv7_linux_androideabi"
24+
export GEMSTONE_ANDROID_ABIS="arm64-v8a,armeabi-v7a"
25+
26+
export RUSTFLAGS="${RUSTFLAGS:-} --remap-path-prefix=$repo_root=. --remap-path-prefix=$HOME=FDROID_HOME"

0 commit comments

Comments
 (0)