Skip to content

Commit

Permalink
Fix Android Emulator not running in some environments
Browse files Browse the repository at this point in the history
* Fixed by adding ANDROID_HOME environment variable to commands
* Added new logEmulatorOutput flag to extension for easier debugging
  • Loading branch information
quittle committed Nov 5, 2019
1 parent 80e3405 commit b4ddd2a
Show file tree
Hide file tree
Showing 7 changed files with 71 additions and 9 deletions.
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ jobs:
- script: ./validate_plugin 3
- script: ./validate_plugin 4
- script: ./validate_plugin 5
- script: ./validate_plugin 6
- stage: Deploy
script: skip
deploy:
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ androidEmulator {
enableForAndroidTests false // Defaults to true
avdRoot '~/.android/avd' // Defaults to be <gradle-build-dir>/android-avd-root
headless true // Defaults to false but should be set to true for most CI systems
logEmulatorOutput true // Defaults to false but can be enabled to have emulator output logged for debugging.
}
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ public void includeGoogleApis(final boolean includeGoogleApis) {
private File avdRoot = null;
private boolean enableForAndroidTests = true;
private boolean headless = false;
private boolean logEmulatorOutput = false;

public EmulatorExtension getEmulator() {
return this.emulator;
Expand Down Expand Up @@ -112,4 +113,16 @@ public void setHeadless(final boolean headless) {
public boolean getHeadless() {
return this.headless;
}

public void logEmulatorOutput(final boolean logEmulatorOutput) {
this.logEmulatorOutput = logEmulatorOutput;
}

public void setLogEmulatorOutput(final boolean logEmulatorOutput) {
this.logEmulatorOutput = logEmulatorOutput;
}

public boolean getLogEmulatorOutput() {
return this.logEmulatorOutput;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,17 @@
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.UnaryOperator;
import org.apache.commons.io.IOUtils;

import com.android.build.gradle.internal.tasks.DeviceProviderInstrumentTestTask;
import org.gradle.api.Action;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.logging.Logger;
import org.gradle.api.Task;
import org.gradle.api.tasks.AbstractExecTask;
import org.gradle.api.tasks.Exec;
Expand Down Expand Up @@ -137,6 +140,8 @@ private static void createCreateEmulatorTask(final Project project, final Emulat
}

private static void createStartStopEmulatorTasks(final Project project, final EmulatorConfiguration emulatorConfiguration) {
final boolean logEmulatorOutput = emulatorConfiguration.getLogEmulatorOutput();

final List<String> command = new ArrayList<>();
command.add(emulatorConfiguration.getEmulator().getAbsolutePath());
command.add("@" + emulatorConfiguration.getEmulatorName());
Expand All @@ -148,15 +153,21 @@ private static void createStartStopEmulatorTasks(final Project project, final Em
}
final ProcessBuilder pb = new ProcessBuilder(command.toArray(new String[0]));
pb.environment().putAll(emulatorConfiguration.getEnvironmentVariableMap());
pb.inheritIO();
if (!logEmulatorOutput) {
pb.inheritIO();
}

final AtomicReference<Process> process = new AtomicReference<>();

project.getTasks().create(START_ANDROID_EMULATOR_TASK_NAME, task -> {
task.doFirst(t -> {
project.getLogger().debug("Starting emulator with command {} {}", pb.environment(), pb.command());
try {
process.set(pb.start());
final Process directProcess = pb.start();
process.set(directProcess);
if (logEmulatorOutput) {
logOutput(directProcess, project.getLogger());
}
Runtime.getRuntime().addShutdownHook(new Thread(() -> process.getAndUpdate(DESTROY_AND_REPLACE_WITH_NULL)));
} catch (final IOException e) {
throw new RuntimeException("Emulator failed to start successfully", e);
Expand All @@ -175,11 +186,34 @@ private static void createStartStopEmulatorTasks(final Project project, final Em
});
}

/**
* Logs emulator output via new threads.
* @param process The process to log the output of
* @param logger The logger to report output with
*/
private static void logOutput(final Process process, final Logger logger) {
final InputStream stdout = process.getInputStream();
final InputStream stderr = process.getErrorStream();
new Thread(() -> {
try {
IOUtils.lineIterator(stdout, StandardCharsets.UTF_8).forEachRemaining(s -> logger.info("[Android Emulator - STDOUT] " + s));
} catch (IOException e) {
logger.error("Error reading Android emulator stdout", e);
}
}).start();
new Thread(() -> {
try {
IOUtils.lineIterator(stderr, StandardCharsets.UTF_8).forEachRemaining(s -> logger.info("[Android Emulator - STDERR] " + s));
} catch (IOException e) {
logger.error("Error reading Android emulator stderr", e);
}
}).start();
}

private static void createWaitForEmulatorTask(final Project project, final EmulatorConfiguration emulatorConfiguration) {
createExecTask(project, emulatorConfiguration, WAIT_FOR_ANDROID_EMULATOR_TASK_NAME, exec -> {
exec.setExecutable(emulatorConfiguration.getAdb());
exec.setArgs(l("wait-for-device", "shell", "while $(exit $(getprop sys.boot_completed)) ; do sleep 1; done;"));
// exec.setArgs(l("wait-for-device", "shell", "sleep 1000000; while [ -z $(getprop sys.boot_completed) ]; do sleep 1; done;"));

exec.dependsOn(ENSURE_ANDROID_EMULATOR_PERMISSIONS_TASK_NAME, START_ANDROID_EMULATOR_TASK_NAME);
});
Expand Down Expand Up @@ -209,12 +243,9 @@ private static Task createExecTask(final Project project,
}

@SafeVarargs
@SuppressWarnings("varargs")
private static <T> List<T> l(T... arr) {
final List<T> ret = new ArrayList<>();
for (final T v : arr) {
ret.add(v);
}
return ret;
return Arrays.asList(arr);
}

private static InputStream buildStandardInLines(String... lines) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ class EmulatorConfiguration {
private final Map<String, String> environmentVariableMap;
private final boolean enableForAndroidTests;
private final boolean headless;
private final boolean logEmulatorOutput;
private final String androidVersion;
private final String flavor;
private final String abi;
Expand All @@ -34,14 +35,16 @@ class EmulatorConfiguration {
if (androidEmulatorExtension.getAvdRoot() != null) {
this.avdRoot = androidEmulatorExtension.getAvdRoot();
} else {
this. avdRoot = new File(project.getBuildDir(), "android-avd-root");
this.avdRoot = new File(project.getBuildDir(), "android-avd-root");
}

this.environmentVariableMap = ImmutableMap.of(
"ANDROID_SDK_ROOT", sdkRoot.getAbsolutePath(),
"ANDROID_HOME", sdkRoot.getAbsolutePath(),
"ANDROID_AVD_HOME", avdRoot.getAbsolutePath());
this.enableForAndroidTests = androidEmulatorExtension.getEnableForAndroidTests();
this.headless = androidEmulatorExtension.getHeadless();
this.logEmulatorOutput = androidEmulatorExtension.getLogEmulatorOutput();

final AndroidEmulatorExtension.EmulatorExtension emulator = androidEmulatorExtension.getEmulator();
int sdkVersion = emulator.getSdkVersion();
Expand Down Expand Up @@ -109,6 +112,10 @@ boolean getHeadless() {
return headless;
}

boolean getLogEmulatorOutput() {
return logEmulatorOutput;
}

String getAndroidVersion() {
return androidVersion;
}
Expand Down
4 changes: 4 additions & 0 deletions example-android-project/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ buildscript {
project.ext.ANDROID_EMULATOR_GOOGLE_APIS = Boolean.valueOf(env.getOrDefault('ANDROID_EMULATOR_GOOGLE_APIS', 'false'))
project.ext.ANDROID_EMULATOR_ABI = env.getOrDefault('ANDROID_EMULATOR_ABI', 'armeabi-v7a')
project.ext.ENABLE_FOR_ANDROID_TESTS = Boolean.valueOf(env.getOrDefault('ENABLE_FOR_ANDROID_TESTS', 'true'))
project.ext.LOG_ANDROID_EMULATOR = Boolean.valueOf(env.getOrDefault('LOG_ANDROID_EMULATOR', 'false'))

repositories {
mavenLocal()
Expand Down Expand Up @@ -79,4 +80,7 @@ androidEmulator {

headless true
headless = true

logEmulatorOutput LOG_ANDROID_EMULATOR
logEmulatorOutput = LOG_ANDROID_EMULATOR
}
5 changes: 5 additions & 0 deletions validate_plugin
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,8 @@ fi
if should_run_target 5; then
ANDROID_GRADLE_VERSION=3.4.0 ANDROID_GRADLE_VERSION_NEW=true ANDROID_EMULATOR_GOOGLE_APIS=true ANDROID_EMULATOR_SDK_VERSION=24 ./gradlew -p example-android-project connectedCheck
fi

# Use the logEmulatorOutput flag
if should_run_target 6; then
LOG_ANDROID_EMULATOR=true ANDROID_EMULATOR_SDK_VERSION=24 ANDROID_EMULATOR_GOOGLE_APIS=true ./gradlew -p example-android-project connectedCheck
fi

0 comments on commit b4ddd2a

Please sign in to comment.