Skip to content

Commit

Permalink
Eliminate sleep from android emulator doc examples (#4277)
Browse files Browse the repository at this point in the history
### Summary

This should fix the android job back to green and has some improvements
on handling the android emulator in the CI (and locally) , part of which
eliminates the need for sleep hack in documentation examples.

Job reference on my fork can be found
[here](https://github.com/vaslabs-ltd/mill/actions/runs/12688329695/job/35364858469)
. ~~Sometimes the device may be stuck in unauthorized, I don't know the
reason yet and can't yet replicate it locally .~~


#### Unauthorized issue

In https://github.com/ReactiveCircus/android-emulator-runner, it seems
that the same issue occurs when caching is used. ~~I'm wondering if
there's any implicit caching that I have missed in the github actions~~

After experimenting with
https://github.com/vaslabs-ltd/test-android-emulator , it seems that the
runner avd directory is polluted. Although I'm not sure why, deleting it
(at least in test-android-emulator) fixes the unauthorized issue . I've
added the same in the github action here

### Improvements

- Removed the need for sleep in doc examples by implementing a new wait
for device boot flag, that's used internally by startAndroidEmulator in
addition to listening for the booted log message. Having both can help
identify potential issues locally or in a CI . `Task.Command(exclusive =
true)` as suggested by @lihaoyi seems to have made it very stable

### Fixes

- Fixed the bundle failing for the manifest/AndroidManifest.xml error
message by flattening the zip file. I am not sure when this broke, but
bundle is not in my radar in the immediate future, so my focus was to
keep it green for now. Bundle build tool also seems to be marked as
experimental, not that it's an issue, just a mention.


### Note

For the wait, I initially had it in a task and used a while loop but
changed my mind and having it outside of task context seems easier to
control the behaviour . I can convert it into a tailrec function if you
prefer that style


EDIT: changed the outcome of unauthorized after some experimentation on
a clean repo
  • Loading branch information
vaslabs authored Jan 9, 2025
1 parent 4954217 commit 7e2cd92
Show file tree
Hide file tree
Showing 5 changed files with 50 additions and 31 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/post-build-selective.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ jobs:
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm
- name: Cleanup any previous avd's to avoid signing key conflicts
if : ${{ inputs.install-android-sdk }}
run: rm -rf /home/runner/.config/.android/avd

- name: Set AVD environment variable globally
if: ${{ inputs.install-android-sdk }}
run: echo "ANDROID_AVD_HOME=/home/runner/.config/.android/avd" >> $GITHUB_ENV
Expand Down
2 changes: 1 addition & 1 deletion example/android/javalib/1-hello-world/build.mill
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ object app extends AndroidAppModule {

> ./mill show app.startAndroidEmulator

> sleep 20 && ./mill show app.adbDevices
> ./mill show app.adbDevices
...emulator-5554...device...

> ./mill show app.it | grep '"OK (1 test)"'
Expand Down
2 changes: 1 addition & 1 deletion example/android/kotlinlib/1-hello-kotlin/build.mill
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ object app extends AndroidAppKotlinModule {

> ./mill show app.startAndroidEmulator

> sleep 20 && ./mill show app.adbDevices
> ./mill show app.adbDevices
...emulator-5556...device...

> ./mill show app.it | grep '"OK (1 test)"'
Expand Down
3 changes: 2 additions & 1 deletion scalalib/src/mill/javalib/android/AndroidAppBundle.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package mill.javalib.android
import mill._
import mill.scalalib._
import mill.api.PathRef
import os.zip.ZipSource

/**
* A Trait for Android App Bundle Creation
Expand Down Expand Up @@ -54,7 +55,7 @@ trait AndroidAppBundle extends AndroidAppModule with JavaModule {
}
}

os.zip(Task.dest / "bundle.zip", Seq(baseDir))
os.zip(Task.dest / "bundle.zip", os.list(baseDir).map(ZipSource.fromPath))

PathRef(Task.dest / "bundle.zip")
}
Expand Down
70 changes: 42 additions & 28 deletions scalalib/src/mill/javalib/android/AndroidAppModule.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package mill.javalib.android
import coursier.Repository
import mill._
import mill.scalalib._
import mill.api.PathRef
import mill.api.{Logger, PathRef}
import mill.define.ModuleRef
import mill.util.Jvm
import os.RelPath
Expand Down Expand Up @@ -810,7 +810,7 @@ trait AndroidAppModule extends JavaModule {
*
* @return The log line that indicates the emulator is ready
*/
def startAndroidEmulator: T[String] = Task {
def startAndroidEmulator(): Command[String] = Task.Command(exclusive = true) {
val ciSettings = Seq(
"-no-snapshot-save",
"-no-window",
Expand All @@ -837,7 +837,7 @@ trait AndroidAppModule extends JavaModule {
command
)

val bootMessage = startEmuCmd.stdout.buffered.lines().filter(l => {
val bootMessage: Option[String] = startEmuCmd.stdout.buffered.lines().filter(l => {
T.log.debug(l.trim())
l.contains("Boot completed in")
}).findFirst().toScala
Expand All @@ -846,7 +846,9 @@ trait AndroidAppModule extends JavaModule {
throw new Exception(s"Emulator failed to start: ${startEmuCmd.exitCode()}")
}

T.log.info(s"Emulator started with message $bootMessage")
val emulator: String = waitForDevice(androidSdkModule().adbPath(), runningEmulator(), T.log)

T.log.info(s"Emulator ${emulator} started with message $bootMessage")

bootMessage.get
}
Expand All @@ -855,30 +857,6 @@ trait AndroidAppModule extends JavaModule {
os.call((androidSdkModule().adbPath().path, "devices", "-l")).out.text()
}

def waitForDevice: Target[String] = Task {
val emulator = runningEmulator()
os.call((
androidSdkModule().adbPath().path,
"-s",
emulator,
"wait-for-device"
))
val bootflag = os.call(
(
androidSdkModule().adbPath().path,
"-s",
emulator,
"shell",
"getprop",
"sys.boot_completed"
)
)

T.log.info(s"$emulator, bootflag is $bootflag")

emulator
}

/**
* Stops the android emulator
*/
Expand Down Expand Up @@ -976,6 +954,42 @@ trait AndroidAppModule extends JavaModule {
relPath.last == "module-info.class"
}

private def waitForDevice(adbPath: PathRef, emulator: String, logger: Logger): String = {
val BootedIndicator = "1"
def getBootflag: String = {
val result = os.call(
(
adbPath.path,
"-s",
emulator,
"shell",
"getprop",
"sys.boot_completed"
),
check = false
)
if (result.exitCode != 0)
"0"
else
result.out.trim()
}

var bootflag = getBootflag
var triesLeft = 25

while (bootflag != BootedIndicator && triesLeft > 0) {
logger.debug(s"Waiting for device to boot. Bootflag: $bootflag . Tries left ${triesLeft}")
Thread.sleep(1000)
triesLeft -= 1
bootflag = getBootflag
}

if (bootflag == BootedIndicator)
emulator
else
throw new Exception("Device failed to boot")
}

trait AndroidAppTests extends JavaTests {
private def testPath = parent.millSourcePath / "src/test"

Expand Down

0 comments on commit 7e2cd92

Please sign in to comment.