You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit d27400e
Browse filesBrowse the repository at this point in the historyBrowse files
Copy file name to clipboardExpand all lines: docs/adr/0002-on-device-builds-via-gradle-tooling-api.md
+2Lines changed: 2 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -20,6 +20,8 @@ Run builds with the **Gradle Tooling API in a separate JVM process**, and have t
20
20
21
21
The app streams progress/events back from this process and renders them (e.g. `BuildState`, build output). The process runs on a **full out-of-process JDK** — the `java` binary from our terminal bootstrap packages (`appdevforall/terminal-packages`), launched by `ToolingServerRunner` — **not** the composite-build toolchains from [ADR 0003](0003-vendored-forked-desktop-toolchain.md), which are a separate, in-IDE-runtime concern.
22
22
23
+
**Scope:** this covers every build that produces an installable artifact, including Quick Build's own proxy-app provisioning. Quick Build's incremental per-save step is the one exception — it compiles outside Gradle, and the trade-offs are recorded in [ADR 0016](0016-quick-build-compiles-outside-gradle.md).
[ADR 0002](0002-on-device-builds-via-gradle-tooling-api.md) builds on device through real Gradle so results match a desktop build, and rejects a custom build engine. That still holds for anything a user installs or ships.
10
+
11
+
Quick Build (ADFA-4128) does a different job: fast live reload, so a developer can iterate while writing code. A standard incremental Gradle build of a single app-module edit medians 4.7 s on a Galaxy A56 and 18.4 s on an A06, against 1.1 s and 2.8 s for Quick Build `[measured on a56, a06]`.
12
+
13
+
Most of that time is not spent on the edit. A one-line Kotlin edit takes 7.8 s to build incrementally on an A06:
14
+
15
+
- launch and configuration, 3.9 s - paid whatever the edit touched
16
+
- packaging and install, 1.1 s - to make an APK a running app does not need
17
+
- dex and resource link, 1.4 s - on outputs the edit did not change
18
+
- kotlinc, 1.2 s - the only stage the edit created
19
+
20
+
The first three cannot be sped up or skipped.
21
+
22
+
## Decision
23
+
24
+
**Quick Build's live reload path does not use Gradle.**`:quickbuild:daemon`, a JVM child process of CoGo, compiles Kotlin with the Kotlin Build Tools API and Java with javac, then dexes with d8 and relinks resources with aapt2, using the SDK already on the device. No AGP, no r8.
25
+
26
+
**Gradle handles what live reload cannot.** It still provisions the proxy app through the existing Tooling API path, and still builds every edit the classifier declines. Nothing a user installs or ships comes out of the daemon.
27
+
28
+
**One compiler, not two.** Quick Build needs Kotlin 2.3.x for faster, more robust incremental compilation. Until the rest of CoGo moves up, the APK carries two Kotlin compilers. The move is in review as ADFA-2602; unifying them is ADFA-4931.
29
+
30
+
## Consequences
31
+
32
+
**Positive**
33
+
34
+
- The edit loop is 4-6x faster, measured in the 2026-09-05 to 09-07 benchmarking run against the initial experimental Quick Build release.
35
+
- The compiler stays warm between edits - the biggest single latency lever, and something Gradle cannot do.
36
+
- A compiler crash kills the daemon, not the IDE, and the daemon can be shut down to give Gradle its memory back.
37
+
38
+
**Negative - inherent to the decision**
39
+
40
+
- Output is not identical to Gradle's. That is deliberate: close enough on the cases that matter beats full compatibility.
41
+
- A second build pipeline to maintain. It will drift from AGP, and we cannot use Gradle as ground truth, so it needs its own ongoing testing - which is slow, because builds on low-spec devices are slow.
42
+
43
+
**Negative - solvable with more work**
44
+
45
+
- No annotation processing. kapt and KSP edits go to Gradle; KSP looks tractable, see [ksp-kapt-feasibility.md](../../quickbuild/docs/ksp-kapt-feasibility.md).
46
+
- Live reload covers a narrow set of edits today; the rest fall back to Gradle. Conservative defaults, not hard limits.
47
+
- Memory is not tuned. Gradle and Quick Build share it, and idle timeouts are all that keeps them out of each other's way.
48
+
49
+
## Alternatives considered
50
+
51
+
-**Gradle with fewer tasks** — rejected: the cost is mostly configuration and task-graph work, which fewer tasks do not remove, and it still builds an APK rather than a deployable payload.
52
+
-**Compile in-process inside the IDE** — rejected for ADR 0002's own reason: a compiler OOM would take the editor with it.
53
+
-**Replace the proxy-app build too** — rejected: it would drift from AGP on the one artifact where that is unacceptable.
54
+
-**ART hot-swap (Apply Changes)** — rejected: needs an attached debugger and replaces only method bodies.
55
+
-**Patch the android.jar** — rejected as infeasible; see [why not android.jar](../../quickbuild/docs/why-not-android-jar.md).
56
+
57
+
## Related
58
+
59
+
-[ADR 0002](0002-on-device-builds-via-gradle-tooling-api.md) — still governs full builds and Quick Build's provisioning.
60
+
-[ADR 0004](0004-embedded-termux-runtime.md) — the daemon runs on the bundled JDK.
61
+
-[`quickbuild/README.md`](../../quickbuild/README.md) — design and measured numbers.
- Cross-plugin service interfaces, where **one plugin implements what another calls** (via `SharedServices`): `LlmInferenceService` — implemented by ai-core, called by every AI plugin — together with the types nested in it that a *backend* plugin implements (`LlmBackend`, `HistoryCapableBackend`, `ToolCallingBackend`, `CancellableBackend`, `ConfigurableBackend`, `EmbeddingBackend`) and the value types either side constructs (`ChatMessage`, `LlmConfig`, `LlmResponse`, `SystemPromptRequest`, `ToolDefinition`, `ToolCallRequest`). Also `ToolSourceRegistry` — implemented by ai-core, called by any plugin contributing tools to the agent — with `ToolSource` and `ToolSpec`, which a *contributing* plugin implements, `ToolInvocation`, which ai-core constructs and passes to `ToolSource.invoke`, and `ToolOutcome`, which the source returns.
16
-
- Utility classes plugins **instantiate**: `KeystoreSecretStore` (AES/GCM over the Android Keystore, alias supplied by the caller). Host-side implementation rather than an interface, so plugins share one copy in the process instead of compiling their own.
16
+
- Utility classes plugins **instantiate**: `KeystoreSecretStore` (AES/GCM over the Android Keystore, alias supplied by the caller); the AI prompt config engine in `ai.prompt` (`PromptTemplateEngine`, `PromptConfigLoader`, `PromptConfigStore`, `PromptConfigDocument`, `PromptConfigObject`, `AssetPromptConfigSource`), generic over a plugin's own config type through `PromptConfigParser` and `PromptConfigProvider`, which a plugin implements; and the settings-pane helpers in `ai.ui` (`SecretRevealController` with `RevealToggle`, `applyPaneStyling` with `PaneStyle`, `ButtonColors` and `FieldColors`). Host-side implementations rather than interfaces, so plugins share one copy in the process instead of compiling their own.
- Enums / sealed types plugins **reference**: `PluginPermission`, `ShowAsAction`, `ArchiveFormat`, `BuildActionCategory`, `ToolbarActionIds`, `CommandSpec`, `CommandResult`, `ExtractResult`, `KeystoreSecretStore.Stored`. Sealed, so a plugin `when`s over the cases exhaustively — adding one is a **breaking** change, not an additive one.
0 commit comments