Skip to content

Commit

Permalink
[JENKINS-11973] Option for emulators in $WORKSPACE
Browse files Browse the repository at this point in the history
This adds a global option that essentially does
ANDROID_SDK_HOME=$WORKSPACE, causing emulators to be put into
$WORKSPACE/.android.

This allows you to run concurrent jobs on the same slave, and to run the
same emulator config from two different jobs on the same slave. It also
makes the emulators more isolated and allows you to clean-slate the
emulators by wiping out the workspace.
  • Loading branch information
jorgenpt committed Jul 11, 2012
1 parent 7a3d75b commit 430606d
Show file tree
Hide file tree
Showing 12 changed files with 111 additions and 71 deletions.
1 change: 1 addition & 0 deletions .gitignore
@@ -1,2 +1,3 @@
target
work
.DS_Store
15 changes: 11 additions & 4 deletions src/main/java/hudson/plugins/android_emulator/AndroidEmulator.java
Expand Up @@ -206,18 +206,19 @@ public Environment setUp(AbstractBuild build, final Launcher launcher, BuildList

// Build emulator config, ensuring that variables expand to valid SDK values
EmulatorConfig emuConfig;
final String androidSdkHome = (envVars != null && descriptor.shouldKeepInWorkspace ? envVars.get("WORKSPACE") : null);
try {
emuConfig = EmulatorConfig.create(avdName, osVersion, screenDensity,
screenResolution, deviceLocale, sdCardSize, wipeData, showWindow, useSnapshots,
commandLineOptions, targetAbi);
commandLineOptions, targetAbi, androidSdkHome);
} catch (IllegalArgumentException e) {
log(logger, Messages.EMULATOR_CONFIGURATION_BAD(e.getLocalizedMessage()));
build.setResult(Result.NOT_BUILT);
return null;
}

