diff options
Diffstat (limited to 'tools')
25 files changed, 892 insertions, 492 deletions
diff --git a/tools/aapt2/Android.mk b/tools/aapt2/Android.mk index 5ef4311..23d679d 100644 --- a/tools/aapt2/Android.mk +++ b/tools/aapt2/Android.mk @@ -30,6 +30,7 @@ sources := \ BinaryXmlPullParser.cpp \ BindingXmlPullParser.cpp \ ConfigDescription.cpp \ + Debug.cpp \ Files.cpp \ Flag.cpp \ JavaClassGenerator.cpp \ diff --git a/tools/aapt2/BinaryResourceParser.cpp b/tools/aapt2/BinaryResourceParser.cpp index d16f63b..3559f43 100644 --- a/tools/aapt2/BinaryResourceParser.cpp +++ b/tools/aapt2/BinaryResourceParser.cpp @@ -475,10 +475,17 @@ bool BinaryResourceParser::parsePublic(const ResChunk_header* chunk) { source.line = entry->sourceLine; } - if (!mTable->markPublic(name, resId, source)) { + if (!mTable->markPublicAllowMangled(name, resId, source)) { return false; } + // Add this resource name->id mapping to the index so + // that we can resolve all ID references to name references. + auto cacheIter = mIdIndex.find(resId); + if (cacheIter == mIdIndex.end()) { + mIdIndex.insert({ resId, name }); + } + entry++; } return true; @@ -611,12 +618,12 @@ bool BinaryResourceParser::parseType(const ResChunk_header* chunk) { source.line = sourceBlock->line; } - if (!mTable->addResource(name, config, source, std::move(resourceValue))) { + if (!mTable->addResourceAllowMangled(name, config, source, std::move(resourceValue))) { return false; } if ((entry->flags & ResTable_entry::FLAG_PUBLIC) != 0) { - if (!mTable->markPublic(name, resId, mSource.line(0))) { + if (!mTable->markPublicAllowMangled(name, resId, mSource.line(0))) { return false; } } @@ -635,6 +642,10 @@ std::unique_ptr<Item> BinaryResourceParser::parseValue(const ResourceNameRef& na const ConfigDescription& config, const Res_value* value, uint16_t flags) { + if (name.type == ResourceType::kId) { + return util::make_unique<Id>(); + } + if (value->dataType == Res_value::TYPE_STRING) { StringPiece16 str = util::getString(mValuePool, value->data); @@ -686,8 +697,7 @@ std::unique_ptr<Item> BinaryResourceParser::parseValue(const ResourceNameRef& na // This is not an unresolved symbol, so it must be the magic @null reference. Res_value nullType = {}; - nullType.dataType = Res_value::TYPE_NULL; - nullType.data = Res_value::DATA_NULL_UNDEFINED; + nullType.dataType = Res_value::TYPE_REFERENCE; return util::make_unique<BinaryPrimitive>(nullType); } @@ -697,13 +707,6 @@ std::unique_ptr<Item> BinaryResourceParser::parseValue(const ResourceNameRef& na StringPool::Context{ 1, config })); } - if (name.type == ResourceType::kId || - (value->dataType == Res_value::TYPE_NULL && - value->data == Res_value::DATA_NULL_UNDEFINED && - (flags & ResTable_entry::FLAG_WEAK) != 0)) { - return util::make_unique<Id>(); - } - // Treat this as a raw binary primitive. return util::make_unique<BinaryPrimitive>(*value); } @@ -731,8 +734,7 @@ std::unique_ptr<Value> BinaryResourceParser::parseMapEntry(const ResourceNameRef std::unique_ptr<Style> BinaryResourceParser::parseStyle(const ResourceNameRef& name, const ConfigDescription& config, const ResTable_map_entry* map) { - const bool isWeak = (map->flags & ResTable_entry::FLAG_WEAK) != 0; - std::unique_ptr<Style> style = util::make_unique<Style>(isWeak); + std::unique_ptr<Style> style = util::make_unique<Style>(); if (map->parent.ident == 0) { // The parent is either not set or it is an unresolved symbol. // Check to see if it is a symbol. @@ -789,10 +791,21 @@ std::unique_ptr<Attribute> BinaryResourceParser::parseAttr(const ResourceNameRef continue; } - attr->symbols.push_back(Attribute::Symbol{ - Reference(mapEntry.name.ident), - mapEntry.value.data - }); + Attribute::Symbol symbol; + symbol.value = mapEntry.value.data; + if (mapEntry.name.ident == 0) { + // The map entry's key (id) is not set. This must be + // a symbol reference, so resolve it. + ResourceNameRef symbolName; + bool result = getSymbol(&mapEntry.name.ident, &symbolName); + assert(result); + symbol.symbol.name = symbolName.toResourceName(); + } else { + // The map entry's key (id) is a regular reference. + symbol.symbol.id = mapEntry.name.ident; + } + + attr->symbols.push_back(std::move(symbol)); } } diff --git a/tools/aapt2/Debug.cpp b/tools/aapt2/Debug.cpp new file mode 100644 index 0000000..cf222c6 --- /dev/null +++ b/tools/aapt2/Debug.cpp @@ -0,0 +1,192 @@ +/* + * 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 "Debug.h" +#include "ResourceTable.h" +#include "ResourceValues.h" +#include "Util.h" + +#include <algorithm> +#include <iostream> +#include <map> +#include <memory> +#include <queue> +#include <set> +#include <vector> + +namespace aapt { + +struct PrintVisitor : ConstValueVisitor { + void visit(const Attribute& attr, ValueVisitorArgs&) override { + std::cout << "(attr) type="; + 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; + } + } + } + + void visit(const Style& style, ValueVisitorArgs&) override { + std::cout << "(style)"; + if (style.parent.name.isValid() || style.parent.id.isValid()) { + std::cout << " parent="; + if (style.parent.name.isValid()) { + std::cout << style.parent.name << " "; + } + + if (style.parent.id.isValid()) { + std::cout << style.parent.id; + } + } + + 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.id.isValid()) { + std::cout << "(" << entry.key.id << ")"; + } + + std::cout << "=" << *entry.value; + } + } + + void visit(const Array& array, ValueVisitorArgs&) override { + array.print(std::cout); + } + + void visit(const Plural& plural, ValueVisitorArgs&) override { + plural.print(std::cout); + } + + void visit(const Styleable& styleable, ValueVisitorArgs&) override { + styleable.print(std::cout); + } + + void visitItem(const Item& item, ValueVisitorArgs& args) override { + item.print(std::cout); + } +}; + +void Debug::printTable(const std::shared_ptr<ResourceTable>& 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; + } + std::cout << " entryCount=" << type->entries.size() << std::endl; + + std::vector<const ResourceEntry*> 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"; + } + std::cout << std::endl; + + PrintVisitor visitor; + for (const auto& value : entry->values) { + std::cout << " (" << value.config << ") "; + value.value->accept(visitor, {}); + std::cout << std::endl; + } + } + } +} + +static size_t getNodeIndex(const std::vector<ResourceName>& names, const ResourceName& name) { + auto iter = std::lower_bound(names.begin(), names.end(), name); + assert(iter != names.end() && *iter == name); + return std::distance(names.begin(), iter); +} + +void Debug::printStyleGraph(const std::shared_ptr<ResourceTable>& table, + const ResourceName& targetStyle) { + std::map<ResourceName, std::set<ResourceName>> graph; + + std::queue<ResourceName> stylesToVisit; + stylesToVisit.push(targetStyle); + for (; !stylesToVisit.empty(); stylesToVisit.pop()) { + const ResourceName& styleName = stylesToVisit.front(); + std::set<ResourceName>& parents = graph[styleName]; + if (!parents.empty()) { + // We've already visited this style. + continue; + } + + const ResourceTableType* type; + const ResourceEntry* entry; + std::tie(type, entry) = table->findResource(styleName); + if (entry) { + for (const auto& value : entry->values) { + visitFunc<Style>(*value.value, [&](const Style& style) { + if (style.parent.name.isValid()) { + parents.insert(style.parent.name); + stylesToVisit.push(style.parent.name); + } + }); + } + } + } + + std::vector<ResourceName> names; + for (const auto& entry : graph) { + names.push_back(entry.first); + } + + std::cout << "digraph styles {\n"; + for (const auto& name : names) { + std::cout << " node_" << getNodeIndex(names, name) + << " [label=\"" << name << "\"];\n"; + } + + for (const auto& entry : graph) { + const ResourceName& styleName = entry.first; + size_t styleNodeIndex = getNodeIndex(names, styleName); + + for (const auto& parentName : entry.second) { + std::cout << " node_" << styleNodeIndex << " -> " + << "node_" << getNodeIndex(names, parentName) << ";\n"; + } + } + + std::cout << "}" << std::endl; +} + +} // namespace aapt diff --git a/tools/aapt2/Debug.h b/tools/aapt2/Debug.h new file mode 100644 index 0000000..cdb3dcb --- /dev/null +++ b/tools/aapt2/Debug.h @@ -0,0 +1,35 @@ +/* + * 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_DEBUG_H +#define AAPT_DEBUG_H + +#include "Resource.h" +#include "ResourceTable.h" + +#include <memory> + +namespace aapt { + +struct Debug { + static void printTable(const std::shared_ptr<ResourceTable>& table); + static void printStyleGraph(const std::shared_ptr<ResourceTable>& table, + const ResourceName& targetStyle); +}; + +} // namespace aapt + +#endif // AAPT_DEBUG_H diff --git a/tools/aapt2/Flag.cpp b/tools/aapt2/Flag.cpp index 0f63c2c..76985da 100644 --- a/tools/aapt2/Flag.cpp +++ b/tools/aapt2/Flag.cpp @@ -13,7 +13,7 @@ namespace flag { struct Flag { std::string name; std::string description; - std::function<void(const StringPiece&)> action; + std::function<bool(const StringPiece&, std::string*)> action; bool required; bool* flagResult; bool flagValueWhenSet; @@ -23,22 +23,38 @@ struct Flag { static std::vector<Flag> sFlags; static std::vector<std::string> sArgs; +static std::function<bool(const StringPiece&, std::string*)> wrap( + const std::function<void(const StringPiece&)>& action) { + return [action](const StringPiece& arg, std::string*) -> bool { + action(arg); + return true; + }; +} + void optionalFlag(const StringPiece& name, const StringPiece& description, std::function<void(const StringPiece&)> action) { - sFlags.push_back( - Flag{ name.toString(), description.toString(), action, false, nullptr, false, false }); + sFlags.push_back(Flag{ + name.toString(), description.toString(), wrap(action), + false, nullptr, false, false }); } void requiredFlag(const StringPiece& name, const StringPiece& description, std::function<void(const StringPiece&)> action) { - sFlags.push_back( - Flag{ name.toString(), description.toString(), action, true, nullptr, false, false }); + sFlags.push_back(Flag{ name.toString(), description.toString(), wrap(action), + true, nullptr, false, false }); +} + +void requiredFlag(const StringPiece& name, const StringPiece& description, + std::function<bool(const StringPiece&, std::string*)> action) { + sFlags.push_back(Flag{ name.toString(), description.toString(), action, + true, nullptr, false, false }); } void optionalSwitch(const StringPiece& name, const StringPiece& description, bool resultWhenSet, bool* result) { sFlags.push_back(Flag{ - name.toString(), description.toString(), {}, false, result, resultWhenSet, false }); + name.toString(), description.toString(), {}, + false, result, resultWhenSet, false }); } void usageAndDie(const StringPiece& command) { @@ -62,6 +78,7 @@ void usageAndDie(const StringPiece& command) { } void parse(int argc, char** argv, const StringPiece& command) { + std::string errorStr; for (int i = 0; i < argc; i++) { const StringPiece arg(argv[i]); if (*arg.data() != '-') { @@ -83,7 +100,11 @@ void parse(int argc, char** argv, const StringPiece& command) { << std::endl; usageAndDie(command); } - flag.action(argv[i]); + + if (!flag.action(argv[i], &errorStr)) { + std::cerr << errorStr << "." << std::endl << std::endl; + usageAndDie(command); + } } break; } diff --git a/tools/aapt2/Flag.h b/tools/aapt2/Flag.h index 4745c35..e863742 100644 --- a/tools/aapt2/Flag.h +++ b/tools/aapt2/Flag.h @@ -13,6 +13,9 @@ namespace flag { void requiredFlag(const StringPiece& name, const StringPiece& description, std::function<void(const StringPiece&)> action); +void requiredFlag(const StringPiece& name, const StringPiece& description, + std::function<bool(const StringPiece&, std::string*)> action); + void optionalFlag(const StringPiece& name, const StringPiece& description, std::function<void(const StringPiece&)> action); diff --git a/tools/aapt2/JavaClassGenerator.cpp b/tools/aapt2/JavaClassGenerator.cpp index 2bb0e65..e2ffe79 100644 --- a/tools/aapt2/JavaClassGenerator.cpp +++ b/tools/aapt2/JavaClassGenerator.cpp @@ -81,23 +81,28 @@ static std::u16string transform(const StringPiece16& symbol) { } struct GenArgs : ValueVisitorArgs { - GenArgs(std::ostream* o, std::u16string* e) : out(o), entryName(e) { + GenArgs(std::ostream* o, const std::u16string* p, std::u16string* e) : + out(o), package(p), entryName(e) { } std::ostream* out; + const std::u16string* package; std::u16string* entryName; }; void JavaClassGenerator::visit(const Styleable& styleable, ValueVisitorArgs& a) { const StringPiece finalModifier = mOptions.useFinal ? " final" : ""; std::ostream* out = static_cast<GenArgs&>(a).out; + const std::u16string* package = static_cast<GenArgs&>(a).package; std::u16string* entryName = static_cast<GenArgs&>(a).entryName; // This must be sorted by resource ID. std::vector<std::pair<ResourceId, ResourceNameRef>> sortedAttributes; sortedAttributes.reserve(styleable.entries.size()); for (const auto& attr : styleable.entries) { - assert(attr.id.isValid() && "no ID set for Styleable entry"); + // If we are not encoding final attributes, the styleable entry may have no ID + // if we are building a static library. + assert((!mOptions.useFinal || attr.id.isValid()) && "no ID set for Styleable entry"); assert(attr.name.isValid() && "no name set for Styleable entry"); sortedAttributes.emplace_back(attr.id, attr.name); } @@ -129,7 +134,7 @@ void JavaClassGenerator::visit(const Styleable& styleable, ValueVisitorArgs& a) // We may reference IDs from other packages, so prefix the entry name with // the package. const ResourceNameRef& itemName = sortedAttributes[i].second; - if (itemName.package != mTable->getPackage()) { + if (itemName.package != *package) { *out << "_" << transform(itemName.package); } *out << "_" << transform(itemName.entry) << " = " << i << ";" << std::endl; @@ -172,7 +177,7 @@ bool JavaClassGenerator::generateType(const std::u16string& package, size_t pack if (type.type == ResourceType::kStyleable) { assert(!entry->values.empty()); - entry->values.front().value->accept(*this, GenArgs{ &out, &unmangledName }); + entry->values.front().value->accept(*this, GenArgs{ &out, &package, &unmangledName }); } else { out << " " << "public static" << finalModifier << " int " << transform(unmangledName) << " = " << id << ";" << std::endl; diff --git a/tools/aapt2/JavaClassGenerator_test.cpp b/tools/aapt2/JavaClassGenerator_test.cpp index d4341b6..b385ff4 100644 --- a/tools/aapt2/JavaClassGenerator_test.cpp +++ b/tools/aapt2/JavaClassGenerator_test.cpp @@ -95,8 +95,9 @@ TEST_F(JavaClassGeneratorTest, EmitPackageMangledSymbols) { SourceLine{ "lib.xml", 33 }, util::make_unique<Id>())); ASSERT_TRUE(mTable->merge(std::move(table))); - Linker linker(mTable, std::make_shared<MockResolver>(mTable, - std::map<ResourceName, ResourceId>())); + Linker linker(mTable, + std::make_shared<MockResolver>(mTable, std::map<ResourceName, ResourceId>()), + {}); ASSERT_TRUE(linker.linkAndValidate()); JavaClassGenerator generator(mTable, {}); @@ -130,7 +131,7 @@ TEST_F(JavaClassGeneratorTest, EmitOtherPackagesAttributesInStyleable) { { ResourceName{ u"com.lib", ResourceType::kAttr, u"bar" }, ResourceId{ 0x02, 0x01, 0x0000 } }})); - Linker linker(mTable, resolver); + Linker linker(mTable, resolver, {}); ASSERT_TRUE(linker.linkAndValidate()); JavaClassGenerator generator(mTable, {}); diff --git a/tools/aapt2/Linker.cpp b/tools/aapt2/Linker.cpp index a8b7a14..f3f04a5 100644 --- a/tools/aapt2/Linker.cpp +++ b/tools/aapt2/Linker.cpp @@ -40,8 +40,9 @@ namespace aapt { Linker::Args::Args(const ResourceNameRef& r, const SourceLine& s) : referrer(r), source(s) { } -Linker::Linker(std::shared_ptr<ResourceTable> table, std::shared_ptr<IResolver> resolver) : - mTable(table), mResolver(resolver), mError(false) { +Linker::Linker(const std::shared_ptr<ResourceTable>& table, + const std::shared_ptr<IResolver>& resolver, const Options& options) : + mResolver(resolver), mTable(table), mOptions(options), mError(false) { } bool Linker::linkAndValidate() { @@ -49,7 +50,7 @@ bool Linker::linkAndValidate() { std::array<std::set<uint16_t>, 256> usedIds; usedTypeIds.set(0); - // First build the graph of references. + // Collect which resource IDs are already taken. for (auto& type : *mTable) { if (type->typeId != ResourceTableType::kUnsetTypeId) { // The ID for this type has already been set. We @@ -66,29 +67,10 @@ bool Linker::linkAndValidate() { // later. usedIds[type->typeId].insert(entry->entryId); } - - if (entry->publicStatus.isPublic && entry->values.empty()) { - // A public resource has no values. It will not be encoded - // properly without a symbol table. This is a unresolved symbol. - addUnresolvedSymbol(ResourceNameRef{ - mTable->getPackage(), type->type, entry->name }, - entry->publicStatus.source); - } else { - for (auto& valueConfig : entry->values) { - // Dispatch to the right method of this linker - // based on the value's type. - valueConfig.value->accept(*this, Args{ - ResourceNameRef{ mTable->getPackage(), type->type, entry->name }, - valueConfig.source - }); - } - } } } - /* - * Assign resource IDs that are available. - */ + // Assign resource IDs that are available. size_t nextTypeIndex = 0; for (auto& type : *mTable) { if (type->typeId == ResourceTableType::kUnsetTypeId) { @@ -109,29 +91,32 @@ bool Linker::linkAndValidate() { ++nextEntryIter; } entry->entryId = nextIndex++; + } + } + } - std::u16string unmangledPackage = mTable->getPackage(); - std::u16string unmangledName = entry->name; - NameMangler::unmangle(&unmangledName, &unmangledPackage); + // Now do reference linking. + for (auto& type : *mTable) { + for (auto& entry : type->entries) { + if (entry->publicStatus.isPublic && entry->values.empty()) { + // A public resource has no values. It will not be encoded + // properly without a symbol table. This is a unresolved symbol. + addUnresolvedSymbol(ResourceNameRef{ + mTable->getPackage(), type->type, entry->name }, + entry->publicStatus.source); + continue; + } - // Update callers of this resource with the right ID. - auto callersIter = mGraph.find(ResourceNameRef{ - unmangledPackage, - type->type, - unmangledName + for (auto& valueConfig : entry->values) { + // Dispatch to the right method of this linker + // based on the value's type. + valueConfig.value->accept(*this, Args{ + ResourceNameRef{ mTable->getPackage(), type->type, entry->name }, + valueConfig.source }); - - if (callersIter != std::end(mGraph)) { - for (Node& caller : callersIter->second) { - caller.reference->id = ResourceId(mTable->getPackageId(), - type->typeId, - entry->entryId); - } - } } } } - return !mError; } @@ -139,12 +124,48 @@ const Linker::ResourceNameToSourceMap& Linker::getUnresolvedReferences() const { return mUnresolvedSymbols; } +void Linker::doResolveReference(Reference& reference, const SourceLine& source) { + Maybe<ResourceId> result = mResolver->findId(reference.name); + if (!result) { + addUnresolvedSymbol(reference.name, source); + return; + } + assert(result.value().isValid()); + + if (mOptions.linkResourceIds) { + reference.id = result.value(); + } else { + reference.id = 0; + } +} + +const Attribute* Linker::doResolveAttribute(Reference& attribute, const SourceLine& source) { + Maybe<IResolver::Entry> result = mResolver->findAttribute(attribute.name); + if (!result || !result.value().attr) { + addUnresolvedSymbol(attribute.name, source); + return nullptr; + } + + const IResolver::Entry& entry = result.value(); + assert(entry.id.isValid()); + + if (mOptions.linkResourceIds) { + attribute.id = entry.id; + } else { + attribute.id = 0; + } + return entry.attr; +} + void Linker::visit(Reference& reference, ValueVisitorArgs& a) { Args& args = static_cast<Args&>(a); if (!reference.name.isValid()) { // We can't have a completely bad reference. - assert(reference.id.isValid()); + if (!reference.id.isValid()) { + Logger::error() << "srsly? " << args.referrer << std::endl; + assert(reference.id.isValid()); + } // This reference has no name but has an ID. // It is a really bad error to have no name and have the same @@ -156,25 +177,7 @@ void Linker::visit(Reference& reference, ValueVisitorArgs& a) { return; } - Maybe<ResourceId> result = mResolver->findId(reference.name); - if (!result) { - addUnresolvedSymbol(reference.name, args.source); - return; - } - - const ResourceId& id = result.value(); - if (id.isValid()) { - reference.id = id; - } else { - // We need to update the ID when it is set, so add it - // to the graph. - mGraph[reference.name].push_back(Node{ - args.referrer, - args.source.path, - args.source.line, - &reference - }); - } + doResolveReference(reference, args.source); // TODO(adamlesinski): Verify the referencedType is another reference // or a compatible primitive. @@ -192,7 +195,6 @@ void Linker::processAttributeValue(const ResourceNameRef& name, const SourceLine // We should never get here. All references would have been // parsed in the parser phase. assert(false); - //mTable->addResource(name, ConfigDescription{}, source, util::make_unique<Id>()); }; convertedValue = ResourceParser::parseItemForAttribute(*str.value, attr, @@ -240,25 +242,10 @@ void Linker::visit(Style& style, ValueVisitorArgs& a) { } for (Style::Entry& styleEntry : style.entries) { - Maybe<IResolver::Entry> result = mResolver->findAttribute(styleEntry.key.name); - if (!result || !result.value().attr) { - addUnresolvedSymbol(styleEntry.key.name, args.source); - continue; - } - - const IResolver::Entry& entry = result.value(); - if (entry.id.isValid()) { - styleEntry.key.id = entry.id; - } else { - // Create a dependency for the style on this attribute. - mGraph[styleEntry.key.name].push_back(Node{ - args.referrer, - args.source.path, - args.source.line, - &styleEntry.key - }); + const Attribute* attr = doResolveAttribute(styleEntry.key, args.source); + if (attr) { + processAttributeValue(args.referrer, args.source, *attr, styleEntry.value); } - processAttributeValue(args.referrer, args.source, *entry.attr, styleEntry.value); } } @@ -300,8 +287,4 @@ void Linker::addUnresolvedSymbol(const ResourceNameRef& name, const SourceLine& mUnresolvedSymbols[name.toResourceName()].push_back(source); } -::std::ostream& operator<<(::std::ostream& out, const Linker::Node& node) { - return out << node.name << "(" << node.source << ":" << node.line << ")"; -} - } // namespace aapt diff --git a/tools/aapt2/Linker.h b/tools/aapt2/Linker.h index 9db64ab..6f03515 100644 --- a/tools/aapt2/Linker.h +++ b/tools/aapt2/Linker.h @@ -50,14 +50,25 @@ namespace aapt { */ class Linker : ValueVisitor { public: + struct Options { + /** + * Assign resource Ids to references when linking. + * When building a static library, set this to false. + */ + bool linkResourceIds = true; + }; + /** * Create a Linker for the given resource table with the sources available in * IResolver. IResolver should contain the ResourceTable as a source too. */ - Linker(std::shared_ptr<ResourceTable> table, std::shared_ptr<IResolver> resolver); + Linker(const std::shared_ptr<ResourceTable>& table, + const std::shared_ptr<IResolver>& resolver, const Options& options); Linker(const Linker&) = delete; + virtual ~Linker() = default; + /** * Entry point to the linker. Assigns resource IDs, follows references, * and validates types. Returns true if all references to defined values @@ -73,6 +84,12 @@ public: using ResourceNameToSourceMap = std::map<ResourceName, std::vector<SourceLine>>; const ResourceNameToSourceMap& getUnresolvedReferences() const; +protected: + virtual void doResolveReference(Reference& reference, const SourceLine& source); + virtual const Attribute* doResolveAttribute(Reference& attribute, const SourceLine& source); + + std::shared_ptr<IResolver> mResolver; + private: struct Args : public ValueVisitorArgs { Args(const ResourceNameRef& r, const SourceLine& s); @@ -92,33 +109,13 @@ private: void visit(Plural& plural, ValueVisitorArgs& args) override; void processAttributeValue(const ResourceNameRef& name, const SourceLine& source, - const Attribute& attr, std::unique_ptr<Item>& value); + const Attribute& attr, std::unique_ptr<Item>& value); void addUnresolvedSymbol(const ResourceNameRef& name, const SourceLine& source); - /** - * Node of the resource table graph. - */ - struct Node { - // We use ResourceNameRef and StringPiece, which are safe so long as the ResourceTable - // that defines the data isn't modified. - ResourceNameRef name; - StringPiece source; - size_t line; - - // The reference object that points to name. - Reference* reference; - - bool operator<(const Node& rhs) const; - bool operator==(const Node& rhs) const; - bool operator!=(const Node& rhs) const; - }; - friend ::std::ostream& operator<<(::std::ostream&, const Node&); - std::shared_ptr<ResourceTable> mTable; - std::shared_ptr<IResolver> mResolver; - std::map<ResourceNameRef, std::vector<Node>> mGraph; std::map<ResourceName, std::vector<SourceLine>> mUnresolvedSymbols; + Options mOptions; bool mError; }; diff --git a/tools/aapt2/Linker_test.cpp b/tools/aapt2/Linker_test.cpp index 3c5b8b4..d897f98 100644 --- a/tools/aapt2/Linker_test.cpp +++ b/tools/aapt2/Linker_test.cpp @@ -32,7 +32,8 @@ struct LinkerTest : public ::testing::Test { mTable->setPackage(u"android"); mTable->setPackageId(0x01); mLinker = std::make_shared<Linker>(mTable, std::make_shared<ResourceTableResolver>( - mTable, std::make_shared<android::AssetManager>())); + mTable, std::vector<std::shared_ptr<const android::AssetManager>>()), + Linker::Options{}); // Create a few attributes for use in the tests. @@ -76,7 +77,7 @@ TEST_F(LinkerTest, DoNotInterpretEscapedStringAsReference) { } TEST_F(LinkerTest, EscapeAndConvertRawString) { - std::unique_ptr<Style> style = util::make_unique<Style>(false); + std::unique_ptr<Style> style = util::make_unique<Style>(); style->entries.push_back(Style::Entry{ ResourceNameRef{ u"android", ResourceType::kAttr, u"integer" }, util::make_unique<RawString>(mTable->getValueStringPool().makeRef(u" 123")) @@ -92,7 +93,7 @@ TEST_F(LinkerTest, EscapeAndConvertRawString) { } TEST_F(LinkerTest, FailToConvertRawString) { - std::unique_ptr<Style> style = util::make_unique<Style>(false); + std::unique_ptr<Style> style = util::make_unique<Style>(); style->entries.push_back(Style::Entry{ ResourceNameRef{ u"android", ResourceType::kAttr, u"integer" }, util::make_unique<RawString>(mTable->getValueStringPool().makeRef(u"yo what is up?")) @@ -104,7 +105,7 @@ TEST_F(LinkerTest, FailToConvertRawString) { } TEST_F(LinkerTest, ConvertRawStringToString) { - std::unique_ptr<Style> style = util::make_unique<Style>(false); + std::unique_ptr<Style> style = util::make_unique<Style>(); style->entries.push_back(Style::Entry{ ResourceNameRef{ u"android", ResourceType::kAttr, u"string" }, util::make_unique<RawString>( @@ -123,7 +124,7 @@ TEST_F(LinkerTest, ConvertRawStringToString) { } TEST_F(LinkerTest, ConvertRawStringToFlags) { - std::unique_ptr<Style> style = util::make_unique<Style>(false); + std::unique_ptr<Style> style = util::make_unique<Style>(); style->entries.push_back(Style::Entry{ ResourceNameRef{ u"android", ResourceType::kAttr, u"flags" }, util::make_unique<RawString>(mTable->getValueStringPool().makeRef(u"banana | apple")) diff --git a/tools/aapt2/Main.cpp b/tools/aapt2/Main.cpp index 3377f07..025ede5 100644 --- a/tools/aapt2/Main.cpp +++ b/tools/aapt2/Main.cpp @@ -19,6 +19,7 @@ #include "BinaryResourceParser.h" #include "BinaryXmlPullParser.h" #include "BindingXmlPullParser.h" +#include "Debug.h" #include "Files.h" #include "Flag.h" #include "JavaClassGenerator.h" @@ -55,54 +56,6 @@ constexpr const char* kAaptVersionStr = "2.0-alpha"; using namespace aapt; -void printTable(const ResourceTable& table) { - std::cout << "ResourceTable package=" << table.getPackage(); - if (table.getPackageId() != ResourceTable::kUnsetPackageId) { - std::cout << " id=" << std::hex << table.getPackageId() << std::dec; - } - std::cout << std::endl - << "---------------------------------------------------------" << std::endl; - - for (const auto& type : table) { - std::cout << "Type " << type->type; - if (type->typeId != ResourceTableType::kUnsetTypeId) { - std::cout << " [" << type->typeId << "]"; - } - std::cout << " (" << type->entries.size() << " entries)" << std::endl; - for (const auto& entry : type->entries) { - std::cout << " " << entry->name; - if (entry->entryId != ResourceEntry::kUnsetEntryId) { - std::cout << " [" << entry->entryId << "]"; - } - std::cout << " (" << entry->values.size() << " configurations)"; - if (entry->publicStatus.isPublic) { - std::cout << " PUBLIC"; - } - std::cout << std::endl; - for (const auto& value : entry->values) { - std::cout << " " << value.config << " (" << value.source << ") : "; - value.value->print(std::cout); - std::cout << std::endl; - } - } - } -} - -void printStringPool(const StringPool& pool) { - std::cout << "String pool of length " << pool.size() << std::endl - << "---------------------------------------------------------" << std::endl; - - size_t i = 0; - for (const auto& entry : pool) { - std::cout << "[" << i << "]: " - << entry->value - << " (Priority " << entry->context.priority - << ", Config '" << entry->context.config << "')" - << std::endl; - i++; - } -} - /** * Collect files from 'root', filtering out any files that do not * match the FileFilter 'filter'. @@ -144,29 +97,6 @@ bool walkTree(const Source& root, const FileFilter& filter, return !error; } -bool loadResTable(android::ResTable* table, const Source& source) { - std::ifstream ifs(source.path, std::ifstream::in | std::ifstream::binary); - if (!ifs) { - Logger::error(source) << strerror(errno) << std::endl; - return false; - } - - std::streampos fsize = ifs.tellg(); - ifs.seekg(0, std::ios::end); - fsize = ifs.tellg() - fsize; - ifs.seekg(0, std::ios::beg); - - assert(fsize >= 0); - size_t dataSize = static_cast<size_t>(fsize); - char* buf = new char[dataSize]; - ifs.read(buf, dataSize); - - bool result = table->add(buf, dataSize, -1, true) == android::NO_ERROR; - - delete [] buf; - return result; -} - void versionStylesForCompat(const std::shared_ptr<ResourceTable>& table) { for (auto& type : *table) { if (type->type != ResourceType::kStyle) { @@ -228,7 +158,6 @@ void versionStylesForCompat(const std::shared_ptr<ResourceTable>& table) { }; Style& newStyle = static_cast<Style&>(*value.value); - newStyle.weak = true; // Move the recorded stripped attributes into this new style. std::move(stripped.begin(), stripped.end(), @@ -285,8 +214,6 @@ static BasicStringPiece<TChar> getExtension(const BasicStringPiece<TChar>& str) return str.substr(offset, str.size() - offset); } - - std::string buildFileReference(const ResourceNameRef& name, const ConfigDescription& config, const StringPiece& extension) { std::stringstream path; @@ -321,6 +248,8 @@ struct AaptOptions { enum class Phase { Link, Compile, + Dump, + DumpStyleGraph, }; enum class PackageType { @@ -364,11 +293,14 @@ struct AaptOptions { // referencing attributes defined in a newer SDK // level than the style or layout is defined for. bool versionStylesAndLayouts = true; -}; + // The target style that will have it's style hierarchy dumped + // when the phase is DumpStyleGraph. + ResourceName dumpStyleTarget; +}; bool compileXml(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table, - const CompileItem& item, std::queue<CompileItem>* outQueue, ZipFile* outApk) { + const CompileItem& item, ZipFile* outApk) { std::ifstream in(item.source.path, std::ifstream::binary); if (!in) { Logger::error(item.source) << strerror(errno) << std::endl; @@ -382,6 +314,45 @@ bool compileXml(const AaptOptions& options, const std::shared_ptr<ResourceTable> XmlFlattener::Options xmlOptions; xmlOptions.defaultPackage = table->getPackage(); + xmlOptions.keepRawValues = true; + + std::shared_ptr<XmlPullParser> parser = std::make_shared<SourceXmlPullParser>(in); + + Maybe<size_t> minStrippedSdk = flattener.flatten(item.source, parser, &outBuffer, + xmlOptions); + if (!minStrippedSdk) { + return false; + } + + // Write the resulting compiled XML file to the output APK. + if (outApk->add(outBuffer, buildFileReference(item).data(), ZipEntry::kCompressStored, + nullptr) != android::NO_ERROR) { + Logger::error(options.output) << "failed to write compiled '" << item.source + << "' to apk." << std::endl; + return false; + } + return true; +} + +bool linkXml(const AaptOptions& options, const std::shared_ptr<IResolver>& resolver, + const LinkItem& item, const void* data, size_t dataLen, ZipFile* outApk, + std::queue<LinkItem>* outQueue) { + std::shared_ptr<android::ResXMLTree> tree = std::make_shared<android::ResXMLTree>(); + if (tree->setTo(data, dataLen, false) != android::NO_ERROR) { + return false; + } + + std::shared_ptr<XmlPullParser> parser = std::make_shared<BinaryXmlPullParser>(tree); + + BigBuffer outBuffer(1024); + XmlFlattener flattener({}, resolver); + + XmlFlattener::Options xmlOptions; + xmlOptions.defaultPackage = item.originalPackage; + + if (options.packageType == AaptOptions::PackageType::StaticLibrary) { + xmlOptions.keepRawValues = true; + } if (options.versionStylesAndLayouts) { // We strip attributes that do not belong in this version of the resource. @@ -390,14 +361,14 @@ bool compileXml(const AaptOptions& options, const std::shared_ptr<ResourceTable> } std::shared_ptr<BindingXmlPullParser> binding; - std::shared_ptr<XmlPullParser> parser = std::make_shared<SourceXmlPullParser>(in); if (item.name.type == ResourceType::kLayout) { // Layouts may have defined bindings, so we need to make sure they get processed. binding = std::make_shared<BindingXmlPullParser>(parser); parser = binding; } - Maybe<size_t> minStrippedSdk = flattener.flatten(item.source, parser, &outBuffer, xmlOptions); + Maybe<size_t> minStrippedSdk = flattener.flatten(item.source, parser, &outBuffer, + xmlOptions); if (!minStrippedSdk) { return false; } @@ -405,16 +376,15 @@ bool compileXml(const AaptOptions& options, const std::shared_ptr<ResourceTable> if (minStrippedSdk.value() > 0) { // Something was stripped, so let's generate a new file // with the version of the smallest SDK version stripped. - CompileItem newWork = item; + LinkItem newWork = item; newWork.config.sdkVersion = minStrippedSdk.value(); outQueue->push(newWork); } - // Write the resulting compiled XML file to the output APK. - if (outApk->add(outBuffer, buildFileReference(item).data(), ZipEntry::kCompressStored, + if (outApk->add(outBuffer, buildFileReference(item).data(), ZipEntry::kCompressDeflated, nullptr) != android::NO_ERROR) { - Logger::error(options.output) << "failed to write compiled '" << item.source << "' to apk." - << std::endl; + Logger::error(options.output) << "failed to write linked file '" + << buildFileReference(item) << "' to apk." << std::endl; return false; } @@ -444,33 +414,6 @@ bool compileXml(const AaptOptions& options, const std::shared_ptr<ResourceTable> return true; } -bool linkXml(const AaptOptions& options, const std::shared_ptr<IResolver>& resolver, - const LinkItem& item, const void* data, size_t dataLen, ZipFile* outApk) { - std::shared_ptr<android::ResXMLTree> tree = std::make_shared<android::ResXMLTree>(); - if (tree->setTo(data, dataLen, false) != android::NO_ERROR) { - return false; - } - - std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<BinaryXmlPullParser>(tree); - - BigBuffer outBuffer(1024); - XmlFlattener flattener({}, resolver); - - XmlFlattener::Options xmlOptions; - xmlOptions.defaultPackage = item.originalPackage; - if (!flattener.flatten(item.source, xmlParser, &outBuffer, xmlOptions)) { - return false; - } - - if (outApk->add(outBuffer, buildFileReference(item).data(), ZipEntry::kCompressDeflated, - nullptr) != android::NO_ERROR) { - Logger::error(options.output) << "failed to write linked file '" << item.source - << "' to apk." << std::endl; - return false; - } - return true; -} - bool compilePng(const AaptOptions& options, const CompileItem& item, ZipFile* outApk) { std::ifstream in(item.source.path, std::ifstream::binary); if (!in) { @@ -505,8 +448,8 @@ bool copyFile(const AaptOptions& options, const CompileItem& item, ZipFile* outA return true; } -bool compileManifest(const AaptOptions& options, - const std::shared_ptr<ResourceTableResolver>& resolver, ZipFile* outApk) { +bool compileManifest(const AaptOptions& options, const std::shared_ptr<IResolver>& resolver, + const android::ResTable& table, ZipFile* outApk) { if (options.verbose) { Logger::note(options.manifest) << "compiling AndroidManifest.xml." << std::endl; } @@ -534,7 +477,7 @@ bool compileManifest(const AaptOptions& options, return false; } - ManifestValidator validator(resolver->getResTable()); + ManifestValidator validator(table); if (!validator.validate(options.manifest, &tree)) { return false; } @@ -690,7 +633,7 @@ struct StaticLibraryData { }; bool link(const AaptOptions& options, const std::shared_ptr<ResourceTable>& outTable, - const std::shared_ptr<ResourceTableResolver>& resolver) { + const std::shared_ptr<IResolver>& resolver) { std::map<std::shared_ptr<ResourceTable>, StaticLibraryData> apkFiles; std::unordered_set<std::u16string> linkedPackages; @@ -744,9 +687,18 @@ bool link(const AaptOptions& options, const std::shared_ptr<ResourceTable>& outT } } + // Version all styles referencing attributes outside of their specified SDK version. + if (options.versionStylesAndLayouts) { + versionStylesForCompat(outTable); + } + { // Now that everything is merged, let's link it. - Linker linker(outTable, resolver); + Linker::Options linkerOptions; + if (options.packageType == AaptOptions::PackageType::StaticLibrary) { + linkerOptions.linkResourceIds = false; + } + Linker linker(outTable, resolver, linkerOptions); if (!linker.linkAndValidate()) { return false; } @@ -771,7 +723,8 @@ bool link(const AaptOptions& options, const std::shared_ptr<ResourceTable>& outT return false; } - if (!compileManifest(options, resolver, &outApk)) { + android::ResTable binTable; + if (!compileManifest(options, resolver, binTable, &outApk)) { return false; } @@ -791,7 +744,7 @@ bool link(const AaptOptions& options, const std::shared_ptr<ResourceTable>& outT assert(uncompressedData); if (!linkXml(options, resolver, item, uncompressedData, entry->getUncompressedLen(), - &outApk)) { + &outApk, &linkQueue)) { Logger::error(options.output) << "failed to link '" << item.originalPath << "'." << std::endl; return false; @@ -864,8 +817,8 @@ bool link(const AaptOptions& options, const std::shared_ptr<ResourceTable>& outT // Flatten the resource table. TableFlattener::Options flattenerOptions; - if (options.packageType == AaptOptions::PackageType::StaticLibrary) { - flattenerOptions.useExtendedChunks = true; + if (options.packageType != AaptOptions::PackageType::StaticLibrary) { + flattenerOptions.useExtendedChunks = false; } if (!writeResourceTable(options, outTable, flattenerOptions, &outApk)) { @@ -920,12 +873,6 @@ bool compile(const AaptOptions& options, const std::shared_ptr<ResourceTable>& t if (error) { return false; } - - // Version all styles referencing attributes outside of their specified SDK version. - if (options.versionStylesAndLayouts) { - versionStylesForCompat(table); - } - // Open the output APK file for writing. ZipFile outApk; if (outApk.open(options.output.path.data(), kOpenFlags) != android::NO_ERROR) { @@ -941,7 +888,7 @@ bool compile(const AaptOptions& options, const std::shared_ptr<ResourceTable>& t error |= !addFileReference(table, item); if (item.extension == "xml") { - error |= !compileXml(options, table, item, &compileQueue, &outApk); + error |= !compileXml(options, table, item, &outApk); } else if (item.extension == "png" || item.extension == "9.png") { error |= !compilePng(options, item, &outApk); } else { @@ -954,7 +901,7 @@ bool compile(const AaptOptions& options, const std::shared_ptr<ResourceTable>& t } // Link and assign resource IDs. - Linker linker(table, resolver); + Linker linker(table, resolver, {}); if (!linker.linkAndValidate()) { return false; } @@ -984,6 +931,7 @@ static void printCommandsAndDie() { std::cerr << "The following commands are supported:" << std::endl << std::endl; std::cerr << "compile compiles a subset of resources" << std::endl; std::cerr << "link links together compiled resources and libraries" << std::endl; + std::cerr << "dump dumps resource contents to to standard out" << std::endl; std::cerr << std::endl; std::cerr << "run aapt2 with one of the commands and the -h flag for extra details." << std::endl; @@ -1009,48 +957,67 @@ static AaptOptions prepareArgs(int argc, char** argv) { options.phase = AaptOptions::Phase::Link; } else if (command == "compile") { options.phase = AaptOptions::Phase::Compile; + } else if (command == "dump") { + options.phase = AaptOptions::Phase::Dump; + } else if (command == "dump-style-graph") { + options.phase = AaptOptions::Phase::DumpStyleGraph; } else { std::cerr << "invalid command '" << command << "'." << std::endl << std::endl; printCommandsAndDie(); } bool isStaticLib = false; - if (options.phase == AaptOptions::Phase::Compile) { - flag::requiredFlag("--package", "Android package name", - [&options](const StringPiece& arg) { - options.appInfo.package = util::utf8ToUtf16(arg); - }); - flag::optionalFlag("--binding", "Output directory for binding XML files", - [&options](const StringPiece& arg) { - options.bindingOutput = Source{ arg.toString() }; - }); - flag::optionalSwitch("--no-version", "Disables automatic style and layout versioning", - false, &options.versionStylesAndLayouts); - - } else if (options.phase == AaptOptions::Phase::Link) { - flag::requiredFlag("--manifest", "AndroidManifest.xml of your app", - [&options](const StringPiece& arg) { - options.manifest = Source{ arg.toString() }; - }); - - flag::optionalFlag("-I", "add an Android APK to link against", - [&options](const StringPiece& arg) { - options.libraries.push_back(Source{ arg.toString() }); - }); + if (options.phase == AaptOptions::Phase::Compile || + options.phase == AaptOptions::Phase::Link) { + if (options.phase == AaptOptions::Phase::Compile) { + flag::requiredFlag("--package", "Android package name", + [&options](const StringPiece& arg) { + options.appInfo.package = util::utf8ToUtf16(arg); + }); + } else if (options.phase == AaptOptions::Phase::Link) { + flag::requiredFlag("--manifest", "AndroidManifest.xml of your app", + [&options](const StringPiece& arg) { + options.manifest = Source{ arg.toString() }; + }); + + flag::optionalFlag("-I", "add an Android APK to link against", + [&options](const StringPiece& arg) { + options.libraries.push_back(Source{ arg.toString() }); + }); + + flag::optionalFlag("--java", "directory in which to generate R.java", + [&options](const StringPiece& arg) { + options.generateJavaClass = Source{ arg.toString() }; + }); + + flag::optionalSwitch("--static-lib", "generate a static Android library", true, + &isStaticLib); + + flag::optionalFlag("--binding", "Output directory for binding XML files", + [&options](const StringPiece& arg) { + options.bindingOutput = Source{ arg.toString() }; + }); + flag::optionalSwitch("--no-version", "Disables automatic style and layout versioning", + false, &options.versionStylesAndLayouts); + } - flag::optionalFlag("--java", "directory in which to generate R.java", - [&options](const StringPiece& arg) { - options.generateJavaClass = Source{ arg.toString() }; + // Common flags for all steps. + flag::requiredFlag("-o", "Output path", [&options](const StringPiece& arg) { + options.output = Source{ arg.toString() }; + }); + } else if (options.phase == AaptOptions::Phase::DumpStyleGraph) { + flag::requiredFlag("--style", "Name of the style to dump", + [&options](const StringPiece& arg, std::string* outError) -> bool { + Reference styleReference; + if (!ResourceParser::parseStyleParentReference(util::utf8ToUtf16(arg), + &styleReference, outError)) { + return false; + } + options.dumpStyleTarget = styleReference.name; + return true; }); - flag::optionalSwitch("--static-lib", "generate a static Android library", true, - &isStaticLib); } - // Common flags for all steps. - flag::requiredFlag("-o", "Output path", [&options](const StringPiece& arg) { - options.output = Source{ arg.toString() }; - }); - bool help = false; flag::optionalSwitch("-v", "enables verbose logging", true, &options.verbose); flag::optionalSwitch("-h", "displays this help menu", true, &help); @@ -1078,10 +1045,56 @@ static AaptOptions prepareArgs(int argc, char** argv) { return options; } +static bool doDump(const AaptOptions& options) { + for (const Source& source : options.input) { + std::unique_ptr<ZipFile> zipFile = util::make_unique<ZipFile>(); + if (zipFile->open(source.path.data(), ZipFile::kOpenReadOnly) != android::NO_ERROR) { + Logger::error(source) << "failed to open: " << strerror(errno) << std::endl; + return false; + } + + std::shared_ptr<ResourceTable> table = std::make_shared<ResourceTable>(); + std::shared_ptr<ResourceTableResolver> resolver = + std::make_shared<ResourceTableResolver>( + table, std::vector<std::shared_ptr<const android::AssetManager>>()); + + ZipEntry* entry = zipFile->getEntryByName("resources.arsc"); + if (!entry) { + Logger::error(source) << "missing 'resources.arsc'." << std::endl; + return false; + } + + std::unique_ptr<void, DeleteMalloc> uncompressedData = std::unique_ptr<void, DeleteMalloc>( + zipFile->uncompress(entry)); + assert(uncompressedData); + + BinaryResourceParser parser(table, resolver, source, uncompressedData.get(), + entry->getUncompressedLen()); + if (!parser.parse()) { + return false; + } + + if (options.phase == AaptOptions::Phase::Dump) { + Debug::printTable(table); + } else if (options.phase == AaptOptions::Phase::DumpStyleGraph) { + Debug::printStyleGraph(table, options.dumpStyleTarget); + } + } + return true; +} + int main(int argc, char** argv) { Logger::setLog(std::make_shared<Log>(std::cerr, std::cerr)); AaptOptions options = prepareArgs(argc, argv); + if (options.phase == AaptOptions::Phase::Dump || + options.phase == AaptOptions::Phase::DumpStyleGraph) { + if (!doDump(options)) { + return 1; + } + return 0; + } + // If we specified a manifest, go ahead and load the package name from the manifest. if (!options.manifest.path.empty()) { if (!loadAppInfo(options.manifest, &options.appInfo)) { @@ -1105,37 +1118,26 @@ int main(int argc, char** argv) { } // Load the included libraries. - std::shared_ptr<android::AssetManager> libraries = std::make_shared<android::AssetManager>(); + std::vector<std::shared_ptr<const android::AssetManager>> sources; for (const Source& source : options.libraries) { - if (util::stringEndsWith<char>(source.path, ".arsc")) { - // We'll process these last so as to avoid a cookie issue. - continue; - } - + std::shared_ptr<android::AssetManager> assetManager = + std::make_shared<android::AssetManager>(); int32_t cookie; - if (!libraries->addAssetPath(android::String8(source.path.data()), &cookie)) { + if (!assetManager->addAssetPath(android::String8(source.path.data()), &cookie)) { Logger::error(source) << "failed to load library." << std::endl; return false; } - } - for (const Source& source : options.libraries) { - if (!util::stringEndsWith<char>(source.path, ".arsc")) { - // We've already processed this. - continue; - } - - // Dirty hack but there is no other way to get a - // writeable ResTable. - if (!loadResTable(const_cast<android::ResTable*>(&libraries->getResources(false)), - source)) { + if (cookie == 0) { + Logger::error(source) << "failed to load library." << std::endl; return false; } + sources.push_back(assetManager); } // Make the resolver that will cache IDs for us. std::shared_ptr<ResourceTableResolver> resolver = std::make_shared<ResourceTableResolver>( - table, libraries); + table, sources); if (options.phase == AaptOptions::Phase::Compile) { if (!compile(options, table, resolver)) { diff --git a/tools/aapt2/MockResolver.h b/tools/aapt2/MockResolver.h index 48ff6a6..0c9b954 100644 --- a/tools/aapt2/MockResolver.h +++ b/tools/aapt2/MockResolver.h @@ -34,7 +34,7 @@ struct MockResolver : public IResolver { MockResolver(const std::shared_ptr<ResourceTable>& table, const std::map<ResourceName, ResourceId>& items) : mResolver(std::make_shared<ResourceTableResolver>( - table, std::make_shared<const android::AssetManager>())), + table, std::vector<std::shared_ptr<const android::AssetManager>>())), mAttr(false, android::ResTable_map::TYPE_ANY), mItems(items) { } diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp index e7e824c..13f916b 100644 --- a/tools/aapt2/ResourceParser.cpp +++ b/tools/aapt2/ResourceParser.cpp @@ -193,18 +193,18 @@ std::unique_ptr<Reference> ResourceParser::tryParseReference(const StringPiece16 std::unique_ptr<BinaryPrimitive> ResourceParser::tryParseNullOrEmpty(const StringPiece16& str) { StringPiece16 trimmedStr(util::trimWhitespace(str)); - uint32_t data = 0; + android::Res_value value = {}; if (trimmedStr == u"@null") { - data = android::Res_value::DATA_NULL_UNDEFINED; + // TYPE_NULL with data set to 0 is interpreted by the runtime as an error. + // Instead we set the data type to TYPE_REFERENCE with a value of 0. + value.dataType = android::Res_value::TYPE_REFERENCE; } else if (trimmedStr == u"@empty") { - data = android::Res_value::DATA_NULL_EMPTY; + // TYPE_NULL with value of DATA_NULL_EMPTY is handled fine by the runtime. + value.dataType = android::Res_value::TYPE_NULL; + value.data = android::Res_value::DATA_NULL_EMPTY; } else { return {}; } - - android::Res_value value = {}; - value.dataType = android::Res_value::TYPE_NULL; - value.data = data; return util::make_unique<BinaryPrimitive>(value); } @@ -1163,7 +1163,7 @@ bool ResourceParser::parseUntypedItem(XmlPullParser* parser, Style& style) { bool ResourceParser::parseStyle(XmlPullParser* parser, const ResourceNameRef& resourceName) { const SourceLine source = mSource.line(parser->getLineNumber()); - std::unique_ptr<Style> style = util::make_unique<Style>(false); + std::unique_ptr<Style> style = util::make_unique<Style>(); const auto endAttrIter = parser->endAttributes(); const auto parentAttrIter = parser->findAttribute(u"", u"parent"); @@ -1181,6 +1181,16 @@ bool ResourceParser::parseStyle(XmlPullParser* parser, const ResourceNameRef& re // If no package is specified, this can not be an alias and is the local package. style->parent.name.package = mTable->getPackage(); } + } else { + // No parent was specified, so try inferring it from the style name. + std::u16string styleName = resourceName.entry.toString(); + size_t pos = styleName.find_last_of(u'.'); + if (pos != std::string::npos) { + style->parentInferred = true; + style->parent.name.package = mTable->getPackage(); + style->parent.name.type = ResourceType::kStyle; + style->parent.name.entry = styleName.substr(0, pos); + } } bool success = true; diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp index 00be3bd..a93d0ff 100644 --- a/tools/aapt2/ResourceParser_test.cpp +++ b/tools/aapt2/ResourceParser_test.cpp @@ -191,6 +191,32 @@ TEST_F(ResourceParserTest, ParseEscapedString) { EXPECT_EQ(std::u16string(u"?123"), *str->value); } +TEST_F(ResourceParserTest, ParseNull) { + std::string input = "<integer name=\"foo\">@null</integer>"; + ASSERT_TRUE(testParse(input)); + + // The Android runtime treats a value of android::Res_value::TYPE_NULL as + // a non-existing value, and this causes problems in styles when trying to resolve + // an attribute. Null values must be encoded as android::Res_value::TYPE_REFERENCE + // with a data value of 0. + const BinaryPrimitive* integer = findResource<BinaryPrimitive>(ResourceName{ + u"android", ResourceType::kInteger, u"foo" }); + ASSERT_NE(nullptr, integer); + EXPECT_EQ(uint16_t(android::Res_value::TYPE_REFERENCE), integer->value.dataType); + EXPECT_EQ(0u, integer->value.data); +} + +TEST_F(ResourceParserTest, ParseEmpty) { + std::string input = "<integer name=\"foo\">@empty</integer>"; + ASSERT_TRUE(testParse(input)); + + const BinaryPrimitive* integer = findResource<BinaryPrimitive>(ResourceName{ + u"android", ResourceType::kInteger, u"foo" }); + ASSERT_NE(nullptr, integer); + EXPECT_EQ(uint16_t(android::Res_value::TYPE_NULL), integer->value.dataType); + EXPECT_EQ(uint32_t(android::Res_value::DATA_NULL_EMPTY), integer->value.data); +} + TEST_F(ResourceParserTest, ParseAttr) { std::string input = "<attr name=\"foo\" format=\"string\"/>\n" "<attr name=\"bar\"/>"; @@ -355,6 +381,28 @@ TEST_F(ResourceParserTest, ParseStyleWithPackageAliasedItems) { style->entries[0].key.name); } +TEST_F(ResourceParserTest, ParseStyleWithInferredParent) { + std::string input = "<style name=\"foo.bar\"/>"; + ASSERT_TRUE(testParse(input)); + + const Style* style = findResource<Style>(ResourceName{ + u"android", ResourceType::kStyle, u"foo.bar" }); + ASSERT_NE(style, nullptr); + EXPECT_EQ(style->parent.name, (ResourceName{ u"android", ResourceType::kStyle, u"foo" })); + EXPECT_TRUE(style->parentInferred); +} + +TEST_F(ResourceParserTest, ParseStyleWithInferredParentOverridenByEmptyParentAttribute) { + std::string input = "<style name=\"foo.bar\" parent=\"\"/>"; + ASSERT_TRUE(testParse(input)); + + const Style* style = findResource<Style>(ResourceName{ + u"android", ResourceType::kStyle, u"foo.bar" }); + ASSERT_NE(style, nullptr); + EXPECT_FALSE(style->parent.name.isValid()); + EXPECT_FALSE(style->parentInferred); +} + TEST_F(ResourceParserTest, ParseAutoGeneratedIdReference) { std::string input = "<string name=\"foo\">@+id/bar</string>"; ASSERT_TRUE(testParse(input)); diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp index 9468860..c93ecc7 100644 --- a/tools/aapt2/ResourceTable.cpp +++ b/tools/aapt2/ResourceTable.cpp @@ -42,6 +42,8 @@ static bool lessThanEntry(const std::unique_ptr<ResourceEntry>& lhs, const Strin } ResourceTable::ResourceTable() : mPackageId(kUnsetPackageId) { + // Make sure attrs always have type ID 1. + findOrCreateType(ResourceType::kAttr)->typeId = 1; } std::unique_ptr<ResourceTableType>& ResourceTable::findOrCreateType(ResourceType type) { @@ -142,10 +144,30 @@ static int defaultCollisionHandler(const Value& existing, const Value& incoming) } static constexpr const char16_t* kValidNameChars = u"._-"; +static constexpr const char16_t* kValidNameMangledChars = u"._-$"; + +bool ResourceTable::addResource(const ResourceNameRef& name, const ConfigDescription& config, + const SourceLine& source, std::unique_ptr<Value> value) { + return addResourceImpl(name, ResourceId{}, config, source, std::move(value), kValidNameChars); +} bool ResourceTable::addResource(const ResourceNameRef& name, const ResourceId resId, - const ConfigDescription& config, const SourceLine& source, - std::unique_ptr<Value> value) { + const ConfigDescription& config, const SourceLine& source, + std::unique_ptr<Value> value) { + return addResourceImpl(name, resId, config, source, std::move(value), kValidNameChars); +} + +bool ResourceTable::addResourceAllowMangled(const ResourceNameRef& name, + const ConfigDescription& config, + const SourceLine& source, + std::unique_ptr<Value> value) { + return addResourceImpl(name, ResourceId{}, config, source, std::move(value), + kValidNameMangledChars); +} + +bool ResourceTable::addResourceImpl(const ResourceNameRef& name, const ResourceId resId, + const ConfigDescription& config, const SourceLine& source, + std::unique_ptr<Value> value, const char16_t* validChars) { if (!name.package.empty() && name.package != mPackage) { Logger::error(source) << "resource '" @@ -157,7 +179,7 @@ bool ResourceTable::addResource(const ResourceNameRef& name, const ResourceId re return false; } - auto badCharIter = util::findNonAlphaNumericAndNotInSet(name.entry, kValidNameChars); + auto badCharIter = util::findNonAlphaNumericAndNotInSet(name.entry, validChars); if (badCharIter != name.entry.end()) { Logger::error(source) << "resource '" @@ -233,13 +255,18 @@ bool ResourceTable::addResource(const ResourceNameRef& name, const ResourceId re return true; } -bool ResourceTable::addResource(const ResourceNameRef& name, const ConfigDescription& config, - const SourceLine& source, std::unique_ptr<Value> value) { - return addResource(name, ResourceId{}, config, source, std::move(value)); -} - bool ResourceTable::markPublic(const ResourceNameRef& name, const ResourceId resId, const SourceLine& source) { + return markPublicImpl(name, resId, source, kValidNameChars); +} + +bool ResourceTable::markPublicAllowMangled(const ResourceNameRef& name, const ResourceId resId, + const SourceLine& source) { + return markPublicImpl(name, resId, source, kValidNameMangledChars); +} + +bool ResourceTable::markPublicImpl(const ResourceNameRef& name, const ResourceId resId, + const SourceLine& source, const char16_t* validChars) { if (!name.package.empty() && name.package != mPackage) { Logger::error(source) << "resource '" @@ -251,7 +278,7 @@ bool ResourceTable::markPublic(const ResourceNameRef& name, const ResourceId res return false; } - auto badCharIter = util::findNonAlphaNumericAndNotInSet(name.entry, kValidNameChars); + auto badCharIter = util::findNonAlphaNumericAndNotInSet(name.entry, validChars); if (badCharIter != name.entry.end()) { Logger::error(source) << "resource '" diff --git a/tools/aapt2/ResourceTable.h b/tools/aapt2/ResourceTable.h index 94bacd8..706f56a 100644 --- a/tools/aapt2/ResourceTable.h +++ b/tools/aapt2/ResourceTable.h @@ -143,11 +143,21 @@ public: bool addResource(const ResourceNameRef& name, const ConfigDescription& config, const SourceLine& source, std::unique_ptr<Value> value); + /** + * Same as addResource, but doesn't verify the validity of the name. This is used + * when loading resources from an existing binary resource table that may have mangled + * names. + */ + bool addResourceAllowMangled(const ResourceNameRef& name, const ConfigDescription& config, + const SourceLine& source, std::unique_ptr<Value> value); + bool addResource(const ResourceNameRef& name, const ResourceId resId, const ConfigDescription& config, const SourceLine& source, std::unique_ptr<Value> value); bool markPublic(const ResourceNameRef& name, const ResourceId resId, const SourceLine& source); + bool markPublicAllowMangled(const ResourceNameRef& name, const ResourceId resId, + const SourceLine& source); /* * Merges the resources from `other` into this table, mangling the names of the resources @@ -176,6 +186,12 @@ private: std::unique_ptr<ResourceEntry>& findOrCreateEntry(std::unique_ptr<ResourceTableType>& type, const StringPiece16& name); + bool addResourceImpl(const ResourceNameRef& name, const ResourceId resId, + const ConfigDescription& config, const SourceLine& source, + std::unique_ptr<Value> value, const char16_t* validChars); + bool markPublicImpl(const ResourceNameRef& name, const ResourceId resId, + const SourceLine& source, const char16_t* validChars); + std::u16string mPackage; size_t mPackageId; diff --git a/tools/aapt2/ResourceTableResolver.cpp b/tools/aapt2/ResourceTableResolver.cpp index 0a9f521..910c2c0 100644 --- a/tools/aapt2/ResourceTableResolver.cpp +++ b/tools/aapt2/ResourceTableResolver.cpp @@ -31,13 +31,15 @@ namespace aapt { ResourceTableResolver::ResourceTableResolver( std::shared_ptr<const ResourceTable> table, - std::shared_ptr<const android::AssetManager> sources) : + const std::vector<std::shared_ptr<const android::AssetManager>>& sources) : mTable(table), mSources(sources) { - const android::ResTable& resTable = mSources->getResources(false); - const size_t packageCount = resTable.getBasePackageCount(); - for (size_t i = 0; i < packageCount; i++) { - std::u16string packageName = resTable.getBasePackageName(i).string(); - mIncludedPackages.insert(std::move(packageName)); + for (const auto& assetManager : mSources) { + const android::ResTable& resTable = assetManager->getResources(false); + const size_t packageCount = resTable.getBasePackageCount(); + for (size_t i = 0; i < packageCount; i++) { + std::u16string packageName = resTable.getBasePackageName(i).string(); + mIncludedPackages.insert(std::move(packageName)); + } } } @@ -99,20 +101,23 @@ Maybe<IResolver::Entry> ResourceTableResolver::findAttribute(const ResourceName& } Maybe<ResourceName> ResourceTableResolver::findName(ResourceId resId) { - const android::ResTable& table = mSources->getResources(false); + for (const auto& assetManager : mSources) { + const android::ResTable& table = assetManager->getResources(false); - android::ResTable::resource_name resourceName; - if (!table.getResourceName(resId.id, false, &resourceName)) { - return {}; - } + android::ResTable::resource_name resourceName; + if (!table.getResourceName(resId.id, false, &resourceName)) { + continue; + } - const ResourceType* type = parseResourceType(StringPiece16(resourceName.type, - resourceName.typeLen)); - assert(type); - return ResourceName{ - { resourceName.package, resourceName.packageLen }, - *type, - { resourceName.name, resourceName.nameLen } }; + const ResourceType* type = parseResourceType(StringPiece16(resourceName.type, + resourceName.typeLen)); + assert(type); + return ResourceName{ + { resourceName.package, resourceName.packageLen }, + *type, + { resourceName.name, resourceName.nameLen } }; + } + return {}; } /** @@ -122,73 +127,76 @@ Maybe<ResourceName> ResourceTableResolver::findName(ResourceId resId) { */ const ResourceTableResolver::CacheEntry* ResourceTableResolver::buildCacheEntry( const ResourceName& name) { - const android::ResTable& table = mSources->getResources(false); - - const StringPiece16 type16 = toString(name.type); - ResourceId resId { - table.identifierForName( - name.entry.data(), name.entry.size(), - type16.data(), type16.size(), - name.package.data(), name.package.size()) - }; - - if (!resId.isValid()) { - return nullptr; - } - - CacheEntry& entry = mCache[name]; - entry.id = resId; + for (const auto& assetManager : mSources) { + const android::ResTable& table = assetManager->getResources(false); + + const StringPiece16 type16 = toString(name.type); + ResourceId resId { + table.identifierForName( + name.entry.data(), name.entry.size(), + type16.data(), type16.size(), + name.package.data(), name.package.size()) + }; + + if (!resId.isValid()) { + continue; + } - // - // Now check to see if this resource is an Attribute. - // + CacheEntry& entry = mCache[name]; + entry.id = resId; - const android::ResTable::bag_entry* bagBegin; - ssize_t bags = table.lockBag(resId.id, &bagBegin); - if (bags < 1) { - table.unlockBag(bagBegin); - return &entry; - } + // + // Now check to see if this resource is an Attribute. + // - // Look for the ATTR_TYPE key in the bag and check the types it supports. - uint32_t attrTypeMask = 0; - for (ssize_t i = 0; i < bags; i++) { - if (bagBegin[i].map.name.ident == android::ResTable_map::ATTR_TYPE) { - attrTypeMask = bagBegin[i].map.value.data; + const android::ResTable::bag_entry* bagBegin; + ssize_t bags = table.lockBag(resId.id, &bagBegin); + if (bags < 1) { + table.unlockBag(bagBegin); + return &entry; } - } - - entry.attr = util::make_unique<Attribute>(false); - if (attrTypeMask & android::ResTable_map::TYPE_ENUM || - attrTypeMask & android::ResTable_map::TYPE_FLAGS) { + // Look for the ATTR_TYPE key in the bag and check the types it supports. + uint32_t attrTypeMask = 0; for (ssize_t i = 0; i < bags; i++) { - if (Res_INTERNALID(bagBegin[i].map.name.ident)) { - // Internal IDs are special keys, which are not enum/flag symbols, so skip. - continue; + if (bagBegin[i].map.name.ident == android::ResTable_map::ATTR_TYPE) { + attrTypeMask = bagBegin[i].map.value.data; } + } - android::ResTable::resource_name symbolName; - bool result = table.getResourceName(bagBegin[i].map.name.ident, false, - &symbolName); - assert(result); - const ResourceType* type = parseResourceType( - StringPiece16(symbolName.type, symbolName.typeLen)); - assert(type); - - entry.attr->symbols.push_back(Attribute::Symbol{ - Reference(ResourceNameRef( - StringPiece16(symbolName.package, symbolName.packageLen), - *type, - StringPiece16(symbolName.name, symbolName.nameLen))), - bagBegin[i].map.value.data - }); + entry.attr = util::make_unique<Attribute>(false); + + if (attrTypeMask & android::ResTable_map::TYPE_ENUM || + attrTypeMask & android::ResTable_map::TYPE_FLAGS) { + for (ssize_t i = 0; i < bags; i++) { + if (Res_INTERNALID(bagBegin[i].map.name.ident)) { + // Internal IDs are special keys, which are not enum/flag symbols, so skip. + continue; + } + + android::ResTable::resource_name symbolName; + bool result = table.getResourceName(bagBegin[i].map.name.ident, false, + &symbolName); + assert(result); + const ResourceType* type = parseResourceType( + StringPiece16(symbolName.type, symbolName.typeLen)); + assert(type); + + entry.attr->symbols.push_back(Attribute::Symbol{ + Reference(ResourceNameRef( + StringPiece16(symbolName.package, symbolName.packageLen), + *type, + StringPiece16(symbolName.name, symbolName.nameLen))), + bagBegin[i].map.value.data + }); + } } - } - entry.attr->typeMask |= attrTypeMask; - table.unlockBag(bagBegin); - return &entry; + entry.attr->typeMask |= attrTypeMask; + table.unlockBag(bagBegin); + return &entry; + } + return nullptr; } } // namespace aapt diff --git a/tools/aapt2/ResourceTableResolver.h b/tools/aapt2/ResourceTableResolver.h index c8e8ab7..8f6b0b5 100644 --- a/tools/aapt2/ResourceTableResolver.h +++ b/tools/aapt2/ResourceTableResolver.h @@ -24,7 +24,6 @@ #include "ResourceValues.h" #include <androidfw/AssetManager.h> -#include <androidfw/ResourceTypes.h> #include <memory> #include <vector> #include <unordered_set> @@ -40,8 +39,9 @@ public: * Creates a resolver with a local ResourceTable and an AssetManager * loaded with library packages. */ - ResourceTableResolver(std::shared_ptr<const ResourceTable> table, - std::shared_ptr<const android::AssetManager> sources); + ResourceTableResolver( + std::shared_ptr<const ResourceTable> table, + const std::vector<std::shared_ptr<const android::AssetManager>>& sources); ResourceTableResolver(const ResourceTableResolver&) = delete; // Not copyable. @@ -51,8 +51,6 @@ public: virtual Maybe<ResourceName> findName(ResourceId resId) override; - const android::ResTable& getResTable() const; - private: struct CacheEntry { ResourceId id; @@ -62,15 +60,11 @@ private: const CacheEntry* buildCacheEntry(const ResourceName& name); std::shared_ptr<const ResourceTable> mTable; - std::shared_ptr<const android::AssetManager> mSources; + std::vector<std::shared_ptr<const android::AssetManager>> mSources; std::map<ResourceName, CacheEntry> mCache; std::unordered_set<std::u16string> mIncludedPackages; }; -inline const android::ResTable& ResourceTableResolver::getResTable() const { - return mSources->getResources(false); -} - } // namespace aapt #endif // AAPT_RESOURCE_TABLE_RESOLVER_H diff --git a/tools/aapt2/ResourceValues.cpp b/tools/aapt2/ResourceValues.cpp index 2bf38e4..aabb375 100644 --- a/tools/aapt2/ResourceValues.cpp +++ b/tools/aapt2/ResourceValues.cpp @@ -101,8 +101,8 @@ bool Id::isWeak() const { } bool Id::flatten(android::Res_value& out) const { - out.dataType = android::Res_value::TYPE_NULL; - out.data = android::Res_value::DATA_NULL_UNDEFINED; + out.dataType = android::Res_value::TYPE_INT_BOOLEAN; + out.data = 0; return true; } @@ -231,17 +231,15 @@ Attribute* Attribute::clone(StringPool* /*newPool*/) const { return attr; } -void Attribute::print(std::ostream& out) const { - out << "(attr)"; +void Attribute::printMask(std::ostream& out) const { if (typeMask == android::ResTable_map::TYPE_ANY) { - out << " any"; + out << "any"; return; } bool set = false; if ((typeMask & android::ResTable_map::TYPE_REFERENCE) != 0) { if (!set) { - out << " "; set = true; } else { out << "|"; @@ -251,7 +249,6 @@ void Attribute::print(std::ostream& out) const { if ((typeMask & android::ResTable_map::TYPE_STRING) != 0) { if (!set) { - out << " "; set = true; } else { out << "|"; @@ -261,7 +258,6 @@ void Attribute::print(std::ostream& out) const { if ((typeMask & android::ResTable_map::TYPE_INTEGER) != 0) { if (!set) { - out << " "; set = true; } else { out << "|"; @@ -271,7 +267,6 @@ void Attribute::print(std::ostream& out) const { if ((typeMask & android::ResTable_map::TYPE_BOOLEAN) != 0) { if (!set) { - out << " "; set = true; } else { out << "|"; @@ -281,7 +276,6 @@ void Attribute::print(std::ostream& out) const { if ((typeMask & android::ResTable_map::TYPE_COLOR) != 0) { if (!set) { - out << " "; set = true; } else { out << "|"; @@ -291,7 +285,6 @@ void Attribute::print(std::ostream& out) const { if ((typeMask & android::ResTable_map::TYPE_FLOAT) != 0) { if (!set) { - out << " "; set = true; } else { out << "|"; @@ -301,7 +294,6 @@ void Attribute::print(std::ostream& out) const { if ((typeMask & android::ResTable_map::TYPE_DIMENSION) != 0) { if (!set) { - out << " "; set = true; } else { out << "|"; @@ -311,7 +303,6 @@ void Attribute::print(std::ostream& out) const { if ((typeMask & android::ResTable_map::TYPE_FRACTION) != 0) { if (!set) { - out << " "; set = true; } else { out << "|"; @@ -321,7 +312,6 @@ void Attribute::print(std::ostream& out) const { if ((typeMask & android::ResTable_map::TYPE_ENUM) != 0) { if (!set) { - out << " "; set = true; } else { out << "|"; @@ -331,13 +321,17 @@ void Attribute::print(std::ostream& out) const { if ((typeMask & android::ResTable_map::TYPE_FLAGS) != 0) { if (!set) { - out << " "; set = true; } else { out << "|"; } out << "flags"; } +} + +void Attribute::print(std::ostream& out) const { + out << "(attr) "; + printMask(out); out << " [" << util::joiner(symbols.begin(), symbols.end(), ", ") @@ -348,20 +342,10 @@ void Attribute::print(std::ostream& out) const { } } -static ::std::ostream& operator<<(::std::ostream& out, const Attribute::Symbol& s) { - return out << s.symbol.name.entry << "=" << s.value; -} - -Style::Style(bool weak) : weak(weak) { -} - -bool Style::isWeak() const { - return weak; -} - Style* Style::clone(StringPool* newPool) const { - Style* style = new Style(weak); + Style* style = new Style(); style->parent = parent; + style->parentInferred = parentInferred; for (auto& entry : entries) { style->entries.push_back(Entry{ entry.key, diff --git a/tools/aapt2/ResourceValues.h b/tools/aapt2/ResourceValues.h index f8ece6f..ef6594e 100644 --- a/tools/aapt2/ResourceValues.h +++ b/tools/aapt2/ResourceValues.h @@ -222,6 +222,7 @@ struct Attribute : public BaseValue<Attribute> { bool isWeak() const override; virtual Attribute* clone(StringPool* newPool) const override; + void printMask(std::ostream& out) const; virtual void print(std::ostream& out) const override; }; @@ -231,12 +232,16 @@ struct Style : public BaseValue<Style> { std::unique_ptr<Item> value; }; - bool weak; Reference parent; + + /** + * If set to true, the parent was auto inferred from the + * style's name. + */ + bool parentInferred = false; + std::vector<Entry> entries; - Style(bool weak); - bool isWeak() const override; Style* clone(StringPool* newPool) const override; void print(std::ostream& out) const override; }; @@ -280,6 +285,10 @@ inline ::std::ostream& operator<<(::std::ostream& out, const Value& value) { return out; } +inline ::std::ostream& operator<<(::std::ostream& out, const Attribute::Symbol& s) { + return out << s.symbol.name.entry << "=" << s.value; +} + /** * The argument object that gets passed through the value * back to the ValueVisitor. Subclasses of ValueVisitor should diff --git a/tools/aapt2/TableFlattener.cpp b/tools/aapt2/TableFlattener.cpp index aa0f1d5..539c48f 100644 --- a/tools/aapt2/TableFlattener.cpp +++ b/tools/aapt2/TableFlattener.cpp @@ -79,6 +79,7 @@ public: // Write the key. if (!Res_INTERNALID(key.id.id) && !key.id.isValid()) { + assert(key.name.isValid()); mSymbols->push_back(std::make_pair(ResourceNameRef(key.name), mOut->size() - sizeof(*outMapEntry))); } @@ -96,6 +97,23 @@ public: outMapEntry->value.size = sizeof(outMapEntry->value); } + void flattenValueOnly(const Item& value) { + mMap->count++; + + android::ResTable_map* outMapEntry = mOut->nextBlock<android::ResTable_map>(); + + // Write the value. + value.flatten(outMapEntry->value); + + if (outMapEntry->value.data == 0x0) { + visitFunc<Reference>(value, [&](const Reference& reference) { + mSymbols->push_back(std::make_pair(ResourceNameRef(reference.name), + mOut->size() - sizeof(outMapEntry->value.data))); + }); + } + outMapEntry->value.size = sizeof(outMapEntry->value); + } + static bool compareStyleEntries(const Style::Entry* lhs, const Style::Entry* rhs) { return lhs->key.id < rhs->key.id; } @@ -139,7 +157,7 @@ public: void visit(const Array& array, ValueVisitorArgs&) override { for (const auto& item : array.items) { - flattenEntry({}, *item); + flattenValueOnly(*item); } } @@ -334,6 +352,10 @@ bool TableFlattener::flatten(BigBuffer* out, const ResourceTable& table) { spec->id = type->typeId; spec->entryCount = type->entries.size(); + if (type->entries.empty()) { + continue; + } + // Reserve space for the masks of each resource in this type. These // show for which configuration axis the resource changes. uint32_t* configMasks = typeBlock.nextBlock<uint32_t>(type->entries.size()); diff --git a/tools/aapt2/XmlFlattener.cpp b/tools/aapt2/XmlFlattener.cpp index 650e624..f78e38d 100644 --- a/tools/aapt2/XmlFlattener.cpp +++ b/tools/aapt2/XmlFlattener.cpp @@ -300,6 +300,7 @@ Maybe<size_t> XmlFlattener::flatten(const Source& source, elem->attributeCount = sortedAttributes.size(); // Flatten the sorted attributes. + uint16_t attributeIndex = 1; for (auto entry : sortedAttributes) { android::ResXMLTree_attribute* attr = out.nextBlock<android::ResXMLTree_attribute>(); @@ -310,44 +311,65 @@ Maybe<size_t> XmlFlattener::flatten(const Source& source, attr->ns.index = -1; } + StringPool::Ref rawValueRef = pool.makeRef(entry.xmlAttr->value, lowPriority); + stringRefs.emplace_back(entry.nameRef, &attr->name); - attr->rawValue.index = -1; - StringPool::Ref rawValueRef = pool.makeRef(entry.xmlAttr->value, lowPriority); + if (options.keepRawValues) { + stringRefs.emplace_back(rawValueRef, &attr->rawValue); + } else { + attr->rawValue.index = -1; + } - if (entry.attr) { - std::unique_ptr<Item> value = ResourceParser::parseItemForAttribute( - entry.xmlAttr->value, *entry.attr); - if (value) { - AttributeValueFlattener flattener( - mResolver, - &logger, - &attr->typedValue, - parser, - &error, - rawValueRef, - &options.defaultPackage, - &stringRefs); - value->accept(flattener, {}); - } else if (!(entry.attr->typeMask & android::ResTable_map::TYPE_STRING)) { - logger.error(parser->getLineNumber()) - << "'" - << *rawValueRef - << "' is not compatible with attribute " - << *entry.attr - << "." - << std::endl; - error = true; - } else { - attr->typedValue.dataType = android::Res_value::TYPE_STRING; - stringRefs.emplace_back(rawValueRef, &attr->rawValue); - stringRefs.emplace_back(rawValueRef, - reinterpret_cast<android::ResStringPool_ref*>( - &attr->typedValue.data)); + // Assign the indices for specific attributes. + if (entry.xmlAttr->namespaceUri == kSchemaAndroid && + entry.xmlAttr->name == u"id") { + elem->idIndex = attributeIndex; + } else if (entry.xmlAttr->namespaceUri.empty()) { + if (entry.xmlAttr->name == u"class") { + elem->classIndex = attributeIndex; + } else if (entry.xmlAttr->name == u"style") { + elem->styleIndex = attributeIndex; } + } + attributeIndex++; + + std::unique_ptr<Item> value; + if (entry.attr) { + value = ResourceParser::parseItemForAttribute(entry.xmlAttr->value, + *entry.attr); + } else { + bool create = false; + value = ResourceParser::tryParseReference(entry.xmlAttr->value, &create); + } + + if (mResolver && value) { + AttributeValueFlattener flattener( + mResolver, + &logger, + &attr->typedValue, + parser, + &error, + rawValueRef, + &options.defaultPackage, + &stringRefs); + value->accept(flattener, {}); + } else if (!value && entry.attr && + !(entry.attr->typeMask & android::ResTable_map::TYPE_STRING)) { + logger.error(parser->getLineNumber()) + << "'" + << *rawValueRef + << "' is not compatible with attribute " + << *entry.attr + << "." + << std::endl; + error = true; } else { attr->typedValue.dataType = android::Res_value::TYPE_STRING; - stringRefs.emplace_back(rawValueRef, &attr->rawValue); + if (!options.keepRawValues) { + // Don't set the string twice. + stringRefs.emplace_back(rawValueRef, &attr->rawValue); + } stringRefs.emplace_back(rawValueRef, reinterpret_cast<android::ResStringPool_ref*>( &attr->typedValue.data)); @@ -440,6 +462,9 @@ Maybe<size_t> XmlFlattener::flatten(const Source& source, header->header.type = android::RES_XML_TYPE; header->header.headerSize = sizeof(*header); + // Flatten the StringPool. + StringPool::flattenUtf16(outBuffer, pool); + // Write the array of resource IDs, indexed by StringPool order. const size_t beforeResIdMapIndex = outBuffer->size(); android::ResChunk_header* resIdMapChunk = outBuffer->nextBlock<android::ResChunk_header>(); @@ -458,10 +483,7 @@ Maybe<size_t> XmlFlattener::flatten(const Source& source, } resIdMapChunk->size = outBuffer->size() - beforeResIdMapIndex; - // Flatten the StringPool. - StringPool::flattenUtf16(outBuffer, pool); - - // Move the temporary BigBuffer into outBuffer-> + // Move the temporary BigBuffer into outBuffer. outBuffer->appendBuffer(std::move(out)); header->header.size = outBuffer->size() - beforeXmlTreeIndex; diff --git a/tools/aapt2/XmlFlattener.h b/tools/aapt2/XmlFlattener.h index 60a500e..2cfcc16 100644 --- a/tools/aapt2/XmlFlattener.h +++ b/tools/aapt2/XmlFlattener.h @@ -47,6 +47,12 @@ public: * max SDK. */ Maybe<size_t> maxSdkAttribute; + + /** + * Setting this to true will keep the raw string value of + * an attribute's value when it has been resolved. + */ + bool keepRawValues = false; }; /** diff --git a/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java index f5ef01b..0c8c0d6 100644 --- a/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java @@ -1137,7 +1137,7 @@ public class Paint_Delegate { } private void reset() { - mFlags = Paint.DEFAULT_PAINT_FLAGS | Paint.HIDDEN_DEFAULT_PAINT_FLAGS; + mFlags = Paint.HIDDEN_DEFAULT_PAINT_FLAGS; mColor = 0xFF000000; mStyle = Paint.Style.FILL.nativeInt; mCap = Paint.Cap.BUTT.nativeInt; |