Skip to content

Commit f0ad7a4

Browse files
committed
JENKINS-216 Adds a gradle plugin to manage android emulators.
This plugin allows to create/start/stop emulators. Furthermore it can handle dependencies of the emulator like the Android SDK and the NDK. Originally the source was part of the Paintroid project.
1 parent e7f7ec1 commit f0ad7a4

29 files changed

+2344
-1
lines changed

README.md

+121-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,122 @@
11
# Gradle
2-
Dev repo for gradle plugins
2+
Repository for Gradle plugins used by the Catrobat Project.
3+
4+
Contains the following plugins:
5+
* android-emulators-gradle allows to create, start, and stop emulators via gradle.
6+
The emulators themselves as well as their dependencies like Android SDK and Android NDK
7+
can be installed automatically.
8+
9+
## android-emulators-gradle Plugin
10+
This plugin allows to manage Android emulators via gradle:
11+
* Creating Android images.
12+
* Starting an Android Emulator.
13+
* Turning off animations.
14+
* Stopping an emulator.
15+
* Automatically retrieves the logcat file.
16+
* Android SDK can be installed automatically.
17+
* Android NDK can be installed automatically.
18+
* The necessary Android images as well as the emulator can be installed automatically.
19+
* Emulator templates avoid redundancies in your emulator specification.
20+
21+
### Gradle Commands
22+
| Command | Description |
23+
| --- | --- |
24+
| `./gradlew startEmulator [-Pemulator=EMULATOR_NAME] [-PlogcatFile=LOGCAT_NAME]` | Creates the emulator if necessary and the starts it. If you configured multiple emulators you need to select which via `-Pemulator`. The logcat file is automatically stored as logcat.txt. |
25+
| `./gradlew stopEmulator` | Stops the first emulator it finds. Running multiple emulators at the same time is not supported. |
26+
| `./gradlew adbDisableAnimationsGlobally` | Turns-off animations of the first running emulator it finds. |
27+
28+
### Basic Emulator Managment
29+
30+
Place the following lines in your build.gradle file.
31+
```
32+
plugins {
33+
id "org.catrobat.gradle.androidemulators" version "0.1"
34+
}
35+
36+
// Place this at the very top.
37+
// This ensures that the dependencies (if you install them) are present
38+
// when other plugins try to access them.
39+
emulators {
40+
// Whether to install the dependencies or not.
41+
// The dependencies are installed during the configuration step.
42+
// As a result you should not always install them, to not slow down configuration.
43+
// The install function takes a boolean whether to install the dependencies or not.
44+
install project.hasProperty('installSdk')
45+
46+
dependencies {
47+
sdk() // install the most recent Android SDK known by the plugin.
48+
}
49+
50+
// Name the emulator you want to create, here it is called android24
51+
emulator 'android24', {
52+
avd {
53+
systemImage = 'system-images;android-24;default;x86_64'
54+
sdcardSizeMb = 200
55+
hardwareProperties += ['hw.ramSize': 800, 'vm.heapSize': 128]
56+
screenDensity = 'xhdpi' // the plugin automatically maps xhdpi to the correct screen density
57+
}
58+
59+
// Paramters that are used to start the emulator.
60+
// Some sensible defaults are provided automatically, see EmulatorStarter.groovy
61+
parameters {
62+
resolution = '768x1280'
63+
language = 'en'
64+
country = 'US'
65+
}
66+
}
67+
}
68+
```
69+
70+
Calling `./gradlew -PinstallSdk` in your project will install the dependencies automatically.
71+
Afterwards you can create and start the emulator with `./gradlew startEmulator adbDisableAnimationsGlobally`
72+
with animations disabled.
73+
Finally you can stop the emulator with `./gradlew stopEmulator`.
74+
75+
### Multiple Emulators and Templates
76+
When you need multiple emulators you often and up with a lot duplication between their configuration.
77+
Templates can help here.
78+
79+
```
80+
emulators {
81+
install project.hasProperty('installSdk')
82+
83+
dependencies {
84+
sdk()
85+
}
86+
87+
// Specify a template like any other emulator.
88+
emulatorTemplate 'englishTemplate', {
89+
avd {
90+
sdcardSizeMb = 200
91+
hardwareProperties += ['hw.ramSize': 800, 'vm.heapSize': 128]
92+
screenDensity = 'xhdpi'
93+
}
94+
95+
parameters {
96+
resolution = '768x1280'
97+
language = 'en'
98+
country = 'US'
99+
}
100+
}
101+
102+
// Now reference the template as second parameter.
103+
emulator 'android24', 'englishTemplate', {
104+
// You only need to provide what you want to override.
105+
avd {
106+
systemImage = 'system-images;android-24;default;x86_64'
107+
}
108+
}
109+
110+
emulator 'android25', 'englishTemplate', {
111+
avd {
112+
systemImage = 'system-images;android-25;default;x86_64'
113+
}
114+
}
115+
}
116+
```
117+
118+
When you have configured multiple emulators you need to specify which emulator to start, for example,
119+
`./gradlew -Pemulator=android24 startEmulator`.
120+
121+
### Known Shortcomings
122+
* Only one emulator can be started at the same time.

