AAPT2: Support static lib referencing static lib
When a static library A references static library B, and app C references both A and B, we get the following symbol merging, symbols from library B get imported twice. We must only check that symbol references to library B are valid when building library A. We should only merge all the symbols when building final app C. Change-Id: I23cba33b0901dcbb5328d9c9dfaa6a979c073c36
This commit is contained in:
@@ -30,6 +30,7 @@ sources := \
|
||||
BinaryXmlPullParser.cpp \
|
||||
BindingXmlPullParser.cpp \
|
||||
ConfigDescription.cpp \
|
||||
Debug.cpp \
|
||||
Files.cpp \
|
||||
Flag.cpp \
|
||||
JavaClassGenerator.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);
|
||||
|
||||
@@ -697,13 +708,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);
|
||||
}
|
||||
@@ -789,10 +793,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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
181
tools/aapt2/Debug.cpp
Normal file
181
tools/aapt2/Debug.cpp
Normal file
@@ -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 "Debug.h"
|
||||
#include "ResourceTable.h"
|
||||
#include "ResourceValues.h"
|
||||
#include "Util.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#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) {
|
||||
std::vector<ResourceName> names;
|
||||
std::map<ResourceName, std::set<ResourceName>> graph;
|
||||
|
||||
for (const auto& type : *table) {
|
||||
for (const auto& entry : type->entries) {
|
||||
ResourceName name = { table->getPackage(), type->type, entry->name };
|
||||
for (const auto& value : entry->values) {
|
||||
visitFunc<Style>(*value.value, [&](const Style& style) {
|
||||
if (style.parent.name.isValid()) {
|
||||
names.push_back(style.parent.name);
|
||||
names.push_back(name);
|
||||
graph[style.parent.name].insert(name);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::sort(names.begin(), names.end());
|
||||
auto it1 = std::unique(names.begin(), names.end());
|
||||
names.resize(std::distance(names.begin(), it1));
|
||||
|
||||
std::cout << "digraph styles {\n";
|
||||
|
||||
for (const auto& name : names) {
|
||||
std::cout << " node_" << getNodeIndex(names, name)
|
||||
<< " [label=\"" << name.entry << "\"];\n";
|
||||
}
|
||||
|
||||
for (const auto& entry : graph) {
|
||||
const ResourceName& parent = entry.first;
|
||||
size_t parentNodeIndex = getNodeIndex(names, parent);
|
||||
|
||||
for (const auto& childName : entry.second) {
|
||||
std::cout << "node_" << getNodeIndex(names, childName) << " -> "
|
||||
<< "node_" << parentNodeIndex << ";\n";
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << "}" << std::endl;
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
33
tools/aapt2/Debug.h
Normal file
33
tools/aapt2/Debug.h
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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 "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);
|
||||
};
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
#endif // AAPT_DEBUG_H
|
||||
@@ -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;
|
||||
|
||||
@@ -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, {});
|
||||
|
||||
@@ -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);
|
||||
|
||||
// Update callers of this resource with the right ID.
|
||||
auto callersIter = mGraph.find(ResourceNameRef{
|
||||
unmangledPackage,
|
||||
type->type,
|
||||
unmangledName
|
||||
});
|
||||
|
||||
if (callersIter != std::end(mGraph)) {
|
||||
for (Node& caller : callersIter->second) {
|
||||
caller.reference->id = ResourceId(mTable->getPackageId(),
|
||||
type->typeId,
|
||||
entry->entryId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
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
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
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 Attribute* attr = doResolveAttribute(styleEntry.key, args.source);
|
||||
if (attr) {
|
||||
processAttributeValue(args.referrer, args.source, *attr, styleEntry.value);
|
||||
}
|
||||
|
||||
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
|
||||
});
|
||||
}
|
||||
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
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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) {
|
||||
@@ -285,8 +215,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 +249,8 @@ struct AaptOptions {
|
||||
enum class Phase {
|
||||
Link,
|
||||
Compile,
|
||||
Dump,
|
||||
DumpStyleGraph,
|
||||
};
|
||||
|
||||
enum class PackageType {
|
||||
@@ -366,9 +296,8 @@ struct AaptOptions {
|
||||
bool versionStylesAndLayouts = true;
|
||||
};
|
||||
|
||||
|
||||
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 +311,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 +358,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 +373,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 +411,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 +445,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 +474,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 +630,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 +684,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 +720,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 +741,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 +814,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 +870,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 +885,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 +898,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 +928,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 +954,56 @@ 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);
|
||||
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() };
|
||||
});
|
||||
|
||||
} 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("-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::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::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);
|
||||
}
|
||||
|
||||
// Common flags for all steps.
|
||||
flag::requiredFlag("-o", "Output path", [&options](const StringPiece& arg) {
|
||||
options.output = Source{ arg.toString() };
|
||||
});
|
||||
}
|
||||
|
||||
// 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 +1031,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);
|
||||
}
|
||||
}
|
||||
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 +1104,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)) {
|
||||
|
||||
@@ -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) {
|
||||
}
|
||||
|
||||
|
||||
@@ -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 '"
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
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())
|
||||
};
|
||||
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;
|
||||
}
|
||||
if (!resId.isValid()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
CacheEntry& entry = mCache[name];
|
||||
entry.id = resId;
|
||||
CacheEntry& entry = mCache[name];
|
||||
entry.id = resId;
|
||||
|
||||
//
|
||||
// Now check to see if this resource is an Attribute.
|
||||
//
|
||||
//
|
||||
// Now check to see if this resource is an Attribute.
|
||||
//
|
||||
|
||||
const android::ResTable::bag_entry* bagBegin;
|
||||
ssize_t bags = table.lockBag(resId.id, &bagBegin);
|
||||
if (bags < 1) {
|
||||
const android::ResTable::bag_entry* bagBegin;
|
||||
ssize_t bags = table.lockBag(resId.id, &bagBegin);
|
||||
if (bags < 1) {
|
||||
table.unlockBag(bagBegin);
|
||||
return &entry;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,10 +342,6 @@ 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) {
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -280,6 +281,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
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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,64 @@ Maybe<size_t> XmlFlattener::flatten(const Source& source,
|
||||
attr->ns.index = -1;
|
||||
}
|
||||
|
||||
stringRefs.emplace_back(entry.nameRef, &attr->name);
|
||||
attr->rawValue.index = -1;
|
||||
|
||||
StringPool::Ref rawValueRef = pool.makeRef(entry.xmlAttr->value, lowPriority);
|
||||
|
||||
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));
|
||||
stringRefs.emplace_back(entry.nameRef, &attr->name);
|
||||
|
||||
if (options.keepRawValues) {
|
||||
stringRefs.emplace_back(rawValueRef, &attr->rawValue);
|
||||
} else {
|
||||
attr->rawValue.index = -1;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
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 +461,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 +482,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;
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user