Merge "AAPT2: Move comments and source into Value"

This commit is contained in:
Adam Lesinski
2015-10-30 18:05:31 +00:00
committed by Android (Google) Code Review
23 changed files with 386 additions and 249 deletions

View File

@@ -105,11 +105,9 @@ TEST(JavaClassGeneratorTest, OnlyWritePublicResources) {
.addSimple(u"@android:id/one", ResourceId(0x01020000))
.addSimple(u"@android:id/two", ResourceId(0x01020001))
.addSimple(u"@android:id/three", ResourceId(0x01020002))
.setSymbolState(u"@android:id/one", ResourceId(0x01020000), SymbolState::kPublic)
.setSymbolState(u"@android:id/two", ResourceId(0x01020001), SymbolState::kPrivate)
.build();
ASSERT_TRUE(table->setSymbolState(test::parseNameOrDie(u"@android:id/one"), {}, {},
SymbolState::kPublic, &diag));
ASSERT_TRUE(table->setSymbolState(test::parseNameOrDie(u"@android:id/two"), {}, {},
SymbolState::kPrivate, &diag));
JavaClassGeneratorOptions options;
options.types = JavaClassGeneratorOptions::SymbolTypes::kPublic;

View File

@@ -186,6 +186,7 @@ struct ParsedResource {
Source source;
ResourceId id;
SymbolState symbolState = SymbolState::kUndefined;
std::u16string comment;
std::unique_ptr<Value> value;
std::list<ParsedResource> childResources;
};
@@ -194,7 +195,11 @@ struct ParsedResource {
static bool addResourcesToTable(ResourceTable* table, const ConfigDescription& config,
IDiagnostics* diag, ParsedResource* res) {
if (res->symbolState != SymbolState::kUndefined) {
if (!table->setSymbolState(res->name, res->id, res->source, res->symbolState, diag)) {
Symbol symbol;
symbol.state = res->symbolState;
symbol.source = res->source;
symbol.comment = res->comment;
if (!table->setSymbolState(res->name, res->id, symbol, diag)) {
return false;
}
}
@@ -203,7 +208,11 @@ static bool addResourcesToTable(ResourceTable* table, const ConfigDescription& c
return true;
}
if (!table->addResource(res->name, res->id, config, res->source, std::move(res->value), diag)) {
// Attach the comment, source and config to the value.
res->value->setComment(std::move(res->comment));
res->value->setSource(std::move(res->source));
if (!table->addResource(res->name, res->id, config, std::move(res->value), diag)) {
return false;
}
@@ -275,6 +284,7 @@ bool ResourceParser::parseResources(XmlPullParser* parser) {
ParsedResource parsedResource;
parsedResource.name.entry = maybeName.value().toString();
parsedResource.source = mSource.withLine(parser->getLineNumber());
parsedResource.comment = std::move(comment);
bool result = true;
if (elementName == u"id") {
@@ -368,8 +378,8 @@ enum {
* an Item. If allowRawValue is false, nullptr is returned in this
* case.
*/
std::unique_ptr<Item> ResourceParser::parseXml(XmlPullParser* parser, uint32_t typeMask,
bool allowRawValue) {
std::unique_ptr<Item> ResourceParser::parseXml(XmlPullParser* parser, const uint32_t typeMask,
const bool allowRawValue) {
const size_t beginXmlLine = parser->getLineNumber();
std::u16string rawValue;
@@ -386,8 +396,9 @@ std::unique_ptr<Item> ResourceParser::parseXml(XmlPullParser* parser, uint32_t t
auto onCreateReference = [&](const ResourceName& name) {
// name.package can be empty here, as it will assume the package name of the table.
mTable->addResource(name, {}, mSource.withLine(beginXmlLine), util::make_unique<Id>(),
mDiag);
std::unique_ptr<Id> id = util::make_unique<Id>();
id->setSource(mSource.withLine(beginXmlLine));
mTable->addResource(name, {}, std::move(id), mDiag);
};
// Process the raw value.
@@ -411,11 +422,12 @@ std::unique_ptr<Item> ResourceParser::parseXml(XmlPullParser* parser, uint32_t t
mTable->stringPool.makeRef(styleString.str, StringPool::Context{ 1, mConfig }));
}
// We can't parse this so return a RawString if we are allowed.
if (allowRawValue) {
// We can't parse this so return a RawString if we are allowed.
return util::make_unique<RawString>(
mTable->stringPool.makeRef(rawValue, StringPool::Context{ 1, mConfig }));
}
return {};
}
@@ -683,8 +695,8 @@ Maybe<Attribute::Symbol> ResourceParser::parseEnumOrFlagItem(XmlPullParser* pars
}
return Attribute::Symbol{
Reference(ResourceName{ {}, ResourceType::kId, maybeName.value().toString() }),
val.data };
Reference(ResourceName({}, ResourceType::kId, maybeName.value().toString())),
val.data };
}
static Maybe<ResourceName> parseXmlAttributeName(StringPiece16 str) {

View File

@@ -65,12 +65,13 @@ private:
StyleString* outStyleString);
/*
* Parses the XML subtree and converts it to an Item. The type of Item that can be
* parsed is denoted by the `typeMask`. If `allowRawValue` is true and the subtree
* can not be parsed as a regular Item, then a RawString is returned. Otherwise
* this returns nullptr.
* Parses the XML subtree and returns an Item.
* The type of Item that can be parsed is denoted by the `typeMask`.
* If `allowRawValue` is true and the subtree can not be parsed as a regular Item, then a
* RawString is returned. Otherwise this returns false;
*/
std::unique_ptr<Item> parseXml(XmlPullParser* parser, uint32_t typeMask, bool allowRawValue);
std::unique_ptr<Item> parseXml(XmlPullParser* parser, const uint32_t typeMask,
const bool allowRawValue);
bool parseResources(XmlPullParser* parser);
bool parseString(XmlPullParser* parser, ParsedResource* outResource);

View File

@@ -379,18 +379,39 @@ TEST_F(ResourceParserTest, ParsePlural) {
}
TEST_F(ResourceParserTest, ParseCommentsWithResource) {
std::string input = "<!-- This is a comment -->\n"
std::string input = "<!--This is a comment-->\n"
"<string name=\"foo\">Hi</string>";
ASSERT_TRUE(testParse(input));
Maybe<ResourceTable::SearchResult> result = mTable.findResource(
test::parseNameOrDie(u"@string/foo"));
AAPT_ASSERT_TRUE(result);
String* value = test::getValue<String>(&mTable, u"@string/foo");
ASSERT_NE(nullptr, value);
EXPECT_EQ(value->getComment(), u"This is a comment");
}
ResourceEntry* entry = result.value().entry;
ASSERT_NE(entry, nullptr);
ASSERT_FALSE(entry->values.empty());
EXPECT_EQ(entry->values.front().comment, u"This is a comment");
TEST_F(ResourceParserTest, DoNotCombineMultipleComments) {
std::string input = "<!--One-->\n"
"<!--Two-->\n"
"<string name=\"foo\">Hi</string>";
ASSERT_TRUE(testParse(input));
String* value = test::getValue<String>(&mTable, u"@string/foo");
ASSERT_NE(nullptr, value);
EXPECT_EQ(value->getComment(), u"Two");
}
TEST_F(ResourceParserTest, IgnoreCommentBeforeEndTag) {
std::string input = "<!--One-->\n"
"<string name=\"foo\">\n"
" Hi\n"
"<!--Two-->\n"
"</string>";
ASSERT_TRUE(testParse(input));
String* value = test::getValue<String>(&mTable, u"@string/foo");
ASSERT_NE(nullptr, value);
EXPECT_EQ(value->getComment(), u"One");
}
/*

View File

@@ -19,6 +19,8 @@
#include "ResourceTable.h"
#include "ResourceValues.h"
#include "ValueVisitor.h"
#include "util/Comparators.h"
#include "util/Util.h"
#include <algorithm>
@@ -29,10 +31,6 @@
namespace aapt {
static bool compareConfigs(const ResourceConfigValue& lhs, const ConfigDescription& rhs) {
return lhs.config < rhs;
}
static bool lessThanType(const std::unique_ptr<ResourceTableType>& lhs, ResourceType rhs) {
return lhs->type < rhs;
}
@@ -191,52 +189,50 @@ static constexpr const char16_t* kValidNameChars = u"._-";
static constexpr const char16_t* kValidNameMangledChars = u"._-$";
bool ResourceTable::addResource(const ResourceNameRef& name, const ConfigDescription& config,
const Source& source, std::unique_ptr<Value> value,
IDiagnostics* diag) {
return addResourceImpl(name, ResourceId{}, config, source, std::move(value), kValidNameChars,
diag);
std::unique_ptr<Value> value, IDiagnostics* diag) {
return addResourceImpl(name, {}, config, std::move(value), kValidNameChars, diag);
}
bool ResourceTable::addResource(const ResourceNameRef& name, const ResourceId resId,
const ConfigDescription& config, const Source& source,
std::unique_ptr<Value> value, IDiagnostics* diag) {
return addResourceImpl(name, resId, config, source, std::move(value), kValidNameChars, diag);
const ConfigDescription& config, std::unique_ptr<Value> value,
IDiagnostics* diag) {
return addResourceImpl(name, resId, config, std::move(value), kValidNameChars, diag);
}
bool ResourceTable::addFileReference(const ResourceNameRef& name, const ConfigDescription& config,
const Source& source, const StringPiece16& path,
IDiagnostics* diag) {
return addResourceImpl(name, ResourceId{}, config, source,
util::make_unique<FileReference>(stringPool.makeRef(path)),
kValidNameChars, 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, diag);
}
bool ResourceTable::addResourceAllowMangled(const ResourceNameRef& name,
const ConfigDescription& config,
const Source& source,
std::unique_ptr<Value> value,
IDiagnostics* diag) {
return addResourceImpl(name, ResourceId{}, config, source, std::move(value),
kValidNameMangledChars, diag);
return addResourceImpl(name, ResourceId{}, config, std::move(value), kValidNameMangledChars,
diag);
}
bool ResourceTable::addResourceAllowMangled(const ResourceNameRef& name,
const ResourceId id,
const ConfigDescription& config,
const Source& source,
std::unique_ptr<Value> value,
IDiagnostics* diag) {
return addResourceImpl(name, id, config, source, std::move(value),
kValidNameMangledChars, diag);
return addResourceImpl(name, id, config, std::move(value), kValidNameMangledChars, diag);
}
bool ResourceTable::addResourceImpl(const ResourceNameRef& name, const ResourceId resId,
const ConfigDescription& config, const Source& source,
std::unique_ptr<Value> value, const char16_t* validChars,
IDiagnostics* diag) {
const ConfigDescription& config, std::unique_ptr<Value> value,
const char16_t* validChars, IDiagnostics* diag) {
assert(value && "value can't be nullptr");
assert(diag && "diagnostics can't be nullptr");
auto badCharIter = util::findNonAlphaNumericAndNotInSet(name.entry, validChars);
if (badCharIter != name.entry.end()) {
diag->error(DiagMessage(source)
diag->error(DiagMessage(value->getSource())
<< "resource '"
<< name
<< "' has invalid entry name '"
@@ -249,7 +245,7 @@ bool ResourceTable::addResourceImpl(const ResourceNameRef& name, const ResourceI
ResourceTablePackage* package = findOrCreatePackage(name.package);
if (resId.isValid() && package->id && package->id.value() != resId.packageId()) {
diag->error(DiagMessage(source)
diag->error(DiagMessage(value->getSource())
<< "trying to add resource '"
<< name
<< "' with ID "
@@ -263,7 +259,7 @@ bool ResourceTable::addResourceImpl(const ResourceNameRef& name, const ResourceI
ResourceTableType* type = package->findOrCreateType(name.type);
if (resId.isValid() && type->id && type->id.value() != resId.typeId()) {
diag->error(DiagMessage(source)
diag->error(DiagMessage(value->getSource())
<< "trying to add resource '"
<< name
<< "' with ID "
@@ -277,7 +273,7 @@ bool ResourceTable::addResourceImpl(const ResourceNameRef& name, const ResourceI
ResourceEntry* entry = type->findOrCreateEntry(name.entry);
if (resId.isValid() && entry->id && entry->id.value() != resId.entryId()) {
diag->error(DiagMessage(source)
diag->error(DiagMessage(value->getSource())
<< "trying to add resource '"
<< name
<< "' with ID "
@@ -288,20 +284,20 @@ bool ResourceTable::addResourceImpl(const ResourceNameRef& name, const ResourceI
}
const auto endIter = entry->values.end();
auto iter = std::lower_bound(entry->values.begin(), endIter, config, compareConfigs);
auto iter = std::lower_bound(entry->values.begin(), endIter, config, cmp::lessThan);
if (iter == endIter || iter->config != config) {
// This resource did not exist before, add it.
entry->values.insert(iter, ResourceConfigValue{ config, source, {}, std::move(value) });
entry->values.insert(iter, ResourceConfigValue{ config, std::move(value) });
} else {
int collisionResult = resolveValueCollision(iter->value.get(), value.get());
if (collisionResult > 0) {
// Take the incoming value.
*iter = ResourceConfigValue{ config, source, {}, std::move(value) };
iter->value = std::move(value);
} else if (collisionResult == 0) {
diag->error(DiagMessage(source)
diag->error(DiagMessage(value->getSource())
<< "duplicate value for resource '" << name << "' "
<< "with config '" << iter->config << "'");
diag->error(DiagMessage(iter->source)
<< "with config '" << config << "'");
diag->error(DiagMessage(iter->value->getSource())
<< "resource previously defined here");
return false;
}
@@ -316,27 +312,29 @@ bool ResourceTable::addResourceImpl(const ResourceNameRef& name, const ResourceI
}
bool ResourceTable::setSymbolState(const ResourceNameRef& name, const ResourceId resId,
const Source& source, SymbolState state, IDiagnostics* diag) {
return setSymbolStateImpl(name, resId, source, state, kValidNameChars, diag);
const Symbol& symbol, IDiagnostics* diag) {
return setSymbolStateImpl(name, resId, symbol, kValidNameChars, diag);
}
bool ResourceTable::setSymbolStateAllowMangled(const ResourceNameRef& name, const ResourceId resId,
const Source& source, SymbolState state,
IDiagnostics* diag) {
return setSymbolStateImpl(name, resId, source, state, kValidNameMangledChars, diag);
bool ResourceTable::setSymbolStateAllowMangled(const ResourceNameRef& name,
const ResourceId resId,
const Symbol& symbol, IDiagnostics* diag) {
return setSymbolStateImpl(name, resId, symbol, kValidNameMangledChars, diag);
}
bool ResourceTable::setSymbolStateImpl(const ResourceNameRef& name, const ResourceId resId,
const Source& source, SymbolState state,
const char16_t* validChars, IDiagnostics* diag) {
if (state == SymbolState::kUndefined) {
const Symbol& symbol, const char16_t* validChars,
IDiagnostics* diag) {
assert(diag && "diagnostics can't be nullptr");
if (symbol.state == SymbolState::kUndefined) {
// Nothing to do.
return true;
}
auto badCharIter = util::findNonAlphaNumericAndNotInSet(name.entry, validChars);
if (badCharIter != name.entry.end()) {
diag->error(DiagMessage(source)
diag->error(DiagMessage(symbol.source)
<< "resource '"
<< name
<< "' has invalid entry name '"
@@ -349,7 +347,7 @@ bool ResourceTable::setSymbolStateImpl(const ResourceNameRef& name, const Resour
ResourceTablePackage* package = findOrCreatePackage(name.package);
if (resId.isValid() && package->id && package->id.value() != resId.packageId()) {
diag->error(DiagMessage(source)
diag->error(DiagMessage(symbol.source)
<< "trying to add resource '"
<< name
<< "' with ID "
@@ -363,7 +361,7 @@ bool ResourceTable::setSymbolStateImpl(const ResourceNameRef& name, const Resour
ResourceTableType* type = package->findOrCreateType(name.type);
if (resId.isValid() && type->id && type->id.value() != resId.typeId()) {
diag->error(DiagMessage(source)
diag->error(DiagMessage(symbol.source)
<< "trying to add resource '"
<< name
<< "' with ID "
@@ -377,7 +375,7 @@ bool ResourceTable::setSymbolStateImpl(const ResourceNameRef& name, const Resour
ResourceEntry* entry = type->findOrCreateEntry(name.entry);
if (resId.isValid() && entry->id && entry->id.value() != resId.entryId()) {
diag->error(DiagMessage(source)
diag->error(DiagMessage(symbol.source)
<< "trying to add resource '"
<< name
<< "' with ID "
@@ -388,15 +386,14 @@ bool ResourceTable::setSymbolStateImpl(const ResourceNameRef& name, const Resour
}
// Only mark the type state as public, it doesn't care about being private.
if (state == SymbolState::kPublic) {
if (symbol.state == SymbolState::kPublic) {
type->symbolStatus.state = SymbolState::kPublic;
}
// Downgrading to a private symbol from a public one is not allowed.
if (entry->symbolStatus.state != SymbolState::kPublic) {
if (entry->symbolStatus.state != state) {
entry->symbolStatus.state = state;
entry->symbolStatus.source = source;
if (entry->symbolStatus.state != symbol.state) {
entry->symbolStatus = std::move(symbol);
}
}

View File

@@ -47,12 +47,10 @@ struct Symbol {
};
/**
* The resource value for a specific configuration.
* Represents a value defined for a given configuration.
*/
struct ResourceConfigValue {
ConfigDescription config;
Source source;
std::u16string comment;
std::unique_ptr<Value> value;
};
@@ -158,12 +156,11 @@ public:
static int resolveValueCollision(Value* existing, Value* incoming);
bool addResource(const ResourceNameRef& name, const ConfigDescription& config,
const Source& source, std::unique_ptr<Value> value,
IDiagnostics* diag);
std::unique_ptr<Value> value, IDiagnostics* diag);
bool addResource(const ResourceNameRef& name, const ResourceId resId,
const ConfigDescription& config, const Source& source,
std::unique_ptr<Value> value, IDiagnostics* diag);
const ConfigDescription& config, std::unique_ptr<Value> value,
IDiagnostics* diag);
bool addFileReference(const ResourceNameRef& name, const ConfigDescription& config,
const Source& source, const StringPiece16& path, IDiagnostics* diag);
@@ -174,18 +171,18 @@ public:
* names.
*/
bool addResourceAllowMangled(const ResourceNameRef& name, const ConfigDescription& config,
const Source& source, std::unique_ptr<Value> value,
IDiagnostics* diag);
std::unique_ptr<Value> value, IDiagnostics* diag);
bool addResourceAllowMangled(const ResourceNameRef& name, const ResourceId id,
const ConfigDescription& config,
const Source& source, std::unique_ptr<Value> value,
const ConfigDescription& config, std::unique_ptr<Value> value,
IDiagnostics* diag);
bool setSymbolState(const ResourceNameRef& name, const ResourceId resId, const Source& source,
SymbolState state, IDiagnostics* diag);
bool setSymbolState(const ResourceNameRef& name, const ResourceId resId,
const Symbol& symbol, IDiagnostics* diag);
bool setSymbolStateAllowMangled(const ResourceNameRef& name, const ResourceId resId,
const Source& source, SymbolState state, IDiagnostics* diag);
const Symbol& symbol, IDiagnostics* diag);
struct SearchResult {
ResourceTablePackage* package;
ResourceTableType* type;
@@ -224,13 +221,11 @@ public:
private:
ResourceTablePackage* findOrCreatePackage(const StringPiece16& name);
bool addResourceImpl(const ResourceNameRef& name, const ResourceId resId,
const ConfigDescription& config, const Source& source,
std::unique_ptr<Value> value, const char16_t* validChars,
IDiagnostics* diag);
bool setSymbolStateImpl(const ResourceNameRef& name, const ResourceId resId,
const Source& source, SymbolState state, const char16_t* validChars,
IDiagnostics* diag);
bool addResourceImpl(const ResourceNameRef& name, ResourceId resId,
const ConfigDescription& config, std::unique_ptr<Value> value,
const char16_t* validChars, IDiagnostics* diag);
bool setSymbolStateImpl(const ResourceNameRef& name, ResourceId resId,
const Symbol& symbol, const char16_t* validChars, IDiagnostics* diag);
};
} // namespace aapt

View File

@@ -19,7 +19,7 @@
#include "ResourceValues.h"
#include "util/Util.h"
#include "test/Common.h"
#include "test/Builders.h"
#include <algorithm>
#include <gtest/gtest.h>
@@ -42,22 +42,26 @@ TEST_F(ResourceTableTest, FailToAddResourceWithBadName) {
ResourceTable table;
EXPECT_FALSE(table.addResource(
ResourceNameRef{ u"android", ResourceType::kId, u"hey,there" },
{}, Source{ "test.xml", 21 },
util::make_unique<Id>(), &mDiagnostics));
ResourceNameRef(u"android", ResourceType::kId, u"hey,there"),
ConfigDescription{},
test::ValueBuilder<Id>().setSource("test.xml", 21u).build(),
&mDiagnostics));
EXPECT_FALSE(table.addResource(
ResourceNameRef{ u"android", ResourceType::kId, u"hey:there" },
{}, Source{ "test.xml", 21 },
util::make_unique<Id>(), &mDiagnostics));
ResourceNameRef(u"android", ResourceType::kId, u"hey:there"),
ConfigDescription{},
test::ValueBuilder<Id>().setSource("test.xml", 21u).build(),
&mDiagnostics));
}
TEST_F(ResourceTableTest, AddOneResource) {
ResourceTable table;
EXPECT_TRUE(table.addResource(test::parseNameOrDie(u"@android:attr/id"), {},
Source{ "test/path/file.xml", 23 },
util::make_unique<Id>(), &mDiagnostics));
EXPECT_TRUE(table.addResource(test::parseNameOrDie(u"@android:attr/id"),
ConfigDescription{},
test::ValueBuilder<Id>()
.setSource("test/path/file.xml", 23u).build(),
&mDiagnostics));
ASSERT_NE(nullptr, test::getValue<Id>(&table, u"@android:attr/id"));
}
@@ -71,23 +75,29 @@ TEST_F(ResourceTableTest, AddMultipleResources) {
EXPECT_TRUE(table.addResource(
test::parseNameOrDie(u"@android:attr/layout_width"),
config, Source{ "test/path/file.xml", 10 },
util::make_unique<Id>(), &mDiagnostics));
config,
test::ValueBuilder<Id>().setSource("test/path/file.xml", 10u).build(),
&mDiagnostics));
EXPECT_TRUE(table.addResource(
test::parseNameOrDie(u"@android:attr/id"),
config, Source{ "test/path/file.xml", 12 },
util::make_unique<Id>(), &mDiagnostics));
config,
test::ValueBuilder<Id>().setSource("test/path/file.xml", 12u).build(),
&mDiagnostics));
EXPECT_TRUE(table.addResource(
test::parseNameOrDie(u"@android:string/ok"),
config, Source{ "test/path/file.xml", 14 },
util::make_unique<Id>(), &mDiagnostics));
config,
test::ValueBuilder<Id>().setSource("test/path/file.xml", 14u).build(),
&mDiagnostics));
EXPECT_TRUE(table.addResource(
test::parseNameOrDie(u"@android:string/ok"),
languageConfig, Source{ "test/path/file.xml", 20 },
util::make_unique<BinaryPrimitive>(android::Res_value{}), &mDiagnostics));
languageConfig,
test::ValueBuilder<BinaryPrimitive>(android::Res_value{})
.setSource("test/path/file.xml", 20u)
.build(),
&mDiagnostics));
ASSERT_NE(nullptr, test::getValue<Id>(&table, u"@android:attr/layout_width"));
ASSERT_NE(nullptr, test::getValue<Id>(&table, u"@android:attr/id"));
@@ -99,14 +109,14 @@ TEST_F(ResourceTableTest, AddMultipleResources) {
TEST_F(ResourceTableTest, OverrideWeakResourceValue) {
ResourceTable table;
ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:attr/foo"), {}, {},
ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:attr/foo"), ConfigDescription{},
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"), {}, {},
ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:attr/foo"), ConfigDescription{},
util::make_unique<Attribute>(false), &mDiagnostics));
attr = test::getValue<Attribute>(&table, u"@android:attr/foo");

