AAPT2: Introduce notion of 'product' to ResourceTable

This allows us to preserve the various product definitions during the compile
phase, and allows us to select the product in the link phase.

This allows compiled files to remain product-independent, so that they do not need
to be recompiled when switching targets.

Bug:25958912
Change-Id: Iaa7eed25c834b67a39cdc9be43613e8b5ab6cdd7
This commit is contained in:
Adam Lesinski
2016-02-12 22:18:51 -08:00
parent 59e04c6f92
commit e4bb9eb5af
30 changed files with 754 additions and 339 deletions

View File

@@ -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 \

View File

@@ -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());

View File

@@ -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.

View File

@@ -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;

View File

@@ -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) {

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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

View File

@@ -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);
}
}
}

View File

@@ -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;

View File

@@ -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());
}
}
}

View File

@@ -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 });
}
}

View File

@@ -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;
}

View File

@@ -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);
}
}
}

View File

@@ -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));

View File

@@ -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(), ',')) {

View File

@@ -26,7 +26,7 @@
namespace aapt {
class ResourceTable;
struct ResourceEntry;
class ResourceEntry;
struct ConfigDescription;
/**

View 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

View 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 */

View 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

View File

@@ -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()) {

View File

@@ -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 {

View File

@@ -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 {};

View File

@@ -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) });
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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) {

View File

@@ -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:

View File

@@ -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, {});
}

View File

@@ -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;
}

View File

@@ -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 */