From 86a2c8d432cc0093b6a0a21131b1046b0ffea71a Mon Sep 17 00:00:00 2001 From: Andrey Prokopyuk Date: Sat, 3 Oct 2026 01:03:39 +0300 Subject: [PATCH] perf: reuse native AAR and Gradle outputs across release APKs --- docs/en/fastlane.md | 6 ++ docs/reviews/release-build-2026-10-03.md | 65 ++++++++++++++++++ docs/ru/fastlane.md | 6 ++ scripts/build-release-apks.sh | 87 ++++-------------------- 4 files changed, 92 insertions(+), 72 deletions(-) create mode 100644 docs/reviews/release-build-2026-10-03.md diff --git a/docs/en/fastlane.md b/docs/en/fastlane.md index ff8dd83..f10622f 100644 --- a/docs/en/fastlane.md +++ b/docs/en/fastlane.md @@ -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 diff --git a/docs/reviews/release-build-2026-10-03.md b/docs/reviews/release-build-2026-10-03.md new file mode 100644 index 0000000..795b814 --- /dev/null +++ b/docs/reviews/release-build-2026-10-03.md @@ -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. diff --git a/docs/ru/fastlane.md b/docs/ru/fastlane.md index b18002d..b13b330 100644 --- a/docs/ru/fastlane.md +++ b/docs/ru/fastlane.md @@ -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`. Нужен работающий локальный diff --git a/scripts/build-release-apks.sh b/scripts/build-release-apks.sh index 1cb58de..5030c79 100755 --- a/scripts/build-release-apks.sh +++ b/scripts/build-release-apks.sh @@ -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=" \ @@ -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" @@ -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