diff options
author | Raphael Moll <ralf@android.com> | 2011-01-10 10:51:48 -0800 |
---|---|---|
committer | Android Code Review <code-review@android.com> | 2011-01-10 10:51:48 -0800 |
commit | 22e85c08a52da817e6faba21d9e2513ce23be290 (patch) | |
tree | 688da1a557111759b75e3579b4bf1600412b67fe | |
parent | fbeff542e9a929dd1fc7e747c3175a8d7533ceb1 (diff) | |
parent | 482fedbe3c48c606fc7fcaae43a6cf36c11db1f8 (diff) | |
download | sdk-22e85c08a52da817e6faba21d9e2513ce23be290.zip sdk-22e85c08a52da817e6faba21d9e2513ce23be290.tar.gz sdk-22e85c08a52da817e6faba21d9e2513ce23be290.tar.bz2 |
Merge "Merge: Support broken addons in SDK Manager UI." into tools_r9
11 files changed, 626 insertions, 194 deletions
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkManager.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkManager.java index 32c5838..2920674 100644 --- a/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkManager.java +++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkManager.java @@ -21,6 +21,7 @@ import com.android.prefs.AndroidLocation.AndroidLocationException; import com.android.sdklib.AndroidVersion.AndroidVersionException; import com.android.sdklib.internal.project.ProjectProperties; import com.android.sdklib.io.FileWrapper; +import com.android.sdklib.util.Pair; import java.io.File; import java.io.FileInputStream; @@ -43,27 +44,27 @@ import java.util.regex.Pattern; */ public final class SdkManager { - public final static String PROP_VERSION_SDK = "ro.build.version.sdk"; - public final static String PROP_VERSION_CODENAME = "ro.build.version.codename"; - public final static String PROP_VERSION_RELEASE = "ro.build.version.release"; + public final static String PROP_VERSION_SDK = "ro.build.version.sdk"; //$NON-NLS-1$ + public final static String PROP_VERSION_CODENAME = "ro.build.version.codename"; //$NON-NLS-1$ + public final static String PROP_VERSION_RELEASE = "ro.build.version.release"; //$NON-NLS-1$ - private final static String ADDON_NAME = "name"; - private final static String ADDON_VENDOR = "vendor"; - private final static String ADDON_API = "api"; - private final static String ADDON_DESCRIPTION = "description"; - private final static String ADDON_LIBRARIES = "libraries"; - private final static String ADDON_DEFAULT_SKIN = "skin"; - private final static String ADDON_USB_VENDOR = "usb-vendor"; - private final static String ADDON_REVISION = "revision"; - private final static String ADDON_REVISION_OLD = "version"; + public final static String ADDON_NAME = "name"; //$NON-NLS-1$ + public final static String ADDON_VENDOR = "vendor"; //$NON-NLS-1$ + public final static String ADDON_API = "api"; //$NON-NLS-1$ + public final static String ADDON_DESCRIPTION = "description"; //$NON-NLS-1$ + public final static String ADDON_LIBRARIES = "libraries"; //$NON-NLS-1$ + public final static String ADDON_DEFAULT_SKIN = "skin"; //$NON-NLS-1$ + public final static String ADDON_USB_VENDOR = "usb-vendor"; //$NON-NLS-1$ + public final static String ADDON_REVISION = "revision"; //$NON-NLS-1$ + public final static String ADDON_REVISION_OLD = "version"; //$NON-NLS-1$ private final static Pattern PATTERN_LIB_DATA = Pattern.compile( - "^([a-zA-Z0-9._-]+\\.jar);(.*)$", Pattern.CASE_INSENSITIVE); + "^([a-zA-Z0-9._-]+\\.jar);(.*)$", Pattern.CASE_INSENSITIVE); //$NON-NLS-1$ // usb ids are 16-bit hexadecimal values. private final static Pattern PATTERN_USB_IDS = Pattern.compile( - "^0x[a-f0-9]{4}$", Pattern.CASE_INSENSITIVE); + "^0x[a-f0-9]{4}$", Pattern.CASE_INSENSITIVE); //$NON-NLS-1$ /** List of items in the platform to check when parsing it. These paths are relative to the * platform root folder. */ @@ -73,39 +74,40 @@ public final class SdkManager { }; /** Preference file containing the usb ids for adb */ - private final static String ADB_INI_FILE = "adb_usb.ini"; + private final static String ADB_INI_FILE = "adb_usb.ini"; //$NON-NLS-1$ //0--------90--------90--------90--------90--------90--------90--------90--------9 private final static String ADB_INI_HEADER = - "# ANDROID 3RD PARTY USB VENDOR ID LIST -- DO NOT EDIT.\n" + - "# USE 'android update adb' TO GENERATE.\n" + - "# 1 USB VENDOR ID PER LINE.\n"; + "# ANDROID 3RD PARTY USB VENDOR ID LIST -- DO NOT EDIT.\n" + //$NON-NLS-1$ + "# USE 'android update adb' TO GENERATE.\n" + //$NON-NLS-1$ + "# 1 USB VENDOR ID PER LINE.\n"; //$NON-NLS-1$ - /** the location of the SDK */ - private final String mSdkLocation; + /** The location of the SDK as an OS path */ + private final String mOsSdkPath; + /** Valid targets that have been loaded. */ private IAndroidTarget[] mTargets; /** * Create a new {@link SdkManager} instance. * External users should use {@link #createManager(String, ISdkLog)}. * - * @param sdkLocation the location of the SDK. + * @param osSdkPath the location of the SDK. */ - private SdkManager(String sdkLocation) { - mSdkLocation = sdkLocation; + private SdkManager(String osSdkPath) { + mOsSdkPath = osSdkPath; } /** * Creates an {@link SdkManager} for a given sdk location. - * @param sdkLocation the location of the SDK. + * @param osSdkPath the location of the SDK. * @param log the ISdkLog object receiving warning/error from the parsing. Cannot be null. * @return the created {@link SdkManager} or null if the location is not valid. */ - public static SdkManager createManager(String sdkLocation, ISdkLog log) { + public static SdkManager createManager(String osSdkPath, ISdkLog log) { try { - SdkManager manager = new SdkManager(sdkLocation); + SdkManager manager = new SdkManager(osSdkPath); ArrayList<IAndroidTarget> list = new ArrayList<IAndroidTarget>(); - loadPlatforms(sdkLocation, list, log); - loadAddOns(sdkLocation, list, log); + loadPlatforms(osSdkPath, list, log); + loadAddOns(osSdkPath, list, log); // sort the targets/add-ons Collections.sort(list); @@ -127,7 +129,7 @@ public final class SdkManager { * Returns the location of the SDK. */ public String getLocation() { - return mSdkLocation; + return mOsSdkPath; } /** @@ -193,7 +195,7 @@ public final class SdkManager { // now write the Id in a text file, one per line. for (Integer i : set) { - writer.write(String.format("0x%04x\n", i)); + writer.write(String.format("0x%04x\n", i)); //$NON-NLS-1$ } } finally { if (writer != null) { @@ -210,8 +212,8 @@ public final class SdkManager { public void reloadSdk(ISdkLog log) { // get the current target list. ArrayList<IAndroidTarget> list = new ArrayList<IAndroidTarget>(); - loadPlatforms(mSdkLocation, list, log); - loadAddOns(mSdkLocation, list, log); + loadPlatforms(mOsSdkPath, list, log); + loadAddOns(mOsSdkPath, list, log); // For now replace the old list with the new one. // In the future we may want to keep the current objects, so that ADT doesn't have to deal @@ -326,7 +328,7 @@ public final class SdkManager { sourcePropFile, log); if (sourceProp != null) { try { - revision = Integer.parseInt(sourceProp.get("Pkg.Revision")); + revision = Integer.parseInt(sourceProp.get("Pkg.Revision")); //$NON-NLS-1$ } catch (NumberFormatException e) { // do nothing, we'll keep the default value of 1. } @@ -366,7 +368,8 @@ public final class SdkManager { return target; } } else { - log.warning("Ignoring platform '%1$s': %2$s is missing.", platformFolder.getName(), + log.warning("Ignoring platform '%1$s': %2$s is missing.", //$NON-NLS-1$ + platformFolder.getName(), SdkConstants.FN_BUILD_PROP); } @@ -376,19 +379,21 @@ public final class SdkManager { /** * Loads the Add-on from the SDK. - * @param location Location of the SDK + * @param osSdkPath Location of the SDK * @param list the list to fill with the add-ons. * @param log the ISdkLog object receiving warning/error from the parsing. Cannot be null. */ - private static void loadAddOns(String location, ArrayList<IAndroidTarget> list, ISdkLog log) { - File addonFolder = new File(location, SdkConstants.FD_ADDONS); + private static void loadAddOns(String osSdkPath, ArrayList<IAndroidTarget> list, ISdkLog log) { + File addonFolder = new File(osSdkPath, SdkConstants.FD_ADDONS); if (addonFolder.isDirectory()) { File[] addons = addonFolder.listFiles(); + IAndroidTarget[] targetList = list.toArray(new IAndroidTarget[list.size()]); + for (File addon : addons) { // Add-ons have to be folders. Ignore files and no need to warn about them. if (addon.isDirectory()) { - AddOnTarget target = loadAddon(addon, list, log); + AddOnTarget target = loadAddon(addon, targetList, log); if (target != null) { list.add(target); } @@ -411,151 +416,222 @@ public final class SdkManager { /** * Loads a specific Add-on at a given location. - * @param addon the location of the addon. + * @param addonDir the location of the add-on directory. * @param targetList The list of Android target that were already loaded from the SDK. * @param log the ISdkLog object receiving warning/error from the parsing. Cannot be null. */ - private static AddOnTarget loadAddon(File addon, ArrayList<IAndroidTarget> targetList, + private static AddOnTarget loadAddon(File addonDir, + IAndroidTarget[] targetList, ISdkLog log) { - FileWrapper addOnManifest = new FileWrapper(addon, SdkConstants.FN_MANIFEST_INI); - if (addOnManifest.isFile()) { - Map<String, String> propertyMap = ProjectProperties.parsePropertyFile( - addOnManifest, log); + // Parse the addon properties to ensure we can load it. + Pair<Map<String, String>, String> infos = parseAddonProperties(addonDir, targetList, log); - if (propertyMap != null) { - // look for some specific values in the map. - // we require name, vendor, and api - String name = propertyMap.get(ADDON_NAME); - if (name == null) { - displayAddonManifestWarning(log, addon.getName(), ADDON_NAME); - return null; - } + Map<String, String> propertyMap = infos.getFirst(); + String error = infos.getSecond(); - String vendor = propertyMap.get(ADDON_VENDOR); - if (vendor == null) { - displayAddonManifestWarning(log, addon.getName(), ADDON_VENDOR); - return null; - } + if (error != null) { + log.warning("Ignoring add-on '%1$s': %2$s", addonDir.getName(), error); + return null; + } - String api = propertyMap.get(ADDON_API); - PlatformTarget baseTarget = null; - if (api == null) { - displayAddonManifestWarning(log, addon.getName(), ADDON_API); - return null; - } else { - // Look for a platform that has a matching api level or codename. - for (IAndroidTarget target : targetList) { - if (target.isPlatform() && target.getVersion().equals(api)) { - baseTarget = (PlatformTarget)target; - break; - } - } + // Since error==null we're not supposed to encounter any issues loading this add-on. + try { + assert propertyMap != null; - if (baseTarget == null) { - // Ignore this add-on. - log.warning( - "Ignoring add-on '%1$s': Unable to find base platform with API level '%2$s'", - addon.getName(), api); - return null; - } - } + String api = propertyMap.get(ADDON_API); + String name = propertyMap.get(ADDON_NAME); + String vendor = propertyMap.get(ADDON_VENDOR); - // get the optional description - String description = propertyMap.get(ADDON_DESCRIPTION); + assert api != null; + assert name != null; + assert vendor != null; - // get the add-on revision - int revisionValue = 1; - String revision = propertyMap.get(ADDON_REVISION); - if (revision == null) { - revision = propertyMap.get(ADDON_REVISION_OLD); - } - if (revision != null) { - try { - revisionValue = Integer.parseInt(revision); - } catch (NumberFormatException e) { - // looks like apiNumber does not parse to a number. - // Ignore this add-on. - log.warning( - "Ignoring add-on '%1$s': %2$s is not a valid number in %3$s.", - addon.getName(), ADDON_REVISION, SdkConstants.FN_BUILD_PROP); - return null; - } + PlatformTarget baseTarget = null; + + // Look for a platform that has a matching api level or codename. + for (IAndroidTarget target : targetList) { + if (target.isPlatform() && target.getVersion().equals(api)) { + baseTarget = (PlatformTarget)target; + break; } + } + + assert baseTarget != null; + + // get the optional description + String description = propertyMap.get(ADDON_DESCRIPTION); + + // get the add-on revision + int revisionValue = 1; + String revision = propertyMap.get(ADDON_REVISION); + if (revision == null) { + revision = propertyMap.get(ADDON_REVISION_OLD); + } + if (revision != null) { + revisionValue = Integer.parseInt(revision); + } - // get the optional libraries - String librariesValue = propertyMap.get(ADDON_LIBRARIES); - Map<String, String[]> libMap = null; - - if (librariesValue != null) { - librariesValue = librariesValue.trim(); - if (librariesValue.length() > 0) { - // split in the string into the libraries name - String[] libraries = librariesValue.split(";"); - if (libraries.length > 0) { - libMap = new HashMap<String, String[]>(); - for (String libName : libraries) { - libName = libName.trim(); - - // get the library data from the properties - String libData = propertyMap.get(libName); - - if (libData != null) { - // split the jar file from the description - Matcher m = PATTERN_LIB_DATA.matcher(libData); - if (m.matches()) { - libMap.put(libName, new String[] { - m.group(1), m.group(2) }); - } else { - log.warning( - "Ignoring library '%1$s', property value has wrong format\n\t%2$s", - libName, libData); - } + // get the optional libraries + String librariesValue = propertyMap.get(ADDON_LIBRARIES); + Map<String, String[]> libMap = null; + + if (librariesValue != null) { + librariesValue = librariesValue.trim(); + if (librariesValue.length() > 0) { + // split in the string into the libraries name + String[] libraries = librariesValue.split(";"); //$NON-NLS-1$ + if (libraries.length > 0) { + libMap = new HashMap<String, String[]>(); + for (String libName : libraries) { + libName = libName.trim(); + + // get the library data from the properties + String libData = propertyMap.get(libName); + + if (libData != null) { + // split the jar file from the description + Matcher m = PATTERN_LIB_DATA.matcher(libData); + if (m.matches()) { + libMap.put(libName, new String[] { + m.group(1), m.group(2) }); } else { log.warning( - "Ignoring library '%1$s', missing property value", + "Ignoring library '%1$s', property value has wrong format\n\t%2$s", libName, libData); } + } else { + log.warning( + "Ignoring library '%1$s', missing property value", + libName, libData); } } } } + } - AddOnTarget target = new AddOnTarget(addon.getAbsolutePath(), name, vendor, - revisionValue, description, libMap, baseTarget); + AddOnTarget target = new AddOnTarget(addonDir.getAbsolutePath(), name, vendor, + revisionValue, description, libMap, baseTarget); - // need to parse the skins. - String[] skins = parseSkinFolder(target.getPath(IAndroidTarget.SKINS)); + // need to parse the skins. + String[] skins = parseSkinFolder(target.getPath(IAndroidTarget.SKINS)); - // get the default skin, or take it from the base platform if needed. - String defaultSkin = propertyMap.get(ADDON_DEFAULT_SKIN); - if (defaultSkin == null) { - if (skins.length == 1) { - defaultSkin = skins[0]; - } else { - defaultSkin = baseTarget.getDefaultSkin(); - } + // get the default skin, or take it from the base platform if needed. + String defaultSkin = propertyMap.get(ADDON_DEFAULT_SKIN); + if (defaultSkin == null) { + if (skins.length == 1) { + defaultSkin = skins[0]; + } else { + defaultSkin = baseTarget.getDefaultSkin(); } + } - // get the USB ID (if available) - int usbVendorId = convertId(propertyMap.get(ADDON_USB_VENDOR)); - if (usbVendorId != IAndroidTarget.NO_USB_ID) { - target.setUsbVendorId(usbVendorId); - } + // get the USB ID (if available) + int usbVendorId = convertId(propertyMap.get(ADDON_USB_VENDOR)); + if (usbVendorId != IAndroidTarget.NO_USB_ID) { + target.setUsbVendorId(usbVendorId); + } - target.setSkins(skins, defaultSkin); + target.setSkins(skins, defaultSkin); - return target; - } - } else { - log.warning("Ignoring add-on '%1$s': %2$s is missing.", addon.getName(), - SdkConstants.FN_MANIFEST_INI); + return target; + } + catch (Exception e) { + log.warning("Ignoring add-on '%1$s': error %2$s.", + addonDir.getName(), e.toString()); } return null; } /** + * Parses the add-on properties and decodes any error that occurs when loading an addon. + * + * @param addonDir the location of the addon directory. + * @param targetList The list of Android target that were already loaded from the SDK. + * @param log the ISdkLog object receiving warning/error from the parsing. Cannot be null. + * @return A pair with the property map and an error string. Both can be null but not at the + * same time. If a non-null error is present then the property map must be ignored. The error + * should be translatable as it might show up in the SdkManager UI. + */ + public static Pair<Map<String, String>, String> parseAddonProperties( + File addonDir, + IAndroidTarget[] targetList, + ISdkLog log) { + Map<String, String> propertyMap = null; + String error = null; + + FileWrapper addOnManifest = new FileWrapper(addonDir, SdkConstants.FN_MANIFEST_INI); + + do { + if (!addOnManifest.isFile()) { + error = String.format("File not found: %1$s", SdkConstants.FN_MANIFEST_INI); + break; + } + + propertyMap = ProjectProperties.parsePropertyFile(addOnManifest, log); + if (propertyMap == null) { + error = String.format("Failed to parse properties from %1$s", + SdkConstants.FN_MANIFEST_INI); + break; + } + + // look for some specific values in the map. + // we require name, vendor, and api + String name = propertyMap.get(ADDON_NAME); + if (name == null) { + error = addonManifestWarning(ADDON_NAME); + break; + } + + String vendor = propertyMap.get(ADDON_VENDOR); + if (vendor == null) { + error = addonManifestWarning(ADDON_VENDOR); + break; + } + + String api = propertyMap.get(ADDON_API); + PlatformTarget baseTarget = null; + if (api == null) { + error = addonManifestWarning(ADDON_API); + break; + } + + // Look for a platform that has a matching api level or codename. + for (IAndroidTarget target : targetList) { + if (target.isPlatform() && target.getVersion().equals(api)) { + baseTarget = (PlatformTarget)target; + break; + } + } + + if (baseTarget == null) { + error = String.format("Unable to find base platform with API level '%1$s'", api); + break; + } + + // get the add-on revision + String revision = propertyMap.get(ADDON_REVISION); + if (revision == null) { + revision = propertyMap.get(ADDON_REVISION_OLD); + } + if (revision != null) { + try { + Integer.parseInt(revision); + } catch (NumberFormatException e) { + // looks like revision does not parse to a number. + error = String.format("%1$s is not a valid number in %2$s.", + ADDON_REVISION, SdkConstants.FN_BUILD_PROP); + break; + } + } + + } while(false); + + return Pair.of(propertyMap, error); + } + + /** * Converts a string representation of an hexadecimal ID into an int. * @param value the string to convert. * @return the int value, or {@link IAndroidTarget#NO_USB_ID} if the convertion failed. @@ -577,16 +653,14 @@ public final class SdkManager { } /** - * Displays a warning in the log about the addon being ignored due to a missing manifest value. + * Prepares a warning about the addon being ignored due to a missing manifest value. + * This string will show up in the SdkManager UI. * - * @param log The logger object. Cannot be null. - * @param addonName The addon name, for display. * @param valueName The missing manifest value, for display. */ - private static void displayAddonManifestWarning(ISdkLog log, String addonName, - String valueName) { - log.warning("Ignoring add-on '%1$s': '%2$s' is missing from %3$s.", - addonName, valueName, SdkConstants.FN_MANIFEST_INI); + private static String addonManifestWarning(String valueName) { + return String.format("'%1$s' is missing from %2$s.", + valueName, SdkConstants.FN_MANIFEST_INI); } /** @@ -603,7 +677,7 @@ public final class SdkManager { File f = new File(platform, relativePath); if (!f.exists()) { log.warning( - "Ignoring platform '%1$s': %2$s is missing.", + "Ignoring platform '%1$s': %2$s is missing.", //$NON-NLS-1$ platform.getName(), relativePath); return false; } @@ -650,7 +724,7 @@ public final class SdkManager { * @param log Logger. Cannot be null. */ private void loadSamples(ISdkLog log) { - File sampleFolder = new File(mSdkLocation, SdkConstants.FD_SAMPLES); + File sampleFolder = new File(mOsSdkPath, SdkConstants.FD_SAMPLES); if (sampleFolder.isDirectory()) { File[] platforms = sampleFolder.listFiles(); @@ -688,13 +762,13 @@ public final class SdkManager { return new AndroidVersion(p); } catch (FileNotFoundException e) { - log.warning("Ignoring sample '%1$s': does not contain %2$s.", //$NON-NLS-1$ + log.warning("Ignoring sample '%1$s': does not contain %2$s.", //$NON-NLS-1$ folder.getName(), SdkConstants.FN_SOURCE_PROP); } catch (IOException e) { - log.warning("Ignoring sample '%1$s': failed reading %2$s.", //$NON-NLS-1$ + log.warning("Ignoring sample '%1$s': failed reading %2$s.", //$NON-NLS-1$ folder.getName(), SdkConstants.FN_SOURCE_PROP); } catch (AndroidVersionException e) { - log.warning("Ignoring sample '%1$s': no android version found in %2$s.", //$NON-NLS-1$ + log.warning("Ignoring sample '%1$s': no android version found in %2$s.", //$NON-NLS-1$ folder.getName(), SdkConstants.FN_SOURCE_PROP); } diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/AddonPackage.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/AddonPackage.java index 2a58a89..8226a60 100755 --- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/AddonPackage.java +++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/AddonPackage.java @@ -21,6 +21,8 @@ import com.android.sdklib.IAndroidTarget; import com.android.sdklib.SdkConstants;
import com.android.sdklib.SdkManager;
import com.android.sdklib.IAndroidTarget.IOptionalLibrary;
+import com.android.sdklib.annotations.VisibleForTesting;
+import com.android.sdklib.annotations.VisibleForTesting.Visibility;
import com.android.sdklib.internal.repository.Archive.Arch;
import com.android.sdklib.internal.repository.Archive.Os;
import com.android.sdklib.repository.SdkRepoConstants;
@@ -98,7 +100,12 @@ public class AddonPackage extends Package * <p/>
* By design, this creates a package with one and only one archive.
*/
- AddonPackage(IAndroidTarget target, Properties props) {
+ static Package create(IAndroidTarget target, Properties props) {
+ return new AddonPackage(target, props);
+ }
+
+ @VisibleForTesting(visibility=Visibility.PRIVATE)
+ protected AddonPackage(IAndroidTarget target, Properties props) {
super( null, //source
props, //properties
target.getRevision(), //revision
@@ -126,6 +133,42 @@ public class AddonPackage extends Package }
/**
+ * Creates a broken addon which we know failed to load properly.
+ *
+ * @param archiveOsPath The absolute OS path of the addon folder.
+ * @param props The properties parsed from the addon manifest (not the source.properties).
+ * @param error The error indicating why this addon failed to be loaded.
+ */
+ static Package create(String archiveOsPath, Map<String, String> props, String error) {
+ String name = props.get(SdkManager.ADDON_NAME);
+ String vendor = props.get(SdkManager.ADDON_VENDOR);
+ String api = props.get(SdkManager.ADDON_API);
+ String revision = props.get(SdkManager.ADDON_REVISION);
+
+ String shortDesc = String.format("%1$s by %2$s, Android API %3$s, revision %4$s [*]",
+ name,
+ vendor,
+ api,
+ revision);
+
+ String longDesc = String.format(
+ "%1$s\n" +
+ "[*] Addon failed to load: %2$s",
+ shortDesc,
+ error);
+
+ int minApiLevel = IMinApiLevelDependency.MIN_API_LEVEL_NOT_SPECIFIED;
+
+ try {
+ minApiLevel = Integer.parseInt(api);
+ } catch(NumberFormatException e) {
+ // ignore
+ }
+
+ return new BrokenPackage(null/*props*/, shortDesc, longDesc, minApiLevel, archiveOsPath);
+ }
+
+ /**
* Save the properties of the current packages in the given {@link Properties} object.
* These properties will later be given to a constructor that takes a {@link Properties} object.
*/
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/BrokenPackage.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/BrokenPackage.java new file mode 100755 index 0000000..646172d --- /dev/null +++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/BrokenPackage.java @@ -0,0 +1,147 @@ +/*
+ * Copyright (C) 2010 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.sdklib.internal.repository;
+
+import com.android.sdklib.SdkManager;
+import com.android.sdklib.internal.repository.Archive.Arch;
+import com.android.sdklib.internal.repository.Archive.Os;
+
+import java.io.File;
+import java.util.Properties;
+
+/**
+ * Represents an SDK repository package that is incomplete.
+ * It has a distinct icon and a specific error that is supposed to help the user on how to fix it.
+ */
+public class BrokenPackage extends Package implements IMinApiLevelDependency {
+
+ /**
+ * The minimal API level required by this extra package, if > 0,
+ * or {@link #MIN_API_LEVEL_NOT_SPECIFIED} if there is no such requirement.
+ */
+ private final int mMinApiLevel;
+
+ private final String mShortDescription;
+
+ /**
+ * Creates a new "broken" package that represents a package that we failed to load,
+ * for whatever error indicated in <code>error</code>.
+ * There is also an <em>optional</em> API level dependency that can be specified.
+ * <p/>
+ * By design, this creates a package with one and only one archive.
+ */
+ BrokenPackage(Properties props,
+ String shortDescription,
+ String longDescription,
+ int minApiLevel,
+ String archiveOsPath) {
+ super( null, //source
+ props, //properties
+ 0, //revision will be taken from props
+ null, //license
+ longDescription, //description
+ null, //descUrl
+ Os.ANY, //archiveOs
+ Arch.ANY, //archiveArch
+ archiveOsPath //archiveOsPath
+ );
+ mShortDescription = shortDescription;
+ mMinApiLevel = minApiLevel;
+ }
+
+ /**
+ * Save the properties of the current packages in the given {@link Properties} object.
+ * These properties will later be given to a constructor that takes a {@link Properties} object.
+ * <p/>
+ * Base implementation override: We don't actually save properties for a broken package.
+ */
+ @Override
+ void saveProperties(Properties props) {
+ // Nop. We don't actually save properties for a broken package.
+ }
+
+ /**
+ * Returns the minimal API level required by this extra package, if > 0,
+ * or {@link #MIN_API_LEVEL_NOT_SPECIFIED} if there is no such requirement.
+ */
+ public int getMinApiLevel() {
+ return mMinApiLevel;
+ }
+
+ /** Returns a short description for an {@link IDescription}. */
+ @Override
+ public String getShortDescription() {
+ return mShortDescription;
+ }
+
+ /**
+ * Returns a long description for an {@link IDescription}.
+ *
+ * The long description is whatever the XML contains for the <description> field,
+ * or the short description if the former is empty.
+ */
+ @Override
+ public String getLongDescription() {
+ String s = getDescription();
+ if (s == null || s.length() == 0) {
+ s = getShortDescription();
+ }
+ return s;
+ }
+
+ /**
+ * We should not be attempting to install a broken package.
+ *
+ * {@inheritDoc}
+ */
+ @Override
+ public File getInstallFolder(String osSdkRoot, SdkManager sdkManager) {
+ // We should not be attempting to install a broken package.
+ return null;
+ }
+
+ @Override
+ public boolean sameItemAs(Package pkg) {
+ if (pkg instanceof BrokenPackage) {
+ return mShortDescription.equals(((BrokenPackage) pkg).mShortDescription) &&
+ getDescription().equals(pkg.getDescription()) &&
+ getMinApiLevel() == ((BrokenPackage) pkg).getMinApiLevel();
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean preInstallHook(Archive archive,
+ ITaskMonitor monitor,
+ String osSdkRoot,
+ File installFolder) {
+ // Nothing specific to do.
+ return super.preInstallHook(archive, monitor, osSdkRoot, installFolder);
+ }
+
+ /**
+ * Computes a hash of the installed content (in case of successful install.)
+ *
+ * {@inheritDoc}
+ */
+ @Override
+ public void postInstallHook(Archive archive, ITaskMonitor monitor, File installFolder) {
+ // Nothing specific to do.
+ super.postInstallHook(archive, monitor, installFolder);
+ }
+}
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/DocPackage.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/DocPackage.java index 5e0a767..8a4c19d 100755 --- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/DocPackage.java +++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/DocPackage.java @@ -68,7 +68,22 @@ public class DocPackage extends Package implements IPackageVersion { * <p/>
* By design, this creates a package with one and only one archive.
*/
- DocPackage(SdkSource source,
+ static Package create(SdkSource source,
+ Properties props,
+ int apiLevel,
+ String codename,
+ int revision,
+ String license,
+ String description,
+ String descUrl,
+ Os archiveOs,
+ Arch archiveArch,
+ String archiveOsPath) {
+ return new DocPackage(source, props, apiLevel, codename, revision, license, description,
+ descUrl, archiveOs, archiveArch, archiveOsPath);
+ }
+
+ private DocPackage(SdkSource source,
Properties props,
int apiLevel,
String codename,
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ExtraPackage.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ExtraPackage.java index d06b08d..cac622d 100755 --- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ExtraPackage.java +++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ExtraPackage.java @@ -87,7 +87,38 @@ public class ExtraPackage extends MinToolsPackage * <p/>
* By design, this creates a package with one and only one archive.
*/
- ExtraPackage(SdkSource source,
+ static Package create(SdkSource source,
+ Properties props,
+ String vendor,
+ String path,
+ int revision,
+ String license,
+ String description,
+ String descUrl,
+ Os archiveOs,
+ Arch archiveArch,
+ String archiveOsPath) {
+ ExtraPackage ep = new ExtraPackage(source, props, vendor, path, revision, license,
+ description, descUrl, archiveOs, archiveArch, archiveOsPath);
+
+ if (ep.isPathValid()) {
+ return ep;
+ } else {
+ String shortDesc = ep.getShortDescription() + " [*]"; //$NON-NLS-1$
+
+ String longDesc = String.format(
+ "Broken Extra Package: %1$s\n" +
+ "[*] Package cannot be used due to error: Invalid install path %2$s",
+ description,
+ ep.getPath());
+
+ BrokenPackage ba = new BrokenPackage(props, shortDesc, longDesc,
+ IMinApiLevelDependency.MIN_API_LEVEL_NOT_SPECIFIED, archiveOsPath);
+ return ba;
+ }
+ }
+
+ private ExtraPackage(SdkSource source,
Properties props,
String vendor,
String path,
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/LocalSdkParser.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/LocalSdkParser.java index 915a8e6..e0a6b21 100755 --- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/LocalSdkParser.java +++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/LocalSdkParser.java @@ -22,6 +22,7 @@ import com.android.sdklib.SdkConstants; import com.android.sdklib.SdkManager;
import com.android.sdklib.internal.repository.Archive.Arch;
import com.android.sdklib.internal.repository.Archive.Os;
+import com.android.sdklib.util.Pair;
import java.io.File;
import java.io.FileInputStream;
@@ -29,6 +30,7 @@ import java.io.IOException; import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
+import java.util.Map;
import java.util.Properties;
import java.util.Set;
@@ -102,13 +104,12 @@ public class LocalSdkParser { // for platforms, add-ons and samples, rely on the SdkManager parser
for(IAndroidTarget target : sdkManager.getTargets()) {
-
Properties props = parseProperties(new File(target.getLocation(),
SdkConstants.FN_SOURCE_PROP));
try {
if (target.isPlatform()) {
- pkg = new PlatformPackage(target, props);
+ pkg = PlatformPackage.create(target, props);
if (samplesRoot.isDirectory()) {
// Get the samples dir for a platform if it is located in the new
@@ -119,14 +120,14 @@ public class LocalSdkParser { Properties samplesProps = parseProperties(
new File(samplesDir, SdkConstants.FN_SOURCE_PROP));
if (samplesProps != null) {
- SamplePackage pkg2 = new SamplePackage(target, samplesProps);
+ Package pkg2 = SamplePackage.create(target, samplesProps);
packages.add(pkg2);
}
visited.add(samplesDir);
}
}
} else {
- pkg = new AddonPackage(target, props);
+ pkg = AddonPackage.create(target, props);
}
} catch (Exception e) {
log.error(e, null);
@@ -138,6 +139,7 @@ public class LocalSdkParser { }
}
+ scanMissingAddons(sdkManager, visited, packages, log);
scanMissingSamples(osSdkRoot, visited, packages, log);
scanExtras(osSdkRoot, visited, packages, log);
@@ -167,7 +169,7 @@ public class LocalSdkParser { Properties props = parseProperties(new File(dir, SdkConstants.FN_SOURCE_PROP));
if (props != null) {
try {
- ExtraPackage pkg = new ExtraPackage(
+ Package pkg = ExtraPackage.create(
null, //source
props, //properties
null, //vendor
@@ -181,11 +183,8 @@ public class LocalSdkParser { dir.getPath() //archiveOsPath
);
- // We only accept this as an extra package if it has a valid local path.
- if (pkg.isPathValid()) {
- packages.add(pkg);
- visited.add(dir);
- }
+ packages.add(pkg);
+ visited.add(dir);
} catch (Exception e) {
log.error(e, null);
}
@@ -217,7 +216,7 @@ public class LocalSdkParser { Properties props = parseProperties(new File(dir, SdkConstants.FN_SOURCE_PROP));
if (props != null) {
try {
- SamplePackage pkg = new SamplePackage(dir.getAbsolutePath(), props);
+ Package pkg = SamplePackage.create(dir.getAbsolutePath(), props);
packages.add(pkg);
visited.add(dir);
} catch (Exception e) {
@@ -229,6 +228,42 @@ public class LocalSdkParser { }
/**
+ * The sdk manager only lists valid addons. However here we also want to find "broken"
+ * addons, i.e. addons that failed to load for some reason.
+ * <p/>
+ * Find any other sub-directories under the /add-ons root that hasn't been visited yet
+ * and assume they contain broken addons.
+ */
+ private void scanMissingAddons(SdkManager sdkManager,
+ HashSet<File> visited,
+ ArrayList<Package> packages,
+ ISdkLog log) {
+ File addons = new File(new File(sdkManager.getLocation()), SdkConstants.FD_ADDONS);
+
+ if (!addons.isDirectory()) {
+ // It makes listFiles() return null so let's avoid it.
+ return;
+ }
+
+ for (File dir : addons.listFiles()) {
+ if (dir.isDirectory() && !visited.contains(dir)) {
+ Pair<Map<String, String>, String> infos =
+ SdkManager.parseAddonProperties(dir, sdkManager.getTargets(), log);
+
+ Map<String, String> props = infos.getFirst();
+ String error = infos.getSecond();
+ try {
+ Package pkg = AddonPackage.create(dir.getAbsolutePath(), props, error);
+ packages.add(pkg);
+ visited.add(dir);
+ } catch (Exception e) {
+ log.error(e, null);
+ }
+ }
+ }
+ }
+
+ /**
* Try to find a tools package at the given location.
* Returns null if not found.
*/
@@ -252,7 +287,7 @@ public class LocalSdkParser { // Create our package. use the properties if we found any.
try {
- ToolPackage pkg = new ToolPackage(
+ Package pkg = ToolPackage.create(
null, //source
props, //properties
0, //revision
@@ -289,20 +324,9 @@ public class LocalSdkParser { return null;
}
- Set<String> names = new HashSet<String>();
- for (File file : platformToolsFolder.listFiles()) {
- names.add(file.getName());
- }
- if (!names.contains(SdkConstants.FN_ADB) ||
- !names.contains(SdkConstants.FN_AAPT) ||
- !names.contains(SdkConstants.FN_AIDL) ||
- !names.contains(SdkConstants.FN_DX)) {
- return null;
- }
-
// Create our package. use the properties if we found any.
try {
- PlatformToolPackage pkg = new PlatformToolPackage(
+ Package pkg = PlatformToolPackage.create(
null, //source
props, //properties
0, //revision
@@ -333,7 +357,7 @@ public class LocalSdkParser { // We don't actually check the content of the file.
if (new File(docFolder, "index.html").isFile()) {
try {
- DocPackage pkg = new DocPackage(
+ Package pkg = DocPackage.create(
null, //source
props, //properties
0, //apiLevel
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/PlatformPackage.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/PlatformPackage.java index 1e5e391..b8a8623 100755 --- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/PlatformPackage.java +++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/PlatformPackage.java @@ -20,6 +20,8 @@ import com.android.sdklib.AndroidVersion; import com.android.sdklib.IAndroidTarget;
import com.android.sdklib.SdkConstants;
import com.android.sdklib.SdkManager;
+import com.android.sdklib.annotations.VisibleForTesting;
+import com.android.sdklib.annotations.VisibleForTesting.Visibility;
import com.android.sdklib.internal.repository.Archive.Arch;
import com.android.sdklib.internal.repository.Archive.Os;
import com.android.sdklib.repository.SdkRepoConstants;
@@ -73,7 +75,12 @@ public class PlatformPackage extends MinToolsPackage implements IPackageVersion * <p/>
* By design, this creates a package with one and only one archive.
*/
- PlatformPackage(IAndroidTarget target, Properties props) {
+ static Package create(IAndroidTarget target, Properties props) {
+ return new PlatformPackage(target, props);
+ }
+
+ @VisibleForTesting(visibility=Visibility.PRIVATE)
+ protected PlatformPackage(IAndroidTarget target, Properties props) {
super( null, //source
props, //properties
target.getRevision(), //revision
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/PlatformToolPackage.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/PlatformToolPackage.java index 717948e..7b0494f 100755 --- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/PlatformToolPackage.java +++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/PlatformToolPackage.java @@ -18,14 +18,18 @@ package com.android.sdklib.internal.repository; import com.android.sdklib.SdkConstants; import com.android.sdklib.SdkManager; +import com.android.sdklib.annotations.VisibleForTesting; +import com.android.sdklib.annotations.VisibleForTesting.Visibility; import com.android.sdklib.internal.repository.Archive.Arch; import com.android.sdklib.internal.repository.Archive.Os; import org.w3c.dom.Node; import java.io.File; +import java.util.HashSet; import java.util.Map; import java.util.Properties; +import java.util.Set; /** * Represents a platform-tool XML node in an SDK repository. @@ -54,7 +58,7 @@ public class PlatformToolPackage extends Package { * <p/> * By design, this creates a package with one and only one archive. */ - PlatformToolPackage( + static Package create( SdkSource source, Properties props, int revision, @@ -64,6 +68,68 @@ public class PlatformToolPackage extends Package { Os archiveOs, Arch archiveArch, String archiveOsPath) { + + PlatformToolPackage ptp = new PlatformToolPackage(source, props, revision, license, + description, descUrl, archiveOs, archiveArch, archiveOsPath); + + File platformToolsFolder = new File(archiveOsPath); + String error = null; + if (!platformToolsFolder.isDirectory()) { + error = "platform-tools folder is missing"; + } else { + File[] files = platformToolsFolder.listFiles(); + if (files == null || files.length == 0) { + error = "platform-tools folder is empty"; + } else { + Set<String> names = new HashSet<String>(); + for (File file : files) { + names.add(file.getName()); + } + for (String name : new String[] { SdkConstants.FN_ADB, + SdkConstants.FN_AAPT, + SdkConstants.FN_AIDL, + SdkConstants.FN_DX } ) { + if (!names.contains(name)) { + if (error == null) { + error = "platform-tools folder is missing "; + } else { + error += ", "; + } + error += name; + } + } + } + } + + if (error != null) { + String shortDesc = ptp.getShortDescription() + " [*]"; //$NON-NLS-1$ + + String longDesc = String.format( + "Broken Platform-Tools Package: %1$s\n" + + "[*] Package cannot be used due to error: %2$s", + description, + error); + + BrokenPackage ba = new BrokenPackage(props, shortDesc, longDesc, + IMinApiLevelDependency.MIN_API_LEVEL_NOT_SPECIFIED, archiveOsPath); + return ba; + } + + + return ptp; + } + + @VisibleForTesting(visibility=Visibility.PRIVATE) + protected PlatformToolPackage( + SdkSource source, + Properties props, + int revision, + String license, + String description, + String descUrl, + Os archiveOs, + Arch archiveArch, + String archiveOsPath) { super(source, props, revision, diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/SamplePackage.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/SamplePackage.java index 920a7e0..b6976e9 100755 --- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/SamplePackage.java +++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/SamplePackage.java @@ -91,7 +91,11 @@ public class SamplePackage extends MinToolsPackage * <p/>
* By design, this creates a package with one and only one archive.
*/
- SamplePackage(IAndroidTarget target, Properties props) {
+ static Package create(IAndroidTarget target, Properties props) {
+ return new SamplePackage(target, props);
+ }
+
+ private SamplePackage(IAndroidTarget target, Properties props) {
super( null, //source
props, //properties
0, //revision will be taken from props
@@ -121,7 +125,11 @@ public class SamplePackage extends MinToolsPackage * @throws AndroidVersionException if the {@link AndroidVersion} can't be restored
* from properties.
*/
- SamplePackage(String archiveOsPath, Properties props) throws AndroidVersionException {
+ static Package create(String archiveOsPath, Properties props) throws AndroidVersionException {
+ return new SamplePackage(archiveOsPath, props);
+ }
+
+ private SamplePackage(String archiveOsPath, Properties props) throws AndroidVersionException {
super(null, //source
props, //properties
0, //revision will be taken from props
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ToolPackage.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ToolPackage.java index c76de30..73ddc24 100755 --- a/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ToolPackage.java +++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/ToolPackage.java @@ -18,6 +18,8 @@ package com.android.sdklib.internal.repository; import com.android.sdklib.SdkConstants;
import com.android.sdklib.SdkManager;
+import com.android.sdklib.annotations.VisibleForTesting;
+import com.android.sdklib.annotations.VisibleForTesting.Visibility;
import com.android.sdklib.internal.repository.Archive.Arch;
import com.android.sdklib.internal.repository.Archive.Os;
import com.android.sdklib.repository.SdkRepoConstants;
@@ -97,7 +99,7 @@ public class ToolPackage extends Package implements IMinPlatformToolsDependency * <p/>
* By design, this creates a package with one and only one archive.
*/
- ToolPackage(
+ static Package create(
SdkSource source,
Properties props,
int revision,
@@ -107,6 +109,21 @@ public class ToolPackage extends Package implements IMinPlatformToolsDependency Os archiveOs,
Arch archiveArch,
String archiveOsPath) {
+ return new ToolPackage(source, props, revision, license, description,
+ descUrl, archiveOs, archiveArch, archiveOsPath);
+ }
+
+ @VisibleForTesting(visibility=Visibility.PRIVATE)
+ protected ToolPackage(
+ SdkSource source,
+ Properties props,
+ int revision,
+ String license,
+ String description,
+ String descUrl,
+ Os archiveOs,
+ Arch archiveArch,
+ String archiveOsPath) {
super(source,
props,
revision,
diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/broken_pkg_16.png b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/broken_pkg_16.png Binary files differnew file mode 100755 index 0000000..6daa67b --- /dev/null +++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/broken_pkg_16.png |