aboutsummaryrefslogtreecommitdiffstats
path: root/sdkmanager/app
diff options
context:
space:
mode:
authorThe Android Open Source Project <initial-contribution@android.com>2009-03-03 19:29:09 -0800
committerThe Android Open Source Project <initial-contribution@android.com>2009-03-03 19:29:09 -0800
commit55a2c71f27d3e0b8344597c7f281e687cb7aeb1b (patch)
treeecd18b995aea8eeeb8b3823266280d41245bf0f7 /sdkmanager/app
parent82ea7a177797b844b252effea5c7c7c5d63ea4ac (diff)
downloadsdk-55a2c71f27d3e0b8344597c7f281e687cb7aeb1b.zip
sdk-55a2c71f27d3e0b8344597c7f281e687cb7aeb1b.tar.gz
sdk-55a2c71f27d3e0b8344597c7f281e687cb7aeb1b.tar.bz2
auto import from //depot/cupcake/@135843
Diffstat (limited to 'sdkmanager/app')
-rw-r--r--sdkmanager/app/.classpath11
-rw-r--r--sdkmanager/app/.project17
-rw-r--r--sdkmanager/app/Android.mk5
-rw-r--r--sdkmanager/app/etc/Android.mk8
-rwxr-xr-xsdkmanager/app/etc/android84
-rwxr-xr-xsdkmanager/app/etc/android.bat51
-rw-r--r--sdkmanager/app/etc/manifest.txt1
-rw-r--r--sdkmanager/app/src/Android.mk16
-rw-r--r--sdkmanager/app/src/com/android/sdkmanager/CommandLineProcessor.java791
-rw-r--r--sdkmanager/app/src/com/android/sdkmanager/Main.java813
-rw-r--r--sdkmanager/app/src/com/android/sdkmanager/SdkCommandLine.java220
-rw-r--r--sdkmanager/app/tests/com/android/sdkmanager/CommandLineProcessorTest.java186
-rw-r--r--sdkmanager/app/tests/com/android/sdkmanager/MockStdLogger.java48
-rw-r--r--sdkmanager/app/tests/com/android/sdkmanager/SdkCommandLineTest.java141
14 files changed, 2392 insertions, 0 deletions
diff --git a/sdkmanager/app/.classpath b/sdkmanager/app/.classpath
new file mode 100644
index 0000000..cbd9d37
--- /dev/null
+++ b/sdkmanager/app/.classpath
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="src" path="src"/>
+ <classpathentry kind="src" path="tests"/>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+ <classpathentry combineaccessrules="false" kind="src" path="/SdkLib"/>
+ <classpathentry combineaccessrules="false" kind="src" path="/AndroidPrefs"/>
+ <classpathentry combineaccessrules="false" kind="src" path="/SdkUiLib"/>
+ <classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/3"/>
+ <classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/sdkmanager/app/.project b/sdkmanager/app/.project
new file mode 100644
index 0000000..e12c17d
--- /dev/null
+++ b/sdkmanager/app/.project
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>SdkManager</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ </natures>
+</projectDescription>
diff --git a/sdkmanager/app/Android.mk b/sdkmanager/app/Android.mk
new file mode 100644
index 0000000..24ba61f
--- /dev/null
+++ b/sdkmanager/app/Android.mk
@@ -0,0 +1,5 @@
+# Copyright 2007 The Android Open Source Project
+#
+SDKMANAGERAPP_LOCAL_DIR := $(call my-dir)
+include $(SDKMANAGERAPP_LOCAL_DIR)/etc/Android.mk
+include $(SDKMANAGERAPP_LOCAL_DIR)/src/Android.mk
diff --git a/sdkmanager/app/etc/Android.mk b/sdkmanager/app/etc/Android.mk
new file mode 100644
index 0000000..8723cd8
--- /dev/null
+++ b/sdkmanager/app/etc/Android.mk
@@ -0,0 +1,8 @@
+# Copyright 2008 The Android Open Source Project
+#
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_PREBUILT_EXECUTABLES := android
+include $(BUILD_HOST_PREBUILT)
+
diff --git a/sdkmanager/app/etc/android b/sdkmanager/app/etc/android
new file mode 100755
index 0000000..af4042b
--- /dev/null
+++ b/sdkmanager/app/etc/android
@@ -0,0 +1,84 @@
+#!/bin/sh
+# Copyright 2005-2007, The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Set up prog to be the path of this script, including following symlinks,
+# and set up progdir to be the fully-qualified pathname of its directory.
+prog="$0"
+while [ -h "${prog}" ]; do
+ newProg=`/bin/ls -ld "${prog}"`
+ newProg=`expr "${newProg}" : ".* -> \(.*\)$"`
+ if expr "x${newProg}" : 'x/' >/dev/null; then
+ prog="${newProg}"
+ else
+ progdir=`dirname "${prog}"`
+ prog="${progdir}/${newProg}"
+ fi
+done
+oldwd=`pwd`
+progdir=`dirname "${prog}"`
+cd "${progdir}"
+progdir=`pwd`
+prog="${progdir}"/`basename "${prog}"`
+cd "${oldwd}"
+
+jarfile=sdkmanager.jar
+frameworkdir="$progdir"
+libdir="$progdir"
+if [ ! -r "$frameworkdir/$jarfile" ]
+then
+ frameworkdir=`dirname "$progdir"`/tools/lib
+ libdir=`dirname "$progdir"`/tools/lib
+fi
+if [ ! -r "$frameworkdir/$jarfile" ]
+then
+ frameworkdir=`dirname "$progdir"`/framework
+ libdir=`dirname "$progdir"`/lib
+fi
+if [ ! -r "$frameworkdir/$jarfile" ]
+then
+ echo `basename "$prog"`": can't find $jarfile"
+ exit 1
+fi
+
+
+# Check args.
+if [ debug = "$1" ]; then
+ # add this in for debugging
+ java_debug=-agentlib:jdwp=transport=dt_socket,server=y,address=8050,suspend=y
+ shift 1
+else
+ java_debug=
+fi
+
+# Mac OS X needs an additional arg, or you get an "illegal thread" complaint.
+if [ `uname` = "Darwin" ]; then
+ os_opts="-XstartOnFirstThread"
+ #because Java 1.6 is 64 bits only and SWT doesn't support this, we force the usage of java 1.5
+ java_cmd="/System/Library/Frameworks/JavaVM.framework/Versions/1.5/Commands/java"
+else
+ os_opts=
+ java_cmd="java"
+fi
+
+if [ "$OSTYPE" = "cygwin" ] ; then
+ jarpath=`cygpath -w "$frameworkdir/$jarfile"`
+ progdir=`cygpath -w "$progdir"`
+else
+ jarpath="$frameworkdir/$jarfile"
+fi
+
+# need to use "java.ext.dirs" because "-jar" causes classpath to be ignored
+# might need more memory, e.g. -Xmx128M
+exec "$java_cmd" -Xmx256M $os_opts $java_debug -Djava.ext.dirs="$frameworkdir" -Djava.library.path="$libdir" -Dcom.android.sdkmanager.toolsdir="$progdir" -jar "$jarpath" "$@"
diff --git a/sdkmanager/app/etc/android.bat b/sdkmanager/app/etc/android.bat
new file mode 100755
index 0000000..de950ed
--- /dev/null
+++ b/sdkmanager/app/etc/android.bat
@@ -0,0 +1,51 @@
+@echo off
+rem Copyright (C) 2007 The Android Open Source Project
+rem
+rem Licensed under the Apache License, Version 2.0 (the "License");
+rem you may not use this file except in compliance with the License.
+rem You may obtain a copy of the License at
+rem
+rem http://www.apache.org/licenses/LICENSE-2.0
+rem
+rem Unless required by applicable law or agreed to in writing, software
+rem distributed under the License is distributed on an "AS IS" BASIS,
+rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+rem See the License for the specific language governing permissions and
+rem limitations under the License.
+
+rem don't modify the caller's environment
+setlocal
+
+rem Set up prog to be the path of this script, including following symlinks,
+rem and set up progdir to be the fully-qualified pathname of its directory.
+set prog=%~f0
+
+rem Grab current directory before we change it
+set workdir=%cd%
+
+rem Change current directory and drive to where the script is, to avoid
+rem issues with directories containing whitespaces.
+cd /d %~dp0
+
+set jarfile=sdkmanager.jar
+set frameworkdir=
+set libdir=
+
+if exist %frameworkdir%%jarfile% goto JarFileOk
+ set frameworkdir=lib\
+ set libdir=lib\
+
+if exist %frameworkdir%%jarfile% goto JarFileOk
+ set frameworkdir=..\framework\
+ set libdir=..\lib\
+
+:JarFileOk
+
+if debug NEQ "%1" goto NoDebug
+ set java_debug=-agentlib:jdwp=transport=dt_socket,server=y,address=8050,suspend=y
+ shift 1
+:NoDebug
+
+set jarpath=%frameworkdir%%jarfile%
+
+call java %java_debug% -Djava.ext.dirs=%frameworkdir% -Djava.library.path=%libdir% -Dcom.android.sdkmanager.toolsdir= -Dcom.android.sdkmanager.workdir="%workdir%" -jar %jarpath% %*
diff --git a/sdkmanager/app/etc/manifest.txt b/sdkmanager/app/etc/manifest.txt
new file mode 100644
index 0000000..5676634
--- /dev/null
+++ b/sdkmanager/app/etc/manifest.txt
@@ -0,0 +1 @@
+Main-Class: com.android.sdkmanager.Main
diff --git a/sdkmanager/app/src/Android.mk b/sdkmanager/app/src/Android.mk
new file mode 100644
index 0000000..b508076
--- /dev/null
+++ b/sdkmanager/app/src/Android.mk
@@ -0,0 +1,16 @@
+# Copyright 2007 The Android Open Source Project
+#
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_JAR_MANIFEST := ../etc/manifest.txt
+LOCAL_JAVA_LIBRARIES := \
+ androidprefs \
+ sdklib \
+ sdkuilib
+LOCAL_MODULE := sdkmanager
+
+include $(BUILD_HOST_JAVA_LIBRARY)
+
diff --git a/sdkmanager/app/src/com/android/sdkmanager/CommandLineProcessor.java b/sdkmanager/app/src/com/android/sdkmanager/CommandLineProcessor.java
new file mode 100644
index 0000000..9f3fb99
--- /dev/null
+++ b/sdkmanager/app/src/com/android/sdkmanager/CommandLineProcessor.java
@@ -0,0 +1,791 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdkmanager;
+
+import com.android.sdklib.ISdkLog;
+
+import java.util.HashMap;
+import java.util.Map.Entry;
+
+/**
+ * Parses the command-line and stores flags needed or requested.
+ * <p/>
+ * This is a base class. To be useful you want to:
+ * <ul>
+ * <li>override it.
+ * <li>pass an action array to the constructor.
+ * <li>define flags for your actions.
+ * </ul>
+ * <p/>
+ * To use, call {@link #parseArgs(String[])} and then
+ * call {@link #getValue(String, String, String)}.
+ */
+public class CommandLineProcessor {
+
+ /** Internal verb name for internally hidden flags. */
+ public final static String GLOBAL_FLAG_VERB = "@@internal@@";
+
+ /** String to use when the verb doesn't need any object. */
+ public final static String NO_VERB_OBJECT = "";
+
+ /** The global help flag. */
+ public static final String KEY_HELP = "help";
+ /** The global verbose flag. */
+ public static final String KEY_VERBOSE = "verbose";
+ /** The global silent flag. */
+ public static final String KEY_SILENT = "silent";
+
+ /** Verb requested by the user. Null if none specified, which will be an error. */
+ private String mVerbRequested;
+ /** Direct object requested by the user. Can be null. */
+ private String mDirectObjectRequested;
+
+ /**
+ * Action definitions.
+ * <p/>
+ * Each entry is a string array with:
+ * <ul>
+ * <li> the verb.
+ * <li> a direct object (use #NO_VERB_OBJECT if there's no object).
+ * <li> a description.
+ * <li> an alternate form for the object (e.g. plural).
+ * </ul>
+ */
+ private final String[][] mActions;
+
+ private static final int ACTION_VERB_INDEX = 0;
+ private static final int ACTION_OBJECT_INDEX = 1;
+ private static final int ACTION_DESC_INDEX = 2;
+ private static final int ACTION_ALT_OBJECT_INDEX = 3;
+
+ /**
+ * The map of all defined arguments.
+ * <p/>
+ * The key is a string "verb/directObject/longName".
+ */
+ private final HashMap<String, Arg> mArguments = new HashMap<String, Arg>();
+ /** Logger */
+ private final ISdkLog mLog;
+
+ public CommandLineProcessor(ISdkLog logger, String[][] actions) {
+ mLog = logger;
+ mActions = actions;
+
+ define(MODE.BOOLEAN, false, GLOBAL_FLAG_VERB, NO_VERB_OBJECT, "v", KEY_VERBOSE,
+ "Verbose mode: errors, warnings and informational messages are printed.",
+ false);
+ define(MODE.BOOLEAN, false, GLOBAL_FLAG_VERB, NO_VERB_OBJECT, "s", KEY_SILENT,
+ "Silent mode: only errors are printed out.",
+ false);
+ define(MODE.BOOLEAN, false, GLOBAL_FLAG_VERB, NO_VERB_OBJECT, "h", KEY_HELP,
+ "This help.",
+ false);
+ }
+
+ //------------------
+ // Helpers to get flags values
+
+ /** Helper that returns true if --verbose was requested. */
+ public boolean isVerbose() {
+ return ((Boolean) getValue(GLOBAL_FLAG_VERB, NO_VERB_OBJECT, KEY_VERBOSE)).booleanValue();
+ }
+
+ /** Helper that returns true if --silent was requested. */
+ public boolean isSilent() {
+ return ((Boolean) getValue(GLOBAL_FLAG_VERB, NO_VERB_OBJECT, KEY_SILENT)).booleanValue();
+ }
+
+ /** Helper that returns true if --help was requested. */
+ public boolean isHelpRequested() {
+ return ((Boolean) getValue(GLOBAL_FLAG_VERB, NO_VERB_OBJECT, KEY_HELP)).booleanValue();
+ }
+
+ /** Returns the verb name from the command-line. Can be null. */
+ public String getVerb() {
+ return mVerbRequested;
+ }
+
+ /** Returns the direct object name from the command-line. Can be null. */
+ public String getDirectObject() {
+ return mDirectObjectRequested;
+ }
+
+ //------------------
+
+ /**
+ * Raw access to parsed parameter values.
+ * <p/>
+ * The default is to scan all parameters. Parameters that have been explicitly set on the
+ * command line are returned first. Otherwise one with a non-null value is returned.
+ * <p/>
+ * Both a verb and a direct object filter can be specified. When they are non-null they limit
+ * the scope of the search.
+ * <p/>
+ * If nothing has been found, return the last default value seen matching the filter.
+ *
+ * @param verb The verb name, including {@link #GLOBAL_FLAG_VERB}. If null, all possible
+ * verbs that match the direct object condition will be examined and the first
+ * value set will be used.
+ * @param directObject The direct object name, including {@link #NO_VERB_OBJECT}. If null,
+ * all possible direct objects that match the verb condition will be examined and
+ * the first value set will be used.
+ * @param longFlagName The long flag name for the given action. Mandatory. Cannot be null.
+ * @return The current value object stored in the parameter, which depends on the argument mode.
+ */
+ public Object getValue(String verb, String directObject, String longFlagName) {
+
+ if (verb != null && directObject != null) {
+ String key = verb + "/" + directObject + "/" + longFlagName;
+ Arg arg = mArguments.get(key);
+ return arg.getCurrentValue();
+ }
+
+ Object lastDefault = null;
+ for (Arg arg : mArguments.values()) {
+ if (arg.getLongArg().equals(longFlagName)) {
+ if (verb == null || arg.getVerb().equals(verb)) {
+ if (directObject == null || arg.getDirectObject().equals(directObject)) {
+ if (arg.isInCommandLine()) {
+ return arg.getCurrentValue();
+ }
+ if (arg.getCurrentValue() != null) {
+ lastDefault = arg.getCurrentValue();
+ }
+ }
+ }
+ }
+ }
+
+ return lastDefault;
+ }
+
+ /**
+ * Internal setter for raw parameter value.
+ * @param verb The verb name, including {@link #GLOBAL_FLAG_VERB}.
+ * @param directObject The direct object name, including {@link #NO_VERB_OBJECT}.
+ * @param longFlagName The long flag name for the given action.
+ * @param value The new current value object stored in the parameter, which depends on the
+ * argument mode.
+ */
+ protected void setValue(String verb, String directObject, String longFlagName, Object value) {
+ String key = verb + "/" + directObject + "/" + longFlagName;
+ Arg arg = mArguments.get(key);
+ arg.setCurrentValue(value);
+ }
+
+ /**
+ * Parses the command-line arguments.
+ * <p/>
+ * This method will exit and not return if a parsing error arise.
+ *
+ * @param args The arguments typically received by a main method.
+ */
+ public void parseArgs(String[] args) {
+ String needsHelp = null;
+ String verb = null;
+ String directObject = null;
+
+ try {
+ int n = args.length;
+ for (int i = 0; i < n; i++) {
+ Arg arg = null;
+ String a = args[i];
+ if (a.startsWith("--")) {
+ arg = findLongArg(verb, directObject, a.substring(2));
+ } else if (a.startsWith("-")) {
+ arg = findShortArg(verb, directObject, a.substring(1));
+ }
+
+ // No matching argument name found
+ if (arg == null) {
+ // Does it looks like a dashed parameter?
+ if (a.startsWith("-")) {
+ if (verb == null || directObject == null) {
+ // It looks like a dashed parameter and we don't have a a verb/object
+ // set yet, the parameter was just given too early.
+
+ needsHelp = String.format(
+ "Flag '%1$s' is not a valid global flag. Did you mean to specify it after the verb/object name?",
+ a);
+ return;
+ } else {
+ // It looks like a dashed parameter and but it is unknown by this
+ // verb-object combination
+
+ needsHelp = String.format(
+ "Flag '%1$s' is not valid for '%2$s %3$s'.",
+ a, verb, directObject);
+ return;
+ }
+ }
+
+ if (verb == null) {
+ // Fill verb first. Find it.
+ for (String[] actionDesc : mActions) {
+ if (actionDesc[ACTION_VERB_INDEX].equals(a)) {
+ verb = a;
+ break;
+ }
+ }
+
+ // Error if it was not a valid verb
+ if (verb == null) {
+ needsHelp = String.format(
+ "Expected verb after global parameters but found '%1$s' instead.",
+ a);
+ return;
+ }
+
+ } else if (directObject == null) {
+ // Then fill the direct object. Find it.
+ for (String[] actionDesc : mActions) {
+ if (actionDesc[ACTION_VERB_INDEX].equals(verb)) {
+ if (actionDesc[ACTION_OBJECT_INDEX].equals(a)) {
+ directObject = a;
+ break;
+ } else if (actionDesc.length > ACTION_ALT_OBJECT_INDEX &&
+ actionDesc[ACTION_ALT_OBJECT_INDEX].equals(a)) {
+ // if the alternate form exist and is used, we internally
+ // only memorize the default direct object form.
+ directObject = actionDesc[ACTION_OBJECT_INDEX];
+ break;
+ }
+ }
+ }
+
+ // Error if it was not a valid object for that verb
+ if (directObject == null) {
+ needsHelp = String.format(
+ "Expected verb after global parameters but found '%1$s' instead.",
+ a);
+ return;
+
+ }
+ }
+ } else if (arg != null) {
+ // This argument was present on the command line
+ arg.setInCommandLine(true);
+
+ // Process keyword
+ String error = null;
+ if (arg.getMode().needsExtra()) {
+ if (++i >= n) {
+ needsHelp = String.format("Missing argument for flag %1$s.", a);
+ return;
+ }
+
+ error = arg.getMode().process(arg, args[i]);
+ } else {
+ error = arg.getMode().process(arg, null);
+
+ // If we just toggled help, we want to exit now without printing any error.
+ // We do this test here only when a Boolean flag is toggled since booleans
+ // are the only flags that don't take parameters and help is a boolean.
+ if (isHelpRequested()) {
+ printHelpAndExit(null);
+ // The call above should terminate however in unit tests we override
+ // it so we still need to return here.
+ return;
+ }
+ }
+
+ if (error != null) {
+ needsHelp = String.format("Invalid usage for flag %1$s: %2$s.", a, error);
+ return;
+ }
+ }
+ }
+
+ if (needsHelp == null) {
+ if (verb == null) {
+ needsHelp = "Missing verb name.";
+ } else {
+ if (directObject == null) {
+ // Make sure this verb has an optional direct object
+ for (String[] actionDesc : mActions) {
+ if (actionDesc[ACTION_VERB_INDEX].equals(verb) &&
+ actionDesc[ACTION_OBJECT_INDEX].equals(NO_VERB_OBJECT)) {
+ directObject = NO_VERB_OBJECT;
+ break;
+ }
+ }
+
+ if (directObject == null) {
+ needsHelp = String.format("Missing object name for verb '%1$s'.", verb);
+ return;
+ }
+ }
+
+ // Validate that all mandatory arguments are non-null for this action
+ String missing = null;
+ boolean plural = false;
+ for (Entry<String, Arg> entry : mArguments.entrySet()) {
+ Arg arg = entry.getValue();
+ if (arg.getVerb().equals(verb) &&
+ arg.getDirectObject().equals(directObject)) {
+ if (arg.isMandatory() && arg.getCurrentValue() == null) {
+ if (missing == null) {
+ missing = "--" + arg.getLongArg();
+ } else {
+ missing += ", --" + arg.getLongArg();
+ plural = true;
+ }
+ }
+ }
+ }
+
+ if (missing != null) {
+ needsHelp = String.format(
+ "The %1$s %2$s must be defined for action '%3$s %4$s'",
+ plural ? "parameters" : "parameter",
+ missing,
+ verb,
+ directObject);
+ }
+
+ mVerbRequested = verb;
+ mDirectObjectRequested = directObject;
+ }
+ }
+ } finally {
+ if (needsHelp != null) {
+ printHelpAndExitForAction(verb, directObject, needsHelp);
+ }
+ }
+ }
+
+ /**
+ * Finds an {@link Arg} given an action name and a long flag name.
+ * @return The {@link Arg} found or null.
+ */
+ protected Arg findLongArg(String verb, String directObject, String longName) {
+ if (verb == null) {
+ verb = GLOBAL_FLAG_VERB;
+ }
+ if (directObject == null) {
+ directObject = NO_VERB_OBJECT;
+ }
+ String key = verb + "/" + directObject + "/" + longName;
+ return mArguments.get(key);
+ }
+
+ /**
+ * Finds an {@link Arg} given an action name and a short flag name.
+ * @return The {@link Arg} found or null.
+ */
+ protected Arg findShortArg(String verb, String directObject, String shortName) {
+ if (verb == null) {
+ verb = GLOBAL_FLAG_VERB;
+ }
+ if (directObject == null) {
+ directObject = NO_VERB_OBJECT;
+ }
+
+ for (Entry<String, Arg> entry : mArguments.entrySet()) {
+ Arg arg = entry.getValue();
+ if (arg.getVerb().equals(verb) && arg.getDirectObject().equals(directObject)) {
+ if (shortName.equals(arg.getShortArg())) {
+ return arg;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Prints the help/usage and exits.
+ *
+ * @param errorFormat Optional error message to print prior to usage using String.format
+ * @param args Arguments for String.format
+ */
+ public void printHelpAndExit(String errorFormat, Object... args) {
+ printHelpAndExitForAction(null /*verb*/, null /*directObject*/, errorFormat, args);
+ }
+
+ /**
+ * Prints the help/usage and exits.
+ *
+ * @param verb If null, displays help for all verbs. If not null, display help only
+ * for that specific verb. In all cases also displays general usage and action list.
+ * @param directObject If null, displays help for all verb objects.
+ * If not null, displays help only for that specific action
+ * In all cases also display general usage and action list.
+ * @param errorFormat Optional error message to print prior to usage using String.format
+ * @param args Arguments for String.format
+ */
+ public void printHelpAndExitForAction(String verb, String directObject,
+ String errorFormat, Object... args) {
+ if (errorFormat != null) {
+ stderr(errorFormat, args);
+ }
+
+ /*
+ * usage should fit in 80 columns
+ * 12345678901234567890123456789012345678901234567890123456789012345678901234567890
+ */
+ stdout("\n" +
+ "Usage:\n" +
+ " android [global options] action [action options]\n" +
+ "\n" +
+ "Global options:");
+ listOptions(GLOBAL_FLAG_VERB, NO_VERB_OBJECT);
+
+ if (verb == null || directObject == null) {
+ stdout("\nValid actions are composed of a verb and an optional direct object:");
+ for (String[] action : mActions) {
+
+ stdout("- %1$6s %2$-7s: %3$s",
+ action[ACTION_VERB_INDEX],
+ action[ACTION_OBJECT_INDEX],
+ action[ACTION_DESC_INDEX]);
+ }
+ }
+
+ for (String[] action : mActions) {
+ if (verb == null || verb.equals(action[ACTION_VERB_INDEX])) {
+ if (directObject == null || directObject.equals(action[ACTION_OBJECT_INDEX])) {
+ stdout("\nAction \"%1$s %2$s\":",
+ action[ACTION_VERB_INDEX],
+ action[ACTION_OBJECT_INDEX]);
+ stdout(" %1$s", action[ACTION_DESC_INDEX]);
+ stdout("Options:");
+ listOptions(action[ACTION_VERB_INDEX], action[ACTION_OBJECT_INDEX]);
+ }
+ }
+ }
+
+ exit();
+ }
+
+ /**
+ * Internal helper to print all the option flags for a given action name.
+ */
+ protected void listOptions(String verb, String directObject) {
+ int numOptions = 0;
+ for (Entry<String, Arg> entry : mArguments.entrySet()) {
+ Arg arg = entry.getValue();
+ if (arg.getVerb().equals(verb) && arg.getDirectObject().equals(directObject)) {
+
+ String value = "";
+ if (arg.getDefaultValue() instanceof String[]) {
+ for (String v : (String[]) arg.getDefaultValue()) {
+ if (value.length() > 0) {
+ value += ", ";
+ }
+ value += v;
+ }
+ } else if (arg.getDefaultValue() != null) {
+ value = arg.getDefaultValue().toString();
+ }
+ if (value.length() > 0) {
+ value = " (" + value + ")";
+ }
+
+ String required = arg.isMandatory() ? " [required]" : "";
+
+ stdout(" -%1$s %2$-10s %3$s%4$s%5$s",
+ arg.getShortArg(),
+ "--" + arg.getLongArg(),
+ arg.getDescription(),
+ value,
+ required);
+ numOptions++;
+ }
+ }
+
+ if (numOptions == 0) {
+ stdout(" No options");
+ }
+ }
+
+ //----
+
+ /**
+ * The mode of an argument specifies the type of variable it represents,
+ * whether an extra parameter is required after the flag and how to parse it.
+ */
+ static enum MODE {
+ /** Argument value is a Boolean. Default value is a Boolean. */
+ BOOLEAN {
+ @Override
+ public boolean needsExtra() {
+ return false;
+ }
+ @Override
+ public String process(Arg arg, String extra) {
+ // Toggle the current value
+ arg.setCurrentValue(! ((Boolean) arg.getCurrentValue()).booleanValue());
+ return null;
+ }
+ },
+
+ /** Argument value is an Integer. Default value is an Integer. */
+ INTEGER {
+ @Override
+ public boolean needsExtra() {
+ return true;
+ }
+ @Override
+ public String process(Arg arg, String extra) {
+ try {
+ arg.setCurrentValue(Integer.parseInt(extra));
+ return null;
+ } catch (NumberFormatException e) {
+ return String.format("Failed to parse '%1$s' as an integer: %2%s",
+ extra, e.getMessage());
+ }
+ }
+ },
+
+ /** Argument value is a String. Default value is a String[]. */
+ ENUM {
+ @Override
+ public boolean needsExtra() {
+ return true;
+ }
+ @Override
+ public String process(Arg arg, String extra) {
+ StringBuilder desc = new StringBuilder();
+ String[] values = (String[]) arg.getDefaultValue();
+ for (String value : values) {
+ if (value.equals(extra)) {
+ arg.setCurrentValue(extra);
+ return null;
+ }
+
+ if (desc.length() != 0) {
+ desc.append(", ");
+ }
+ desc.append(value);
+ }
+
+ return String.format("'%1$s' is not one of %2$s", extra, desc.toString());
+ }
+ },
+
+ /** Argument value is a String. Default value is a null. */
+ STRING {
+ @Override
+ public boolean needsExtra() {
+ return true;
+ }
+ @Override
+ public String process(Arg arg, String extra) {
+ arg.setCurrentValue(extra);
+ return null;
+ }
+ };
+
+ /**
+ * Returns true if this mode requires an extra parameter.
+ */
+ public abstract boolean needsExtra();
+
+ /**
+ * Processes the flag for this argument.
+ *
+ * @param arg The argument being processed.
+ * @param extra The extra parameter. Null if {@link #needsExtra()} returned false.
+ * @return An error string or null if there's no error.
+ */
+ public abstract String process(Arg arg, String extra);
+ }
+
+ /**
+ * An argument accepted by the command-line, also called "a flag".
+ * Arguments must have a short version (one letter), a long version name and a description.
+ * They can have a default value, or it can be null.
+ * Depending on the {@link MODE}, the default value can be a Boolean, an Integer, a String
+ * or a String array (in which case the first item is the current by default.)
+ */
+ static class Arg {
+ /** Verb for that argument. Never null. */
+ private final String mVerb;
+ /** Direct Object for that argument. Never null, but can be empty string. */
+ private final String mDirectObject;
+ /** The 1-letter short name of the argument, e.g. -v. */
+ private final String mShortName;
+ /** The long name of the argument, e.g. --verbose. */
+ private final String mLongName;
+ /** A description. Never null. */
+ private final String mDescription;
+ /** A default value. Can be null. */
+ private final Object mDefaultValue;
+ /** The argument mode (type + process method). Never null. */
+ private final MODE mMode;
+ /** True if this argument is mandatory for this verb/directobject. */
+ private final boolean mMandatory;
+ /** Current value. Initially set to the default value. */
+ private Object mCurrentValue;
+ /** True if the argument has been used on the command line. */
+ private boolean mInCommandLine;
+
+ /**
+ * Creates a new argument flag description.
+ *
+ * @param mode The {@link MODE} for the argument.
+ * @param mandatory True if this argument is mandatory for this action.
+ * @param directObject The action name. Can be #NO_VERB_OBJECT or #INTERNAL_FLAG.
+ * @param shortName The one-letter short argument name. Cannot be empty nor null.
+ * @param longName The long argument name. Cannot be empty nor null.
+ * @param description The description. Cannot be null.
+ * @param defaultValue The default value (or values), which depends on the selected {@link MODE}.
+ */
+ public Arg(MODE mode,
+ boolean mandatory,
+ String verb,
+ String directObject,
+ String shortName,
+ String longName,
+ String description,
+ Object defaultValue) {
+ mMode = mode;
+ mMandatory = mandatory;
+ mVerb = verb;
+ mDirectObject = directObject;
+ mShortName = shortName;
+ mLongName = longName;
+ mDescription = description;
+ mDefaultValue = defaultValue;
+ mInCommandLine = false;
+ if (defaultValue instanceof String[]) {
+ mCurrentValue = ((String[])defaultValue)[0];
+ } else {
+ mCurrentValue = mDefaultValue;
+ }
+ }
+
+ /** Return true if this argument is mandatory for this verb/directobject. */
+ public boolean isMandatory() {
+ return mMandatory;
+ }
+
+ /** Returns the 1-letter short name of the argument, e.g. -v. */
+ public String getShortArg() {
+ return mShortName;
+ }
+
+ /** Returns the long name of the argument, e.g. --verbose. */
+ public String getLongArg() {
+ return mLongName;
+ }
+
+ /** Returns the description. Never null. */
+ public String getDescription() {
+ return mDescription;
+ }
+
+ /** Returns the verb for that argument. Never null. */
+ public String getVerb() {
+ return mVerb;
+ }
+
+ /** Returns the direct Object for that argument. Never null, but can be empty string. */
+ public String getDirectObject() {
+ return mDirectObject;
+ }
+
+ /** Returns the default value. Can be null. */
+ public Object getDefaultValue() {
+ return mDefaultValue;
+ }
+
+ /** Returns the current value. Initially set to the default value. Can be null. */
+ public Object getCurrentValue() {
+ return mCurrentValue;
+ }
+
+ /** Sets the current value. Can be null. */
+ public void setCurrentValue(Object currentValue) {
+ mCurrentValue = currentValue;
+ }
+
+ /** Returns the argument mode (type + process method). Never null. */
+ public MODE getMode() {
+ return mMode;
+ }
+
+ /** Returns true if the argument has been used on the command line. */
+ public boolean isInCommandLine() {
+ return mInCommandLine;
+ }
+
+ /** Sets if the argument has been used on the command line. */
+ public void setInCommandLine(boolean inCommandLine) {
+ mInCommandLine = inCommandLine;
+ }
+ }
+
+ /**
+ * Internal helper to define a new argument for a give action.
+ *
+ * @param mode The {@link MODE} for the argument.
+ * @param verb The verb name. Can be #INTERNAL_VERB.
+ * @param directObject The action name. Can be #NO_VERB_OBJECT or #INTERNAL_FLAG.
+ * @param shortName The one-letter short argument name. Cannot be empty nor null.
+ * @param longName The long argument name. Cannot be empty nor null.
+ * @param description The description. Cannot be null.
+ * @param defaultValue The default value (or values), which depends on the selected {@link MODE}.
+ */
+ protected void define(MODE mode,
+ boolean mandatory,
+ String verb,
+ String directObject,
+ String shortName, String longName,
+ String description, Object defaultValue) {
+ assert(mandatory || mode == MODE.BOOLEAN); // a boolean mode cannot be mandatory
+
+ if (directObject == null) {
+ directObject = NO_VERB_OBJECT;
+ }
+
+ String key = verb + "/" + directObject + "/" + longName;
+ mArguments.put(key, new Arg(mode, mandatory,
+ verb, directObject, shortName, longName, description, defaultValue));
+ }
+
+ /**
+ * Exits in case of error.
+ * This is protected so that it can be overridden in unit tests.
+ */
+ protected void exit() {
+ System.exit(1);
+ }
+
+ /**
+ * Prints a line to stdout.
+ * This is protected so that it can be overridden in unit tests.
+ *
+ * @param format The string to be formatted. Cannot be null.
+ * @param args Format arguments.
+ */
+ protected void stdout(String format, Object...args) {
+ mLog.printf(format + "\n", args);
+ }
+
+ /**
+ * Prints a line to stderr.
+ * This is protected so that it can be overridden in unit tests.
+ *
+ * @param format The string to be formatted. Cannot be null.
+ * @param args Format arguments.
+ */
+ protected void stderr(String format, Object...args) {
+ mLog.error(null, format, args);
+ }
+}
diff --git a/sdkmanager/app/src/com/android/sdkmanager/Main.java b/sdkmanager/app/src/com/android/sdkmanager/Main.java
new file mode 100644
index 0000000..154788e
--- /dev/null
+++ b/sdkmanager/app/src/com/android/sdkmanager/Main.java
@@ -0,0 +1,813 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdkmanager;
+
+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 com.android.sdklib.IAndroidTarget.IOptionalLibrary;
+import com.android.sdklib.avd.AvdManager;
+import com.android.sdklib.avd.HardwareProperties;
+import com.android.sdklib.avd.AvdManager.AvdInfo;
+import com.android.sdklib.avd.HardwareProperties.HardwareProperty;
+import com.android.sdklib.project.ProjectCreator;
+import com.android.sdklib.project.ProjectCreator.OutputLevel;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Main class for the 'android' application.
+ */
+class Main {
+
+ /** Java property that defines the location of the sdk/tools directory. */
+ private final static String TOOLSDIR = "com.android.sdkmanager.toolsdir";
+ /** Java property that defines the working directory. On Windows the current working directory
+ * is actually the tools dir, in which case this is used to get the original CWD. */
+ private final static String WORKDIR = "com.android.sdkmanager.workdir";
+
+ private final static String[] BOOLEAN_YES_REPLIES = new String[] { "yes", "y" };
+ private final static String[] BOOLEAN_NO_REPLIES = new String[] { "no", "n" };
+
+ /** Path to the SDK folder. This is the parent of {@link #TOOLSDIR}. */
+ private String mSdkFolder;
+ /** Logger object. Use this to print normal output, warnings or errors. */
+ private ISdkLog mSdkLog;
+ /** The SDK manager parses the SDK folder and gives access to the content. */
+ private SdkManager mSdkManager;
+ /** Command-line processor with options specific to SdkManager. */
+ private SdkCommandLine mSdkCommandLine;
+ /** The working directory, either null or set to an existing absolute canonical directory. */
+ private File mWorkDir;
+
+ public static void main(String[] args) {
+ new Main().run(args);
+ }
+
+ /**
+ * Runs the sdk manager app
+ */
+ private void run(String[] args) {
+ createLogger();
+ init();
+ mSdkCommandLine.parseArgs(args);
+ parseSdk();
+ doAction();
+ }
+
+ /**
+ * Creates the {@link #mSdkLog} object.
+ * <p/>
+ * This must be done before {@link #init()} as it will be used to report errors.
+ */
+ private void createLogger() {
+ mSdkLog = new ISdkLog() {
+ public void error(Throwable t, String errorFormat, Object... args) {
+ if (errorFormat != null) {
+ System.err.printf("Error: " + errorFormat, args);
+ if (!errorFormat.endsWith("\n")) {
+ System.err.printf("\n");
+ }
+ }
+ if (t != null) {
+ System.err.printf("Error: %s\n", t.getMessage());
+ }
+ }
+
+ public void warning(String warningFormat, Object... args) {
+ if (mSdkCommandLine.isVerbose()) {
+ System.out.printf("Warning: " + warningFormat, args);
+ if (!warningFormat.endsWith("\n")) {
+ System.out.printf("\n");
+ }
+ }
+ }
+
+ public void printf(String msgFormat, Object... args) {
+ System.out.printf(msgFormat, args);
+ }
+ };
+ }
+
+ /**
+ * Init the application by making sure the SDK path is available and
+ * doing basic parsing of the SDK.
+ */
+ private void init() {
+ mSdkCommandLine = new SdkCommandLine(mSdkLog);
+
+ // We get passed a property for the tools dir
+ String toolsDirProp = System.getProperty(TOOLSDIR);
+ if (toolsDirProp == null) {
+ // for debugging, it's easier to override using the process environment
+ toolsDirProp = System.getenv(TOOLSDIR);
+ }
+
+ if (toolsDirProp != null) {
+ // got back a level for the SDK folder
+ File tools;
+ if (toolsDirProp.length() > 0) {
+ tools = new File(toolsDirProp);
+ mSdkFolder = tools.getParent();
+ } else {
+ try {
+ tools = new File(".").getCanonicalFile();
+ mSdkFolder = tools.getParent();
+ } catch (IOException e) {
+ // Will print an error below since mSdkFolder is not defined
+ }
+ }
+ }
+
+ if (mSdkFolder == null) {
+ errorAndExit("The tools directory property is not set, please make sure you are executing %1$s",
+ SdkConstants.androidCmdName());
+ }
+
+ // We might get passed a property for the working directory
+ // Either it is a valid directory and mWorkDir is set to it's absolute canonical value
+ // or mWorkDir remains null.
+ String workDirProp = System.getProperty(WORKDIR);
+ if (workDirProp == null) {
+ workDirProp = System.getenv(WORKDIR);
+ }
+ if (workDirProp != null) {
+ // This should be a valid directory
+ mWorkDir = new File(workDirProp);
+ try {
+ mWorkDir = mWorkDir.getCanonicalFile().getAbsoluteFile();
+ } catch (IOException e) {
+ mWorkDir = null;
+ }
+ if (mWorkDir == null || !mWorkDir.isDirectory()) {
+ errorAndExit("The working directory does not seem to be valid: '%1$s", workDirProp);
+ }
+ }
+ }
+
+ /**
+ * Does the basic SDK parsing required for all actions
+ */
+ private void parseSdk() {
+ mSdkManager = SdkManager.createManager(mSdkFolder, mSdkLog);
+
+ if (mSdkManager == null) {
+ errorAndExit("Unable to parse SDK content.");
+ }
+ }
+
+ /**
+ * Actually do an action...
+ */
+ private void doAction() {
+ String verb = mSdkCommandLine.getVerb();
+ String directObject = mSdkCommandLine.getDirectObject();
+
+ if (SdkCommandLine.VERB_LIST.equals(verb)) {
+ // list action.
+ if (SdkCommandLine.OBJECT_TARGET.equals(directObject)) {
+ displayTargetList();
+ } else if (SdkCommandLine.OBJECT_AVD.equals(directObject)) {
+ displayAvdList();
+ } else {
+ displayTargetList();
+ displayAvdList();
+ }
+
+ } else if (SdkCommandLine.VERB_CREATE.equals(verb) &&
+ SdkCommandLine.OBJECT_AVD.equals(directObject)) {
+ createAvd();
+
+ } else if (SdkCommandLine.VERB_DELETE.equals(verb) &&
+ SdkCommandLine.OBJECT_AVD.equals(directObject)) {
+ deleteAvd();
+
+ } else if (SdkCommandLine.VERB_MOVE.equals(verb) &&
+ SdkCommandLine.OBJECT_AVD.equals(directObject)) {
+ moveAvd();
+
+ } else if (SdkCommandLine.VERB_CREATE.equals(verb) &&
+ SdkCommandLine.OBJECT_PROJECT.equals(directObject)) {
+ createProject();
+
+ } else if (SdkCommandLine.VERB_UPDATE.equals(verb) &&
+ SdkCommandLine.OBJECT_PROJECT.equals(directObject)) {
+ updateProject();
+ } else {
+ mSdkCommandLine.printHelpAndExit(null);
+ }
+ }
+
+ /**
+ * Creates a new Android project based on command-line parameters
+ */
+ private void createProject() {
+ // get the target and try to resolve it.
+ int targetId = mSdkCommandLine.getParamTargetId();
+ IAndroidTarget[] targets = mSdkManager.getTargets();
+ if (targetId < 1 || targetId > targets.length) {
+ errorAndExit("Target id is not valid. Use '%s list targets' to get the target ids.",
+ SdkConstants.androidCmdName());
+ }
+ IAndroidTarget target = targets[targetId - 1];
+
+ ProjectCreator creator = new ProjectCreator(mSdkFolder,
+ mSdkCommandLine.isVerbose() ? OutputLevel.VERBOSE :
+ mSdkCommandLine.isSilent() ? OutputLevel.SILENT :
+ OutputLevel.NORMAL,
+ mSdkLog);
+
+ String projectDir = getProjectLocation(mSdkCommandLine.getParamLocationPath());
+
+ creator.createProject(projectDir,
+ mSdkCommandLine.getParamName(),
+ mSdkCommandLine.getParamProjectPackage(),
+ mSdkCommandLine.getParamProjectActivity(),
+ target,
+ false /* isTestProject*/);
+ }
+
+ /**
+ * Updates an existing Android project based on command-line parameters
+ */
+ private void updateProject() {
+ // get the target and try to resolve it.
+ IAndroidTarget target = null;
+ int targetId = mSdkCommandLine.getParamTargetId();
+ if (targetId >= 0) {
+ IAndroidTarget[] targets = mSdkManager.getTargets();
+ if (targetId < 1 || targetId > targets.length) {
+ errorAndExit("Target id is not valid. Use '%s list targets' to get the target ids.",
+ SdkConstants.androidCmdName());
+ }
+ target = targets[targetId - 1];
+ }
+
+ ProjectCreator creator = new ProjectCreator(mSdkFolder,
+ mSdkCommandLine.isVerbose() ? OutputLevel.VERBOSE :
+ mSdkCommandLine.isSilent() ? OutputLevel.SILENT :
+ OutputLevel.NORMAL,
+ mSdkLog);
+
+ String projectDir = getProjectLocation(mSdkCommandLine.getParamLocationPath());
+
+ creator.updateProject(projectDir,
+ target,
+ mSdkCommandLine.getParamName());
+ }
+
+ /**
+ * Adjusts the project location to make it absolute & canonical relative to the
+ * working directory, if any.
+ *
+ * @return The project absolute path relative to {@link #mWorkDir} or the original
+ * newProjectLocation otherwise.
+ */
+ private String getProjectLocation(String newProjectLocation) {
+
+ // If the new project location is absolute, use it as-is
+ File projectDir = new File(newProjectLocation);
+ if (projectDir.isAbsolute()) {
+ return newProjectLocation;
+ }
+
+ // if there's no working directory, just use the project location as-is.
+ if (mWorkDir == null) {
+ return newProjectLocation;
+ }
+
+ // Combine then and get an absolute canonical directory
+ try {
+ projectDir = new File(mWorkDir, newProjectLocation).getCanonicalFile();
+
+ return projectDir.getPath();
+ } catch (IOException e) {
+ errorAndExit("Failed to combine working directory '%1$s' with project location '%2$s': %3$s",
+ mWorkDir.getPath(),
+ newProjectLocation,
+ e.getMessage());
+ return null;
+ }
+ }
+
+ /**
+ * Displays the list of available Targets (Platforms and Add-ons)
+ */
+ private void displayTargetList() {
+ mSdkLog.printf("Available Android targets:\n");
+
+ int index = 1;
+ for (IAndroidTarget target : mSdkManager.getTargets()) {
+ if (target.isPlatform()) {
+ mSdkLog.printf("[%d] %s\n", index, target.getName());
+ mSdkLog.printf(" API level: %d\n", target.getApiVersionNumber());
+ } else {
+ mSdkLog.printf("[%d] Add-on: %s\n", index, target.getName());
+ mSdkLog.printf(" Vendor: %s\n", target.getVendor());
+ if (target.getDescription() != null) {
+ mSdkLog.printf(" Description: %s\n", target.getDescription());
+ }
+ mSdkLog.printf(" Based on Android %s (API level %d)\n",
+ target.getApiVersionName(), target.getApiVersionNumber());
+
+ // display the optional libraries.
+ IOptionalLibrary[] libraries = target.getOptionalLibraries();
+ if (libraries != null) {
+ mSdkLog.printf(" Libraries:\n");
+ for (IOptionalLibrary library : libraries) {
+ mSdkLog.printf(" * %1$s (%2$s)\n",
+ library.getName(), library.getJarName());
+ mSdkLog.printf(String.format(
+ " %1$s\n", library.getDescription()));
+ }
+ }
+ }
+
+ // get the target skins
+ displaySkinList(target, " Skins: ");
+
+ index++;
+ }
+ }
+
+ /**
+ * Displays the skins valid for the given target.
+ */
+ private void displaySkinList(IAndroidTarget target, String message) {
+ String[] skins = target.getSkins();
+ String defaultSkin = target.getDefaultSkin();
+ mSdkLog.printf(message);
+ if (skins != null) {
+ boolean first = true;
+ for (String skin : skins) {
+ if (first == false) {
+ mSdkLog.printf(", ");
+ } else {
+ first = false;
+ }
+ mSdkLog.printf(skin);
+
+ if (skin.equals(defaultSkin)) {
+ mSdkLog.printf(" (default)");
+ }
+ }
+ mSdkLog.printf("\n");
+ } else {
+ mSdkLog.printf("no skins.\n");
+ }
+ }
+
+ /**
+ * Displays the list of available AVDs.
+ */
+ private void displayAvdList() {
+ try {
+ AvdManager avdManager = new AvdManager(mSdkManager, mSdkLog);
+
+ mSdkLog.printf("Available Android Virtual Devices:\n");
+
+ AvdInfo[] avds = avdManager.getAvds();
+ for (int index = 0 ; index < avds.length ; index++) {
+ AvdInfo info = avds[index];
+ if (index > 0) {
+ mSdkLog.printf("---------\n");
+ }
+ mSdkLog.printf(" Name: %s\n", info.getName());
+ mSdkLog.printf(" Path: %s\n", info.getPath());
+
+ // get the target of the AVD
+ IAndroidTarget target = info.getTarget();
+ if (target.isPlatform()) {
+ mSdkLog.printf(" Target: %s (API level %d)\n", target.getName(),
+ target.getApiVersionNumber());
+ } else {
+ mSdkLog.printf(" Target: %s (%s)\n", target.getName(), target
+ .getVendor());
+ mSdkLog.printf(" Based on Android %s (API level %d)\n", target
+ .getApiVersionName(), target.getApiVersionNumber());
+ }
+
+ // display some extra values.
+ Map<String, String> properties = info.getProperties();
+ String skin = properties.get(AvdManager.AVD_INI_SKIN_NAME);
+ if (skin != null) {
+ mSdkLog.printf(" Skin: %s\n", skin);
+ }
+ String sdcard = properties.get(AvdManager.AVD_INI_SDCARD_SIZE);
+ if (sdcard == null) {
+ sdcard = properties.get(AvdManager.AVD_INI_SDCARD_PATH);
+ }
+ if (sdcard != null) {
+ mSdkLog.printf(" Sdcard: %s\n", sdcard);
+ }
+ }
+ } catch (AndroidLocationException e) {
+ errorAndExit(e.getMessage());
+ }
+ }
+
+ /**
+ * Creates a new AVD. This is a text based creation with command line prompt.
+ */
+ private void createAvd() {
+ // find a matching target
+ int targetId = mSdkCommandLine.getParamTargetId();
+ IAndroidTarget target = null;
+
+ if (targetId >= 1 && targetId <= mSdkManager.getTargets().length) {
+ target = mSdkManager.getTargets()[targetId-1]; // target it is 1-based
+ } else {
+ errorAndExit("Target id is not valid. Use '%s list targets' to get the target ids.",
+ SdkConstants.androidCmdName());
+ }
+
+ try {
+ boolean removePrevious = false;
+ AvdManager avdManager = new AvdManager(mSdkManager, mSdkLog);
+
+ String avdName = mSdkCommandLine.getParamName();
+ AvdInfo info = avdManager.getAvd(avdName);
+ if (info != null) {
+ if (mSdkCommandLine.getFlagForce()) {
+ removePrevious = true;
+ mSdkLog.warning(
+ "Android Virtual Device '%s' already exists and will be replaced.",
+ avdName);
+ } else {
+ errorAndExit("Android Virtual Device '%s' already exists.", avdName);
+ return;
+ }
+ }
+
+ String paramFolderPath = mSdkCommandLine.getParamLocationPath();
+ File avdFolder = null;
+ if (paramFolderPath != null) {
+ avdFolder = new File(paramFolderPath);
+ } else {
+ avdFolder = new File(AndroidLocation.getFolder() + AndroidLocation.FOLDER_AVD,
+ avdName + AvdManager.AVD_FOLDER_EXTENSION);
+ }
+
+ Map<String, String> hardwareConfig = null;
+ if (target.isPlatform()) {
+ try {
+ hardwareConfig = promptForHardware(target);
+ } catch (IOException e) {
+ errorAndExit(e.getMessage());
+ }
+ }
+
+ AvdInfo oldAvdInfo = null;
+ if (removePrevious) {
+ oldAvdInfo = avdManager.getAvd(avdName);
+ }
+
+ // Validate skin is either default (empty) or NNNxMMM or a valid skin name.
+ String skin = mSdkCommandLine.getParamSkin();
+ if (skin != null && skin.length() == 0) {
+ skin = null;
+ }
+ if (skin != null) {
+ boolean valid = false;
+ // Is it a know skin name for this target?
+ for (String s : target.getSkins()) {
+ if (skin.equalsIgnoreCase(s)) {
+ skin = s; // Make skin names case-insensitive.
+ valid = true;
+ break;
+ }
+ }
+
+ // Is it NNNxMMM?
+ if (!valid) {
+ valid = AvdManager.NUMERIC_SKIN_SIZE.matcher(skin).matches();
+ }
+
+ if (!valid) {
+ displaySkinList(target, "Valid skins: ");
+ errorAndExit("'%s' is not a valid skin name or size (NNNxMMM)", skin);
+ return;
+ }
+ }
+
+ AvdInfo newAvdInfo = avdManager.createAvd(avdFolder,
+ avdName,
+ target,
+ skin,
+ mSdkCommandLine.getParamSdCard(),
+ hardwareConfig,
+ removePrevious,
+ mSdkLog);
+
+ if (newAvdInfo != null &&
+ oldAvdInfo != null &&
+ !oldAvdInfo.getPath().equals(newAvdInfo.getPath())) {
+ mSdkLog.warning("Removing previous AVD directory at %s", oldAvdInfo.getPath());
+ // Remove the old data directory
+ File dir = new File(oldAvdInfo.getPath());
+ avdManager.recursiveDelete(dir);
+ dir.delete();
+ // Remove old avd info from manager
+ avdManager.removeAvd(oldAvdInfo);
+ }
+
+ } catch (AndroidLocationException e) {
+ errorAndExit(e.getMessage());
+ }
+ }
+
+ /**
+ * Delete an AVD.
+ */
+ private void deleteAvd() {
+ try {
+ String avdName = mSdkCommandLine.getParamName();
+ AvdManager avdManager = new AvdManager(mSdkManager, mSdkLog);
+ AvdInfo info = avdManager.getAvd(avdName);
+
+ if (info == null) {
+ errorAndExit("There is no Android Virtual Device named '%s'.", avdName);
+ return;
+ }
+
+ avdManager.deleteAvd(info, mSdkLog);
+ } catch (AndroidLocationException e) {
+ errorAndExit(e.getMessage());
+ }
+ }
+
+ /**
+ * Move an AVD.
+ */
+ private void moveAvd() {
+ try {
+ String avdName = mSdkCommandLine.getParamName();
+ AvdManager avdManager = new AvdManager(mSdkManager, mSdkLog);
+ AvdInfo info = avdManager.getAvd(avdName);
+
+ if (info == null) {
+ errorAndExit("There is no Android Virtual Device named '%s'.", avdName);
+ return;
+ }
+
+ // This is a rename if there's a new name for the AVD
+ String newName = mSdkCommandLine.getParamMoveNewName();
+ if (newName != null && newName.equals(info.getName())) {
+ // same name, not actually a rename operation
+ newName = null;
+ }
+
+ // This is a move (of the data files) if there's a new location path
+ String paramFolderPath = mSdkCommandLine.getParamLocationPath();
+ if (paramFolderPath != null) {
+ // check if paths are the same. Use File methods to account for OS idiosyncrasies.
+ try {
+ File f1 = new File(paramFolderPath).getCanonicalFile();
+ File f2 = new File(info.getPath()).getCanonicalFile();
+ if (f1.equals(f2)) {
+ // same canonical path, so not actually a move
+ paramFolderPath = null;
+ }
+ } catch (IOException e) {
+ // Fail to resolve canonical path. Fail now since a move operation might fail
+ // later and be harder to recover from.
+ errorAndExit(e.getMessage());
+ return;
+ }
+ }
+
+ if (newName == null && paramFolderPath == null) {
+ mSdkLog.warning("Move operation aborted: same AVD name, same canonical data path");
+ return;
+ }
+
+ // If a rename was requested and no data move was requested, check if the original
+ // data path is our default constructed from the AVD name. In this case we still want
+ // to rename that folder too.
+ if (newName != null && paramFolderPath == null) {
+ // Compute the original data path
+ File originalFolder = new File(
+ AndroidLocation.getFolder() + AndroidLocation.FOLDER_AVD,
+ info.getName() + AvdManager.AVD_FOLDER_EXTENSION);
+ if (originalFolder.equals(info.getPath())) {
+ try {
+ // The AVD is using the default data folder path based on the AVD name.
+ // That folder needs to be adjusted to use the new name.
+ File f = new File(AndroidLocation.getFolder() + AndroidLocation.FOLDER_AVD,
+ newName + AvdManager.AVD_FOLDER_EXTENSION);
+ paramFolderPath = f.getCanonicalPath();
+ } catch (IOException e) {
+ // Fail to resolve canonical path. Fail now rather than later.
+ errorAndExit(e.getMessage());
+ }
+ }
+ }
+
+ // Check for conflicts
+
+ if (newName != null && avdManager.getAvd(newName) != null) {
+ errorAndExit("There is already an AVD named '%s'.", newName);
+ return;
+ }
+ if (newName != null) {
+ if (avdManager.getAvd(newName) != null) {
+ errorAndExit("There is already an AVD named '%s'.", newName);
+ return;
+ }
+
+ File ini = info.getIniFile();
+ if (ini.equals(AvdInfo.getIniFile(newName))) {
+ errorAndExit("The AVD file '%s' is in the way.", ini.getCanonicalPath());
+ return;
+ }
+ }
+
+ if (paramFolderPath != null && new File(paramFolderPath).exists()) {
+ errorAndExit(
+ "There is already a file or directory at '%s'.\nUse --path to specify a different data folder.",
+ paramFolderPath);
+ }
+
+ avdManager.moveAvd(info, newName, paramFolderPath, mSdkLog);
+ } catch (AndroidLocationException e) {
+ errorAndExit(e.getMessage());
+ } catch (IOException e) {
+ errorAndExit(e.getMessage());
+ }
+ }
+
+ /**
+ * Prompts the user to setup a hardware config for a Platform-based AVD.
+ * @throws IOException
+ */
+ private Map<String, String> promptForHardware(IAndroidTarget createTarget) throws IOException {
+ byte[] readLineBuffer = new byte[256];
+ String result;
+ String defaultAnswer = "no";
+
+ mSdkLog.printf("%s is a basic Android platform.\n", createTarget.getName());
+ mSdkLog.printf("Do you wish to create a custom hardware profile [%s]",
+ defaultAnswer);
+
+ result = readLine(readLineBuffer).trim();
+ // handle default:
+ if (result.length() == 0) {
+ result = defaultAnswer;
+ }
+
+ if (getBooleanReply(result) == false) {
+ // no custom config.
+ return null;
+ }
+
+ mSdkLog.printf("\n"); // empty line
+
+ // get the list of possible hardware properties
+ File hardwareDefs = new File (mSdkFolder + File.separator +
+ SdkConstants.OS_SDK_TOOLS_LIB_FOLDER, SdkConstants.FN_HARDWARE_INI);
+ List<HardwareProperty> list = HardwareProperties.parseHardwareDefinitions(hardwareDefs,
+ null /*sdkLog*/);
+
+ HashMap<String, String> map = new HashMap<String, String>();
+
+ for (int i = 0 ; i < list.size() ;) {
+ HardwareProperty property = list.get(i);
+
+ String description = property.getDescription();
+ if (description != null) {
+ mSdkLog.printf("%s: %s\n", property.getAbstract(), description);
+ } else {
+ mSdkLog.printf("%s\n", property.getAbstract());
+ }
+
+ String defaultValue = property.getDefault();
+
+ if (defaultValue != null) {
+ mSdkLog.printf("%s [%s]:", property.getName(), defaultValue);
+ } else {
+ mSdkLog.printf("%s (%s):", property.getName(), property.getType());
+ }
+
+ result = readLine(readLineBuffer);
+ if (result.length() == 0) {
+ if (defaultValue != null) {
+ mSdkLog.printf("\n"); // empty line
+ i++; // go to the next property if we have a valid default value.
+ // if there's no default, we'll redo this property
+ }
+ continue;
+ }
+
+ switch (property.getType()) {
+ case BOOLEAN:
+ try {
+ if (getBooleanReply(result)) {
+ map.put(property.getName(), "yes");
+ i++; // valid reply, move to next property
+ } else {
+ map.put(property.getName(), "no");
+ i++; // valid reply, move to next property
+ }
+ } catch (IOException e) {
+ // display error, and do not increment i to redo this property
+ mSdkLog.printf("\n%s\n", e.getMessage());
+ }
+ break;
+ case INTEGER:
+ try {
+ Integer.parseInt(result);
+ map.put(property.getName(), result);
+ i++; // valid reply, move to next property
+ } catch (NumberFormatException e) {
+ // display error, and do not increment i to redo this property
+ mSdkLog.printf("\n%s\n", e.getMessage());
+ }
+ break;
+ case DISKSIZE:
+ // TODO check validity
+ map.put(property.getName(), result);
+ i++; // valid reply, move to next property
+ break;
+ }
+
+ mSdkLog.printf("\n"); // empty line
+ }
+
+ return map;
+ }
+
+ /**
+ * Reads the line from the input stream.
+ * @param buffer
+ * @throws IOException
+ */
+ private String readLine(byte[] buffer) throws IOException {
+ int count = System.in.read(buffer);
+
+ // is the input longer than the buffer?
+ if (count == buffer.length && buffer[count-1] != 10) {
+ // create a new temp buffer
+ byte[] tempBuffer = new byte[256];
+
+ // and read the rest
+ String secondHalf = readLine(tempBuffer);
+
+ // return a concat of both
+ return new String(buffer, 0, count) + secondHalf;
+ }
+
+ // ignore end whitespace
+ while (count > 0 && (buffer[count-1] == '\r' || buffer[count-1] == '\n')) {
+ count--;
+ }
+
+ return new String(buffer, 0, count);
+ }
+
+ /**
+ * Returns the boolean value represented by the string.
+ * @throws IOException If the value is not a boolean string.
+ */
+ private boolean getBooleanReply(String reply) throws IOException {
+
+ for (String valid : BOOLEAN_YES_REPLIES) {
+ if (valid.equalsIgnoreCase(reply)) {
+ return true;
+ }
+ }
+
+ for (String valid : BOOLEAN_NO_REPLIES) {
+ if (valid.equalsIgnoreCase(reply)) {
+ return false;
+ }
+ }
+
+ throw new IOException(String.format("%s is not a valid reply", reply));
+ }
+
+ private void errorAndExit(String format, Object...args) {
+ mSdkLog.error(null, format, args);
+ System.exit(1);
+ }
+} \ No newline at end of file
diff --git a/sdkmanager/app/src/com/android/sdkmanager/SdkCommandLine.java b/sdkmanager/app/src/com/android/sdkmanager/SdkCommandLine.java
new file mode 100644
index 0000000..34a69bd
--- /dev/null
+++ b/sdkmanager/app/src/com/android/sdkmanager/SdkCommandLine.java
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdkmanager;
+
+import com.android.sdklib.ISdkLog;
+import com.android.sdklib.SdkManager;
+
+
+/**
+ * Specific command-line flags for the {@link SdkManager}.
+ */
+public class SdkCommandLine extends CommandLineProcessor {
+
+ public final static String VERB_LIST = "list";
+ public final static String VERB_CREATE = "create";
+ public final static String VERB_MOVE = "move";
+ 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 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";
+
+ /**
+ * Action definitions for SdkManager command line.
+ * <p/>
+ * Each entry is a string array with:
+ * <ul>
+ * <li> the verb.
+ * <li> an object (use #NO_VERB_OBJECT if there's no object).
+ * <li> a description.
+ * <li> an alternate form for the object (e.g. plural).
+ * </ul>
+ */
+ private final static String[][] ACTIONS = {
+ { VERB_LIST, NO_VERB_OBJECT,
+ "Lists existing targets or virtual devices." },
+ { VERB_LIST, OBJECT_AVD,
+ "Lists existing Android Virtual Devices.",
+ OBJECT_AVDS },
+ { VERB_LIST, OBJECT_TARGET,
+ "Lists existing targets.",
+ OBJECT_TARGETS },
+
+ { VERB_CREATE, OBJECT_AVD,
+ "Creates a new Android Virtual Device." },
+ { VERB_MOVE, OBJECT_AVD,
+ "Moves or renames an Android Virtual Device." },
+ { VERB_DELETE, OBJECT_AVD,
+ "Deletes an Android Virtual Device." },
+
+ { VERB_CREATE, OBJECT_PROJECT,
+ "Creates a new Android Project." },
+ { VERB_UPDATE, OBJECT_PROJECT,
+ "Updates an Android Project (must have an AndroidManifest.xml)." },
+ };
+
+ public SdkCommandLine(ISdkLog logger) {
+ super(logger, ACTIONS);
+
+ // --- create avd ---
+
+ define(MODE.STRING, false,
+ VERB_CREATE, OBJECT_AVD, "p", KEY_PATH,
+ "Location path of the directory where the new AVD will be created", null);
+ define(MODE.STRING, true,
+ VERB_CREATE, OBJECT_AVD, "n", KEY_NAME,
+ "Name of the new AVD", null);
+ define(MODE.INTEGER, true,
+ VERB_CREATE, OBJECT_AVD, "t", KEY_TARGET_ID,
+ "Target id of the new AVD", null);
+ define(MODE.STRING, false,
+ VERB_CREATE, OBJECT_AVD, "s", KEY_SKIN,
+ "Skin of the new AVD", null);
+ define(MODE.STRING, false,
+ VERB_CREATE, OBJECT_AVD, "c", KEY_SDCARD,
+ "Path to a shared SD card image, or size of a new sdcard for the new AVD", null);
+ define(MODE.BOOLEAN, false,
+ VERB_CREATE, OBJECT_AVD, "f", KEY_FORCE,
+ "Force creation (override an existing AVD)", false);
+
+ // --- delete avd ---
+
+ define(MODE.STRING, true,
+ VERB_DELETE, OBJECT_AVD, "n", KEY_NAME,
+ "Name of the AVD to delete", null);
+
+ // --- move avd ---
+
+ define(MODE.STRING, true,
+ VERB_MOVE, OBJECT_AVD, "n", KEY_NAME,
+ "Name of the AVD to move or rename", null);
+ define(MODE.STRING, false,
+ VERB_MOVE, OBJECT_AVD, "r", KEY_RENAME,
+ "New name of the AVD to rename", null);
+ define(MODE.STRING, false,
+ VERB_MOVE, OBJECT_AVD, "p", KEY_PATH,
+ "New location path of the directory where to move the AVD", null);
+
+ // --- create project ---
+
+ define(MODE.ENUM, true,
+ VERB_CREATE, OBJECT_PROJECT, "m", KEY_MODE,
+ "Project mode", new String[] { ARG_ACTIVITY, ARG_ALIAS });
+ define(MODE.STRING, true,
+ VERB_CREATE, OBJECT_PROJECT,
+ "p", KEY_PATH,
+ "Location path of new project", null);
+ define(MODE.INTEGER, true,
+ VERB_CREATE, OBJECT_PROJECT, "t", KEY_TARGET_ID,
+ "Target id of the new project", null);
+ define(MODE.STRING, true,
+ VERB_CREATE, OBJECT_PROJECT, "k", KEY_PACKAGE,
+ "Package name", null);
+ define(MODE.STRING, true,
+ VERB_CREATE, OBJECT_PROJECT, "a", KEY_ACTIVITY,
+ "Activity name", null);
+ define(MODE.STRING, false,
+ VERB_CREATE, OBJECT_PROJECT, "n", KEY_NAME,
+ "Project name", null);
+
+ // --- update project ---
+
+ define(MODE.STRING, true,
+ VERB_UPDATE, OBJECT_PROJECT,
+ "p", KEY_PATH,
+ "Location path of the project", null);
+ define(MODE.INTEGER, true,
+ VERB_UPDATE, OBJECT_PROJECT,
+ "t", KEY_TARGET_ID,
+ "Target id to set for the project", -1);
+ define(MODE.STRING, false,
+ VERB_UPDATE, OBJECT_PROJECT,
+ "n", KEY_NAME,
+ "Project name", null);
+ }
+
+ // -- some helpers for generic action flags
+
+ /** Helper to retrieve the --path value. */
+ public String getParamLocationPath() {
+ return ((String) getValue(null, null, KEY_PATH));
+ }
+
+ /** Helper to retrieve the --target id value. */
+ public int getParamTargetId() {
+ return ((Integer) getValue(null, null, KEY_TARGET_ID)).intValue();
+ }
+
+ /** Helper to retrieve the --name value. */
+ public String getParamName() {
+ return ((String) getValue(null, null, KEY_NAME));
+ }
+
+ /** Helper to retrieve the --skin value. */
+ public String getParamSkin() {
+ return ((String) getValue(null, null, KEY_SKIN));
+ }
+
+ /** Helper to retrieve the --sdcard value. */
+ public String getParamSdCard() {
+ return ((String) getValue(null, null, KEY_SDCARD));
+ }
+
+ /** Helper to retrieve the --force flag. */
+ public boolean getFlagForce() {
+ return ((Boolean) getValue(null, null, KEY_FORCE)).booleanValue();
+ }
+
+ // -- some helpers for avd action flags
+
+ /** Helper to retrieve the --rename value for a move verb. */
+ public String getParamMoveNewName() {
+ return ((String) getValue(VERB_MOVE, null, KEY_RENAME));
+ }
+
+
+ // -- some helpers for project action flags
+
+ /** Helper to retrieve the --package value. */
+ public String getParamProjectPackage() {
+ return ((String) getValue(null, OBJECT_PROJECT, KEY_PACKAGE));
+ }
+
+ /** Helper to retrieve the --activity for the new project action. */
+ public String getParamProjectActivity() {
+ return ((String) getValue(null, OBJECT_PROJECT, KEY_ACTIVITY));
+ }
+}
diff --git a/sdkmanager/app/tests/com/android/sdkmanager/CommandLineProcessorTest.java b/sdkmanager/app/tests/com/android/sdkmanager/CommandLineProcessorTest.java
new file mode 100644
index 0000000..918591b
--- /dev/null
+++ b/sdkmanager/app/tests/com/android/sdkmanager/CommandLineProcessorTest.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdkmanager;
+
+import com.android.sdklib.ISdkLog;
+
+import junit.framework.TestCase;
+
+
+public class CommandLineProcessorTest extends TestCase {
+
+ private MockStdLogger mLog;
+
+ /**
+ * A mock version of the {@link CommandLineProcessor} class that does not
+ * exits and captures its stdout/stderr output.
+ */
+ public static class MockCommandLineProcessor extends CommandLineProcessor {
+ private boolean mExitCalled;
+ private boolean mHelpCalled;
+ private String mStdOut = "";
+ private String mStdErr = "";
+
+ public MockCommandLineProcessor(ISdkLog logger) {
+ super(logger,
+ new String[][] {
+ { "verb1", "action1", "Some action" },
+ { "verb1", "action2", "Another action" },
+ });
+ define(MODE.STRING, false /*mandatory*/,
+ "verb1", "action1", "1", "first", "non-mandatory flag", null);
+ define(MODE.STRING, true /*mandatory*/,
+ "verb1", "action1", "2", "second", "mandatory flag", null);
+ }
+
+ @Override
+ public void printHelpAndExitForAction(String verb, String directObject,
+ String errorFormat, Object... args) {
+ mHelpCalled = true;
+ super.printHelpAndExitForAction(verb, directObject, errorFormat, args);
+ }
+
+ @Override
+ protected void exit() {
+ mExitCalled = true;
+ }
+
+ @Override
+ protected void stdout(String format, Object... args) {
+ String s = String.format(format, args);
+ mStdOut += s + "\n";
+ // don't call super to avoid printing stuff
+ }
+
+ @Override
+ protected void stderr(String format, Object... args) {
+ String s = String.format(format, args);
+ mStdErr += s + "\n";
+ // don't call super to avoid printing stuff
+ }
+
+ public boolean wasHelpCalled() {
+ return mHelpCalled;
+ }
+
+ public boolean wasExitCalled() {
+ return mExitCalled;
+ }
+
+ public String getStdOut() {
+ return mStdOut;
+ }
+
+ public String getStdErr() {
+ return mStdErr;
+ }
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ mLog = new MockStdLogger();
+ super.setUp();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ public final void testPrintHelpAndExit() {
+ MockCommandLineProcessor c = new MockCommandLineProcessor(mLog);
+ assertFalse(c.wasExitCalled());
+ assertFalse(c.wasHelpCalled());
+ assertTrue(c.getStdOut().equals(""));
+ assertTrue(c.getStdErr().equals(""));
+ c.printHelpAndExit(null);
+ assertTrue(c.getStdOut().indexOf("-v") != -1);
+ assertTrue(c.getStdOut().indexOf("--verbose") != -1);
+ assertTrue(c.getStdErr().equals(""));
+ assertTrue(c.wasExitCalled());
+
+ c = new MockCommandLineProcessor(mLog);
+ assertFalse(c.wasExitCalled());
+ assertTrue(c.getStdOut().equals(""));
+ assertTrue(c.getStdErr().indexOf("Missing parameter") == -1);
+
+ c.printHelpAndExit("Missing %s", "parameter");
+ assertTrue(c.wasExitCalled());
+ assertFalse(c.getStdOut().equals(""));
+ assertTrue(c.getStdErr().indexOf("Missing parameter") != -1);
+ }
+
+ public final void testVerbose() {
+ MockCommandLineProcessor c = new MockCommandLineProcessor(mLog);
+
+ assertFalse(c.isVerbose());
+ c.parseArgs(new String[] { "-v" });
+ assertTrue(c.isVerbose());
+ assertTrue(c.wasExitCalled());
+ assertTrue(c.wasHelpCalled());
+ assertTrue(c.getStdErr().indexOf("Missing verb name.") != -1);
+
+ c = new MockCommandLineProcessor(mLog);
+ c.parseArgs(new String[] { "--verbose" });
+ assertTrue(c.isVerbose());
+ assertTrue(c.wasExitCalled());
+ assertTrue(c.wasHelpCalled());
+ assertTrue(c.getStdErr().indexOf("Missing verb name.") != -1);
+ }
+
+ public final void testHelp() {
+ MockCommandLineProcessor c = new MockCommandLineProcessor(mLog);
+
+ c.parseArgs(new String[] { "-h" });
+ assertTrue(c.wasExitCalled());
+ assertTrue(c.wasHelpCalled());
+ assertTrue(c.getStdErr().indexOf("Missing verb name.") == -1);
+
+ c = new MockCommandLineProcessor(mLog);
+ c.parseArgs(new String[] { "--help" });
+ assertTrue(c.wasExitCalled());
+ assertTrue(c.wasHelpCalled());
+ assertTrue(c.getStdErr().indexOf("Missing verb name.") == -1);
+ }
+
+ public final void testMandatory() {
+ MockCommandLineProcessor c = new MockCommandLineProcessor(mLog);
+
+ c.parseArgs(new String[] { "verb1", "action1", "-1", "value1", "-2", "value2" });
+ assertFalse(c.wasExitCalled());
+ assertFalse(c.wasHelpCalled());
+ assertEquals("", c.getStdErr());
+ assertEquals("value1", c.getValue("verb1", "action1", "first"));
+ assertEquals("value2", c.getValue("verb1", "action1", "second"));
+
+ c = new MockCommandLineProcessor(mLog);
+ c.parseArgs(new String[] { "verb1", "action1", "-2", "value2" });
+ assertFalse(c.wasExitCalled());
+ assertFalse(c.wasHelpCalled());
+ assertEquals("", c.getStdErr());
+ assertEquals(null, c.getValue("verb1", "action1", "first"));
+ assertEquals("value2", c.getValue("verb1", "action1", "second"));
+
+ c = new MockCommandLineProcessor(mLog);
+ c.parseArgs(new String[] { "verb1", "action1" });
+ assertTrue(c.wasExitCalled());
+ assertTrue(c.wasHelpCalled());
+ assertTrue(c.getStdErr().indexOf("must be defined") != -1);
+ assertEquals(null, c.getValue("verb1", "action1", "first"));
+ assertEquals(null, c.getValue("verb1", "action1", "second"));
+ }
+}
diff --git a/sdkmanager/app/tests/com/android/sdkmanager/MockStdLogger.java b/sdkmanager/app/tests/com/android/sdkmanager/MockStdLogger.java
new file mode 100644
index 0000000..961e88d
--- /dev/null
+++ b/sdkmanager/app/tests/com/android/sdkmanager/MockStdLogger.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdkmanager;
+
+import com.android.sdklib.ISdkLog;
+
+/**
+ *
+ */
+public class MockStdLogger implements ISdkLog {
+
+ public void error(Throwable t, String errorFormat, Object... args) {
+ if (errorFormat != null) {
+ System.err.printf("Error: " + errorFormat, args);
+ if (!errorFormat.endsWith("\n")) {
+ System.err.printf("\n");
+ }
+ }
+ if (t != null) {
+ System.err.printf("Error: %s\n", t.getMessage());
+ }
+ }
+
+ public void warning(String warningFormat, Object... args) {
+ System.out.printf("Warning: " + warningFormat, args);
+ if (!warningFormat.endsWith("\n")) {
+ System.out.printf("\n");
+ }
+ }
+
+ public void printf(String msgFormat, Object... args) {
+ System.out.printf(msgFormat, args);
+ }
+}
diff --git a/sdkmanager/app/tests/com/android/sdkmanager/SdkCommandLineTest.java b/sdkmanager/app/tests/com/android/sdkmanager/SdkCommandLineTest.java
new file mode 100644
index 0000000..07a32e0
--- /dev/null
+++ b/sdkmanager/app/tests/com/android/sdkmanager/SdkCommandLineTest.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.sdkmanager;
+
+import com.android.sdklib.ISdkLog;
+
+import junit.framework.TestCase;
+
+public class SdkCommandLineTest extends TestCase {
+
+ private MockStdLogger mLog;
+
+ /**
+ * A mock version of the {@link SdkCommandLine} class that does not
+ * exits and discards its stdout/stderr output.
+ */
+ public static class MockSdkCommandLine extends SdkCommandLine {
+ private boolean mExitCalled;
+ private boolean mHelpCalled;
+
+ public MockSdkCommandLine(ISdkLog logger) {
+ super(logger);
+ }
+
+ @Override
+ public void printHelpAndExitForAction(String verb, String directObject,
+ String errorFormat, Object... args) {
+ mHelpCalled = true;
+ super.printHelpAndExitForAction(verb, directObject, errorFormat, args);
+ }
+
+ @Override
+ protected void exit() {
+ mExitCalled = true;
+ }
+
+ @Override
+ protected void stdout(String format, Object... args) {
+ // discard
+ }
+
+ @Override
+ protected void stderr(String format, Object... args) {
+ // discard
+ }
+
+ public boolean wasExitCalled() {
+ return mExitCalled;
+ }
+
+ public boolean wasHelpCalled() {
+ return mHelpCalled;
+ }
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ mLog = new MockStdLogger();
+ super.setUp();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ /** Test list */
+ public final void testList_Avd_Verbose() {
+ MockSdkCommandLine c = new MockSdkCommandLine(mLog);
+ c.parseArgs(new String[] { "-v", "list", "avd" });
+ assertFalse(c.wasHelpCalled());
+ assertFalse(c.wasExitCalled());
+ assertEquals("list", c.getVerb());
+ assertEquals("avd", c.getDirectObject());
+ assertTrue(c.isVerbose());
+ }
+
+ public final void testList_Target() {
+ MockSdkCommandLine c = new MockSdkCommandLine(mLog);
+ c.parseArgs(new String[] { "list", "target" });
+ assertFalse(c.wasHelpCalled());
+ assertFalse(c.wasExitCalled());
+ assertEquals("list", c.getVerb());
+ assertEquals("target", c.getDirectObject());
+ assertFalse(c.isVerbose());
+ }
+
+ public final void testList_None() {
+ MockSdkCommandLine c = new MockSdkCommandLine(mLog);
+ c.parseArgs(new String[] { "list" });
+ assertFalse(c.wasHelpCalled());
+ assertFalse(c.wasExitCalled());
+ assertEquals("list", c.getVerb());
+ assertEquals("", c.getDirectObject());
+ assertFalse(c.isVerbose());
+ }
+
+ public final void testList_Invalid() {
+ MockSdkCommandLine c = new MockSdkCommandLine(mLog);
+ c.parseArgs(new String[] { "list", "unknown" });
+ assertTrue(c.wasHelpCalled());
+ assertTrue(c.wasExitCalled());
+ assertEquals(null, c.getVerb());
+ assertEquals(null, c.getDirectObject());
+ assertFalse(c.isVerbose());
+ }
+
+ public final void testList_Plural() {
+ MockSdkCommandLine c = new MockSdkCommandLine(mLog);
+ c.parseArgs(new String[] { "list", "avds" });
+ assertFalse(c.wasHelpCalled());
+ assertFalse(c.wasExitCalled());
+ assertEquals("list", c.getVerb());
+ // we get the non-plural form
+ assertEquals("avd", c.getDirectObject());
+ assertFalse(c.isVerbose());
+
+ c = new MockSdkCommandLine(mLog);
+ c.parseArgs(new String[] { "list", "targets" });
+ assertFalse(c.wasHelpCalled());
+ assertFalse(c.wasExitCalled());
+ assertEquals("list", c.getVerb());
+ // we get the non-plural form
+ assertEquals("target", c.getDirectObject());
+ assertFalse(c.isVerbose());
+ }
+}