Merge "AAPT2: Introduce notion of 'product' to ResourceTable" into nyc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
923e6342fb
@@ -38,6 +38,7 @@ sources := \
|
||||
io/ZipArchive.cpp \
|
||||
link/AutoVersioner.cpp \
|
||||
link/ManifestFixer.cpp \
|
||||
link/ProductFilter.cpp \
|
||||
link/PrivateAttributeMover.cpp \
|
||||
link/ReferenceLinker.cpp \
|
||||
link/TableMerger.cpp \
|
||||
@@ -83,6 +84,7 @@ testSources := \
|
||||
link/AutoVersioner_test.cpp \
|
||||
link/ManifestFixer_test.cpp \
|
||||
link/PrivateAttributeMover_test.cpp \
|
||||
link/ProductFilter_test.cpp \
|
||||
link/ReferenceLinker_test.cpp \
|
||||
link/TableMerger_test.cpp \
|
||||
link/XmlReferenceLinker_test.cpp \
|
||||
|
||||
@@ -144,8 +144,8 @@ void Debug::printTable(ResourceTable* table) {
|
||||
|
||||
PrintVisitor visitor;
|
||||
for (const auto& value : entry->values) {
|
||||
std::cout << " (" << value.config << ") ";
|
||||
value.value->accept(&visitor);
|
||||
std::cout << " (" << value->config << ") ";
|
||||
value->value->accept(&visitor);
|
||||
std::cout << std::endl;
|
||||
}
|
||||
}
|
||||
@@ -176,7 +176,7 @@ void Debug::printStyleGraph(ResourceTable* table, const ResourceName& targetStyl
|
||||
if (result) {
|
||||
ResourceEntry* entry = result.value().entry;
|
||||
for (const auto& value : entry->values) {
|
||||
if (Style* style = valueCast<Style>(value.value.get())) {
|
||||
if (Style* style = valueCast<Style>(value->value.get())) {
|
||||
if (style->parent && style->parent.value().name) {
|
||||
parents.insert(style->parent.value().name.value());
|
||||
stylesToVisit.push(style->parent.value().name.value());
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
#include "ResourceUtils.h"
|
||||
#include "ResourceValues.h"
|
||||
#include "ValueVisitor.h"
|
||||
#include "util/Comparators.h"
|
||||
#include "util/ImmutableMap.h"
|
||||
#include "util/Util.h"
|
||||
#include "xml/XmlPullParser.h"
|
||||
@@ -71,6 +70,7 @@ static uint32_t parseFormatAttribute(const StringPiece16& str) {
|
||||
struct ParsedResource {
|
||||
ResourceName name;
|
||||
ConfigDescription config;
|
||||
std::string product;
|
||||
Source source;
|
||||
ResourceId id;
|
||||
Maybe<SymbolState> symbolState;
|
||||
@@ -79,35 +79,6 @@ struct ParsedResource {
|
||||
std::list<ParsedResource> childResources;
|
||||
};
|
||||
|
||||
bool ResourceParser::shouldStripResource(const ResourceNameRef& name,
|
||||
const Maybe<std::u16string>& product) const {
|
||||
if (product) {
|
||||
for (const std::u16string& productToMatch : mOptions.products) {
|
||||
if (product.value() == productToMatch) {
|
||||
// We specified a product, and it is in the list, so don't strip.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Nothing matched, try 'default'. Default only matches if we didn't already use another
|
||||
// product variant.
|
||||
if (!product || product.value() == u"default") {
|
||||
if (Maybe<ResourceTable::SearchResult> result = mTable->findResource(name)) {
|
||||
const ResourceEntry* entry = result.value().entry;
|
||||
auto iter = std::lower_bound(entry->values.begin(), entry->values.end(), mConfig,
|
||||
cmp::lessThanConfig);
|
||||
if (iter != entry->values.end() && iter->config == mConfig && !iter->value->isWeak()) {
|
||||
// We have a value for this config already, and it is not weak,
|
||||
// so filter out this default.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Recursively adds resources to the ResourceTable.
|
||||
static bool addResourcesToTable(ResourceTable* table, IDiagnostics* diag, ParsedResource* res) {
|
||||
if (res->symbolState) {
|
||||
@@ -125,7 +96,8 @@ static bool addResourcesToTable(ResourceTable* table, IDiagnostics* diag, Parsed
|
||||
res->value->setComment(std::move(res->comment));
|
||||
res->value->setSource(std::move(res->source));
|
||||
|
||||
if (!table->addResource(res->name, res->id, res->config, std::move(res->value), diag)) {
|
||||
if (!table->addResource(res->name, res->id, res->config, res->product,
|
||||
std::move(res->value), diag)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -295,9 +267,8 @@ bool ResourceParser::parseResources(xml::XmlPullParser* parser) {
|
||||
parsedResource.comment = std::move(comment);
|
||||
|
||||
// Extract the product name if it exists.
|
||||
Maybe<std::u16string> product;
|
||||
if (Maybe<StringPiece16> maybeProduct = xml::findNonEmptyAttribute(parser, u"product")) {
|
||||
product = maybeProduct.value().toString();
|
||||
parsedResource.product = util::utf16ToUtf8(maybeProduct.value());
|
||||
}
|
||||
|
||||
// Parse the resource regardless of product.
|
||||
@@ -306,12 +277,7 @@ bool ResourceParser::parseResources(xml::XmlPullParser* parser) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// We successfully parsed the resource. Check if we should include it or strip it.
|
||||
if (shouldStripResource(parsedResource.name, product)) {
|
||||
// Record that we stripped out this resource name.
|
||||
// We will check that at least one variant of this resource was included.
|
||||
strippedResources.insert(parsedResource.name);
|
||||
} else if (!addResourcesToTable(mTable, mDiag, &parsedResource)) {
|
||||
if (!addResourcesToTable(mTable, mDiag, &parsedResource)) {
|
||||
error = true;
|
||||
}
|
||||
}
|
||||
@@ -524,7 +490,7 @@ std::unique_ptr<Item> ResourceParser::parseXml(xml::XmlPullParser* parser, const
|
||||
// name.package can be empty here, as it will assume the package name of the table.
|
||||
std::unique_ptr<Id> id = util::make_unique<Id>();
|
||||
id->setSource(mSource.withLine(beginXmlLine));
|
||||
mTable->addResource(name, {}, std::move(id), mDiag);
|
||||
mTable->addResource(name, {}, {}, std::move(id), mDiag);
|
||||
};
|
||||
|
||||
// Process the raw value.
|
||||
|
||||
@@ -33,13 +33,6 @@ namespace aapt {
|
||||
struct ParsedResource;
|
||||
|
||||
struct ResourceParserOptions {
|
||||
/**
|
||||
* Optional product names by which to filter resources.
|
||||
* This is like a preprocessor definition in that we strip out resources
|
||||
* that don't match before we compile them.
|
||||
*/
|
||||
std::vector<std::u16string> products;
|
||||
|
||||
/**
|
||||
* Whether the default setting for this parser is to allow translation.
|
||||
*/
|
||||
@@ -106,9 +99,6 @@ private:
|
||||
bool parseArrayImpl(xml::XmlPullParser* parser, ParsedResource* outResource, uint32_t typeMask);
|
||||
bool parsePlural(xml::XmlPullParser* parser, ParsedResource* outResource);
|
||||
|
||||
bool shouldStripResource(const ResourceNameRef& name,
|
||||
const Maybe<std::u16string>& product) const;
|
||||
|
||||
IDiagnostics* mDiag;
|
||||
ResourceTable* mTable;
|
||||
Source mSource;
|
||||
|
||||
@@ -48,24 +48,13 @@ struct ResourceParserTest : public ::testing::Test {
|
||||
}
|
||||
|
||||
::testing::AssertionResult testParse(const StringPiece& str) {
|
||||
return testParse(str, ConfigDescription{}, {});
|
||||
return testParse(str, ConfigDescription{});
|
||||
}
|
||||
|
||||
::testing::AssertionResult testParse(const StringPiece& str, const ConfigDescription& config) {
|
||||
return testParse(str, config, {});
|
||||
}
|
||||
|
||||
::testing::AssertionResult testParse(const StringPiece& str,
|
||||
std::initializer_list<std::u16string> products) {
|
||||
return testParse(str, {}, std::move(products));
|
||||
}
|
||||
|
||||
::testing::AssertionResult testParse(const StringPiece& str, const ConfigDescription& config,
|
||||
std::initializer_list<std::u16string> products) {
|
||||
std::stringstream input(kXmlPreamble);
|
||||
input << "<resources>\n" << str << "\n</resources>" << std::endl;
|
||||
ResourceParserOptions parserOptions;
|
||||
parserOptions.products = products;
|
||||
ResourceParser parser(mContext->getDiagnostics(), &mTable, Source{ "test" }, config,
|
||||
parserOptions);
|
||||
xml::XmlPullParser xmlParser(input);
|
||||
@@ -546,7 +535,7 @@ TEST_F(ResourceParserTest, ParsePublicIdAsDefinition) {
|
||||
ASSERT_NE(nullptr, id);
|
||||
}
|
||||
|
||||
TEST_F(ResourceParserTest, FilterProductsThatDontMatch) {
|
||||
TEST_F(ResourceParserTest, KeepAllProducts) {
|
||||
std::string input = R"EOF(
|
||||
<string name="foo" product="phone">hi</string>
|
||||
<string name="foo" product="no-sdcard">ho</string>
|
||||
@@ -555,33 +544,26 @@ TEST_F(ResourceParserTest, FilterProductsThatDontMatch) {
|
||||
<string name="bit" product="phablet">hoot</string>
|
||||
<string name="bot" product="default">yes</string>
|
||||
)EOF";
|
||||
ASSERT_TRUE(testParse(input, { std::u16string(u"no-sdcard"), std::u16string(u"phablet") }));
|
||||
ASSERT_TRUE(testParse(input));
|
||||
|
||||
String* fooStr = test::getValue<String>(&mTable, u"@string/foo");
|
||||
ASSERT_NE(nullptr, fooStr);
|
||||
EXPECT_EQ(StringPiece16(u"ho"), *fooStr->value);
|
||||
|
||||
EXPECT_NE(nullptr, test::getValue<String>(&mTable, u"@string/bar"));
|
||||
EXPECT_NE(nullptr, test::getValue<String>(&mTable, u"@string/baz"));
|
||||
EXPECT_NE(nullptr, test::getValue<String>(&mTable, u"@string/bit"));
|
||||
EXPECT_NE(nullptr, test::getValue<String>(&mTable, u"@string/bot"));
|
||||
}
|
||||
|
||||
TEST_F(ResourceParserTest, FilterProductsThatBothMatchInOrder) {
|
||||
std::string input = R"EOF(
|
||||
<string name="foo" product="phone">phone</string>
|
||||
<string name="foo" product="default">default</string>
|
||||
)EOF";
|
||||
ASSERT_TRUE(testParse(input, { std::u16string(u"phone") }));
|
||||
|
||||
String* foo = test::getValue<String>(&mTable, u"@string/foo");
|
||||
ASSERT_NE(nullptr, foo);
|
||||
EXPECT_EQ(std::u16string(u"phone"), *foo->value);
|
||||
}
|
||||
|
||||
TEST_F(ResourceParserTest, FailWhenProductFilterStripsOutAllVersionsOfResource) {
|
||||
std::string input = "<string name=\"foo\" product=\"tablet\">hello</string>\n";
|
||||
ASSERT_FALSE(testParse(input, { std::u16string(u"phone") }));
|
||||
EXPECT_NE(nullptr, test::getValueForConfigAndProduct<String>(&mTable, u"@string/foo",
|
||||
ConfigDescription::defaultConfig(),
|
||||
"phone"));
|
||||
EXPECT_NE(nullptr, test::getValueForConfigAndProduct<String>(&mTable, u"@string/foo",
|
||||
ConfigDescription::defaultConfig(),
|
||||
"no-sdcard"));
|
||||
EXPECT_NE(nullptr, test::getValueForConfigAndProduct<String>(&mTable, u"@string/bar",
|
||||
ConfigDescription::defaultConfig(),
|
||||
""));
|
||||
EXPECT_NE(nullptr, test::getValueForConfigAndProduct<String>(&mTable, u"@string/baz",
|
||||
ConfigDescription::defaultConfig(),
|
||||
""));
|
||||
EXPECT_NE(nullptr, test::getValueForConfigAndProduct<String>(&mTable, u"@string/bit",
|
||||
ConfigDescription::defaultConfig(),
|
||||
"phablet"));
|
||||
EXPECT_NE(nullptr, test::getValueForConfigAndProduct<String>(&mTable, u"@string/bot",
|
||||
ConfigDescription::defaultConfig(),
|
||||
"default"));
|
||||
}
|
||||
|
||||
TEST_F(ResourceParserTest, AutoIncrementIdsInPublicGroup) {
|
||||
|
||||
@@ -19,8 +19,6 @@
|
||||
#include "ResourceTable.h"
|
||||
#include "ResourceValues.h"
|
||||
#include "ValueVisitor.h"
|
||||
|
||||
#include "util/Comparators.h"
|
||||
#include "util/Util.h"
|
||||
|
||||
#include <algorithm>
|
||||
@@ -124,6 +122,73 @@ ResourceEntry* ResourceTableType::findOrCreateEntry(const StringPiece16& name) {
|
||||
return entries.emplace(iter, new ResourceEntry(name))->get();
|
||||
}
|
||||
|
||||
ResourceConfigValue* ResourceEntry::findValue(const ConfigDescription& config) {
|
||||
return findValue(config, StringPiece());
|
||||
}
|
||||
|
||||
struct ConfigKey {
|
||||
const ConfigDescription* config;
|
||||
const StringPiece& product;
|
||||
};
|
||||
|
||||
bool ltConfigKeyRef(const std::unique_ptr<ResourceConfigValue>& lhs, const ConfigKey& rhs) {
|
||||
int cmp = lhs->config.compare(*rhs.config);
|
||||
if (cmp == 0) {
|
||||
cmp = StringPiece(lhs->product).compare(rhs.product);
|
||||
}
|
||||
return cmp < 0;
|
||||
}
|
||||
|
||||
ResourceConfigValue* ResourceEntry::findValue(const ConfigDescription& config,
|
||||
const StringPiece& product) {
|
||||
auto iter = std::lower_bound(values.begin(), values.end(),
|
||||
ConfigKey{ &config, product }, ltConfigKeyRef);
|
||||
if (iter != values.end()) {
|
||||
ResourceConfigValue* value = iter->get();
|
||||
if (value->config == config && StringPiece(value->product) == product) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ResourceConfigValue* ResourceEntry::findOrCreateValue(const ConfigDescription& config,
|
||||
const StringPiece& product) {
|
||||
auto iter = std::lower_bound(values.begin(), values.end(),
|
||||
ConfigKey{ &config, product }, ltConfigKeyRef);
|
||||
if (iter != values.end()) {
|
||||
ResourceConfigValue* value = iter->get();
|
||||
if (value->config == config && StringPiece(value->product) == product) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
ResourceConfigValue* newValue = values.insert(
|
||||
iter, util::make_unique<ResourceConfigValue>(config, product))->get();
|
||||
return newValue;
|
||||
}
|
||||
|
||||
std::vector<ResourceConfigValue*> ResourceEntry::findAllValues(const ConfigDescription& config) {
|
||||
std::vector<ResourceConfigValue*> results;
|
||||
|
||||
auto iter = values.begin();
|
||||
for (; iter != values.end(); ++iter) {
|
||||
ResourceConfigValue* value = iter->get();
|
||||
if (value->config == config) {
|
||||
results.push_back(value);
|
||||
++iter;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (; iter != values.end(); ++iter) {
|
||||
ResourceConfigValue* value = iter->get();
|
||||
if (value->config == config) {
|
||||
results.push_back(value);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* The default handler for collisions. A return value of -1 means keep the
|
||||
* existing value, 0 means fail, and +1 means take the incoming value.
|
||||
@@ -188,56 +253,69 @@ int ResourceTable::resolveValueCollision(Value* existing, 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,
|
||||
std::unique_ptr<Value> value, IDiagnostics* diag) {
|
||||
return addResourceImpl(name, {}, config, std::move(value), kValidNameChars,
|
||||
resolveValueCollision, diag);
|
||||
}
|
||||
|
||||
bool ResourceTable::addResource(const ResourceNameRef& name, const ResourceId resId,
|
||||
const ConfigDescription& config, std::unique_ptr<Value> value,
|
||||
bool ResourceTable::addResource(const ResourceNameRef& name,
|
||||
const ConfigDescription& config,
|
||||
const StringPiece& product,
|
||||
std::unique_ptr<Value> value,
|
||||
IDiagnostics* diag) {
|
||||
return addResourceImpl(name, resId, config, std::move(value), kValidNameChars,
|
||||
return addResourceImpl(name, {}, config, product, std::move(value), kValidNameChars,
|
||||
resolveValueCollision, diag);
|
||||
}
|
||||
|
||||
bool ResourceTable::addFileReference(const ResourceNameRef& name, const ConfigDescription& config,
|
||||
const Source& source, const StringPiece16& path,
|
||||
bool ResourceTable::addResource(const ResourceNameRef& name,
|
||||
const ResourceId resId,
|
||||
const ConfigDescription& config,
|
||||
const StringPiece& product,
|
||||
std::unique_ptr<Value> value,
|
||||
IDiagnostics* diag) {
|
||||
return addResourceImpl(name, resId, config, product, std::move(value), kValidNameChars,
|
||||
resolveValueCollision, diag);
|
||||
}
|
||||
|
||||
bool ResourceTable::addFileReference(const ResourceNameRef& name,
|
||||
const ConfigDescription& config,
|
||||
const Source& source,
|
||||
const StringPiece16& path,
|
||||
IDiagnostics* diag) {
|
||||
return addFileReference(name, config, source, path, resolveValueCollision, diag);
|
||||
}
|
||||
|
||||
bool ResourceTable::addFileReference(const ResourceNameRef& name, const ConfigDescription& config,
|
||||
const Source& source, const StringPiece16& path,
|
||||
bool ResourceTable::addFileReference(const ResourceNameRef& name,
|
||||
const ConfigDescription& config,
|
||||
const Source& source,
|
||||
const StringPiece16& path,
|
||||
std::function<int(Value*,Value*)> conflictResolver,
|
||||
IDiagnostics* diag) {
|
||||
std::unique_ptr<FileReference> fileRef = util::make_unique<FileReference>(
|
||||
stringPool.makeRef(path));
|
||||
fileRef->setSource(source);
|
||||
return addResourceImpl(name, ResourceId{}, config, std::move(fileRef), kValidNameChars,
|
||||
conflictResolver, diag);
|
||||
return addResourceImpl(name, ResourceId{}, config, StringPiece{}, std::move(fileRef),
|
||||
kValidNameChars, conflictResolver, diag);
|
||||
}
|
||||
|
||||
bool ResourceTable::addResourceAllowMangled(const ResourceNameRef& name,
|
||||
const ConfigDescription& config,
|
||||
const StringPiece& product,
|
||||
std::unique_ptr<Value> value,
|
||||
IDiagnostics* diag) {
|
||||
return addResourceImpl(name, ResourceId{}, config, std::move(value), kValidNameMangledChars,
|
||||
resolveValueCollision, diag);
|
||||
return addResourceImpl(name, ResourceId{}, config, product, std::move(value),
|
||||
kValidNameMangledChars, resolveValueCollision, diag);
|
||||
}
|
||||
|
||||
bool ResourceTable::addResourceAllowMangled(const ResourceNameRef& name,
|
||||
const ResourceId id,
|
||||
const ConfigDescription& config,
|
||||
const StringPiece& product,
|
||||
std::unique_ptr<Value> value,
|
||||
IDiagnostics* diag) {
|
||||
return addResourceImpl(name, id, config, std::move(value), kValidNameMangledChars,
|
||||
return addResourceImpl(name, id, config, product, std::move(value), kValidNameMangledChars,
|
||||
resolveValueCollision, diag);
|
||||
}
|
||||
|
||||
bool ResourceTable::addResourceImpl(const ResourceNameRef& name,
|
||||
const ResourceId resId,
|
||||
const ConfigDescription& config,
|
||||
const StringPiece& product,
|
||||
std::unique_ptr<Value> value,
|
||||
const char16_t* validChars,
|
||||
std::function<int(Value*,Value*)> conflictResolver,
|
||||
@@ -298,21 +376,21 @@ bool ResourceTable::addResourceImpl(const ResourceNameRef& name,
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto endIter = entry->values.end();
|
||||
auto iter = std::lower_bound(entry->values.begin(), endIter, config, cmp::lessThanConfig);
|
||||
if (iter == endIter || iter->config != config) {
|
||||
// This resource did not exist before, add it.
|
||||
entry->values.insert(iter, ResourceConfigValue{ config, std::move(value) });
|
||||
ResourceConfigValue* configValue = entry->findOrCreateValue(config, product);
|
||||
if (!configValue->value) {
|
||||
// Resource does not exist, add it now.
|
||||
configValue->value = std::move(value);
|
||||
|
||||
} else {
|
||||
int collisionResult = conflictResolver(iter->value.get(), value.get());
|
||||
int collisionResult = conflictResolver(configValue->value.get(), value.get());
|
||||
if (collisionResult > 0) {
|
||||
// Take the incoming value.
|
||||
iter->value = std::move(value);
|
||||
configValue->value = std::move(value);
|
||||
} else if (collisionResult == 0) {
|
||||
diag->error(DiagMessage(value->getSource())
|
||||
<< "duplicate value for resource '" << name << "' "
|
||||
<< "with config '" << config << "'");
|
||||
diag->error(DiagMessage(iter->value->getSource())
|
||||
diag->error(DiagMessage(configValue->value->getSource())
|
||||
<< "resource previously defined here");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -24,9 +24,12 @@
|
||||
#include "Source.h"
|
||||
#include "StringPool.h"
|
||||
|
||||
#include <android-base/macros.h>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace aapt {
|
||||
@@ -46,19 +49,36 @@ struct Symbol {
|
||||
std::u16string comment;
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents a value defined for a given configuration.
|
||||
*/
|
||||
struct ResourceConfigValue {
|
||||
ConfigDescription config;
|
||||
class ResourceConfigValue {
|
||||
public:
|
||||
/**
|
||||
* The configuration for which this value is defined.
|
||||
*/
|
||||
const ConfigDescription config;
|
||||
|
||||
/**
|
||||
* The product for which this value is defined.
|
||||
*/
|
||||
const std::string product;
|
||||
|
||||
/**
|
||||
* The actual Value.
|
||||
*/
|
||||
std::unique_ptr<Value> value;
|
||||
|
||||
ResourceConfigValue(const ConfigDescription& config, const StringPiece& product) :
|
||||
config(config), product(product.toString()) { }
|
||||
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(ResourceConfigValue);
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents a resource entry, which may have
|
||||
* varying values for each defined configuration.
|
||||
*/
|
||||
struct ResourceEntry {
|
||||
class ResourceEntry {
|
||||
public:
|
||||
/**
|
||||
* The name of the resource. Immutable, as
|
||||
* this determines the order of this resource
|
||||
@@ -72,24 +92,33 @@ struct ResourceEntry {
|
||||
Maybe<uint16_t> id;
|
||||
|
||||
/**
|
||||
* Whether this resource is public (and must maintain the same
|
||||
* entry ID across builds).
|
||||
* Whether this resource is public (and must maintain the same entry ID across builds).
|
||||
*/
|
||||
Symbol symbolStatus;
|
||||
|
||||
/**
|
||||
* The resource's values for each configuration.
|
||||
*/
|
||||
std::vector<ResourceConfigValue> values;
|
||||
std::vector<std::unique_ptr<ResourceConfigValue>> values;
|
||||
|
||||
ResourceEntry(const StringPiece16& name) : name(name.toString()) { }
|
||||
|
||||
ResourceConfigValue* findValue(const ConfigDescription& config);
|
||||
ResourceConfigValue* findValue(const ConfigDescription& config, const StringPiece& product);
|
||||
ResourceConfigValue* findOrCreateValue(const ConfigDescription& config,
|
||||
const StringPiece& product);
|
||||
std::vector<ResourceConfigValue*> findAllValues(const ConfigDescription& config);
|
||||
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(ResourceEntry);
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents a resource type, which holds entries defined
|
||||
* for this type.
|
||||
*/
|
||||
struct ResourceTableType {
|
||||
class ResourceTableType {
|
||||
public:
|
||||
/**
|
||||
* The logical type of resource (string, drawable, layout, etc.).
|
||||
*/
|
||||
@@ -114,8 +143,10 @@ struct ResourceTableType {
|
||||
explicit ResourceTableType(const ResourceType type) : type(type) { }
|
||||
|
||||
ResourceEntry* findEntry(const StringPiece16& name);
|
||||
|
||||
ResourceEntry* findOrCreateEntry(const StringPiece16& name);
|
||||
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(ResourceTableType);
|
||||
};
|
||||
|
||||
enum class PackageType {
|
||||
@@ -125,16 +156,20 @@ enum class PackageType {
|
||||
Dynamic
|
||||
};
|
||||
|
||||
struct ResourceTablePackage {
|
||||
class ResourceTablePackage {
|
||||
public:
|
||||
PackageType type = PackageType::App;
|
||||
Maybe<uint8_t> id;
|
||||
std::u16string name;
|
||||
|
||||
std::vector<std::unique_ptr<ResourceTableType>> types;
|
||||
|
||||
ResourceTablePackage() = default;
|
||||
ResourceTableType* findType(ResourceType type);
|
||||
|
||||
ResourceTableType* findOrCreateType(const ResourceType type);
|
||||
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(ResourceTablePackage);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -144,8 +179,6 @@ struct ResourceTablePackage {
|
||||
class ResourceTable {
|
||||
public:
|
||||
ResourceTable() = default;
|
||||
ResourceTable(const ResourceTable&) = delete;
|
||||
ResourceTable& operator=(const ResourceTable&) = delete;
|
||||
|
||||
/**
|
||||
* When a collision of resources occurs, this method decides which value to keep.
|
||||
@@ -155,38 +188,59 @@ public:
|
||||
*/
|
||||
static int resolveValueCollision(Value* existing, Value* incoming);
|
||||
|
||||
bool addResource(const ResourceNameRef& name, const ConfigDescription& config,
|
||||
std::unique_ptr<Value> value, IDiagnostics* diag);
|
||||
|
||||
bool addResource(const ResourceNameRef& name, const ResourceId resId,
|
||||
const ConfigDescription& config, std::unique_ptr<Value> value,
|
||||
bool addResource(const ResourceNameRef& name,
|
||||
const ConfigDescription& config,
|
||||
const StringPiece& product,
|
||||
std::unique_ptr<Value> value,
|
||||
IDiagnostics* diag);
|
||||
|
||||
bool addFileReference(const ResourceNameRef& name, const ConfigDescription& config,
|
||||
const Source& source, const StringPiece16& path,
|
||||
bool addResource(const ResourceNameRef& name,
|
||||
const ResourceId resId,
|
||||
const ConfigDescription& config,
|
||||
const StringPiece& product,
|
||||
std::unique_ptr<Value> value,
|
||||
IDiagnostics* diag);
|
||||
|
||||
bool addFileReference(const ResourceNameRef& name,
|
||||
const ConfigDescription& config,
|
||||
const Source& source,
|
||||
const StringPiece16& path,
|
||||
IDiagnostics* diag);
|
||||
|
||||
bool addFileReference(const ResourceNameRef& name, const ConfigDescription& config,
|
||||
const Source& source, const StringPiece16& path,
|
||||
std::function<int(Value*,Value*)> conflictResolver, IDiagnostics* diag);
|
||||
bool addFileReference(const ResourceNameRef& name,
|
||||
const ConfigDescription& config,
|
||||
const Source& source,
|
||||
const StringPiece16& path,
|
||||
std::function<int(Value*,Value*)> conflictResolver,
|
||||
IDiagnostics* diag);
|
||||
|
||||
/**
|
||||
* 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,
|
||||
std::unique_ptr<Value> value, IDiagnostics* diag);
|
||||
|
||||
bool addResourceAllowMangled(const ResourceNameRef& name, const ResourceId id,
|
||||
const ConfigDescription& config, std::unique_ptr<Value> value,
|
||||
bool addResourceAllowMangled(const ResourceNameRef& name,
|
||||
const ConfigDescription& config,
|
||||
const StringPiece& product,
|
||||
std::unique_ptr<Value> value,
|
||||
IDiagnostics* diag);
|
||||
|
||||
bool setSymbolState(const ResourceNameRef& name, const ResourceId resId,
|
||||
const Symbol& symbol, IDiagnostics* diag);
|
||||
bool addResourceAllowMangled(const ResourceNameRef& name,
|
||||
const ResourceId id,
|
||||
const ConfigDescription& config,
|
||||
const StringPiece& product,
|
||||
std::unique_ptr<Value> value,
|
||||
IDiagnostics* diag);
|
||||
|
||||
bool setSymbolStateAllowMangled(const ResourceNameRef& name, const ResourceId resId,
|
||||
const Symbol& symbol, IDiagnostics* diag);
|
||||
bool setSymbolState(const ResourceNameRef& name,
|
||||
const ResourceId resId,
|
||||
const Symbol& symbol,
|
||||
IDiagnostics* diag);
|
||||
|
||||
bool setSymbolStateAllowMangled(const ResourceNameRef& name,
|
||||
const ResourceId resId,
|
||||
const Symbol& symbol,
|
||||
IDiagnostics* diag);
|
||||
|
||||
struct SearchResult {
|
||||
ResourceTablePackage* package;
|
||||
@@ -229,13 +283,19 @@ private:
|
||||
bool addResourceImpl(const ResourceNameRef& name,
|
||||
ResourceId resId,
|
||||
const ConfigDescription& config,
|
||||
const StringPiece& product,
|
||||
std::unique_ptr<Value> value,
|
||||
const char16_t* validChars,
|
||||
std::function<int(Value*,Value*)> conflictResolver,
|
||||
IDiagnostics* diag);
|
||||
|
||||
bool setSymbolStateImpl(const ResourceNameRef& name, ResourceId resId,
|
||||
const Symbol& symbol, const char16_t* validChars, IDiagnostics* diag);
|
||||
bool setSymbolStateImpl(const ResourceNameRef& name,
|
||||
ResourceId resId,
|
||||
const Symbol& symbol,
|
||||
const char16_t* validChars,
|
||||
IDiagnostics* diag);
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(ResourceTable);
|
||||
};
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
@@ -43,13 +43,13 @@ TEST_F(ResourceTableTest, FailToAddResourceWithBadName) {
|
||||
|
||||
EXPECT_FALSE(table.addResource(
|
||||
ResourceNameRef(u"android", ResourceType::kId, u"hey,there"),
|
||||
ConfigDescription{},
|
||||
ConfigDescription{}, "",
|
||||
test::ValueBuilder<Id>().setSource("test.xml", 21u).build(),
|
||||
&mDiagnostics));
|
||||
|
||||
EXPECT_FALSE(table.addResource(
|
||||
ResourceNameRef(u"android", ResourceType::kId, u"hey:there"),
|
||||
ConfigDescription{},
|
||||
ConfigDescription{}, "",
|
||||
test::ValueBuilder<Id>().setSource("test.xml", 21u).build(),
|
||||
&mDiagnostics));
|
||||
}
|
||||
@@ -59,6 +59,7 @@ TEST_F(ResourceTableTest, AddOneResource) {
|
||||
|
||||
EXPECT_TRUE(table.addResource(test::parseNameOrDie(u"@android:attr/id"),
|
||||
ConfigDescription{},
|
||||
"",
|
||||
test::ValueBuilder<Id>()
|
||||
.setSource("test/path/file.xml", 23u).build(),
|
||||
&mDiagnostics));
|
||||
@@ -76,24 +77,28 @@ TEST_F(ResourceTableTest, AddMultipleResources) {
|
||||
EXPECT_TRUE(table.addResource(
|
||||
test::parseNameOrDie(u"@android:attr/layout_width"),
|
||||
config,
|
||||
"",
|
||||
test::ValueBuilder<Id>().setSource("test/path/file.xml", 10u).build(),
|
||||
&mDiagnostics));
|
||||
|
||||
EXPECT_TRUE(table.addResource(
|
||||
test::parseNameOrDie(u"@android:attr/id"),
|
||||
config,
|
||||
"",
|
||||
test::ValueBuilder<Id>().setSource("test/path/file.xml", 12u).build(),
|
||||
&mDiagnostics));
|
||||
|
||||
EXPECT_TRUE(table.addResource(
|
||||
test::parseNameOrDie(u"@android:string/ok"),
|
||||
config,
|
||||
"",
|
||||
test::ValueBuilder<Id>().setSource("test/path/file.xml", 14u).build(),
|
||||
&mDiagnostics));
|
||||
|
||||
EXPECT_TRUE(table.addResource(
|
||||
test::parseNameOrDie(u"@android:string/ok"),
|
||||
languageConfig,
|
||||
"",
|
||||
test::ValueBuilder<BinaryPrimitive>(android::Res_value{})
|
||||
.setSource("test/path/file.xml", 20u)
|
||||
.build(),
|
||||
@@ -110,18 +115,49 @@ TEST_F(ResourceTableTest, OverrideWeakResourceValue) {
|
||||
ResourceTable table;
|
||||
|
||||
ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:attr/foo"), ConfigDescription{},
|
||||
util::make_unique<Attribute>(true), &mDiagnostics));
|
||||
"", util::make_unique<Attribute>(true), &mDiagnostics));
|
||||
|
||||
Attribute* attr = test::getValue<Attribute>(&table, u"@android:attr/foo");
|
||||
ASSERT_NE(nullptr, attr);
|
||||
EXPECT_TRUE(attr->isWeak());
|
||||
|
||||
ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:attr/foo"), ConfigDescription{},
|
||||
util::make_unique<Attribute>(false), &mDiagnostics));
|
||||
"", util::make_unique<Attribute>(false), &mDiagnostics));
|
||||
|
||||
attr = test::getValue<Attribute>(&table, u"@android:attr/foo");
|
||||
ASSERT_NE(nullptr, attr);
|
||||
EXPECT_FALSE(attr->isWeak());
|
||||
}
|
||||
|
||||
TEST_F(ResourceTableTest, ProductVaryingValues) {
|
||||
ResourceTable table;
|
||||
|
||||
EXPECT_TRUE(table.addResource(test::parseNameOrDie(u"@android:string/foo"),
|
||||
test::parseConfigOrDie("land"),
|
||||
"tablet",
|
||||
util::make_unique<Id>(),
|
||||
&mDiagnostics));
|
||||
EXPECT_TRUE(table.addResource(test::parseNameOrDie(u"@android:string/foo"),
|
||||
test::parseConfigOrDie("land"),
|
||||
"phone",
|
||||
util::make_unique<Id>(),
|
||||
&mDiagnostics));
|
||||
|
||||
EXPECT_NE(nullptr, test::getValueForConfigAndProduct<Id>(&table, u"@android:string/foo",
|
||||
test::parseConfigOrDie("land"),
|
||||
"tablet"));
|
||||
EXPECT_NE(nullptr, test::getValueForConfigAndProduct<Id>(&table, u"@android:string/foo",
|
||||
test::parseConfigOrDie("land"),
|
||||
"phone"));
|
||||
|
||||
Maybe<ResourceTable::SearchResult> sr = table.findResource(
|
||||
test::parseNameOrDie(u"@android:string/foo"));
|
||||
AAPT_ASSERT_TRUE(sr);
|
||||
std::vector<ResourceConfigValue*> values = sr.value().entry->findAllValues(
|
||||
test::parseConfigOrDie("land"));
|
||||
ASSERT_EQ(2u, values.size());
|
||||
EXPECT_EQ(std::string("phone"), values[0]->product);
|
||||
EXPECT_EQ(std::string("tablet"), values[1]->product);
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
@@ -146,7 +146,7 @@ inline void visitAllValuesInPackage(ResourceTablePackage* pkg, RawValueVisitor*
|
||||
for (auto& type : pkg->types) {
|
||||
for (auto& entry : type->entries) {
|
||||
for (auto& configValue : entry->values) {
|
||||
configValue.value->accept(visitor);
|
||||
configValue->value->accept(visitor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,7 +107,6 @@ static Maybe<ResourcePathData> extractResourcePathData(const std::string& path,
|
||||
struct CompileOptions {
|
||||
std::string outputPath;
|
||||
Maybe<std::string> resDir;
|
||||
std::vector<std::u16string> products;
|
||||
bool pseudolocalize = false;
|
||||
bool legacyMode = false;
|
||||
bool verbose = false;
|
||||
@@ -198,7 +197,6 @@ static bool compileTable(IAaptContext* context, const CompileOptions& options,
|
||||
xml::XmlPullParser xmlParser(fin);
|
||||
|
||||
ResourceParserOptions parserOptions;
|
||||
parserOptions.products = options.products;
|
||||
parserOptions.errorOnPositionalArguments = !options.legacyMode;
|
||||
|
||||
// If the filename includes donottranslate, then the default translatable is false.
|
||||
@@ -457,11 +455,8 @@ public:
|
||||
int compile(const std::vector<StringPiece>& args) {
|
||||
CompileOptions options;
|
||||
|
||||
Maybe<std::string> productList;
|
||||
Flags flags = Flags()
|
||||
.requiredFlag("-o", "Output path", &options.outputPath)
|
||||
.optionalFlag("--product", "Comma separated list of product types to compile",
|
||||
&productList)
|
||||
.optionalFlag("--dir", "Directory to scan for resources", &options.resDir)
|
||||
.optionalSwitch("--pseudo-localize", "Generate resources for pseudo-locales "
|
||||
"(en-XA and ar-XB)", &options.pseudolocalize)
|
||||
@@ -472,12 +467,6 @@ int compile(const std::vector<StringPiece>& args) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (productList) {
|
||||
for (StringPiece part : util::tokenize<char>(productList.value(), ',')) {
|
||||
options.products.push_back(util::utf8ToUtf16(part));
|
||||
}
|
||||
}
|
||||
|
||||
CompileContext context;
|
||||
std::unique_ptr<IArchiveWriter> archiveWriter;
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
#include "ValueVisitor.h"
|
||||
#include "compile/PseudolocaleGenerator.h"
|
||||
#include "compile/Pseudolocalizer.h"
|
||||
#include "util/Comparators.h"
|
||||
|
||||
namespace aapt {
|
||||
|
||||
@@ -208,10 +207,12 @@ ConfigDescription modifyConfigForPseudoLocale(const ConfigDescription& base,
|
||||
return modified;
|
||||
}
|
||||
|
||||
void pseudolocalizeIfNeeded(std::vector<ResourceConfigValue>* configValues,
|
||||
Pseudolocalizer::Method method, StringPool* pool, Value* value) {
|
||||
void pseudolocalizeIfNeeded(const Pseudolocalizer::Method method,
|
||||
ResourceConfigValue* originalValue,
|
||||
StringPool* pool,
|
||||
ResourceEntry* entry) {
|
||||
Visitor visitor(pool, method);
|
||||
value->accept(&visitor);
|
||||
originalValue->value->accept(&visitor);
|
||||
|
||||
std::unique_ptr<Value> localizedValue;
|
||||
if (visitor.mValue) {
|
||||
@@ -220,16 +221,18 @@ void pseudolocalizeIfNeeded(std::vector<ResourceConfigValue>* configValues,
|
||||
localizedValue = std::move(visitor.mItem);
|
||||
}
|
||||
|
||||
if (localizedValue) {
|
||||
ConfigDescription pseudolocalizedConfig = modifyConfigForPseudoLocale(ConfigDescription{},
|
||||
method);
|
||||
auto iter = std::lower_bound(configValues->begin(), configValues->end(),
|
||||
pseudolocalizedConfig, cmp::lessThanConfig);
|
||||
if (iter == configValues->end() || iter->config != pseudolocalizedConfig) {
|
||||
// The pseudolocalized config doesn't exist, add it.
|
||||
configValues->insert(iter, ResourceConfigValue{ pseudolocalizedConfig,
|
||||
std::move(localizedValue) });
|
||||
}
|
||||
if (!localizedValue) {
|
||||
return;
|
||||
}
|
||||
|
||||
ConfigDescription configWithAccent = modifyConfigForPseudoLocale(
|
||||
originalValue->config, method);
|
||||
|
||||
ResourceConfigValue* newConfigValue = entry->findOrCreateValue(
|
||||
configWithAccent, originalValue->product);
|
||||
if (!newConfigValue->value) {
|
||||
// Only use auto-generated pseudo-localization if none is defined.
|
||||
newConfigValue->value = std::move(localizedValue);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -239,18 +242,13 @@ bool PseudolocaleGenerator::consume(IAaptContext* context, ResourceTable* table)
|
||||
for (auto& package : table->packages) {
|
||||
for (auto& type : package->types) {
|
||||
for (auto& entry : type->entries) {
|
||||
auto iter = std::lower_bound(entry->values.begin(), entry->values.end(),
|
||||
ConfigDescription{}, cmp::lessThanConfig);
|
||||
if (iter != entry->values.end() && iter->config == ConfigDescription{}) {
|
||||
// Only pseudolocalize the default configuration.
|
||||
|
||||
// The iterator will be invalidated, so grab a pointer to the value.
|
||||
Value* originalValue = iter->value.get();
|
||||
|
||||
pseudolocalizeIfNeeded(&entry->values, Pseudolocalizer::Method::kAccent,
|
||||
&table->stringPool, originalValue);
|
||||
pseudolocalizeIfNeeded(&entry->values, Pseudolocalizer::Method::kBidi,
|
||||
&table->stringPool, originalValue);
|
||||
std::vector<ResourceConfigValue*> values = entry->findAllValues(
|
||||
ConfigDescription::defaultConfig());
|
||||
for (ResourceConfigValue* value : values) {
|
||||
pseudolocalizeIfNeeded(Pseudolocalizer::Method::kAccent, value,
|
||||
&table->stringPool, entry.get());
|
||||
pseudolocalizeIfNeeded(Pseudolocalizer::Method::kBidi, value,
|
||||
&table->stringPool, entry.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -402,10 +402,10 @@ private:
|
||||
|
||||
const size_t configCount = entry->values.size();
|
||||
for (size_t i = 0; i < configCount; i++) {
|
||||
const ConfigDescription& config = entry->values[i].config;
|
||||
const ConfigDescription& config = entry->values[i]->config;
|
||||
for (size_t j = i + 1; j < configCount; j++) {
|
||||
configMasks[entry->id.value()] |= util::hostToDevice32(
|
||||
config.diff(entry->values[j].config));
|
||||
config.diff(entry->values[j]->config));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -445,8 +445,8 @@ private:
|
||||
|
||||
// Group values by configuration.
|
||||
for (auto& configValue : entry->values) {
|
||||
configToEntryListMap[configValue.config].push_back(FlatEntry{
|
||||
entry, configValue.value.get(), keyIndex });
|
||||
configToEntryListMap[configValue->config].push_back(FlatEntry{
|
||||
entry, configValue->value.get(), keyIndex });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
#include "java/AnnotationProcessor.h"
|
||||
#include "java/ClassDefinitionWriter.h"
|
||||
#include "java/JavaClassGenerator.h"
|
||||
#include "util/Comparators.h"
|
||||
#include "util/StringPiece.h"
|
||||
|
||||
#include <algorithm>
|
||||
@@ -258,12 +257,12 @@ bool JavaClassGenerator::writeEntriesForClass(ClassDefinitionWriter* outClassDef
|
||||
}
|
||||
|
||||
for (const auto& configValue : entry->values) {
|
||||
processor.appendComment(configValue.value->getComment());
|
||||
processor.appendComment(configValue->value->getComment());
|
||||
}
|
||||
|
||||
// If this is an Attribute, append the format Javadoc.
|
||||
if (!entry->values.empty()) {
|
||||
if (Attribute* attr = valueCast<Attribute>(entry->values.front().value.get())) {
|
||||
if (Attribute* attr = valueCast<Attribute>(entry->values.front()->value.get())) {
|
||||
// We list out the available values for the given attribute.
|
||||
addAttributeFormatDoc(&processor, attr);
|
||||
}
|
||||
@@ -272,7 +271,7 @@ bool JavaClassGenerator::writeEntriesForClass(ClassDefinitionWriter* outClassDef
|
||||
if (type->type == ResourceType::kStyleable) {
|
||||
assert(!entry->values.empty());
|
||||
const Styleable* styleable = static_cast<const Styleable*>(
|
||||
entry->values.front().value.get());
|
||||
entry->values.front()->value.get());
|
||||
writeStyleableEntryForClass(outClassDef, &processor, packageNameToGenerate,
|
||||
unmangledName, styleable);
|
||||
} else {
|
||||
@@ -311,11 +310,10 @@ bool JavaClassGenerator::generate(const StringPiece16& packageNameToGenerate,
|
||||
|
||||
if (type->type == ResourceType::kAttr) {
|
||||
// Also include private attributes in this same class.
|
||||
auto iter = std::lower_bound(package->types.begin(), package->types.end(),
|
||||
ResourceType::kAttrPrivate, cmp::lessThanType);
|
||||
if (iter != package->types.end() && (*iter)->type == ResourceType::kAttrPrivate) {
|
||||
ResourceTableType* privType = package->findType(ResourceType::kAttrPrivate);
|
||||
if (privType) {
|
||||
result = writeEntriesForClass(&classDef, packageNameToGenerate,
|
||||
package.get(), iter->get());
|
||||
package.get(), privType);
|
||||
if (!result) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -18,9 +18,7 @@
|
||||
#include "ResourceTable.h"
|
||||
#include "SdkConstants.h"
|
||||
#include "ValueVisitor.h"
|
||||
|
||||
#include "link/Linkers.h"
|
||||
#include "util/Comparators.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
@@ -31,7 +29,12 @@ bool shouldGenerateVersionedResource(const ResourceEntry* entry, const ConfigDes
|
||||
const int sdkVersionToGenerate) {
|
||||
assert(sdkVersionToGenerate > config.sdkVersion);
|
||||
const auto endIter = entry->values.end();
|
||||
auto iter = std::lower_bound(entry->values.begin(), endIter, config, cmp::lessThanConfig);
|
||||
auto iter = entry->values.begin();
|
||||
for (; iter != endIter; ++iter) {
|
||||
if ((*iter)->config == config) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// The source config came from this list, so it should be here.
|
||||
assert(iter != entry->values.end());
|
||||
@@ -45,10 +48,10 @@ bool shouldGenerateVersionedResource(const ResourceEntry* entry, const ConfigDes
|
||||
// are no higher sdk level versions of this resource.
|
||||
ConfigDescription tempConfig(config);
|
||||
for (; iter != endIter; ++iter) {
|
||||
tempConfig.sdkVersion = iter->config.sdkVersion;
|
||||
if (tempConfig == iter->config) {
|
||||
tempConfig.sdkVersion = (*iter)->config.sdkVersion;
|
||||
if (tempConfig == (*iter)->config) {
|
||||
// The two configs are the same, check the sdk version.
|
||||
return sdkVersionToGenerate < iter->config.sdkVersion;
|
||||
return sdkVersionToGenerate < (*iter)->config.sdkVersion;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,14 +68,14 @@ bool AutoVersioner::consume(IAaptContext* context, ResourceTable* table) {
|
||||
|
||||
for (auto& entry : type->entries) {
|
||||
for (size_t i = 0; i < entry->values.size(); i++) {
|
||||
ResourceConfigValue& configValue = entry->values[i];
|
||||
if (configValue.config.sdkVersion >= SDK_LOLLIPOP_MR1) {
|
||||
ResourceConfigValue* configValue = entry->values[i].get();
|
||||
if (configValue->config.sdkVersion >= SDK_LOLLIPOP_MR1) {
|
||||
// If this configuration is only used on L-MR1 then we don't need
|
||||
// to do anything since we use private attributes since that version.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Style* style = valueCast<Style>(configValue.value.get())) {
|
||||
if (Style* style = valueCast<Style>(configValue->value.get())) {
|
||||
Maybe<size_t> minSdkStripped;
|
||||
std::vector<Style::Entry> stripped;
|
||||
|
||||
@@ -82,7 +85,7 @@ bool AutoVersioner::consume(IAaptContext* context, ResourceTable* table) {
|
||||
|
||||
// Find the SDK level that is higher than the configuration allows.
|
||||
const size_t sdkLevel = findAttributeSdkLevel(iter->key.id.value());
|
||||
if (sdkLevel > std::max<size_t>(configValue.config.sdkVersion, 1)) {
|
||||
if (sdkLevel > std::max<size_t>(configValue->config.sdkVersion, 1)) {
|
||||
// Record that we are about to strip this.
|
||||
stripped.emplace_back(std::move(*iter));
|
||||
|
||||
@@ -105,10 +108,10 @@ bool AutoVersioner::consume(IAaptContext* context, ResourceTable* table) {
|
||||
// there is no other defined resource for the version we want to
|
||||
// generate.
|
||||
if (shouldGenerateVersionedResource(entry.get(),
|
||||
configValue.config,
|
||||
configValue->config,
|
||||
minSdkStripped.value())) {
|
||||
// Let's create a new Style for this versioned resource.
|
||||
ConfigDescription newConfig(configValue.config);
|
||||
ConfigDescription newConfig(configValue->config);
|
||||
newConfig.sdkVersion = minSdkStripped.value();
|
||||
|
||||
std::unique_ptr<Style> newStyle(style->clone(&table->stringPool));
|
||||
@@ -121,14 +124,8 @@ bool AutoVersioner::consume(IAaptContext* context, ResourceTable* table) {
|
||||
std::make_move_iterator(stripped.end()));
|
||||
|
||||
// Insert the new Resource into the correct place.
|
||||
auto iter = std::lower_bound(entry->values.begin(),
|
||||
entry->values.end(),
|
||||
newConfig,
|
||||
cmp::lessThanConfig);
|
||||
|
||||
entry->values.insert(
|
||||
iter,
|
||||
ResourceConfigValue{ newConfig, std::move(newStyle) });
|
||||
entry->findOrCreateValue(newConfig, {})->value =
|
||||
std::move(newStyle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,9 +15,7 @@
|
||||
*/
|
||||
|
||||
#include "ConfigDescription.h"
|
||||
|
||||
#include "link/Linkers.h"
|
||||
|
||||
#include "test/Builders.h"
|
||||
#include "test/Context.h"
|
||||
|
||||
@@ -31,9 +29,9 @@ TEST(AutoVersionerTest, GenerateVersionedResources) {
|
||||
const ConfigDescription sw600dpLandConfig = test::parseConfigOrDie("sw600dp-land");
|
||||
|
||||
ResourceEntry entry(u"foo");
|
||||
entry.values.push_back(ResourceConfigValue{ defaultConfig });
|
||||
entry.values.push_back(ResourceConfigValue{ landConfig });
|
||||
entry.values.push_back(ResourceConfigValue{ sw600dpLandConfig });
|
||||
entry.values.push_back(util::make_unique<ResourceConfigValue>(defaultConfig, ""));
|
||||
entry.values.push_back(util::make_unique<ResourceConfigValue>(landConfig, ""));
|
||||
entry.values.push_back(util::make_unique<ResourceConfigValue>(sw600dpLandConfig, ""));
|
||||
|
||||
EXPECT_TRUE(shouldGenerateVersionedResource(&entry, defaultConfig, 17));
|
||||
EXPECT_TRUE(shouldGenerateVersionedResource(&entry, landConfig, 17));
|
||||
@@ -45,9 +43,9 @@ TEST(AutoVersionerTest, GenerateVersionedResourceWhenHigherVersionExists) {
|
||||
const ConfigDescription v21Config = test::parseConfigOrDie("v21");
|
||||
|
||||
ResourceEntry entry(u"foo");
|
||||
entry.values.push_back(ResourceConfigValue{ defaultConfig });
|
||||
entry.values.push_back(ResourceConfigValue{ sw600dpV13Config });
|
||||
entry.values.push_back(ResourceConfigValue{ v21Config });
|
||||
entry.values.push_back(util::make_unique<ResourceConfigValue>(defaultConfig, ""));
|
||||
entry.values.push_back(util::make_unique<ResourceConfigValue>(sw600dpV13Config, ""));
|
||||
entry.values.push_back(util::make_unique<ResourceConfigValue>(v21Config, ""));
|
||||
|
||||
EXPECT_TRUE(shouldGenerateVersionedResource(&entry, defaultConfig, 17));
|
||||
EXPECT_FALSE(shouldGenerateVersionedResource(&entry, defaultConfig, 22));
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
#include "java/ManifestClassGenerator.h"
|
||||
#include "java/ProguardRules.h"
|
||||
#include "link/Linkers.h"
|
||||
#include "link/ProductFilter.h"
|
||||
#include "link/ReferenceLinker.h"
|
||||
#include "link/ManifestFixer.h"
|
||||
#include "link/TableMerger.h"
|
||||
@@ -70,6 +71,7 @@ struct LinkOptions {
|
||||
Maybe<std::u16string> privateSymbols;
|
||||
ManifestFixerOptions manifestFixerOptions;
|
||||
IConfigFilter* configFilter = nullptr;
|
||||
std::unordered_set<std::string> products;
|
||||
};
|
||||
|
||||
struct LinkContext : public IAaptContext {
|
||||
@@ -292,16 +294,16 @@ public:
|
||||
for (const auto& configValue : entry->values) {
|
||||
// Special case the occurrence of an ID that is being generated for the
|
||||
// 'android' package. This is due to legacy reasons.
|
||||
if (valueCast<Id>(configValue.value.get()) &&
|
||||
if (valueCast<Id>(configValue->value.get()) &&
|
||||
package->name == u"android") {
|
||||
mContext->getDiagnostics()->warn(
|
||||
DiagMessage(configValue.value->getSource())
|
||||
DiagMessage(configValue->value->getSource())
|
||||
<< "generated id '" << resName
|
||||
<< "' for external package '" << package->name
|
||||
<< "'");
|
||||
} else {
|
||||
mContext->getDiagnostics()->error(
|
||||
DiagMessage(configValue.value->getSource())
|
||||
DiagMessage(configValue->value->getSource())
|
||||
<< "defined resource '" << resName
|
||||
<< "' for external package '" << package->name
|
||||
<< "'");
|
||||
@@ -512,7 +514,10 @@ public:
|
||||
|
||||
std::unique_ptr<Id> id = util::make_unique<Id>();
|
||||
id->setSource(fileDesc->source.withLine(exportedSymbol.line));
|
||||
bool result = mFinalTable.addResourceAllowMangled(resName, {}, std::move(id),
|
||||
bool result = mFinalTable.addResourceAllowMangled(resName,
|
||||
ConfigDescription::defaultConfig(),
|
||||
std::string(),
|
||||
std::move(id),
|
||||
mContext->getDiagnostics());
|
||||
if (!result) {
|
||||
return false;
|
||||
@@ -681,6 +686,12 @@ public:
|
||||
mContext->getDiagnostics()->error(DiagMessage() << "failed linking references");
|
||||
return 1;
|
||||
}
|
||||
|
||||
ProductFilter productFilter(mOptions.products);
|
||||
if (!productFilter.consume(mContext, &mFinalTable)) {
|
||||
mContext->getDiagnostics()->error(DiagMessage() << "failed stripping products");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
proguard::KeepSet proguardKeepSet;
|
||||
@@ -931,6 +942,7 @@ int link(const std::vector<StringPiece>& args) {
|
||||
Maybe<std::string> customJavaPackage;
|
||||
std::vector<std::string> extraJavaPackages;
|
||||
Maybe<std::string> configs;
|
||||
Maybe<std::string> productList;
|
||||
bool legacyXFlag = false;
|
||||
bool requireLocalization = false;
|
||||
Flags flags = Flags()
|
||||
@@ -954,6 +966,8 @@ int link(const std::vector<StringPiece>& args) {
|
||||
&requireLocalization)
|
||||
.optionalFlag("-c", "Comma separated list of configurations to include. The default\n"
|
||||
"is all configurations", &configs)
|
||||
.optionalFlag("--product", "Comma separated list of product names to keep",
|
||||
&productList)
|
||||
.optionalSwitch("--output-to-dir", "Outputs the APK contents to a directory specified "
|
||||
"by -o",
|
||||
&options.outputToDirectory)
|
||||
@@ -1039,6 +1053,14 @@ int link(const std::vector<StringPiece>& args) {
|
||||
}
|
||||
}
|
||||
|
||||
if (productList) {
|
||||
for (StringPiece product : util::tokenize<char>(productList.value(), ',')) {
|
||||
if (product != "" && product != "default") {
|
||||
options.products.insert(product.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AxisConfigFilter filter;
|
||||
if (configs) {
|
||||
for (const StringPiece& configStr : util::tokenize<char>(configs.value(), ',')) {
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
namespace aapt {
|
||||
|
||||
class ResourceTable;
|
||||
struct ResourceEntry;
|
||||
class ResourceEntry;
|
||||
struct ConfigDescription;
|
||||
|
||||
/**
|
||||
|
||||
118
tools/aapt2/link/ProductFilter.cpp
Normal file
118
tools/aapt2/link/ProductFilter.cpp
Normal file
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* Copyright (C) 2016 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "link/ProductFilter.h"
|
||||
|
||||
namespace aapt {
|
||||
|
||||
ProductFilter::ResourceConfigValueIter
|
||||
ProductFilter::selectProductToKeep(const ResourceNameRef& name,
|
||||
const ResourceConfigValueIter begin,
|
||||
const ResourceConfigValueIter end,
|
||||
IDiagnostics* diag) {
|
||||
ResourceConfigValueIter defaultProductIter = end;
|
||||
ResourceConfigValueIter selectedProductIter = end;
|
||||
|
||||
for (ResourceConfigValueIter iter = begin; iter != end; ++iter) {
|
||||
ResourceConfigValue* configValue = iter->get();
|
||||
if (mProducts.find(configValue->product) != mProducts.end()) {
|
||||
if (selectedProductIter != end) {
|
||||
// We have two possible values for this product!
|
||||
diag->error(DiagMessage(configValue->value->getSource())
|
||||
<< "selection of product '" << configValue->product
|
||||
<< "' for resource " << name << " is ambiguous");
|
||||
|
||||
ResourceConfigValue* previouslySelectedConfigValue = selectedProductIter->get();
|
||||
diag->note(DiagMessage(previouslySelectedConfigValue->value->getSource())
|
||||
<< "product '" << previouslySelectedConfigValue->product
|
||||
<< "' is also a candidate");
|
||||
return end;
|
||||
}
|
||||
|
||||
// Select this product.
|
||||
selectedProductIter = iter;
|
||||
}
|
||||
|
||||
if (configValue->product.empty() || configValue->product == "default") {
|
||||
if (defaultProductIter != end) {
|
||||
// We have two possible default values.
|
||||
diag->error(DiagMessage(configValue->value->getSource())
|
||||
<< "multiple default products defined for resource " << name);
|
||||
|
||||
ResourceConfigValue* previouslyDefaultConfigValue = defaultProductIter->get();
|
||||
diag->note(DiagMessage(previouslyDefaultConfigValue->value->getSource())
|
||||
<< "default product also defined here");
|
||||
return end;
|
||||
}
|
||||
|
||||
// Mark the default.
|
||||
defaultProductIter = iter;
|
||||
}
|
||||
}
|
||||
|
||||
if (defaultProductIter == end) {
|
||||
diag->error(DiagMessage() << "no default product defined for resource " << name);
|
||||
return end;
|
||||
}
|
||||
|
||||
if (selectedProductIter == end) {
|
||||
selectedProductIter = defaultProductIter;
|
||||
}
|
||||
return selectedProductIter;
|
||||
}
|
||||
|
||||
bool ProductFilter::consume(IAaptContext* context, ResourceTable* table) {
|
||||
bool error = false;
|
||||
for (auto& pkg : table->packages) {
|
||||
for (auto& type : pkg->types) {
|
||||
for (auto& entry : type->entries) {
|
||||
std::vector<std::unique_ptr<ResourceConfigValue>> newValues;
|
||||
|
||||
ResourceConfigValueIter iter = entry->values.begin();
|
||||
ResourceConfigValueIter startRangeIter = iter;
|
||||
while (iter != entry->values.end()) {
|
||||
++iter;
|
||||
if (iter == entry->values.end() ||
|
||||
(*iter)->config != (*startRangeIter)->config) {
|
||||
|
||||
// End of the array, or we saw a different config,
|
||||
// so this must be the end of a range of products.
|
||||
// Select the product to keep from the set of products defined.
|
||||
ResourceNameRef name(pkg->name, type->type, entry->name);
|
||||
auto valueToKeep = selectProductToKeep(name, startRangeIter, iter,
|
||||
context->getDiagnostics());
|
||||
if (valueToKeep == iter) {
|
||||
// An error occurred, we could not pick a product.
|
||||
error = true;
|
||||
} else {
|
||||
// We selected a product to keep. Move it to the new array.
|
||||
newValues.push_back(std::move(*valueToKeep));
|
||||
}
|
||||
|
||||
// Start the next range of products.
|
||||
startRangeIter = iter;
|
||||
}
|
||||
}
|
||||
|
||||
// Now move the new values in to place.
|
||||
entry->values = std::move(newValues);
|
||||
}
|
||||
}
|
||||
}
|
||||
return !error;
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
49
tools/aapt2/link/ProductFilter.h
Normal file
49
tools/aapt2/link/ProductFilter.h
Normal file
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright (C) 2016 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef AAPT_LINK_PRODUCTFILTER_H
|
||||
#define AAPT_LINK_PRODUCTFILTER_H
|
||||
|
||||
#include "ResourceTable.h"
|
||||
#include "process/IResourceTableConsumer.h"
|
||||
|
||||
#include <android-base/macros.h>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
class ProductFilter {
|
||||
public:
|
||||
using ResourceConfigValueIter = std::vector<std::unique_ptr<ResourceConfigValue>>::iterator;
|
||||
|
||||
ProductFilter(std::unordered_set<std::string> products) : mProducts(products) { }
|
||||
|
||||
ResourceConfigValueIter selectProductToKeep(const ResourceNameRef& name,
|
||||
const ResourceConfigValueIter begin,
|
||||
const ResourceConfigValueIter end,
|
||||
IDiagnostics* diag);
|
||||
|
||||
bool consume(IAaptContext* context, ResourceTable* table);
|
||||
|
||||
private:
|
||||
std::unordered_set<std::string> mProducts;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(ProductFilter);
|
||||
};
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
#endif /* AAPT_LINK_PRODUCTFILTER_H */
|
||||
136
tools/aapt2/link/ProductFilter_test.cpp
Normal file
136
tools/aapt2/link/ProductFilter_test.cpp
Normal file
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
* Copyright (C) 2016 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "link/ProductFilter.h"
|
||||
#include "test/Builders.h"
|
||||
#include "test/Context.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
TEST(ProductFilterTest, SelectTwoProducts) {
|
||||
std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
|
||||
|
||||
const ConfigDescription land = test::parseConfigOrDie("land");
|
||||
const ConfigDescription port = test::parseConfigOrDie("port");
|
||||
|
||||
ResourceTable table;
|
||||
ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:string/one"),
|
||||
land, "",
|
||||
test::ValueBuilder<Id>()
|
||||
.setSource(Source("land/default.xml")).build(),
|
||||
context->getDiagnostics()));
|
||||
ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:string/one"),
|
||||
land, "tablet",
|
||||
test::ValueBuilder<Id>()
|
||||
.setSource(Source("land/tablet.xml")).build(),
|
||||
context->getDiagnostics()));
|
||||
|
||||
ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:string/one"),
|
||||
port, "",
|
||||
test::ValueBuilder<Id>()
|
||||
.setSource(Source("port/default.xml")).build(),
|
||||
context->getDiagnostics()));
|
||||
ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:string/one"),
|
||||
port, "tablet",
|
||||
test::ValueBuilder<Id>()
|
||||
.setSource(Source("port/tablet.xml")).build(),
|
||||
context->getDiagnostics()));
|
||||
|
||||
ProductFilter filter({ "tablet" });
|
||||
ASSERT_TRUE(filter.consume(context.get(), &table));
|
||||
|
||||
EXPECT_EQ(nullptr, test::getValueForConfigAndProduct<Id>(&table, u"@android:string/one",
|
||||
land, ""));
|
||||
EXPECT_NE(nullptr, test::getValueForConfigAndProduct<Id>(&table, u"@android:string/one",
|
||||
land, "tablet"));
|
||||
EXPECT_EQ(nullptr, test::getValueForConfigAndProduct<Id>(&table, u"@android:string/one",
|
||||
port, ""));
|
||||
EXPECT_NE(nullptr, test::getValueForConfigAndProduct<Id>(&table, u"@android:string/one",
|
||||
port, "tablet"));
|
||||
}
|
||||
|
||||
TEST(ProductFilterTest, SelectDefaultProduct) {
|
||||
std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
|
||||
|
||||
ResourceTable table;
|
||||
ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:string/one"),
|
||||
ConfigDescription::defaultConfig(), "",
|
||||
test::ValueBuilder<Id>()
|
||||
.setSource(Source("default.xml")).build(),
|
||||
context->getDiagnostics()));
|
||||
ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:string/one"),
|
||||
ConfigDescription::defaultConfig(), "tablet",
|
||||
test::ValueBuilder<Id>()
|
||||
.setSource(Source("tablet.xml")).build(),
|
||||
context->getDiagnostics()));
|
||||
|
||||
ProductFilter filter({});
|
||||
ASSERT_TRUE(filter.consume(context.get(), &table));
|
||||
|
||||
EXPECT_NE(nullptr, test::getValueForConfigAndProduct<Id>(&table, u"@android:string/one",
|
||||
ConfigDescription::defaultConfig(),
|
||||
""));
|
||||
EXPECT_EQ(nullptr, test::getValueForConfigAndProduct<Id>(&table, u"@android:string/one",
|
||||
ConfigDescription::defaultConfig(),
|
||||
"tablet"));
|
||||
}
|
||||
|
||||
TEST(ProductFilterTest, FailOnAmbiguousProduct) {
|
||||
std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
|
||||
|
||||
ResourceTable table;
|
||||
ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:string/one"),
|
||||
ConfigDescription::defaultConfig(), "",
|
||||
test::ValueBuilder<Id>()
|
||||
.setSource(Source("default.xml")).build(),
|
||||
context->getDiagnostics()));
|
||||
ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:string/one"),
|
||||
ConfigDescription::defaultConfig(), "tablet",
|
||||
test::ValueBuilder<Id>()
|
||||
.setSource(Source("tablet.xml")).build(),
|
||||
context->getDiagnostics()));
|
||||
ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:string/one"),
|
||||
ConfigDescription::defaultConfig(), "no-sdcard",
|
||||
test::ValueBuilder<Id>()
|
||||
.setSource(Source("no-sdcard.xml")).build(),
|
||||
context->getDiagnostics()));
|
||||
|
||||
ProductFilter filter({ "tablet", "no-sdcard" });
|
||||
ASSERT_FALSE(filter.consume(context.get(), &table));
|
||||
}
|
||||
|
||||
TEST(ProductFilterTest, FailOnMultipleDefaults) {
|
||||
std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
|
||||
|
||||
ResourceTable table;
|
||||
ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:string/one"),
|
||||
ConfigDescription::defaultConfig(), "",
|
||||
test::ValueBuilder<Id>()
|
||||
.setSource(Source(".xml")).build(),
|
||||
context->getDiagnostics()));
|
||||
ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:string/one"),
|
||||
ConfigDescription::defaultConfig(), "default",
|
||||
test::ValueBuilder<Id>()
|
||||
.setSource(Source("default.xml")).build(),
|
||||
context->getDiagnostics()));
|
||||
|
||||
ProductFilter filter({});
|
||||
ASSERT_FALSE(filter.consume(context.get(), &table));
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
@@ -316,7 +316,7 @@ bool ReferenceLinker::consume(IAaptContext* context, ResourceTable* table) {
|
||||
&table->stringPool, &declStack, &callSite);
|
||||
|
||||
for (auto& configValue : entry->values) {
|
||||
configValue.value->accept(&visitor);
|
||||
configValue->value->accept(&visitor);
|
||||
}
|
||||
|
||||
if (visitor.hasError()) {
|
||||
|
||||
@@ -18,9 +18,7 @@
|
||||
#include "ResourceUtils.h"
|
||||
#include "ResourceValues.h"
|
||||
#include "ValueVisitor.h"
|
||||
|
||||
#include "link/TableMerger.h"
|
||||
#include "util/Comparators.h"
|
||||
#include "util/Util.h"
|
||||
|
||||
#include <cassert>
|
||||
@@ -197,28 +195,28 @@ bool TableMerger::doMerge(const Source& src,
|
||||
|
||||
ResourceNameRef resName(mMasterPackage->name, dstType->type, dstEntry->name);
|
||||
|
||||
for (ResourceConfigValue& srcValue : srcEntry->values) {
|
||||
auto iter = std::lower_bound(dstEntry->values.begin(), dstEntry->values.end(),
|
||||
srcValue.config, cmp::lessThanConfig);
|
||||
for (auto& srcValue : srcEntry->values) {
|
||||
ResourceConfigValue* dstValue = dstEntry->findValue(srcValue->config,
|
||||
srcValue->product);
|
||||
|
||||
const bool stripConfig = mOptions.filter ?
|
||||
!mOptions.filter->match(srcValue.config) : false;
|
||||
!mOptions.filter->match(srcValue->config) : false;
|
||||
|
||||
if (iter != dstEntry->values.end() && iter->config == srcValue.config) {
|
||||
if (dstValue) {
|
||||
const int collisionResult = ResourceTable::resolveValueCollision(
|
||||
iter->value.get(), srcValue.value.get());
|
||||
dstValue->value.get(), srcValue->value.get());
|
||||
if (collisionResult == 0 && !overlay) {
|
||||
// Error!
|
||||
ResourceNameRef resourceName(srcPackage->name,
|
||||
srcType->type,
|
||||
srcEntry->name);
|
||||
|
||||
mContext->getDiagnostics()->error(DiagMessage(srcValue.value->getSource())
|
||||
mContext->getDiagnostics()->error(DiagMessage(srcValue->value->getSource())
|
||||
<< "resource '" << resourceName
|
||||
<< "' has a conflicting value for "
|
||||
<< "configuration ("
|
||||
<< srcValue.config << ")");
|
||||
mContext->getDiagnostics()->note(DiagMessage(iter->value->getSource())
|
||||
<< srcValue->config << ")");
|
||||
mContext->getDiagnostics()->note(DiagMessage(dstValue->value->getSource())
|
||||
<< "originally defined here");
|
||||
error = true;
|
||||
continue;
|
||||
@@ -227,16 +225,18 @@ bool TableMerger::doMerge(const Source& src,
|
||||
continue;
|
||||
}
|
||||
|
||||
} else if (!stripConfig){
|
||||
// Insert a place holder value. We will fill it in below.
|
||||
iter = dstEntry->values.insert(iter, ResourceConfigValue{ srcValue.config });
|
||||
}
|
||||
|
||||
if (stripConfig) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (FileReference* f = valueCast<FileReference>(srcValue.value.get())) {
|
||||
if (!dstValue) {
|
||||
// Force create the entry if we didn't have it.
|
||||
dstValue = dstEntry->findOrCreateValue(srcValue->config, srcValue->product);
|
||||
}
|
||||
|
||||
if (FileReference* f = valueCast<FileReference>(srcValue->value.get())) {
|
||||
std::unique_ptr<FileReference> newFileRef;
|
||||
if (manglePackage) {
|
||||
newFileRef = cloneAndMangleFile(srcPackage->name, *f);
|
||||
@@ -246,15 +246,15 @@ bool TableMerger::doMerge(const Source& src,
|
||||
}
|
||||
|
||||
if (callback) {
|
||||
if (!callback(resName, iter->config, newFileRef.get(), f)) {
|
||||
if (!callback(resName, srcValue->config, newFileRef.get(), f)) {
|
||||
error = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
iter->value = std::move(newFileRef);
|
||||
dstValue->value = std::move(newFileRef);
|
||||
|
||||
} else {
|
||||
iter->value = std::unique_ptr<Value>(srcValue.value->clone(
|
||||
dstValue->value = std::unique_ptr<Value>(srcValue->value->clone(
|
||||
&mMasterTable->stringPool));
|
||||
}
|
||||
}
|
||||
@@ -290,7 +290,8 @@ bool TableMerger::mergeFileImpl(const ResourceFile& fileDesc, io::IFile* file, b
|
||||
ResourceTablePackage* pkg = table.createPackage(fileDesc.name.package, 0x0);
|
||||
pkg->findOrCreateType(fileDesc.name.type)
|
||||
->findOrCreateEntry(fileDesc.name.entry)
|
||||
->values.push_back(ResourceConfigValue{ fileDesc.config, std::move(fileRef) });
|
||||
->findOrCreateValue(fileDesc.config, {})
|
||||
->value = std::move(fileRef);
|
||||
|
||||
auto callback = [&](const ResourceNameRef& name, const ConfigDescription& config,
|
||||
FileReference* newFile, FileReference* oldFile) -> bool {
|
||||
|
||||
@@ -17,9 +17,7 @@
|
||||
#include "ConfigDescription.h"
|
||||
#include "Resource.h"
|
||||
#include "ValueVisitor.h"
|
||||
|
||||
#include "process/SymbolTable.h"
|
||||
#include "util/Comparators.h"
|
||||
#include "util/Util.h"
|
||||
|
||||
#include <androidfw/AssetManager.h>
|
||||
@@ -55,12 +53,10 @@ const ISymbolTable::Symbol* SymbolTableWrapper::findByName(const ResourceName& n
|
||||
|
||||
if (name.type == ResourceType::kAttr || name.type == ResourceType::kAttrPrivate) {
|
||||
const ConfigDescription kDefaultConfig;
|
||||
auto iter = std::lower_bound(sr.entry->values.begin(), sr.entry->values.end(),
|
||||
kDefaultConfig, cmp::lessThanConfig);
|
||||
|
||||
if (iter != sr.entry->values.end() && iter->config == kDefaultConfig) {
|
||||
ResourceConfigValue* configValue = sr.entry->findValue(kDefaultConfig);
|
||||
if (configValue) {
|
||||
// This resource has an Attribute.
|
||||
if (Attribute* attr = valueCast<Attribute>(iter->value.get())) {
|
||||
if (Attribute* attr = valueCast<Attribute>(configValue->value.get())) {
|
||||
symbol->attribute = util::make_unique<Attribute>(*attr);
|
||||
} else {
|
||||
return {};
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
#include "ValueVisitor.h"
|
||||
#include "proto/ProtoHelpers.h"
|
||||
#include "proto/ProtoSerialize.h"
|
||||
#include "util/Comparators.h"
|
||||
|
||||
#include <androidfw/ResourceTypes.h>
|
||||
|
||||
@@ -134,21 +133,19 @@ public:
|
||||
return {};
|
||||
}
|
||||
|
||||
auto iter = std::lower_bound(entry->values.begin(), entry->values.end(),
|
||||
config, cmp::lessThanConfig);
|
||||
if (iter != entry->values.end() && iter->config == config) {
|
||||
ResourceConfigValue* configValue = entry->findOrCreateValue(config,
|
||||
pbConfig.product());
|
||||
if (configValue->value) {
|
||||
// Duplicate config.
|
||||
mDiag->error(DiagMessage(mSource) << "duplicate configuration");
|
||||
return {};
|
||||
}
|
||||
|
||||
std::unique_ptr<Value> value = deserializeValueFromPb(pbConfigValue.value(),
|
||||
config,
|
||||
&table->stringPool);
|
||||
if (!value) {
|
||||
configValue->value = deserializeValueFromPb(pbConfigValue.value(),
|
||||
config, &table->stringPool);
|
||||
if (!configValue->value) {
|
||||
return {};
|
||||
}
|
||||
entry->values.insert(iter, ResourceConfigValue{ config, std::move(value) });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -242,21 +242,24 @@ std::unique_ptr<pb::ResourceTable> serializeTableToPb(ResourceTable* table) {
|
||||
|
||||
for (auto& configValue : entry->values) {
|
||||
pb::ConfigValue* pbConfigValue = pbEntry->add_config_values();
|
||||
serializeConfig(configValue.config, pbConfigValue->mutable_config());
|
||||
|
||||
pb::Value* pbValue = pbConfigValue->mutable_value();
|
||||
serializeSourceToPb(configValue.value->getSource(), &sourcePool,
|
||||
pbValue->mutable_source());
|
||||
if (!configValue.value->getComment().empty()) {
|
||||
pbValue->set_comment(util::utf16ToUtf8(configValue.value->getComment()));
|
||||
serializeConfig(configValue->config, pbConfigValue->mutable_config());
|
||||
if (!configValue->product.empty()) {
|
||||
pbConfigValue->mutable_config()->set_product(configValue->product);
|
||||
}
|
||||
|
||||
if (configValue.value->isWeak()) {
|
||||
pb::Value* pbValue = pbConfigValue->mutable_value();
|
||||
serializeSourceToPb(configValue->value->getSource(), &sourcePool,
|
||||
pbValue->mutable_source());
|
||||
if (!configValue->value->getComment().empty()) {
|
||||
pbValue->set_comment(util::utf16ToUtf8(configValue->value->getComment()));
|
||||
}
|
||||
|
||||
if (configValue->value->isWeak()) {
|
||||
pbValue->set_weak(true);
|
||||
}
|
||||
|
||||
PbSerializerVisitor visitor(&sourcePool, &symbolPool, pbValue);
|
||||
configValue.value->accept(&visitor);
|
||||
configValue->value->accept(&visitor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,9 +49,19 @@ TEST(TableProtoSerializer, SerializeSinglePackage) {
|
||||
std::unique_ptr<Plural> plural = util::make_unique<Plural>();
|
||||
plural->values[Plural::One] = util::make_unique<String>(table->stringPool.makeRef(u"one"));
|
||||
ASSERT_TRUE(table->addResource(test::parseNameOrDie(u"@com.app.a:plurals/hey"),
|
||||
ConfigDescription{}, std::move(plural),
|
||||
ConfigDescription{}, std::string(), std::move(plural),
|
||||
context->getDiagnostics()));
|
||||
|
||||
// Make a resource with different products.
|
||||
ASSERT_TRUE(table->addResource(test::parseNameOrDie(u"@com.app.a:integer/one"),
|
||||
test::parseConfigOrDie("land"), std::string(),
|
||||
test::buildPrimitive(android::Res_value::TYPE_INT_DEC, 123u),
|
||||
context->getDiagnostics()));
|
||||
ASSERT_TRUE(table->addResource(test::parseNameOrDie(u"@com.app.a:integer/one"),
|
||||
test::parseConfigOrDie("land"), std::string("tablet"),
|
||||
test::buildPrimitive(android::Res_value::TYPE_INT_DEC, 321u),
|
||||
context->getDiagnostics()));
|
||||
|
||||
std::unique_ptr<pb::ResourceTable> pbTable = serializeTableToPb(table.get());
|
||||
ASSERT_NE(nullptr, pbTable);
|
||||
|
||||
@@ -69,6 +79,17 @@ TEST(TableProtoSerializer, SerializeSinglePackage) {
|
||||
AAPT_ASSERT_TRUE(result);
|
||||
EXPECT_EQ(SymbolState::kPublic, result.value().type->symbolStatus.state);
|
||||
EXPECT_EQ(SymbolState::kPublic, result.value().entry->symbolStatus.state);
|
||||
|
||||
// Find the product-dependent values
|
||||
BinaryPrimitive* prim = test::getValueForConfigAndProduct<BinaryPrimitive>(
|
||||
newTable.get(), u"@com.app.a:integer/one", test::parseConfigOrDie("land"), "");
|
||||
ASSERT_NE(nullptr, prim);
|
||||
EXPECT_EQ(123u, prim->value.data);
|
||||
|
||||
prim = test::getValueForConfigAndProduct<BinaryPrimitive>(
|
||||
newTable.get(), u"@com.app.a:integer/one", test::parseConfigOrDie("land"), "tablet");
|
||||
ASSERT_NE(nullptr, prim);
|
||||
EXPECT_EQ(321u, prim->value.data);
|
||||
}
|
||||
|
||||
TEST(TableProtoSerializer, SerializeFileHeader) {
|
||||
|
||||
@@ -104,8 +104,8 @@ public:
|
||||
const ConfigDescription& config,
|
||||
std::unique_ptr<Value> value) {
|
||||
ResourceName resName = parseNameOrDie(name);
|
||||
bool result = mTable->addResourceAllowMangled(resName, id, config, std::move(value),
|
||||
&mDiagnostics);
|
||||
bool result = mTable->addResourceAllowMangled(resName, id, config, std::string(),
|
||||
std::move(value), &mDiagnostics);
|
||||
assert(result);
|
||||
return *this;
|
||||
}
|
||||
@@ -132,6 +132,14 @@ inline std::unique_ptr<Reference> buildReference(const StringPiece16& ref,
|
||||
return reference;
|
||||
}
|
||||
|
||||
inline std::unique_ptr<BinaryPrimitive> buildPrimitive(uint8_t type, uint32_t data) {
|
||||
android::Res_value value = {};
|
||||
value.size = sizeof(value);
|
||||
value.dataType = type;
|
||||
value.data = data;
|
||||
return util::make_unique<BinaryPrimitive>(value);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
class ValueBuilder {
|
||||
private:
|
||||
|
||||
@@ -52,6 +52,11 @@ struct DummyDiagnosticsImpl : public IDiagnostics {
|
||||
void note(const DiagMessage& message) override {}
|
||||
};
|
||||
|
||||
inline IDiagnostics* getDiagnostics() {
|
||||
static DummyDiagnosticsImpl diag;
|
||||
return &diag;
|
||||
}
|
||||
|
||||
inline ResourceName parseNameOrDie(const StringPiece16& str) {
|
||||
ResourceNameRef ref;
|
||||
bool result = ResourceUtils::tryParseReference(str, &ref);
|
||||
@@ -66,23 +71,25 @@ inline ConfigDescription parseConfigOrDie(const StringPiece& str) {
|
||||
return config;
|
||||
}
|
||||
|
||||
template <typename T> T* getValueForConfig(ResourceTable* table, const StringPiece16& resName,
|
||||
const ConfigDescription& config) {
|
||||
template <typename T> T* getValueForConfigAndProduct(ResourceTable* table,
|
||||
const StringPiece16& resName,
|
||||
const ConfigDescription& config,
|
||||
const StringPiece& product) {
|
||||
Maybe<ResourceTable::SearchResult> result = table->findResource(parseNameOrDie(resName));
|
||||
if (result) {
|
||||
ResourceEntry* entry = result.value().entry;
|
||||
auto iter = std::lower_bound(entry->values.begin(), entry->values.end(), config,
|
||||
[](const ResourceConfigValue& a, const ConfigDescription& b)
|
||||
-> bool {
|
||||
return a.config < b;
|
||||
});
|
||||
if (iter != entry->values.end() && iter->config == config) {
|
||||
return valueCast<T>(iter->value.get());
|
||||
ResourceConfigValue* configValue = result.value().entry->findValue(config, product);
|
||||
if (configValue) {
|
||||
return valueCast<T>(configValue->value.get());
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template <typename T> T* getValueForConfig(ResourceTable* table, const StringPiece16& resName,
|
||||
const ConfigDescription& config) {
|
||||
return getValueForConfigAndProduct<T>(table, resName, config, {});
|
||||
}
|
||||
|
||||
template <typename T> T* getValue(ResourceTable* table, const StringPiece16& resName) {
|
||||
return getValueForConfig<T>(table, resName, {});
|
||||
}
|
||||
|
||||
@@ -352,7 +352,7 @@ bool BinaryResourceParser::parseType(const ResourceTablePackage* package,
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!mTable->addResourceAllowMangled(name, config, std::move(resourceValue),
|
||||
if (!mTable->addResourceAllowMangled(name, config, {}, std::move(resourceValue),
|
||||
mContext->getDiagnostics())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef AAPT_UTIL_COMPARATORS_H
|
||||
#define AAPT_UTIL_COMPARATORS_H
|
||||
|
||||
#include "ConfigDescription.h"
|
||||
#include "ResourceTable.h"
|
||||
|
||||
namespace aapt {
|
||||
namespace cmp {
|
||||
|
||||
inline bool lessThanConfig(const ResourceConfigValue& a, const ConfigDescription& b) {
|
||||
return a.config < b;
|
||||
}
|
||||
|
||||
inline bool lessThanType(const std::unique_ptr<ResourceTableType>& a, ResourceType b) {
|
||||
return a->type < b;
|
||||
}
|
||||
|
||||
} // namespace cmp
|
||||
} // namespace aapt
|
||||
|
||||
#endif /* AAPT_UTIL_COMPARATORS_H */
|
||||
Reference in New Issue
Block a user