diff options
| author | 2016-08-24 11:23:13 -0700 | |
|---|---|---|
| committer | 2016-08-24 11:23:13 -0700 | |
| commit | e71ecb2c4df15f727f51a0e1b65459f071853e35 (patch) | |
| tree | 0b87e8d0c0e2fe4dcd1d186fe1546b3b4489b002 /tools/aapt/ResourceTable.cpp | |
| parent | 5d4732555dd0f1611de037dee59e0006da23be46 (diff) | |
| parent | 29835cc56eb26089cd8d2c21c7507c9d0588ceb0 (diff) | |
Merge remote-tracking branch 'goog/stage-aosp-master' into HEAD
Change-Id: I1c7301e4e6d7e5fed1fd57d2fb9cb65baf819de0
Diffstat (limited to 'tools/aapt/ResourceTable.cpp')
| -rw-r--r-- | tools/aapt/ResourceTable.cpp | 333 |
1 files changed, 304 insertions, 29 deletions
diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp index 66018724ee2e..8b07db6d9fee 100644 --- a/tools/aapt/ResourceTable.cpp +++ b/tools/aapt/ResourceTable.cpp @@ -88,8 +88,11 @@ status_t compileXmlFile(const Bundle* bundle, root->setUTF8(true); } - bool hasErrors = false; + if (table->processBundleFormat(bundle, resourceName, target, root) != NO_ERROR) { + return UNKNOWN_ERROR; + } + bool hasErrors = false; if ((options&XML_COMPILE_ASSIGN_ATTRIBUTE_IDS) != 0) { status_t err = root->assignResourceIds(assets, table); if (err != NO_ERROR) { @@ -97,9 +100,11 @@ status_t compileXmlFile(const Bundle* bundle, } } - status_t err = root->parseValues(assets, table); - if (err != NO_ERROR) { - hasErrors = true; + if ((options&XML_COMPILE_PARSE_VALUES) != 0) { + status_t err = root->parseValues(assets, table); + if (err != NO_ERROR) { + hasErrors = true; + } } if (hasErrors) { @@ -114,7 +119,7 @@ status_t compileXmlFile(const Bundle* bundle, printf("Input XML Resource:\n"); root->print(); } - err = root->flatten(target, + status_t err = root->flatten(target, (options&XML_COMPILE_STRIP_COMMENTS) != 0, (options&XML_COMPILE_STRIP_RAW_VALUES) != 0); if (err != NO_ERROR) { @@ -303,29 +308,11 @@ struct PendingAttribute } added = true; - String16 attr16("attr"); - - if (outTable->hasBagOrEntry(myPackage, attr16, ident)) { - sourcePos.error("Attribute \"%s\" has already been defined\n", - String8(ident).string()); + if (!outTable->makeAttribute(myPackage, ident, sourcePos, type, comment, appendComment)) { hasErrors = true; return UNKNOWN_ERROR; } - - char numberStr[16]; - sprintf(numberStr, "%d", type); - status_t err = outTable->addBag(sourcePos, myPackage, - attr16, ident, String16(""), - String16("^type"), - String16(numberStr), NULL, NULL); - if (err != NO_ERROR) { - hasErrors = true; - return err; - } - outTable->appendComment(myPackage, attr16, ident, comment, appendComment); - //printf("Attribute %s comment: %s\n", String8(ident).string(), - // String8(comment).string()); - return err; + return NO_ERROR; } }; @@ -1136,7 +1123,15 @@ status_t compileResourceFile(Bundle* bundle, } pkg = String16(block.getAttributeStringValue(pkgIdx, &len)); if (!localHasErrors) { - assets->setSymbolsPrivatePackage(String8(pkg)); + SourcePos(in->getPrintableSource(), block.getLineNumber()).warning( + "<private-symbols> is deprecated. Use the command line flag " + "--private-symbols instead.\n"); + if (assets->havePrivateSymbols()) { + SourcePos(in->getPrintableSource(), block.getLineNumber()).warning( + "private symbol package already specified. Ignoring...\n"); + } else { + assets->setSymbolsPrivatePackage(String8(pkg)); + } } while ((code=block.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) { @@ -2102,6 +2097,61 @@ bool ResourceTable::appendTypeComment(const String16& package, return false; } +bool ResourceTable::makeAttribute(const String16& package, + const String16& name, + const SourcePos& source, + int32_t format, + const String16& comment, + bool shouldAppendComment) { + const String16 attr16("attr"); + + // First look for this in the included resources... + uint32_t rid = mAssets->getIncludedResources() + .identifierForName(name.string(), name.size(), + attr16.string(), attr16.size(), + package.string(), package.size()); + if (rid != 0) { + source.error("Attribute \"%s\" has already been defined", String8(name).string()); + return false; + } + + sp<ResourceTable::Entry> entry = getEntry(package, attr16, name, source, false); + if (entry == NULL) { + source.error("Failed to create entry attr/%s", String8(name).string()); + return false; + } + + if (entry->makeItABag(source) != NO_ERROR) { + return false; + } + + const String16 formatKey16("^type"); + const String16 formatValue16(String8::format("%d", format)); + + ssize_t idx = entry->getBag().indexOfKey(formatKey16); + if (idx >= 0) { + // We have already set a format for this attribute, check if they are different. + // We allow duplicate attribute definitions so long as they are identical. + // This is to ensure inter-operation with libraries that define the same generic attribute. + const Item& formatItem = entry->getBag().valueAt(idx); + if ((format & (ResTable_map::TYPE_ENUM | ResTable_map::TYPE_FLAGS)) || + formatItem.value != formatValue16) { + source.error("Attribute \"%s\" already defined with incompatible format.\n" + "%s:%d: Original attribute defined here.", + String8(name).string(), formatItem.sourcePos.file.string(), + formatItem.sourcePos.line); + return false; + } + } else { + entry->addToBag(source, formatKey16, formatValue16); + // Increment the number of resources we have. This is used to determine if we should + // even generate a resource table. + mNumLocal++; + } + appendComment(package, attr16, name, comment, shouldAppendComment); + return true; +} + void ResourceTable::canAddEntry(const SourcePos& pos, const String16& package, const String16& type, const String16& name) { @@ -4755,9 +4805,9 @@ status_t ResourceTable::modifyForCompat(const Bundle* bundle, newConfig.sdkVersion = sdkVersionToGenerate; sp<AaptFile> newFile = new AaptFile(target->getSourceFile(), AaptGroupEntry(newConfig), target->getResourceType()); - String8 resPath = String8::format("res/%s/%s", + String8 resPath = String8::format("res/%s/%s.xml", newFile->getGroupEntry().toDirName(target->getResourceType()).string(), - target->getSourceFile().getPathLeaf().string()); + String8(resourceName).string()); resPath.convertToResPath(); // Add a resource table entry. @@ -4784,9 +4834,11 @@ status_t ResourceTable::modifyForCompat(const Bundle* bundle, item.resourceName = resourceName; item.resPath = resPath; item.file = newFile; + item.xmlRoot = newRoot; + item.needsCompiling = false; // This step occurs after we parse/assign, so we don't need + // to do it again. mWorkQueue.push(item); } - return NO_ERROR; } @@ -4825,3 +4877,226 @@ void ResourceTable::getDensityVaryingResources( } } } + +static String16 buildNamespace(const String16& package) { + return String16("http://schemas.android.com/apk/res/") + package; +} + +static sp<XMLNode> findOnlyChildElement(const sp<XMLNode>& parent) { + const Vector<sp<XMLNode> >& children = parent->getChildren(); + sp<XMLNode> onlyChild; + for (size_t i = 0; i < children.size(); i++) { + if (children[i]->getType() != XMLNode::TYPE_CDATA) { + if (onlyChild != NULL) { + return NULL; + } + onlyChild = children[i]; + } + } + return onlyChild; +} + +/** + * Detects use of the `bundle' format and extracts nested resources into their own top level + * resources. The bundle format looks like this: + * + * <!-- res/drawable/bundle.xml --> + * <animated-vector xmlns:aapt="http://schemas.android.com/aapt"> + * <aapt:attr name="android:drawable"> + * <vector android:width="60dp" + * android:height="60dp"> + * <path android:name="v" + * android:fillColor="#000000" + * android:pathData="M300,70 l 0,-70 70,..." /> + * </vector> + * </aapt:attr> + * </animated-vector> + * + * When AAPT sees the <aapt:attr> tag, it will extract its single element and its children + * into a new high-level resource, assigning it a name and ID. Then value of the `name` + * attribute must be a resource attribute. That resource attribute is inserted into the parent + * with the reference to the extracted resource as the value. + * + * <!-- res/drawable/bundle.xml --> + * <animated-vector android:drawable="@drawable/bundle_1.xml"> + * </animated-vector> + * + * <!-- res/drawable/bundle_1.xml --> + * <vector android:width="60dp" + * android:height="60dp"> + * <path android:name="v" + * android:fillColor="#000000" + * android:pathData="M300,70 l 0,-70 70,..." /> + * </vector> + */ +status_t ResourceTable::processBundleFormat(const Bundle* bundle, + const String16& resourceName, + const sp<AaptFile>& target, + const sp<XMLNode>& root) { + Vector<sp<XMLNode> > namespaces; + if (root->getType() == XMLNode::TYPE_NAMESPACE) { + namespaces.push(root); + } + return processBundleFormatImpl(bundle, resourceName, target, root, &namespaces); +} + +status_t ResourceTable::processBundleFormatImpl(const Bundle* bundle, + const String16& resourceName, + const sp<AaptFile>& target, + const sp<XMLNode>& parent, + Vector<sp<XMLNode> >* namespaces) { + const String16 kAaptNamespaceUri16("http://schemas.android.com/aapt"); + const String16 kName16("name"); + const String16 kAttr16("attr"); + const String16 kAssetPackage16(mAssets->getPackage()); + + Vector<sp<XMLNode> >& children = parent->getChildren(); + for (size_t i = 0; i < children.size(); i++) { + const sp<XMLNode>& child = children[i]; + + if (child->getType() == XMLNode::TYPE_CDATA) { + continue; + } else if (child->getType() == XMLNode::TYPE_NAMESPACE) { + namespaces->push(child); + } + + if (child->getElementNamespace() != kAaptNamespaceUri16 || + child->getElementName() != kAttr16) { + status_t result = processBundleFormatImpl(bundle, resourceName, target, child, + namespaces); + if (result != NO_ERROR) { + return result; + } + + if (child->getType() == XMLNode::TYPE_NAMESPACE) { + namespaces->pop(); + } + continue; + } + + // This is the <aapt:attr> tag. Look for the 'name' attribute. + SourcePos source(child->getFilename(), child->getStartLineNumber()); + + sp<XMLNode> nestedRoot = findOnlyChildElement(child); + if (nestedRoot == NULL) { + source.error("<%s:%s> must have exactly one child element", + String8(child->getElementNamespace()).string(), + String8(child->getElementName()).string()); + return UNKNOWN_ERROR; + } + + // Find the special attribute 'parent-attr'. This attribute's value contains + // the resource attribute for which this element should be assigned in the parent. + const XMLNode::attribute_entry* attr = child->getAttribute(String16(), kName16); + if (attr == NULL) { + source.error("inline resource definition must specify an attribute via 'name'"); + return UNKNOWN_ERROR; + } + + // Parse the attribute name. + const char* errorMsg = NULL; + String16 attrPackage, attrType, attrName; + bool result = ResTable::expandResourceRef(attr->string.string(), + attr->string.size(), + &attrPackage, &attrType, &attrName, + &kAttr16, &kAssetPackage16, + &errorMsg, NULL); + if (!result) { + source.error("invalid attribute name for 'name': %s", errorMsg); + return UNKNOWN_ERROR; + } + + if (attrType != kAttr16) { + // The value of the 'name' attribute must be an attribute reference. + source.error("value of 'name' must be an attribute reference."); + return UNKNOWN_ERROR; + } + + // Generate a name for this nested resource and try to add it to the table. + // We do this in a loop because the name may be taken, in which case we will + // increment a suffix until we succeed. + String8 nestedResourceName; + String8 nestedResourcePath; + int suffix = 1; + while (true) { + // This child element will be extracted into its own resource file. + // Generate a name and path for it from its parent. + nestedResourceName = String8::format("%s_%d", + String8(resourceName).string(), suffix++); + nestedResourcePath = String8::format("res/%s/%s.xml", + target->getGroupEntry().toDirName(target->getResourceType()) + .string(), + nestedResourceName.string()); + + // Lookup or create the entry for this name. + sp<Entry> entry = getEntry(kAssetPackage16, + String16(target->getResourceType()), + String16(nestedResourceName), + source, + false, + &target->getGroupEntry().toParams(), + true); + if (entry == NULL) { + return UNKNOWN_ERROR; + } + + if (entry->getType() == Entry::TYPE_UNKNOWN) { + // The value for this resource has never been set, + // meaning we're good! + entry->setItem(source, String16(nestedResourcePath)); + break; + } + + // We failed (name already exists), so try with a different name + // (increment the suffix). + } + + if (bundle->getVerbose()) { + source.printf("generating nested resource %s:%s/%s", + mAssets->getPackage().string(), target->getResourceType().string(), + nestedResourceName.string()); + } + + // Build the attribute reference and assign it to the parent. + String16 nestedResourceRef = String16(String8::format("@%s:%s/%s", + mAssets->getPackage().string(), target->getResourceType().string(), + nestedResourceName.string())); + + String16 attrNs = buildNamespace(attrPackage); + if (parent->getAttribute(attrNs, attrName) != NULL) { + SourcePos(parent->getFilename(), parent->getStartLineNumber()) + .error("parent of nested resource already defines attribute '%s:%s'", + String8(attrPackage).string(), String8(attrName).string()); + return UNKNOWN_ERROR; + } + + // Add the reference to the inline resource. + parent->addAttribute(attrNs, attrName, nestedResourceRef); + + // Remove the <aapt:attr> child element from here. + children.removeAt(i); + i--; + + // Append all namespace declarations that we've seen on this branch in the XML tree + // to this resource. + // We do this because the order of namespace declarations and prefix usage is determined + // by the developer and we do not want to override any decisions. Be conservative. + for (size_t nsIndex = namespaces->size(); nsIndex > 0; nsIndex--) { + const sp<XMLNode>& ns = namespaces->itemAt(nsIndex - 1); + sp<XMLNode> newNs = XMLNode::newNamespace(ns->getFilename(), ns->getNamespacePrefix(), + ns->getNamespaceUri()); + newNs->addChild(nestedRoot); + nestedRoot = newNs; + } + + // Schedule compilation of the nested resource. + CompileResourceWorkItem workItem; + workItem.resPath = nestedResourcePath; + workItem.resourceName = String16(nestedResourceName); + workItem.xmlRoot = nestedRoot; + workItem.file = new AaptFile(target->getSourceFile(), target->getGroupEntry(), + target->getResourceType()); + mWorkQueue.push(workItem); + } + return NO_ERROR; +} |