diff --git a/tools/aapt2/Android.mk b/tools/aapt2/Android.mk index e5c42d5f74c17..275476cb20813 100644 --- a/tools/aapt2/Android.mk +++ b/tools/aapt2/Android.mk @@ -25,62 +25,71 @@ LOCAL_PATH:= $(call my-dir) main := Main.cpp sources := \ - BigBuffer.cpp \ - BinaryResourceParser.cpp \ - BindingXmlPullParser.cpp \ + compile/IdAssigner.cpp \ + compile/Png.cpp \ + compile/XmlIdCollector.cpp \ + flatten/Archive.cpp \ + flatten/TableFlattener.cpp \ + flatten/XmlFlattener.cpp \ + link/AutoVersioner.cpp \ + link/PrivateAttributeMover.cpp \ + link/ReferenceLinker.cpp \ + link/TableMerger.cpp \ + link/XmlReferenceLinker.cpp \ + process/SymbolTable.cpp \ + unflatten/BinaryResourceParser.cpp \ + unflatten/ResChunkPullParser.cpp \ + util/BigBuffer.cpp \ + util/Files.cpp \ + util/Util.cpp \ ConfigDescription.cpp \ Debug.cpp \ - Files.cpp \ - Flag.cpp \ + Flags.cpp \ JavaClassGenerator.cpp \ - Linker.cpp \ Locale.cpp \ - Logger.cpp \ - ManifestMerger.cpp \ - ManifestParser.cpp \ - ManifestValidator.cpp \ - Png.cpp \ ProguardRules.cpp \ - ResChunkPullParser.cpp \ Resource.cpp \ ResourceParser.cpp \ ResourceTable.cpp \ - ResourceTableResolver.cpp \ + ResourceUtils.cpp \ ResourceValues.cpp \ SdkConstants.cpp \ StringPool.cpp \ - TableFlattener.cpp \ - Util.cpp \ - ScopedXmlPullParser.cpp \ - SourceXmlPullParser.cpp \ - XliffXmlPullParser.cpp \ XmlDom.cpp \ - XmlFlattener.cpp \ - ZipEntry.cpp \ - ZipFile.cpp + XmlPullParser.cpp testSources := \ - BigBuffer_test.cpp \ - BindingXmlPullParser_test.cpp \ - Compat_test.cpp \ + compile/IdAssigner_test.cpp \ + compile/XmlIdCollector_test.cpp \ + flatten/FileExportWriter_test.cpp \ + flatten/TableFlattener_test.cpp \ + flatten/XmlFlattener_test.cpp \ + link/AutoVersioner_test.cpp \ + link/PrivateAttributeMover_test.cpp \ + link/ReferenceLinker_test.cpp \ + link/TableMerger_test.cpp \ + link/XmlReferenceLinker_test.cpp \ + process/SymbolTable_test.cpp \ + unflatten/FileExportHeaderReader_test.cpp \ + util/BigBuffer_test.cpp \ + util/Maybe_test.cpp \ + util/StringPiece_test.cpp \ + util/Util_test.cpp \ ConfigDescription_test.cpp \ JavaClassGenerator_test.cpp \ - Linker_test.cpp \ Locale_test.cpp \ - ManifestMerger_test.cpp \ - ManifestParser_test.cpp \ - Maybe_test.cpp \ - NameMangler_test.cpp \ - ResourceParser_test.cpp \ Resource_test.cpp \ + ResourceParser_test.cpp \ ResourceTable_test.cpp \ - ScopedXmlPullParser_test.cpp \ - StringPiece_test.cpp \ + ResourceUtils_test.cpp \ StringPool_test.cpp \ - Util_test.cpp \ - XliffXmlPullParser_test.cpp \ + ValueVisitor_test.cpp \ XmlDom_test.cpp \ - XmlFlattener_test.cpp + XmlPullParser_test.cpp + +toolSources := \ + compile/Compile.cpp \ + link/Link.cpp hostLdLibs := @@ -101,7 +110,7 @@ else endif cFlags := -Wall -Werror -Wno-unused-parameter -UNDEBUG -cppFlags := -std=c++11 -Wno-missing-field-initializers -Wno-unused-private-field +cppFlags := -std=c++11 -Wno-missing-field-initializers -fno-exceptions # ========================================================== # Build the host static library: libaapt2 @@ -139,7 +148,7 @@ include $(BUILD_HOST_NATIVE_TEST) include $(CLEAR_VARS) LOCAL_MODULE := aapt2 -LOCAL_SRC_FILES := $(main) +LOCAL_SRC_FILES := $(main) $(toolSources) LOCAL_STATIC_LIBRARIES += libaapt2 $(hostStaticLibs) LOCAL_LDLIBS += $(hostLdLibs) diff --git a/tools/aapt2/BindingXmlPullParser.cpp b/tools/aapt2/BindingXmlPullParser.cpp deleted file mode 100644 index 4b7a656deac66..0000000000000 --- a/tools/aapt2/BindingXmlPullParser.cpp +++ /dev/null @@ -1,268 +0,0 @@ -/* - * Copyright (C) 2015 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 "BindingXmlPullParser.h" -#include "Util.h" - -#include -#include -#include -#include - -namespace aapt { - -constexpr const char16_t* kBindingNamespaceUri = u"http://schemas.android.com/apk/binding"; -constexpr const char16_t* kAndroidNamespaceUri = u"http://schemas.android.com/apk/res/android"; -constexpr const char16_t* kVariableTagName = u"variable"; -constexpr const char* kBindingTagPrefix = "android:binding_"; - -BindingXmlPullParser::BindingXmlPullParser(const std::shared_ptr& parser) : - mParser(parser), mOverride(false), mNextTagId(0) { -} - -bool BindingXmlPullParser::readVariableDeclaration() { - VarDecl var; - - const auto endAttrIter = mParser->endAttributes(); - for (auto attrIter = mParser->beginAttributes(); attrIter != endAttrIter; ++attrIter) { - if (!attrIter->namespaceUri.empty()) { - continue; - } - - if (attrIter->name == u"name") { - var.name = util::utf16ToUtf8(attrIter->value); - } else if (attrIter->name == u"type") { - var.type = util::utf16ToUtf8(attrIter->value); - } - } - - XmlPullParser::skipCurrentElement(mParser.get()); - - if (var.name.empty()) { - mLastError = "variable declaration missing name"; - return false; - } - - if (var.type.empty()) { - mLastError = "variable declaration missing type"; - return false; - } - - mVarDecls.push_back(std::move(var)); - return true; -} - -bool BindingXmlPullParser::readExpressions() { - mOverride = true; - std::vector expressions; - std::string idValue; - - const auto endAttrIter = mParser->endAttributes(); - for (auto attr = mParser->beginAttributes(); attr != endAttrIter; ++attr) { - if (attr->namespaceUri == kAndroidNamespaceUri && attr->name == u"id") { - idValue = util::utf16ToUtf8(attr->value); - } else { - StringPiece16 value = util::trimWhitespace(attr->value); - if (util::stringStartsWith(value, u"@{") && - util::stringEndsWith(value, u"}")) { - // This is attribute's value is an expression of the form - // @{expression}. We need to capture the expression inside. - expressions.push_back(XmlPullParser::Attribute{ - attr->namespaceUri, - attr->name, - value.substr(2, value.size() - 3).toString() - }); - } else { - // This is a normal attribute, use as is. - mAttributes.emplace_back(*attr); - } - } - } - - // Check if we have any expressions. - if (!expressions.empty()) { - // We have expressions, so let's assign the target a tag number - // and add it to our targets list. - int32_t targetId = mNextTagId++; - mTargets.push_back(Target{ - util::utf16ToUtf8(mParser->getElementName()), - idValue, - targetId, - std::move(expressions) - }); - - std::stringstream numGen; - numGen << kBindingTagPrefix << targetId; - mAttributes.push_back(XmlPullParser::Attribute{ - std::u16string(kAndroidNamespaceUri), - std::u16string(u"tag"), - util::utf8ToUtf16(numGen.str()) - }); - } - return true; -} - -XmlPullParser::Event BindingXmlPullParser::next() { - // Clear old state in preparation for the next event. - mOverride = false; - mAttributes.clear(); - - while (true) { - Event event = mParser->next(); - if (event == Event::kStartElement) { - if (mParser->getElementNamespace().empty() && - mParser->getElementName() == kVariableTagName) { - // This is a variable tag. Record data from it, and - // then discard the entire element. - if (!readVariableDeclaration()) { - // mLastError is set, so getEvent will return kBadDocument. - return getEvent(); - } - continue; - } else { - // Check for expressions of the form @{} in attribute text. - const auto endAttrIter = mParser->endAttributes(); - for (auto attr = mParser->beginAttributes(); attr != endAttrIter; ++attr) { - StringPiece16 value = util::trimWhitespace(attr->value); - if (util::stringStartsWith(value, u"@{") && - util::stringEndsWith(value, u"}")) { - if (!readExpressions()) { - return getEvent(); - } - break; - } - } - } - } else if (event == Event::kStartNamespace || event == Event::kEndNamespace) { - if (mParser->getNamespaceUri() == kBindingNamespaceUri) { - // Skip binding namespace tags. - continue; - } - } - return event; - } - return Event::kBadDocument; -} - -bool BindingXmlPullParser::writeToFile(std::ostream& out) const { - out << "\n"; - out << "\n"; - - // Write the variables. - out << " \n"; - for (const VarDecl& v : mVarDecls) { - out << " \n"; - } - out << " \n"; - - // Write the imports. - - std::stringstream tagGen; - - // Write the targets. - out << " \n"; - for (const Target& t : mTargets) { - tagGen.str({}); - tagGen << kBindingTagPrefix << t.tagId; - out << " \n"; - out << " \n"; - for (const XmlPullParser::Attribute& a : t.expressions) { - out << " \n"; - } - out << " \n"; - out << " \n"; - } - out << " \n"; - - out << "\n"; - return bool(out); -} - -XmlPullParser::const_iterator BindingXmlPullParser::beginAttributes() const { - if (mOverride) { - return mAttributes.begin(); - } - return mParser->beginAttributes(); -} - -XmlPullParser::const_iterator BindingXmlPullParser::endAttributes() const { - if (mOverride) { - return mAttributes.end(); - } - return mParser->endAttributes(); -} - -size_t BindingXmlPullParser::getAttributeCount() const { - if (mOverride) { - return mAttributes.size(); - } - return mParser->getAttributeCount(); -} - -XmlPullParser::Event BindingXmlPullParser::getEvent() const { - if (!mLastError.empty()) { - return Event::kBadDocument; - } - return mParser->getEvent(); -} - -const std::string& BindingXmlPullParser::getLastError() const { - if (!mLastError.empty()) { - return mLastError; - } - return mParser->getLastError(); -} - -const std::u16string& BindingXmlPullParser::getComment() const { - return mParser->getComment(); -} - -size_t BindingXmlPullParser::getLineNumber() const { - return mParser->getLineNumber(); -} - -size_t BindingXmlPullParser::getDepth() const { - return mParser->getDepth(); -} - -const std::u16string& BindingXmlPullParser::getText() const { - return mParser->getText(); -} - -const std::u16string& BindingXmlPullParser::getNamespacePrefix() const { - return mParser->getNamespacePrefix(); -} - -const std::u16string& BindingXmlPullParser::getNamespaceUri() const { - return mParser->getNamespaceUri(); -} - -bool BindingXmlPullParser::applyPackageAlias(std::u16string* package, - const std::u16string& defaultPackage) const { - return mParser->applyPackageAlias(package, defaultPackage); -} - -const std::u16string& BindingXmlPullParser::getElementNamespace() const { - return mParser->getElementNamespace(); -} - -const std::u16string& BindingXmlPullParser::getElementName() const { - return mParser->getElementName(); -} - -} // namespace aapt diff --git a/tools/aapt2/BindingXmlPullParser.h b/tools/aapt2/BindingXmlPullParser.h deleted file mode 100644 index cfb16ef477c9d..0000000000000 --- a/tools/aapt2/BindingXmlPullParser.h +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (C) 2015 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_BINDING_XML_PULL_PARSER_H -#define AAPT_BINDING_XML_PULL_PARSER_H - -#include "XmlPullParser.h" - -#include -#include -#include - -namespace aapt { - -class BindingXmlPullParser : public XmlPullParser { -public: - BindingXmlPullParser(const std::shared_ptr& parser); - BindingXmlPullParser(const BindingXmlPullParser& rhs) = delete; - - Event getEvent() const override; - const std::string& getLastError() const override; - Event next() override; - - const std::u16string& getComment() const override; - size_t getLineNumber() const override; - size_t getDepth() const override; - - const std::u16string& getText() const override; - - const std::u16string& getNamespacePrefix() const override; - const std::u16string& getNamespaceUri() const override; - bool applyPackageAlias(std::u16string* package, const std::u16string& defaultPackage) - const override; - - const std::u16string& getElementNamespace() const override; - const std::u16string& getElementName() const override; - - const_iterator beginAttributes() const override; - const_iterator endAttributes() const override; - size_t getAttributeCount() const override; - - bool writeToFile(std::ostream& out) const; - -private: - struct VarDecl { - std::string name; - std::string type; - }; - - struct Import { - std::string name; - std::string type; - }; - - struct Target { - std::string className; - std::string id; - int32_t tagId; - - std::vector expressions; - }; - - bool readVariableDeclaration(); - bool readExpressions(); - - std::shared_ptr mParser; - std::string mLastError; - bool mOverride; - std::vector mAttributes; - std::vector mVarDecls; - std::vector mTargets; - int32_t mNextTagId; -}; - -} // namespace aapt - -#endif // AAPT_BINDING_XML_PULL_PARSER_H diff --git a/tools/aapt2/BindingXmlPullParser_test.cpp b/tools/aapt2/BindingXmlPullParser_test.cpp deleted file mode 100644 index 28edcb6728402..0000000000000 --- a/tools/aapt2/BindingXmlPullParser_test.cpp +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright (C) 2015 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 "SourceXmlPullParser.h" -#include "BindingXmlPullParser.h" - -#include -#include -#include - -namespace aapt { - -constexpr const char16_t* kAndroidNamespaceUri = u"http://schemas.android.com/apk/res/android"; - -TEST(BindingXmlPullParserTest, SubstituteBindingExpressionsWithTag) { - std::stringstream input; - input << "\n" - << "\n" - << " \n" - << " \n" - << "\n"; - std::shared_ptr sourceParser = std::make_shared(input); - BindingXmlPullParser parser(sourceParser); - - ASSERT_EQ(XmlPullParser::Event::kStartNamespace, parser.next()); - EXPECT_EQ(std::u16string(u"http://schemas.android.com/apk/res/android"), - parser.getNamespaceUri()); - - ASSERT_EQ(XmlPullParser::Event::kStartElement, parser.next()); - EXPECT_EQ(std::u16string(u"LinearLayout"), parser.getElementName()); - - while (parser.next() == XmlPullParser::Event::kText) {} - - ASSERT_EQ(XmlPullParser::Event::kStartElement, parser.getEvent()); - EXPECT_EQ(std::u16string(u"TextView"), parser.getElementName()); - - ASSERT_EQ(3u, parser.getAttributeCount()); - const auto endAttr = parser.endAttributes(); - EXPECT_NE(endAttr, parser.findAttribute(kAndroidNamespaceUri, u"layout_width")); - EXPECT_NE(endAttr, parser.findAttribute(kAndroidNamespaceUri, u"layout_height")); - EXPECT_NE(endAttr, parser.findAttribute(kAndroidNamespaceUri, u"tag")); - - while (parser.next() == XmlPullParser::Event::kText) {} - - ASSERT_EQ(XmlPullParser::Event::kEndElement, parser.getEvent()); - - while (parser.next() == XmlPullParser::Event::kText) {} - - ASSERT_EQ(XmlPullParser::Event::kEndElement, parser.getEvent()); - ASSERT_EQ(XmlPullParser::Event::kEndNamespace, parser.next()); -} - -TEST(BindingXmlPullParserTest, GenerateVariableDeclarations) { - std::stringstream input; - input << "\n" - << "\n" - << " \n" - << "\n"; - std::shared_ptr sourceParser = std::make_shared(input); - BindingXmlPullParser parser(sourceParser); - - while (XmlPullParser::isGoodEvent(parser.next())) { - ASSERT_NE(XmlPullParser::Event::kBadDocument, parser.getEvent()); - } - - std::stringstream output; - ASSERT_TRUE(parser.writeToFile(output)); - - std::string result = output.str(); - EXPECT_NE(std::string::npos, - result.find("")); -} - -TEST(BindingXmlPullParserTest, FailOnMissingNameOrTypeInVariableDeclaration) { - std::stringstream input; - input << "\n" - << "\n" - << " \n" - << "\n"; - std::shared_ptr sourceParser = std::make_shared(input); - BindingXmlPullParser parser(sourceParser); - - while (XmlPullParser::isGoodEvent(parser.next())) {} - - EXPECT_EQ(XmlPullParser::Event::kBadDocument, parser.getEvent()); - EXPECT_FALSE(parser.getLastError().empty()); -} - - -} // namespace aapt diff --git a/tools/aapt2/ConfigDescription.cpp b/tools/aapt2/ConfigDescription.cpp index 6ddf94a681b8c..8120fa709b3c5 100644 --- a/tools/aapt2/ConfigDescription.cpp +++ b/tools/aapt2/ConfigDescription.cpp @@ -17,8 +17,8 @@ #include "ConfigDescription.h" #include "Locale.h" #include "SdkConstants.h" -#include "StringPiece.h" -#include "Util.h" +#include "util/StringPiece.h" +#include "util/Util.h" #include #include diff --git a/tools/aapt2/ConfigDescription.h b/tools/aapt2/ConfigDescription.h index 67b4b75cce0ba..4af089dc282a8 100644 --- a/tools/aapt2/ConfigDescription.h +++ b/tools/aapt2/ConfigDescription.h @@ -17,7 +17,7 @@ #ifndef AAPT_CONFIG_DESCRIPTION_H #define AAPT_CONFIG_DESCRIPTION_H -#include "StringPiece.h" +#include "util/StringPiece.h" #include #include diff --git a/tools/aapt2/ConfigDescription_test.cpp b/tools/aapt2/ConfigDescription_test.cpp index c57e35191a760..83708165a6d75 100644 --- a/tools/aapt2/ConfigDescription_test.cpp +++ b/tools/aapt2/ConfigDescription_test.cpp @@ -15,7 +15,7 @@ */ #include "ConfigDescription.h" -#include "StringPiece.h" +#include "util/StringPiece.h" #include #include diff --git a/tools/aapt2/Debug.cpp b/tools/aapt2/Debug.cpp index cf222c68de550..84f438520e902 100644 --- a/tools/aapt2/Debug.cpp +++ b/tools/aapt2/Debug.cpp @@ -17,7 +17,8 @@ #include "Debug.h" #include "ResourceTable.h" #include "ResourceValues.h" -#include "Util.h" +#include "util/Util.h" +#include "ValueVisitor.h" #include #include @@ -29,102 +30,119 @@ namespace aapt { -struct PrintVisitor : ConstValueVisitor { - void visit(const Attribute& attr, ValueVisitorArgs&) override { +struct PrintVisitor : public ValueVisitor { + using ValueVisitor::visit; + + void visit(Attribute* attr) override { std::cout << "(attr) type="; - attr.printMask(std::cout); + attr->printMask(&std::cout); static constexpr uint32_t kMask = android::ResTable_map::TYPE_ENUM | android::ResTable_map::TYPE_FLAGS; - if (attr.typeMask & kMask) { - for (const auto& symbol : attr.symbols) { - std::cout << "\n " - << symbol.symbol.name.entry << " (" << symbol.symbol.id << ") = " - << symbol.value; + if (attr->typeMask & kMask) { + for (const auto& symbol : attr->symbols) { + std::cout << "\n " << symbol.symbol.name.value().entry; + if (symbol.symbol.id) { + std::cout << " (" << symbol.symbol.id.value() << ")"; + } + std::cout << " = " << symbol.value; } } } - void visit(const Style& style, ValueVisitorArgs&) override { + void visit(Style* style) override { std::cout << "(style)"; - if (style.parent.name.isValid() || style.parent.id.isValid()) { + if (style->parent) { std::cout << " parent="; - if (style.parent.name.isValid()) { - std::cout << style.parent.name << " "; + if (style->parent.value().name) { + std::cout << style->parent.value().name.value() << " "; } - if (style.parent.id.isValid()) { - std::cout << style.parent.id; + if (style->parent.value().id) { + std::cout << style->parent.value().id.value(); } } - for (const auto& entry : style.entries) { + for (const auto& entry : style->entries) { std::cout << "\n "; - if (entry.key.name.isValid()) { - std::cout << entry.key.name.package << ":" << entry.key.name.entry; + if (entry.key.name) { + std::cout << entry.key.name.value().package << ":" << entry.key.name.value().entry; } - if (entry.key.id.isValid()) { - std::cout << "(" << entry.key.id << ")"; + if (entry.key.id) { + std::cout << "(" << entry.key.id.value() << ")"; } std::cout << "=" << *entry.value; } } - void visit(const Array& array, ValueVisitorArgs&) override { - array.print(std::cout); + void visit(Array* array) override { + array->print(&std::cout); } - void visit(const Plural& plural, ValueVisitorArgs&) override { - plural.print(std::cout); + void visit(Plural* plural) override { + plural->print(&std::cout); } - void visit(const Styleable& styleable, ValueVisitorArgs&) override { - styleable.print(std::cout); + void visit(Styleable* styleable) override { + styleable->print(&std::cout); } - void visitItem(const Item& item, ValueVisitorArgs& args) override { - item.print(std::cout); + void visitItem(Item* item) override { + item->print(&std::cout); } }; -void Debug::printTable(const std::shared_ptr& table) { - std::cout << "Package name=" << table->getPackage(); - if (table->getPackageId() != ResourceTable::kUnsetPackageId) { - std::cout << " id=" << std::hex << table->getPackageId() << std::dec; - } - std::cout << std::endl; - - for (const auto& type : *table) { - std::cout << " type " << type->type; - if (type->typeId != ResourceTableType::kUnsetTypeId) { - std::cout << " id=" << std::hex << type->typeId << std::dec; +void Debug::printTable(ResourceTable* table) { + for (auto& package : table->packages) { + std::cout << "Package name=" << package->name; + if (package->id) { + std::cout << " id=" << std::hex << (int) package->id.value() << std::dec; } - std::cout << " entryCount=" << type->entries.size() << std::endl; + std::cout << std::endl; - std::vector sortedEntries; - for (const auto& entry : type->entries) { - auto iter = std::lower_bound(sortedEntries.begin(), sortedEntries.end(), entry.get(), - [](const ResourceEntry* a, const ResourceEntry* b) -> bool { - return a->entryId < b->entryId; - }); - sortedEntries.insert(iter, entry.get()); - } - - for (const ResourceEntry* entry : sortedEntries) { - ResourceId id = { table->getPackageId(), type->typeId, entry->entryId }; - ResourceName name = { table->getPackage(), type->type, entry->name }; - std::cout << " spec resource " << id << " " << name; - if (entry->publicStatus.isPublic) { - std::cout << " PUBLIC"; + for (const auto& type : package->types) { + std::cout << " type " << type->type; + if (type->id) { + std::cout << " id=" << std::hex << (int) type->id.value() << std::dec; } - std::cout << std::endl; + std::cout << " entryCount=" << type->entries.size() << std::endl; - PrintVisitor visitor; - for (const auto& value : entry->values) { - std::cout << " (" << value.config << ") "; - value.value->accept(visitor, {}); + std::vector sortedEntries; + for (const auto& entry : type->entries) { + auto iter = std::lower_bound(sortedEntries.begin(), sortedEntries.end(), entry.get(), + [](const ResourceEntry* a, const ResourceEntry* b) -> bool { + if (a->id && b->id) { + return a->id.value() < b->id.value(); + } else if (a->id) { + return true; + } else { + return false; + } + }); + sortedEntries.insert(iter, entry.get()); + } + + for (const ResourceEntry* entry : sortedEntries) { + ResourceId id = { + package->id ? package->id.value() : uint8_t(0), + type->id ? type->id.value() : uint8_t(0), + entry->id ? entry->id.value() : uint16_t(0) + }; + + ResourceName name = { package->name, type->type, entry->name }; + std::cout << " spec resource " << id << " " << name; + if (entry->publicStatus.isPublic) { + std::cout << " PUBLIC"; + } std::cout << std::endl; + + PrintVisitor visitor; + for (const auto& value : entry->values) { + std::cout << " (" << value.config << ") "; + value.value->accept(&visitor); + std::cout << std::endl; + } } } } @@ -136,8 +154,7 @@ static size_t getNodeIndex(const std::vector& names, const Resourc return std::distance(names.begin(), iter); } -void Debug::printStyleGraph(const std::shared_ptr& table, - const ResourceName& targetStyle) { +void Debug::printStyleGraph(ResourceTable* table, const ResourceName& targetStyle) { std::map> graph; std::queue stylesToVisit; @@ -150,17 +167,16 @@ void Debug::printStyleGraph(const std::shared_ptr& table, continue; } - const ResourceTableType* type; - const ResourceEntry* entry; - std::tie(type, entry) = table->findResource(styleName); - if (entry) { + Maybe result = table->findResource(styleName); + if (result) { + ResourceEntry* entry = result.value().entry; for (const auto& value : entry->values) { - visitFunc"; ASSERT_TRUE(testParse(input)); - const Style* style = findResource"; ASSERT_TRUE(testParse(input)); - const Style* style = findResource @string/wow diff --git a/tools/aapt2/data/res/values/test.xml b/tools/aapt2/data/res/values/test.xml index d3ead34d043cc..d7ab1c8ddde99 100644 --- a/tools/aapt2/data/res/values/test.xml +++ b/tools/aapt2/data/res/values/test.xml @@ -3,7 +3,7 @@ Hey guys! My name is Adam. How are you? @android:string/ok - + diff --git a/tools/aapt2/flatten/Archive.cpp b/tools/aapt2/flatten/Archive.cpp new file mode 100644 index 0000000000000..6db13b86fbfa4 --- /dev/null +++ b/tools/aapt2/flatten/Archive.cpp @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2015 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 "flatten/Archive.h" +#include "util/Files.h" +#include "util/StringPiece.h" + +#include +#include +#include +#include +#include + +namespace aapt { + +namespace { + +struct DirectoryWriter : public IArchiveWriter { + std::string mOutDir; + std::vector> mEntries; + + explicit DirectoryWriter(const StringPiece& outDir) : mOutDir(outDir.toString()) { + } + + ArchiveEntry* writeEntry(const StringPiece& path, uint32_t flags, + const BigBuffer& buffer) override { + std::string fullPath = mOutDir; + file::appendPath(&fullPath, path); + file::mkdirs(file::getStem(fullPath)); + + std::ofstream fout(fullPath, std::ofstream::binary); + if (!fout) { + return nullptr; + } + + if (!util::writeAll(fout, buffer)) { + return nullptr; + } + + mEntries.push_back(util::make_unique(fullPath, flags, buffer.size())); + return mEntries.back().get(); + } + + ArchiveEntry* writeEntry(const StringPiece& path, uint32_t flags, android::FileMap* fileMap, + size_t offset, size_t len) override { + std::string fullPath = mOutDir; + file::appendPath(&fullPath, path); + file::mkdirs(file::getStem(fullPath)); + + std::ofstream fout(fullPath, std::ofstream::binary); + if (!fout) { + return nullptr; + } + + if (!fout.write((const char*) fileMap->getDataPtr() + offset, len)) { + return nullptr; + } + + mEntries.push_back(util::make_unique(fullPath, flags, len)); + return mEntries.back().get(); + } + + virtual ~DirectoryWriter() { + + } +}; + +struct ZipFileWriter : public IArchiveWriter { + FILE* mFile; + std::unique_ptr mWriter; + std::vector> mEntries; + + explicit ZipFileWriter(const StringPiece& path) { + mFile = fopen(path.data(), "w+b"); + if (mFile) { + mWriter = util::make_unique(mFile); + } + } + + ArchiveEntry* writeEntry(const StringPiece& path, uint32_t flags, + const BigBuffer& buffer) override { + if (!mWriter) { + return nullptr; + } + + size_t zipFlags = 0; + if (flags & ArchiveEntry::kCompress) { + zipFlags |= ZipWriter::kCompress; + } + + if (flags & ArchiveEntry::kAlign) { + zipFlags |= ZipWriter::kAlign32; + } + + int32_t result = mWriter->StartEntry(path.data(), zipFlags); + if (result != 0) { + return nullptr; + } + + for (const BigBuffer::Block& b : buffer) { + result = mWriter->WriteBytes(reinterpret_cast(b.buffer.get()), b.size); + if (result != 0) { + return nullptr; + } + } + + result = mWriter->FinishEntry(); + if (result != 0) { + return nullptr; + } + + mEntries.push_back(util::make_unique(path.toString(), flags, buffer.size())); + return mEntries.back().get(); + } + + ArchiveEntry* writeEntry(const StringPiece& path, uint32_t flags, android::FileMap* fileMap, + size_t offset, size_t len) override { + if (!mWriter) { + return nullptr; + } + + size_t zipFlags = 0; + if (flags & ArchiveEntry::kCompress) { + zipFlags |= ZipWriter::kCompress; + } + + if (flags & ArchiveEntry::kAlign) { + zipFlags |= ZipWriter::kAlign32; + } + + int32_t result = mWriter->StartEntry(path.data(), zipFlags); + if (result != 0) { + return nullptr; + } + + result = mWriter->WriteBytes((const char*) fileMap->getDataPtr() + offset, len); + if (result != 0) { + return nullptr; + } + + result = mWriter->FinishEntry(); + if (result != 0) { + return nullptr; + } + + mEntries.push_back(util::make_unique(path.toString(), flags, len)); + return mEntries.back().get(); + } + + virtual ~ZipFileWriter() { + if (mWriter) { + mWriter->Finish(); + fclose(mFile); + } + } +}; + +} // namespace + +std::unique_ptr createDirectoryArchiveWriter(const StringPiece& path) { + return util::make_unique(path); +} + +std::unique_ptr createZipFileArchiveWriter(const StringPiece& path) { + return util::make_unique(path); +} + +} // namespace aapt diff --git a/tools/aapt2/flatten/Archive.h b/tools/aapt2/flatten/Archive.h new file mode 100644 index 0000000000000..c4ddeb3163c01 --- /dev/null +++ b/tools/aapt2/flatten/Archive.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2015 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_FLATTEN_ARCHIVE_H +#define AAPT_FLATTEN_ARCHIVE_H + +#include "util/BigBuffer.h" +#include "util/Files.h" +#include "util/StringPiece.h" + +#include +#include +#include +#include + +namespace aapt { + +struct ArchiveEntry { + enum : uint32_t { + kCompress = 0x01, + kAlign = 0x02, + }; + + std::string path; + uint32_t flags; + size_t uncompressedSize; +}; + +struct IArchiveWriter { + virtual ~IArchiveWriter() = default; + + virtual ArchiveEntry* writeEntry(const StringPiece& path, uint32_t flags, + const BigBuffer& buffer) = 0; + virtual ArchiveEntry* writeEntry(const StringPiece& path, uint32_t flags, + android::FileMap* fileMap, size_t offset, size_t len) = 0; +}; + +std::unique_ptr createDirectoryArchiveWriter(const StringPiece& path); + +std::unique_ptr createZipFileArchiveWriter(const StringPiece& path); + +} // namespace aapt + +#endif /* AAPT_FLATTEN_ARCHIVE_H */ diff --git a/tools/aapt2/flatten/ChunkWriter.h b/tools/aapt2/flatten/ChunkWriter.h new file mode 100644 index 0000000000000..de1d87a57e6dc --- /dev/null +++ b/tools/aapt2/flatten/ChunkWriter.h @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2015 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_FLATTEN_CHUNKWRITER_H +#define AAPT_FLATTEN_CHUNKWRITER_H + +#include "util/BigBuffer.h" +#include "util/Util.h" + +#include + +namespace aapt { + +class ChunkWriter { +private: + BigBuffer* mBuffer; + size_t mStartSize = 0; + android::ResChunk_header* mHeader = nullptr; + +public: + explicit inline ChunkWriter(BigBuffer* buffer) : mBuffer(buffer) { + } + + ChunkWriter(const ChunkWriter&) = delete; + ChunkWriter& operator=(const ChunkWriter&) = delete; + ChunkWriter(ChunkWriter&&) = default; + ChunkWriter& operator=(ChunkWriter&&) = default; + + template + inline T* startChunk(uint16_t type) { + mStartSize = mBuffer->size(); + T* chunk = mBuffer->nextBlock(); + mHeader = &chunk->header; + mHeader->type = util::hostToDevice16(type); + mHeader->headerSize = util::hostToDevice16(sizeof(T)); + return chunk; + } + + template + inline T* nextBlock(size_t count = 1) { + return mBuffer->nextBlock(count); + } + + inline BigBuffer* getBuffer() { + return mBuffer; + } + + inline android::ResChunk_header* getChunkHeader() { + return mHeader; + } + + inline size_t size() { + return mBuffer->size() - mStartSize; + } + + inline android::ResChunk_header* finish() { + mBuffer->align4(); + mHeader->size = util::hostToDevice32(mBuffer->size() - mStartSize); + return mHeader; + } +}; + +template <> +inline android::ResChunk_header* ChunkWriter::startChunk(uint16_t type) { + mStartSize = mBuffer->size(); + mHeader = mBuffer->nextBlock(); + mHeader->type = util::hostToDevice16(type); + mHeader->headerSize = util::hostToDevice16(sizeof(android::ResChunk_header)); + return mHeader; +} + +} // namespace aapt + +#endif /* AAPT_FLATTEN_CHUNKWRITER_H */ diff --git a/tools/aapt2/flatten/FileExportWriter.h b/tools/aapt2/flatten/FileExportWriter.h new file mode 100644 index 0000000000000..7688fa71246ed --- /dev/null +++ b/tools/aapt2/flatten/FileExportWriter.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2015 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_FLATTEN_FILEEXPORTWRITER_H +#define AAPT_FLATTEN_FILEEXPORTWRITER_H + +#include "StringPool.h" + +#include "flatten/ResourceTypeExtensions.h" +#include "flatten/ChunkWriter.h" +#include "process/IResourceTableConsumer.h" +#include "util/BigBuffer.h" +#include "util/Util.h" + +#include +#include + +namespace aapt { + +static ChunkWriter wrapBufferWithFileExportHeader(BigBuffer* buffer, ResourceFile* res) { + ChunkWriter fileExportWriter(buffer); + FileExport_header* fileExport = fileExportWriter.startChunk( + RES_FILE_EXPORT_TYPE); + + ExportedSymbol* symbolRefs = nullptr; + if (!res->exportedSymbols.empty()) { + symbolRefs = fileExportWriter.nextBlock( + res->exportedSymbols.size()); + } + fileExport->exportedSymbolCount = util::hostToDevice32(res->exportedSymbols.size()); + + StringPool symbolExportPool; + memcpy(fileExport->magic, "AAPT", NELEM(fileExport->magic)); + fileExport->config = res->config; + fileExport->config.swapHtoD(); + fileExport->name.index = util::hostToDevice32(symbolExportPool.makeRef(res->name.toString()) + .getIndex()); + fileExport->source.index = util::hostToDevice32(symbolExportPool.makeRef(util::utf8ToUtf16( + res->source.path)).getIndex()); + + for (const SourcedResourceName& name : res->exportedSymbols) { + symbolRefs->name.index = util::hostToDevice32(symbolExportPool.makeRef(name.name.toString()) + .getIndex()); + symbolRefs->line = util::hostToDevice32(name.line); + symbolRefs++; + } + + StringPool::flattenUtf16(fileExportWriter.getBuffer(), symbolExportPool); + return fileExportWriter; +} + +} // namespace aapt + +#endif /* AAPT_FLATTEN_FILEEXPORTWRITER_H */ diff --git a/tools/aapt2/flatten/FileExportWriter_test.cpp b/tools/aapt2/flatten/FileExportWriter_test.cpp new file mode 100644 index 0000000000000..32fc203c4deef --- /dev/null +++ b/tools/aapt2/flatten/FileExportWriter_test.cpp @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2015 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 "Resource.h" + +#include "flatten/FileExportWriter.h" +#include "util/BigBuffer.h" +#include "util/Util.h" + +#include "test/Common.h" + +#include + +namespace aapt { + +TEST(FileExportWriterTest, FlattenResourceFileDataWithNoExports) { + ResourceFile resFile = { + test::parseNameOrDie(u"@android:layout/main.xml"), + test::parseConfigOrDie("sw600dp-v4"), + Source{ "res/layout/main.xml" }, + }; + + BigBuffer buffer(1024); + ChunkWriter writer = wrapBufferWithFileExportHeader(&buffer, &resFile); + *writer.getBuffer()->nextBlock() = 42u; + writer.finish(); + + std::unique_ptr data = util::copy(buffer); + + // There should be more data (string pool) besides the header and our data. + ASSERT_GT(buffer.size(), sizeof(FileExport_header) + sizeof(uint32_t)); + + // Write at the end of this chunk is our data. + uint32_t* val = (uint32_t*)(data.get() + buffer.size()) - 1; + EXPECT_EQ(*val, 42u); +} + +} // namespace aapt diff --git a/tools/aapt2/ResourceTypeExtensions.h b/tools/aapt2/flatten/ResourceTypeExtensions.h similarity index 75% rename from tools/aapt2/ResourceTypeExtensions.h rename to tools/aapt2/flatten/ResourceTypeExtensions.h index dcbe9233f6b0c..af0afefe1676f 100644 --- a/tools/aapt2/ResourceTypeExtensions.h +++ b/tools/aapt2/flatten/ResourceTypeExtensions.h @@ -30,6 +30,12 @@ namespace aapt { * future collisions. */ enum { + /** + * A chunk that contains an entire file that + * has been compiled. + */ + RES_FILE_EXPORT_TYPE = 0x000c, + RES_TABLE_PUBLIC_TYPE = 0x000d, /** @@ -60,6 +66,48 @@ struct ExtendedTypes { }; }; +/** + * Followed by exportedSymbolCount ExportedSymbol structs, followed by the string pool. + */ +struct FileExport_header { + android::ResChunk_header header; + + /** + * MAGIC value. Must be 'AAPT' (0x41415054) + */ + uint8_t magic[4]; + + /** + * Version of AAPT that built this file. + */ + uint32_t version; + + /** + * The resource name. + */ + android::ResStringPool_ref name; + + /** + * Configuration of this file. + */ + android::ResTable_config config; + + /** + * Original source path of this file. + */ + android::ResStringPool_ref source; + + /** + * Number of symbols exported by this file. + */ + uint32_t exportedSymbolCount; +}; + +struct ExportedSymbol { + android::ResStringPool_ref name; + uint32_t line; +}; + struct Public_header { android::ResChunk_header header; @@ -142,6 +190,16 @@ struct ResTable_entry_source { uint32_t line; }; +/** + * An alternative struct to use instead of ResTable_map_entry. This one is a standard_layout + * struct. + */ +struct ResTable_entry_ext { + android::ResTable_entry entry; + android::ResTable_ref parent; + uint32_t count; +}; + } // namespace aapt #endif // AAPT_RESOURCE_TYPE_EXTENSIONS_H diff --git a/tools/aapt2/flatten/TableFlattener.cpp b/tools/aapt2/flatten/TableFlattener.cpp new file mode 100644 index 0000000000000..427ab18567bdc --- /dev/null +++ b/tools/aapt2/flatten/TableFlattener.cpp @@ -0,0 +1,644 @@ +/* + * Copyright (C) 2015 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 "ResourceTable.h" +#include "ResourceValues.h" +#include "ValueVisitor.h" + +#include "flatten/ChunkWriter.h" +#include "flatten/ResourceTypeExtensions.h" +#include "flatten/TableFlattener.h" +#include "util/BigBuffer.h" + +#include +#include +#include + +using namespace android; + +namespace aapt { + +namespace { + +template +static bool cmpIds(const T* a, const T* b) { + return a->id.value() < b->id.value(); +} + +static void strcpy16_htod(uint16_t* dst, size_t len, const StringPiece16& src) { + if (len == 0) { + return; + } + + size_t i; + const char16_t* srcData = src.data(); + for (i = 0; i < len - 1 && i < src.size(); i++) { + dst[i] = util::hostToDevice16((uint16_t) srcData[i]); + } + dst[i] = 0; +} + +struct FlatEntry { + ResourceEntry* entry; + Value* value; + uint32_t entryKey; + uint32_t sourcePathKey; + uint32_t sourceLine; +}; + +struct SymbolWriter { + struct Entry { + StringPool::Ref name; + size_t offset; + }; + + StringPool pool; + std::vector symbols; + + void addSymbol(const ResourceNameRef& name, size_t offset) { + symbols.push_back(Entry{ pool.makeRef(name.package.toString() + u":" + + toString(name.type).toString() + u"/" + + name.entry.toString()), offset }); + } +}; + +struct MapFlattenVisitor : public RawValueVisitor { + using RawValueVisitor::visit; + + SymbolWriter* mSymbols; + FlatEntry* mEntry; + BigBuffer* mBuffer; + size_t mEntryCount = 0; + Maybe mParentIdent; + Maybe mParentName; + + MapFlattenVisitor(SymbolWriter* symbols, FlatEntry* entry, BigBuffer* buffer) : + mSymbols(symbols), mEntry(entry), mBuffer(buffer) { + } + + void flattenKey(Reference* key, ResTable_map* outEntry) { + if (!key->id) { + assert(key->name && "reference must have a name"); + + outEntry->name.ident = util::hostToDevice32(0); + mSymbols->addSymbol(key->name.value(), (mBuffer->size() - sizeof(ResTable_map)) + + offsetof(ResTable_map, name)); + } else { + outEntry->name.ident = util::hostToDevice32(key->id.value().id); + } + } + + void flattenValue(Item* value, ResTable_map* outEntry) { + if (Reference* ref = valueCast(value)) { + if (!ref->id) { + assert(ref->name && "reference must have a name"); + + mSymbols->addSymbol(ref->name.value(), (mBuffer->size() - sizeof(ResTable_map)) + + offsetof(ResTable_map, value) + offsetof(Res_value, data)); + } + } + + bool result = value->flatten(&outEntry->value); + assert(result && "flatten failed"); + } + + void flattenEntry(Reference* key, Item* value) { + ResTable_map* outEntry = mBuffer->nextBlock(); + flattenKey(key, outEntry); + flattenValue(value, outEntry); + outEntry->value.size = util::hostToDevice16(sizeof(outEntry->value)); + mEntryCount++; + } + + void visit(Attribute* attr) override { + { + Reference key(ResourceId{ ResTable_map::ATTR_TYPE }); + BinaryPrimitive val(Res_value::TYPE_INT_DEC, attr->typeMask); + flattenEntry(&key, &val); + } + + for (Attribute::Symbol& s : attr->symbols) { + BinaryPrimitive val(Res_value::TYPE_INT_DEC, s.value); + flattenEntry(&s.symbol, &val); + } + } + + static bool cmpStyleEntries(const Style::Entry& a, const Style::Entry& b) { + if (a.key.id) { + if (b.key.id) { + return a.key.id.value() < b.key.id.value(); + } + return true; + } else if (!b.key.id) { + return a.key.name.value() < b.key.name.value(); + } + return false; + } + + void visit(Style* style) override { + if (style->parent) { + if (!style->parent.value().id) { + assert(style->parent.value().name && "reference must have a name"); + mParentName = style->parent.value().name; + } else { + mParentIdent = style->parent.value().id.value().id; + } + } + + // Sort the style. + std::sort(style->entries.begin(), style->entries.end(), cmpStyleEntries); + + for (Style::Entry& entry : style->entries) { + flattenEntry(&entry.key, entry.value.get()); + } + } + + void visit(Styleable* styleable) override { + for (auto& attrRef : styleable->entries) { + BinaryPrimitive val(Res_value{}); + flattenEntry(&attrRef, &val); + } + } + + void visit(Array* array) override { + for (auto& item : array->items) { + ResTable_map* outEntry = mBuffer->nextBlock(); + flattenValue(item.get(), outEntry); + outEntry->value.size = util::hostToDevice16(sizeof(outEntry->value)); + mEntryCount++; + } + } + + void visit(Plural* plural) override { + const size_t count = plural->values.size(); + for (size_t i = 0; i < count; i++) { + if (!plural->values[i]) { + continue; + } + + ResourceId q; + switch (i) { + case Plural::Zero: + q.id = android::ResTable_map::ATTR_ZERO; + break; + + case Plural::One: + q.id = android::ResTable_map::ATTR_ONE; + break; + + case Plural::Two: + q.id = android::ResTable_map::ATTR_TWO; + break; + + case Plural::Few: + q.id = android::ResTable_map::ATTR_FEW; + break; + + case Plural::Many: + q.id = android::ResTable_map::ATTR_MANY; + break; + + case Plural::Other: + q.id = android::ResTable_map::ATTR_OTHER; + break; + + default: + assert(false); + break; + } + + Reference key(q); + flattenEntry(&key, plural->values[i].get()); + } + } +}; + +struct PackageFlattener { + IDiagnostics* mDiag; + TableFlattenerOptions mOptions; + ResourceTable* mTable; + ResourceTablePackage* mPackage; + SymbolWriter mSymbols; + StringPool mTypePool; + StringPool mKeyPool; + StringPool mSourcePool; + + template + T* writeEntry(FlatEntry* entry, BigBuffer* buffer) { + static_assert(std::is_same::value || + std::is_same::value, + "T must be ResTable_entry or ResTable_entry_ext"); + + T* result = buffer->nextBlock(); + ResTable_entry* outEntry = (ResTable_entry*)(result); + if (entry->entry->publicStatus.isPublic) { + outEntry->flags |= ResTable_entry::FLAG_PUBLIC; + } + + if (entry->value->isWeak()) { + outEntry->flags |= ResTable_entry::FLAG_WEAK; + } + + if (!entry->value->isItem()) { + outEntry->flags |= ResTable_entry::FLAG_COMPLEX; + } + + outEntry->key.index = util::hostToDevice32(entry->entryKey); + outEntry->size = sizeof(T); + + if (mOptions.useExtendedChunks) { + // Write the extra source block. This will be ignored by the Android runtime. + ResTable_entry_source* sourceBlock = buffer->nextBlock(); + sourceBlock->pathIndex = util::hostToDevice32(entry->sourcePathKey); + sourceBlock->line = util::hostToDevice32(entry->sourceLine); + outEntry->size += sizeof(*sourceBlock); + } + + outEntry->flags = util::hostToDevice16(outEntry->flags); + outEntry->size = util::hostToDevice16(outEntry->size); + return result; + } + + bool flattenValue(FlatEntry* entry, BigBuffer* buffer) { + if (entry->value->isItem()) { + writeEntry(entry, buffer); + if (Reference* ref = valueCast(entry->value)) { + if (!ref->id) { + assert(ref->name && "reference must have at least a name"); + mSymbols.addSymbol(ref->name.value(), + buffer->size() + offsetof(Res_value, data)); + } + } + Res_value* outValue = buffer->nextBlock(); + bool result = static_cast(entry->value)->flatten(outValue); + assert(result && "flatten failed"); + outValue->size = util::hostToDevice16(sizeof(*outValue)); + } else { + const size_t beforeEntry = buffer->size(); + ResTable_entry_ext* outEntry = writeEntry(entry, buffer); + MapFlattenVisitor visitor(&mSymbols, entry, buffer); + entry->value->accept(&visitor); + outEntry->count = util::hostToDevice32(visitor.mEntryCount); + if (visitor.mParentName) { + mSymbols.addSymbol(visitor.mParentName.value(), + beforeEntry + offsetof(ResTable_entry_ext, parent)); + } else if (visitor.mParentIdent) { + outEntry->parent.ident = util::hostToDevice32(visitor.mParentIdent.value()); + } + } + return true; + } + + bool flattenConfig(const ResourceTableType* type, const ConfigDescription& config, + std::vector* entries, BigBuffer* buffer) { + ChunkWriter typeWriter(buffer); + ResTable_type* typeHeader = typeWriter.startChunk(RES_TABLE_TYPE_TYPE); + typeHeader->id = type->id.value(); + typeHeader->config = config; + typeHeader->config.swapHtoD(); + + auto maxAccum = [](uint32_t max, const std::unique_ptr& a) -> uint32_t { + return std::max(max, (uint32_t) a->id.value()); + }; + + // Find the largest entry ID. That is how many entries we will have. + const uint32_t entryCount = + std::accumulate(type->entries.begin(), type->entries.end(), 0, maxAccum) + 1; + + typeHeader->entryCount = util::hostToDevice32(entryCount); + uint32_t* indices = typeWriter.nextBlock(entryCount); + + assert((size_t) entryCount <= std::numeric_limits::max() + 1); + memset(indices, 0xff, entryCount * sizeof(uint32_t)); + + typeHeader->entriesStart = util::hostToDevice32(typeWriter.size()); + + const size_t entryStart = typeWriter.getBuffer()->size(); + for (FlatEntry& flatEntry : *entries) { + assert(flatEntry.entry->id.value() < entryCount); + indices[flatEntry.entry->id.value()] = util::hostToDevice32( + typeWriter.getBuffer()->size() - entryStart); + if (!flattenValue(&flatEntry, typeWriter.getBuffer())) { + mDiag->error(DiagMessage() + << "failed to flatten resource '" + << ResourceNameRef(mPackage->name, type->type, flatEntry.entry->name) + << "' for configuration '" << config << "'"); + return false; + } + } + typeWriter.finish(); + return true; + } + + std::vector collectAndSortTypes() { + std::vector sortedTypes; + for (auto& type : mPackage->types) { + if (type->type == ResourceType::kStyleable && !mOptions.useExtendedChunks) { + // Styleables aren't real Resource Types, they are represented in the R.java + // file. + continue; + } + + assert(type->id && "type must have an ID set"); + + sortedTypes.push_back(type.get()); + } + std::sort(sortedTypes.begin(), sortedTypes.end(), cmpIds); + return sortedTypes; + } + + std::vector collectAndSortEntries(ResourceTableType* type) { + // Sort the entries by entry ID. + std::vector sortedEntries; + for (auto& entry : type->entries) { + assert(entry->id && "entry must have an ID set"); + sortedEntries.push_back(entry.get()); + } + std::sort(sortedEntries.begin(), sortedEntries.end(), cmpIds); + return sortedEntries; + } + + bool flattenTypeSpec(ResourceTableType* type, std::vector* sortedEntries, + BigBuffer* buffer) { + ChunkWriter typeSpecWriter(buffer); + ResTable_typeSpec* specHeader = typeSpecWriter.startChunk( + RES_TABLE_TYPE_SPEC_TYPE); + specHeader->id = type->id.value(); + + if (sortedEntries->empty()) { + typeSpecWriter.finish(); + return true; + } + + // We can't just take the size of the vector. There may be holes in the entry ID space. + // Since the entries are sorted by ID, the last one will be the biggest. + const size_t numEntries = sortedEntries->back()->id.value() + 1; + + specHeader->entryCount = util::hostToDevice32(numEntries); + + // Reserve space for the masks of each resource in this type. These + // show for which configuration axis the resource changes. + uint32_t* configMasks = typeSpecWriter.nextBlock(numEntries); + + const size_t actualNumEntries = sortedEntries->size(); + for (size_t entryIndex = 0; entryIndex < actualNumEntries; entryIndex++) { + ResourceEntry* entry = sortedEntries->at(entryIndex); + + // Populate the config masks for this entry. + + if (entry->publicStatus.isPublic) { + configMasks[entry->id.value()] |= + util::hostToDevice32(ResTable_typeSpec::SPEC_PUBLIC); + } + + const size_t configCount = entry->values.size(); + for (size_t i = 0; i < configCount; i++) { + const ConfigDescription& config = entry->values[i].config; + for (size_t j = i + 1; j < configCount; j++) { + configMasks[entry->id.value()] |= util::hostToDevice32( + config.diff(entry->values[j].config)); + } + } + } + typeSpecWriter.finish(); + return true; + } + + bool flattenPublic(ResourceTableType* type, std::vector* sortedEntries, + BigBuffer* buffer) { + ChunkWriter publicWriter(buffer); + Public_header* publicHeader = publicWriter.startChunk(RES_TABLE_PUBLIC_TYPE); + publicHeader->typeId = type->id.value(); + + for (ResourceEntry* entry : *sortedEntries) { + if (entry->publicStatus.isPublic) { + // Write the public status of this entry. + Public_entry* publicEntry = publicWriter.nextBlock(); + publicEntry->entryId = util::hostToDevice32(entry->id.value()); + publicEntry->key.index = util::hostToDevice32(mKeyPool.makeRef( + entry->name).getIndex()); + publicEntry->source.index = util::hostToDevice32(mSourcePool.makeRef( + util::utf8ToUtf16(entry->publicStatus.source.path)).getIndex()); + if (entry->publicStatus.source.line) { + publicEntry->sourceLine = util::hostToDevice32( + entry->publicStatus.source.line.value()); + } + + // Don't hostToDevice until the last step. + publicHeader->count += 1; + } + } + + publicHeader->count = util::hostToDevice32(publicHeader->count); + publicWriter.finish(); + return true; + } + + bool flattenTypes(BigBuffer* buffer) { + // Sort the types by their IDs. They will be inserted into the StringPool in this order. + std::vector sortedTypes = collectAndSortTypes(); + + size_t expectedTypeId = 1; + for (ResourceTableType* type : sortedTypes) { + // If there is a gap in the type IDs, fill in the StringPool + // with empty values until we reach the ID we expect. + while (type->id.value() > expectedTypeId) { + std::u16string typeName(u"?"); + typeName += expectedTypeId; + mTypePool.makeRef(typeName); + expectedTypeId++; + } + expectedTypeId++; + mTypePool.makeRef(toString(type->type)); + + std::vector sortedEntries = collectAndSortEntries(type); + + if (!flattenTypeSpec(type, &sortedEntries, buffer)) { + return false; + } + + if (mOptions.useExtendedChunks) { + if (!flattenPublic(type, &sortedEntries, buffer)) { + return false; + } + } + + // The binary resource table lists resource entries for each configuration. + // We store them inverted, where a resource entry lists the values for each + // configuration available. Here we reverse this to match the binary table. + std::map> configToEntryListMap; + for (ResourceEntry* entry : sortedEntries) { + const size_t keyIndex = mKeyPool.makeRef(entry->name).getIndex(); + + // Group values by configuration. + for (auto& configValue : entry->values) { + configToEntryListMap[configValue.config].push_back(FlatEntry{ + entry, configValue.value.get(), (uint32_t) keyIndex, + (uint32_t)(mSourcePool.makeRef(util::utf8ToUtf16( + configValue.source.path)).getIndex()), + (uint32_t)(configValue.source.line + ? configValue.source.line.value() : 0) + }); + } + } + + // Flatten a configuration value. + for (auto& entry : configToEntryListMap) { + if (!flattenConfig(type, entry.first, &entry.second, buffer)) { + return false; + } + } + } + return true; + } + + bool flattenPackage(BigBuffer* buffer) { + // We must do this before writing the resources, since the string pool IDs may change. + mTable->stringPool.sort([](const StringPool::Entry& a, const StringPool::Entry& b) -> bool { + int diff = a.context.priority - b.context.priority; + if (diff < 0) return true; + if (diff > 0) return false; + diff = a.context.config.compare(b.context.config); + if (diff < 0) return true; + if (diff > 0) return false; + return a.value < b.value; + }); + mTable->stringPool.prune(); + + const size_t beginningIndex = buffer->size(); + + BigBuffer typeBuffer(1024); + if (!flattenTypes(&typeBuffer)) { + return false; + } + + ChunkWriter tableWriter(buffer); + ResTable_header* tableHeader = tableWriter.startChunk(RES_TABLE_TYPE); + tableHeader->packageCount = util::hostToDevice32(1); + + SymbolTable_entry* symbolEntryData = nullptr; + if (mOptions.useExtendedChunks && !mSymbols.symbols.empty()) { + // Sort the offsets so we can scan them linearly. + std::sort(mSymbols.symbols.begin(), mSymbols.symbols.end(), + [](const SymbolWriter::Entry& a, const SymbolWriter::Entry& b) -> bool { + return a.offset < b.offset; + }); + + ChunkWriter symbolWriter(tableWriter.getBuffer()); + SymbolTable_header* symbolHeader = symbolWriter.startChunk( + RES_TABLE_SYMBOL_TABLE_TYPE); + symbolHeader->count = util::hostToDevice32(mSymbols.symbols.size()); + + symbolEntryData = symbolWriter.nextBlock(mSymbols.symbols.size()); + StringPool::flattenUtf8(symbolWriter.getBuffer(), mSymbols.pool); + symbolWriter.finish(); + } + + if (mOptions.useExtendedChunks && mSourcePool.size() > 0) { + // Write out source pool. + ChunkWriter srcWriter(tableWriter.getBuffer()); + srcWriter.startChunk(RES_TABLE_SOURCE_POOL_TYPE); + StringPool::flattenUtf8(srcWriter.getBuffer(), mSourcePool); + srcWriter.finish(); + } + + StringPool::flattenUtf8(tableWriter.getBuffer(), mTable->stringPool); + + ChunkWriter pkgWriter(tableWriter.getBuffer()); + ResTable_package* pkgHeader = pkgWriter.startChunk( + RES_TABLE_PACKAGE_TYPE); + pkgHeader->id = util::hostToDevice32(mPackage->id.value()); + + if (mPackage->name.size() >= NELEM(pkgHeader->name)) { + mDiag->error(DiagMessage() << + "package name '" << mPackage->name << "' is too long"); + return false; + } + + strcpy16_htod(pkgHeader->name, NELEM(pkgHeader->name), mPackage->name); + + pkgHeader->typeStrings = util::hostToDevice32(pkgWriter.size()); + StringPool::flattenUtf16(pkgWriter.getBuffer(), mTypePool); + + pkgHeader->keyStrings = util::hostToDevice32(pkgWriter.size()); + StringPool::flattenUtf16(pkgWriter.getBuffer(), mKeyPool); + + // Actually write out the symbol entries if we have symbols. + if (symbolEntryData) { + for (auto& entry : mSymbols.symbols) { + symbolEntryData->stringIndex = util::hostToDevice32(entry.name.getIndex()); + + // The symbols were all calculated with the typeBuffer offset. We need to + // add the beginning of the output buffer. + symbolEntryData->offset = util::hostToDevice32( + (pkgWriter.getBuffer()->size() - beginningIndex) + entry.offset); + + symbolEntryData++; + } + } + + // Write out the types and entries. + pkgWriter.getBuffer()->appendBuffer(std::move(typeBuffer)); + + pkgWriter.finish(); + tableWriter.finish(); + return true; + } +}; + +} // namespace + +bool TableFlattener::consume(IAaptContext* context, ResourceTable* table) { + for (auto& package : table->packages) { + // Only support flattening one package. Since the StringPool is shared between packages + // in ResourceTable, we must fail if other packages are present, since their strings + // will be included in the final ResourceTable. + if (context->getCompilationPackage() != package->name) { + context->getDiagnostics()->error(DiagMessage() + << "resources for package '" << package->name + << "' can't be flattened when compiling package '" + << context->getCompilationPackage() << "'"); + return false; + } + + if (!package->id || package->id.value() != context->getPackageId()) { + context->getDiagnostics()->error(DiagMessage() + << "package '" << package->name << "' must have " + << "package id " + << std::hex << context->getPackageId() << std::dec); + return false; + } + + PackageFlattener flattener = { + context->getDiagnostics(), + mOptions, + table, + package.get() + }; + + if (!flattener.flattenPackage(mBuffer)) { + return false; + } + return true; + } + + context->getDiagnostics()->error(DiagMessage() + << "compilation package '" << context->getCompilationPackage() + << "' not found"); + return false; +} + +} // namespace aapt diff --git a/tools/aapt2/flatten/TableFlattener.h b/tools/aapt2/flatten/TableFlattener.h new file mode 100644 index 0000000000000..901b129725eaf --- /dev/null +++ b/tools/aapt2/flatten/TableFlattener.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2015 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_FLATTEN_TABLEFLATTENER_H +#define AAPT_FLATTEN_TABLEFLATTENER_H + +#include "process/IResourceTableConsumer.h" + +namespace aapt { + +class BigBuffer; +class ResourceTable; + +struct TableFlattenerOptions { + /** + * Specifies whether to output extended chunks, like + * source information and missing symbol entries. Default + * is false. + * + * Set this to true when emitting intermediate resource table. + */ + bool useExtendedChunks = false; +}; + +class TableFlattener : public IResourceTableConsumer { +public: + TableFlattener(BigBuffer* buffer, TableFlattenerOptions options) : + mBuffer(buffer), mOptions(options) { + } + + bool consume(IAaptContext* context, ResourceTable* table) override; + +private: + BigBuffer* mBuffer; + TableFlattenerOptions mOptions; +}; + +} // namespace aapt + +#endif /* AAPT_FLATTEN_TABLEFLATTENER_H */ diff --git a/tools/aapt2/flatten/TableFlattener_test.cpp b/tools/aapt2/flatten/TableFlattener_test.cpp new file mode 100644 index 0000000000000..68a1f478c34de --- /dev/null +++ b/tools/aapt2/flatten/TableFlattener_test.cpp @@ -0,0 +1,265 @@ +/* + * Copyright (C) 2015 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 "flatten/TableFlattener.h" +#include "unflatten/BinaryResourceParser.h" +#include "util/Util.h" + +#include "test/Builders.h" +#include "test/Context.h" + +#include + +using namespace android; + +namespace aapt { + +class TableFlattenerTest : public ::testing::Test { +public: + void SetUp() override { + mContext = test::ContextBuilder() + .setCompilationPackage(u"com.app.test") + .setPackageId(0x7f) + .build(); + } + + ::testing::AssertionResult flatten(ResourceTable* table, ResTable* outTable) { + BigBuffer buffer(1024); + TableFlattenerOptions options = {}; + options.useExtendedChunks = true; + TableFlattener flattener(&buffer, options); + if (!flattener.consume(mContext.get(), table)) { + return ::testing::AssertionFailure() << "failed to flatten ResourceTable"; + } + + std::unique_ptr data = util::copy(buffer); + if (outTable->add(data.get(), buffer.size(), -1, true) != NO_ERROR) { + return ::testing::AssertionFailure() << "flattened ResTable is corrupt"; + } + return ::testing::AssertionSuccess(); + } + + ::testing::AssertionResult flatten(ResourceTable* table, ResourceTable* outTable) { + BigBuffer buffer(1024); + TableFlattenerOptions options = {}; + options.useExtendedChunks = true; + TableFlattener flattener(&buffer, options); + if (!flattener.consume(mContext.get(), table)) { + return ::testing::AssertionFailure() << "failed to flatten ResourceTable"; + } + + std::unique_ptr data = util::copy(buffer); + BinaryResourceParser parser(mContext.get(), outTable, {}, data.get(), buffer.size()); + if (!parser.parse()) { + return ::testing::AssertionFailure() << "flattened ResTable is corrupt"; + } + return ::testing::AssertionSuccess(); + } + + ::testing::AssertionResult exists(ResTable* table, + const StringPiece16& expectedName, + const ResourceId expectedId, + const ConfigDescription& expectedConfig, + const uint8_t expectedDataType, const uint32_t expectedData, + const uint32_t expectedSpecFlags) { + const ResourceName expectedResName = test::parseNameOrDie(expectedName); + + table->setParameters(&expectedConfig); + + ResTable_config config; + Res_value val; + uint32_t specFlags; + if (table->getResource(expectedId.id, &val, false, 0, &specFlags, &config) < 0) { + return ::testing::AssertionFailure() << "could not find resource with"; + } + + if (expectedDataType != val.dataType) { + return ::testing::AssertionFailure() + << "expected data type " + << std::hex << (int) expectedDataType << " but got data type " + << (int) val.dataType << std::dec << " instead"; + } + + if (expectedData != val.data) { + return ::testing::AssertionFailure() + << "expected data " + << std::hex << expectedData << " but got data " + << val.data << std::dec << " instead"; + } + + if (expectedSpecFlags != specFlags) { + return ::testing::AssertionFailure() + << "expected specFlags " + << std::hex << expectedSpecFlags << " but got specFlags " + << specFlags << std::dec << " instead"; + } + + ResTable::resource_name actualName; + if (!table->getResourceName(expectedId.id, false, &actualName)) { + return ::testing::AssertionFailure() << "failed to find resource name"; + } + + StringPiece16 package16(actualName.package, actualName.packageLen); + if (package16 != expectedResName.package) { + return ::testing::AssertionFailure() + << "expected package '" << expectedResName.package << "' but got '" + << package16 << "'"; + } + + StringPiece16 type16(actualName.type, actualName.typeLen); + if (type16 != toString(expectedResName.type)) { + return ::testing::AssertionFailure() + << "expected type '" << expectedResName.type + << "' but got '" << type16 << "'"; + } + + StringPiece16 name16(actualName.name, actualName.nameLen); + if (name16 != expectedResName.entry) { + return ::testing::AssertionFailure() + << "expected name '" << expectedResName.entry + << "' but got '" << name16 << "'"; + } + + if (expectedConfig != config) { + return ::testing::AssertionFailure() + << "expected config '" << expectedConfig << "' but got '" + << ConfigDescription(config) << "'"; + } + return ::testing::AssertionSuccess(); + } + +private: + std::unique_ptr mContext; +}; + +TEST_F(TableFlattenerTest, FlattenFullyLinkedTable) { + std::unique_ptr table = test::ResourceTableBuilder() + .setPackageId(u"com.app.test", 0x7f) + .addSimple(u"@com.app.test:id/one", ResourceId(0x7f020000)) + .addSimple(u"@com.app.test:id/two", ResourceId(0x7f020001)) + .addValue(u"@com.app.test:id/three", ResourceId(0x7f020002), + test::buildReference(u"@com.app.test:id/one", ResourceId(0x7f020000))) + .addValue(u"@com.app.test:integer/one", ResourceId(0x7f030000), + util::make_unique(uint8_t(Res_value::TYPE_INT_DEC), 1u)) + .addValue(u"@com.app.test:integer/one", ResourceId(0x7f030000), + test::parseConfigOrDie("v1"), + util::make_unique(uint8_t(Res_value::TYPE_INT_DEC), 2u)) + .addString(u"@com.app.test:string/test", ResourceId(0x7f040000), u"foo") + .addString(u"@com.app.test:layout/bar", ResourceId(0x7f050000), u"res/layout/bar.xml") + .build(); + + ResTable resTable; + ASSERT_TRUE(flatten(table.get(), &resTable)); + + EXPECT_TRUE(exists(&resTable, u"@com.app.test:id/one", ResourceId(0x7f020000), {}, + Res_value::TYPE_INT_BOOLEAN, 0u, 0u)); + + EXPECT_TRUE(exists(&resTable, u"@com.app.test:id/two", ResourceId(0x7f020001), {}, + Res_value::TYPE_INT_BOOLEAN, 0u, 0u)); + + EXPECT_TRUE(exists(&resTable, u"@com.app.test:id/three", ResourceId(0x7f020002), {}, + Res_value::TYPE_REFERENCE, 0x7f020000u, 0u)); + + EXPECT_TRUE(exists(&resTable, u"@com.app.test:integer/one", ResourceId(0x7f030000), + {}, Res_value::TYPE_INT_DEC, 1u, + ResTable_config::CONFIG_VERSION)); + + EXPECT_TRUE(exists(&resTable, u"@com.app.test:integer/one", ResourceId(0x7f030000), + test::parseConfigOrDie("v1"), Res_value::TYPE_INT_DEC, 2u, + ResTable_config::CONFIG_VERSION)); + + StringPiece16 fooStr = u"foo"; + ssize_t idx = resTable.getTableStringBlock(0)->indexOfString(fooStr.data(), fooStr.size()); + ASSERT_GE(idx, 0); + EXPECT_TRUE(exists(&resTable, u"@com.app.test:string/test", ResourceId(0x7f040000), + {}, Res_value::TYPE_STRING, (uint32_t) idx, 0u)); + + StringPiece16 barPath = u"res/layout/bar.xml"; + idx = resTable.getTableStringBlock(0)->indexOfString(barPath.data(), barPath.size()); + ASSERT_GE(idx, 0); + EXPECT_TRUE(exists(&resTable, u"@com.app.test:layout/bar", ResourceId(0x7f050000), {}, + Res_value::TYPE_STRING, (uint32_t) idx, 0u)); +} + +TEST_F(TableFlattenerTest, FlattenEntriesWithGapsInIds) { + std::unique_ptr table = test::ResourceTableBuilder() + .setPackageId(u"com.app.test", 0x7f) + .addSimple(u"@com.app.test:id/one", ResourceId(0x7f020001)) + .addSimple(u"@com.app.test:id/three", ResourceId(0x7f020003)) + .build(); + + ResTable resTable; + ASSERT_TRUE(flatten(table.get(), &resTable)); + + EXPECT_TRUE(exists(&resTable, u"@com.app.test:id/one", ResourceId(0x7f020001), {}, + Res_value::TYPE_INT_BOOLEAN, 0u, 0u)); + EXPECT_TRUE(exists(&resTable, u"@com.app.test:id/three", ResourceId(0x7f020003), {}, + Res_value::TYPE_INT_BOOLEAN, 0u, 0u)); +} + +TEST_F(TableFlattenerTest, FlattenUnlinkedTable) { + std::unique_ptr table = test::ResourceTableBuilder() + .setPackageId(u"com.app.test", 0x7f) + .addValue(u"@com.app.test:integer/one", ResourceId(0x7f020000), + test::buildReference(u"@android:integer/foo")) + .addValue(u"@com.app.test:style/Theme", ResourceId(0x7f030000), test::StyleBuilder() + .setParent(u"@android:style/Theme.Material") + .addItem(u"@android:attr/background", {}) + .addItem(u"@android:attr/colorAccent", + test::buildReference(u"@com.app.test:color/green")) + .build()) + .build(); + + { + // Need access to stringPool to make RawString. + Style* style = test::getValue