android-emulators-gradle/build.gradle

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
* Android Emulators Plugin: A gradle plugin to manage Android emulators.
3+
* Copyright (C) 2018 The Catrobat Team
4+
* (<http://developer.catrobat.org/credits>)
5+
*
6+
* This program is free software: you can redistribute it and/or modify
7+
* it under the terms of the GNU Affero General Public License as
8+
* published by the Free Software Foundation, either version 3 of the
9+
* License, or (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
* GNU Affero General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Affero General Public License
17+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
18+
*/
19+
plugins {
20+
id 'java-gradle-plugin'
21+
id 'com.gradle.plugin-publish' version '0.10.0'
22+
id 'groovy'
23+
}
24+
25+
dependencies {
26+
compile gradleApi()
27+
compile localGroovy()
28+
}
29+
30+
group 'org.catrobat.gradle.androidemulators'
31+
version '0.1'
32+
33+
gradlePlugin {
34+
plugins {
35+
androidEmulatorsPlugin {
36+
id = 'org.catrobat.gradle.androidemulators'
37+
implementationClass = 'org.catrobat.gradle.androidemulators.AndroidEmulatorsPlugin'
38+
}
39+
}
40+
}
41+
42+
pluginBundle {
43+
website = 'https://github.com/mfuchs/android-emulators-gradle'
44+
vcsUrl = 'https://github.com/mfuchs/android-emulators-gradle'
45+
tags = ['android', 'android emulator', 'android sdk', 'android ndk']
46+
47+
plugins {
48+
androidEmulatorsPlugin {
49+
displayName = 'Android Emulators Plugin'
50+
description = 'Plugin that manages the installation and management of Android Emulators.'
51+
}
52+
}
53+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/*
2+
* Android Emulators Plugin: A gradle plugin to manage Android emulators.
3+
* Copyright (C) 2018 The Catrobat Team
4+
* (<http://developer.catrobat.org/credits>)
5+
*
6+
* This program is free software: you can redistribute it and/or modify
7+
* it under the terms of the GNU Affero General Public License as
8+
* published by the Free Software Foundation, either version 3 of the
9+
* License, or (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
* GNU Affero General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Affero General Public License
17+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
18+
*/
19+
20+
package org.catrobat.gradle.androidemulators
21+
22+
import groovy.transform.TypeChecked
23+
24+
@TypeChecked
25+
class Adb {
26+
File adbExe
27+
28+
Adb(File adbExe) {
29+
this.adbExe = adbExe
30+
}
31+
32+
CommandBuilder command() {
33+
new CommandBuilder(adbExe)
34+
}
35+
36+
List<String> getAndroidDevices() {
37+
List<String> deviceIds = []
38+
command().addArguments(['devices']).execute().eachLine { line ->
39+
line = line.trim()
40+
def i = line.indexOf('\tdevice')
41+
if (i > 0) {
42+
deviceIds << line.substring(0, i)
43+
}
44+
}
45+
deviceIds
46+
}
47+
48+
String getAndroidSerial() {
49+
def availableDevices = getAndroidDevices()
50+
def androidSerial = System.getenv('ANDROID_SERIAL')
51+
52+
if (androidSerial?.trim() && !availableDevices.contains(androidSerial)) {
53+
throw new DeviceNotFoundException("Device ${androidSerial} not found")
54+
} else if (availableDevices.size() == 0) {
55+
throw new NoDeviceException("No connected devices!")
56+
} else {
57+
androidSerial = availableDevices.first()
58+
}
59+
60+
return androidSerial.toString().trim()
61+
}
62+
63+
String waitForSerial(int timeout=60) {
64+
for (int i = 0; i < timeout; ++i) {
65+
try {
66+
return getAndroidSerial()
67+
} catch (NoDeviceException) {
68+
sleep(1000)
69+
}
70+
}
71+
return getAndroidSerial()
72+
}
73+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
/*
2+
* Android Emulators Plugin: A gradle plugin to manage Android emulators.
3+
* Copyright (C) 2018 The Catrobat Team
4+
* (<http://developer.catrobat.org/credits>)
5+
*
6+
* This program is free software: you can redistribute it and/or modify
7+
* it under the terms of the GNU Affero General Public License as
8+
* published by the Free Software Foundation, either version 3 of the
9+
* License, or (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
* GNU Affero General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Affero General Public License
17+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
18+
*/
19+
20+
package org.catrobat.gradle.androidemulators
21+
22+
import groovy.transform.TypeChecked
23+
24+
@TypeChecked
25+
class AndroidDevice {
26+
Adb adb
27+
String androidSerial
28+
29+
AndroidDevice(Adb adb, String androidSerial = null) {
30+
this.adb = adb
31+
this.androidSerial = androidSerial ?: adb.getAndroidSerial()
32+
}
33+
34+
/**
35+
* @return A command builder with the given command and using the serial to run on the device
36+
*/
37+
CommandBuilder command(List additionalParameters) {
38+
adb.command().addArguments(['-s', androidSerial]).addArguments(additionalParameters)
39+
}
40+
41+
void setGlobalSetting(String setting_name, def value) {
42+
command(['shell', 'settings', 'put', 'global', setting_name, value.toString()]).verbose().execute()
43+
}
44+
45+
void deleteGlobalSetting(String setting_name) {
46+
command(['shell', 'settings', 'delete', 'global', setting_name]).verbose().execute()
47+
}
48+
49+
void writeLogcat(File logcat) {
50+
if (!stillRunning()) {
51+
println("WARNING: Cannot retrieve logcat from '$androidSerial'.")
52+
return
53+
}
54+
logcat.withOutputStream { os ->
55+
command(['logcat', '-d']).executeAsynchronously().waitForProcessOutput(os, os)
56+
}
57+
}
58+
59+
void waitForBooted() {
60+
println("Waiting for device $androidSerial to be booted.")
61+
for (int i = 0; i < 60; ++i) {
62+
def result = command(['shell', 'getprop', 'sys.boot_completed']).execute().trim()
63+
if (result == "1") {
64+
println("Deivce $androidSerial booted successfully.")
65+
return
66+
}
67+
68+
sleep(1000)
69+
}
70+
throw new BootIncompleteException("The boot of device $androidSerial did not complete.")
71+
}
72+
73+
void waitForStopped() {
74+
println("Wait for device $androidSerial to stop.")
75+
for (int i = 0; i < 30; ++i) {
76+
if (!stillRunning()) {
77+
// device could not be found, thus it is stopped
78+
return
79+
}
80+
sleep(1000)
81+
}
82+
83+
// If it turns out that the emulator fails to be killed often consider killing
84+
// the emulator via OS commands.
85+
// Similar to what was done in buildScripts/
86+
throw new ResourceException("Failed to stop device $androidSerial.")
87+
}
88+
89+
void install(File apk) {
90+
println("Installing '$apk'")
91+
command(['install', apk]).verbose().execute()
92+
}
93+
94+
void disableAnimationsGlobally() {
95+
println("Disabling animations for device $androidSerial.")
96+
setGlobalSetting('window_animation_scale', '0.0')
97+
setGlobalSetting('transition_animation_scale', '0.0')
98+
setGlobalSetting('animator_duration_scale', '0.0',)
99+
}
100+
101+
void resetAnimationsGlobally() {
102+
println("Resetting animations for device $androidSerial.")
103+
deleteGlobalSetting('window_animation_scale')
104+
deleteGlobalSetting('transition_animation_scale')
105+
deleteGlobalSetting('animator_duration_scale')
106+
}
107+
108+
private boolean stillRunning() {
109+
adb.getAndroidDevices().contains(androidSerial)
110+
}
111+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Android Emulators Plugin: A gradle plugin to manage Android emulators.
3+
* Copyright (C) 2018 The Catrobat Team
4+
* (<http://developer.catrobat.org/credits>)
5+
*
6+
* This program is free software: you can redistribute it and/or modify
7+
* it under the terms of the GNU Affero General Public License as
8+
* published by the Free Software Foundation, either version 3 of the
9+
* License, or (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
* GNU Affero General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Affero General Public License
17+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
18+
*/
19+
20+
package org.catrobat.gradle.androidemulators
21+
22+
import org.gradle.api.Plugin
23+
import org.gradle.api.Project
24+
25+
class AndroidEmulatorsPlugin implements Plugin<Project> {
26+
27+
@Override
28+
void apply(Project project) {
29+
def extension = project.extensions.create('emulators', EmulatorsPluginExtension)
30+
new EmulatorsPluginTasks(project, extension).registerTasks()
31+
}
32+
}

0 commit comments

Comments
 (0)