auto import from //branches/cupcake/...@127101
This commit is contained in:
@@ -31,6 +31,11 @@ import java.io.File;
|
||||
*/
|
||||
public final class SdkConstants {
|
||||
|
||||
/** An SDK Project's AndroidManifest.xml file */
|
||||
public static final String FN_ANDROID_MANIFEST_XML= "AndroidManifest.xml";
|
||||
/** An SDK Project's build.xml file */
|
||||
public final static String FN_BUILD_XML = "build.xml";
|
||||
|
||||
/** Name of the framework library, i.e. "android.jar" */
|
||||
public static final String FN_FRAMEWORK_LIBRARY = "android.jar";
|
||||
/** Name of the layout attributes, i.e. "attrs.xml" */
|
||||
@@ -129,7 +134,9 @@ public final class SdkConstants {
|
||||
/** Name of the addon libs folder. */
|
||||
public final static String FD_ADDON_LIBS = "libs";
|
||||
|
||||
|
||||
/** Namespace for the resource XML, i.e. "http://schemas.android.com/apk/res/android" */
|
||||
public final static String NS_RESOURCES = "http://schemas.android.com/apk/res/android";
|
||||
|
||||
/* Folder path relative to the SDK root */
|
||||
/** Path of the documentation directory relative to the sdk folder.
|
||||
* This is an OS path, ending with a separator. */
|
||||
@@ -206,7 +213,7 @@ public final class SdkConstants {
|
||||
|
||||
/** Returns the appropriate name for the 'android' command, which is 'android.bat' for
|
||||
* Windows and 'android' for all other platforms. */
|
||||
public static String AndroidCmdName() {
|
||||
public static String androidCmdName() {
|
||||
String os = System.getProperty("os.name");
|
||||
String cmd = "android";
|
||||
if (os.startsWith("Windows")) {
|
||||
@@ -215,4 +222,15 @@ public final class SdkConstants {
|
||||
return cmd;
|
||||
}
|
||||
|
||||
/** Returns the appropriate name for the 'mksdcard' command, which is 'mksdcard.exe' for
|
||||
* Windows and 'mkdsdcard' for all other platforms. */
|
||||
public static String mkSdCardCmdName() {
|
||||
String os = System.getProperty("os.name");
|
||||
String cmd = "mksdcard";
|
||||
if (os.startsWith("Windows")) {
|
||||
cmd += ".exe";
|
||||
}
|
||||
return cmd;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -21,14 +21,27 @@ import com.android.sdklib.ISdkLog;
|
||||
import com.android.sdklib.SdkConstants;
|
||||
import com.android.sdklib.project.ProjectProperties.PropertyType;
|
||||
|
||||
import org.w3c.dom.NodeList;
|
||||
import org.xml.sax.InputSource;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.xml.XMLConstants;
|
||||
import javax.xml.namespace.NamespaceContext;
|
||||
import javax.xml.xpath.XPath;
|
||||
import javax.xml.xpath.XPathConstants;
|
||||
import javax.xml.xpath.XPathExpressionException;
|
||||
import javax.xml.xpath.XPathFactory;
|
||||
|
||||
/**
|
||||
* Creates the basic files needed to get an Android project up and running. Also
|
||||
@@ -38,16 +51,31 @@ import java.util.Map;
|
||||
*/
|
||||
public class ProjectCreator {
|
||||
|
||||
/** Package path substitution string used in template files, i.e. "PACKAGE_PATH" */
|
||||
private final static String PH_JAVA_FOLDER = "PACKAGE_PATH";
|
||||
/** Package name substitution string used in template files, i.e. "PACKAGE" */
|
||||
private final static String PH_PACKAGE = "PACKAGE";
|
||||
/** Activity name substitution string used in template files, i.e. "ACTIVITY_NAME". */
|
||||
private final static String PH_ACTIVITY_NAME = "ACTIVITY_NAME";
|
||||
/** Project name substitution string used in template files, i.e. "PROJECT_NAME". */
|
||||
private final static String PH_PROJECT_NAME = "PROJECT_NAME";
|
||||
|
||||
private final static String FOLDER_TESTS = "tests";
|
||||
|
||||
public enum OutputLevel {
|
||||
SILENT, NORMAL, VERBOSE;
|
||||
/** Silent mode. Project creation will only display errors. */
|
||||
SILENT,
|
||||
/** Normal mode. Project creation will display what's being done, display
|
||||
* error but not warnings. */
|
||||
NORMAL,
|
||||
/** Verbose mode. Project creation will display what's being done, errors and warnings. */
|
||||
VERBOSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Exception thrown when a project creation fails, typically because a template
|
||||
* file cannot be written.
|
||||
*/
|
||||
private static class ProjectCreateException extends Exception {
|
||||
/** default UID. This will not be serialized anyway. */
|
||||
private static final long serialVersionUID = 1L;
|
||||
@@ -78,7 +106,8 @@ public class ProjectCreator {
|
||||
|
||||
/**
|
||||
* Creates a new project.
|
||||
* @param folderPath the folder of the project to create. This folder must exist.
|
||||
*
|
||||
* @param folderPath the folder of the project to create.
|
||||
* @param projectName the name of the project.
|
||||
* @param packageName the package of the project.
|
||||
* @param activityName the activity of the project as it will appear in the manifest.
|
||||
@@ -113,16 +142,16 @@ public class ProjectCreator {
|
||||
try {
|
||||
String[] content = projectFolder.list();
|
||||
if (content == null) {
|
||||
error = "Project directory %1$s is not a directory.";
|
||||
error = "Project folder '%1$s' is not a directory.";
|
||||
} else if (content.length != 0) {
|
||||
error = "Project directory %1$s is not empty. Please consider using '%2$s update' instead.";
|
||||
error = "Project folder '%1$s' is not empty. Please consider using '%2$s update' instead.";
|
||||
}
|
||||
} catch (Exception e1) {
|
||||
e = e1;
|
||||
}
|
||||
|
||||
if (e != null || error != null) {
|
||||
mLog.error(e, error, projectFolder, SdkConstants.AndroidCmdName());
|
||||
mLog.error(e, error, projectFolder, SdkConstants.androidCmdName());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,6 +193,21 @@ public class ProjectCreator {
|
||||
keywords.put(PH_ACTIVITY_NAME, activityName);
|
||||
}
|
||||
|
||||
// Take the project name from the command line if there's one
|
||||
if (projectName != null) {
|
||||
keywords.put(PH_PROJECT_NAME, projectName);
|
||||
} else {
|
||||
if (activityName != null) {
|
||||
// Use the activity as project name
|
||||
keywords.put(PH_PROJECT_NAME, activityName);
|
||||
} else {
|
||||
// We need a project name. Just pick up the basename of the project
|
||||
// directory.
|
||||
projectName = projectFolder.getName();
|
||||
keywords.put(PH_PROJECT_NAME, projectName);
|
||||
}
|
||||
}
|
||||
|
||||
// create the source folder and the java package folders.
|
||||
final String srcFolderPath = SdkConstants.FD_SOURCES + File.separator + packagePath;
|
||||
File sourceFolder = createDirs(projectFolder, srcFolderPath);
|
||||
@@ -198,10 +242,13 @@ public class ProjectCreator {
|
||||
manifestTemplate = "AndroidManifest.tests.template";
|
||||
}
|
||||
|
||||
installTemplate(manifestTemplate, new File(projectFolder, "AndroidManifest.xml"),
|
||||
installTemplate(manifestTemplate,
|
||||
new File(projectFolder, SdkConstants.FN_ANDROID_MANIFEST_XML),
|
||||
keywords, target);
|
||||
|
||||
installTemplate("build.template", new File(projectFolder, "build.xml"), keywords);
|
||||
installTemplate("build.template",
|
||||
new File(projectFolder, SdkConstants.FN_BUILD_XML),
|
||||
keywords);
|
||||
|
||||
// if this is not a test project, then we create one.
|
||||
if (isTestProject == false) {
|
||||
@@ -219,6 +266,293 @@ public class ProjectCreator {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates an existing project.
|
||||
* <p/>
|
||||
* Workflow:
|
||||
* <ul>
|
||||
* <li> Check AndroidManifest.xml is present (required)
|
||||
* <li> Check there's a default.properties with a target *or* --target was specified
|
||||
* <li> Update default.prop if --target was specified
|
||||
* <li> Refresh/create "sdk" in local.properties
|
||||
* <li> Build.xml: create if not present or no <androidinit(\w|/>) in it
|
||||
* </ul>
|
||||
*
|
||||
* @param folderPath the folder of the project to update. This folder must exist.
|
||||
* @param target the project target. Can be null.
|
||||
* @param projectName The project name from --name. Can be null.
|
||||
*/
|
||||
public void updateProject(String folderPath, IAndroidTarget target, String projectName ) {
|
||||
// project folder must exist and be a directory, since this is an update
|
||||
File projectFolder = new File(folderPath);
|
||||
if (!projectFolder.isDirectory()) {
|
||||
mLog.error(null, "Project folder '%1$s' is not a valid directory, this is not an Android project you can update.",
|
||||
projectFolder);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check AndroidManifest.xml is present
|
||||
File androidManifest = new File(projectFolder, SdkConstants.FN_ANDROID_MANIFEST_XML);
|
||||
if (!androidManifest.isFile()) {
|
||||
mLog.error(null,
|
||||
"%1$s not found in '%2$s', this is not an Android project you can update.",
|
||||
SdkConstants.FN_ANDROID_MANIFEST_XML,
|
||||
folderPath);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check there's a default.properties with a target *or* --target was specified
|
||||
ProjectProperties props = ProjectProperties.load(folderPath, PropertyType.DEFAULT);
|
||||
if (props == null || props.getProperty(ProjectProperties.PROPERTY_TARGET) == null) {
|
||||
if (target == null) {
|
||||
mLog.error(null,
|
||||
"There is no %1$s file in '%2$s'. Please provide a --target to the '%3$s update' command.",
|
||||
PropertyType.DEFAULT.getFilename(),
|
||||
folderPath,
|
||||
SdkConstants.androidCmdName());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Update default.prop iif --target was specified
|
||||
if (target != null) {
|
||||
props = ProjectProperties.create(folderPath, PropertyType.DEFAULT);
|
||||
props.setAndroidTarget(target);
|
||||
try {
|
||||
props.save();
|
||||
println("Updated %1$s", PropertyType.DEFAULT.getFilename());
|
||||
} catch (IOException e) {
|
||||
mLog.error(e, "Failed to write %1$s file in '%2$s'",
|
||||
PropertyType.DEFAULT.getFilename(),
|
||||
folderPath);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Refresh/create "sdk" in local.properties
|
||||
props = ProjectProperties.create(folderPath, PropertyType.LOCAL);
|
||||
props.setProperty(ProjectProperties.PROPERTY_SDK, mSdkFolder);
|
||||
try {
|
||||
props.save();
|
||||
println("Updated %1$s", PropertyType.LOCAL.getFilename());
|
||||
} catch (IOException e) {
|
||||
mLog.error(e, "Failed to write %1$s file in '%2$s'",
|
||||
PropertyType.LOCAL.getFilename(),
|
||||
folderPath);
|
||||
return;
|
||||
}
|
||||
|
||||
// Build.xml: create if not present or no <androidinit/> in it
|
||||
File buildXml = new File(projectFolder, SdkConstants.FN_BUILD_XML);
|
||||
boolean needsBuildXml = projectName != null || !buildXml.exists();
|
||||
if (!needsBuildXml) {
|
||||
// Note that "<androidinit" must be followed by either a whitespace, a "/" (for the
|
||||
// XML /> closing tag) or an end-of-line. This way we know the XML tag is really this
|
||||
// one and later we will be able to use an "androidinit2" tag or such as necessary.
|
||||
needsBuildXml = !checkFileContainsRegexp(buildXml, "<androidinit(?:\\s|/|$)");
|
||||
if (needsBuildXml) {
|
||||
println("File %1$s is too old and needs to be updated.", SdkConstants.FN_BUILD_XML);
|
||||
}
|
||||
}
|
||||
|
||||
if (needsBuildXml) {
|
||||
// create the map for place-holders of values to replace in the templates
|
||||
final HashMap<String, String> keywords = new HashMap<String, String>();
|
||||
|
||||
// Take the project name from the command line if there's one
|
||||
if (projectName != null) {
|
||||
keywords.put(PH_PROJECT_NAME, projectName);
|
||||
} else {
|
||||
extractPackageFromManifest(androidManifest, keywords);
|
||||
if (keywords.containsKey(PH_ACTIVITY_NAME)) {
|
||||
// Use the activity as project name
|
||||
keywords.put(PH_PROJECT_NAME, keywords.get(PH_ACTIVITY_NAME));
|
||||
} else {
|
||||
// We need a project name. Just pick up the basename of the project
|
||||
// directory.
|
||||
projectName = projectFolder.getName();
|
||||
keywords.put(PH_PROJECT_NAME, projectName);
|
||||
}
|
||||
}
|
||||
|
||||
if (mLevel == OutputLevel.VERBOSE) {
|
||||
println("Regenerating %1$s with project name %2$s",
|
||||
SdkConstants.FN_BUILD_XML,
|
||||
keywords.get(PH_PROJECT_NAME));
|
||||
}
|
||||
|
||||
try {
|
||||
installTemplate("build.template",
|
||||
new File(projectFolder, SdkConstants.FN_BUILD_XML),
|
||||
keywords);
|
||||
} catch (ProjectCreateException e) {
|
||||
mLog.error(e, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if any line of the input file contains the requested regexp.
|
||||
*/
|
||||
private boolean checkFileContainsRegexp(File file, String regexp) {
|
||||
Pattern p = Pattern.compile(regexp);
|
||||
|
||||
try {
|
||||
BufferedReader in = new BufferedReader(new FileReader(file));
|
||||
String line;
|
||||
|
||||
while ((line = in.readLine()) != null) {
|
||||
if (p.matcher(line).find()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
in.close();
|
||||
} catch (Exception e) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts a "full" package & activity name from an AndroidManifest.xml.
|
||||
* <p/>
|
||||
* The keywords dictionary is always filed the package name under the key {@link #PH_PACKAGE}.
|
||||
* If an activity name can be found, it is filed under the key {@link #PH_ACTIVITY_NAME}.
|
||||
* When no activity is found, this key is not created.
|
||||
*
|
||||
* @param manifestFile The AndroidManifest.xml file
|
||||
* @param outKeywords Place where to put the out parameters: package and activity names.
|
||||
* @return True if the package/activity was parsed and updated in the keyword dictionary.
|
||||
*/
|
||||
private boolean extractPackageFromManifest(File manifestFile,
|
||||
Map<String, String> outKeywords) {
|
||||
try {
|
||||
final String nsPrefix = "android";
|
||||
final String nsURI = SdkConstants.NS_RESOURCES;
|
||||
|
||||
XPath xpath = XPathFactory.newInstance().newXPath();
|
||||
|
||||
xpath.setNamespaceContext(new NamespaceContext() {
|
||||
public String getNamespaceURI(String prefix) {
|
||||
if (nsPrefix.equals(prefix)) {
|
||||
return nsURI;
|
||||
}
|
||||
return XMLConstants.NULL_NS_URI;
|
||||
}
|
||||
|
||||
public String getPrefix(String namespaceURI) {
|
||||
if (nsURI.equals(namespaceURI)) {
|
||||
return nsPrefix;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public Iterator getPrefixes(String namespaceURI) {
|
||||
if (nsURI.equals(namespaceURI)) {
|
||||
ArrayList<String> list = new ArrayList<String>();
|
||||
list.add(nsPrefix);
|
||||
return list.iterator();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
InputSource source = new InputSource(new FileReader(manifestFile));
|
||||
String packageName = xpath.evaluate("/manifest/@package", source);
|
||||
|
||||
source = new InputSource(new FileReader(manifestFile));
|
||||
|
||||
// Select the "android:name" attribute of all <activity> nodes but only if they
|
||||
// contain a sub-node <intent-filter><action> with an "android:name" attribute which
|
||||
// is 'android.intent.action.MAIN' and an <intent-filter><category> with an
|
||||
// "android:name" attribute which is 'android.intent.category.LAUNCHER'
|
||||
String expression = String.format("/manifest/application/activity" +
|
||||
"[intent-filter/action/@%1$s:name='android.intent.action.MAIN' and " +
|
||||
"intent-filter/category/@%1$s:name='android.intent.category.LAUNCHER']" +
|
||||
"/@%1$s:name", nsPrefix);
|
||||
|
||||
NodeList activityNames = (NodeList) xpath.evaluate(expression, source,
|
||||
XPathConstants.NODESET);
|
||||
|
||||
// If we get here, both XPath expressions were valid so we're most likely dealing
|
||||
// with an actual AndroidManifest.xml file. The nodes may not have the requested
|
||||
// attributes though, if which case we should warn.
|
||||
|
||||
if (packageName == null || packageName.length() == 0) {
|
||||
mLog.error(null,
|
||||
"Missing <manifest package=\"...\"> in '%1$s'",
|
||||
manifestFile.getName());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get the first activity that matched earlier. If there is no activity,
|
||||
// activityName is set to an empty string and the generated "combined" name
|
||||
// will be in the form "package." (with a dot at the end).
|
||||
String activityName = "";
|
||||
if (activityNames.getLength() > 0) {
|
||||
activityName = activityNames.item(0).getNodeValue();
|
||||
}
|
||||
|
||||
if (mLevel == OutputLevel.VERBOSE && activityNames.getLength() > 1) {
|
||||
println("WARNING: There is more than one activity defined in '%1$s'.\n" +
|
||||
"Only the first one will be used. If this is not appropriate, you need\n" +
|
||||
"to specify one of these values manually instead:",
|
||||
manifestFile.getName());
|
||||
|
||||
for (int i = 0; i < activityNames.getLength(); i++) {
|
||||
String name = activityNames.item(i).getNodeValue();
|
||||
name = combinePackageActivityNames(packageName, name);
|
||||
println("- %1$s", name);
|
||||
}
|
||||
}
|
||||
|
||||
if (activityName.length() == 0) {
|
||||
mLog.warning("Missing <activity %1$s:name=\"...\"> in '%2$s'.\n" +
|
||||
"No activity will be generated.",
|
||||
nsPrefix, manifestFile.getName());
|
||||
} else {
|
||||
outKeywords.put(PH_ACTIVITY_NAME, activityName);
|
||||
}
|
||||
|
||||
outKeywords.put(PH_PACKAGE, packageName);
|
||||
return true;
|
||||
|
||||
} catch (IOException e) {
|
||||
mLog.error(e, "Failed to read %1$s", manifestFile.getName());
|
||||
} catch (XPathExpressionException e) {
|
||||
Throwable t = e.getCause();
|
||||
mLog.error(t == null ? e : t,
|
||||
"Failed to parse %1$s",
|
||||
manifestFile.getName());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private String combinePackageActivityNames(String packageName, String activityName) {
|
||||
// Activity Name can have 3 forms:
|
||||
// - ".Name" means this is a class name in the given package name.
|
||||
// The full FQCN is thus packageName + ".Name"
|
||||
// - "Name" is an older variant of the former. Full FQCN is packageName + "." + "Name"
|
||||
// - "com.blah.Name" is a full FQCN. Ignore packageName and use activityName as-is.
|
||||
// To be valid, the package name should have at least two components. This is checked
|
||||
// later during the creation of the build.xml file, so we just need to detect there's
|
||||
// a dot but not at pos==0.
|
||||
|
||||
int pos = activityName.indexOf('.');
|
||||
if (pos == 0) {
|
||||
return packageName + activityName;
|
||||
} else if (pos > 0) {
|
||||
return activityName;
|
||||
} else {
|
||||
return packageName + "." + activityName;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs a new file that is based on a template file provided by a given target.
|
||||
* Each match of each key from the place-holder map in the template will be replaced with its
|
||||
@@ -272,6 +606,9 @@ public class ProjectCreator {
|
||||
*/
|
||||
private void installFullPathTemplate(String sourcePath, File destFile,
|
||||
Map<String, String> placeholderMap) throws ProjectCreateException {
|
||||
|
||||
boolean existed = destFile.exists();
|
||||
|
||||
try {
|
||||
BufferedWriter out = new BufferedWriter(new FileWriter(destFile));
|
||||
BufferedReader in = new BufferedReader(new FileReader(sourcePath));
|
||||
@@ -293,17 +630,26 @@ public class ProjectCreator {
|
||||
destFile, e.getMessage());
|
||||
}
|
||||
|
||||
println("Added file %1$s", destFile);
|
||||
println("%1$s file %2$s",
|
||||
existed ? "Updated" : "Added",
|
||||
destFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints a message unless silence is enabled.
|
||||
* <p/>
|
||||
* This is just a convenience wrapper around {@link ISdkLog#printf(String, Object...)} from
|
||||
* {@link #mLog} after testing if ouput level is {@link OutputLevel#VERBOSE}.
|
||||
*
|
||||
* @param format Format for String.format
|
||||
* @param args Arguments for String.format
|
||||
*/
|
||||
private void println(String format, Object... args) {
|
||||
if (mLevel == OutputLevel.VERBOSE) {
|
||||
System.out.println(String.format(format, args));
|
||||
if (mLevel != OutputLevel.SILENT) {
|
||||
if (!format.endsWith("\n")) {
|
||||
format += "\n";
|
||||
}
|
||||
mLog.printf(format, args);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -47,6 +47,10 @@ public final class ProjectProperties {
|
||||
mFilename = filename;
|
||||
mHeader = header;
|
||||
}
|
||||
|
||||
public String getFilename() {
|
||||
return mFilename;
|
||||
}
|
||||
}
|
||||
|
||||
private final static String LOCAL_HEADER =
|
||||
|
||||
@@ -20,14 +20,17 @@ import com.android.prefs.AndroidLocation;
|
||||
import com.android.prefs.AndroidLocation.AndroidLocationException;
|
||||
import com.android.sdklib.IAndroidTarget;
|
||||
import com.android.sdklib.ISdkLog;
|
||||
import com.android.sdklib.SdkConstants;
|
||||
import com.android.sdklib.SdkManager;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FileWriter;
|
||||
import java.io.FilenameFilter;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
@@ -42,13 +45,15 @@ public final class VmManager {
|
||||
|
||||
private final static String VM_INFO_PATH = "path";
|
||||
private final static String VM_INFO_TARGET = "target";
|
||||
|
||||
|
||||
private final static String IMAGE_USERDATA = "userdata.img";
|
||||
private final static String CONFIG_INI = "config.ini";
|
||||
|
||||
|
||||
private final static Pattern INI_NAME_PATTERN = Pattern.compile("(.+)\\.ini$",
|
||||
Pattern.CASE_INSENSITIVE);
|
||||
|
||||
private final static Pattern SDCARD_SIZE_PATTERN = Pattern.compile("\\d+[MK]?");
|
||||
|
||||
public static final class VmInfo {
|
||||
String name;
|
||||
String path;
|
||||
@@ -69,10 +74,12 @@ public final class VmManager {
|
||||
|
||||
private final ArrayList<VmInfo> mVmList = new ArrayList<VmInfo>();
|
||||
private ISdkLog mSdkLog;
|
||||
private final SdkManager mSdk;
|
||||
|
||||
public VmManager(SdkManager sdk, ISdkLog sdkLog) throws AndroidLocationException {
|
||||
mSdk = sdk;
|
||||
mSdkLog = sdkLog;
|
||||
buildVmList(sdk);
|
||||
buildVmList();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -104,12 +111,12 @@ public final class VmManager {
|
||||
* @param name the name of the VM
|
||||
* @param target the target of the VM
|
||||
* @param skinName the name of the skin. Can be null.
|
||||
* @param sdcardPath the path to the sdCard. Can be null.
|
||||
* @param sdcardSize the size of a local sdcard to create. Can be 0 for no local sdcard.
|
||||
* @param sdcard the parameter value for the sdCard. Can be null. This is either a path to
|
||||
* an existing sdcard image or a sdcard size (\d+, \d+K, \dM).
|
||||
* @param hardwareConfig the hardware setup for the VM
|
||||
*/
|
||||
public VmInfo createVm(String parentFolder, String name, IAndroidTarget target,
|
||||
String skinName, String sdcardPath, int sdcardSize, Map<String,String> hardwareConfig,
|
||||
String skinName, String sdcard, Map<String,String> hardwareConfig,
|
||||
ISdkLog log) {
|
||||
|
||||
try {
|
||||
@@ -128,7 +135,7 @@ public final class VmManager {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
// create the vm folder.
|
||||
vmFolder.mkdir();
|
||||
|
||||
@@ -177,15 +184,40 @@ public final class VmManager {
|
||||
}
|
||||
}
|
||||
|
||||
if (sdcardPath != null) {
|
||||
File sdcard = new File(sdcardPath);
|
||||
if (sdcard.isFile()) {
|
||||
values.put("sdcard", sdcardPath);
|
||||
} else if (log != null) {
|
||||
log.warning("sdcarad image '%1$s' does not exists.", sdcardPath);
|
||||
if (sdcard != null) {
|
||||
File sdcardFile = new File(sdcard);
|
||||
if (sdcardFile.isFile()) {
|
||||
values.put("sdcard", sdcard);
|
||||
} else {
|
||||
// check that it matches the pattern for sdcard size
|
||||
Matcher m = SDCARD_SIZE_PATTERN.matcher(sdcard);
|
||||
if (m.matches()) {
|
||||
// create the sdcard.
|
||||
sdcardFile = new File(vmFolder, "sdcard.img");
|
||||
String path = sdcardFile.getAbsolutePath();
|
||||
|
||||
// execute mksdcard with the proper parameters.
|
||||
File toolsFolder = new File(mSdk.getLocation(), SdkConstants.FD_TOOLS);
|
||||
File mkSdCard = new File(toolsFolder, SdkConstants.mkSdCardCmdName());
|
||||
|
||||
if (mkSdCard.isFile() == false) {
|
||||
log.error(null, "'%1$s' is missing from the SDK tools folder.",
|
||||
mkSdCard.getName());
|
||||
return null;
|
||||
}
|
||||
|
||||
if (createSdCard(mkSdCard.getAbsolutePath(), sdcard, path, log) == false) {
|
||||
return null; // mksdcard output has already been displayed, no need to
|
||||
// output anything else.
|
||||
}
|
||||
|
||||
// add its path to the values.
|
||||
values.put("sdcard", path);
|
||||
} else {
|
||||
log.error(null, "'%1$s' is not recognized as a valid sdcard value", sdcard);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
} else if (sdcardSize != 0) {
|
||||
// TODO: create sdcard image.
|
||||
}
|
||||
|
||||
if (hardwareConfig != null) {
|
||||
@@ -227,7 +259,7 @@ public final class VmManager {
|
||||
return null;
|
||||
}
|
||||
|
||||
private void buildVmList(SdkManager sdk) throws AndroidLocationException {
|
||||
private void buildVmList() throws AndroidLocationException {
|
||||
// get the Android prefs location.
|
||||
String vmRoot = AndroidLocation.getFolder() + AndroidLocation.FOLDER_VMS;
|
||||
|
||||
@@ -253,14 +285,14 @@ public final class VmManager {
|
||||
});
|
||||
|
||||
for (File vm : vms) {
|
||||
VmInfo info = parseVmInfo(vm, sdk);
|
||||
VmInfo info = parseVmInfo(vm);
|
||||
if (info != null) {
|
||||
mVmList.add(info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private VmInfo parseVmInfo(File path, SdkManager sdk) {
|
||||
private VmInfo parseVmInfo(File path) {
|
||||
Map<String, String> map = SdkManager.parsePropertyFile(path, mSdkLog);
|
||||
|
||||
String vmPath = map.get(VM_INFO_PATH);
|
||||
@@ -273,7 +305,7 @@ public final class VmManager {
|
||||
return null;
|
||||
}
|
||||
|
||||
IAndroidTarget target = sdk.getTargetFromHashString(targetHash);
|
||||
IAndroidTarget target = mSdk.getTargetFromHashString(targetHash);
|
||||
if (target == null) {
|
||||
return null;
|
||||
}
|
||||
@@ -301,4 +333,118 @@ public final class VmManager {
|
||||
writer.close();
|
||||
|
||||
}
|
||||
|
||||
private boolean createSdCard(String toolLocation, String size, String location, ISdkLog log) {
|
||||
try {
|
||||
String[] command = new String[3];
|
||||
command[0] = toolLocation;
|
||||
command[1] = size;
|
||||
command[2] = location;
|
||||
Process process = Runtime.getRuntime().exec(command);
|
||||
|
||||
ArrayList<String> errorOutput = new ArrayList<String>();
|
||||
ArrayList<String> stdOutput = new ArrayList<String>();
|
||||
int status = grabProcessOutput(process, errorOutput, stdOutput,
|
||||
true /* waitForReaders */);
|
||||
|
||||
if (status != 0) {
|
||||
log.error(null, "Failed to create the SD card.");
|
||||
for (String error : errorOutput) {
|
||||
log.error(null, error);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (InterruptedException e) {
|
||||
log.error(null, "Failed to create the SD card.");
|
||||
} catch (IOException e) {
|
||||
log.error(null, "Failed to create the SD card.");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the stderr/stdout outputs of a process and returns when the process is done.
|
||||
* Both <b>must</b> be read or the process will block on windows.
|
||||
* @param process The process to get the ouput from
|
||||
* @param errorOutput The array to store the stderr output. cannot be null.
|
||||
* @param stdOutput The array to store the stdout output. cannot be null.
|
||||
* @param waitforReaders if true, this will wait for the reader threads.
|
||||
* @return the process return code.
|
||||
* @throws InterruptedException
|
||||
*/
|
||||
private int grabProcessOutput(final Process process, final ArrayList<String> errorOutput,
|
||||
final ArrayList<String> stdOutput, boolean waitforReaders)
|
||||
throws InterruptedException {
|
||||
assert errorOutput != null;
|
||||
assert stdOutput != null;
|
||||
// read the lines as they come. if null is returned, it's
|
||||
// because the process finished
|
||||
Thread t1 = new Thread("") { //$NON-NLS-1$
|
||||
@Override
|
||||
public void run() {
|
||||
// create a buffer to read the stderr output
|
||||
InputStreamReader is = new InputStreamReader(process.getErrorStream());
|
||||
BufferedReader errReader = new BufferedReader(is);
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
String line = errReader.readLine();
|
||||
if (line != null) {
|
||||
errorOutput.add(line);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// do nothing.
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Thread t2 = new Thread("") { //$NON-NLS-1$
|
||||
@Override
|
||||
public void run() {
|
||||
InputStreamReader is = new InputStreamReader(process.getInputStream());
|
||||
BufferedReader outReader = new BufferedReader(is);
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
String line = outReader.readLine();
|
||||
if (line != null) {
|
||||
stdOutput.add(line);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// do nothing.
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
t1.start();
|
||||
t2.start();
|
||||
|
||||
// it looks like on windows process#waitFor() can return
|
||||
// before the thread have filled the arrays, so we wait for both threads and the
|
||||
// process itself.
|
||||
if (waitforReaders) {
|
||||
try {
|
||||
t1.join();
|
||||
} catch (InterruptedException e) {
|
||||
}
|
||||
try {
|
||||
t2.join();
|
||||
} catch (InterruptedException e) {
|
||||
}
|
||||
}
|
||||
|
||||
// get the return code from the process
|
||||
return process.waitFor();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user