Skip to content

Commit

Permalink
[FIXED JENKINS-11083] Allow adding a suffix to generated AVD names.
Browse files Browse the repository at this point in the history
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 24c2389
Show file tree
Hide file tree
Showing 5 changed files with 63 additions and 33 deletions.
32 changes: 18 additions & 14 deletions src/main/java/hudson/plugins/android_emulator/AndroidEmulator.java
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -77,6 +75,7 @@ public class AndroidEmulator extends BuildWrapper implements Serializable {
@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
Expand All @@ -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;
Expand All @@ -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() {
Expand Down Expand Up @@ -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
Expand All @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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");
Expand All @@ -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");
Expand All @@ -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
Expand Down
40 changes: 22 additions & 18 deletions src/main/java/hudson/plugins/android_emulator/EmulatorConfig.java
Expand Up @@ -43,27 +43,24 @@ class EmulatorConfig implements Serializable {
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.");
Expand Down Expand Up @@ -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;
}
Expand All @@ -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() {
Expand Down
Expand Up @@ -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);
}

/**
Expand Down
Expand Up @@ -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}"
Expand Down
19 changes: 19 additions & 0 deletions src/main/webapp/help-avdNameSuffix.html
@@ -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.