Skip to content

Commit db1fb3f

Browse files
fryanpanclaude
andcommitted
ADFA-4128: qb 11/11 app + bench — wires Quick Build into the IDE and adds the debug-only benchmark harness
The app wiring and the bench surface land together because they are mutually dependent: :app's ProjectHandlerActivity and QuickBuildModule call into QuickBuildBenchHooks, and QuickBuildBenchHooks returns AutostartBuild and resolves EnvironmentQuickBuildPaths. Neither ordering of a two-PR split compiles. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Kj9YeCDHGp9DU8LPtfWJ7W
1 parent c64ad0f commit db1fb3f

101 files changed

Lines changed: 12547 additions & 315 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎app/build.gradle.kts‎

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import com.itsaky.androidide.build.config.BuildConfig
44
import com.itsaky.androidide.desugaring.utils.JavaIOReplacements.applyJavaIOReplacements
55
import com.itsaky.androidide.plugins.AndroidIDEAssetsPlugin
6+
import com.itsaky.androidide.plugins.tasks.AddFileToAssetsTask
67
import org.json.JSONObject
78
import java.io.BufferedOutputStream
89
import java.io.ByteArrayInputStream
@@ -334,6 +335,7 @@ dependencies {
334335
implementation(projects.floatingWindow)
335336
implementation(projects.gitCore)
336337
implementation(projects.profiler)
338+
implementation(projects.quickbuild.core)
337339

338340
// This is to build the tooling-api-impl project before the app is built
339341
// So we always copy the latest JAR file to assets
@@ -379,6 +381,69 @@ dependencies {
379381
implementation("io.pebbletemplates:pebble:4.1.1")
380382
}
381383

384+
// Quick Build (ADFA-4128): stage the runtime AAR + daemon (jar + runtime classpath)
385+
// into APK assets, mirroring the LogSender AAR flow in AndroidIDEAssetsPlugin. The
386+
// artifacts are extracted to <ANDROIDIDE_HOME>/quickbuild/ at session start
387+
// (QuickBuildArtifactStager).
388+
evaluationDependsOn(":quickbuild:runtime")
389+
evaluationDependsOn(":quickbuild:daemon")
390+
391+
val quickBuildDaemonZip =
392+
tasks.register<Zip>("quickBuildDaemonZip") {
393+
archiveFileName.set("quickbuild-daemon.zip")
394+
destinationDirectory.set(layout.buildDirectory.dir("intermediates/quickbuild"))
395+
val daemonProject = rootProject.project(":quickbuild:daemon")
396+
dependsOn(daemonProject.tasks.named("daemonJar"))
397+
from(daemonProject.tasks.named("daemonJar"))
398+
// The daemon jar's manifest Class-Path names these by file name; they must sit
399+
// next to the jar after extraction.
400+
from(daemonProject.configurations.named("runtimeClasspath"))
401+
// Compose compiler plugin, version-matched to the daemon's compiler; the stable
402+
// name is the contract EnvironmentQuickBuildPaths.composeCompilerPlugin reads.
403+
from(daemonProject.configurations.named("composeCompilerPlugin")) {
404+
rename { "compose-compiler-plugin.jar" }
405+
}
406+
}
407+
408+
androidComponents.onVariants { variant ->
409+
val variantName = variant.name.replaceFirstChar(Char::uppercaseChar)
410+
val flavorName = variant.flavorName!!
411+
412+
val copyRuntimeAar =
413+
tasks.register<AddFileToAssetsTask>("copy${variantName}QuickBuildRuntimeAar") {
414+
val runtimeProject = rootProject.project(":quickbuild:runtime")
415+
dependsOn(
416+
runtimeProject.tasks.named(
417+
"assemble${flavorName.replaceFirstChar(Char::uppercaseChar)}Release",
418+
),
419+
)
420+
inputFile.set(
421+
runtimeProject.layout.buildDirectory.file(
422+
"outputs/aar/quickbuild-runtime-$flavorName-release.aar",
423+
),
424+
)
425+
baseAssetsPath.set("data/common")
426+
// Flavor-agnostic asset name: the runtime AAR is pure Java, both flavors
427+
// produce identical bits, and the stager doesn't need to care.
428+
fileName.set("quickbuild-runtime.aar")
429+
}
430+
variant.sources.assets?.addGeneratedSourceDirectory(
431+
copyRuntimeAar,
432+
AddFileToAssetsTask::outputDirectory,
433+
)
434+
435+
val copyDaemonZip =
436+
tasks.register<AddFileToAssetsTask>("copy${variantName}QuickBuildDaemonZip") {
437+
dependsOn(quickBuildDaemonZip)
438+
inputFile.set(quickBuildDaemonZip.flatMap { it.archiveFile })
439+
baseAssetsPath.set("data/common")
440+
}
441+
variant.sources.assets?.addGeneratedSourceDirectory(
442+
copyDaemonZip,
443+
AddFileToAssetsTask::outputDirectory,
444+
)
445+
}
446+
382447
tasks.register("downloadDocDb") {
383448
doLast {
384449
val githubRepo = "appdevforall/OfflineDocumentationTools"

‎app/src/androidTest/kotlin/com/itsaky/androidide/OrderedTestSuite.kt‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ import org.junit.runners.Suite
55

66
@RunWith(Suite::class)
77
@Suite.SuiteClasses(
8-
CleanupTest::class,
9-
EndToEndTest::class,
8+
CleanupTest::class,
9+
EndToEndTest::class,
10+
QuickBuildSmokeTest::class,
11+
QuickBuildFlagOffTest::class,
1012
)
1113
class OrderedTestSuite
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package com.itsaky.androidide
2+
3+
import androidx.test.core.app.ActivityScenario
4+
import androidx.test.ext.junit.runners.AndroidJUnit4
5+
import androidx.test.platform.app.InstrumentationRegistry
6+
import com.itsaky.androidide.activities.SplashActivity
7+
import com.itsaky.androidide.activities.editor.EditorHandlerActivity
8+
import com.itsaky.androidide.app.configuration.IJdkDistributionProvider
9+
import com.itsaky.androidide.helper.isExperimentsFlagSet
10+
import com.itsaky.androidide.helper.setExperimentsFlagForTest
11+
import com.itsaky.androidide.helper.waitForMainHomeOrEditorUi
12+
import com.itsaky.androidide.screens.QuickBuildScreen.assertQuickBuildButtonAbsent
13+
import com.itsaky.androidide.screens.QuickBuildScreen.assertQuickBuildButtonShown
14+
import com.itsaky.androidide.utils.EditorActivityActions
15+
import com.kaspersky.kaspresso.testcases.api.testcase.TestCase
16+
import org.junit.Test
17+
import org.junit.runner.RunWith
18+
19+
private const val TOOLBAR_TIMEOUT_MS = 15_000L
20+
21+
/**
22+
* The shipping-state gate for Quick Build (ADFA-4128, manual test T13): with no
23+
* `CodeOnTheGo.exp` flag file on the device, the feature must be invisible.
24+
*
25+
* The gate is a single read of [com.itsaky.androidide.utils.FeatureFlags.isExperimentsEnabled]
26+
* at [EditorActivityActions.register], so this drives that decision directly instead of
27+
* restarting the process: flip the flag, re-register, rebuild the toolbar, look. That also
28+
* makes the test honest about what it covers - the registration site, not the process-start
29+
* caching around it.
30+
*
31+
* Both directions run in one test on purpose. An absence assertion alone passes when the
32+
* accessibility selector rots or the toolbar simply never rendered, so the flag-on step
33+
* ahead of it is load-bearing, not decoration.
34+
*
35+
* Runs after [QuickBuildSmokeTest] in [OrderedTestSuite], which leaves the editor open on a
36+
* synced project - this test needs a populated editor toolbar and creates no project of its
37+
* own.
38+
*/
39+
@RunWith(AndroidJUnit4::class)
40+
class QuickBuildFlagOffTest : TestCase() {
41+
private var hadExperimentsFlag = false
42+
43+
@Test
44+
fun test_noExperimentsFlagHidesQuickBuild() =
45+
before {
46+
// A dev device may legitimately have experiments enabled; restore whatever
47+
// state this test found.
48+
hadExperimentsFlag = isExperimentsFlagSet()
49+
IJdkDistributionProvider.getInstance().loadDistributions()
50+
}.after {
51+
setExperimentsFlagForTest(hadExperimentsFlag)
52+
// Leave the toolbar matching the restored flag so a later test does not
53+
// inherit this one's registry.
54+
runCatching { rebuildEditorToolbar() }
55+
}.run {
56+
step("Launch app") {
57+
ActivityScenario.launch(SplashActivity::class.java)
58+
waitForMainHomeOrEditorUi(device.uiDevice)
59+
}
60+
61+
step("Experiments on: the toolbar carries Quick Build") {
62+
setExperimentsFlagForTest(true)
63+
rebuildEditorToolbar()
64+
assertQuickBuildButtonShown(TOOLBAR_TIMEOUT_MS)
65+
}
66+
67+
step("Experiments off: the toolbar drops Quick Build") {
68+
setExperimentsFlagForTest(false)
69+
rebuildEditorToolbar()
70+
assertQuickBuildButtonAbsent(TOOLBAR_TIMEOUT_MS)
71+
}
72+
}
73+
74+
/**
75+
* Re-runs action registration and repopulates the toolbar, which is what an editor
76+
* launch does. On the main thread: both touch the actions registry and the toolbar
77+
* views.
78+
*/
79+
private fun rebuildEditorToolbar() {
80+
val instrumentation = InstrumentationRegistry.getInstrumentation()
81+
val activity = resumedEditorActivity()
82+
instrumentation.runOnMainSync {
83+
EditorActivityActions.register(activity)
84+
activity.prepareOptionsMenu()
85+
}
86+
instrumentation.waitForIdleSync()
87+
}
88+
89+
private fun resumedEditorActivity(): EditorHandlerActivity =
90+
device.activities.getResumed() as? EditorHandlerActivity
91+
?: error("Resumed activity is not the editor; this test needs an open project")
92+
}

0 commit comments

Comments
 (0)