Skip to content
Permalink
Browse files

[FIXED JENKINS-11083] Allow adding a suffix to generated AVD names.

This makes it possible to use the exact same emulator config in two jobs without
one job having to block waiting for the other job to finish using the emulator.

This patch comes from pull requests #9 and #33, with some additional changes to
documentation and assuring that generated AVD names are valid, despite the
suffix field being free text.
  • Loading branch information...
orrc committed May 18, 2014
1 parent ab4aa12 commit 24c23897e7e100b373f8f4aa0989a9584688ccbf
@@ -9,13 +9,13 @@
import hudson.Proc;
import hudson.Util;
import hudson.matrix.Combination;
import hudson.model.BuildListener;
import hudson.model.Result;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.BuildListener;
import hudson.model.Computer;
import hudson.model.Hudson;
import hudson.model.Node;
import hudson.model.Result;
import hudson.plugins.android_emulator.sdk.AndroidSdk;
import hudson.plugins.android_emulator.sdk.Tool;
import hudson.plugins.android_emulator.util.Utils;
@@ -27,6 +27,12 @@
import hudson.util.ForkOutputStream;
import hudson.util.FormValidation;
import hudson.util.NullStream;
import net.sf.json.JSONObject;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.export.Exported;
import org.kohsuke.stapler.export.ExportedBean;

import java.io.ByteArrayOutputStream;
import java.io.File;
@@ -44,14 +50,6 @@
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import net.sf.json.JSONObject;

import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.export.Exported;
import org.kohsuke.stapler.export.ExportedBean;

public class AndroidEmulator extends BuildWrapper implements Serializable {

private static final long serialVersionUID = 1L;
@@ -77,6 +75,7 @@
@Exported public final String deviceLocale;
@Exported public final String targetAbi;
@Exported public final String sdCardSize;
@Exported public final String avdNameSuffix;
@Exported public final HardwareProperty[] hardwareProperties;

// Common properties
@@ -96,7 +95,7 @@ public AndroidEmulator(String avdName, String osVersion, String screenDensity,
String screenResolution, String deviceLocale, String sdCardSize,
HardwareProperty[] hardwareProperties, boolean wipeData, boolean showWindow,
boolean useSnapshots, boolean deleteAfterBuild, int startupDelay,
String commandLineOptions, String targetAbi, String executable) {
String commandLineOptions, String targetAbi, String executable, String avdNameSuffix) {
this.avdName = avdName;
this.osVersion = osVersion;
this.screenDensity = screenDensity;
@@ -112,6 +111,7 @@ public AndroidEmulator(String avdName, String osVersion, String screenDensity,
this.startupDelay = Math.abs(startupDelay);
this.commandLineOptions = commandLineOptions;
this.targetAbi = targetAbi;
this.avdNameSuffix = avdNameSuffix;
}

public boolean getUseNamedEmulator() {
@@ -153,9 +153,10 @@ public String getConfigHash(Node node, Combination combination) {
String screenResolution = Utils.expandVariables(envVars, combination, this.screenResolution);
String deviceLocale = Utils.expandVariables(envVars, combination, this.deviceLocale);
String targetAbi = Utils.expandVariables(envVars, combination, this.targetAbi);
String avdNameSuffix = Utils.expandVariables(envVars, combination, this.avdNameSuffix);

return EmulatorConfig.getAvdName(avdName, osVersion, screenDensity, screenResolution,
deviceLocale, targetAbi);
deviceLocale, targetAbi, avdNameSuffix);
}

@Override
@@ -182,6 +183,7 @@ public Environment setUp(AbstractBuild build, final Launcher launcher, BuildList
sdCardSize = sdCardSize.toUpperCase().replaceAll("[ B]", "");
}
String targetAbi = Utils.expandVariables(envVars, buildVars, this.targetAbi);
String avdNameSuffix = Utils.expandVariables(envVars, buildVars, this.avdNameSuffix);

// Expand macros within hardware property values
final int propCount = hardwareProperties == null ? 0 : hardwareProperties.length;
@@ -218,7 +220,7 @@ public Environment setUp(AbstractBuild build, final Launcher launcher, BuildList
try {
emuConfig = EmulatorConfig.create(avdName, osVersion, screenDensity,
screenResolution, deviceLocale, sdCardSize, wipeData, showWindow, useSnapshots,
commandLineOptions, targetAbi, androidSdkHome, executable);
commandLineOptions, targetAbi, androidSdkHome, executable, avdNameSuffix);
} catch (IllegalArgumentException e) {
log(logger, Messages.EMULATOR_CONFIGURATION_BAD(e.getLocalizedMessage()));
build.setResult(Result.NOT_BUILT);
@@ -795,6 +797,7 @@ public BuildWrapper newInstance(StaplerRequest req, JSONObject formData) throws
int startupDelay = 0;
String commandLineOptions = null;
String executable = null;
String avdNameSuffix = null;

JSONObject emulatorData = formData.getJSONObject("useNamed");
String useNamedValue = emulatorData.getString("value");
@@ -808,6 +811,7 @@ public BuildWrapper newInstance(StaplerRequest req, JSONObject formData) throws
sdCardSize = Util.fixEmptyAndTrim(emulatorData.getString("sdCardSize"));
hardware = req.bindJSONToList(HardwareProperty.class, emulatorData.get("hardwareProperties"));
targetAbi = Util.fixEmptyAndTrim(emulatorData.getString("targetAbi"));
avdNameSuffix = Util.fixEmptyAndTrim(emulatorData.getString("avdNameSuffix"));
}
wipeData = formData.getBoolean("wipeData");
showWindow = formData.getBoolean("showWindow");
@@ -823,7 +827,7 @@ 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, executable);
targetAbi, executable, avdNameSuffix);
}

