diff options
author | The Android Open Source Project <initial-contribution@android.com> | 2009-03-03 19:29:09 -0800 |
---|---|---|
committer | The Android Open Source Project <initial-contribution@android.com> | 2009-03-03 19:29:09 -0800 |
commit | 55a2c71f27d3e0b8344597c7f281e687cb7aeb1b (patch) | |
tree | ecd18b995aea8eeeb8b3823266280d41245bf0f7 /sdkmanager/app | |
parent | 82ea7a177797b844b252effea5c7c7c5d63ea4ac (diff) | |
download | sdk-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/.classpath | 11 | ||||
-rw-r--r-- | sdkmanager/app/.project | 17 | ||||
-rw-r--r-- | sdkmanager/app/Android.mk | 5 | ||||
-rw-r--r-- | sdkmanager/app/etc/Android.mk | 8 | ||||
-rwxr-xr-x | sdkmanager/app/etc/android | 84 | ||||
-rwxr-xr-x | sdkmanager/app/etc/android.bat | 51 | ||||
-rw-r--r-- | sdkmanager/app/etc/manifest.txt | 1 | ||||
-rw-r--r-- | sdkmanager/app/src/Android.mk | 16 | ||||
-rw-r--r-- | sdkmanager/app/src/com/android/sdkmanager/CommandLineProcessor.java | 791 | ||||
-rw-r--r-- | sdkmanager/app/src/com/android/sdkmanager/Main.java | 813 | ||||
-rw-r--r-- | sdkmanager/app/src/com/android/sdkmanager/SdkCommandLine.java | 220 | ||||
-rw-r--r-- | sdkmanager/app/tests/com/android/sdkmanager/CommandLineProcessorTest.java | 186 | ||||
-rw-r--r-- | sdkmanager/app/tests/com/android/sdkmanager/MockStdLogger.java | 48 | ||||
-rw-r--r-- | sdkmanager/app/tests/com/android/sdkmanager/SdkCommandLineTest.java | 141 |
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()); + } +} |