Search code examples
androidgradlegroovyandroid-emulatoradb

Gradle task using adb to install apk not being executed


I am writing a gradle task to install an apk unto emulators before espresso tests are run.

This is the task I have so far.

task installButlerApk {

doLast {
    println "Verifying test-butler installation in emulators"

    final adb = "$android.sdkDirectory.absolutePath/platform-tools/adb"

    final String[] split = ["$adb", "devices", "-l"].execute().text.split("\\r?\\n")

    split.each {

        if (it.isEmpty())
            return;

        println "Emulator: $it"

        final emu = it.split("\\s")[0]
        checks whether the APK is already installed
        if (["$adb", "-s", "$emu", "shell", "pm", "list", "packages"].execute().text.contains(butlerPackage))
           return;

        final installResult = ["$adb", "-s", "$emu", "install", "$butlerApkPath"].execute().text

        if (!installResult.contains("Success"))
            println "Could not install APK. Install output:\n$installResult"

        else
            println "Installed $butlerApkPath in $emu successfully"

    }
}

}

However when I run it via the terminal the task ends up freezing. I am not sure why. I did some research about it and at one point I thought the command that was being passed to ProcessGroovyMethods' execute was failing because it was being passed as a string (execute(String self)) so I then used the array representation of execute (execute(String[] commandArray)) to see if that would work but I am still ending up with the same result so I am just asking for someone who has experience writing these tasks to give me some assistance. So far, I am printing the result of the command and it hasn't shown any errors. It's just stuck at the building process for hours.

Microsoft Windows [Version 6.3.9600]
(c) 2013 Microsoft Corporation. All rights reserved.

C:\Users\Joel\Documents\Projects\Forms>gradlew installButlerApk
Picked up _JAVA_OPTIONS: -XX:ParallelGCThreads=2
To honour the JVM settings for this build a new JVM will be forked. Please consider using the daemon:
https://docs.gradle.org/2.14.1/userguide/gradle_daemon.html.
Incremental java compilation is an incubating feature.                                               
:app:installButlerApk                                                                                
Verifying test-butler installation in emulators
Emulator: List of devices attached   
> Building 0% > :app:installButlerApk

Solution

  • Well, this is expected behavior.

    If you look at your output closely, you see

    Emulator: List of devices attached

    So following your code:

    println "Emulator: $it"
    

    outputs that line I quoted

    final emu = it.split("\\s")[0]
    

    takes the first space separated token which is List

    checks whether the APK is already installed
    

    this will not even compile, but I guess you just forgot the comment characters that you added in the question as explanation

    if (["$adb", "-s", "$emu", "shell", "pm", "list", "packages"].execute().text.contains(butlerPackage))
           return;
    

    Now you execute adb -s List shell pm list
    Executed manually this two times prints error: device not found for me and then exits, so your contains condition is false and the return is not done.

    final installResult = ["$adb", "-s", "$emu", "install", "$butlerApkPath"].execute().text
    

    Now you execute adb -s List install butler.apk
    Executed manually this three times prints out error: device not found, then one time - waiting for device - and then sits there waiting until you cancel it, or a device with serial number List becomes available which of course will never happen and thus your task hangs until you kill it.

    You have to skip the header line when you work through the list of devices, as this is of course not a device.

    Besides this, you can of course use the Groovy standard ways to execute external commands. Yet while in Gradle, I'd rather use the Gradle variants. If you only want to execute one thing it would be a task of type Exec, if you want to execute multiple things like in your case, it is the Project.exec() or Script.exec() methods, so you would do something like

    def output
    new ByteArrayOutputStream().withStream { baos ->
        exec {
            executable android.adbExe
            args "-s", emu, "shell", "pm", "list", "packages"
            standardOutput baos
        }.assertNormalExitValue()
        output = baos.toString()
    }
    if (output.contains(butlerPackage)) {
        return
    }
    
    exec {
        executable android.adbExe
        args "-s", emu, "install", butlerApkPath
    }.assertNormalExitValue()