@Override
@@ -43,27 +43,24 @@
private final String commandLineOptions;
private final String androidSdkHome;
private final String executable;
private final String avdNameSuffix;

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) {
private EmulatorConfig(String avdName, boolean wipeData, boolean showWindow,
boolean useSnapshots, String commandLineOptions, String androidSdkHome, String avdNameSuffix) {
this.avdName = avdName;
this.wipeData = wipeData;
this.showWindow = showWindow;
this.useSnapshots = useSnapshots;
this.commandLineOptions = commandLineOptions;
this.androidSdkHome = androidSdkHome;
this.executable = null;
this.avdNameSuffix = avdNameSuffix;
}

public EmulatorConfig(String osVersion, String screenDensity, String screenResolution,
private EmulatorConfig(String osVersion, String screenDensity, String screenResolution,
String deviceLocale, String sdCardSize, boolean wipeData, boolean showWindow,
boolean useSnapshots, String commandLineOptions, String targetAbi, String androidSdkHome,
String executable)
String executable, String avdNameSuffix)
throws IllegalArgumentException {
if (osVersion == null || screenDensity == null || screenResolution == null) {
throw new IllegalArgumentException("Valid OS version and screen properties must be supplied.");
@@ -109,25 +106,27 @@ public EmulatorConfig(String osVersion, String screenDensity, String screenResol
this.targetAbi = targetAbi;
this.androidSdkHome = androidSdkHome;
this.executable = executable;
this.avdNameSuffix = avdNameSuffix;
}

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,
String androidSdkHome, String executable) {
String androidSdkHome, String executable, String avdNameSuffix) {
if (Util.fixEmptyAndTrim(avdName) == null) {
return new EmulatorConfig(osVersion, screenDensity, screenResolution, deviceLocale,
sdCardSize, wipeData, showWindow, useSnapshots, commandLineOptions, targetAbi, androidSdkHome, executable);
return new EmulatorConfig(osVersion, screenDensity, screenResolution, deviceLocale, sdCardSize, wipeData,
showWindow, useSnapshots, commandLineOptions, targetAbi, androidSdkHome, executable, avdNameSuffix);
}

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

public static final String getAvdName(String avdName, String osVersion, String screenDensity,
String screenResolution, String deviceLocale, String targetAbi) {
String screenResolution, String deviceLocale, String targetAbi, String avdNameSuffix) {
try {
return create(avdName, osVersion, screenDensity, screenResolution, deviceLocale, null,
false, false, false, null, targetAbi, null, null).getAvdName();
return create(avdName, osVersion, screenDensity, screenResolution, deviceLocale, null, false, false, false,
null, targetAbi, null, null, avdNameSuffix).getAvdName();
} catch (IllegalArgumentException e) {}
return null;
}
@@ -150,10 +149,15 @@ private String getGeneratedAvdName() {
String resolution = screenResolution.toString();
String platform = osVersion.getTargetName().replace(':', '_').replace(' ', '_');
String abi = "";
if (Util.fixEmptyAndTrim(targetAbi) != null && osVersion.requiresAbi()) {
if (targetAbi != null && osVersion.requiresAbi()) {
abi = "_" + targetAbi.replace(' ', '-');
}
return String.format("hudson_%s_%s_%s_%s%s", locale, density, resolution, platform, abi);
String suffix = "";
if (avdNameSuffix != null) {
suffix = "_" + avdNameSuffix.replaceAll("[^a-zA-Z0-9._-]", "-");
}

return String.format("hudson_%s_%s_%s_%s%s%s", locale, density, resolution, platform, abi, suffix);
}

public AndroidPlatform getOsVersion() {
@@ -495,7 +495,7 @@ public static String expandVariables(EnvVars envVars, Map<String,String> buildVa
if (result != null) {
result = Util.replaceMacro(result, vars);
}
return result;
return Util.fixEmptyAndTrim(result);
}

/**
@@ -53,6 +53,9 @@
items="${descriptor.targetAbis}"
checkUrl="'descriptorByName/AndroidEmulator/checkTargetAbi?value='+escape(this.value)" />
</f:entry>
<f:entry title="${%Emulator name suffix}" help="/plugin/android-emulator/help-avdNameSuffix.html">
<f:textbox name="android-emulator.avdNameSuffix" value="${instance.avdNameSuffix}" style="width:12em" />
</f:entry>

<f:entry title="${%Hardware}" help="/plugin/android-emulator/help-hardware.html">
<f:repeatable var="hw" name="hardwareProperties" items="${instance.hardwareProperties}"
@@ -0,0 +1,19 @@
Should be empty or a custom suffix for the AVD name.<br/>
e.g. "<code>mySuffix</code>" or "<code>withPdf</code>"
<p>
If this field is left blank, no custom suffix will be added to the AVD name that
this plugin generates when automatically creating an emulator.<br/>
If a value is set, a suffix will be added to the AVD name, with any invalid characters
replaced by a hyphen (as AVD names may only contain [a-z A-Z 0-9 . _ -]).
</p>
<p>
This allows you to run multiple emulators with the same configuration in parallel on the
same build machine.<br/>
Normally, if you create two jobs with the same emulator configuration, only one of those
jobs will run at a time &mdash; as the other job is using the emulator. But by setting a
custom suffix one on or both jobs, the emulator names will differ for the two jobs, and
Jenkins will consider them as two completely different emulators, allowing them to run in
parallel.
</p>
Note that this will cause more disk space to be used, as a new emulator will be created
on disk for each unique suffix used.

0 comments on commit 24c2389

Please sign in to comment.
You can’t perform that action at this time.