mirror of
https://github.com/priyanshujain/sanderling.git
synced 2026-10-02 11:07:10 +00:00
fix(sample-app): make it standalone, no repo_root assumptions (#8)
* fix(cli): fall back to node_modules for @uatu/spec resolution
Drop the hard failure when the uatu source tree is not reachable from
the spec file. Users integrating uatu in their own app have @uatu/spec
installed via npm; esbuild now resolves it from node_modules.
* build(gradle): drop :sample-app include from root settings
The sample now has its own Gradle project in examples/sample-app/android.
* build(sample-app): vendor gradle wrapper
Users running the sample build the APK via ./gradlew from inside the
sample-app's own android/ directory, no repo-root wrapper required.
* build(sample-app): make gradle project standalone
Drop the project(':sdk-android') dependency in favor of the Maven
Central coordinate io.github.priyanshujain:sdk-android. The sample now
owns its settings.gradle.kts and gradle.properties, so it builds
without any pieces of the uatu source tree.
* chore(sample-app): declare @uatu/spec npm dependency
Mirrors what a downstream user would put in their own package.json.
Uses file: for pre-release development; becomes a normal semver pin
once @uatu/spec ships to npm.
* docs(sample-app): rewrite justfile and add README
Justfile drops repo_root; all recipes run against the local gradle
wrapper and uatu from PATH. README is scoped to what a user needs to
run the sample against their own device.
* docs(manual): update sample install steps to standalone layout
./gradlew :sample-app:installDebug no longer exists; the sample owns
its own wrapper under android/.
* feat(sdk): log when Uatu.start succeeds
Silent SDK start makes the "SDK didn't connect" failure mode
impossible to debug. One INFO line at start time is enough.
* fix(sidecar): launch via am start -W instead of monkey
monkey -p <pkg> -c LAUNCHER 1 is unreliable on API 36+: it reports no
error but silently fails to start the activity, so the SDK never runs
and the CLI times out on the SDK-accept handshake.
Resolve the launcher activity via `cmd package resolve-activity
--brief` and launch it with `am start -W -n`. -W makes the call block
until the activity is up, which also makes the subsequent SDK
accept timing deterministic.
* feat(cli): auto-resolve Android device; boot AVD if none connected
--avd becomes optional. Resolution order:
- use any already-connected adb device;
- else if --avd names an existing AVD, boot it and wait for boot;
- else if --avd is missing or names no AVD, error with a clear message.
Falls back to $ANDROID_HOME/emulator/emulator when the binary is not on
PATH, so a standard Android SDK install works without extra shell setup.
* docs(sample-app): AVD is optional; document both paths
just test runs against any connected device. If none, pass AVD=<name>
to have uatu boot the emulator for you.
* feat(cli): auto-discover Android SDK; auto-pick the lone AVD
adb and emulator are looked up via PATH, then $ANDROID_HOME,
$ANDROID_SDK_ROOT, ~/Library/Android/sdk, ~/Android/Sdk, and the
Homebrew cask path. The discovered platform-tools directory is
prepended to the sidecar's PATH so its adb subprocess calls work too.
When --avd isn't passed and no device is connected, the CLI picks the
sole local AVD and boots it. Multiple AVDs → error listing them.
* build(make): add `make install` that go-installs uatu onto PATH
Puts `uatu` into $GOBIN (or $GOPATH/bin) so the sample and any local
dev flow can call it without PATH= prefixes.
* docs(sample-app): zero-config just test; dotenv-load for persistence
Drop the expectation that users prefix commands with PATH=, ANDROID_HOME=,
or AVD=. `just test` now works as-is; optional knobs can be pinned in a
.env file alongside the justfile.
* fix(sample-app): auto-detect ANDROID_HOME for Gradle tasks
The Go CLI finds the SDK itself, but AGP still needs ANDROID_HOME to
resolve `sdk.dir`. The justfile now resolves it from env or canonical
install paths before invoking ./gradlew, so `just install` works out
of the box on a standard Android SDK setup.
* gitignore runs directory for sample app
This commit is contained in:
26 files changed
+1098
-65
No files matched your search
@@ -36,13 +36,47 @@ class StubDriverBackend(private val platform: String) : DriverBackend {
|
||||
if (clearState) {
|
||||
runAdb(listOf("shell", "pm", "clear", bundleId))
|
||||
}
|
||||
if (launcherActivity.isNotEmpty()) {
|
||||
val component = if (launcherActivity.contains('/')) launcherActivity else "$bundleId/$launcherActivity"
|
||||
runAdb(listOf("shell", "am", "start", "-n", component))
|
||||
} else {
|
||||
// `monkey` uses PackageManager.getLaunchIntentForPackage, which
|
||||
// picks the canonical default launcher.
|
||||
runAdb(listOf("shell", "monkey", "-p", bundleId, "-c", "android.intent.category.LAUNCHER", "1"))
|
||||
val component = when {
|
||||
launcherActivity.isEmpty() -> "$bundleId/${resolveLauncherActivity(bundleId)}"
|
||||
launcherActivity.contains('/') -> launcherActivity
|
||||
else -> "$bundleId/$launcherActivity"
|
||||
}
|
||||
runAdb(listOf("shell", "am", "start", "-W", "-n", component))
|
||||
}
|
||||
|
||||
private fun resolveLauncherActivity(bundleId: String): String {
|
||||
val output = captureAdb(
|
||||
listOf(
|
||||
"shell", "cmd", "package", "resolve-activity", "--brief",
|
||||
"-a", "android.intent.action.MAIN",
|
||||
"-c", "android.intent.category.LAUNCHER",
|
||||
bundleId,
|
||||
),
|
||||
)
|
||||
return parseResolvedActivity(bundleId, output)
|
||||
?: throw IllegalStateException("could not resolve launcher activity for $bundleId: $output")
|
||||
}
|
||||
|
||||
private fun captureAdb(arguments: List<String>): String {
|
||||
val process = ProcessBuilder(listOf("adb") + arguments).redirectErrorStream(true).start()
|
||||
val output = process.inputStream.bufferedReader().readText()
|
||||
process.waitFor()
|
||||
return output
|
||||
}
|
||||
|
||||
companion object {
|
||||
// parseResolvedActivity extracts the activity name from the output of
|
||||
// `cmd package resolve-activity --brief`. The brief output is two
|
||||
// lines: metadata, then `<pkg>/<activity>`.
|
||||
internal fun parseResolvedActivity(bundleId: String, output: String): String? {
|
||||
val prefix = "$bundleId/"
|
||||
for (line in output.lines()) {
|
||||
val trimmed = line.trim()
|
||||
if (trimmed.startsWith(prefix)) {
|
||||
return trimmed.removePrefix(prefix)
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,13 @@ class DriverServiceTest {
|
||||
val backend = StubDriverBackend("android")
|
||||
val client = newClient(backend)
|
||||
|
||||
client.launch(LaunchRequest.newBuilder().setBundleId("com.example").setClearState(true).build())
|
||||
client.launch(
|
||||
LaunchRequest.newBuilder()
|
||||
.setBundleId("com.example")
|
||||
.setLauncherActivity(".MainActivity")
|
||||
.setClearState(true)
|
||||
.build(),
|
||||
)
|
||||
assertEquals("com.example", backend.lastBundleId)
|
||||
assertEquals(1, backend.launchCount)
|
||||
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
package dev.uatu.sidecar
|
||||
|
||||
import org.junit.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNull
|
||||
|
||||
class ResolveActivityTest {
|
||||
|
||||
@Test fun extractsActivityFromBriefOutput() {
|
||||
val output = """
|
||||
priority=0 preferredOrder=0 match=0x108000 specificIndex=-1 isDefault=false
|
||||
dev.uatu.sample/.MainActivity
|
||||
""".trimIndent()
|
||||
|
||||
val activity = StubDriverBackend.parseResolvedActivity("dev.uatu.sample", output)
|
||||
assertEquals(".MainActivity", activity)
|
||||
}
|
||||
|
||||
@Test fun extractsFullyQualifiedActivity() {
|
||||
val output = "com.example.app/com.example.app.ui.LaunchActivity"
|
||||
|
||||
val activity = StubDriverBackend.parseResolvedActivity("com.example.app", output)
|
||||
assertEquals("com.example.app.ui.LaunchActivity", activity)
|
||||
}
|
||||
|
||||
@Test fun returnsNullWhenPackageNotFound() {
|
||||
val output = "No activity found"
|
||||
|
||||
val activity = StubDriverBackend.parseResolvedActivity("dev.uatu.sample", output)
|
||||
assertNull(activity)
|
||||
}
|
||||
|
||||
@Test fun doesNotMatchDifferentPackagePrefix() {
|
||||
val output = "other.pkg/.MainActivity"
|
||||
|
||||
val activity = StubDriverBackend.parseResolvedActivity("dev.uatu.sample", output)
|
||||
assertNull(activity)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user