// Confirm that the required SDK tools are available
AndroidSdk androidSdk = Utils.getAndroidSdk(launcher, androidHome);
AndroidSdk androidSdk = Utils.getAndroidSdk(launcher, androidHome, androidSdkHome);
if (androidSdk == null) {
if (!descriptor.shouldInstallSdk) {
// Couldn't find an SDK, don't want to install it, give up
Expand All @@ -229,12 +230,14 @@ public Environment setUp(AbstractBuild build, final Launcher launcher, BuildList
// Ok, let's download and install the SDK
log(logger, Messages.INSTALLING_SDK());
try {
androidSdk = SdkInstaller.install(launcher, listener);
androidSdk = SdkInstaller.install(launcher, listener, androidSdkHome);
} catch (SdkInstallationException e) {
log(logger, Messages.SDK_INSTALLATION_FAILED(), e);
build.setResult(Result.NOT_BUILT);
return null;
}
} else if (descriptor.shouldKeepInWorkspace) {
SdkInstaller.optOutOfSdkStatistics(launcher, listener, androidSdkHome);
}

// Install the required SDK components for the desired platform, if necessary
Expand Down Expand Up @@ -745,6 +748,8 @@ public static final class DescriptorImpl extends BuildWrapperDescriptor implemen

/** Whether the SDK should be automatically installed where it's not found. */
public boolean shouldInstallSdk = true;
/** Whether the emulators should be kept in the workspace. */
public boolean shouldKeepInWorkspace = false;

public DescriptorImpl() {
super(AndroidEmulator.class);
Expand All @@ -760,6 +765,7 @@ public String getDisplayName() {
public boolean configure(StaplerRequest req, JSONObject json) throws FormException {
androidHome = json.optString("androidHome");
shouldInstallSdk = json.optBoolean("shouldInstallSdk", true);
shouldKeepInWorkspace = json.optBoolean("shouldKeepInWorkspace", false);
save();
return true;
}
Expand Down Expand Up @@ -808,7 +814,8 @@ public BuildWrapper newInstance(StaplerRequest req, JSONObject formData) throws

return new AndroidEmulator(avdName, osVersion, screenDensity, screenResolution,
deviceLocale, sdCardSize, hardware.toArray(new HardwareProperty[0]), wipeData,
showWindow, useSnapshots, deleteAfterBuild, startupDelay, commandLineOptions, targetAbi);
showWindow, useSnapshots, deleteAfterBuild, startupDelay, commandLineOptions,
targetAbi);
}

@Override
Expand Down
Expand Up @@ -107,6 +107,9 @@ public ProcStarter getProcStarter() throws IOException,
InterruptedException {
final EnvVars buildEnvironment = build.getEnvironment(TaskListener.NULL);
buildEnvironment.put("ANDROID_ADB_SERVER_PORT", Integer.toString(adbServerPort));
if (sdk.hasKnownHome()) {
buildEnvironment.put("ANDROID_SDK_HOME", sdk.getSdkHome());
}
return launcher.launch().stdout(new NullStream()).stderr(logger()).envs(buildEnvironment);
}

Expand Down
35 changes: 25 additions & 10 deletions src/main/java/hudson/plugins/android_emulator/EmulatorConfig.java
Expand Up @@ -40,19 +40,26 @@ class EmulatorConfig implements Serializable {
private final boolean showWindow;
private final boolean useSnapshots;
private final String commandLineOptions;
private final String androidSdkHome;

public EmulatorConfig(String avdName, boolean wipeData, boolean showWindow,
boolean useSnapshots, String commandLineOptions) {
this(avdName, wipeData, showWindow, useSnapshots, commandLineOptions, null);
}

public EmulatorConfig(String avdName, boolean wipeData, boolean showWindow,
boolean useSnapshots, String commandLineOptions, String androidSdkHome) {
this.avdName = avdName;
this.wipeData = wipeData;
this.showWindow = showWindow;
this.useSnapshots = useSnapshots;
this.commandLineOptions = commandLineOptions;
this.androidSdkHome = androidSdkHome;
}

public EmulatorConfig(String osVersion, String screenDensity, String screenResolution,
String deviceLocale, String sdCardSize, boolean wipeData, boolean showWindow,
boolean useSnapshots, String commandLineOptions, String targetAbi)
boolean useSnapshots, String commandLineOptions, String targetAbi, String androidSdkHome)
throws IllegalArgumentException {
if (osVersion == null || screenDensity == null || screenResolution == null) {
throw new IllegalArgumentException("Valid OS version and screen properties must be supplied.");
Expand Down Expand Up @@ -99,24 +106,26 @@ public EmulatorConfig(String osVersion, String screenDensity, String screenResol
this.useSnapshots = useSnapshots;
this.commandLineOptions = commandLineOptions;
this.targetAbi = targetAbi;
this.androidSdkHome = androidSdkHome;
}

public static final EmulatorConfig create(String avdName, String osVersion, String screenDensity,
String screenResolution, String deviceLocale, String sdCardSize, boolean wipeData,
boolean showWindow, boolean useSnapshots, String commandLineOptions, String targetAbi) {
boolean showWindow, boolean useSnapshots, String commandLineOptions, String targetAbi,
String androidSdkHome) {
if (Util.fixEmptyAndTrim(avdName) == null) {
return new EmulatorConfig(osVersion, screenDensity, screenResolution, deviceLocale,
sdCardSize, wipeData, showWindow, useSnapshots, commandLineOptions, targetAbi);
sdCardSize, wipeData, showWindow, useSnapshots, commandLineOptions, targetAbi, androidSdkHome);
}

return new EmulatorConfig(avdName, wipeData, showWindow, useSnapshots, commandLineOptions);
return new EmulatorConfig(avdName, wipeData, showWindow, useSnapshots, commandLineOptions, androidSdkHome);
}

public static final String getAvdName(String avdName, String osVersion, String screenDensity,
String screenResolution, String deviceLocale) {
try {
return create(avdName, osVersion, screenDensity, screenResolution, deviceLocale, null,
false, false, false, null, null).getAvdName();
false, false, false, null, null, null).getAvdName();
} catch (IllegalArgumentException e) {}
return null;
}
Expand Down Expand Up @@ -235,8 +244,8 @@ private File getAvdDirectory(final File homeDir) {
}

public File getAvdMetadataFile(boolean isUnix) {
final File homeDir = Utils.getHomeDirectory(isUnix);
return new File(getAvdHome(homeDir), getAvdName() +".ini");
final File homeDir = Utils.getHomeDirectory(androidSdkHome, isUnix);
return new File(getAvdHome(homeDir), getAvdName() + ".ini");
}

private File getAvdConfigFile(File homeDir) {
Expand Down Expand Up @@ -358,7 +367,7 @@ public Boolean call() throws AndroidEmulatorException {
logger = listener.getLogger();
}

final File homeDir = Utils.getHomeDirectory(isUnix);
final File homeDir = Utils.getHomeDirectory(androidSdk.getSdkHome(), isUnix);
final File avdDirectory = getAvdDirectory(homeDir);
final boolean emulatorExists = getAvdConfigFile(homeDir).exists();

Expand Down Expand Up @@ -490,6 +499,9 @@ public Boolean call() throws AndroidEmulatorException {
final Process process;
try {
ProcessBuilder procBuilder = new ProcessBuilder(builder.toList());
if (androidSdk.hasKnownHome()) {
procBuilder.environment().put("ANDROID_SDK_HOME", androidSdk.getSdkHome());
}
process = procBuilder.start();
} catch (IOException ex) {
throw new EmulatorCreationException(Messages.AVD_CREATION_FAILED());
Expand Down Expand Up @@ -582,6 +594,9 @@ private boolean createSdCard(File homeDir) {
// Run!
try {
ProcessBuilder procBuilder = new ProcessBuilder(builder.toList());
if (androidSdkHome != null) {
procBuilder.environment().put("ANDROID_SDK_HOME", androidSdkHome);
}
procBuilder.start().waitFor();
} catch (InterruptedException ex) {
return false;
Expand Down Expand Up @@ -618,7 +633,7 @@ public Void call() throws IOException {
logger = listener.getLogger();
}

final File homeDir = Utils.getHomeDirectory(isUnix);
final File homeDir = Utils.getHomeDirectory(androidSdkHome, isUnix);

// Parse the AVD's config
Map<String, String> configValues;
Expand Down Expand Up @@ -658,7 +673,7 @@ public Boolean call() throws Exception {
}

// Check whether the AVD exists
final File homeDir = Utils.getHomeDirectory(isUnix);
final File homeDir = Utils.getHomeDirectory(androidSdkHome, isUnix);
final File avdDirectory = getAvdDirectory(homeDir);
final boolean emulatorExists = avdDirectory.exists();
if (!emulatorExists) {
Expand Down
22 changes: 12 additions & 10 deletions src/main/java/hudson/plugins/android_emulator/SdkInstaller.java
Expand Up @@ -53,17 +53,17 @@ public class SdkInstaller {
*
* @return An {@code AndroidSdk} object for the newly-installed SDK.
*/
public static AndroidSdk install(Launcher launcher, BuildListener listener)
public static AndroidSdk install(Launcher launcher, BuildListener listener, String androidSdkHome)
throws SdkInstallationException, IOException, InterruptedException {
Semaphore semaphore = acquireLock();
try {
return doInstall(launcher, listener);
return doInstall(launcher, listener, androidSdkHome);
} finally {
semaphore.release();
}
}

private static AndroidSdk doInstall(Launcher launcher, BuildListener listener)
private static AndroidSdk doInstall(Launcher launcher, BuildListener listener, String androidSdkHome)
throws SdkInstallationException, IOException, InterruptedException {
// We should install the SDK on the current build machine
Node node = Computer.currentComputer().getNode();
Expand All @@ -82,19 +82,19 @@ private static AndroidSdk doInstall(Launcher launcher, BuildListener listener)
if (!isSdkInstallComplete(node, androidHome)) {
PrintStream logger = listener.getLogger();
log(logger, Messages.INSTALLING_REQUIRED_COMPONENTS());
AndroidSdk sdk = new AndroidSdk(androidHome);
AndroidSdk sdk = new AndroidSdk(androidHome, androidSdkHome);
installComponent(logger, launcher, sdk, "platform-tool", "tool");

// If we made it this far, confirm completion by writing our our metadata file
getInstallationInfoFilename(node).write(String.valueOf(SDK_VERSION), "UTF-8");

// As this SDK will not be used manually, opt out of the stats gathering;
// this also prevents the opt-in dialog from popping up during execution
optOutOfSdkStatistics(launcher, listener);
optOutOfSdkStatistics(launcher, listener, androidSdkHome);
}

// Create an SDK object now that all the components exist
return Utils.getAndroidSdk(launcher, androidHome);
return Utils.getAndroidSdk(launcher, androidHome, androidSdkHome);
}

/**
Expand Down Expand Up @@ -341,8 +341,8 @@ public String call() throws IOException {
* @param launcher Used for running tasks on the remote node.
* @param listener Used to access logger.
*/
private static void optOutOfSdkStatistics(Launcher launcher, BuildListener listener) {
Callable<Void, Exception> optOutTask = new StatsOptOutTask(launcher.isUnix(), listener);
public static void optOutOfSdkStatistics(Launcher launcher, BuildListener listener, String androidSdkHome) {
Callable<Void, Exception> optOutTask = new StatsOptOutTask(androidSdkHome, launcher.isUnix(), listener);
try {
launcher.getChannel().call(optOutTask);
} catch (Exception e) {
Expand Down Expand Up @@ -439,12 +439,14 @@ public boolean accept(File f) {
private static final class StatsOptOutTask implements Callable<Void, Exception> {

private static final long serialVersionUID = 1L;
private final String androidSdkHome;
private final boolean isUnix;

private final BuildListener listener;
private transient PrintStream logger;

public StatsOptOutTask(boolean isUnix, BuildListener listener) {
public StatsOptOutTask(String androidSdkHome, boolean isUnix, BuildListener listener) {
this.androidSdkHome = androidSdkHome;
this.isUnix = isUnix;
this.listener = listener;
}
Expand All @@ -454,7 +456,7 @@ public Void call() throws Exception {
logger = listener.getLogger();
}

final File homeDir = Utils.getHomeDirectory(isUnix);
final File homeDir = Utils.getHomeDirectory(androidSdkHome, isUnix);
final File androidDir = new File(homeDir, ".android");
androidDir.mkdirs();

Expand Down
Expand Up @@ -14,6 +14,8 @@
import hudson.model.queue.QueueTaskDispatcher;
import hudson.model.queue.SubTask;

import hudson.plugins.android_emulator.AndroidEmulator.DescriptorImpl;

/**
* This QueueTaskDispatcher prevents any one Android emulator instance from being executed more than
* once concurrently on any one build machine.
Expand All @@ -39,6 +41,12 @@ public CauseOfBlockage canTake(Node node, Task task) {
return null;
}

// If the AndroidEmulator uses workspace-local emulators, we don't care.
DescriptorImpl descriptor = Hudson.getInstance().getDescriptorByType(DescriptorImpl.class);
if (descriptor != null && descriptor.shouldKeepInWorkspace) {
return null;
}

// Check for builds in the queue which have the same emulator config as this task
Queue queue = Hudson.getInstance().getQueue();
for (BuildableItem item : queue.getBuildableItems()) {
Expand Down
Expand Up @@ -43,12 +43,13 @@ public abstract class AbstractBuilder extends Builder {
* @return An Android SDK instance, or {@code null} if none was found.
*/
protected static AndroidSdk getAndroidSdk(AbstractBuild<?, ?> build, Launcher launcher, BuildListener listener) {
boolean installSdkIfRequired = false;
boolean installSdkIfRequired = false, keepInWorkspace = false;
DescriptorImpl descriptor = Hudson.getInstance().getDescriptorByType(DescriptorImpl.class);
if (descriptor != null) {
installSdkIfRequired = descriptor.shouldInstallSdk;
keepInWorkspace = descriptor.shouldKeepInWorkspace;
}
return getAndroidSdk(build, launcher, listener, installSdkIfRequired);
return getAndroidSdk(build, launcher, listener, installSdkIfRequired, keepInWorkspace);
}

/**
Expand All @@ -58,10 +59,11 @@ protected static AndroidSdk getAndroidSdk(AbstractBuild<?, ?> build, Launcher la
* @param launcher The launcher for the remote node.
* @param listener The listener used to get the environment variables.
* @param installIfRequired If {@code true}, will automatically install the SDK if not found.
* @param keepInWorkspace If {@code true}, will keep emulators in workspace.
* @return An Android SDK instance, or {@code null} if none was found and installation failed.
*/
protected static AndroidSdk getAndroidSdk(AbstractBuild<?, ?> build, Launcher launcher,
BuildListener listener, boolean installIfRequired) {
BuildListener listener, boolean installIfRequired, boolean keepInWorkspace) {
// Get configured, expanded Android SDK root value
String androidHome = Utils.expandVariables(build, listener, Utils.getConfiguredAndroidHome());
EnvVars envVars = Utils.getEnvironment(build, listener);
Expand All @@ -70,40 +72,10 @@ protected static AndroidSdk getAndroidSdk(AbstractBuild<?, ?> build, Launcher la
Node node = Computer.currentComputer().getNode();
String discoveredAndroidHome = Utils.discoverAndroidHome(launcher, node, envVars, androidHome);

// Get Android SDK object from the given root (or locate on PATH)
AndroidSdk sdk = Utils.getAndroidSdk(launcher, discoveredAndroidHome);
if (sdk == null && installIfRequired) {
try {
log(listener.getLogger(), Messages.INSTALLING_SDK());
sdk = SdkInstaller.install(launcher, listener);
} catch (Exception e) {
log(listener.getLogger(), Messages.SDK_INSTALLATION_FAILED(), e);
}
}
final String androidSdkHome = (envVars != null && keepInWorkspace ? envVars.get("WORKSPACE") : null);

// Set environment variable for discovered SDK
if (sdk != null) {
final String sdkRoot = sdk.getSdkRoot();
build.addAction(new EnvironmentContributingAction() {
public void buildEnvVars(AbstractBuild<?, ?> build, EnvVars env) {
env.put("ANDROID_HOME", sdkRoot);
}

public String getDisplayName() {
return null;
}

public String getIconFileName() {
return null;
}

public String getUrlName() {
return null;
}
});
}

return sdk;
// Get Android SDK object from the given root (or locate on PATH)
return Utils.getAndroidSdk(launcher, discoveredAndroidHome, androidSdkHome);
}

/**
Expand Down

0 comments on commit 430606d

Please sign in to comment.