diff options
author | 2016-08-25 12:26:56 -0700 | |
---|---|---|
committer | 2016-08-31 18:32:34 -0700 | |
commit | 5eeaaddffd23d8d85aeb321e3ceea626e42cf9de (patch) | |
tree | dcd102c0f14825c34c2251427db84e48cb11d8a7 | |
parent | 79758c8e7706f3cce265a881cc66df8771d3c456 (diff) |
AAPT2: Add Inline Complex XML support
See: https://developer.android.com/guide/topics/resources/complex-xml-resources.html
Change-Id: I8274c85e25cabf90423141c228697e873167d136
29 files changed, 1156 insertions, 358 deletions
diff --git a/tools/aapt2/Android.mk b/tools/aapt2/Android.mk index b52c530d03cb..1d4c3d27e109 100644 --- a/tools/aapt2/Android.mk +++ b/tools/aapt2/Android.mk @@ -23,6 +23,7 @@ LOCAL_PATH:= $(call my-dir) main := Main.cpp sources := \ compile/IdAssigner.cpp \ + compile/InlineXmlFormatParser.cpp \ compile/Png.cpp \ compile/PseudolocaleGenerator.cpp \ compile/Pseudolocalizer.cpp \ @@ -31,6 +32,7 @@ sources := \ flatten/Archive.cpp \ flatten/TableFlattener.cpp \ flatten/XmlFlattener.cpp \ + io/File.cpp \ io/FileSystem.cpp \ io/ZipArchive.cpp \ link/AutoVersioner.cpp \ @@ -77,6 +79,7 @@ sources += Format.proto testSources := \ compile/IdAssigner_test.cpp \ + compile/InlineXmlFormatParser_test.cpp \ compile/PseudolocaleGenerator_test.cpp \ compile/Pseudolocalizer_test.cpp \ compile/XmlIdCollector_test.cpp \ diff --git a/tools/aapt2/Debug.cpp b/tools/aapt2/Debug.cpp index 19bd5210c840..304e571832f4 100644 --- a/tools/aapt2/Debug.cpp +++ b/tools/aapt2/Debug.cpp @@ -247,5 +247,59 @@ void Debug::dumpHex(const void* data, size_t len) { } } +namespace { + +class XmlPrinter : public xml::Visitor { +public: + using xml::Visitor::visit; + + void visit(xml::Element* el) override { + std::cerr << mPrefix; + std::cerr << "E: "; + if (!el->namespaceUri.empty()) { + std::cerr << el->namespaceUri << ":"; + } + std::cerr << el->name << " (line=" << el->lineNumber << ")\n"; + + for (const xml::Attribute& attr : el->attributes) { + std::cerr << mPrefix << " A: "; + if (!attr.namespaceUri.empty()) { + std::cerr << attr.namespaceUri << ":"; + } + std::cerr << attr.name << "=" << attr.value << "\n"; + } + + const size_t previousSize = mPrefix.size(); + mPrefix += " "; + xml::Visitor::visit(el); + mPrefix.resize(previousSize); + } + + void visit(xml::Namespace* ns) override { + std::cerr << mPrefix; + std::cerr << "N: " << ns->namespacePrefix << "=" << ns->namespaceUri + << " (line=" << ns->lineNumber << ")\n"; + + const size_t previousSize = mPrefix.size(); + mPrefix += " "; + xml::Visitor::visit(ns); + mPrefix.resize(previousSize); + } + + void visit(xml::Text* text) override { + std::cerr << mPrefix; + std::cerr << "T: '" << text->text << "'\n"; + } + +private: + std::string mPrefix; +}; + +} // namespace + +void Debug::dumpXml(xml::XmlResource* doc) { + XmlPrinter printer; + doc->root->accept(&printer); +} } // namespace aapt diff --git a/tools/aapt2/Debug.h b/tools/aapt2/Debug.h index fbe64773d4ed..c0fcbf1f16fc 100644 --- a/tools/aapt2/Debug.h +++ b/tools/aapt2/Debug.h @@ -19,6 +19,7 @@ #include "Resource.h" #include "ResourceTable.h" +#include "xml/XmlDom.h" // Include for printf-like debugging. #include <iostream> @@ -34,6 +35,7 @@ struct Debug { static void printStyleGraph(ResourceTable* table, const ResourceName& targetStyle); static void dumpHex(const void* data, size_t len); + static void dumpXml(xml::XmlResource* doc); }; } // namespace aapt diff --git a/tools/aapt2/Format.proto b/tools/aapt2/Format.proto index d05425c5c64d..0917129ba90d 100644 --- a/tools/aapt2/Format.proto +++ b/tools/aapt2/Format.proto @@ -34,7 +34,7 @@ message CompiledFile { optional string resource_name = 1; optional uint32 line_no = 2; } - + optional string resource_name = 1; optional ConfigDescription config = 2; optional string source_path = 3; diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp index bcdf401077ee..32e5cfd573bc 100644 --- a/tools/aapt2/ResourceParser.cpp +++ b/tools/aapt2/ResourceParser.cpp @@ -925,35 +925,6 @@ Maybe<Attribute::Symbol> ResourceParser::parseEnumOrFlagItem(xml::XmlPullParser* Reference(ResourceNameRef({}, ResourceType::kId, maybeName.value())), val.data }; } -static Maybe<Reference> parseXmlAttributeName(StringPiece str) { - str = util::trimWhitespace(str); - const char* start = str.data(); - const char* const end = start + str.size(); - const char* p = start; - - Reference ref; - if (p != end && *p == '*') { - ref.privateReference = true; - start++; - p++; - } - - StringPiece package; - StringPiece name; - while (p != end) { - if (*p == ':') { - package = StringPiece(start, p - start); - name = StringPiece(p + 1, end - (p + 1)); - break; - } - p++; - } - - ref.name = ResourceName(package.toString(), ResourceType::kAttr, - name.empty() ? str.toString() : name.toString()); - return Maybe<Reference>(std::move(ref)); -} - bool ResourceParser::parseStyleItem(xml::XmlPullParser* parser, Style* style) { const Source source = mSource.withLine(parser->getLineNumber()); @@ -963,7 +934,7 @@ bool ResourceParser::parseStyleItem(xml::XmlPullParser* parser, Style* style) { return false; } - Maybe<Reference> maybeKey = parseXmlAttributeName(maybeName.value()); + Maybe<Reference> maybeKey = ResourceUtils::parseXmlAttributeName(maybeName.value()); if (!maybeKey) { mDiag->error(DiagMessage(source) << "invalid attribute name '" << maybeName.value() << "'"); return false; @@ -1226,7 +1197,7 @@ bool ResourceParser::parseDeclareStyleable(xml::XmlPullParser* parser, // If this is a declaration, the package name may be in the name. Separate these out. // Eg. <attr name="android:text" /> - Maybe<Reference> maybeRef = parseXmlAttributeName(maybeName.value()); + Maybe<Reference> maybeRef = ResourceUtils::parseXmlAttributeName(maybeName.value()); if (!maybeRef) { mDiag->error(DiagMessage(itemSource) << "<attr> tag has invalid name '" << maybeName.value() << "'"); diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp index 21d2f642c7d6..bdc6a8c5d4f9 100644 --- a/tools/aapt2/ResourceTable.cpp +++ b/tools/aapt2/ResourceTable.cpp @@ -327,7 +327,7 @@ bool ResourceTable::addFileReferenceImpl(const ResourceNameRef& name, fileRef->setSource(source); fileRef->file = file; return addResourceImpl(name, ResourceId{}, config, StringPiece{}, std::move(fileRef), - kValidNameChars, resolveValueCollision, diag); + validChars, resolveValueCollision, diag); } bool ResourceTable::addResourceAllowMangled(const ResourceNameRef& name, diff --git a/tools/aapt2/ResourceTable.h b/tools/aapt2/ResourceTable.h index df6081456605..6c246d0d81d6 100644 --- a/tools/aapt2/ResourceTable.h +++ b/tools/aapt2/ResourceTable.h @@ -290,14 +290,6 @@ public: private: ResourceTablePackage* findOrCreatePackage(const StringPiece& name); - bool addFileReferenceImpl(const ResourceNameRef& name, - const ConfigDescription& config, - const Source& source, - const StringPiece& path, - io::IFile* file, - const char* validChars, - IDiagnostics* diag); - bool addResourceImpl(const ResourceNameRef& name, const ResourceId& resId, const ConfigDescription& config, @@ -307,6 +299,14 @@ private: const CollisionResolverFunc& conflictResolver, IDiagnostics* diag); + bool addFileReferenceImpl(const ResourceNameRef& name, + const ConfigDescription& config, + const Source& source, + const StringPiece& path, + io::IFile* file, + const char* validChars, + IDiagnostics* diag); + bool setSymbolStateImpl(const ResourceNameRef& name, const ResourceId& resId, const Symbol& symbol, diff --git a/tools/aapt2/ResourceUtils.cpp b/tools/aapt2/ResourceUtils.cpp index 11619fa9c67d..73a194e90ea2 100644 --- a/tools/aapt2/ResourceUtils.cpp +++ b/tools/aapt2/ResourceUtils.cpp @@ -268,6 +268,35 @@ Maybe<Reference> parseStyleParentReference(const StringPiece& str, std::string* return result; } +Maybe<Reference> parseXmlAttributeName(const StringPiece& str) { + StringPiece trimmedStr = util::trimWhitespace(str); + const char* start = trimmedStr.data(); + const char* const end = start + trimmedStr.size(); + const char* p = start; + + Reference ref; + if (p != end && *p == '*') { + ref.privateReference = true; + start++; + p++; + } + + StringPiece package; + StringPiece name; + while (p != end) { + if (*p == ':') { + package = StringPiece(start, p - start); + name = StringPiece(p + 1, end - (p + 1)); + break; + } + p++; + } + + ref.name = ResourceName(package.toString(), ResourceType::kAttr, + name.empty() ? trimmedStr.toString() : name.toString()); + return Maybe<Reference>(std::move(ref)); +} + std::unique_ptr<Reference> tryParseReference(const StringPiece& str, bool* outCreate) { ResourceNameRef ref; bool privateRef = false; diff --git a/tools/aapt2/ResourceUtils.h b/tools/aapt2/ResourceUtils.h index 244047bae117..555203b84393 100644 --- a/tools/aapt2/ResourceUtils.h +++ b/tools/aapt2/ResourceUtils.h @@ -111,6 +111,14 @@ Maybe<int> parseSdkVersion(const StringPiece& str); Maybe<Reference> parseStyleParentReference(const StringPiece& str, std::string* outError); /* + * Returns a Reference if the string `str` was parsed as a valid XML attribute name. + * The valid format for an XML attribute name is: + * + * package:entry + */ +Maybe<Reference> parseXmlAttributeName(const StringPiece& str); + +/* * Returns a Reference object if the string was parsed as a resource or attribute reference, * ( @[+][package:]type/name | ?[package:]type/name ) setting outCreate to true if * the '+' was present in the string. diff --git a/tools/aapt2/compile/Compile.cpp b/tools/aapt2/compile/Compile.cpp index 39e4489ffda2..e0f37ec37b92 100644 --- a/tools/aapt2/compile/Compile.cpp +++ b/tools/aapt2/compile/Compile.cpp @@ -20,6 +20,7 @@ #include "ResourceParser.h" #include "ResourceTable.h" #include "compile/IdAssigner.h" +#include "compile/InlineXmlFormatParser.h" #include "compile/Png.h" #include "compile/PseudolocaleGenerator.h" #include "compile/XmlIdCollector.h" @@ -39,6 +40,9 @@ #include <fstream> #include <string> +using google::protobuf::io::CopyingOutputStreamAdaptor; +using google::protobuf::io::ZeroCopyOutputStream; + namespace aapt { struct ResourcePathData { @@ -238,13 +242,14 @@ static bool compileTable(IAaptContext* context, const CompileOptions& options, return false; } - std::unique_ptr<pb::ResourceTable> pbTable = serializeTableToPb(&table); - - // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream interface. + // Make sure CopyingOutputStreamAdaptor is deleted before we call writer->finishEntry(). { - google::protobuf::io::CopyingOutputStreamAdaptor adaptor(writer); + // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream + // interface. + CopyingOutputStreamAdaptor copyingAdaptor(writer); - if (!pbTable->SerializeToZeroCopyStream(&adaptor)) { + std::unique_ptr<pb::ResourceTable> pbTable = serializeTableToPb(&table); + if (!pbTable->SerializeToZeroCopyStream(©ingAdaptor)) { context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to write"); return false; } @@ -266,21 +271,23 @@ static bool writeHeaderAndBufferToWriter(const StringPiece& outputPath, const Re return false; } - // Create the header. - std::unique_ptr<pb::CompiledFile> pbCompiledFile = serializeCompiledFileToPb(file); - + // Make sure CopyingOutputStreamAdaptor is deleted before we call writer->finishEntry(). { - // The stream must be destroyed before we finish the entry, or else - // some data won't be flushed. // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream // interface. - google::protobuf::io::CopyingOutputStreamAdaptor adaptor(writer); - CompiledFileOutputStream outputStream(&adaptor, pbCompiledFile.get()); - for (const BigBuffer::Block& block : buffer) { - if (!outputStream.Write(block.buffer.get(), block.size)) { - diag->error(DiagMessage(outputPath) << "failed to write data"); - return false; - } + CopyingOutputStreamAdaptor copyingAdaptor(writer); + CompiledFileOutputStream outputStream(©ingAdaptor); + + // Number of CompiledFiles. + outputStream.WriteLittleEndian32(1); + + std::unique_ptr<pb::CompiledFile> compiledFile = serializeCompiledFileToPb(file); + outputStream.WriteCompiledFile(compiledFile.get()); + outputStream.WriteData(&buffer); + + if (outputStream.HadError()) { + diag->error(DiagMessage(outputPath) << "failed to write data"); + return false; } } @@ -300,17 +307,21 @@ static bool writeHeaderAndMmapToWriter(const StringPiece& outputPath, const Reso return false; } - // Create the header. - std::unique_ptr<pb::CompiledFile> pbCompiledFile = serializeCompiledFileToPb(file); - + // Make sure CopyingOutputStreamAdaptor is deleted before we call writer->finishEntry(). { - // The stream must be destroyed before we finish the entry, or else - // some data won't be flushed. // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream // interface. - google::protobuf::io::CopyingOutputStreamAdaptor adaptor(writer); - CompiledFileOutputStream outputStream(&adaptor, pbCompiledFile.get()); - if (!outputStream.Write(map.getDataPtr(), map.getDataLength())) { + CopyingOutputStreamAdaptor copyingAdaptor(writer); + CompiledFileOutputStream outputStream(©ingAdaptor); + + // Number of CompiledFiles. + outputStream.WriteLittleEndian32(1); + + std::unique_ptr<pb::CompiledFile> compiledFile = serializeCompiledFileToPb(file); + outputStream.WriteCompiledFile(compiledFile.get()); + outputStream.WriteData(map.getDataPtr(), map.getDataLength()); + + if (outputStream.HadError()) { diag->error(DiagMessage(outputPath) << "failed to write data"); return false; } @@ -323,6 +334,28 @@ static bool writeHeaderAndMmapToWriter(const StringPiece& outputPath, const Reso return true; } +static bool flattenXmlToOutStream(IAaptContext* context, const StringPiece& outputPath, + xml::XmlResource* xmlRes, + CompiledFileOutputStream* out) { + BigBuffer buffer(1024); + XmlFlattenerOptions xmlFlattenerOptions; + xmlFlattenerOptions.keepRawValues = true; + XmlFlattener flattener(&buffer, xmlFlattenerOptions); + if (!flattener.consume(context, xmlRes)) { + return false; + } + + std::unique_ptr<pb::CompiledFile> pbCompiledFile = serializeCompiledFileToPb(xmlRes->file); + out->WriteCompiledFile(pbCompiledFile.get()); + out->WriteData(&buffer); + + if (out->HadError()) { + context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to write data"); + return false; + } + return true; +} + static bool compileXml(IAaptContext* context, const CompileOptions& options, const ResourcePathData& pathData, IArchiveWriter* writer, const std::string& outputPath) { @@ -344,26 +377,55 @@ static bool compileXml(IAaptContext* context, const CompileOptions& options, return false; } + xmlRes->file.name = ResourceName({}, *parseResourceType(pathData.resourceDir), pathData.name); + xmlRes->file.config = pathData.config; + xmlRes->file.source = pathData.source; + // Collect IDs that are defined here. XmlIdCollector collector; if (!collector.consume(context, xmlRes.get())) { return false; } - xmlRes->file.name = ResourceName({}, *parseResourceType(pathData.resourceDir), pathData.name); - xmlRes->file.config = pathData.config; - xmlRes->file.source = pathData.source; + // Look for and process any <aapt:attr> tags and create sub-documents. + InlineXmlFormatParser inlineXmlFormatParser; + if (!inlineXmlFormatParser.consume(context, xmlRes.get())) { + return false; + } - BigBuffer buffer(1024); - XmlFlattenerOptions xmlFlattenerOptions; - xmlFlattenerOptions.keepRawValues = true; - XmlFlattener flattener(&buffer, xmlFlattenerOptions); - if (!flattener.consume(context, xmlRes.get())) { + // Start the entry so we can write the header. + if (!writer->startEntry(outputPath, 0)) { + context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to open file"); return false; } - if (!writeHeaderAndBufferToWriter(outputPath, xmlRes->file, buffer, writer, - context->getDiagnostics())) { + // Make sure CopyingOutputStreamAdaptor is deleted before we call writer->finishEntry(). + { + // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream + // interface. + CopyingOutputStreamAdaptor copyingAdaptor(writer); + CompiledFileOutputStream outputStream(©ingAdaptor); + + std::vector<std::unique_ptr<xml::XmlResource>>& inlineDocuments = + inlineXmlFormatParser.getExtractedInlineXmlDocuments(); + + // Number of CompiledFiles. + outputStream.WriteLittleEndian32(1 + inlineDocuments.size()); + + if (!flattenXmlToOutStream(context, outputPath, xmlRes.get(), &outputStream)) { + return false; + } + + for (auto& inlineXmlDoc : inlineDocuments) { + if (!flattenXmlToOutStream(context, outputPath, inlineXmlDoc.get(), &outputStream)) { + return false; + } + } + } + + if (!writer->finishEntry()) { + context->getDiagnostics()->error(DiagMessage(outputPath) + << "failed to finish writing data"); return false; } return true; diff --git a/tools/aapt2/compile/InlineXmlFormatParser.cpp b/tools/aapt2/compile/InlineXmlFormatParser.cpp new file mode 100644 index 000000000000..f965bff187a2 --- /dev/null +++ b/tools/aapt2/compile/InlineXmlFormatParser.cpp @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Debug.h" +#include "ResourceUtils.h" +#include "compile/InlineXmlFormatParser.h" +#include "util/Util.h" +#include "xml/XmlDom.h" +#include "xml/XmlUtil.h" + +#include <android-base/macros.h> +#include <sstream> +#include <string> + +namespace aapt { + +namespace { + +/** + * XML Visitor that will find all <aapt:attr> elements for extraction. + */ +class Visitor : public xml::PackageAwareVisitor { +public: + using xml::PackageAwareVisitor::visit; + + struct InlineDeclaration { + xml::Element* el; + std::string attrNamespaceUri; + std::string attrName; + }; + + explicit Visitor(IAaptContext* context, xml::XmlResource* xmlResource) : + mContext(context), mXmlResource(xmlResource) { + } + + void visit(xml::Element* el) override { + if (el->namespaceUri != xml::kSchemaAapt || el->name != "attr") { + xml::PackageAwareVisitor::visit(el); + return; + } + + const Source& src = mXmlResource->file.source.withLine(el->lineNumber); + + xml::Attribute* attr = el->findAttribute({}, "name"); + if (!attr) { + mContext->getDiagnostics()->error(DiagMessage(src) << "missing 'name' attribute"); + mError = true; + return; + } + + Maybe<Reference> ref = ResourceUtils::parseXmlAttributeName(attr->value); + if (!ref) { + mContext->getDiagnostics()->error(DiagMessage(src) << "invalid XML attribute '" + << attr->value << "'"); + mError = true; + return; + } + + const ResourceName& name = ref.value().name.value(); + + // Use an empty string for the compilation package because we don't want to default to + // the local package if the user specified name="style" or something. This should just + // be the default namespace. + Maybe<xml::ExtractedPackage> maybePkg = transformPackageAlias(name.package, {}); + if (!maybePkg) { + mContext->getDiagnostics()->error(DiagMessage(src) << "invalid namespace prefix '" + << name.package << "'"); + mError = true; + return; + } + + const xml::ExtractedPackage& pkg = maybePkg.value(); + const bool privateNamespace = pkg.privateNamespace || ref.value().privateReference; + + InlineDeclaration decl; + decl.el = el; + decl.attrName = name.entry; + if (!pkg.package.empty()) { + decl.attrNamespaceUri = xml::buildPackageNamespace(pkg.package, privateNamespace); + } + + mInlineDeclarations.push_back(std::move(decl)); + } + + const std::vector<InlineDeclaration>& getInlineDeclarations() const { + return mInlineDeclarations; + } + + bool hasError() const { + return mError; + } + +private: + DISALLOW_COPY_AND_ASSIGN(Visitor); + + IAaptContext* mContext; + xml::XmlResource* mXmlResource; + std::vector<InlineDeclaration> mInlineDeclarations; + bool mError = false; +}; + +} // namespace + +bool InlineXmlFormatParser::consume(IAaptContext* context, xml::XmlResource* doc) { + Visitor visitor(context, doc); + doc->root->accept(&visitor); + if (visitor.hasError()) { + return false; + } + + size_t nameSuffixCounter = 0; + for (const Visitor::InlineDeclaration& decl : visitor.getInlineDeclarations()) { + auto newDoc = util::make_unique<xml::XmlResource>(); + newDoc->file.config = doc->file.config; + newDoc->file.source = doc->file.source.withLine(decl.el->lineNumber); + newDoc->file.name = doc->file.name; + + // Modify the new entry name. We need to suffix the entry with a number to avoid + // local collisions, then mangle it with the empty package, such that it won't show up + // in R.java. + + newDoc->file.name.entry = NameMangler::mangleEntry( + {}, newDoc->file.name.entry + "__" + std::to_string(nameSuffixCounter)); + + // Extracted elements must be the only child of <aapt:attr>. + // Make sure there is one root node in the children (ignore empty text). + for (auto& child : decl.el->children) { + const Source childSource = doc->file.source.withLine(child->lineNumber); + if (xml::Text* t = xml::nodeCast<xml::Text>(child.get())) { + if (!util::trimWhitespace(t->text).empty()) { + context->getDiagnostics()->error(DiagMessage(childSource) + << "can't extract text into its own resource"); + return false; + } + } else if (newDoc->root) { + context->getDiagnostics()->error(DiagMessage(childSource) + << "inline XML resources must have a single root"); + return false; + } else { + newDoc->root = std::move(child); + newDoc->root->parent = nullptr; + } + } + + // Walk up and find the parent element. + xml::Node* node = decl.el; + xml::Element* parentEl = nullptr; + while (node->parent && (parentEl = xml::nodeCast<xml::Element>(node->parent)) == nullptr) { + node = node->parent; + } + + if (!parentEl) { + context->getDiagnostics()->error(DiagMessage(newDoc->file.source) + << "no suitable parent for inheriting attribute"); + return false; + } + + // Add the inline attribute to the parent. + parentEl->attributes.push_back(xml::Attribute{ + decl.attrNamespaceUri, decl.attrName, "@" + newDoc->file.name.toString() }); + + // Delete the subtree. + for (auto iter = parentEl->children.begin(); iter != parentEl->children.end(); ++iter) { + if (iter->get() == node) { + parentEl->children.erase(iter); + break; + } + } + + mQueue.push_back(std::move(newDoc)); + + nameSuffixCounter++; + } + return true; +} + +} // namespace aapt diff --git a/tools/aapt2/compile/InlineXmlFormatParser.h b/tools/aapt2/compile/InlineXmlFormatParser.h new file mode 100644 index 000000000000..69065fd2b6e8 --- /dev/null +++ b/tools/aapt2/compile/InlineXmlFormatParser.h @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AAPT_COMPILE_INLINEXMLFORMATPARSER_H +#define AAPT_COMPILE_INLINEXMLFORMATPARSER_H + +#include "process/IResourceTableConsumer.h" + +#include <android-base/macros.h> +#include <memory> +#include <vector> + +namespace aapt { + +/** + * Extracts Inline XML definitions into their own xml::XmlResource objects. + * + * Inline XML looks like: + * + * <animated-vector xmlns:android="http://schemas.android.com/apk/res/android" + * xmlns:aapt="http://schemas.android.com/aapt" > + * <aapt:attr name="android:drawable" > + * <vector + * android:height="64dp" + * android:width="64dp" + * android:viewportHeight="600" + * android:viewportWidth="600"/> + * </aapt:attr> + * </animated-vector> + * + * The <vector> will be extracted into its own XML file and <animated-vector> will + * gain an attribute 'android:drawable' set to a reference to the extracted <vector> resource. + */ +class InlineXmlFormatParser : public IXmlResourceConsumer { +public: + explicit InlineXmlFormatParser() = default; + + bool consume(IAaptContext* context, xml::XmlResource* doc) override; + + std::vector<std::unique_ptr<xml::XmlResource>>& getExtractedInlineXmlDocuments() { + return mQueue; + } + +private: + DISALLOW_COPY_AND_ASSIGN(InlineXmlFormatParser); + + std::vector<std::unique_ptr<xml::XmlResource>> mQueue; +}; + +} // namespace aapt + +#endif /* AAPT_COMPILE_INLINEXMLFORMATPARSER_H */ diff --git a/tools/aapt2/compile/InlineXmlFormatParser_test.cpp b/tools/aapt2/compile/InlineXmlFormatParser_test.cpp new file mode 100644 index 000000000000..8d62210da311 --- /dev/null +++ b/tools/aapt2/compile/InlineXmlFormatParser_test.cpp @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "compile/InlineXmlFormatParser.h" +#include "test/Test.h" + +namespace aapt { + +TEST(InlineXmlFormatParserTest, PassThrough) { + std::unique_ptr<IAaptContext> context = test::ContextBuilder().build(); + std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF( + <View xmlns:android="http://schemas.android.com/apk/res/android"> + <View android:text="hey"> + <View android:id="hi" /> + </View> + </View>)EOF"); + + InlineXmlFormatParser parser; + ASSERT_TRUE(parser.consume(context.get(), doc.get())); + EXPECT_EQ(0u, parser.getExtractedInlineXmlDocuments().size()); +} + +TEST(InlineXmlFormatParserTest, ExtractOneXmlResource) { + std::unique_ptr<IAaptContext> context = test::ContextBuilder().build(); + std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF( + <View1 xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:aapt="http://schemas.android.com/aapt"> + <aapt:attr name="android:text"> + <View2 android:text="hey"> + <View3 android:id="hi" /> + </View2> + </aapt:attr> + </View1>)EOF"); + + doc->file.name = test::parseNameOrDie("layout/main"); + + InlineXmlFormatParser parser; + ASSERT_TRUE(parser.consume(context.get(), doc.get())); + + // One XML resource should have been extracted. + EXPECT_EQ(1u, parser.getExtractedInlineXmlDocuments().size()); + + xml::Element* el = xml::findRootElement(doc.get()); + ASSERT_NE(nullptr, el); + + EXPECT_EQ("View1", el->name); + + // The <aapt:attr> tag should be extracted. + EXPECT_EQ(nullptr, el->findChild(xml::kSchemaAapt, "attr")); + + // The 'android:text' attribute should be set with a reference. + xml::Attribute* attr = el->findAttribute(xml::kSchemaAndroid, "text"); + ASSERT_NE(nullptr, attr); + + ResourceNameRef nameRef; + ASSERT_TRUE(ResourceUtils::parseReference(attr->value, &nameRef)); + + xml::XmlResource* extractedDoc = parser.getExtractedInlineXmlDocuments()[0].get(); + ASSERT_NE(nullptr, extractedDoc); + + // Make sure the generated reference is correct. + EXPECT_EQ(nameRef.package, extractedDoc->file.name.package); + EXPECT_EQ(nameRef.type, extractedDoc->file.name.type); + EXPECT_EQ(nameRef.entry, extractedDoc->file.name.entry); + + // Verify the structure of the extracted XML. + el = xml::findRootElement(extractedDoc); + ASSERT_NE(nullptr, el); + EXPECT_EQ("View2", el->name); + EXPECT_NE(nullptr, el->findChild({}, "View3")); +} + +TEST(InlineXmlFormatParserTest, ExtractTwoXmlResources) { + std::unique_ptr<IAaptContext> context = test::ContextBuilder().build(); + std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF( + <View1 xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:aapt="http://schemas.android.com/aapt"> + <aapt:attr name="android:text"> + <View2 android:text="hey"> + <View3 android:id="hi" /> + </View2> + </aapt:attr> + + <aapt:attr name="android:drawable"> + <vector /> + </aapt:attr> + </View1>)EOF"); + + doc->file.name = test::parseNameOrDie("layout/main"); + + InlineXmlFormatParser parser; + ASSERT_TRUE(parser.consume(context.get(), doc.get())); + ASSERT_EQ(2u, parser.getExtractedInlineXmlDocuments().size()); + + xml::Element* el = xml::findRootElement(doc.get()); + ASSERT_NE(nullptr, el); + + EXPECT_EQ("View1", el->name); + + xml::Attribute* attrText = el->findAttribute(xml::kSchemaAndroid, "text"); + ASSERT_NE(nullptr, attrText); + + xml::Attribute* attrDrawable = el->findAttribute(xml::kSchemaAndroid, "drawable"); + ASSERT_NE(nullptr, attrDrawable); + + // The two extracted resources should have different names. + EXPECT_NE(attrText->value, attrDrawable->value); + + // The child <aapt:attr> elements should be gone. + EXPECT_EQ(nullptr, el->findChild(xml::kSchemaAapt, "attr")); + + xml::XmlResource* extractedDocText = parser.getExtractedInlineXmlDocuments()[0].get(); + ASSERT_NE(nullptr, extractedDocText); + el = xml::findRootElement(extractedDocText); + ASSERT_NE(nullptr, el); + EXPECT_EQ("View2", el->name); + + xml::XmlResource* extractedDocDrawable = parser.getExtractedInlineXmlDocuments()[1].get(); + ASSERT_NE(nullptr, extractedDocDrawable); + el = xml::findRootElement(extractedDocDrawable); + ASSERT_NE(nullptr, el); + EXPECT_EQ("vector", el->name); +} + +} // namespace aapt diff --git a/tools/aapt2/dump/Dump.cpp b/tools/aapt2/dump/Dump.cpp index 88c6f64f5510..f61ec945a1c1 100644 --- a/tools/aapt2/dump/Dump.cpp +++ b/tools/aapt2/dump/Dump.cpp @@ -37,6 +37,7 @@ void dumpCompiledFile(const pb::CompiledFile& pbFile, const void* data, size_t l std::unique_ptr<ResourceFile> file = deserializeCompiledFileFromPb(pbFile, source, context->getDiagnostics()); if (!file) { + context->getDiagnostics()->warn(DiagMessage() << "failed to read compiled file"); return; } @@ -112,9 +113,27 @@ void tryDumpFile(IAaptContext* context, const std::string& filePath) { if (!table) { // Try as a compiled file. CompiledFileInputStream input(fileMap->getDataPtr(), fileMap->getDataLength()); - if (const pb::CompiledFile* pbFile = input.CompiledFile()) { - dumpCompiledFile(*pbFile, input.data(), input.size(), Source(filePath), context); - return; + + uint32_t numFiles = 0; + if (!input.ReadLittleEndian32(&numFiles)) { + return; + } + + for (uint32_t i = 0; i < numFiles; i++) { + pb::CompiledFile compiledFile; + if (!input.ReadCompiledFile(&compiledFile)) { + context->getDiagnostics()->warn(DiagMessage() << "failed to read compiled file"); + return; + } + + uint64_t offset, len; + if (!input.ReadDataMetaData(&offset, &len)) { + context->getDiagnostics()->warn(DiagMessage() << "failed to read meta data"); + return; + } + + const void* data = static_cast<const uint8_t*>(fileMap->getDataPtr()) + offset; + dumpCompiledFile(compiledFile, data, len, Source(filePath), context); } } } diff --git a/tools/aapt2/flatten/Archive.h b/tools/aapt2/flatten/Archive.h index 34c10ad40365..96d8512ca877 100644 --- a/tools/aapt2/flatten/Archive.h +++ b/tools/aapt2/flatten/Archive.h @@ -41,7 +41,8 @@ struct ArchiveEntry { size_t uncompressedSize; }; -struct IArchiveWriter : public google::protobuf::io::CopyingOutputStream { +class IArchiveWriter : public google::protobuf::io::CopyingOutputStream { +public: virtual ~IArchiveWriter() = default; virtual bool startEntry(const StringPiece& path, uint32_t flags) = 0; diff --git a/tools/aapt2/integration-tests/AppOne/res/layout/special.xml b/tools/aapt2/integration-tests/AppOne/res/layout/special.xml new file mode 100644 index 000000000000..28c85ca92019 --- /dev/null +++ b/tools/aapt2/integration-tests/AppOne/res/layout/special.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="utf-8"?> +<View xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="wrap_content" + android:layout_height="wrap_content"> + <include> + <aapt:attr name="layout" xmlns:aapt="http://schemas.android.com/aapt"> + <RelativeLayout android:id="@+id/hello" /> + </aapt:attr> + </include> +</View> diff --git a/tools/aapt2/io/Data.h b/tools/aapt2/io/Data.h index 467e60464a68..34eed63471f8 100644 --- a/tools/aapt2/io/Data.h +++ b/tools/aapt2/io/Data.h @@ -17,9 +17,9 @@ #ifndef AAPT_IO_DATA_H #define AAPT_IO_DATA_H -#include <utils/FileMap.h> - +#include <android-base/macros.h> #include <memory> +#include <utils/FileMap.h> namespace aapt { namespace io { @@ -35,6 +35,28 @@ public: virtual size_t size() const = 0; }; +class DataSegment : public IData { +public: + explicit DataSegment(std::unique_ptr<IData> data, size_t offset, size_t len) : + mData(std::move(data)), mOffset(offset), mLen(len) { + } + + const void* data() const override { + return static_cast<const uint8_t*>(mData->data()) + mOffset; + } + + size_t size() const override { + return mLen; + } + +private: + DISALLOW_COPY_AND_ASSIGN(DataSegment); + + std::unique_ptr<IData> mData; + size_t mOffset; + size_t mLen; +}; + /** * Implementation of IData that exposes a memory mapped file. The mmapped file is owned by this * object. diff --git a/tools/aapt2/io/File.cpp b/tools/aapt2/io/File.cpp new file mode 100644 index 000000000000..739c0d21e06e --- /dev/null +++ b/tools/aapt2/io/File.cpp @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "io/File.h" + +#include <memory> + +namespace aapt { +namespace io { + +IFile* IFile::createFileSegment(size_t offset, size_t len) { + FileSegment* fileSegment = new FileSegment(this, offset, len); + mSegments.push_back(std::unique_ptr<IFile>(fileSegment)); + return fileSegment; +} + +std::unique_ptr<IData> FileSegment::openAsData() { + std::unique_ptr<IData> data = mFile->openAsData(); + if (!data) { + return {}; + } + + if (mOffset <= data->size() - mLen) { + return util::make_unique<DataSegment>(std::move(data), mOffset, mLen); + } + return {}; +} + +} // namespace io +} // namespace aapt diff --git a/tools/aapt2/io/File.h b/tools/aapt2/io/File.h index b4d49719aa3e..807981edf663 100644 --- a/tools/aapt2/io/File.h +++ b/tools/aapt2/io/File.h @@ -19,7 +19,10 @@ #include "Source.h" #include "io/Data.h" +#include "util/Util.h" +#include <android-base/macros.h> +#include <list> #include <memory> #include <vector> @@ -50,6 +53,37 @@ public: * a ZIP archive from the path to the containing ZIP archive. */ virtual const Source& getSource() const = 0; + + IFile* createFileSegment(size_t offset, size_t len); + +private: + // Any segments created from this IFile need to be owned by this IFile, so keep them + // in a list. This will never be read, so we prefer better insertion performance + // than cache locality, hence the list. + std::list<std::unique_ptr<IFile>> mSegments; +}; + +/** + * An IFile that wraps an underlying IFile but limits it to a subsection of that file. + */ +class FileSegment : public IFile { +public: + explicit FileSegment(IFile* file, size_t offset, size_t len) : + mFile(file), mOffset(offset), mLen(len) { + } + + std::unique_ptr<IData> openAsData() override; + + const Source& getSource() const override { + return mFile->getSource(); + } + +private: + DISALLOW_COPY_AND_ASSIGN(FileSegment); + + IFile* mFile; + size_t mOffset; + size_t mLen; }; class IFileCollectionIterator { diff --git a/tools/aapt2/link/Link.cpp b/tools/aapt2/link/Link.cpp index ea95dd1e6c2f..c2363943b535 100644 --- a/tools/aapt2/link/Link.cpp +++ b/tools/aapt2/link/Link.cpp @@ -48,10 +48,13 @@ #include <google/protobuf/io/coded_stream.h> #include <fstream> +#include <queue> #include <sys/stat.h> #include <unordered_map> #include <vector> +using google::protobuf::io::CopyingOutputStreamAdaptor; + namespace aapt { struct LinkOptions { @@ -166,19 +169,7 @@ static bool copyFileToArchive(io::IFile* file, const std::string& outPath, } const uint8_t* buffer = reinterpret_cast<const uint8_t*>(data->data()); - size_t bufferSize = data->size(); - - // If the file ends with .flat, we must strip off the CompiledFileHeader from it. - if (util::stringEndsWith(file->getSource().path, ".flat")) { - CompiledFileInputStream inputStream(data->data(), data->size()); - if (!inputStream.CompiledFile()) { - context->getDiagnostics()->error(DiagMessage(file->getSource()) - << "invalid compiled file header"); - return false; - } - buffer = reinterpret_cast<const uint8_t*>(inputStream.data()); - bufferSize = inputStream.size(); - } + const size_t bufferSize = data->size(); if (context->verbose()) { context->getDiagnostics()->note(DiagMessage() << "writing " << outPath << " to archive"); @@ -255,42 +246,6 @@ static std::unique_ptr<xml::XmlResource> loadXml(const std::string& path, IDiagn return xml::inflate(&fin, diag, Source(path)); } -static std::unique_ptr<xml::XmlResource> loadBinaryXmlSkipFileExport(const Source& source, - const void* data, size_t len, - IDiagnostics* diag) { - CompiledFileInputStream inputStream(data, len); - if (!inputStream.CompiledFile()) { - diag->error(DiagMessage(source) << "invalid compiled file header"); - return {}; - } - - const uint8_t* xmlData = reinterpret_cast<const uint8_t*>(inputStream.data()); - const size_t xmlDataLen = inputStream.size(); - - std::unique_ptr<xml::XmlResource> xmlRes = xml::inflate(xmlData, xmlDataLen, diag, source); - if (!xmlRes) { - return {}; - } - return xmlRes; -} - -static std::unique_ptr<ResourceFile> loadFileExportHeader(const Source& source, - const void* data, size_t len, - IDiagnostics* diag) { - CompiledFileInputStream inputStream(data, len); - const pb::CompiledFile* pbFile = inputStream.CompiledFile(); - if (!pbFile) { - diag->error(DiagMessage(source) << "invalid compiled file header"); - return {}; - } - - std::unique_ptr<ResourceFile> resFile = deserializeCompiledFileFromPb(*pbFile, source, diag); - if (!resFile) { - return {}; - } - return resFile; -} - struct ResourceFileFlattenerOptions { bool noAutoVersion = false; bool noVersionVectors = false; @@ -312,16 +267,26 @@ public: private: struct FileOperation { + ConfigDescription config; + + // The entry this file came from. + const ResourceEntry* entry; + + // The file to copy as-is. io::IFile* fileToCopy; + + // The XML to process and flatten. std::unique_ptr<xml::XmlResource> xmlToFlatten; + + // The destination to write this file to. std::string dstPath; bool skipVersion = false; }; uint32_t getCompressionFlags(const StringPiece& str); - bool linkAndVersionXmlFile(const ResourceEntry* entry, const ResourceFile& fileDesc, - io::IFile* file, ResourceTable* table, FileOperation* outFileOp); + bool linkAndVersionXmlFile(ResourceTable* table, FileOperation* fileOp, + std::queue<FileOperation>* outFileOpQueue); ResourceFileFlattenerOptions mOptions; IAaptContext* mContext; @@ -341,52 +306,28 @@ uint32_t ResourceFileFlattener::getCompressionFlags(const StringPiece& str) { return ArchiveEntry::kCompress; } -bool ResourceFileFlattener::linkAndVersionXmlFile(const ResourceEntry* entry, - const ResourceFile& fileDesc, - io::IFile* file, - ResourceTable* table, - FileOperation* outFileOp) { - const StringPiece srcPath = file->getSource().path; - if (mContext->verbose()) { - mContext->getDiagnostics()->note(DiagMessage() << "linking " << srcPath); - } +bool ResourceFileFlattener::linkAndVersionXmlFile(ResourceTable* table, + FileOperation* fileOp, + std::queue<FileOperation>* outFileOpQueue) { + xml::XmlResource* doc = fileOp->xmlToFlatten.get(); + const Source& src = doc->file.source; - std::unique_ptr<io::IData> data = file->openAsData(); - if (!data) { - mContext->getDiagnostics()->error(DiagMessage(file->getSource()) << "failed to open file"); - return false; - } - - if (util::stringEndsWith(srcPath, ".flat")) { - outFileOp->xmlToFlatten = loadBinaryXmlSkipFileExport(file->getSource(), - data->data(), data->size(), - mContext->getDiagnostics()); - } else { - outFileOp->xmlToFlatten = xml::inflate(data->data(), data->size(), - mContext->getDiagnostics(), - file->getSource()); - } - - if (!outFileOp->xmlToFlatten) { - return false; + if (mContext->verbose()) { + mContext->getDiagnostics()->note(DiagMessage() << "linking " << src.path); } - // Copy the the file description header. - outFileOp->xmlToFlatten->file = fileDesc; - XmlReferenceLinker xmlLinker; - if (!xmlLinker.consume(mContext, outFileOp->xmlToFlatten.get())) { + if (!xmlLinker.consume(mContext, doc)) { return false; } - if (mOptions.updateProguardSpec && !proguard::collectProguardRules( - outFileOp->xmlToFlatten->file.source, outFileOp->xmlToFlatten.get(), mKeepSet)) { + if (mOptions.updateProguardSpec && !proguard::collectProguardRules(src, doc, mKeepSet)) { return false; } if (mOptions.noXmlNamespaces) { XmlNamespaceRemover namespaceRemover; - if (!namespaceRemover.consume(mContext, outFileOp->xmlToFlatten.get())) { + if (!namespaceRemover.consume(mContext, doc)) { return false; } } @@ -394,51 +335,58 @@ bool ResourceFileFlattener::linkAndVersionXmlFile(const ResourceEntry* entry, if (!mOptions.noAutoVersion) { if (mOptions.noVersionVectors) { // Skip this if it is a vector or animated-vector. - xml::Element* el = xml::findRootElement(outFileOp->xmlToFlatten.get()); + xml::Element* el = xml::findRootElement(doc); if (el && el->namespaceUri.empty()) { if (el->name == "vector" || el->name == "animated-vector") { // We are NOT going to version this file. - outFileOp->skipVersion = true; + fileOp->skipVersion = true; return true; } } } + const ConfigDescription& config = fileOp->config; + // Find the first SDK level used that is higher than this defined config and // not superseded by a lower or equal SDK level resource. const int minSdkVersion = mContext->getMinSdkVersion(); for (int sdkLevel : xmlLinker.getSdkLevels()) { - if (sdkLevel > minSdkVersion - && sdkLevel > outFileOp->xmlToFlatten->file.config.sdkVersion) { - if (!shouldGenerateVersionedResource(entry, outFileOp->xmlToFlatten->file.config, - sdkLevel)) { + if (sdkLevel > minSdkVersion && sdkLevel > config.sdkVersion) { + if (!shouldGenerateVersionedResource(fileOp->entry, config, sdkLevel)) { // If we shouldn't generate a versioned resource, stop checking. break; } - ResourceFile versionedFileDesc = outFileOp->xmlToFlatten->file; + ResourceFile versionedFileDesc = doc->file; versionedFileDesc.config.sdkVersion = (uint16_t) sdkLevel; + FileOperation newFileOp; + newFileOp.xmlToFlatten = util::make_unique<xml::XmlResource>( + versionedFileDesc, doc->root->clone()); + newFileOp.config = versionedFileDesc.config; + newFileOp.entry = fileOp->entry; + newFileOp.dstPath = ResourceUtils::buildResourceFileName( + versionedFileDesc, mContext->getNameMangler()); + if (mContext->verbose()) { mContext->getDiagnostics()->note(DiagMessage(versionedFileDesc.source) << "auto-versioning resource from config '" - << outFileOp->xmlToFlatten->file.config + << config << "' -> '" << versionedFileDesc.config << "'"); } - std::string genPath = ResourceUtils::buildResourceFileName( - versionedFileDesc, mContext->getNameMangler()); - bool added = table->addFileReferenceAllowMangled(versionedFileDesc.name, versionedFileDesc.config, versionedFileDesc.source, - genPath, - file, + newFileOp.dstPath, + nullptr, mContext->getDiagnostics()); if (!added) { return false; } + + outFileOpQueue->push(std::move(newFileOp)); break; } } @@ -458,12 +406,11 @@ bool ResourceFileFlattener::flatten(ResourceTable* table, IArchiveWriter* archiv for (auto& type : pkg->types) { // Sort by config and name, so that we get better locality in the zip file. configSortedFiles.clear(); - for (auto& entry : type->entries) { - // Iterate via indices because auto generated values can be inserted ahead of - // the value being processed. - for (size_t i = 0; i < entry->values.size(); i++) { - ResourceConfigValue* configValue = entry->values[i].get(); + std::queue<FileOperation> fileOperations; + // Populate the queue with all files in the ResourceTable. + for (auto& entry : type->entries) { + for (auto& configValue : entry->values) { FileReference* fileRef = valueCast<FileReference>(configValue->value.get()); if (!fileRef) { continue; @@ -477,33 +424,65 @@ bool ResourceFileFlattener::flatten(ResourceTable* table, IArchiveWriter* archiv } FileOperation fileOp; + fileOp.entry = entry.get(); fileOp.dstPath = *fileRef->path; + fileOp.config = configValue->config; const StringPiece srcPath = file->getSource().path; if (type->type != ResourceType::kRaw && (util::stringEndsWith(srcPath, ".xml.flat") || util::stringEndsWith(srcPath, ".xml"))) { - ResourceFile fileDesc; - fileDesc.config = configValue->config; - fileDesc.name = ResourceName(pkg->name, type->type, entry->name); - fileDesc.source = fileRef->getSource(); - if (!linkAndVersionXmlFile(entry.get(), fileDesc, file, table, &fileOp)) { - error = true; - continue; + std::unique_ptr<io::IData> data = file->openAsData(); + if (!data) { + mContext->getDiagnostics()->error(DiagMessage(file->getSource()) + << "failed to open file"); + return false; + } + + fileOp.xmlToFlatten = xml::inflate(data->data(), data->size(), + mContext->getDiagnostics(), + file->getSource()); + + if (!fileOp.xmlToFlatten) { + return false; } + fileOp.xmlToFlatten->file.config = configValue->config; + fileOp.xmlToFlatten->file.source = fileRef->getSource(); + fileOp.xmlToFlatten->file.name = + ResourceName(pkg->name, type->type, entry->name); + + // Enqueue the XML files to be processed. + fileOperations.push(std::move(fileOp)); } else { fileOp.fileToCopy = file; + + // NOTE(adamlesinski): Explicitly construct a StringPiece here, or else + // we end up copying the string in the std::make_pair() method, then + // creating a StringPiece from the copy, which would cause us to end up + // referencing garbage in the map. + const StringPiece entryName(entry->name); + configSortedFiles[std::make_pair(configValue->config, entryName)] = + std::move(fileOp); } + } + } + + // Now process the XML queue + for (; !fileOperations.empty(); fileOperations.pop()) { + FileOperation& fileOp = fileOperations.front(); - // NOTE(adamlesinski): Explicitly construct a StringPiece16 here, or else - // we end up copying the string in the std::make_pair() method, then creating - // a StringPiece16 from the copy, which would cause us to end up referencing - // garbage in the map. - const StringPiece entryName(entry->name); - configSortedFiles[std::make_pair(configValue->config, entryName)] = - std::move(fileOp); + if (!linkAndVersionXmlFile(table, &fileOp, &fileOperations)) { + error = true; + continue; } + + // NOTE(adamlesinski): Explicitly construct a StringPiece here, or else + // we end up copying the string in the std::make_pair() method, then creating + // a StringPiece from the copy, which would cause us to end up referencing + // garbage in the map. + const StringPiece entryName(fileOp.entry->name); + configSortedFiles[std::make_pair(fileOp.config, entryName)] = std::move(fileOp); } if (error) { @@ -518,10 +497,8 @@ bool ResourceFileFlattener::flatten(ResourceTable* table, IArchiveWriter* archiv if (fileOp.xmlToFlatten) { Maybe<size_t> maxSdkLevel; if (!mOptions.noAutoVersion && !fileOp.skipVersion) { - maxSdkLevel = - std::max<size_t>( - std::max<size_t>(config.sdkVersion, 1u), - mContext->getMinSdkVersion()); + maxSdkLevel = std::max<size_t>(std::max<size_t>(config.sdkVersion, 1u), + mContext->getMinSdkVersion()); } bool result = flattenXml(fileOp.xmlToFlatten.get(), fileOp.dstPath, maxSdkLevel, @@ -866,13 +843,13 @@ public: return false; } - std::unique_ptr<pb::ResourceTable> pbTable = serializeTableToPb(table); - - // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream - // interface. + // Make sure CopyingOutputStreamAdaptor is deleted before we call writer->finishEntry(). { - google::protobuf::io::CopyingOutputStreamAdaptor adaptor(writer); + // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream + // interface. + CopyingOutputStreamAdaptor adaptor(writer); + std::unique_ptr<pb::ResourceTable> pbTable = serializeTableToPb(table); if (!pbTable->SerializeToZeroCopyStream(&adaptor)) { mContext->getDiagnostics()->error(DiagMessage() << "failed to write"); return false; @@ -1109,7 +1086,9 @@ public: bool mergeCompiledFile(io::IFile* file, ResourceFile* fileDesc, bool override) { if (mContext->verbose()) { - mContext->getDiagnostics()->note(DiagMessage() << "merging compiled file " + mContext->getDiagnostics()->note(DiagMessage() + << "merging '" << fileDesc->name + << "' from compiled file " << file->getSource()); } @@ -1230,12 +1209,40 @@ public: return false; } - std::unique_ptr<ResourceFile> resourceFile = loadFileExportHeader( - src, data->data(), data->size(), mContext->getDiagnostics()); - if (resourceFile) { - return mergeCompiledFile(file, resourceFile.get(), override); + CompiledFileInputStream inputStream(data->data(), data->size()); + uint32_t numFiles = 0; + if (!inputStream.ReadLittleEndian32(&numFiles)) { + mContext->getDiagnostics()->error(DiagMessage(src) << "failed read num files"); + return false; } - return false; + + for (uint32_t i = 0; i < numFiles; i++) { + pb::CompiledFile compiledFile; + if (!inputStream.ReadCompiledFile(&compiledFile)) { + mContext->getDiagnostics()->error(DiagMessage(src) + << "failed to read compiled file header"); + return false; + } + + uint64_t offset, len; + if (!inputStream.ReadDataMetaData(&offset, &len)) { + mContext->getDiagnostics()->error(DiagMessage(src) + << "failed to read data meta data"); + return false; + } + + std::unique_ptr<ResourceFile> resourceFile = deserializeCompiledFileFromPb( + compiledFile, file->getSource(), mContext->getDiagnostics()); + if (!resourceFile) { + return false; + } + + if (!mergeCompiledFile(file->createFileSegment(offset, len), resourceFile.get(), + override)) { + return false; + } + } + return true; } // Ignore non .flat files. This could be classes.dex or something else that happens diff --git a/tools/aapt2/process/IResourceTableConsumer.h b/tools/aapt2/process/IResourceTableConsumer.h index 69b7d9230db4..a7e752a42125 100644 --- a/tools/aapt2/process/IResourceTableConsumer.h +++ b/tools/aapt2/process/IResourceTableConsumer.h @@ -51,7 +51,7 @@ struct IResourceTableConsumer { }; namespace xml { -struct XmlResource; +class XmlResource; } struct IXmlResourceConsumer { diff --git a/tools/aapt2/proto/ProtoSerialize.h b/tools/aapt2/proto/ProtoSerialize.h index 6e224ab00af4..cc7c25175933 100644 --- a/tools/aapt2/proto/ProtoSerialize.h +++ b/tools/aapt2/proto/ProtoSerialize.h @@ -28,50 +28,50 @@ namespace aapt { -std::unique_ptr<pb::ResourceTable> serializeTableToPb(ResourceTable* table); -std::unique_ptr<ResourceTable> deserializeTableFromPb(const pb::ResourceTable& pbTable, - const Source& source, - IDiagnostics* diag); - -std::unique_ptr<pb::CompiledFile> serializeCompiledFileToPb(const ResourceFile& file); -std::unique_ptr<ResourceFile> deserializeCompiledFileFromPb(const pb::CompiledFile& pbFile, - const Source& source, - IDiagnostics* diag); - -class CompiledFileOutputStream : public google::protobuf::io::CopyingOutputStream { +class CompiledFileOutputStream { public: - CompiledFileOutputStream(google::protobuf::io::ZeroCopyOutputStream* out, - pb::CompiledFile* pbFile); - bool Write(const void* data, int size) override; - bool Finish(); + explicit CompiledFileOutputStream(google::protobuf::io::ZeroCopyOutputStream* out); + + void WriteLittleEndian32(uint32_t value); + void WriteCompiledFile(const pb::CompiledFile* compiledFile); + void WriteData(const BigBuffer* buffer); + void WriteData(const void* data, size_t len); + bool HadError(); private: - bool ensureFileWritten(); + DISALLOW_COPY_AND_ASSIGN(CompiledFileOutputStream); - google::protobuf::io::CodedOutputStream mOut; - pb::CompiledFile* mPbFile; + void ensureAlignedWrite(); - DISALLOW_COPY_AND_ASSIGN(CompiledFileOutputStream); + google::protobuf::io::CodedOutputStream mOut; }; class CompiledFileInputStream { public: - CompiledFileInputStream(const void* data, size_t size); - - const pb::CompiledFile* CompiledFile(); + explicit CompiledFileInputStream(const void* data, size_t size); - const void* data(); - size_t size(); + bool ReadLittleEndian32(uint32_t* outVal); + bool ReadCompiledFile(pb::CompiledFile* outVal); + bool ReadDataMetaData(uint64_t* outOffset, uint64_t* outLen); private: - google::protobuf::io::CodedInputStream mIn; - std::unique_ptr<pb::CompiledFile> mPbFile; - const uint8_t* mData; - size_t mSize; - DISALLOW_COPY_AND_ASSIGN(CompiledFileInputStream); + + void ensureAlignedRead(); + + google::protobuf::io::CodedInputStream mIn; }; +std::unique_ptr<pb::ResourceTable> serializeTableToPb(ResourceTable* table); +std::unique_ptr<ResourceTable> deserializeTableFromPb(const pb::ResourceTable& pbTable, + const Source& source, + IDiagnostics* diag); + +std::unique_ptr<pb::CompiledFile> serializeCompiledFileToPb(const ResourceFile& file); +std::unique_ptr<ResourceFile> deserializeCompiledFileFromPb(const pb::CompiledFile& pbFile, + const Source& source, + IDiagnostics* diag); + } // namespace aapt #endif /* AAPT_FLATTEN_TABLEPROTOSERIALIZER_H */ diff --git a/tools/aapt2/proto/TableProtoDeserializer.cpp b/tools/aapt2/proto/TableProtoDeserializer.cpp index ca25c6a83dd9..595fa6f29fac 100644 --- a/tools/aapt2/proto/TableProtoDeserializer.cpp +++ b/tools/aapt2/proto/TableProtoDeserializer.cpp @@ -468,52 +468,4 @@ std::unique_ptr<ResourceFile> deserializeCompiledFileFromPb(const pb::CompiledFi return file; } -CompiledFileInputStream::CompiledFileInputStream(const void* data, size_t size) : - mIn(static_cast<const uint8_t*>(data), size), mPbFile(), - mData(static_cast<const uint8_t*>(data)), mSize(size) { -} - -const pb::CompiledFile* CompiledFileInputStream::CompiledFile() { - if (!mPbFile) { - std::unique_ptr<pb::CompiledFile> pbFile = util::make_unique<pb::CompiledFile>(); - uint64_t pbSize = 0u; - if (!mIn.ReadLittleEndian64(&pbSize)) { - return nullptr; - } - mIn.PushLimit(static_cast<int>(pbSize)); - if (!pbFile->ParsePartialFromCodedStream(&mIn)) { - return nullptr; - } - - const size_t padding = 4 - (pbSize & 0x03); - const size_t offset = sizeof(uint64_t) + pbSize + padding; - if (offset > mSize) { - return nullptr; - } - - mData += offset; - mSize -= offset; - mPbFile = std::move(pbFile); - } - return mPbFile.get(); -} - -const void* CompiledFileInputStream::data() { - if (!mPbFile) { - if (!CompiledFile()) { - return nullptr; - } - } - return mData; -} - -size_t CompiledFileInputStream::size() { - if (!mPbFile) { - if (!CompiledFile()) { - return 0; - } - } - return mSize; -} - } // namespace aapt diff --git a/tools/aapt2/proto/TableProtoSerializer.cpp b/tools/aapt2/proto/TableProtoSerializer.cpp index 425fca695a0b..a5c2cbc0277b 100644 --- a/tools/aapt2/proto/TableProtoSerializer.cpp +++ b/tools/aapt2/proto/TableProtoSerializer.cpp @@ -22,6 +22,10 @@ #include "proto/ProtoSerialize.h" #include "util/BigBuffer.h" +using google::protobuf::io::CodedOutputStream; +using google::protobuf::io::CodedInputStream; +using google::protobuf::io::ZeroCopyOutputStream; + namespace aapt { namespace { @@ -210,7 +214,7 @@ std::unique_ptr<pb::ResourceTable> serializeTableToPb(ResourceTable* table) { }); table->stringPool.prune(); - std::unique_ptr<pb::ResourceTable> pbTable = util::make_unique<pb::ResourceTable>(); + auto pbTable = util::make_unique<pb::ResourceTable>(); serializeStringPoolToPb(table->stringPool, pbTable->mutable_string_pool()); StringPool sourcePool, symbolPool; @@ -274,7 +278,7 @@ std::unique_ptr<pb::ResourceTable> serializeTableToPb(ResourceTable* table) { } std::unique_ptr<pb::CompiledFile> serializeCompiledFileToPb(const ResourceFile& file) { - std::unique_ptr<pb::CompiledFile> pbFile = util::make_unique<pb::CompiledFile>(); + auto pbFile = util::make_unique<pb::CompiledFile>(); pbFile->set_resource_name(file.name.toString()); pbFile->set_source_path(file.source.path); serializeConfig(file.config, pbFile->mutable_config()); @@ -287,36 +291,112 @@ std::unique_ptr<pb::CompiledFile> serializeCompiledFileToPb(const ResourceFile& return pbFile; } -CompiledFileOutputStream::CompiledFileOutputStream(google::protobuf::io::ZeroCopyOutputStream* out, - pb::CompiledFile* pbFile) : - mOut(out), mPbFile(pbFile) { +CompiledFileOutputStream::CompiledFileOutputStream(ZeroCopyOutputStream* out) : mOut(out) { } -bool CompiledFileOutputStream::ensureFileWritten() { - if (mPbFile) { - const uint64_t pbSize = mPbFile->ByteSize(); - mOut.WriteLittleEndian64(pbSize); - mPbFile->SerializeWithCachedSizes(&mOut); - const size_t padding = 4 - (pbSize & 0x03); - if (padding > 0) { - uint32_t zero = 0u; - mOut.WriteRaw(&zero, padding); - } - mPbFile = nullptr; +void CompiledFileOutputStream::ensureAlignedWrite() { + const int padding = mOut.ByteCount() % 4; + if (padding > 0) { + uint32_t zero = 0u; + mOut.WriteRaw(&zero, padding); + } +} + +void CompiledFileOutputStream::WriteLittleEndian32(uint32_t val) { + ensureAlignedWrite(); + mOut.WriteLittleEndian32(val); +} + +void CompiledFileOutputStream::WriteCompiledFile(const pb::CompiledFile* compiledFile) { + ensureAlignedWrite(); + mOut.WriteLittleEndian64(static_cast<uint64_t>(compiledFile->ByteSize())); + compiledFile->SerializeWithCachedSizes(&mOut); +} + +void CompiledFileOutputStream::WriteData(const BigBuffer* buffer) { + ensureAlignedWrite(); + mOut.WriteLittleEndian64(static_cast<uint64_t>(buffer->size())); + for (const BigBuffer::Block& block : *buffer) { + mOut.WriteRaw(block.buffer.get(), block.size); + } +} + +void CompiledFileOutputStream::WriteData(const void* data, size_t len) { + ensureAlignedWrite(); + mOut.WriteLittleEndian64(static_cast<uint64_t>(len)); + mOut.WriteRaw(data, len); +} + +bool CompiledFileOutputStream::HadError() { + return mOut.HadError(); +} + +CompiledFileInputStream::CompiledFileInputStream(const void* data, size_t size) : + mIn(static_cast<const uint8_t*>(data), size) { +} + +void CompiledFileInputStream::ensureAlignedRead() { + const int padding = mIn.CurrentPosition() % 4; + if (padding > 0) { + // Reads are always 4 byte aligned. + mIn.Skip(padding); } - return !mOut.HadError(); } -bool CompiledFileOutputStream::Write(const void* data, int size) { - if (!ensureFileWritten()) { +bool CompiledFileInputStream::ReadLittleEndian32(uint32_t* outVal) { + ensureAlignedRead(); + return mIn.ReadLittleEndian32(outVal); +} + +bool CompiledFileInputStream::ReadCompiledFile(pb::CompiledFile* outVal) { + ensureAlignedRead(); + + uint64_t pbSize = 0u; + if (!mIn.ReadLittleEndian64(&pbSize)) { + return false; + } + + CodedInputStream::Limit l = mIn.PushLimit(static_cast<int>(pbSize)); + + // Check that we haven't tried to read past the end. + if (static_cast<uint64_t>(mIn.BytesUntilLimit()) != pbSize) { + mIn.PopLimit(l); + mIn.PushLimit(0); return false; } - mOut.WriteRaw(data, size); - return !mOut.HadError(); + + if (!outVal->ParsePartialFromCodedStream(&mIn)) { + mIn.PopLimit(l); + mIn.PushLimit(0); + return false; + } + + mIn.PopLimit(l); + return true; } -bool CompiledFileOutputStream::Finish() { - return ensureFileWritten(); +bool CompiledFileInputStream::ReadDataMetaData(uint64_t* outOffset, uint64_t* outLen) { + ensureAlignedRead(); + + uint64_t pbSize = 0u; + if (!mIn.ReadLittleEndian64(&pbSize)) { + return false; + } + + // Check that we aren't trying to read past the end. + if (pbSize > static_cast<uint64_t>(mIn.BytesUntilLimit())) { + mIn.PushLimit(0); + return false; + } + + uint64_t offset = static_cast<uint64_t>(mIn.CurrentPosition()); + if (!mIn.Skip(pbSize)) { + return false; + } + + *outOffset = offset; + *outLen = pbSize; + return true; } } // namespace aapt diff --git a/tools/aapt2/proto/TableProtoSerializer_test.cpp b/tools/aapt2/proto/TableProtoSerializer_test.cpp index af1b011a035e..2bd976796f53 100644 --- a/tools/aapt2/proto/TableProtoSerializer_test.cpp +++ b/tools/aapt2/proto/TableProtoSerializer_test.cpp @@ -18,6 +18,8 @@ #include "proto/ProtoSerialize.h" #include "test/Test.h" +using namespace google::protobuf::io; + namespace aapt { TEST(TableProtoSerializer, SerializeSinglePackage) { @@ -115,33 +117,66 @@ TEST(TableProtoSerializer, SerializeFileHeader) { f.source.path = "res/layout-hdpi-v9/main.xml"; f.exportedSymbols.push_back(SourcedResourceName{ test::parseNameOrDie("id/unchecked"), 23u }); - const std::string expectedData = "1234"; - - std::unique_ptr<pb::CompiledFile> pbFile = serializeCompiledFileToPb(f); + const std::string expectedData1 = "123"; + const std::string expectedData2 = "1234"; std::string outputStr; { - google::protobuf::io::StringOutputStream outStream(&outputStr); - CompiledFileOutputStream outFileStream(&outStream, pbFile.get()); - - ASSERT_TRUE(outFileStream.Write(expectedData.data(), expectedData.size())); - ASSERT_TRUE(outFileStream.Finish()); + std::unique_ptr<pb::CompiledFile> pbFile1 = serializeCompiledFileToPb(f); + + f.name.entry = "__" + f.name.entry + "$0"; + std::unique_ptr<pb::CompiledFile> pbFile2 = serializeCompiledFileToPb(f); + + StringOutputStream outStream(&outputStr); + CompiledFileOutputStream outFileStream(&outStream); + outFileStream.WriteLittleEndian32(2); + outFileStream.WriteCompiledFile(pbFile1.get()); + outFileStream.WriteData(expectedData1.data(), expectedData1.size()); + outFileStream.WriteCompiledFile(pbFile2.get()); + outFileStream.WriteData(expectedData2.data(), expectedData2.size()); + ASSERT_FALSE(outFileStream.HadError()); } CompiledFileInputStream inFileStream(outputStr.data(), outputStr.size()); - const pb::CompiledFile* newPbFile = inFileStream.CompiledFile(); - ASSERT_NE(nullptr, newPbFile); + uint32_t numFiles = 0; + ASSERT_TRUE(inFileStream.ReadLittleEndian32(&numFiles)); + ASSERT_EQ(2u, numFiles); - std::unique_ptr<ResourceFile> file = deserializeCompiledFileFromPb(*newPbFile, Source("test"), + // Read the first compiled file. + + pb::CompiledFile newPbFile; + ASSERT_TRUE(inFileStream.ReadCompiledFile(&newPbFile)); + + std::unique_ptr<ResourceFile> file = deserializeCompiledFileFromPb(newPbFile, Source("test"), context->getDiagnostics()); ASSERT_NE(nullptr, file); - std::string actualData((const char*)inFileStream.data(), inFileStream.size()); - EXPECT_EQ(expectedData, actualData); - EXPECT_EQ(0u, reinterpret_cast<uintptr_t>(inFileStream.data()) & 0x03); + uint64_t offset, len; + ASSERT_TRUE(inFileStream.ReadDataMetaData(&offset, &len)); + + std::string actualData(outputStr.data() + offset, len); + EXPECT_EQ(expectedData1, actualData); + + // Expect the data to be aligned. + EXPECT_EQ(0u, offset & 0x03); ASSERT_EQ(1u, file->exportedSymbols.size()); EXPECT_EQ(test::parseNameOrDie("id/unchecked"), file->exportedSymbols[0].name); + + // Read the second compiled file. + + ASSERT_TRUE(inFileStream.ReadCompiledFile(&newPbFile)); + + file = deserializeCompiledFileFromPb(newPbFile, Source("test"), context->getDiagnostics()); + ASSERT_NE(nullptr, file); + + ASSERT_TRUE(inFileStream.ReadDataMetaData(&offset, &len)); + + actualData = std::string(outputStr.data() + offset, len); + EXPECT_EQ(expectedData2, actualData); + + // Expect the data to be aligned. + EXPECT_EQ(0u, offset & 0x03); } TEST(TableProtoSerializer, DeserializeCorruptHeaderSafely) { @@ -152,19 +187,27 @@ TEST(TableProtoSerializer, DeserializeCorruptHeaderSafely) { std::string outputStr; { - google::protobuf::io::StringOutputStream outStream(&outputStr); - CompiledFileOutputStream outFileStream(&outStream, pbFile.get()); - - ASSERT_TRUE(outFileStream.Write(expectedData.data(), expectedData.size())); - ASSERT_TRUE(outFileStream.Finish()); + StringOutputStream outStream(&outputStr); + CompiledFileOutputStream outFileStream(&outStream); + outFileStream.WriteLittleEndian32(1); + outFileStream.WriteCompiledFile(pbFile.get()); + outFileStream.WriteData(expectedData.data(), expectedData.size()); + ASSERT_FALSE(outFileStream.HadError()); } - outputStr[0] = 0xff; + outputStr[4] = 0xff; CompiledFileInputStream inFileStream(outputStr.data(), outputStr.size()); - EXPECT_EQ(nullptr, inFileStream.CompiledFile()); - EXPECT_EQ(nullptr, inFileStream.data()); - EXPECT_EQ(0u, inFileStream.size()); + + uint32_t numFiles = 0; + EXPECT_TRUE(inFileStream.ReadLittleEndian32(&numFiles)); + EXPECT_EQ(1u, numFiles); + + pb::CompiledFile newPbFile; + EXPECT_FALSE(inFileStream.ReadCompiledFile(&newPbFile)); + + uint64_t offset, len; + EXPECT_FALSE(inFileStream.ReadDataMetaData(&offset, &len)); } } // namespace aapt diff --git a/tools/aapt2/xml/XmlDom.cpp b/tools/aapt2/xml/XmlDom.cpp index 39bd5bf44f39..28de78a400f9 100644 --- a/tools/aapt2/xml/XmlDom.cpp +++ b/tools/aapt2/xml/XmlDom.cpp @@ -322,6 +322,21 @@ std::unique_ptr<XmlResource> inflate(const void* data, size_t dataLen, IDiagnost return util::make_unique<XmlResource>(ResourceFile{}, std::move(root)); } +std::unique_ptr<Node> Namespace::clone() { + auto ns = util::make_unique<Namespace>(); + ns->comment = comment; + ns->lineNumber = lineNumber; + ns->columnNumber = columnNumber; + ns->namespacePrefix = namespacePrefix; + ns->namespaceUri = namespaceUri; + + ns->children.reserve(children.size()); + for (const std::unique_ptr<xml::Node>& child : children) { + ns->addChild(child->clone()); + } + return std::move(ns); +} + Element* findRootElement(XmlResource* doc) { return findRootElement(doc->root.get()); } @@ -406,6 +421,36 @@ std::vector<Element*> Element::getChildElements() { return elements; } +std::unique_ptr<Node> Element::clone() { + auto el = util::make_unique<Element>(); + el->comment = comment; + el->lineNumber = lineNumber; + el->columnNumber = columnNumber; + el->name = name; + el->namespaceUri = namespaceUri; + + el->attributes.reserve(attributes.size()); + for (xml::Attribute& attr : attributes) { + // Don't copy compiled values or attributes. + el->attributes.push_back(xml::Attribute{ attr.namespaceUri, attr.name, attr.value }); + } + + el->children.reserve(children.size()); + for (const std::unique_ptr<xml::Node>& child : children) { + el->addChild(child->clone()); + } + return std::move(el); +} + +std::unique_ptr<Node> Text::clone() { + auto t = util::make_unique<Text>(); + t->comment = comment; + t->lineNumber = lineNumber; + t->columnNumber = columnNumber; + t->text = text; + return std::move(t); +} + void PackageAwareVisitor::visit(Namespace* ns) { bool added = false; if (Maybe<ExtractedPackage> maybePackage = extractPackageFromNamespace(ns->namespaceUri)) { diff --git a/tools/aapt2/xml/XmlDom.h b/tools/aapt2/xml/XmlDom.h index d083d82d711e..e4f41b03dbe0 100644 --- a/tools/aapt2/xml/XmlDom.h +++ b/tools/aapt2/xml/XmlDom.h @@ -32,12 +32,13 @@ namespace aapt { namespace xml { -struct RawVisitor; +class RawVisitor; /** * Base class for all XML nodes. */ -struct Node { +class Node { +public: Node* parent = nullptr; size_t lineNumber = 0; size_t columnNumber = 0; @@ -48,6 +49,7 @@ struct Node { void addChild(std::unique_ptr<Node> child); virtual void accept(RawVisitor* visitor) = 0; + virtual std::unique_ptr<Node> clone() = 0; }; /** @@ -55,16 +57,20 @@ struct Node { * subclass of Node. */ template <typename Derived> -struct BaseNode : public Node { +class BaseNode : public Node { +public: virtual void accept(RawVisitor* visitor) override; }; /** * A Namespace XML node. Can only have one child. */ -struct Namespace : public BaseNode<Namespace> { +class Namespace : public BaseNode<Namespace> { +public: std::string namespacePrefix; std::string namespaceUri; + + std::unique_ptr<Node> clone() override; }; struct AaptAttribute { @@ -87,7 +93,8 @@ struct Attribute { /** * An Element XML node. */ -struct Element : public BaseNode<Element> { +class Element : public BaseNode<Element> { +public: std::string namespaceUri; std::string name; std::vector<Attribute> attributes; @@ -99,19 +106,24 @@ struct Element : public BaseNode<Element> { const StringPiece& attrName, const StringPiece& attrValue); std::vector<xml::Element*> getChildElements(); + std::unique_ptr<Node> clone() override; }; /** * A Text (CDATA) XML node. Can not have any children. */ -struct Text : public BaseNode<Text> { +class Text : public BaseNode<Text> { +public: std::string text; + + std::unique_ptr<Node> clone() override; }; /** * An XML resource with a source, name, and XML tree. */ -struct XmlResource { +class XmlResource { +public: ResourceFile file; std::unique_ptr<xml::Node> root; }; @@ -136,7 +148,8 @@ Element* findRootElement(Node* node); * A visitor interface for the different XML Node subtypes. This will not traverse into * children. Use Visitor for that. */ -struct RawVisitor { +class RawVisitor { +public: virtual ~RawVisitor() = default; virtual void visit(Namespace* node) {} @@ -147,7 +160,8 @@ struct RawVisitor { /** * Visitor whose default implementation visits the children nodes of any node. */ -struct Visitor : public RawVisitor { +class Visitor : public RawVisitor { +public: using RawVisitor::visit; void visit(Namespace* node) override { @@ -173,6 +187,13 @@ struct Visitor : public RawVisitor { * An XML DOM visitor that will record the package name for a namespace prefix. */ class PackageAwareVisitor : public Visitor, public IPackageDeclStack { +public: + using Visitor::visit; + + void visit(Namespace* ns) override; + Maybe<ExtractedPackage> transformPackageAlias( + const StringPiece& alias, const StringPiece& localPackage) const override; + private: struct PackageDecl { std::string prefix; @@ -180,13 +201,6 @@ private: }; std::vector<PackageDecl> mPackageDecls; - -public: - using Visitor::visit; - - void visit(Namespace* ns) override; - Maybe<ExtractedPackage> transformPackageAlias( - const StringPiece& alias, const StringPiece& localPackage) const override; }; // Implementations @@ -197,7 +211,8 @@ void BaseNode<Derived>::accept(RawVisitor* visitor) { } template <typename T> -struct NodeCastImpl : public RawVisitor { +class NodeCastImpl : public RawVisitor { +public: using RawVisitor::visit; T* value = nullptr; diff --git a/tools/aapt2/xml/XmlUtil.cpp b/tools/aapt2/xml/XmlUtil.cpp index 0e9d005dd57c..b570fd75b44e 100644 --- a/tools/aapt2/xml/XmlUtil.cpp +++ b/tools/aapt2/xml/XmlUtil.cpp @@ -23,8 +23,8 @@ namespace aapt { namespace xml { -std::string buildPackageNamespace(const StringPiece& package) { - std::string result = kSchemaPublicPrefix; +std::string buildPackageNamespace(const StringPiece& package, bool privateReference) { + std::string result = privateReference ? kSchemaPrivatePrefix : kSchemaPublicPrefix; result.append(package.data(), package.size()); return result; } diff --git a/tools/aapt2/xml/XmlUtil.h b/tools/aapt2/xml/XmlUtil.h index b75d8ac1506a..a6ad79da1a22 100644 --- a/tools/aapt2/xml/XmlUtil.h +++ b/tools/aapt2/xml/XmlUtil.h @@ -30,6 +30,7 @@ constexpr const char* kSchemaPublicPrefix = "http://schemas.android.com/apk/res/ constexpr const char* kSchemaPrivatePrefix = "http://schemas.android.com/apk/prv/res/"; constexpr const char* kSchemaAndroid = "http://schemas.android.com/apk/res/android"; constexpr const char* kSchemaTools = "http://schemas.android.com/tools"; +constexpr const char* kSchemaAapt = "http://schemas.android.com/aapt"; /** * Result of extracting a package name from a namespace URI declaration. @@ -62,8 +63,12 @@ Maybe<ExtractedPackage> extractPackageFromNamespace(const std::string& namespace * Returns an XML Android namespace for the given package of the form: * * http://schemas.android.com/apk/res/<package> + * + * If privateReference == true, the package will be of the form: + * + * http://schemas.android.com/apk/prv/res/<package> */ -std::string buildPackageNamespace(const StringPiece& package); +std::string buildPackageNamespace(const StringPiece& package, bool privateReference=false); /** * Interface representing a stack of XML namespace declarations. When looking up the package |