Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/en/fastlane.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,12 @@ creates or updates one APK-links comment on the pull request. It does not check
execute pull-request code or artifacts. These are test artifacts only: neither APK is signed with
the MegaProxy release key, and neither is published as a GitHub Release or sent to an app store.

The release lane builds one native AAR containing all four ABIs, then reuses it
for the four architecture-specific APKs and the universal APK. Gradle's ABI filters
select native libraries; intermediate outputs are retained between APKs. Version
codes and `APK_VARIANT` remain variant-specific, so some compilation and R8 work
still repeats. The universal AAR uses the same binding flags as the F-Droid recipe.

## Real proxy server tests

Run `bundle exec fastlane android native_integration` with a running local Docker
Expand Down
65 changes: 65 additions & 0 deletions docs/reviews/release-build-2026-10-03.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Release APK reuse benchmark — 2026-10-03

Source baseline: `5c7047c` (v1.0.0), macOS arm64, JDK 21. Both builds used
`bundle exec fastlane android release_artifacts`, the same local release signer,
SDK/NDK, Go module cache and Gradle daemon. Baseline ran first, candidate second;
existing dependency/compiler caches were retained. This is one sequential pair,
not a cold-cache benchmark, median, or GitHub runner performance guarantee.

## Change and timing

Build one universal gomobile AAR and reuse it for all APKs with the existing
Gradle ABI filters. Remove the five intermediate Gradle cleans. Preserve binding
flags, version-code offsets, APK_VARIANT, signing and verification.

| Complete lane | Wall time |
| --- | ---: |
| Baseline | 214.47 s |
| Candidate | 177.11 s |

Observed saving: 37.36 s (17.4%). Gradle reports reused intermediate tasks;
variant-specific BuildConfig/version fields still require Kotlin/Java/R8 work.
No Gradle build-cache or dependency changes were needed.

## Artifact checks

All five signed APKs are byte-for-byte identical between the two runs (matching
SHA-256), including every ZIP entry. Sizes below are exact bytes, not rounded MB.
All size deltas are zero. Universal contains four native ABIs; each other APK
contains only its named ABI, including transitive AndroidX native libraries.

| APK variant | Baseline bytes | Candidate bytes | SHA-256 (both) |
| --- | ---: | ---: | --- |
| universal | 55573043 | 55573043 | `e085e3fc85cec1636a7306e77bd7067aa90710cf013fe5b625ee17c459411a46` |
| armeabi-v7a | 14998868 | 14998868 | `7d9646b06e9c793d5dafbc1edbbd97326864bb0ede6691164e42c4f22f8e8cf4` |
| arm64-v8a | 15364196 | 15364196 | `c9ac04f98b66c29c06cc40e17ef231854b89d85880291f807d0919e90da4a2d7` |
| x86 | 15042172 | 15042172 | `a9bd1796d8df34583745cd4700a4a6b3029110278b1d20a5aeb092a7076554c9` |
| x86_64 | 16300126 | 16300126 | `ae155d94a1fcd014bd4d79cbab65425809d1672d2525de67a129d7ba744da2d5` |

Both lanes passed native tests, Android JVM tests and release lint/build tasks.
The candidate retained reports for 169 JVM tests in 46 suites: zero failures,
errors or skips. Every APK passed the script's apksigner certificate and
version-code verification; independent aapt checks confirmed versionName 1.0.0,
minSdk 26 and codes 15000–15004 with their existing offsets.

Byte-identical universal output confirms this local incremental build matches the
baseline's clean universal assembly. This is not an official F-Droid buildserver
reproducibility result or a new device/VPN test. No APK was published or installed.

Local evidence (ignored): `dist/release-benchmark/{baseline,optimized}.log`,
`dist/release-benchmark/{baseline,optimized}/*.apk`, and `comparison.json`.
Reproduce with separate MEGAPROXY_RELEASE_DIR values and `/usr/bin/time -p` around
the lane; compare SHA256SUMS, aapt badging and ZIP library paths for each variant.

## APK contents review

ARM64's Go library is 13,294,096 bytes; AndroidX graphics-path is 10,096 bytes.
Both are stored uncompressed. No extra CPU architectures or packaged source AAR
were found in architecture-specific APKs. Common DEX/resources are expected.
The resource table is 371,144 bytes and includes dependency translations beyond
English/Russian; this is the entire table size, not the translations' overhead.
META-INF and Kotlin metadata together occupy about 22 KB compressed; coroutine
DebugProbesKt.bin and kotlin-tooling-metadata.json add 1,066 compressed bytes.
These are possible separate size investigations, not demonstrated safe deletions.
Resource stripping and native compression were deliberately not mixed into this
build-reuse change.
6 changes: 6 additions & 0 deletions docs/ru/fastlane.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,12 @@ checkout, не скачивает и не исполняет код или ар
один из APK не подписан официальным release-ключом MegaProxy, не публикуется в GitHub Releases и не
отправляется в магазин приложений.

