diff --git a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/AddonPackage.java b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/AddonPackage.java index f1cdf1d07..0dec3bb3b 100755 --- a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/AddonPackage.java +++ b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/AddonPackage.java @@ -143,12 +143,13 @@ public class AddonPackage extends Package implements IPackageVersion { ArrayList libs = new ArrayList(); if (libsNode != null) { + String nsUri = libsNode.getNamespaceURI(); for(Node child = libsNode.getFirstChild(); child != null; child = child.getNextSibling()) { if (child.getNodeType() == Node.ELEMENT_NODE && - SdkRepository.NS_SDK_REPOSITORY.equals(child.getNamespaceURI()) && + nsUri.equals(child.getNamespaceURI()) && SdkRepository.NODE_LIB.equals(child.getLocalName())) { libs.add(parseLib(child)); } diff --git a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/Archive.java b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/Archive.java index 040668cc7..18774eaaf 100755 --- a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/Archive.java +++ b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/Archive.java @@ -96,6 +96,11 @@ public class Archive implements IDescription { return mUiName; } + /** Returns the XML name of the OS. */ + public String getXmlName() { + return toString().toLowerCase(); + } + /** * Returns the current OS as one of the {@link Os} enum values or null. */ @@ -113,6 +118,16 @@ public class Archive implements IDescription { return null; } + + /** Returns true if this OS is compatible with the current one. */ + public boolean isCompatible() { + if (this == ANY) { + return true; + } + + Os os = getCurrentOs(); + return this == os; + } } /** The Architecture that this archive can be downloaded on. */ @@ -133,6 +148,11 @@ public class Archive implements IDescription { return mUiName; } + /** Returns the XML name of the architecture. */ + public String getXmlName() { + return toString().toLowerCase(); + } + /** * Returns the current architecture as one of the {@link Arch} enum values or null. */ @@ -154,6 +174,16 @@ public class Archive implements IDescription { return null; } + + /** Returns true if this architecture is compatible with the current one. */ + public boolean isCompatible() { + if (this == ANY) { + return true; + } + + Arch arch = getCurrentArch(); + return this == arch; + } } private final Os mOs; @@ -324,27 +354,7 @@ public class Archive implements IDescription { * Returns true if this archive can be installed on the current platform. */ public boolean isCompatible() { - // Check OS - Os os = getOs(); - - if (os != Os.ANY) { - Os os2 = Os.getCurrentOs(); - if (os2 != os) { - return false; - } - } - - // Check Arch - Arch arch = getArch(); - - if (arch != Arch.ANY) { - Arch arch2 = Arch.getCurrentArch(); - if (arch2 != arch) { - return false; - } - } - - return true; + return getOs().isCompatible() && getArch().isCompatible(); } /** @@ -1053,8 +1063,7 @@ public class Archive implements IDescription { /** * Sets the executable Unix permission (0777) on a file or folder. * @param file The file to set permissions on. - * @param unixMode the permissions as received from {@link ZipArchiveEntry#getUnixMode()}. - * @throws IOException + * @throws IOException If an I/O error occurs */ private void setExecutablePermission(File file) throws IOException { Runtime.getRuntime().exec(new String[] { diff --git a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/Package.java b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/Package.java index 657bb143a..b43acc7bf 100755 --- a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/Package.java +++ b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/Package.java @@ -211,12 +211,13 @@ public abstract class Package implements IDescription { ArrayList archives = new ArrayList(); if (archivesNode != null) { + String nsUri = archivesNode.getNamespaceURI(); for(Node child = archivesNode.getFirstChild(); child != null; child = child.getNextSibling()) { if (child.getNodeType() == Node.ELEMENT_NODE && - SdkRepository.NS_SDK_REPOSITORY.equals(child.getNamespaceURI()) && + nsUri.equals(child.getNamespaceURI()) && SdkRepository.NODE_ARCHIVE.equals(child.getLocalName())) { archives.add(parseArchive(child)); } diff --git a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/RepoSource.java b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/RepoSource.java index 573454d99..6e8d98480 100755 --- a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/RepoSource.java +++ b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/RepoSource.java @@ -16,9 +16,13 @@ package com.android.sdklib.internal.repository; +import com.android.sdklib.internal.repository.Archive.Arch; +import com.android.sdklib.internal.repository.Archive.Os; import com.android.sdklib.repository.SdkRepository; import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.xml.sax.InputSource; import org.xml.sax.SAXException; @@ -30,6 +34,7 @@ import java.io.InputStream; import java.net.URL; import java.util.ArrayList; import java.util.HashMap; +import java.util.regex.Pattern; import javax.net.ssl.SSLKeyException; import javax.xml.XMLConstants; @@ -156,15 +161,23 @@ public class RepoSource implements IDescription { mFetchError = null; Exception[] exception = new Exception[] { null }; ByteArrayInputStream xml = fetchUrl(url, exception); - boolean validated = false; + Document validatedDoc = null; + String validatedUri = null; if (xml != null) { monitor.setDescription("Validate XML"); - validated = validateXml(xml, url, monitor); + String uri = validateXml(xml, url, monitor); + if (uri != null) { + validatedDoc = getDocument(xml, monitor); + validatedUri = uri; + } else { + validatedDoc = findAlternateToolsXml(xml); + validatedUri = SdkRepository.NS_SDK_REPOSITORY; + } } // If we failed the first time and the URL doesn't explicitly end with // our filename, make another tentative after changing the URL. - if (!validated && !url.endsWith(SdkRepository.URL_DEFAULT_XML_FILE)) { + if (validatedDoc == null && !url.endsWith(SdkRepository.URL_DEFAULT_XML_FILE)) { if (!url.endsWith("/")) { //$NON-NLS-1$ url += "/"; //$NON-NLS-1$ } @@ -172,10 +185,17 @@ public class RepoSource implements IDescription { xml = fetchUrl(url, exception); if (xml != null) { - validated = validateXml(xml, url, monitor); + String uri = validateXml(xml, url, monitor); + if (uri != null) { + validatedDoc = getDocument(xml, monitor); + validatedUri = uri; + } else { + validatedDoc = findAlternateToolsXml(xml); + validatedUri = SdkRepository.NS_SDK_REPOSITORY; + } } - if (validated) { + if (validatedDoc != null) { // If the second tentative succeeded, indicate it in the console // with the URL that worked. monitor.setResult("Repository found at %1$s", url); @@ -209,7 +229,7 @@ public class RepoSource implements IDescription { } // Stop here if we failed to validate the XML. We don't want to load it. - if (!validated) { + if (validatedDoc == null) { return; } @@ -218,7 +238,7 @@ public class RepoSource implements IDescription { if (xml != null) { monitor.setDescription("Parse XML"); monitor.incProgress(1); - parsePackages(xml, monitor); + parsePackages(validatedDoc, validatedUri, monitor); if (mPackages == null || mPackages.length == 0) { mDescription += "\nNo packages found."; } else if (mPackages.length == 1) { @@ -293,43 +313,324 @@ public class RepoSource implements IDescription { } /** - * Validates this XML against the SDK Repository schema. - * Returns true if the XML was correctly validated. + * Validates this XML against one of the possible SDK Repository schema, starting + * by the most recent one. + * If the XML was correctly validated, returns the schema that worked. + * If no schema validated the XML, returns null. */ - private boolean validateXml(ByteArrayInputStream xml, String url, ITaskMonitor monitor) { + private String validateXml(ByteArrayInputStream xml, String url, ITaskMonitor monitor) { - try { - Validator validator = getValidator(); + String lastError = null; + String extraError = null; + for (int version = SdkRepository.XSD_LATEST_VERSION; version >= 1; version--) { + try { + Validator validator = getValidator(version); - if (validator == null) { - monitor.setResult( - "XML verification failed for %1$s.\nNo suitable XML Schema Validator could be found in your Java environment. Please consider updating your version of Java.", - url); - return false; + if (validator == null) { + lastError = "XML verification failed for %1$s.\nNo suitable XML Schema Validator could be found in your Java environment. Please consider updating your version of Java."; + continue; + } + + xml.reset(); + // Validation throws a bunch of possible Exceptions on failure. + validator.validate(new StreamSource(xml)); + return SdkRepository.getSchemaUri(version); + + } catch (Exception e) { + lastError = "XML verification failed for %1$s.\nError: %2$s"; + extraError = e.getMessage(); + if (extraError == null) { + extraError = e.getClass().getName(); + } } - - xml.reset(); - validator.validate(new StreamSource(xml)); - return true; - - } catch (Exception e) { - String s = e.getMessage(); - if (s == null) { - s = e.getClass().getName(); - } - monitor.setResult("XML verification failed for %1$s.\nError: %2$s", - url, - s); } - return false; + if (lastError != null) { + monitor.setResult(lastError, url, extraError); + } + return null; } /** - * Helper method that returns a validator for our XSD + * The purpose of this method is to support forward evolution of our schema. + *

+ * At this point, we know that xml does not point to any schema that this version of + * the tool know how to process, so it's not one of the possible 1..N versions of our + * XSD schema. + *

+ * We thus try to interpret the byte stream as a possible XML stream. It may not be + * one at all in the first place. If it looks anything line an XML schema, we try to + * find its <tool> elements. If we find any, we recreate a suitable document + * that conforms to what we expect from our XSD schema with only those elements. + * To be valid, the <tool> element must have at least one <archive> + * compatible with this platform. + * + * If we don't find anything suitable, we drop the whole thing. + * + * @param xml The input XML stream. Can be null. + * @return Either a new XML document conforming to our schema with at least one <tool> + * element or null. */ - private Validator getValidator() throws SAXException { - InputStream xsdStream = SdkRepository.getXsdStream(); + protected Document findAlternateToolsXml(InputStream xml) { + // Note: protected for unit-test access + + if (xml == null) { + return null; + } + + // Reset the stream if it supports that operation. + // At runtime we use a ByteArrayInputStream which can be reset; however for unit tests + // we use a FileInputStream that doesn't support resetting and is read-once. + try { + xml.reset(); + } catch (IOException e1) { + // ignore if not supported + } + + // Get an XML document + + Document oldDoc = null; + Document newDoc = null; + try { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setIgnoringComments(false); + factory.setValidating(false); + + // Parse the old document using a non namespace aware builder + factory.setNamespaceAware(false); + DocumentBuilder builder = factory.newDocumentBuilder(); + oldDoc = builder.parse(xml); + + // Prepare a new document using a namespace aware builder + factory.setNamespaceAware(true); + builder = factory.newDocumentBuilder(); + newDoc = builder.newDocument(); + + } catch (Exception e) { + // Failed to get builder factor + // Failed to create XML document builder + // Failed to parse XML document + // Failed to read XML document + } + + if (oldDoc == null || newDoc == null) { + return null; + } + + + // Check the root element is an xsd-schema with at least the following properties: + // + // + // Note that we don't have namespace support enabled, we just do it manually. + + Pattern nsPattern = Pattern.compile(SdkRepository.NS_SDK_REPOSITORY_PATTERN); + + Node oldRoot = null; + String prefix = null; + for (Node child = oldDoc.getFirstChild(); child != null; child = child.getNextSibling()) { + if (child.getNodeType() == Node.ELEMENT_NODE) { + prefix = null; + String name = child.getNodeName(); + int pos = name.indexOf(':'); + if (pos > 0 && pos < name.length() - 1) { + prefix = name.substring(0, pos); + name = name.substring(pos + 1); + } + if (SdkRepository.NODE_SDK_REPOSITORY.equals(name)) { + NamedNodeMap attrs = child.getAttributes(); + String xmlns = "xmlns"; //$NON-NLS-1$ + if (prefix != null) { + xmlns += ":" + prefix; //$NON-NLS-1$ + } + Node attr = attrs.getNamedItem(xmlns); + if (attr != null) { + String uri = attr.getNodeValue(); + if (uri != null && nsPattern.matcher(uri).matches()) { + oldRoot = child; + break; + } + } + } + } + } + + // we must have found the root node, and it must have an XML namespace prefix. + if (oldRoot == null || prefix == null || prefix.length() == 0) { + return null; + } + + final String ns = SdkRepository.NS_SDK_REPOSITORY; + Element newRoot = newDoc.createElementNS(ns, SdkRepository.NODE_SDK_REPOSITORY); + newRoot.setPrefix(prefix); + newDoc.appendChild(newRoot); + int numTool = 0; + + // Find an inner node and extract its required parameters + + Node tool = null; + while ((tool = findChild(oldRoot, tool, prefix, SdkRepository.NODE_TOOL)) != null) { + // To be valid, the tool element must have: + // - a element with a number + // - an optional node, which we'll skip right now. + // (if we add it later, we must find the license declaration element too) + // - an element with one or more elements inside + // - one of the elements must have an "os" and "arch" attributes + // compatible with the current platform. Only keep the first such element found. + // - the element must contain a , a and a . + + try { + Node revision = findChild(tool, null, prefix, SdkRepository.NODE_REVISION); + Node archives = findChild(tool, null, prefix, SdkRepository.NODE_ARCHIVES); + + if (revision == null || archives == null) { + continue; + } + + int rev = 0; + try { + String content = revision.getTextContent(); + content = content.trim(); + rev = Integer.parseInt(content); + if (rev < 1) { + continue; + } + } catch (NumberFormatException ignore) { + continue; + } + + Element newTool = newDoc.createElementNS(ns, SdkRepository.NODE_TOOL); + newTool.setPrefix(prefix); + appendChild(newTool, ns, prefix, + SdkRepository.NODE_REVISION, Integer.toString(rev)); + Element newArchives = appendChild(newTool, ns, prefix, + SdkRepository.NODE_ARCHIVES, null); + int numArchives = 0; + + Node archive = null; + while ((archive = findChild(archives, + archive, + prefix, + SdkRepository.NODE_ARCHIVE)) != null) { + try { + Os os = (Os) XmlParserUtils.getEnumAttribute(archive, + SdkRepository.ATTR_OS, + Os.values(), + null /*default*/); + Arch arch = (Arch) XmlParserUtils.getEnumAttribute(archive, + SdkRepository.ATTR_ARCH, + Arch.values(), + Arch.ANY); + if (os == null || !os.isCompatible() || + arch == null || !arch.isCompatible()) { + continue; + } + + Node node = findChild(archive, null, prefix, SdkRepository.NODE_URL); + String url = node == null ? null : node.getTextContent().trim(); + if (url == null || url.length() == 0) { + continue; + } + + node = findChild(archive, null, prefix, SdkRepository.NODE_SIZE); + long size = 0; + try { + size = Long.parseLong(node.getTextContent()); + } catch (Exception e) { + // pass + } + if (size < 1) { + continue; + } + + node = findChild(archive, null, prefix, SdkRepository.NODE_CHECKSUM); + // double check that the checksum element contains a type=sha1 attribute + if (node == null) { + continue; + } + NamedNodeMap attrs = node.getAttributes(); + Node typeNode = attrs.getNamedItem(SdkRepository.ATTR_TYPE); + if (typeNode == null || + !SdkRepository.ATTR_TYPE.equals(typeNode.getNodeName()) || + !SdkRepository.SHA1_TYPE.equals(typeNode.getNodeValue())) { + continue; + } + String sha1 = node == null ? null : node.getTextContent().trim(); + if (sha1 == null || sha1.length() != SdkRepository.SHA1_CHECKSUM_LEN) { + continue; + } + + // Use that archive for the new tool element + Element ar = appendChild(newArchives, ns, prefix, + SdkRepository.NODE_ARCHIVE, null); + ar.setAttributeNS(ns, SdkRepository.ATTR_OS, os.getXmlName()); + ar.setAttributeNS(ns, SdkRepository.ATTR_ARCH, arch.getXmlName()); + + appendChild(ar, ns, prefix, SdkRepository.NODE_URL, url); + appendChild(ar, ns, prefix, SdkRepository.NODE_SIZE, Long.toString(size)); + Element cs = appendChild(ar, ns, prefix, SdkRepository.NODE_CHECKSUM, sha1); + cs.setAttributeNS(ns, SdkRepository.ATTR_TYPE, SdkRepository.SHA1_TYPE); + + numArchives++; + + } catch (Exception ignore1) { + // pass + } + } // while + + if (numArchives > 0) { + newRoot.appendChild(newTool); + numTool++; + } + } catch (Exception ignore2) { + // pass + } + } // while + + return numTool > 0 ? newDoc : null; + } + + /** + * Helper method used by {@link #findAlternateToolsXml(InputStream)} to find a given + * element child in a root XML node. + */ + private Node findChild(Node rootNode, Node after, String prefix, String nodeName) { + nodeName = prefix + ":" + nodeName; + Node child = after == null ? rootNode.getFirstChild() : after.getNextSibling(); + for(; child != null; child = child.getNextSibling()) { + if (child.getNodeType() == Node.ELEMENT_NODE && nodeName.equals(child.getNodeName())) { + return child; + } + } + return null; + } + + /** + * Helper method used by {@link #findAlternateToolsXml(InputStream)} to create a new + * XML element into a parent element. + */ + private Element appendChild(Element rootNode, String namespaceUri, + String prefix, String nodeName, + String nodeValue) { + Element node = rootNode.getOwnerDocument().createElementNS(namespaceUri, nodeName); + node.setPrefix(prefix); + if (nodeValue != null) { + node.setTextContent(nodeValue); + } + rootNode.appendChild(node); + return node; + } + + + /** + * Helper method that returns a validator for our XSD, or null if the current Java + * implementation can't process XSD schemas. + * + * @param version The version of the XML Schema. + * See {@link SdkRepository#getXsdStream(int)} + */ + private Validator getValidator(int version) throws SAXException { + InputStream xsdStream = SdkRepository.getXsdStream(version); SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); if (factory == null) { @@ -349,84 +650,74 @@ public class RepoSource implements IDescription { * Parse all packages defined in the SDK Repository XML and creates * a new mPackages array with them. */ - private boolean parsePackages(ByteArrayInputStream xml, ITaskMonitor monitor) { + protected boolean parsePackages(Document doc, String nsUri, ITaskMonitor monitor) { + // protected for unit-test acces - try { - Document doc = getDocument(xml); + assert doc != null; - Node root = getFirstChild(doc, SdkRepository.NODE_SDK_REPOSITORY); - if (root != null) { + Node root = getFirstChild(doc, nsUri, SdkRepository.NODE_SDK_REPOSITORY); + if (root != null) { - ArrayList packages = new ArrayList(); + ArrayList packages = new ArrayList(); - // Parse license definitions - HashMap licenses = new HashMap(); - for (Node child = root.getFirstChild(); - child != null; - child = child.getNextSibling()) { - if (child.getNodeType() == Node.ELEMENT_NODE && - SdkRepository.NS_SDK_REPOSITORY.equals(child.getNamespaceURI()) && - child.getLocalName().equals(SdkRepository.NODE_LICENSE)) { - Node id = child.getAttributes().getNamedItem(SdkRepository.ATTR_ID); - if (id != null) { - licenses.put(id.getNodeValue(), child.getTextContent()); - } + // Parse license definitions + HashMap licenses = new HashMap(); + for (Node child = root.getFirstChild(); + child != null; + child = child.getNextSibling()) { + if (child.getNodeType() == Node.ELEMENT_NODE && + nsUri.equals(child.getNamespaceURI()) && + child.getLocalName().equals(SdkRepository.NODE_LICENSE)) { + Node id = child.getAttributes().getNamedItem(SdkRepository.ATTR_ID); + if (id != null) { + licenses.put(id.getNodeValue(), child.getTextContent()); } } - - // Parse packages - for (Node child = root.getFirstChild(); - child != null; - child = child.getNextSibling()) { - if (child.getNodeType() == Node.ELEMENT_NODE && - SdkRepository.NS_SDK_REPOSITORY.equals(child.getNamespaceURI())) { - String name = child.getLocalName(); - Package p = null; - - try { - // We can load addon and extra packages from all sources, either - // internal or user sources. - if (SdkRepository.NODE_ADD_ON.equals(name)) { - p = new AddonPackage(this, child, licenses); - - } else if (SdkRepository.NODE_EXTRA.equals(name)) { - p = new ExtraPackage(this, child, licenses); - - } else if (!mUserSource) { - // We only load platform, doc and tool packages from internal - // sources, never from user sources. - if (SdkRepository.NODE_PLATFORM.equals(name)) { - p = new PlatformPackage(this, child, licenses); - } else if (SdkRepository.NODE_DOC.equals(name)) { - p = new DocPackage(this, child, licenses); - } else if (SdkRepository.NODE_TOOL.equals(name)) { - p = new ToolPackage(this, child, licenses); - } - } - - if (p != null) { - packages.add(p); - monitor.setDescription("Found %1$s", p.getShortDescription()); - } - } catch (Exception e) { - // Ignore invalid packages - } - } - } - - mPackages = packages.toArray(new Package[packages.size()]); - - return true; } - } catch (ParserConfigurationException e) { - monitor.setResult("Failed to create XML document builder"); + // Parse packages + for (Node child = root.getFirstChild(); + child != null; + child = child.getNextSibling()) { + if (child.getNodeType() == Node.ELEMENT_NODE && + nsUri.equals(child.getNamespaceURI())) { + String name = child.getLocalName(); + Package p = null; - } catch (SAXException e) { - monitor.setResult("Failed to parse XML document"); + try { + // We can load addon and extra packages from all sources, either + // internal or user sources. + if (SdkRepository.NODE_ADD_ON.equals(name)) { + p = new AddonPackage(this, child, licenses); - } catch (IOException e) { - monitor.setResult("Failed to read XML document"); + } else if (SdkRepository.NODE_EXTRA.equals(name)) { + p = new ExtraPackage(this, child, licenses); + + } else if (!mUserSource) { + // We only load platform, doc and tool packages from internal + // sources, never from user sources. + if (SdkRepository.NODE_PLATFORM.equals(name)) { + p = new PlatformPackage(this, child, licenses); + } else if (SdkRepository.NODE_DOC.equals(name)) { + p = new DocPackage(this, child, licenses); + } else if (SdkRepository.NODE_TOOL.equals(name)) { + p = new ToolPackage(this, child, licenses); + } + } + + if (p != null) { + packages.add(p); + monitor.setDescription("Found %1$s", p.getShortDescription()); + } + } catch (Exception e) { + // Ignore invalid packages + } + } + } + + mPackages = packages.toArray(new Package[packages.size()]); + + return true; } return false; @@ -436,11 +727,11 @@ public class RepoSource implements IDescription { * Returns the first child element with the given XML local name. * If xmlLocalName is null, returns the very first child element. */ - private Node getFirstChild(Node node, String xmlLocalName) { + private Node getFirstChild(Node node, String nsUri, String xmlLocalName) { for(Node child = node.getFirstChild(); child != null; child = child.getNextSibling()) { if (child.getNodeType() == Node.ELEMENT_NODE && - SdkRepository.NS_SDK_REPOSITORY.equals(child.getNamespaceURI())) { + nsUri.equals(child.getNamespaceURI())) { if (xmlLocalName == null || child.getLocalName().equals(xmlLocalName)) { return child; } @@ -452,17 +743,30 @@ public class RepoSource implements IDescription { /** * Takes an XML document as a string as parameter and returns a DOM for it. + * + * On error, returns null and prints a (hopefully) useful message on the monitor. */ - private Document getDocument(ByteArrayInputStream xml) - throws ParserConfigurationException, SAXException, IOException { - DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); - factory.setIgnoringComments(true); - factory.setNamespaceAware(true); + private Document getDocument(ByteArrayInputStream xml, ITaskMonitor monitor) { + try { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setIgnoringComments(true); + factory.setNamespaceAware(true); - DocumentBuilder builder = factory.newDocumentBuilder(); - xml.reset(); - Document doc = builder.parse(new InputSource(xml)); + DocumentBuilder builder = factory.newDocumentBuilder(); + xml.reset(); + Document doc = builder.parse(new InputSource(xml)); - return doc; + return doc; + } catch (ParserConfigurationException e) { + monitor.setResult("Failed to create XML document builder"); + + } catch (SAXException e) { + monitor.setResult("Failed to parse XML document"); + + } catch (IOException e) { + monitor.setResult("Failed to read XML document"); + } + + return null; } } diff --git a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/XmlParserUtils.java b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/XmlParserUtils.java index 7a8bc7dd3..7d3cd7efe 100755 --- a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/XmlParserUtils.java +++ b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/internal/repository/XmlParserUtils.java @@ -16,8 +16,6 @@ package com.android.sdklib.internal.repository; -import com.android.sdklib.repository.SdkRepository; - import org.w3c.dom.Node; /** @@ -31,9 +29,10 @@ class XmlParserUtils { */ public static Node getFirstChild(Node node, String xmlLocalName) { + String nsUri = node.getNamespaceURI(); for(Node child = node.getFirstChild(); child != null; child = child.getNextSibling()) { if (child.getNodeType() == Node.ELEMENT_NODE && - SdkRepository.NS_SDK_REPOSITORY.equals(child.getNamespaceURI())) { + nsUri.equals(child.getNamespaceURI())) { if (xmlLocalName == null || xmlLocalName.equals(child.getLocalName())) { return child; } diff --git a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/SdkRepository.java b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/SdkRepository.java index 2bf87d727..2cfca6f5c 100755 --- a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/SdkRepository.java +++ b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/SdkRepository.java @@ -30,9 +30,26 @@ public class SdkRepository { public static final String URL_DEFAULT_XML_FILE = "repository.xml"; //$NON-NLS-1$ - /** The XML namespace of the sdk-repository XML. */ - public static final String NS_SDK_REPOSITORY = - "http://schemas.android.com/sdk/android/repository/1"; //$NON-NLS-1$ + /** The XML namespace of the sdk-repository XML version 1. + * @deprecated + */ + public static final String NS_SDK_REPOSITORY_1 = getSchemaUri(1); + + /** The XML namespace of the sdk-repository XML version 2. + * @deprecated + */ + public static final String NS_SDK_REPOSITORY_2 = getSchemaUri(2); + + /** The XML namespace of the latest sdk-repository XML. */ + public static final String NS_SDK_REPOSITORY = NS_SDK_REPOSITORY_2; + + /** The pattern of our XML namespace. */ + public static final String NS_SDK_REPOSITORY_PATTERN = + "http://schemas.android.com/sdk/android/repository/[1-9][0-9]*"; //$NON-NLS-1$ + + /** The latest version of the sdk-repository XML Schema, currently 2. + * Valid version numbers are between 1 and this number, included. */ + public static final int XSD_LATEST_VERSION = 2; /** The root sdk-repository element */ public static final String NODE_SDK_REPOSITORY = "sdk-repository"; //$NON-NLS-1$ @@ -108,9 +125,33 @@ public class SdkRepository { /** A license reference. */ public static final String ATTR_REF = "ref"; //$NON-NLS-1$ + /** Type of a sha1 checksum. */ + public static final String SHA1_TYPE = "sha1"; //$NON-NLS-1$ - public static InputStream getXsdStream() { - return SdkRepository.class.getResourceAsStream("sdk-repository.xsd"); //$NON-NLS-1$ + /** Length of a string representing a SHA1 checksum; always 40 characters long. */ + public static final int SHA1_CHECKSUM_LEN = 40; + + + /** + * Returns a stream to the requested repository XML Schema. + * + * @param version 1 for {@link #NS_SDK_REPOSITORY_1}, 2 for {@link #NS_SDK_REPOSITORY_2}. + * You can use {@link #XSD_LATEST_VERSION} to always get the latest version. + * @return An {@link InputStream} object for the local XSD file or + * null if there is no schema for the requested version. + */ + public static InputStream getXsdStream(int version) { + String filename = String.format("sdk-repository-%d.xsd", version); //$NON-NLS-1$ + return SdkRepository.class.getResourceAsStream(filename); + } + + /** + * Returns the URI of the SDK Repository schema for the given version number. + * @param version Between 1 and {@link #XSD_LATEST_VERSION} included. + */ + public static String getSchemaUri(int version) { + return String.format("http://schemas.android.com/sdk/android/repository/%d", //$NON-NLS-1$ + version); // } } diff --git a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/sdk-repository.xsd b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/sdk-repository-1.xsd similarity index 100% rename from tools/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/sdk-repository.xsd rename to tools/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/sdk-repository-1.xsd diff --git a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/sdk-repository-2.xsd b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/sdk-repository-2.xsd new file mode 100755 index 000000000..31b173f44 --- /dev/null +++ b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/sdk-repository-2.xsd @@ -0,0 +1,421 @@ + + + + + + + + + + The repository contains collections of downloadable packages. + + + + + + + + + + + An SDK platform package. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + An SDK add-on package. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + An SDK tool package. + + + + + + + + + + + + + + + + + + + + + + + + + + + An SDK doc package. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + An SDK extra package. This kind of package is for "free" + content and specifies in which fixed root directory it must be + installed. + The paths "add-ons", "platforms", "tools" and "docs" are + reserved and cannot be used. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + An SDK sample package. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + A license definition. Such a license must be used later as a reference + using a uses-license element in one of the package elements. + + + + + + + + + + + + + + + + + + + + + + Describes the license used by a package. The license MUST be defined + using a license node and referenced using the ref attribute of the + license element inside a package. + + + + + + + + + + + + A collection of files that can be downloaded for a given architecture. + The <archives> node is mandatory in the repository packages and the + collection must have at least one <archive> declared. + Each archive is a zip file that will be unzipped in a location that depends + on its package type. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + A SHA1 checksum. + + + + + + + + + A file checksum, currently only SHA1. + + + + + + + + + + diff --git a/tools/sdkmanager/libs/sdklib/tests/com/android/sdklib/internal/repository/RepoSourceTest.java b/tools/sdkmanager/libs/sdklib/tests/com/android/sdklib/internal/repository/RepoSourceTest.java new file mode 100755 index 000000000..87da29415 --- /dev/null +++ b/tools/sdkmanager/libs/sdklib/tests/com/android/sdklib/internal/repository/RepoSourceTest.java @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2009 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.sdklib.internal.repository; + +import com.android.sdklib.repository.SdkRepository; + +import org.w3c.dom.Document; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; + +import junit.framework.TestCase; + +/** + * Tests for {@link RepoSource} + */ +public class RepoSourceTest extends TestCase { + + private static class MockMonitor implements ITaskMonitor { + public void setResult(String resultFormat, Object... args) { + } + + public void setProgressMax(int max) { + } + + public void setDescription(String descriptionFormat, Object... args) { + } + + public boolean isCancelRequested() { + return false; + } + + public void incProgress(int delta) { + } + + public int getProgress() { + return 0; + } + + public boolean displayPrompt(String title, String message) { + return false; + } + + public ITaskMonitor createSubMonitor(int tickCount) { + return null; + } + } + + /** + * An internal helper class to give us visibility to the protected members we want + * to test. + */ + private static class MockRepoSource extends RepoSource { + public MockRepoSource() { + super("fake-url", false /*userSource*/); + } + + public Document _findAlternateToolsXml(InputStream xml) { + return super.findAlternateToolsXml(xml); + } + + public boolean _parsePackages(Document doc, String nsUri, ITaskMonitor monitor) { + return super.parsePackages(doc, nsUri, monitor); + } + } + + private MockRepoSource mSource; + + @Override + protected void setUp() throws Exception { + super.setUp(); + + mSource = new MockRepoSource(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + + mSource = null; + } + + public void testFindAlternateToolsXml_Errors() { + // Support null as input + Document result = mSource._findAlternateToolsXml(null); + assertNull(result); + + // Support an empty input + String str = ""; + ByteArrayInputStream input = new ByteArrayInputStream(str.getBytes()); + result = mSource._findAlternateToolsXml(input); + assertNull(result); + + // Support a random string as input + str = "Some random string, not even HTML nor XML"; + input = new ByteArrayInputStream(str.getBytes()); + result = mSource._findAlternateToolsXml(input); + assertNull(result); + + // Support an HTML input, e.g. a typical 404 document as returned by DL + str = " " + + " " + + "404 Not Found " + " " + " " + " " + + " " + + " " + + " " + + "
" + + "Google   " + + " 
Error
 
" + "

" + "

Not Found

" + + "The requested URL /404 was not found on this server." + " " + "

" + + "

" + + "
\"\"
" + + " "; + input = new ByteArrayInputStream(str.getBytes()); + result = mSource._findAlternateToolsXml(input); + assertNull(result); + + // Support some random XML document, totally unrelated to our sdk-repository schema + str = "" + + "" + + " " + + ""; + input = new ByteArrayInputStream(str.getBytes()); + result = mSource._findAlternateToolsXml(input); + assertNull(result); + } + + public void testFindAlternateToolsXml_1() { + InputStream xmlStream = this.getClass().getResourceAsStream( + "/com/android/sdklib/testdata/repository_sample_1.xml"); + + Document result = mSource._findAlternateToolsXml(xmlStream); + assertNotNull(result); + assertTrue(mSource._parsePackages(result, + SdkRepository.NS_SDK_REPOSITORY, new MockMonitor())); + + // check the packages we found... we expected to find 2 tool packages with 1 archive each. + Package[] pkgs = mSource.getPackages(); + assertEquals(2, pkgs.length); + for (Package p : pkgs) { + assertEquals(ToolPackage.class, p.getClass()); + assertEquals(1, p.getArchives().length); + } + } + +} diff --git a/tools/sdkmanager/libs/sdklib/tests/com/android/sdklib/repository/TestSdkRepository.java b/tools/sdkmanager/libs/sdklib/tests/com/android/sdklib/repository/SdkRepositoryTest.java similarity index 74% rename from tools/sdkmanager/libs/sdklib/tests/com/android/sdklib/repository/TestSdkRepository.java rename to tools/sdkmanager/libs/sdklib/tests/com/android/sdklib/repository/SdkRepositoryTest.java index de50b6ece..27f881ef1 100755 --- a/tools/sdkmanager/libs/sdklib/tests/com/android/sdklib/repository/TestSdkRepository.java +++ b/tools/sdkmanager/libs/sdklib/tests/com/android/sdklib/repository/SdkRepositoryTest.java @@ -38,7 +38,7 @@ import junit.framework.TestCase; * References: * http://www.ibm.com/developerworks/xml/library/x-javaxmlvalidapi.html */ -public class TestSdkRepository extends TestCase { +public class SdkRepositoryTest extends TestCase { @Override protected void setUp() throws Exception { @@ -59,10 +59,12 @@ public class TestSdkRepository extends TestCase { private String mWarnings = ""; private String mErrors = ""; + @SuppressWarnings("unused") public String getErrors() { return mErrors; } + @SuppressWarnings("unused") public String getWarnings() { return mWarnings; } @@ -109,8 +111,8 @@ public class TestSdkRepository extends TestCase { // --- Helpers ------------ /** Helper method that returns a validator for our XSD */ - private Validator getValidator(CaptureErrorHandler handler) throws SAXException { - InputStream xsdStream = SdkRepository.getXsdStream(); + private Validator getValidator(int version, CaptureErrorHandler handler) throws SAXException { + InputStream xsdStream = SdkRepository.getXsdStream(version); SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); Schema schema = factory.newSchema(new StreamSource(xsdStream)); Validator validator = schema.newValidator(); @@ -121,19 +123,6 @@ public class TestSdkRepository extends TestCase { return validator; } - /** Validate a valid sample using an InputStream */ - public void testValidateLocalRepositoryFile() throws Exception { - InputStream xmlStream = - this.getClass().getClassLoader().getResourceAsStream( - "com/android/sdklib/testdata/repository_sample.xml"); - Source source = new StreamSource(xmlStream); - - CaptureErrorHandler handler = new CaptureErrorHandler(); - Validator validator = getValidator(handler); - validator.validate(source); - handler.verify(); - } - /** An helper that validates a string against an expected regexp. */ private void assertRegex(String expectedRegexp, String actualString) { assertNotNull(actualString); @@ -145,6 +134,49 @@ public class TestSdkRepository extends TestCase { // --- Tests ------------ + /** Validate a valid sample using namespace version 1 using an InputStream */ + public void testValidateLocalRepositoryFile1() throws Exception { + InputStream xmlStream = this.getClass().getResourceAsStream( + "/com/android/sdklib/testdata/repository_sample_1.xml"); + Source source = new StreamSource(xmlStream); + + CaptureErrorHandler handler = new CaptureErrorHandler(); + Validator validator = getValidator(1, handler); + validator.validate(source); + handler.verify(); + } + + /** Validate a valid sample using namespace version 2 using an InputStream */ + public void testValidateLocalRepositoryFile2() throws Exception { + InputStream xmlStream = this.getClass().getResourceAsStream( + "/com/android/sdklib/testdata/repository_sample_2.xml"); + Source source = new StreamSource(xmlStream); + + CaptureErrorHandler handler = new CaptureErrorHandler(); + Validator validator = getValidator(2, handler); + validator.validate(source); + handler.verify(); + } + + /** Test that validating a v2 file using the v1 schema fails. */ + public void testValidateFile2UsingNs1() throws Exception { + InputStream xmlStream = this.getClass().getResourceAsStream( + "/com/android/sdklib/testdata/repository_sample_2.xml"); + Source source = new StreamSource(xmlStream); + + Validator validator = getValidator(1, null); // validate v2 against v1... fail! + + try { + validator.validate(source); + } catch (SAXParseException e) { + // We expect to get this specific exception message + assertRegex("cvc-elt.1: Cannot find the declaration of element 'sdk:sdk-repository'.*", e.getMessage()); + return; + } + // We shouldn't get here + fail(); + } + /** A document should at least have a root to be valid */ public void testEmptyXml() throws Exception { String document = ""; @@ -152,7 +184,7 @@ public class TestSdkRepository extends TestCase { Source source = new StreamSource(new StringReader(document)); CaptureErrorHandler handler = new CaptureErrorHandler(); - Validator validator = getValidator(handler); + Validator validator = getValidator(SdkRepository.XSD_LATEST_VERSION, handler); try { validator.validate(source); @@ -166,15 +198,23 @@ public class TestSdkRepository extends TestCase { fail(); } + private static String OPEN_TAG = + ""; + + private static String CLOSE_TAG = ""; + /** A document with a root element containing no platform, addon, etc., is valid. */ public void testEmptyRootXml() throws Exception { String document = "" + - ""; + OPEN_TAG + + CLOSE_TAG; Source source = new StreamSource(new StringReader(document)); CaptureErrorHandler handler = new CaptureErrorHandler(); - Validator validator = getValidator(handler); + Validator validator = getValidator(SdkRepository.XSD_LATEST_VERSION, handler); validator.validate(source); handler.verify(); } @@ -182,14 +222,14 @@ public class TestSdkRepository extends TestCase { /** A document with an unknown element. */ public void testUnknownContentXml() throws Exception { String document = "" + - "" + + OPEN_TAG + "" + - ""; + CLOSE_TAG; Source source = new StreamSource(new StringReader(document)); // don't capture the validator errors, we want it to fail and catch the exception - Validator validator = getValidator(null); + Validator validator = getValidator(SdkRepository.XSD_LATEST_VERSION, null); try { validator.validate(source); } catch (SAXParseException e) { @@ -204,14 +244,14 @@ public class TestSdkRepository extends TestCase { /** A document with an incomplete element. */ public void testIncompleteContentXml() throws Exception { String document = "" + - "" + + OPEN_TAG + " 1 " + - ""; + CLOSE_TAG; Source source = new StreamSource(new StringReader(document)); // don't capture the validator errors, we want it to fail and catch the exception - Validator validator = getValidator(null); + Validator validator = getValidator(SdkRepository.XSD_LATEST_VERSION, null); try { validator.validate(source); } catch (SAXParseException e) { @@ -226,14 +266,14 @@ public class TestSdkRepository extends TestCase { /** A document with a wrong type element. */ public void testWrongTypeContentXml() throws Exception { String document = "" + - "" + + OPEN_TAG + " NotAnInteger " + - ""; + CLOSE_TAG; Source source = new StreamSource(new StringReader(document)); // don't capture the validator errors, we want it to fail and catch the exception - Validator validator = getValidator(null); + Validator validator = getValidator(SdkRepository.XSD_LATEST_VERSION, null); try { validator.validate(source); } catch (SAXParseException e) { @@ -250,17 +290,17 @@ public class TestSdkRepository extends TestCase { public void testLicenseIdNotFound() throws Exception { // we define a license named "lic1" and then reference "lic2" instead String document = "" + - "" + + OPEN_TAG + " some license " + " 1 " + " 1 2822ae37115ebf13412bbef91339ee0d9454525e " + "url " + - ""; + CLOSE_TAG; Source source = new StreamSource(new StringReader(document)); // don't capture the validator errors, we want it to fail and catch the exception - Validator validator = getValidator(null); + Validator validator = getValidator(SdkRepository.XSD_LATEST_VERSION, null); try { validator.validate(source); } catch (SAXParseException e) { @@ -277,16 +317,16 @@ public class TestSdkRepository extends TestCase { public void testExtraPathWithSlash() throws Exception { // we define a license named "lic1" and then reference "lic2" instead String document = "" + - "" + + OPEN_TAG + " 1 path/cannot\\contain\\segments " + " 1 2822ae37115ebf13412bbef91339ee0d9454525e " + "url " + - ""; + CLOSE_TAG; Source source = new StreamSource(new StringReader(document)); // don't capture the validator errors, we want it to fail and catch the exception - Validator validator = getValidator(null); + Validator validator = getValidator(SdkRepository.XSD_LATEST_VERSION, null); try { validator.validate(source); } catch (SAXParseException e) { diff --git a/tools/sdkmanager/libs/sdklib/tests/com/android/sdklib/testdata/repository_sample.xml b/tools/sdkmanager/libs/sdklib/tests/com/android/sdklib/testdata/repository_sample_1.xml similarity index 100% rename from tools/sdkmanager/libs/sdklib/tests/com/android/sdklib/testdata/repository_sample.xml rename to tools/sdkmanager/libs/sdklib/tests/com/android/sdklib/testdata/repository_sample_1.xml diff --git a/tools/sdkmanager/libs/sdklib/tests/com/android/sdklib/testdata/repository_sample_2.xml b/tools/sdkmanager/libs/sdklib/tests/com/android/sdklib/testdata/repository_sample_2.xml new file mode 100755 index 000000000..d5276e56b --- /dev/null +++ b/tools/sdkmanager/libs/sdklib/tests/com/android/sdklib/testdata/repository_sample_2.xml @@ -0,0 +1,288 @@ + + + + + + + + This is the license + for this platform. + + + + Licenses are only of type 'text' right now, so this is implied. + + + + + + 1.0 + 1 + 3 + + Some optional description + http://www.example.com/platform1.html + This is an optional release note + for this package. It's a free multi-line text. + + http://some/url/for/the/release/note.html + 2 + + + + 65536 + 2822ae37115ebf13412bbef91339ee0d9454525e + http://www.example.com/files/plat1.zip + + + + + + 1 + 1 + + Some optional description + http://www.example.com/docs.html + + + 65536 + 2822ae37115ebf13412bbef91339ee0d9454525e + http://www.example.com/docs/docs1.zip + + + + + + My First add-on + 1 + John Doe + 1 + + Some optional description + http://www.example.com/myfirstaddon + + + 65536 + 2822ae37115ebf13412bbef91339ee0d9454525e + http://www.example.com/add-ons/first.zip + + + + + + android.blah.somelib + The description for this library. + + + + com.android.mymaps + + + + + + 1.1 + 2 + 12 + + + + + + 65536 + 2822ae37115ebf13412bbef91339ee0d9454525e + distrib/platform-2-12-win.zip + + + 65536 + 2822ae37115ebf13412bbef91339ee0d9454525e + distrib/platform-2-12-mac.zip + + + 65536 + 2822ae37115ebf13412bbef91339ee0d9454525e + distrib/platform-2-12-mac.zip + + + 65536 + 2822ae37115ebf13412bbef91339ee0d9454525e + distrib/platform-2-12-linux.tar.bz2 + + + 65536 + 2822ae37115ebf13412bbef91339ee0d9454525e + distrib/platform-2-12-linux.tar.bz2 + + + + + + My Second add-on + 2 + John Deer + 42 + + + 65536 + 2822ae37115ebf13412bbef91339ee0d9454525e + distrib/second-42-win.zip + + + 65536 + 2822ae37115ebf13412bbef91339ee0d9454525e + distrib/second-42-linux.tar.bz2 + + + + + android.blah.somelib + The description for this library. + + + com.android.mymaps + + + + + + + Pastry + 5 + Pastry + 3 + + Preview version for Pastry + http://www.example.com/platform1.html + + + + 65536 + 2822ae37115ebf13412bbef91339ee0d9454525e + http://www.example.com/files/plat1.zip + + + + + + 1 + Some optional description + http://www.example.com/tools.html + + + + 65536 + 2822ae37115ebf13412bbef91339ee0d9454525e + http://www.example.com/files/tools1.zip + + + + + + 2 + 42 + + + + 65536 + 2822ae37115ebf13412bbef91339ee0d9454525e + distrib/docs/2.zip + + + 65536 + 2822ae37115ebf13412bbef91339ee0d9454525e + distrib/docs2-linux.tar.bz2 + + + 65536 + 2822ae37115ebf13412bbef91339ee0d9454525e + distrib/docs2-mac.tar.bz2 + + + + + + 42 + + + + 65536 + 2822ae37115ebf13412bbef91339ee0d9454525e + distrib/tools/2.zip + + + 65536 + 2822ae37115ebf13412bbef91339ee0d9454525e + distrib/tools2-linux.tar.bz2 + + + 65536 + 2822ae37115ebf13412bbef91339ee0d9454525e + distrib/tools2-mac.tar.bz2 + + + + + + + This add-on has no libraries + 4 + Joe Bar + 3 + + + 65536 + 2822ae37115ebf13412bbef91339ee0d9454525e + distrib/imnotanarchiveimadoctorjim.zip + + + + + + + + usb_driver + + 43 + + + 65536 + 2822ae37115ebf13412bbef91339ee0d9454525e + distrib/extraduff.zip + + + An Extra package for the USB driver, it will install in $SDK/usb_driver + http://www.example.com/extra.html + 3 + + + + + 1234 + 314153 + + + 123456 + 2822ae37115ebf13412bbef91339ee0d94545228 + archives/samples/dream-1234.zip + + + + +