Merge change I663d4cb7 into eclair

* changes:
  Update the project creation (from the command line):
This commit is contained in:
Android (Google) Code Review
2009-09-28 16:33:57 -04:00
19 changed files with 387 additions and 961 deletions

View File

@@ -460,7 +460,7 @@ class CommandLineProcessor {
stdout("\nValid actions are composed of a verb and an optional direct object:");
for (String[] action : mActions) {
stdout("- %1$6s %2$-7s: %3$s",
stdout("- %1$6s %2$-12s: %3$s",
action[ACTION_VERB_INDEX],
action[ACTION_OBJECT_INDEX],
action[ACTION_DESC_INDEX]);

View File

@@ -28,17 +28,27 @@ import com.android.sdklib.internal.avd.HardwareProperties;
import com.android.sdklib.internal.avd.AvdManager.AvdInfo;
import com.android.sdklib.internal.avd.HardwareProperties.HardwareProperty;
import com.android.sdklib.internal.project.ProjectCreator;
import com.android.sdklib.internal.project.ProjectProperties;
import com.android.sdklib.internal.project.ProjectCreator.OutputLevel;
import com.android.sdklib.internal.project.ProjectProperties.PropertyType;
import com.android.sdklib.xml.AndroidXPathFactory;
import com.android.sdkmanager.internal.repository.AboutPage;
import com.android.sdkmanager.internal.repository.SettingsPage;
import com.android.sdkuilib.repository.UpdaterWindow;
import org.xml.sax.InputSource;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathExpressionException;
/**
* Main class for the 'android' application.
*/
@@ -221,6 +231,10 @@ public class Main {
SdkCommandLine.OBJECT_PROJECT.equals(directObject)) {
createProject();
} else if (SdkCommandLine.VERB_CREATE.equals(verb) &&
SdkCommandLine.OBJECT_TEST_PROJECT.equals(directObject)) {
createTestProject();
} else if (SdkCommandLine.VERB_UPDATE.equals(verb) &&
SdkCommandLine.OBJECT_PROJECT.equals(directObject)) {
updateProject();
@@ -314,9 +328,112 @@ public class Main {
packageName,
activityName,
target,
false /* isTestProject*/);
null /*pathToMain*/);
}
/**
* Creates a new Android test project based on command-line parameters
*/
private void createTestProject() {
String projectDir = getProjectLocation(mSdkCommandLine.getParamLocationPath());
// first check the path of the parent project, and make sure it's valid.
String pathToMainProject = mSdkCommandLine.getParamTestProjectMain();
File parentProject = new File(pathToMainProject);
if (parentProject.isAbsolute() == false) {
// if the path is not absolute, we need to resolve it based on the
// destination path of the project
parentProject = new File(projectDir, pathToMainProject);
}
if (parentProject.isDirectory() == false) {
errorAndExit("Main project's directory does not exist: %1$s",
pathToMainProject);
}
// now look for a manifest in there
File manifest = new File(parentProject, SdkConstants.FN_ANDROID_MANIFEST_XML);
if (manifest.isFile() == false) {
errorAndExit("No AndroidManifest.xml file found in the main project directory: %1$s",
parentProject.getAbsolutePath());
}
// now query the manifest for the package file.
XPath xpath = AndroidXPathFactory.newXPath();
String packageName, activityName;
try {
packageName = xpath.evaluate("/manifest/@package",
new InputSource(new FileInputStream(manifest)));
mSdkLog.printf("Found main project package: %1$s\n", packageName);
// now get the name of the first activity we find
activityName = xpath.evaluate("/manifest/application/activity[1]/@android:name",
new InputSource(new FileInputStream(manifest)));
// xpath will return empty string when there's no match
if (activityName == null || activityName.length() == 0) {
activityName = null;
} else {
mSdkLog.printf("Found main project activity: %1$s\n", activityName);
}
} catch (FileNotFoundException e) {
// this shouldn't happen as we test it above.
errorAndExit("No AndroidManifest.xml file found in main project.");
return; // this is not strictly needed because errorAndExit will stop the execution,
// but this makes the java compiler happy, wrt to uninitialized variables.
} catch (XPathExpressionException e) {
// looks like the main manifest is not valid.
errorAndExit("Unable to parse main project manifest to get information.");
return; // this is not strictly needed because errorAndExit will stop the execution,
// but this makes the java compiler happy, wrt to uninitialized variables.
}
// now get the target hash
ProjectProperties p = ProjectProperties.load(parentProject.getAbsolutePath(),
PropertyType.DEFAULT);
String targetHash = p.getProperty(ProjectProperties.PROPERTY_TARGET);
if (targetHash == null) {
errorAndExit("Couldn't find the main project target");
}
// and resolve it.
IAndroidTarget target = mSdkManager.getTargetFromHashString(targetHash);
if (target == null) {
errorAndExit(
"Unable to resolve main project target '%1$s'. You may want to install the platform in your SDK.",
targetHash);
}
mSdkLog.printf("Found main project target: %1$s\n", target.getFullName());
ProjectCreator creator = new ProjectCreator(mOsSdkFolder,
mSdkCommandLine.isVerbose() ? OutputLevel.VERBOSE :
mSdkCommandLine.isSilent() ? OutputLevel.SILENT :
OutputLevel.NORMAL,
mSdkLog);
String projectName = mSdkCommandLine.getParamName();
if (projectName != null &&
!ProjectCreator.RE_PROJECT_NAME.matcher(projectName).matches()) {
errorAndExit(
"Project name '%1$s' contains invalid characters.\nAllowed characters are: %2$s",
projectName, ProjectCreator.CHARS_PROJECT_NAME);
return;
}
creator.createProject(projectDir,
projectName,
packageName,
activityName,
target,
pathToMainProject);
}
/**
* Updates an existing Android project based on command-line parameters
*/

View File

@@ -31,28 +31,30 @@ class SdkCommandLine extends CommandLineProcessor {
public final static String VERB_DELETE = "delete";
public final static String VERB_UPDATE = "update";
public static final String OBJECT_AVD = "avd";
public static final String OBJECT_AVDS = "avds";
public static final String OBJECT_TARGET = "target";
public static final String OBJECT_TARGETS = "targets";
public static final String OBJECT_PROJECT = "project";
public static final String OBJECT_ADB = "adb";
public static final String OBJECT_AVD = "avd";
public static final String OBJECT_AVDS = "avds";
public static final String OBJECT_TARGET = "target";
public static final String OBJECT_TARGETS = "targets";
public static final String OBJECT_PROJECT = "project";
public static final String OBJECT_TEST_PROJECT = "test-project";
public static final String OBJECT_ADB = "adb";
public static final String ARG_ALIAS = "alias";
public static final String ARG_ACTIVITY = "activity";
public static final String ARG_ALIAS = "alias";
public static final String ARG_ACTIVITY = "activity";
public static final String KEY_ACTIVITY = ARG_ACTIVITY;
public static final String KEY_PACKAGE = "package";
public static final String KEY_MODE = "mode";
public static final String KEY_TARGET_ID = OBJECT_TARGET;
public static final String KEY_NAME = "name";
public static final String KEY_PATH = "path";
public static final String KEY_FILTER = "filter";
public static final String KEY_SKIN = "skin";
public static final String KEY_SDCARD = "sdcard";
public static final String KEY_FORCE = "force";
public static final String KEY_RENAME = "rename";
public static final String KEY_SUBPROJECTS = "subprojects";
public static final String KEY_ACTIVITY = ARG_ACTIVITY;
public static final String KEY_PACKAGE = "package";
public static final String KEY_MODE = "mode";
public static final String KEY_TARGET_ID = OBJECT_TARGET;
public static final String KEY_NAME = "name";
public static final String KEY_PATH = "path";
public static final String KEY_FILTER = "filter";
public static final String KEY_SKIN = "skin";
public static final String KEY_SDCARD = "sdcard";
public static final String KEY_FORCE = "force";
public static final String KEY_RENAME = "rename";
public static final String KEY_SUBPROJECTS = "subprojects";
public static final String KEY_MAIN_PROJECT = "main";
/**
* Action definitions for SdkManager command line.
@@ -89,6 +91,11 @@ class SdkCommandLine extends CommandLineProcessor {
{ VERB_UPDATE, OBJECT_PROJECT,
"Updates an Android Project (must have an AndroidManifest.xml)." },
{ VERB_CREATE, OBJECT_TEST_PROJECT,
"Creates a new Android Test Project." },
{ VERB_UPDATE, OBJECT_TEST_PROJECT,
"Updates an Android Test Project (must have an AndroidManifest.xml)." },
{ VERB_UPDATE, OBJECT_ADB,
"Updates adb to support the USB devices declared in the SDK add-ons." },
};
@@ -167,6 +174,18 @@ class SdkCommandLine extends CommandLineProcessor {
VERB_CREATE, OBJECT_PROJECT, "n", KEY_NAME,
"Project name", null);
// --- create test-project ---
define(Mode.STRING, true,
VERB_CREATE, OBJECT_TEST_PROJECT,
"p", KEY_PATH,
"Location path of new project", null);
define(Mode.STRING, false,
VERB_CREATE, OBJECT_TEST_PROJECT, "n", KEY_NAME,
"Project name", null);
define(Mode.STRING, true,
VERB_CREATE, OBJECT_TEST_PROJECT, "m", KEY_MAIN_PROJECT,
"Location path of the project to test, relative to the new project", null);
// --- update project ---
define(Mode.STRING, true,
@@ -185,6 +204,17 @@ class SdkCommandLine extends CommandLineProcessor {
VERB_UPDATE, OBJECT_PROJECT,
"s", KEY_SUBPROJECTS,
"Also update any projects in sub-folders, such as test projects.", false);
// --- update test project ---
define(Mode.STRING, true,
VERB_UPDATE, OBJECT_TEST_PROJECT,
"p", KEY_PATH,
"Location path of the project", null);
define(Mode.STRING, true,
VERB_UPDATE, OBJECT_TEST_PROJECT,
"m", KEY_MAIN_PROJECT,
"Location path of the project to test", null);
}
@Override
@@ -255,4 +285,11 @@ class SdkCommandLine extends CommandLineProcessor {
public boolean getParamSubProject() {
return ((Boolean) getValue(null, OBJECT_PROJECT, KEY_SUBPROJECTS)).booleanValue();
}
// -- some helpers for test-project action flags
/** Helper to retrieve the --main value. */
public String getParamTestProjectMain() {
return ((String) getValue(null, OBJECT_TEST_PROJECT, KEY_MAIN_PROJECT));
}
}

View File

@@ -20,6 +20,7 @@ import com.android.sdklib.IAndroidTarget;
import com.android.sdklib.ISdkLog;
import com.android.sdklib.SdkConstants;
import com.android.sdklib.internal.project.ProjectProperties.PropertyType;
import com.android.sdklib.xml.AndroidManifest;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
@@ -44,8 +45,7 @@ import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
/**
* Creates the basic files needed to get an Android project up and running. Also
* allows creation of IntelliJ project files.
* Creates the basic files needed to get an Android project up and running.
*
* @hide
*/
@@ -55,13 +55,22 @@ public class ProjectCreator {
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". */
/** Activity name substitution string used in template files, i.e. "ACTIVITY_NAME".
* @deprecated This is only used for older templates. For new ones see
* {@link #PH_ACTIVITY_ENTRY_NAME}, and {@link #PH_ACTIVITY_CLASS_NAME}. */
private final static String PH_ACTIVITY_NAME = "ACTIVITY_NAME";
/** Activity name substitution string used in manifest templates, i.e. "ACTIVITY_ENTRY_NAME".*/
private final static String PH_ACTIVITY_ENTRY_NAME = "ACTIVITY_ENTRY_NAME";
/** Activity name substitution string used in class templates, i.e. "ACTIVITY_CLASS_NAME".*/
private final static String PH_ACTIVITY_CLASS_NAME = "ACTIVITY_CLASS_NAME";
/** Activity FQ-name substitution string used in class templates, i.e. "ACTIVITY_FQ_NAME".*/
private final static String PH_ACTIVITY_FQ_NAME = "ACTIVITY_FQ_NAME";
/** Original Activity class name substitution string used in class templates, i.e.
* "ACTIVITY_TESTED_CLASS_NAME".*/
private final static String PH_ACTIVITY_TESTED_CLASS_NAME = "ACTIVITY_TESTED_CLASS_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";
/** Pattern for characters accepted in a project name. Since this will be used as a
* directory name, we're being a bit conservative on purpose: dot and space cannot be used. */
public static final Pattern RE_PROJECT_NAME = Pattern.compile("[a-zA-Z0-9_]+");
@@ -135,17 +144,16 @@ public class ProjectCreator {
* {@link #RE_PROJECT_NAME} regex.
* @param packageName the package of the project. The name must match the
* {@link #RE_PACKAGE_NAME} regex.
* @param activityName the activity of the project as it will appear in the manifest. Can be
* @param activityEntry the activity of the project as it will appear in the manifest. Can be
* null if no activity should be created. The name must match the
* {@link #RE_ACTIVITY_NAME} regex.
* @param target the project target.
* @param isTestProject whether the project to create is a test project. Caller should
* initially call this will false. The method will call itself back to create
* a test project as needed.
* @param pathToMainProject if non-null the project will be setup to test a main project
* located at the given path.
*/
public void createProject(String folderPath, String projectName,
String packageName, String activityName, IAndroidTarget target,
boolean isTestProject) {
String packageName, String activityEntry, IAndroidTarget target,
String pathToMainProject) {
// create project folder if it does not exist
File projectFolder = new File(folderPath);
@@ -185,6 +193,8 @@ public class ProjectCreator {
}
try {
boolean isTestProject = pathToMainProject != null;
// first create the project properties.
// location of the SDK goes in localProperty
@@ -202,9 +212,16 @@ public class ProjectCreator {
// create a build.properties file with just the application package
ProjectProperties buildProperties = ProjectProperties.create(folderPath,
PropertyType.BUILD);
buildProperties.setProperty(ProjectProperties.PROPERTY_APP_PACKAGE, packageName);
if (isTestProject == true) {
buildProperties.setProperty(ProjectProperties.PROPERTY_TESTED_PROJECT, "..");
// only put application.package for older target where the rules file didn't.
// grab it through xpath
if (target.getVersion().getApiLevel() < 4) {
buildProperties.setProperty(ProjectProperties.PROPERTY_APP_PACKAGE, packageName);
}
if (isTestProject) {
buildProperties.setProperty(ProjectProperties.PROPERTY_TESTED_PROJECT,
pathToMainProject);
}
buildProperties.save();
@@ -221,19 +238,76 @@ public class ProjectCreator {
// put this path in the place-holder map for project files that needs to list
// files manually.
keywords.put(PH_JAVA_FOLDER, packagePath);
keywords.put(PH_PACKAGE, packageName);
if (activityName != null) {
keywords.put(PH_ACTIVITY_NAME, activityName);
// compute some activity related information
String fqActivityName = null, activityPath = null, activityClassName = null;
String originalActivityEntry = activityEntry;
String originalActivityClassName = null;
if (activityEntry != null) {
if (isTestProject) {
// append Test so that it doesn't collide with the main project activity.
activityEntry += "Test";
// get the classname from the original activity entry.
int pos = originalActivityEntry.lastIndexOf('.');
if (pos != -1) {
originalActivityClassName = originalActivityEntry.substring(pos + 1);
} else {
originalActivityClassName = originalActivityEntry;
}
}
// get the fully qualified name of the activity
fqActivityName = AndroidManifest.combinePackageAndClassName(packageName,
activityEntry);
// get the activity path (replace the . to /)
activityPath = stripString(fqActivityName.replace(".", File.separator),
File.separatorChar);
// remove the last segment, so that we only have the path to the activity, but
// not the activity filename itself.
activityPath = activityPath.substring(0,
activityPath.lastIndexOf(File.separatorChar));
// finally, get the class name for the activity
activityClassName = fqActivityName.substring(fqActivityName.lastIndexOf('.') + 1);
}
// at this point we have the following for the activity:
// activityEntry: this is the manifest entry. For instance .MyActivity
// fqActivityName: full-qualified class name: com.foo.MyActivity
// activityClassName: only the classname: MyActivity
// originalActivityClassName: the classname of the activity being tested (if applicable)
// Add whatever activity info is needed in the place-holder map.
// Older templates only expect ACTIVITY_NAME to be the same (and unmodified for tests).
if (target.getVersion().getApiLevel() < 4) { // legacy
if (originalActivityEntry != null) {
keywords.put(PH_ACTIVITY_NAME, originalActivityEntry);
}
} else {
// newer templates make a difference between the manifest entries, classnames,
// as well as the main and test classes.
if (activityEntry != null) {
keywords.put(PH_ACTIVITY_ENTRY_NAME, activityEntry);
keywords.put(PH_ACTIVITY_CLASS_NAME, activityClassName);
keywords.put(PH_ACTIVITY_FQ_NAME, fqActivityName);
if (originalActivityClassName != null) {
keywords.put(PH_ACTIVITY_TESTED_CLASS_NAME, originalActivityClassName);
}
}
}
// 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);
if (activityClassName != null) {
// Use the activity class name as project name
keywords.put(PH_PROJECT_NAME, activityClassName);
} else {
// We need a project name. Just pick up the basename of the project
// directory.
@@ -242,21 +316,21 @@ public class ProjectCreator {
}
}
// create the source folder and the java package folders.
String srcFolderPath = SdkConstants.FD_SOURCES + File.separator + packagePath;
File sourceFolder = createDirs(projectFolder, srcFolderPath);
String javaTemplate = "java_file.template";
String activityFileName = activityName + ".java";
if (isTestProject) {
javaTemplate = "java_tests_file.template";
activityFileName = activityName + "Test.java";
}
installTemplate(javaTemplate, new File(sourceFolder, activityFileName),
keywords, target);
// create the source folder for the activity
if (activityClassName != null) {
String srcActivityFolderPath = SdkConstants.FD_SOURCES + File.separator + activityPath;
File sourceFolder = createDirs(projectFolder, srcActivityFolderPath);
// create the generate source folder
srcFolderPath = SdkConstants.FD_GEN_SOURCES + File.separator + packagePath;
sourceFolder = createDirs(projectFolder, srcFolderPath);
String javaTemplate = isTestProject ? "java_tests_file.template"
: "java_file.template";
String activityFileName = activityClassName + ".java";
installTemplate(javaTemplate, new File(sourceFolder, activityFileName),
keywords, target);
} else {
// we should at least create 'src'
createDirs(projectFolder, SdkConstants.FD_SOURCES);
}
// create other useful folders
File resourceFodler = createDirs(projectFolder, SdkConstants.FD_RESOURCES);
@@ -287,16 +361,6 @@ public class ProjectCreator {
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) {
// create the test project folder.
createDirs(projectFolder, FOLDER_TESTS);
File testProjectFolder = new File(folderPath, FOLDER_TESTS);
createProject(testProjectFolder.getAbsolutePath(), projectName, packageName,
activityName, target, true /*isTestProject*/);
}
} catch (ProjectCreateException e) {
mLog.error(e, null);
} catch (IOException e) {

View File

@@ -251,8 +251,10 @@ public final class ProjectProperties {
writer.write(comment);
}
String value = entry.getValue();
value = value.replaceAll("\\\\", "\\\\\\\\");
writer.write(String.format("%s=%s\n", entry.getKey(), value));
if (value != null) {
value = value.replaceAll("\\\\", "\\\\\\\\");
writer.write(String.format("%s=%s\n", entry.getKey(), value));
}
}
// close the file to flush

View File

@@ -17,10 +17,10 @@
package com.android.sdklib.xml;
/**
* Constants for nodes and attributes of the AndroidManifest.xml file.
* Helper and Constants for the AndroidManifest.xml file.
*
*/
public final class ManifestConstants {
public final class AndroidManifest {
public final static String NODE_MANIFEST = "manifest"; //$NON-NLS-1$
public final static String NODE_APPLICATION = "application"; //$NON-NLS-1$
@@ -42,4 +42,39 @@ public final class ManifestConstants {
public final static String ATTRIBUTE_MIN_SDK_VERSION = "minSdkVersion"; //$NON-NLS-$
public final static String ATTRIBUTE_TARGET_PACKAGE = "targetPackage"; //$NON-NLS-1$
public final static String ATTRIBUTE_EXPORTED = "exported"; //$NON-NLS-1$
/**
* Combines a java package, with a class value from the manifest to make a fully qualified
* class name
* @param javaPackage the java package from the manifest.
* @param className the class name from the manifest.
* @return the fully qualified class name.
*/
public static String combinePackageAndClassName(String javaPackage, String className) {
if (className == null || className.length() == 0) {
return javaPackage;
}
if (javaPackage == null || javaPackage.length() == 0) {
return className;
}
// the class name can be a subpackage (starts with a '.'
// char), a simple class name (no dot), or a full java package
boolean startWithDot = (className.charAt(0) == '.');
boolean hasDot = (className.indexOf('.') != -1);
if (startWithDot || hasDot == false) {
// add the concatenation of the package and class name
if (startWithDot) {
return javaPackage + className;
} else {
return javaPackage + '.' + className;
}
} else {
// just add the class as it should be a fully qualified java name.
return className;
}
}
}

View File

@@ -18,7 +18,9 @@ package com.android.sdklib.xml;
import com.android.sdklib.SdkConstants;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javax.xml.XMLConstants;
import javax.xml.namespace.NamespaceContext;
@@ -39,7 +41,8 @@ public class AndroidXPathFactory {
private final static AndroidNamespaceContext sThis = new AndroidNamespaceContext(
DEFAULT_NS_PREFIX);
private String mAndroidPrefix;
private final String mAndroidPrefix;
private final List<String> mAndroidPrefixes = new ArrayList<String>();
/**
* Returns the default {@link AndroidNamespaceContext}.
@@ -54,6 +57,7 @@ public class AndroidXPathFactory {
*/
public AndroidNamespaceContext(String androidPrefix) {
mAndroidPrefix = androidPrefix;
mAndroidPrefixes.add(mAndroidPrefix);
}
public String getNamespaceURI(String prefix) {
@@ -67,14 +71,18 @@ public class AndroidXPathFactory {
}
public String getPrefix(String namespaceURI) {
// This isn't necessary for our use.
assert false;
if (SdkConstants.NS_RESOURCES.equals(namespaceURI)) {
return mAndroidPrefix;
}
return null;
}
public Iterator<?> getPrefixes(String namespaceURI) {
// This isn't necessary for our use.
assert false;
if (SdkConstants.NS_RESOURCES.equals(namespaceURI)) {
return mAndroidPrefixes.iterator();
}
return null;
}
}