Релизная сборка создаёт один нативный AAR со всеми четырьмя ABI и использует его
для четырёх APK по архитектурам и универсального APK. Фильтры ABI в Gradle выбирают
нативные библиотеки; промежуточные результаты сохраняются между APK. Коды версий
и `APK_VARIANT` остаются разными, поэтому часть компиляции и работы R8 повторяется.
Универсальный AAR использует те же флаги генерации, что и рецепт F-Droid.

## Тесты с настоящими прокси-серверами

Запуск: `bundle exec fastlane android native_integration`. Нужен работающий локальный
Expand Down
87 changes: 15 additions & 72 deletions scripts/build-release-apks.sh
Original file line number Diff line number Diff line change
Expand Up @@ -128,26 +128,13 @@ echo "Running native tests"
go test ./...
)

# gomobile architecture names and their corresponding Android ABI names.
targets=(
"arm64:arm64-v8a:$((version_code_base * 1000 + 2))"
"arm:armeabi-v7a:$((version_code_base * 1000 + 1))"
"amd64:x86_64:$((version_code_base * 1000 + 4))"
"386:x86:$((version_code_base * 1000 + 3))"
)

# A clean checkout has no app/libs/megaproxy.aar. Gradle resolves local AAR
# dependencies while configuring Android unit tests, so build the first ABI
# before invoking Gradle and reuse it for the first release APK below.
first_target="${targets[0]}"
first_go_arch="${first_target%%:*}"
first_target_rest="${first_target#*:}"
first_android_abi="${first_target_rest%%:*}"
echo "Building initial native AAR for $first_android_abi"
# Build every native ABI once. Gradle's abiFilters select the libraries for
# each single-ABI APK; universal keeps the same inputs as the F-Droid recipe.
echo "Building shared native AAR for all APKs"
(
cd "$project_dir/native"
gomobile bind \
-target="android/$first_go_arch" \
-target=android \
-androidapi 26 \
-trimpath \
-ldflags="-s -w -buildid= -X=runtime.modinfo=" \
Expand All @@ -163,36 +150,23 @@ echo "Running Android unit tests"
./gradlew testDebugUnitTest
)

targets=(
"arm64-v8a:2"
"armeabi-v7a:1"
"x86_64:4"
"x86:3"
"universal:0"
)
for target in "${targets[@]}"; do
go_arch="${target%%:*}"
target_rest="${target#*:}"
android_abi="${target_rest%%:*}"
version_code="${target_rest##*:}"
android_abi="${target%%:*}"
version_code="$((version_code_base * 1000 + ${target##*:}))"
output_apk="$MEGAPROXY_RELEASE_DIR/mega-proxy-${android_abi}.apk"

if [[ "$android_abi" == "$first_android_abi" ]]; then
echo "Reusing initial native AAR for $android_abi"
else
echo "Building optimized native AAR for $android_abi"
(
cd "$project_dir/native"
gomobile bind \
-target="android/$go_arch" \
-androidapi 26 \
-trimpath \
-ldflags="-s -w -buildid= -X=runtime.modinfo=" \
-o ../app/libs/megaproxy.aar \
./mobile
)
fi

echo "Building and signing $android_abi APK"
(
cd "$project_dir"
# Keep clean and assemble in separate Gradle invocations. When both are
# requested in one task graph, Gradle does not guarantee that clean has
# finished before every generated-resource task starts.
./gradlew clean
# Keep unchanged task outputs between variants. Gradle tracks the AAR,
# ABI filters, versionCode and BuildConfig fields as task inputs.
./gradlew assembleRelease -PmegaproxyVersionVariant="$android_abi"
)
built_apk="$project_dir/app/build/outputs/apk/release/app-release.apk"
Expand All @@ -206,37 +180,6 @@ for target in "${targets[@]}"; do
verify_release_version_code "$output_apk" "$version_code"
done

# F-Droid verifies this universal APK against a clean source build before
# publishing it with the upstream signature. Keep its gomobile and Gradle
# inputs compatible with the recipe maintained in fdroiddata.
echo "Building optimized native AAR for the universal APK"
(
cd "$project_dir/native"
gomobile bind \
-target=android \
-androidapi 26 \
-trimpath \
-ldflags="-s -w -buildid= -X=runtime.modinfo=" \
-o ../app/libs/megaproxy.aar \
./mobile
)

echo "Building and signing universal APK"
(
cd "$project_dir"
./gradlew clean
./gradlew assembleRelease -PmegaproxyVersionVariant=universal
)
universal_apk="$MEGAPROXY_RELEASE_DIR/mega-proxy-universal.apk"
built_apk="$project_dir/app/build/outputs/apk/release/app-release.apk"
if [[ ! -f "$built_apk" ]]; then
echo "Gradle did not produce the expected APK: $built_apk" >&2
exit 1
fi
cp "$built_apk" "$universal_apk"
verify_release_apk "$universal_apk"
verify_release_version_code "$universal_apk" "$((version_code_base * 1000))"

(
cd "$MEGAPROXY_RELEASE_DIR"
shasum -a 256 ./*.apk > SHA256SUMS
Expand Down
Loading