From 580e982719c8140464e654f9e9de5c39b7d206b2 Mon Sep 17 00:00:00 2001 From: Raphael Date: Sat, 14 Nov 2009 21:30:27 -0800 Subject: SDK Manager: phase 1 of support for future schemas This is phase 1 of the support for future schemas: - introduces a new v2 of the XSD - handles an unknown XSD and extract its tools elements after some hand checks. TODO in phase 2: - we still need to iron out the details of the sample node. - there's no support for samples yet... this commit is not about switching the sample code support to the new structure. - there's no indication to the user that we are handling a new schema... there should be some visual clue on why only the tools are shown. SDK BUG 2252825 Change-Id: If1bda26414cfe15275063d3187a98be6301d2992 --- .../sdklib/internal/repository/AddonPackage.java | 3 +- .../sdklib/internal/repository/Archive.java | 55 ++- .../sdklib/internal/repository/Package.java | 3 +- .../sdklib/internal/repository/RepoSource.java | 516 ++++++++++++++++----- .../sdklib/internal/repository/XmlParserUtils.java | 5 +- .../android/sdklib/repository/SdkRepository.java | 51 +- .../android/sdklib/repository/sdk-repository-1.xsd | 379 +++++++++++++++ .../android/sdklib/repository/sdk-repository-2.xsd | 421 +++++++++++++++++ .../android/sdklib/repository/sdk-repository.xsd | 379 --------------- .../sdklib/internal/repository/RepoSourceTest.java | 163 +++++++ .../sdklib/repository/SdkRepositoryTest.java | 341 ++++++++++++++ .../sdklib/repository/TestSdkRepository.java | 301 ------------ .../android/sdklib/testdata/repository_sample.xml | 275 ----------- .../sdklib/testdata/repository_sample_1.xml | 275 +++++++++++ .../sdklib/testdata/repository_sample_2.xml | 288 ++++++++++++ 15 files changed, 2361 insertions(+), 1094 deletions(-) create mode 100755 tools/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/sdk-repository-1.xsd create mode 100755 tools/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/sdk-repository-2.xsd delete mode 100755 tools/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/sdk-repository.xsd create mode 100755 tools/sdkmanager/libs/sdklib/tests/com/android/sdklib/internal/repository/RepoSourceTest.java create mode 100755 tools/sdkmanager/libs/sdklib/tests/com/android/sdklib/repository/SdkRepositoryTest.java delete mode 100755 tools/sdkmanager/libs/sdklib/tests/com/android/sdklib/repository/TestSdkRepository.java delete mode 100755 tools/sdkmanager/libs/sdklib/tests/com/android/sdklib/testdata/repository_sample.xml create mode 100755 tools/sdkmanager/libs/sdklib/tests/com/android/sdklib/testdata/repository_sample_1.xml create mode 100755 tools/sdkmanager/libs/sdklib/tests/com/android/sdklib/testdata/repository_sample_2.xml (limited to 'tools') 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) { + 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); - 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; + } catch (Exception e) { + lastError = "XML verification failed for %1$s.\nError: %2$s"; + extraError = e.getMessage(); + if (extraError == null) { + extraError = e.getClass().getName(); + } } + } + + if (lastError != null) { + monitor.setResult(lastError, url, extraError); + } + return null; + } + /** + * 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. + */ + 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(); - validator.validate(new StreamSource(xml)); - return true; + } 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) { - String s = e.getMessage(); - if (s == null) { - s = e.getClass().getName(); + // 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; + } + } + } } - monitor.setResult("XML verification failed for %1$s.\nError: %2$s", - url, - s); } - return false; + // 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 that returns a validator for our XSD + * Helper method used by {@link #findAlternateToolsXml(InputStream)} to find a given + * element child in a root XML node. */ - private Validator getValidator() throws SAXException { - InputStream xsdStream = SdkRepository.getXsdStream(); + 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) { - - try { - Document doc = getDocument(xml); - - Node root = getFirstChild(doc, SdkRepository.NODE_SDK_REPOSITORY); - if (root != null) { - - 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()); - } + protected boolean parsePackages(Document doc, String nsUri, ITaskMonitor monitor) { + // protected for unit-test acces + + assert doc != null; + + Node root = getFirstChild(doc, nsUri, SdkRepository.NODE_SDK_REPOSITORY); + if (root != null) { + + 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 && + 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; + // 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; - 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); - } + 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 + 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"); + mPackages = packages.toArray(new Package[packages.size()]); - } catch (SAXException e) { - monitor.setResult("Failed to parse XML document"); - - } catch (IOException e) { - monitor.setResult("Failed to read XML document"); + 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; + } catch (ParserConfigurationException e) { + monitor.setResult("Failed to create XML document builder"); - return doc; + } 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$ + + /** 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); + } - public static InputStream getXsdStream() { - return SdkRepository.class.getResourceAsStream("sdk-repository.xsd"); //$NON-NLS-1$ + /** + * 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-1.xsd b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/sdk-repository-1.xsd new file mode 100755 index 000000000..f00e337c6 --- /dev/null +++ b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/sdk-repository-1.xsd @@ -0,0 +1,379 @@ + + + + + + + + + + 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. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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/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/src/com/android/sdklib/repository/sdk-repository.xsd b/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/sdk-repository.xsd deleted file mode 100755 index f00e337c6..000000000 --- a/tools/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/sdk-repository.xsd +++ /dev/null @@ -1,379 +0,0 @@ - - - - - - - - - - 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. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 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/SdkRepositoryTest.java b/tools/sdkmanager/libs/sdklib/tests/com/android/sdklib/repository/SdkRepositoryTest.java new file mode 100755 index 000000000..27f881ef1 --- /dev/null +++ b/tools/sdkmanager/libs/sdklib/tests/com/android/sdklib/repository/SdkRepositoryTest.java @@ -0,0 +1,341 @@ +/* + * 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.repository; + +import org.xml.sax.ErrorHandler; +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; + +import java.io.InputStream; +import java.io.StringReader; + +import javax.xml.XMLConstants; +import javax.xml.transform.Source; +import javax.xml.transform.stream.StreamSource; +import javax.xml.validation.Schema; +import javax.xml.validation.SchemaFactory; +import javax.xml.validation.Validator; + +import junit.framework.TestCase; + +/** + * Tests local validation of an SDK Repository sample XMLs using an XML Schema validator. + * + * References: + * http://www.ibm.com/developerworks/xml/library/x-javaxmlvalidapi.html + */ +public class SdkRepositoryTest extends TestCase { + + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + } + + /** + * A SAX error handler that captures the errors and warnings. + * This allows us to capture *all* errors and just not get an exception on the first one. + */ + private static class CaptureErrorHandler implements ErrorHandler { + + private String mWarnings = ""; + private String mErrors = ""; + + @SuppressWarnings("unused") + public String getErrors() { + return mErrors; + } + + @SuppressWarnings("unused") + public String getWarnings() { + return mWarnings; + } + + /** + * Verifies if the handler captures some errors or warnings. + * Prints them on stderr. + * Also fails the unit test if any error was generated. + */ + public void verify() { + if (mWarnings.length() > 0) { + System.err.println(mWarnings); + } + + if (mErrors.length() > 0) { + System.err.println(mErrors); + fail(mErrors); + } + } + + /** + * @throws SAXException + */ + public void error(SAXParseException ex) throws SAXException { + mErrors += "Error: " + ex.getMessage() + "\n"; + } + + /** + * @throws SAXException + */ + public void fatalError(SAXParseException ex) throws SAXException { + mErrors += "Fatal Error: " + ex.getMessage() + "\n"; + } + + /** + * @throws SAXException + */ + public void warning(SAXParseException ex) throws SAXException { + mWarnings += "Warning: " + ex.getMessage() + "\n"; + } + + } + + // --- Helpers ------------ + + /** Helper method that returns a validator for our XSD */ + 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(); + if (handler != null) { + validator.setErrorHandler(handler); + } + + return validator; + } + + /** An helper that validates a string against an expected regexp. */ + private void assertRegex(String expectedRegexp, String actualString) { + assertNotNull(actualString); + assertTrue( + String.format("Regexp Assertion Failed:\nExpected: %s\nActual: %s\n", + expectedRegexp, actualString), + actualString.matches(expectedRegexp)); + } + + // --- 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 = ""; + + Source source = new StreamSource(new StringReader(document)); + + CaptureErrorHandler handler = new CaptureErrorHandler(); + Validator validator = getValidator(SdkRepository.XSD_LATEST_VERSION, handler); + + try { + validator.validate(source); + } catch (SAXParseException e) { + // We expect to get this specific exception message + assertRegex("Premature end of file.*", e.getMessage()); + return; + } + // We shouldn't get here + handler.verify(); + 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(SdkRepository.XSD_LATEST_VERSION, handler); + validator.validate(source); + handler.verify(); + } + + /** 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(SdkRepository.XSD_LATEST_VERSION, null); + try { + validator.validate(source); + } catch (SAXParseException e) { + // We expect a parse expression referring to this grammar rule + assertRegex("cvc-complex-type.2.4.a: Invalid content was found.*", e.getMessage()); + return; + } + // If we get here, the validator has not failed as we expected it to. + fail(); + } + + /** 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(SdkRepository.XSD_LATEST_VERSION, null); + try { + validator.validate(source); + } catch (SAXParseException e) { + // We expect a parse error referring to this grammar rule + assertRegex("cvc-complex-type.2.4.a: Invalid content was found.*", e.getMessage()); + return; + } + // If we get here, the validator has not failed as we expected it to. + fail(); + } + + /** 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(SdkRepository.XSD_LATEST_VERSION, null); + try { + validator.validate(source); + } catch (SAXParseException e) { + // We expect a parse error referring to this grammar rule + assertRegex("cvc-datatype-valid.1.2.1: 'NotAnInteger' is not a valid value.*", + e.getMessage()); + return; + } + // If we get here, the validator has not failed as we expected it to. + fail(); + } + + /** A document an unknown license id. */ + 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(SdkRepository.XSD_LATEST_VERSION, null); + try { + validator.validate(source); + } catch (SAXParseException e) { + // We expect a parse error referring to this grammar rule + assertRegex("cvc-id.1: There is no ID/IDREF binding for IDREF 'lic2'.*", + e.getMessage()); + return; + } + // If we get here, the validator has not failed as we expected it to. + fail(); + } + + /** A document a slash in an extra path. */ + 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(SdkRepository.XSD_LATEST_VERSION, null); + try { + validator.validate(source); + } catch (SAXParseException e) { + // We expect a parse error referring to this grammar rule + assertRegex("cvc-pattern-valid: Value 'path/cannot\\\\contain\\\\segments' is not facet-valid with respect to pattern.*", + e.getMessage()); + return; + } + // If we get here, the validator has not failed as we expected it to. + fail(); + } +} diff --git a/tools/sdkmanager/libs/sdklib/tests/com/android/sdklib/repository/TestSdkRepository.java b/tools/sdkmanager/libs/sdklib/tests/com/android/sdklib/repository/TestSdkRepository.java deleted file mode 100755 index 5d639ecca..000000000 --- a/tools/sdkmanager/libs/sdklib/tests/com/android/sdklib/repository/TestSdkRepository.java +++ /dev/null @@ -1,301 +0,0 @@ -/* - * 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.repository; - -import org.xml.sax.ErrorHandler; -import org.xml.sax.SAXException; -import org.xml.sax.SAXParseException; - -import java.io.InputStream; -import java.io.StringReader; - -import javax.xml.XMLConstants; -import javax.xml.transform.Source; -import javax.xml.transform.stream.StreamSource; -import javax.xml.validation.Schema; -import javax.xml.validation.SchemaFactory; -import javax.xml.validation.Validator; - -import junit.framework.TestCase; - -/** - * Tests local validation of an SDK Repository sample XMLs using an XML Schema validator. - * - * References: - * http://www.ibm.com/developerworks/xml/library/x-javaxmlvalidapi.html - */ -public class TestSdkRepository extends TestCase { - - @Override - protected void setUp() throws Exception { - super.setUp(); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - - /** - * A SAX error handler that captures the errors and warnings. - * This allows us to capture *all* errors and just not get an exception on the first one. - */ - private static class CaptureErrorHandler implements ErrorHandler { - - private String mWarnings = ""; - private String mErrors = ""; - - public String getErrors() { - return mErrors; - } - - public String getWarnings() { - return mWarnings; - } - - /** - * Verifies if the handler captures some errors or warnings. - * Prints them on stderr. - * Also fails the unit test if any error was generated. - */ - public void verify() { - if (mWarnings.length() > 0) { - System.err.println(mWarnings); - } - - if (mErrors.length() > 0) { - System.err.println(mErrors); - fail(mErrors); - } - } - - /** - * @throws SAXException - */ - public void error(SAXParseException ex) throws SAXException { - mErrors += "Error: " + ex.getMessage() + "\n"; - } - - /** - * @throws SAXException - */ - public void fatalError(SAXParseException ex) throws SAXException { - mErrors += "Fatal Error: " + ex.getMessage() + "\n"; - } - - /** - * @throws SAXException - */ - public void warning(SAXParseException ex) throws SAXException { - mWarnings += "Warning: " + ex.getMessage() + "\n"; - } - - } - - // --- Helpers ------------ - - /** Helper method that returns a validator for our XSD */ - private Validator getValidator(CaptureErrorHandler handler) throws SAXException { - InputStream xsdStream = SdkRepository.getXsdStream(); - SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); - Schema schema = factory.newSchema(new StreamSource(xsdStream)); - Validator validator = schema.newValidator(); - if (handler != null) { - validator.setErrorHandler(handler); - } - - return validator; - } - - /** Validate a valid sample using an InputStream */ - public void testValidateLocalRepositoryFile() throws Exception { - InputStream xmlStream = - TestSdkRepository.class.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); - assertTrue( - String.format("Regexp Assertion Failed:\nExpected: %s\nActual: %s\n", - expectedRegexp, actualString), - actualString.matches(expectedRegexp)); - } - - // --- Tests ------------ - - /** A document should at least have a root to be valid */ - public void testEmptyXml() throws Exception { - String document = ""; - - Source source = new StreamSource(new StringReader(document)); - - CaptureErrorHandler handler = new CaptureErrorHandler(); - Validator validator = getValidator(handler); - - try { - validator.validate(source); - } catch (SAXParseException e) { - // We expect to get this specific exception message - assertRegex("Premature end of file.*", e.getMessage()); - return; - } - // We shouldn't get here - handler.verify(); - fail(); - } - - /** A document with a root element containing no platform, addon, etc., is valid. */ - public void testEmptyRootXml() throws Exception { - String document = "" + - ""; - - Source source = new StreamSource(new StringReader(document)); - - CaptureErrorHandler handler = new CaptureErrorHandler(); - Validator validator = getValidator(handler); - validator.validate(source); - handler.verify(); - } - - /** A document with an unknown element. */ - public void testUnknownContentXml() throws Exception { - String document = "" + - "" + - "" + - ""; - - 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); - try { - validator.validate(source); - } catch (SAXParseException e) { - // We expect a parse expression referring to this grammar rule - assertRegex("cvc-complex-type.2.4.a: Invalid content was found.*", e.getMessage()); - return; - } - // If we get here, the validator has not failed as we expected it to. - fail(); - } - - /** A document with an incomplete element. */ - public void testIncompleteContentXml() throws Exception { - String document = "" + - "" + - " 1 " + - ""; - - 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); - try { - validator.validate(source); - } catch (SAXParseException e) { - // We expect a parse error referring to this grammar rule - assertRegex("cvc-complex-type.2.4.a: Invalid content was found.*", e.getMessage()); - return; - } - // If we get here, the validator has not failed as we expected it to. - fail(); - } - - /** A document with a wrong type element. */ - public void testWrongTypeContentXml() throws Exception { - String document = "" + - "" + - " NotAnInteger " + - ""; - - 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); - try { - validator.validate(source); - } catch (SAXParseException e) { - // We expect a parse error referring to this grammar rule - assertRegex("cvc-datatype-valid.1.2.1: 'NotAnInteger' is not a valid value.*", - e.getMessage()); - return; - } - // If we get here, the validator has not failed as we expected it to. - fail(); - } - - /** A document an unknown license id. */ - public void testLicenseIdNotFound() throws Exception { - // we define a license named "lic1" and then reference "lic2" instead - String document = "" + - "" + - " some license " + - " 1 " + - " 1 2822ae37115ebf13412bbef91339ee0d9454525e " + - "url " + - ""; - - 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); - try { - validator.validate(source); - } catch (SAXParseException e) { - // We expect a parse error referring to this grammar rule - assertRegex("cvc-id.1: There is no ID/IDREF binding for IDREF 'lic2'.*", - e.getMessage()); - return; - } - // If we get here, the validator has not failed as we expected it to. - fail(); - } - - /** A document a slash in an extra path. */ - public void testExtraPathWithSlash() throws Exception { - // we define a license named "lic1" and then reference "lic2" instead - String document = "" + - "" + - " 1 path/cannot\\contain\\segments " + - " 1 2822ae37115ebf13412bbef91339ee0d9454525e " + - "url " + - ""; - - 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); - try { - validator.validate(source); - } catch (SAXParseException e) { - // We expect a parse error referring to this grammar rule - assertRegex("cvc-pattern-valid: Value 'path/cannot\\\\contain\\\\segments' is not facet-valid with respect to pattern.*", - e.getMessage()); - return; - } - // If we get here, the validator has not failed as we expected it to. - fail(); - } -} 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.xml deleted file mode 100755 index c90e18d76..000000000 --- a/tools/sdkmanager/libs/sdklib/tests/com/android/sdklib/testdata/repository_sample.xml +++ /dev/null @@ -1,275 +0,0 @@ - - - - - - - - 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 - - - diff --git a/tools/sdkmanager/libs/sdklib/tests/com/android/sdklib/testdata/repository_sample_1.xml b/tools/sdkmanager/libs/sdklib/tests/com/android/sdklib/testdata/repository_sample_1.xml new file mode 100755 index 000000000..c90e18d76 --- /dev/null +++ b/tools/sdkmanager/libs/sdklib/tests/com/android/sdklib/testdata/repository_sample_1.xml @@ -0,0 +1,275 @@ + + + + + + + + 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 + + + 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 + + + + + -- cgit v1.2.3