DarkFlippers / DarkFlippers/qUnleashed

Android build is 511s of a 741s job; measure it before optimising

Open
#48 2 comments 0 reactions 1 assignee Claimed by @mishamyte View on GitHub
area/ci-build
Dominant language
C
Stars
111
Forks
8
Avg merge
3h 41m
Merged PRs (30d)
37

Description

**Decision: keep open, blocked on #50, with a defined exit.**

`Build Android release APKs` is 511s of a 741s job — the critical path of a ~13-minute pipeline, paid ~20 times in the last 90 days. That is worth understanding. It is not yet worth optimising, because nobody can say what the 511s is made of.

## Settled, so it is not relitigated

**The universal APK stays.** It is needed for sideloading where the ABI is unknown, which is the normal case for an app distributed outside a store. It is also not the cost: measured from release `dev-0.12.1`, the universal build is 452s and `--split-per-abi` adds 59s. The second build is 11.5%, because Flutter's filecache and Gradle's incremental build already reuse the AOT artifacts and native libraries across the two invocations. The original framing of this issue — "builds the APK twice" — was wrong about the magnitude.

**Runner minutes are not the concern.** The repository is public, so GitHub-hosted minutes are free. The cost is wall-clock on a release, not money.

**`~/.gradle` caching is not the answer on its own.** Attempted and abandoned on `chore/m1-android-build-cache`; see the previous comment for why. The short version: Flutter's `FlutterTask` is not `@CacheableTask` and AGP's `externalNativeBuild` is not cacheable, so the two expensive stages both write outside `~/.gradle`.

## The blocker

The decomposition — Dart AOT vs the CMake/NDK build of the C++ modules vs packaging — does not exist, and cannot be obtained without either cutting a release or being able to run the release workflow on demand. That is **#50**. Nothing here should move before it does.

Timing the two `flutter build apk` invocations does not help: those are the 452s and 59s already recorded above.

## Next action, once #50 lands

- [ ] Get the per-task breakdown — a Gradle `--profile` run, an init-script task timer, or `gradle/actions/setup-gradle`'s task summary
- [ ] **Then** decide, from the numbers:
- If the NDK build is the large share, the lever is `ccache` (`CMAKE_C_COMPILER_LAUNCHER`) or caching `android/app/.cxx`, not `~/.gradle`
- If Dart AOT dominates, there is likely nothing to do — it is inherent, and three ABIs means three compiles
- If packaging and Kotlin/Java dominate, `~/.gradle` caching is worth revisiting, keyed on `android/gradle.properties` and the Flutter version, with a `run_id` suffix so it refreshes

## Exit criterion

**Close this as not planned if the decomposition shows less than roughly a fifth of the 511s is cacheable.** A ~13-minute release on free runners does not justify carrying speculative build-cache machinery, and the abandoned attempt showed that machinery has its own costs — an unrefreshed cache, a key that does not describe the build, and new integrity surface (#52) on the job that holds the signing key.

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.