View File

@@ -15,11 +15,12 @@
*/
#include "Resource.h"
#include "flatten/ResourceTypeExtensions.h"
#include "ResourceValues.h"
#include "util/Util.h"
#include "ValueVisitor.h"
#include "util/Util.h"
#include "flatten/ResourceTypeExtensions.h"
#include <androidfw/ResourceTypes.h>
#include <limits>
@@ -35,18 +36,10 @@ void BaseItem<Derived>::accept(RawValueVisitor* visitor) {
visitor->visit(static_cast<Derived*>(this));
}
bool Value::isItem() const {
return false;
}
bool Value::isWeak() const {
return false;
}
bool Item::isItem() const {
return true;
}
RawString::RawString(const StringPool::Ref& ref) : value(ref) {
}

View File

@@ -40,17 +40,42 @@ struct RawValueVisitor;
struct Value {
virtual ~Value() = default;
/**
* Whether or not this is an Item.
*/
virtual bool isItem() const;
/**
* Whether this value is weak and can be overridden without
* warning or error. Default for base class is false.
*/
virtual bool isWeak() const;
/**
* Returns the source where this value was defined.
*/
const Source& getSource() const {
return mSource;
}
void setSource(const Source& source) {
mSource = source;
}
void setSource(Source&& source) {
mSource = std::move(source);
}
/**
* Returns the comment that was associated with this resource.
*/
StringPiece16 getComment() const {
return mComment;
}
void setComment(const StringPiece16& str) {
mComment = str.toString();
}
void setComment(std::u16string&& str) {
mComment = std::move(str);
}
/**
* Calls the appropriate overload of ValueVisitor.
*/
@@ -65,6 +90,10 @@ struct Value {
* Human readable printout of this value.
*/
virtual void print(std::ostream* out) const = 0;
private:
Source mSource;
std::u16string mComment;
};
/**
@@ -79,11 +108,6 @@ struct BaseValue : public Value {
* A resource item with a single value. This maps to android::ResTable_entry.
*/
struct Item : public Value {
/**
* An Item is, of course, an Item.
*/
virtual bool isItem() const override;
/**
* Clone the Item.
*/

View File

@@ -114,6 +114,18 @@ struct DynCastVisitor : public RawValueVisitor {
}
};
/**
* Specialization that checks if the value is an Item.
*/
template <>
struct DynCastVisitor<Item> : public RawValueVisitor {
Item* value = nullptr;
void visitItem(Item* item) override {
value = item;
}
};
/**
* Returns a valid pointer to T if the Value is of subtype T.
* Otherwise, returns nullptr.

View File

@@ -97,7 +97,7 @@ const std::string& XmlPullParser::getLastError() const {
}
const std::u16string& XmlPullParser::getComment() const {
return mEventQueue.front().comment;
return mEventQueue.front().data1;
}
size_t XmlPullParser::getLineNumber() const {

View File

@@ -158,7 +158,6 @@ private:
size_t depth;
std::u16string data1;
std::u16string data2;
std::u16string comment;
std::vector<Attribute> attributes;
};

View File

@@ -292,7 +292,7 @@ private:
SymbolWriter* mSymbols;
StringPool* mSourcePool;
template <typename T>
template <typename T, bool IsItem>
T* writeEntry(FlatEntry* entry, BigBuffer* buffer) {
static_assert(std::is_same<ResTable_entry, T>::value ||
std::is_same<ResTable_entry_ext, T>::value,
@@ -308,7 +308,7 @@ private:
outEntry->flags |= ResTable_entry::FLAG_WEAK;
}
if (!entry->value->isItem()) {
if (!IsItem) {
outEntry->flags |= ResTable_entry::FLAG_COMPLEX;
}
@@ -329,8 +329,8 @@ private:
}
bool flattenValue(FlatEntry* entry, BigBuffer* buffer) {
if (entry->value->isItem()) {
writeEntry<ResTable_entry>(entry, buffer);
if (Item* item = valueCast<Item>(entry->value)) {
writeEntry<ResTable_entry, true>(entry, buffer);
if (Reference* ref = valueCast<Reference>(entry->value)) {
if (!ref->id) {
assert(ref->name && "reference must have at least a name");
@@ -339,12 +339,12 @@ private:
}
}
Res_value* outValue = buffer->nextBlock<Res_value>();
bool result = static_cast<Item*>(entry->value)->flatten(outValue);
bool result = item->flatten(outValue);
assert(result && "flatten failed");
outValue->size = util::hostToDevice16(sizeof(*outValue));
} else {
const size_t beforeEntry = buffer->size();
ResTable_entry_ext* outEntry = writeEntry<ResTable_entry_ext>(entry, buffer);
ResTable_entry_ext* outEntry = writeEntry<ResTable_entry_ext, false>(entry, buffer);
MapFlattenVisitor visitor(mSymbols, entry, buffer);
entry->value->accept(&visitor);
outEntry->count = util::hostToDevice32(visitor.mEntryCount);
@@ -551,17 +551,27 @@ private:
// configuration available. Here we reverse this to match the binary table.
std::map<ConfigDescription, std::vector<FlatEntry>> configToEntryListMap;
for (ResourceEntry* entry : sortedEntries) {
const size_t keyIndex = mKeyPool.makeRef(entry->name).getIndex();
const uint32_t keyIndex = (uint32_t) mKeyPool.makeRef(entry->name).getIndex();
// Group values by configuration.
for (auto& configValue : entry->values) {
configToEntryListMap[configValue.config].push_back(FlatEntry{
entry, configValue.value.get(), (uint32_t) keyIndex,
(uint32_t)(mSourcePool->makeRef(util::utf8ToUtf16(
configValue.source.path)).getIndex()),
(uint32_t)(configValue.source.line
? configValue.source.line.value() : 0)
});
Value* value = configValue.value.get();
const StringPool::Ref sourceRef = mSourcePool->makeRef(
util::utf8ToUtf16(value->getSource().path));
uint32_t lineNumber = 0;
if (value->getSource().line) {
lineNumber = value->getSource().line.value();
}
configToEntryListMap[configValue.config]
.push_back(FlatEntry{
entry,
value,
keyIndex,
(uint32_t) sourceRef.getIndex(),
lineNumber });
}
}

View File

@@ -20,21 +20,18 @@
#include "ValueVisitor.h"
#include "link/Linkers.h"
#include "util/Comparators.h"
#include <algorithm>
#include <cassert>
namespace aapt {
static bool cmpConfigValue(const ResourceConfigValue& lhs, const ConfigDescription& config) {
return lhs.config < config;
}
bool shouldGenerateVersionedResource(const ResourceEntry* entry, const ConfigDescription& config,
const int sdkVersionToGenerate) {
assert(sdkVersionToGenerate > config.sdkVersion);
const auto endIter = entry->values.end();
auto iter = std::lower_bound(entry->values.begin(), endIter, config, cmpConfigValue);
auto iter = std::lower_bound(entry->values.begin(), endIter, config, cmp::lessThan);
// The source config came from this list, so it should be here.
assert(iter != entry->values.end());
@@ -107,21 +104,16 @@ bool AutoVersioner::consume(IAaptContext* context, ResourceTable* table) {
// We found attributes from a higher SDK level. Check that
// there is no other defined resource for the version we want to
// generate.
if (shouldGenerateVersionedResource(entry.get(), configValue.config,
if (shouldGenerateVersionedResource(entry.get(),
configValue.config,
minSdkStripped.value())) {
// Let's create a new Style for this versioned resource.
ConfigDescription newConfig(configValue.config);
newConfig.sdkVersion = minSdkStripped.value();
ResourceConfigValue newValue = {
newConfig,
configValue.source,
configValue.comment,
std::unique_ptr<Value>(configValue.value->clone(
&table->stringPool))
};
Style* newStyle = static_cast<Style*>(newValue.value.get());
std::unique_ptr<Style> newStyle(style->clone(&table->stringPool));
newStyle->setComment(style->getComment());
newStyle->setSource(style->getSource());
// Move the previously stripped attributes into this style.
newStyle->entries.insert(newStyle->entries.end(),
@@ -130,9 +122,13 @@ bool AutoVersioner::consume(IAaptContext* context, ResourceTable* table) {
// Insert the new Resource into the correct place.
auto iter = std::lower_bound(entry->values.begin(),
entry->values.end(), newConfig,
cmpConfigValue);
entry->values.insert(iter, std::move(newValue));
entry->values.end(),
newConfig,
cmp::lessThan);
entry->values.insert(
iter,
ResourceConfigValue{ newConfig, std::move(newStyle) });
}
}
}

View File

@@ -266,13 +266,14 @@ struct LinkCommand {
for (const auto& type : package->types) {
for (const auto& entry : type->entries) {
for (const auto& configValue : entry->values) {
mContext.getDiagnostics()->error(DiagMessage(configValue.source)
<< "defined resource '"
<< ResourceNameRef(package->name,
type->type,
entry->name)
<< "' for external package '"
<< package->name << "'");
mContext.getDiagnostics()->error(
DiagMessage(configValue.value->getSource())
<< "defined resource '"
<< ResourceNameRef(package->name,
type->type,
entry->name)
<< "' for external package '"
<< package->name << "'");
error = true;
}
}
@@ -472,10 +473,11 @@ struct LinkCommand {
Maybe<ResourceName> mangledName = mContext.getNameMangler()->mangleName(
exportedSymbol.name);
if (!mergedTable.addResource(
std::unique_ptr<Id> id = util::make_unique<Id>();
id->setSource(f.source.withLine(exportedSymbol.line));
if (!mergedTable.addResourceAllowMangled(
mangledName ? mangledName.value() : exportedSymbol.name,
{}, {}, f.source.withLine(exportedSymbol.line),
util::make_unique<Id>(), mContext.getDiagnostics())) {
{}, std::move(id), mContext.getDiagnostics())) {
error = true;
}
}

View File

@@ -30,13 +30,9 @@ TEST(PrivateAttributeMoverTest, MovePrivateAttributes) {
.addSimple(u"@android:attr/privateA")
.addSimple(u"@android:attr/publicB")
.addSimple(u"@android:attr/privateB")
.setSymbolState(u"@android:attr/publicA", ResourceId(0x01010000), SymbolState::kPublic)
.setSymbolState(u"@android:attr/publicB", ResourceId(0x01010000), SymbolState::kPublic)
.build();
ASSERT_TRUE(table->setSymbolState(test::parseNameOrDie(u"@android:attr/publicA"),
ResourceId(0x01010000), {}, SymbolState::kPublic,
context->getDiagnostics()));
ASSERT_TRUE(table->setSymbolState(test::parseNameOrDie(u"@android:attr/publicB"),
ResourceId(0x01010002), {}, SymbolState::kPublic,
context->getDiagnostics()));
PrivateAttributeMover mover;
ASSERT_TRUE(mover.consume(context.get(), table.get()));

View File

@@ -206,13 +206,13 @@ public:
if (!(typeMask & ResourceUtils::androidTypeToAttributeTypeMask(val.dataType))) {
// The actual type of this item is incompatible with the attribute.
DiagMessage msg;
DiagMessage msg(style->getSource());
buildAttributeMismatchMessage(&msg, s->attribute.get(), entry.value.get());
mContext->getDiagnostics()->error(msg);
mError = true;
}
} else {
DiagMessage msg;
DiagMessage msg(style->getSource());
msg << "style attribute '";
if (entry.key.name) {
msg << entry.key.name.value().package << ":" << entry.key.name.value().entry;

View File

@@ -19,6 +19,7 @@
#include "ValueVisitor.h"
#include "link/TableMerger.h"
#include "util/Comparators.h"
#include "util/Util.h"
#include <cassert>
@@ -120,27 +121,24 @@ bool TableMerger::doMerge(const Source& src, ResourceTable* srcTable,
}
for (ResourceConfigValue& srcValue : srcEntry->values) {
auto cmp = [](const ResourceConfigValue& a,
const ConfigDescription& b) -> bool {
return a.config < b;
};
auto iter = std::lower_bound(dstEntry->values.begin(), dstEntry->values.end(),
srcValue.config, cmp);
srcValue.config, cmp::lessThan);
if (iter != dstEntry->values.end() && iter->config == srcValue.config) {
const int collisionResult = ResourceTable::resolveValueCollision(
iter->value.get(), srcValue.value.get());
if (collisionResult == 0) {
// Error!
ResourceNameRef resourceName =
{ srcPackage->name, srcType->type, srcEntry->name };
mContext->getDiagnostics()->error(DiagMessage(srcValue.source)
ResourceNameRef resourceName(srcPackage->name,
srcType->type,
srcEntry->name);
mContext->getDiagnostics()->error(DiagMessage(srcValue.value->getSource())
<< "resource '" << resourceName
<< "' has a conflicting value for "
<< "configuration ("
<< srcValue.config << ")");
mContext->getDiagnostics()->note(DiagMessage(iter->source)
mContext->getDiagnostics()->note(DiagMessage(iter->value->getSource())
<< "originally defined here");
error = true;
continue;
@@ -150,16 +148,12 @@ bool TableMerger::doMerge(const Source& src, ResourceTable* srcTable,
}
} else {
// Insert a new value.
iter = dstEntry->values.insert(iter,
ResourceConfigValue{ srcValue.config });
// Insert a place holder value. We will fill it in below.
iter = dstEntry->values.insert(iter, ResourceConfigValue{ srcValue.config });
}
iter->source = std::move(srcValue.source);
iter->comment = std::move(srcValue.comment);
if (manglePackage) {
iter->value = cloneAndMangle(srcTable, srcPackage->name,
srcValue.value.get());
iter->value = cloneAndMangle(srcTable, srcPackage->name, srcValue.value.get());
} else {
iter->value = clone(srcValue.value.get());
}
@@ -179,7 +173,11 @@ std::unique_ptr<Value> TableMerger::cloneAndMangle(ResourceTable* table,
std::u16string mangledEntry = NameMangler::mangleEntry(package, entry.toString());
std::u16string newPath = prefix.toString() + mangledEntry + suffix.toString();
mFilesToMerge.push(FileToMerge{ table, *f->path, newPath });
return util::make_unique<FileReference>(mMasterTable->stringPool.makeRef(newPath));
std::unique_ptr<FileReference> fileRef = util::make_unique<FileReference>(
mMasterTable->stringPool.makeRef(newPath));
fileRef->setComment(f->getComment());
fileRef->setSource(f->getSource());
return std::move(fileRef);
}
}
return clone(value);

View File

@@ -16,9 +16,11 @@
#include "ConfigDescription.h"
#include "Resource.h"
#include "util/Util.h"
#include "ValueVisitor.h"
#include "process/SymbolTable.h"
#include "util/Comparators.h"
#include "util/Util.h"
#include <androidfw/AssetManager.h>
#include <androidfw/ResourceTypes.h>
@@ -34,7 +36,7 @@ const ISymbolTable::Symbol* SymbolTableWrapper::findByName(const ResourceName& n
if (!result) {
if (name.type == ResourceType::kAttr) {
// Recurse and try looking up a private attribute.
return findByName(ResourceName{ name.package, ResourceType::kAttrPrivate, name.entry });
return findByName(ResourceName(name.package, ResourceType::kAttrPrivate, name.entry));
}
return {};
}
@@ -48,28 +50,26 @@ const ISymbolTable::Symbol* SymbolTableWrapper::findByName(const ResourceName& n
}
std::shared_ptr<Symbol> symbol = std::make_shared<Symbol>();
symbol->id = ResourceId{
sr.package->id.value(), sr.type->id.value(), sr.entry->id.value() };
symbol->id = ResourceId(sr.package->id.value(), sr.type->id.value(), sr.entry->id.value());
if (name.type == ResourceType::kAttr || name.type == ResourceType::kAttrPrivate) {
auto lt = [](ResourceConfigValue& lhs, const ConfigDescription& rhs) -> bool {
return lhs.config < rhs;
};
const ConfigDescription kDefaultConfig;
auto iter = std::lower_bound(sr.entry->values.begin(), sr.entry->values.end(),
kDefaultConfig, lt);
kDefaultConfig, cmp::lessThan);
if (iter != sr.entry->values.end() && iter->config == kDefaultConfig) {
// This resource has an Attribute.
symbol->attribute = util::make_unique<Attribute>(
*static_cast<Attribute*>(iter->value.get()));
if (Attribute* attr = valueCast<Attribute>(iter->value.get())) {
symbol->attribute = std::unique_ptr<Attribute>(attr->clone(nullptr));
} else {
return {};
}
}
}
if (name.type == ResourceType::kAttrPrivate) {
// Masquerade this entry as kAttr.
mCache.put(ResourceName{ name.package, ResourceType::kAttr, name.entry }, symbol);
mCache.put(ResourceName(name.package, ResourceType::kAttr, name.entry), symbol);
} else {
mCache.put(name, symbol);
}

View File

@@ -43,7 +43,7 @@ public:
return *this;
}
ResourceTableBuilder& addSimple(const StringPiece16& name, ResourceId id = {}) {
ResourceTableBuilder& addSimple(const StringPiece16& name, const ResourceId id = {}) {
return addValue(name, id, util::make_unique<Id>());
}
@@ -51,7 +51,7 @@ public:
return addReference(name, {}, ref);
}
ResourceTableBuilder& addReference(const StringPiece16& name, ResourceId id,
ResourceTableBuilder& addReference(const StringPiece16& name, const ResourceId id,
const StringPiece16& ref) {
return addValue(name, id, util::make_unique<Reference>(parseNameOrDie(ref)));
}
@@ -60,7 +60,7 @@ public:
return addString(name, {}, str);
}
ResourceTableBuilder& addString(const StringPiece16& name, ResourceId id,
ResourceTableBuilder& addString(const StringPiece16& name, const ResourceId id,
const StringPiece16& str) {
return addValue(name, id, util::make_unique<String>(mTable->stringPool.makeRef(str)));
}
@@ -69,31 +69,43 @@ public:
return addFileReference(name, {}, path);
}
ResourceTableBuilder& addFileReference(const StringPiece16& name, ResourceId id,
ResourceTableBuilder& addFileReference(const StringPiece16& name, const ResourceId id,
const StringPiece16& path) {
return addValue(name, id,
util::make_unique<FileReference>(mTable->stringPool.makeRef(path)));
}
ResourceTableBuilder& addValue(const StringPiece16& name, std::unique_ptr<Value> value) {
ResourceTableBuilder& addValue(const StringPiece16& name,
std::unique_ptr<Value> value) {
return addValue(name, {}, std::move(value));
}
ResourceTableBuilder& addValue(const StringPiece16& name, ResourceId id,
ResourceTableBuilder& addValue(const StringPiece16& name, const ResourceId id,
std::unique_ptr<Value> value) {
return addValue(name, id, {}, std::move(value));
}
ResourceTableBuilder& addValue(const StringPiece16& name, ResourceId id,
const ConfigDescription& config, std::unique_ptr<Value> value) {
ResourceTableBuilder& addValue(const StringPiece16& name, const ResourceId id,
const ConfigDescription& config,
std::unique_ptr<Value> value) {
ResourceName resName = parseNameOrDie(name);
bool result = mTable->addResourceAllowMangled(resName, id, config, {}, std::move(value),
bool result = mTable->addResourceAllowMangled(resName, id, config, std::move(value),
&mDiagnostics);
assert(result);
return *this;
}
ResourceTableBuilder& setSymbolState(const StringPiece16& name, ResourceId id,
SymbolState state) {
ResourceName resName = parseNameOrDie(name);
Symbol symbol;
symbol.state = state;
bool result = mTable->setSymbolStateAllowMangled(resName, id, symbol, &mDiagnostics);
assert(result);
return *this;
}
std::unique_ptr<ResourceTable> build() {
return std::move(mTable);
}
@@ -106,6 +118,32 @@ inline std::unique_ptr<Reference> buildReference(const StringPiece16& ref,
return reference;
}
template <typename T>
class ValueBuilder {
private:
std::unique_ptr<Value> mValue;
public:
template <typename... Args>
ValueBuilder(Args&&... args) : mValue(new T{ std::forward<Args>(args)... }) {
}
template <typename... Args>
ValueBuilder& setSource(Args&&... args) {
mValue->setSource(Source{ std::forward<Args>(args)... });
return *this;
}
ValueBuilder& setComment(const StringPiece16& str) {
mValue->setComment(str);
return *this;
}
std::unique_ptr<Value> build() {
return std::move(mValue);
}
};
class AttributeBuilder {
private:
std::unique_ptr<Attribute> mAttr;

View File

@@ -422,26 +422,24 @@ bool BinaryResourceParser::parsePublic(const ResourceTablePackage* package,
const ResourceName name(package->name, *parsedType,
util::getString(mKeyPool, entry->key.index).toString());
Source source;
Symbol symbol;
if (mSourcePool.getError() == NO_ERROR) {
source.path = util::utf16ToUtf8(util::getString(
symbol.source.path = util::utf16ToUtf8(util::getString(
mSourcePool, util::deviceToHost32(entry->source.index)));
source.line = util::deviceToHost32(entry->sourceLine);
symbol.source.line = util::deviceToHost32(entry->sourceLine);
}
SymbolState state = SymbolState::kUndefined;
switch (util::deviceToHost16(entry->state)) {
case Public_entry::kPrivate:
state = SymbolState::kPrivate;
symbol.state = SymbolState::kPrivate;
break;
case Public_entry::kPublic:
state = SymbolState::kPublic;
symbol.state = SymbolState::kPublic;
break;
}
if (!mTable->setSymbolStateAllowMangled(name, resId, source, state,
mContext->getDiagnostics())) {
if (!mTable->setSymbolStateAllowMangled(name, resId, symbol, mContext->getDiagnostics())) {
return false;
}
@@ -570,14 +568,17 @@ bool BinaryResourceParser::parseType(const ResourceTablePackage* package,
source.line = util::deviceToHost32(sourceBlock->line);
}
if (!mTable->addResourceAllowMangled(name, config, source, std::move(resourceValue),
resourceValue->setSource(source);
if (!mTable->addResourceAllowMangled(name, config, std::move(resourceValue),
mContext->getDiagnostics())) {
return false;
}
if ((entry->flags & ResTable_entry::FLAG_PUBLIC) != 0) {
if (!mTable->setSymbolStateAllowMangled(name, resId, mSource.withLine(0),
SymbolState::kPublic,
Symbol symbol;
symbol.state = SymbolState::kPublic;
symbol.source = mSource.withLine(0);
if (!mTable->setSymbolStateAllowMangled(name, resId, symbol,
mContext->getDiagnostics())) {
return false;
}

View File

@@ -66,26 +66,30 @@ private:
bool parseTypeSpec(const android::ResChunk_header* chunk);
bool parseType(const ResourceTablePackage* package, const android::ResChunk_header* chunk);
std::unique_ptr<Item> parseValue(const ResourceNameRef& name,
const ConfigDescription& config, const android::Res_value* value, uint16_t flags);
std::unique_ptr<Item> parseValue(const ResourceNameRef& name, const ConfigDescription& config,
const android::Res_value* value, uint16_t flags);
std::unique_ptr<Value> parseMapEntry(const ResourceNameRef& name,
const ConfigDescription& config, const android::ResTable_map_entry* map);
const ConfigDescription& config,
const android::ResTable_map_entry* map);
std::unique_ptr<Style> parseStyle(const ResourceNameRef& name,
const ConfigDescription& config, const android::ResTable_map_entry* map);
std::unique_ptr<Style> parseStyle(const ResourceNameRef& name, const ConfigDescription& config,
const android::ResTable_map_entry* map);
std::unique_ptr<Attribute> parseAttr(const ResourceNameRef& name,
const ConfigDescription& config, const android::ResTable_map_entry* map);
const ConfigDescription& config,
const android::ResTable_map_entry* map);
std::unique_ptr<Array> parseArray(const ResourceNameRef& name,
const ConfigDescription& config, const android::ResTable_map_entry* map);
std::unique_ptr<Array> parseArray(const ResourceNameRef& name, const ConfigDescription& config,
const android::ResTable_map_entry* map);
std::unique_ptr<Plural> parsePlural(const ResourceNameRef& name,
const ConfigDescription& config, const android::ResTable_map_entry* map);
const ConfigDescription& config,
const android::ResTable_map_entry* map);
std::unique_ptr<Styleable> parseStyleable(const ResourceNameRef& name,
const ConfigDescription& config, const android::ResTable_map_entry* map);
const ConfigDescription& config,
const android::ResTable_map_entry* map);
IAaptContext* mContext;
ResourceTable* mTable;

View File

@@ -0,0 +1,30 @@
/*
* 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
namespace aapt {
namespace cmp {
inline bool lessThan(const ResourceConfigValue& a, const ConfigDescription& b) {
return a.config < b;
}
} // namespace cmp
} // namespace aapt
#endif /* AAPT_UTIL_COMPARATORS_H */