Merge "Add <macro> tag to aapt2" into sc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
3ec445926b
@@ -78,6 +78,8 @@ StringPiece to_string(ResourceType type) {
|
||||
return "interpolator";
|
||||
case ResourceType::kLayout:
|
||||
return "layout";
|
||||
case ResourceType::kMacro:
|
||||
return "macro";
|
||||
case ResourceType::kMenu:
|
||||
return "menu";
|
||||
case ResourceType::kMipmap:
|
||||
@@ -119,6 +121,7 @@ static const std::map<StringPiece, ResourceType> sResourceTypeMap{
|
||||
{"integer", ResourceType::kInteger},
|
||||
{"interpolator", ResourceType::kInterpolator},
|
||||
{"layout", ResourceType::kLayout},
|
||||
{"macro", ResourceType::kMacro},
|
||||
{"menu", ResourceType::kMenu},
|
||||
{"mipmap", ResourceType::kMipmap},
|
||||
{"navigation", ResourceType::kNavigation},
|
||||
|
||||
@@ -57,6 +57,7 @@ enum class ResourceType {
|
||||
kInteger,
|
||||
kInterpolator,
|
||||
kLayout,
|
||||
kMacro,
|
||||
kMenu,
|
||||
kMipmap,
|
||||
kNavigation,
|
||||
|
||||
@@ -627,6 +627,16 @@ bool ResourceParser::ParseResource(xml::XmlPullParser* parser,
|
||||
}
|
||||
|
||||
return true;
|
||||
} else if (resource_type == "macro") {
|
||||
if (!maybe_name) {
|
||||
diag_->Error(DiagMessage(out_resource->source)
|
||||
<< "<" << parser->element_name() << "> missing 'name' attribute");
|
||||
return false;
|
||||
}
|
||||
|
||||
out_resource->name.type = ResourceType::kMacro;
|
||||
out_resource->name.entry = maybe_name.value().to_string();
|
||||
return ParseMacro(parser, out_resource);
|
||||
}
|
||||
|
||||
if (can_be_item) {
|
||||
@@ -726,16 +736,8 @@ bool ResourceParser::ParseItem(xml::XmlPullParser* parser,
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the entire XML subtree and attempts to parse it as some Item,
|
||||
* with typeMask denoting which items it can be. If allowRawValue is
|
||||
* true, a RawString is returned if the XML couldn't be parsed as
|
||||
* an Item. If allowRawValue is false, nullptr is returned in this
|
||||
* case.
|
||||
*/
|
||||
std::unique_ptr<Item> ResourceParser::ParseXml(xml::XmlPullParser* parser,
|
||||
const uint32_t type_mask,
|
||||
const bool allow_raw_value) {
|
||||
std::optional<FlattenedXmlSubTree> ResourceParser::CreateFlattenSubTree(
|
||||
xml::XmlPullParser* parser) {
|
||||
const size_t begin_xml_line = parser->line_number();
|
||||
|
||||
std::string raw_value;
|
||||
@@ -745,30 +747,60 @@ std::unique_ptr<Item> ResourceParser::ParseXml(xml::XmlPullParser* parser,
|
||||
return {};
|
||||
}
|
||||
|
||||
if (!style_string.spans.empty()) {
|
||||
return FlattenedXmlSubTree{.raw_value = raw_value,
|
||||
.style_string = style_string,
|
||||
.untranslatable_sections = untranslatable_sections,
|
||||
.namespace_resolver = parser,
|
||||
.source = source_.WithLine(begin_xml_line)};
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the entire XML subtree and attempts to parse it as some Item,
|
||||
* with typeMask denoting which items it can be. If allowRawValue is
|
||||
* true, a RawString is returned if the XML couldn't be parsed as
|
||||
* an Item. If allowRawValue is false, nullptr is returned in this
|
||||
* case.
|
||||
*/
|
||||
std::unique_ptr<Item> ResourceParser::ParseXml(xml::XmlPullParser* parser, const uint32_t type_mask,
|
||||
const bool allow_raw_value) {
|
||||
auto sub_tree = CreateFlattenSubTree(parser);
|
||||
if (!sub_tree.has_value()) {
|
||||
return {};
|
||||
}
|
||||
return ParseXml(sub_tree.value(), type_mask, allow_raw_value, *table_, config_, *diag_);
|
||||
}
|
||||
|
||||
std::unique_ptr<Item> ResourceParser::ParseXml(const FlattenedXmlSubTree& xmlsub_tree,
|
||||
const uint32_t type_mask, const bool allow_raw_value,
|
||||
ResourceTable& table,
|
||||
const android::ConfigDescription& config,
|
||||
IDiagnostics& diag) {
|
||||
if (!xmlsub_tree.style_string.spans.empty()) {
|
||||
// This can only be a StyledString.
|
||||
std::unique_ptr<StyledString> styled_string =
|
||||
util::make_unique<StyledString>(table_->string_pool.MakeRef(
|
||||
style_string, StringPool::Context(StringPool::Context::kNormalPriority, config_)));
|
||||
styled_string->untranslatable_sections = std::move(untranslatable_sections);
|
||||
util::make_unique<StyledString>(table.string_pool.MakeRef(
|
||||
xmlsub_tree.style_string,
|
||||
StringPool::Context(StringPool::Context::kNormalPriority, config)));
|
||||
styled_string->untranslatable_sections = xmlsub_tree.untranslatable_sections;
|
||||
return std::move(styled_string);
|
||||
}
|
||||
|
||||
auto on_create_reference = [&](const ResourceName& name) {
|
||||
// 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(source_.WithLine(begin_xml_line));
|
||||
table_->AddResource(NewResourceBuilder(name).SetValue(std::move(id)).Build(), diag_);
|
||||
auto id = util::make_unique<Id>();
|
||||
id->SetSource(xmlsub_tree.source);
|
||||
return table.AddResource(NewResourceBuilder(name).SetValue(std::move(id)).Build(), &diag);
|
||||
};
|
||||
|
||||
// Process the raw value.
|
||||
std::unique_ptr<Item> processed_item =
|
||||
ResourceUtils::TryParseItemForAttribute(raw_value, type_mask, on_create_reference);
|
||||
std::unique_ptr<Item> processed_item = ResourceUtils::TryParseItemForAttribute(
|
||||
xmlsub_tree.raw_value, type_mask, on_create_reference);
|
||||
if (processed_item) {
|
||||
// Fix up the reference.
|
||||
if (Reference* ref = ValueCast<Reference>(processed_item.get())) {
|
||||
ResolvePackage(parser, ref);
|
||||
if (auto ref = ValueCast<Reference>(processed_item.get())) {
|
||||
ref->allow_raw = allow_raw_value;
|
||||
ResolvePackage(xmlsub_tree.namespace_resolver, ref);
|
||||
}
|
||||
return processed_item;
|
||||
}
|
||||
@@ -777,17 +809,16 @@ std::unique_ptr<Item> ResourceParser::ParseXml(xml::XmlPullParser* parser,
|
||||
if (type_mask & android::ResTable_map::TYPE_STRING) {
|
||||
// Use the trimmed, escaped string.
|
||||
std::unique_ptr<String> string = util::make_unique<String>(
|
||||
table_->string_pool.MakeRef(style_string.str, StringPool::Context(config_)));
|
||||
string->untranslatable_sections = std::move(untranslatable_sections);
|
||||
table.string_pool.MakeRef(xmlsub_tree.style_string.str, StringPool::Context(config)));
|
||||
string->untranslatable_sections = xmlsub_tree.untranslatable_sections;
|
||||
return std::move(string);
|
||||
}
|
||||
|
||||
if (allow_raw_value) {
|
||||
// We can't parse this so return a RawString if we are allowed.
|
||||
return util::make_unique<RawString>(
|
||||
table_->string_pool.MakeRef(util::TrimWhitespace(raw_value),
|
||||
StringPool::Context(config_)));
|
||||
} else if (util::TrimWhitespace(raw_value).empty()) {
|
||||
return util::make_unique<RawString>(table.string_pool.MakeRef(
|
||||
util::TrimWhitespace(xmlsub_tree.raw_value), StringPool::Context(config)));
|
||||
} else if (util::TrimWhitespace(xmlsub_tree.raw_value).empty()) {
|
||||
// If the text is empty, and the value is not allowed to be a string, encode it as a @null.
|
||||
return ResourceUtils::MakeNull();
|
||||
}
|
||||
@@ -850,6 +881,35 @@ bool ResourceParser::ParseString(xml::XmlPullParser* parser,
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ResourceParser::ParseMacro(xml::XmlPullParser* parser, ParsedResource* out_resource) {
|
||||
auto sub_tree = CreateFlattenSubTree(parser);
|
||||
if (!sub_tree) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (out_resource->config != ConfigDescription::DefaultConfig()) {
|
||||
diag_->Error(DiagMessage(out_resource->source)
|
||||
<< "<macro> tags cannot be declared in configurations other than the default "
|
||||
"configuration'");
|
||||
return false;
|
||||
}
|
||||
|
||||
auto macro = std::make_unique<Macro>();
|
||||
macro->raw_value = std::move(sub_tree->raw_value);
|
||||
macro->style_string = std::move(sub_tree->style_string);
|
||||
macro->untranslatable_sections = std::move(sub_tree->untranslatable_sections);
|
||||
|
||||
for (const auto& decl : parser->package_decls()) {
|
||||
macro->alias_namespaces.emplace_back(
|
||||
Macro::Namespace{.alias = decl.prefix,
|
||||
.package_name = decl.package.package,
|
||||
.is_private = decl.package.private_namespace});
|
||||
}
|
||||
|
||||
out_resource->value = std::move(macro);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ResourceParser::ParsePublic(xml::XmlPullParser* parser, ParsedResource* out_resource) {
|
||||
if (options_.visibility) {
|
||||
diag_->Error(DiagMessage(out_resource->source)
|
||||
|
||||
@@ -57,6 +57,14 @@ struct ResourceParserOptions {
|
||||
Maybe<Visibility::Level> visibility;
|
||||
};
|
||||
|
||||
struct FlattenedXmlSubTree {
|
||||
std::string raw_value;
|
||||
StyleString style_string;
|
||||
std::vector<UntranslatableSection> untranslatable_sections;
|
||||
xml::IPackageDeclStack* namespace_resolver;
|
||||
Source source;
|
||||
};
|
||||
|
||||
/*
|
||||
* Parses an XML file for resources and adds them to a ResourceTable.
|
||||
*/
|
||||
@@ -67,9 +75,16 @@ class ResourceParser {
|
||||
const ResourceParserOptions& options = {});
|
||||
bool Parse(xml::XmlPullParser* parser);
|
||||
|
||||
static std::unique_ptr<Item> ParseXml(const FlattenedXmlSubTree& xmlsub_tree, uint32_t type_mask,
|
||||
bool allow_raw_value, ResourceTable& table,
|
||||
const android::ConfigDescription& config,
|
||||
IDiagnostics& diag);
|
||||
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(ResourceParser);
|
||||
|
||||
std::optional<FlattenedXmlSubTree> CreateFlattenSubTree(xml::XmlPullParser* parser);
|
||||
|
||||
// Parses the XML subtree as a StyleString (flattened XML representation for strings with
|
||||
// formatting). If parsing fails, false is returned and the out parameters are left in an
|
||||
// unspecified state. Otherwise,
|
||||
@@ -96,7 +111,7 @@ class ResourceParser {
|
||||
|
||||
bool ParseItem(xml::XmlPullParser* parser, ParsedResource* out_resource, uint32_t format);
|
||||
bool ParseString(xml::XmlPullParser* parser, ParsedResource* out_resource);
|
||||
|
||||
bool ParseMacro(xml::XmlPullParser* parser, ParsedResource* out_resource);
|
||||
bool ParsePublic(xml::XmlPullParser* parser, ParsedResource* out_resource);
|
||||
bool ParsePublicGroup(xml::XmlPullParser* parser, ParsedResource* out_resource);
|
||||
bool ParseStagingPublicGroup(xml::XmlPullParser* parser, ParsedResource* out_resource);
|
||||
@@ -108,8 +123,7 @@ class ResourceParser {
|
||||
bool ParseAttrImpl(xml::XmlPullParser* parser, ParsedResource* out_resource, bool weak);
|
||||
Maybe<Attribute::Symbol> ParseEnumOrFlagItem(xml::XmlPullParser* parser,
|
||||
const android::StringPiece& tag);
|
||||
bool ParseStyle(const ResourceType type, xml::XmlPullParser* parser,
|
||||
ParsedResource* out_resource);
|
||||
bool ParseStyle(ResourceType type, xml::XmlPullParser* parser, ParsedResource* out_resource);
|
||||
bool ParseStyleItem(xml::XmlPullParser* parser, Style* style);
|
||||
bool ParseDeclareStyleable(xml::XmlPullParser* parser, ParsedResource* out_resource);
|
||||
bool ParseArray(xml::XmlPullParser* parser, ParsedResource* out_resource);
|
||||
|
||||
@@ -336,6 +336,90 @@ TEST_F(ResourceParserTest, ParseAttr) {
|
||||
EXPECT_THAT(attr->type_mask, Eq(ResTable_map::TYPE_ANY));
|
||||
}
|
||||
|
||||
TEST_F(ResourceParserTest, ParseMacro) {
|
||||
std::string input = R"(<macro name="foo">12345</macro>)";
|
||||
ASSERT_TRUE(TestParse(input));
|
||||
|
||||
Macro* macro = test::GetValue<Macro>(&table_, "macro/foo");
|
||||
ASSERT_THAT(macro, NotNull());
|
||||
EXPECT_THAT(macro->raw_value, Eq("12345"));
|
||||
EXPECT_THAT(macro->style_string.str, Eq("12345"));
|
||||
EXPECT_THAT(macro->style_string.spans, IsEmpty());
|
||||
EXPECT_THAT(macro->untranslatable_sections, IsEmpty());
|
||||
EXPECT_THAT(macro->alias_namespaces, IsEmpty());
|
||||
}
|
||||
|
||||
TEST_F(ResourceParserTest, ParseMacroUntranslatableSection) {
|
||||
std::string input = R"(<macro name="foo" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
This being <b><xliff:g>human</xliff:g></b> is a guest house.</macro>)";
|
||||
ASSERT_TRUE(TestParse(input));
|
||||
|
||||
Macro* macro = test::GetValue<Macro>(&table_, "macro/foo");
|
||||
ASSERT_THAT(macro, NotNull());
|
||||
EXPECT_THAT(macro->raw_value, Eq("\nThis being human is a guest house."));
|
||||
EXPECT_THAT(macro->style_string.str, Eq(" This being human is a guest house."));
|
||||
EXPECT_THAT(macro->style_string.spans.size(), Eq(1));
|
||||
EXPECT_THAT(macro->style_string.spans[0].name, Eq("b"));
|
||||
EXPECT_THAT(macro->style_string.spans[0].first_char, Eq(12));
|
||||
EXPECT_THAT(macro->style_string.spans[0].last_char, Eq(16));
|
||||
ASSERT_THAT(macro->untranslatable_sections.size(), Eq(1));
|
||||
EXPECT_THAT(macro->untranslatable_sections[0].start, Eq(12));
|
||||
EXPECT_THAT(macro->untranslatable_sections[0].end, Eq(17));
|
||||
EXPECT_THAT(macro->alias_namespaces, IsEmpty());
|
||||
}
|
||||
|
||||
TEST_F(ResourceParserTest, ParseMacroNamespaces) {
|
||||
std::string input = R"(<macro name="foo" xmlns:app="http://schemas.android.com/apk/res/android">
|
||||
@app:string/foo</macro>)";
|
||||
ASSERT_TRUE(TestParse(input));
|
||||
|
||||
Macro* macro = test::GetValue<Macro>(&table_, "macro/foo");
|
||||
ASSERT_THAT(macro, NotNull());
|
||||
EXPECT_THAT(macro->raw_value, Eq("\n@app:string/foo"));
|
||||
EXPECT_THAT(macro->style_string.str, Eq("@app:string/foo"));
|
||||
EXPECT_THAT(macro->style_string.spans, IsEmpty());
|
||||
EXPECT_THAT(macro->untranslatable_sections, IsEmpty());
|
||||
EXPECT_THAT(macro->alias_namespaces.size(), Eq(1));
|
||||
EXPECT_THAT(macro->alias_namespaces[0].alias, Eq("app"));
|
||||
EXPECT_THAT(macro->alias_namespaces[0].package_name, Eq("android"));
|
||||
EXPECT_THAT(macro->alias_namespaces[0].is_private, Eq(false));
|
||||
}
|
||||
|
||||
TEST_F(ResourceParserTest, ParseMacroReference) {
|
||||
std::string input = R"(<string name="res_string">@macro/foo</string>)";
|
||||
ASSERT_TRUE(TestParse(input));
|
||||
|
||||
Reference* macro = test::GetValue<Reference>(&table_, "string/res_string");
|
||||
ASSERT_THAT(macro, NotNull());
|
||||
EXPECT_THAT(macro->type_flags, Eq(ResTable_map::TYPE_STRING));
|
||||
EXPECT_THAT(macro->allow_raw, Eq(false));
|
||||
|
||||
input = R"(<style name="foo">
|
||||
<item name="bar">@macro/foo</item>
|
||||
</style>)";
|
||||
|
||||
ASSERT_TRUE(TestParse(input));
|
||||
Style* style = test::GetValue<Style>(&table_, "style/foo");
|
||||
ASSERT_THAT(style, NotNull());
|
||||
EXPECT_THAT(style->entries.size(), Eq(1));
|
||||
|
||||
macro = ValueCast<Reference>(style->entries[0].value.get());
|
||||
ASSERT_THAT(macro, NotNull());
|
||||
EXPECT_THAT(macro->type_flags, Eq(0U));
|
||||
EXPECT_THAT(macro->allow_raw, Eq(true));
|
||||
}
|
||||
|
||||
TEST_F(ResourceParserTest, ParseMacroNoNameFail) {
|
||||
std::string input = R"(<macro>12345</macro>)";
|
||||
ASSERT_FALSE(TestParse(input));
|
||||
}
|
||||
|
||||
TEST_F(ResourceParserTest, ParseMacroNonDefaultConfigurationFail) {
|
||||
const ConfigDescription watch_config = test::ParseConfigOrDie("watch");
|
||||
std::string input = R"(<macro name="foo">12345</macro>)";
|
||||
ASSERT_FALSE(TestParse(input, watch_config));
|
||||
}
|
||||
|
||||
// Old AAPT allowed attributes to be defined under different configurations, but ultimately
|
||||
// stored them with the default configuration. Check that we have the same behavior.
|
||||
TEST_F(ResourceParserTest, ParseAttrAndDeclareStyleableUnderConfigButRecordAsNoConfig) {
|
||||
|
||||
@@ -628,7 +628,7 @@ uint32_t AndroidTypeToAttributeTypeMask(uint16_t type) {
|
||||
|
||||
std::unique_ptr<Item> TryParseItemForAttribute(
|
||||
const StringPiece& value, uint32_t type_mask,
|
||||
const std::function<void(const ResourceName&)>& on_create_reference) {
|
||||
const std::function<bool(const ResourceName&)>& on_create_reference) {
|
||||
using android::ResTable_map;
|
||||
|
||||
auto null_or_empty = TryParseNullOrEmpty(value);
|
||||
@@ -639,8 +639,11 @@ std::unique_ptr<Item> TryParseItemForAttribute(
|
||||
bool create = false;
|
||||
auto reference = TryParseReference(value, &create);
|
||||
if (reference) {
|
||||
reference->type_flags = type_mask;
|
||||
if (create && on_create_reference) {
|
||||
on_create_reference(reference->name.value());
|
||||
if (!on_create_reference(reference->name.value())) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
return std::move(reference);
|
||||
}
|
||||
@@ -689,7 +692,7 @@ std::unique_ptr<Item> TryParseItemForAttribute(
|
||||
*/
|
||||
std::unique_ptr<Item> TryParseItemForAttribute(
|
||||
const StringPiece& str, const Attribute* attr,
|
||||
const std::function<void(const ResourceName&)>& on_create_reference) {
|
||||
const std::function<bool(const ResourceName&)>& on_create_reference) {
|
||||
using android::ResTable_map;
|
||||
|
||||
const uint32_t type_mask = attr->type_mask;
|
||||
|
||||
@@ -204,11 +204,11 @@ std::unique_ptr<BinaryPrimitive> TryParseFlagSymbol(const Attribute* enum_attr,
|
||||
*/
|
||||
std::unique_ptr<Item> TryParseItemForAttribute(
|
||||
const android::StringPiece& value, const Attribute* attr,
|
||||
const std::function<void(const ResourceName&)>& on_create_reference = {});
|
||||
const std::function<bool(const ResourceName&)>& on_create_reference = {});
|
||||
|
||||
std::unique_ptr<Item> TryParseItemForAttribute(
|
||||
const android::StringPiece& value, uint32_t type_mask,
|
||||
const std::function<void(const ResourceName&)>& on_create_reference = {});
|
||||
const std::function<bool(const ResourceName&)>& on_create_reference = {});
|
||||
|
||||
uint32_t AndroidTypeToAttributeTypeMask(uint16_t type);
|
||||
|
||||
|
||||
@@ -111,12 +111,15 @@ bool Reference::Equals(const Value* value) const {
|
||||
if (!other) {
|
||||
return false;
|
||||
}
|
||||
return reference_type == other->reference_type &&
|
||||
private_reference == other->private_reference && id == other->id &&
|
||||
name == other->name;
|
||||
return reference_type == other->reference_type && private_reference == other->private_reference &&
|
||||
id == other->id && name == other->name && type_flags == other->type_flags;
|
||||
}
|
||||
|
||||
bool Reference::Flatten(android::Res_value* out_value) const {
|
||||
if (name && name.value().type == ResourceType::kMacro) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const ResourceId resid = id.value_or_default(ResourceId(0));
|
||||
const bool dynamic = resid.is_valid() && is_dynamic;
|
||||
|
||||
@@ -551,7 +554,7 @@ bool Attribute::IsCompatibleWith(const Attribute& attr) const {
|
||||
return this_type_mask == that_type_mask;
|
||||
}
|
||||
|
||||
std::string Attribute::MaskString() const {
|
||||
std::string Attribute::MaskString(uint32_t type_mask) {
|
||||
if (type_mask == android::ResTable_map::TYPE_ANY) {
|
||||
return "any";
|
||||
}
|
||||
@@ -650,6 +653,10 @@ std::string Attribute::MaskString() const {
|
||||
return out.str();
|
||||
}
|
||||
|
||||
std::string Attribute::MaskString() const {
|
||||
return MaskString(type_mask);
|
||||
}
|
||||
|
||||
void Attribute::Print(std::ostream* out) const {
|
||||
*out << "(attr) " << MaskString();
|
||||
|
||||
@@ -1017,6 +1024,21 @@ void Styleable::Print(std::ostream* out) const {
|
||||
<< " [" << util::Joiner(entries, ", ") << "]";
|
||||
}
|
||||
|
||||
bool Macro::Equals(const Value* value) const {
|
||||
const Macro* other = ValueCast<Macro>(value);
|
||||
if (!other) {
|
||||
return false;
|
||||
}
|
||||
return other->raw_value == raw_value && other->style_string.spans == style_string.spans &&
|
||||
other->style_string.str == style_string.str &&
|
||||
other->untranslatable_sections == untranslatable_sections &&
|
||||
other->alias_namespaces == alias_namespaces;
|
||||
}
|
||||
|
||||
void Macro::Print(std::ostream* out) const {
|
||||
*out << "(macro) ";
|
||||
}
|
||||
|
||||
bool operator<(const Reference& a, const Reference& b) {
|
||||
int cmp = a.name.value_or_default({}).compare(b.name.value_or_default({}));
|
||||
if (cmp != 0) return cmp < 0;
|
||||
@@ -1149,4 +1171,9 @@ std::unique_ptr<Styleable> CloningValueTransformer::TransformDerived(const Style
|
||||
return CopyValueFields(std::move(new_value), value);
|
||||
}
|
||||
|
||||
std::unique_ptr<Macro> CloningValueTransformer::TransformDerived(const Macro* value) {
|
||||
auto new_value = std::make_unique<Macro>(*value);
|
||||
return CopyValueFields(std::move(new_value), value);
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
@@ -164,6 +164,8 @@ struct Reference : public TransformableItem<Reference, BaseItem<Reference>> {
|
||||
Reference::Type reference_type;
|
||||
bool private_reference = false;
|
||||
bool is_dynamic = false;
|
||||
std::optional<uint32_t> type_flags;
|
||||
bool allow_raw;
|
||||
|
||||
Reference();
|
||||
explicit Reference(const ResourceNameRef& n, Type type = Type::kResource);
|
||||
@@ -311,6 +313,8 @@ struct Attribute : public TransformableValue<Attribute, BaseValue<Attribute>> {
|
||||
bool IsCompatibleWith(const Attribute& attr) const;
|
||||
|
||||
std::string MaskString() const;
|
||||
static std::string MaskString(uint32_t type_mask);
|
||||
|
||||
void Print(std::ostream* out) const override;
|
||||
bool Matches(const Item& item, DiagMessage* out_msg = nullptr) const;
|
||||
};
|
||||
@@ -362,6 +366,28 @@ struct Styleable : public TransformableValue<Styleable, BaseValue<Styleable>> {
|
||||
void MergeWith(Styleable* styleable);
|
||||
};
|
||||
|
||||
struct Macro : public TransformableValue<Macro, BaseValue<Macro>> {
|
||||
std::string raw_value;
|
||||
StyleString style_string;
|
||||
std::vector<UntranslatableSection> untranslatable_sections;
|
||||
|
||||
struct Namespace {
|
||||
std::string alias;
|
||||
std::string package_name;
|
||||
bool is_private;
|
||||
|
||||
bool operator==(const Namespace& right) const {
|
||||
return alias == right.alias && package_name == right.package_name &&
|
||||
is_private == right.is_private;
|
||||
}
|
||||
};
|
||||
|
||||
std::vector<Namespace> alias_namespaces;
|
||||
|
||||
bool Equals(const Value* value) const override;
|
||||
void Print(std::ostream* out) const override;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
typename std::enable_if<std::is_base_of<Value, T>::value, std::ostream&>::type operator<<(
|
||||
std::ostream& out, const std::unique_ptr<T>& value) {
|
||||
@@ -388,6 +414,7 @@ struct CloningValueTransformer : public ValueTransformer {
|
||||
std::unique_ptr<Array> TransformDerived(const Array* value) override;
|
||||
std::unique_ptr<Plural> TransformDerived(const Plural* value) override;
|
||||
std::unique_ptr<Styleable> TransformDerived(const Styleable* value) override;
|
||||
std::unique_ptr<Macro> TransformDerived(const Macro* value) override;
|
||||
};
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
@@ -273,6 +273,7 @@ message CompoundValue {
|
||||
Styleable styleable = 3;
|
||||
Array array = 4;
|
||||
Plural plural = 5;
|
||||
MacroBody macro = 6;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -304,6 +305,13 @@ message Reference {
|
||||
|
||||
// Whether this reference is dynamic.
|
||||
Boolean is_dynamic = 5;
|
||||
|
||||
// The type flags used when compiling the reference. Used for substituting the contents of macros.
|
||||
uint32 type_flags = 6;
|
||||
|
||||
// Whether raw string values would have been accepted in place of this reference definition. Used
|
||||
// for substituting the contents of macros.
|
||||
bool allow_raw = 7;
|
||||
}
|
||||
|
||||
// A value that represents an ID. This is just a placeholder, as ID values are used to occupy a
|
||||
@@ -591,3 +599,32 @@ message XmlAttribute {
|
||||
// The optional interpreted/compiled version of the `value` string.
|
||||
Item compiled_item = 6;
|
||||
}
|
||||
|
||||
message MacroBody {
|
||||
string raw_string = 1;
|
||||
StyleString style_string = 2;
|
||||
repeated UntranslatableSection untranslatable_sections = 3;
|
||||
repeated NamespaceAlias namespace_stack = 4;
|
||||
SourcePosition source = 5;
|
||||
}
|
||||
|
||||
message NamespaceAlias {
|
||||
string prefix = 1;
|
||||
string package_name = 2;
|
||||
bool is_private = 3;
|
||||
}
|
||||
|
||||
message StyleString {
|
||||
message Span {
|
||||
string name = 1;
|
||||
uint32 start_index = 2;
|
||||
uint32 end_index = 3;
|
||||
}
|
||||
string str = 1;
|
||||
repeated Span spans = 2;
|
||||
}
|
||||
|
||||
message UntranslatableSection {
|
||||
uint64 start_index = 1;
|
||||
uint64 end_index = 2;
|
||||
}
|
||||
@@ -36,6 +36,10 @@ struct Span {
|
||||
std::string name;
|
||||
uint32_t first_char;
|
||||
uint32_t last_char;
|
||||
|
||||
bool operator==(const Span& right) const {
|
||||
return name == right.name && first_char == right.first_char && last_char == right.last_char;
|
||||
}
|
||||
};
|
||||
|
||||
struct StyleString {
|
||||
|
||||
@@ -46,5 +46,6 @@ VALUE_CREATE_VALUE_DECL(Style);
|
||||
VALUE_CREATE_VALUE_DECL(Array);
|
||||
VALUE_CREATE_VALUE_DECL(Plural);
|
||||
VALUE_CREATE_VALUE_DECL(Styleable);
|
||||
VALUE_CREATE_VALUE_DECL(Macro);
|
||||
|
||||
} // namespace aapt
|
||||
@@ -37,6 +37,7 @@ struct Style;
|
||||
struct Array;
|
||||
struct Plural;
|
||||
struct Styleable;
|
||||
struct Macro;
|
||||
|
||||
#define AAPT_TRANSFORM_VALUE(T) \
|
||||
virtual std::unique_ptr<T> TransformDerived(const T* value) = 0; \
|
||||
@@ -97,6 +98,7 @@ struct ValueTransformer {
|
||||
AAPT_TRANSFORM_VALUE(Array);
|
||||
AAPT_TRANSFORM_VALUE(Plural);
|
||||
AAPT_TRANSFORM_VALUE(Styleable);
|
||||
AAPT_TRANSFORM_VALUE(Macro);
|
||||
|
||||
protected:
|
||||
StringPool* const pool_;
|
||||
|
||||
@@ -43,6 +43,9 @@ class ValueVisitor {
|
||||
virtual void Visit(Array* value) { VisitAny(value); }
|
||||
virtual void Visit(Plural* value) { VisitAny(value); }
|
||||
virtual void Visit(Styleable* value) { VisitAny(value); }
|
||||
virtual void Visit(Macro* value) {
|
||||
VisitAny(value);
|
||||
}
|
||||
};
|
||||
|
||||
// Const version of ValueVisitor.
|
||||
@@ -92,6 +95,9 @@ class ConstValueVisitor {
|
||||
virtual void Visit(const Styleable* value) {
|
||||
VisitAny(value);
|
||||
}
|
||||
virtual void Visit(const Macro* value) {
|
||||
VisitAny(value);
|
||||
}
|
||||
};
|
||||
|
||||
// NOLINT, do not add parentheses around T.
|
||||
|
||||
@@ -462,7 +462,7 @@ std::vector<std::unique_ptr<xml::XmlResource>> ResourceFileFlattener::LinkAndVer
|
||||
// that existing projects have out-of-date references which pass compilation.
|
||||
xml::StripAndroidStudioAttributes(doc->root.get());
|
||||
|
||||
XmlReferenceLinker xml_linker;
|
||||
XmlReferenceLinker xml_linker(table);
|
||||
if (!options_.do_not_fail_on_missing_resources && !xml_linker.Consume(context_, doc)) {
|
||||
return {};
|
||||
}
|
||||
@@ -2112,7 +2112,7 @@ class Linker {
|
||||
std::unique_ptr<xml::XmlResource> split_manifest =
|
||||
GenerateSplitManifest(app_info_, *split_constraints_iter);
|
||||
|
||||
XmlReferenceLinker linker;
|
||||
XmlReferenceLinker linker(&final_table_);
|
||||
if (!linker.Consume(context_, split_manifest.get())) {
|
||||
context_->GetDiagnostics()->Error(DiagMessage()
|
||||
<< "failed to create Split AndroidManifest.xml");
|
||||
@@ -2143,7 +2143,7 @@ class Linker {
|
||||
// So we give it a package name so it can see local resources.
|
||||
manifest_xml->file.name.package = context_->GetCompilationPackage();
|
||||
|
||||
XmlReferenceLinker manifest_linker;
|
||||
XmlReferenceLinker manifest_linker(&final_table_);
|
||||
if (options_.merge_only || manifest_linker.Consume(context_, manifest_xml.get())) {
|
||||
if (options_.generate_proguard_rules_path &&
|
||||
!proguard::CollectProguardRulesForManifest(manifest_xml.get(), &proguard_keep_set)) {
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
|
||||
using testing::Eq;
|
||||
using testing::HasSubstr;
|
||||
using testing::IsNull;
|
||||
using testing::Ne;
|
||||
using testing::NotNull;
|
||||
|
||||
@@ -532,4 +533,109 @@ TEST_F(LinkTest, StagedAndroidApi) {
|
||||
EXPECT_THAT(*result, Eq(0x01fd0072));
|
||||
}
|
||||
|
||||
TEST_F(LinkTest, MacroSubstitution) {
|
||||
StdErrDiagnostics diag;
|
||||
const std::string values =
|
||||
R"(<resources xmlns:an="http://schemas.android.com/apk/res/android">
|
||||
<macro name="is_enabled">true</macro>
|
||||
<macro name="deep_is_enabled">@macro/is_enabled</macro>
|
||||
<macro name="attr_ref">?is_enabled_attr</macro>
|
||||
<macro name="raw_string">Hello World!</macro>
|
||||
<macro name="android_ref">@an:color/primary_text_dark</macro>
|
||||
|
||||
<attr name="is_enabled_attr" />
|
||||
<public type="attr" name="is_enabled_attr" id="0x7f010000"/>
|
||||
|
||||
<string name="is_enabled_str">@macro/is_enabled</string>
|
||||
<bool name="is_enabled_bool">@macro/deep_is_enabled</bool>
|
||||
|
||||
<array name="my_array">
|
||||
<item>@macro/is_enabled</item>
|
||||
</array>
|
||||
|
||||
<style name="MyStyle">
|
||||
<item name="android:background">@macro/attr_ref</item>
|
||||
<item name="android:fontFamily">@macro/raw_string</item>
|
||||
</style>
|
||||
</resources>)";
|
||||
|
||||
const std::string xml_values =
|
||||
R"(<SomeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:background="@macro/android_ref"
|
||||
android:fontFamily="@macro/raw_string">
|
||||
</SomeLayout>)";
|
||||
|
||||
// Build a library with a public attribute
|
||||
const std::string lib_res = GetTestPath("test-res");
|
||||
ASSERT_TRUE(CompileFile(GetTestPath("res/values/values.xml"), values, lib_res, &diag));
|
||||
ASSERT_TRUE(CompileFile(GetTestPath("res/layout/layout.xml"), xml_values, lib_res, &diag));
|
||||
|
||||
const std::string lib_apk = GetTestPath("test.apk");
|
||||
// clang-format off
|
||||
auto lib_link_args = LinkCommandBuilder(this)
|
||||
.SetManifestFile(ManifestBuilder(this).SetPackageName("com.test").Build())
|
||||
.AddCompiledResDir(lib_res, &diag)
|
||||
.AddFlag("--no-auto-version")
|
||||
.Build(lib_apk);
|
||||
// clang-format on
|
||||
ASSERT_TRUE(Link(lib_link_args, &diag));
|
||||
|
||||
auto apk = LoadedApk::LoadApkFromPath(lib_apk, &diag);
|
||||
ASSERT_THAT(apk, NotNull());
|
||||
|
||||
// Test that the type flags determines the value type
|
||||
auto actual_bool =
|
||||
test::GetValue<BinaryPrimitive>(apk->GetResourceTable(), "com.test:bool/is_enabled_bool");
|
||||
ASSERT_THAT(actual_bool, NotNull());
|
||||
EXPECT_EQ(android::Res_value::TYPE_INT_BOOLEAN, actual_bool->value.dataType);
|
||||
EXPECT_EQ(0xffffffffu, actual_bool->value.data);
|
||||
|
||||
auto actual_str =
|
||||
test::GetValue<String>(apk->GetResourceTable(), "com.test:string/is_enabled_str");
|
||||
ASSERT_THAT(actual_str, NotNull());
|
||||
EXPECT_EQ(*actual_str->value, "true");
|
||||
|
||||
// Test nested data structures
|
||||
auto actual_array = test::GetValue<Array>(apk->GetResourceTable(), "com.test:array/my_array");
|
||||
ASSERT_THAT(actual_array, NotNull());
|
||||
EXPECT_THAT(actual_array->elements.size(), Eq(1));
|
||||
|
||||
auto array_el_ref = ValueCast<BinaryPrimitive>(actual_array->elements[0].get());
|
||||
ASSERT_THAT(array_el_ref, NotNull());
|
||||
EXPECT_THAT(array_el_ref->value.dataType, Eq(android::Res_value::TYPE_INT_BOOLEAN));
|
||||
EXPECT_THAT(array_el_ref->value.data, Eq(0xffffffffu));
|
||||
|
||||
auto actual_style = test::GetValue<Style>(apk->GetResourceTable(), "com.test:style/MyStyle");
|
||||
ASSERT_THAT(actual_style, NotNull());
|
||||
EXPECT_THAT(actual_style->entries.size(), Eq(2));
|
||||
|
||||
{
|
||||
auto style_el = ValueCast<Reference>(actual_style->entries[0].value.get());
|
||||
ASSERT_THAT(style_el, NotNull());
|
||||
EXPECT_THAT(style_el->reference_type, Eq(Reference::Type::kAttribute));
|
||||
EXPECT_THAT(style_el->id, Eq(0x7f010000));
|
||||
}
|
||||
|
||||
{
|
||||
auto style_el = ValueCast<String>(actual_style->entries[1].value.get());
|
||||
ASSERT_THAT(style_el, NotNull());
|
||||
EXPECT_THAT(*style_el->value, Eq("Hello World!"));
|
||||
}
|
||||
|
||||
// Test substitution in compiled xml files
|
||||
auto xml = apk->LoadXml("res/layout/layout.xml", &diag);
|
||||
ASSERT_THAT(xml, NotNull());
|
||||
|
||||
auto& xml_attrs = xml->root->attributes;
|
||||
ASSERT_THAT(xml_attrs.size(), Eq(2));
|
||||
|
||||
auto attr_value = ValueCast<Reference>(xml_attrs[0].compiled_value.get());
|
||||
ASSERT_THAT(attr_value, NotNull());
|
||||
EXPECT_THAT(attr_value->reference_type, Eq(Reference::Type::kResource));
|
||||
EXPECT_THAT(attr_value->id, Eq(0x01060001));
|
||||
|
||||
EXPECT_THAT(xml_attrs[1].compiled_value.get(), IsNull());
|
||||
EXPECT_THAT(xml_attrs[1].value, Eq("Hello World!"));
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
@@ -567,13 +567,10 @@ class PackageFlattener {
|
||||
}
|
||||
|
||||
bool FlattenTypes(BigBuffer* buffer) {
|
||||
// Sort the types by their IDs. They will be inserted into the StringPool in
|
||||
// this order.
|
||||
|
||||
size_t expected_type_id = 1;
|
||||
for (const ResourceTableTypeView& type : package_.types) {
|
||||
if (type.type == ResourceType::kStyleable) {
|
||||
// Styleables aren't real Resource Types, they are represented in the R.java file.
|
||||
if (type.type == ResourceType::kStyleable || type.type == ResourceType::kMacro) {
|
||||
// Styleables and macros are not real resource types.
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -222,7 +222,7 @@ TEST_F(XmlFlattenerTest, FlattenNonStandardPackageId) {
|
||||
android:id="@id/foo"
|
||||
app:foo="@id/foo" />)");
|
||||
|
||||
XmlReferenceLinker linker;
|
||||
XmlReferenceLinker linker(nullptr);
|
||||
ASSERT_TRUE(linker.Consume(context_.get(), doc.get()));
|
||||
|
||||
// The tree needs a custom DynamicRefTable since it is not using a standard app ID (0x7f).
|
||||
|
||||
@@ -656,6 +656,38 @@ static bool DeserializeReferenceFromPb(const pb::Reference& pb_ref, Reference* o
|
||||
}
|
||||
out_ref->name = name_ref.ToResourceName();
|
||||
}
|
||||
if (pb_ref.type_flags() != 0) {
|
||||
out_ref->type_flags = pb_ref.type_flags();
|
||||
}
|
||||
out_ref->allow_raw = pb_ref.allow_raw();
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool DeserializeMacroFromPb(const pb::MacroBody& pb_ref, Macro* out_ref,
|
||||
std::string* out_error) {
|
||||
out_ref->raw_value = pb_ref.raw_string();
|
||||
|
||||
if (pb_ref.has_style_string()) {
|
||||
out_ref->style_string.str = pb_ref.style_string().str();
|
||||
for (const auto& span : pb_ref.style_string().spans()) {
|
||||
out_ref->style_string.spans.emplace_back(Span{
|
||||
.name = span.name(), .first_char = span.start_index(), .last_char = span.end_index()});
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& untranslatable_section : pb_ref.untranslatable_sections()) {
|
||||
out_ref->untranslatable_sections.emplace_back(
|
||||
UntranslatableSection{.start = static_cast<size_t>(untranslatable_section.start_index()),
|
||||
.end = static_cast<size_t>(untranslatable_section.end_index())});
|
||||
}
|
||||
|
||||
for (const auto& namespace_decls : pb_ref.namespace_stack()) {
|
||||
out_ref->alias_namespaces.emplace_back(
|
||||
Macro::Namespace{.alias = namespace_decls.prefix(),
|
||||
.package_name = namespace_decls.package_name(),
|
||||
.is_private = namespace_decls.is_private()});
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -801,6 +833,15 @@ std::unique_ptr<Value> DeserializeValueFromPb(const pb::Value& pb_value,
|
||||
value = std::move(plural);
|
||||
} break;
|
||||
|
||||
case pb::CompoundValue::kMacro: {
|
||||
const pb::MacroBody& pb_macro = pb_compound_value.macro();
|
||||
auto macro = std::make_unique<Macro>();
|
||||
if (!DeserializeMacroFromPb(pb_macro, macro.get(), out_error)) {
|
||||
return {};
|
||||
}
|
||||
value = std::move(macro);
|
||||
} break;
|
||||
|
||||
default:
|
||||
LOG(FATAL) << "unknown compound value: " << (int)pb_compound_value.value_case();
|
||||
break;
|
||||
|
||||
@@ -440,6 +440,36 @@ static void SerializeReferenceToPb(const Reference& ref, pb::Reference* pb_ref)
|
||||
if (ref.is_dynamic) {
|
||||
pb_ref->mutable_is_dynamic()->set_value(ref.is_dynamic);
|
||||
}
|
||||
if (ref.type_flags) {
|
||||
pb_ref->set_type_flags(*ref.type_flags);
|
||||
}
|
||||
pb_ref->set_allow_raw(ref.allow_raw);
|
||||
}
|
||||
|
||||
static void SerializeMacroToPb(const Macro& ref, pb::MacroBody* pb_macro) {
|
||||
pb_macro->set_raw_string(ref.raw_value);
|
||||
|
||||
auto pb_style_str = pb_macro->mutable_style_string();
|
||||
pb_style_str->set_str(ref.style_string.str);
|
||||
for (const auto& span : ref.style_string.spans) {
|
||||
auto pb_span = pb_style_str->add_spans();
|
||||
pb_span->set_name(span.name);
|
||||
pb_span->set_start_index(span.first_char);
|
||||
pb_span->set_end_index(span.last_char);
|
||||
}
|
||||
|
||||
for (const auto& untranslatable_section : ref.untranslatable_sections) {
|
||||
auto pb_section = pb_macro->add_untranslatable_sections();
|
||||
pb_section->set_start_index(untranslatable_section.start);
|
||||
pb_section->set_end_index(untranslatable_section.end);
|
||||
}
|
||||
|
||||
for (const auto& namespace_decls : ref.alias_namespaces) {
|
||||
auto pb_namespace = pb_macro->add_namespace_stack();
|
||||
pb_namespace->set_prefix(namespace_decls.alias);
|
||||
pb_namespace->set_package_name(namespace_decls.package_name);
|
||||
pb_namespace->set_is_private(namespace_decls.is_private);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
@@ -643,6 +673,11 @@ class ValueSerializer : public ConstValueVisitor {
|
||||
}
|
||||
}
|
||||
|
||||
void Visit(const Macro* macro) override {
|
||||
pb::MacroBody* pb_macro = out_value_->mutable_compound_value()->mutable_macro();
|
||||
SerializeMacroToPb(*macro, pb_macro);
|
||||
}
|
||||
|
||||
void VisitAny(const Value* unknown) override {
|
||||
LOG(FATAL) << "unimplemented value: " << *unknown;
|
||||
}
|
||||
|
||||
@@ -894,4 +894,38 @@ TEST(ProtoSerializeTest, ObfuscatingResourceNamesWithNameCollapseExemptionsSucce
|
||||
EXPECT_THAT(*(s->value), Eq("foo"));
|
||||
}
|
||||
|
||||
TEST(ProtoSerializeTest, SerializeMacro) {
|
||||
auto original = std::make_unique<Macro>();
|
||||
original->raw_value = "\nThis being human is a guest house.";
|
||||
original->style_string.str = " This being human is a guest house.";
|
||||
original->style_string.spans.emplace_back(Span{.name = "b", .first_char = 12, .last_char = 16});
|
||||
original->untranslatable_sections.emplace_back(UntranslatableSection{.start = 12, .end = 17});
|
||||
original->alias_namespaces.emplace_back(
|
||||
Macro::Namespace{.alias = "prefix", .package_name = "package.name", .is_private = true});
|
||||
|
||||
CloningValueTransformer cloner(nullptr);
|
||||
std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
|
||||
std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
|
||||
.Add(NewResourceBuilder("com.app.a:macro/foo")
|
||||
.SetValue(original->Transform(cloner))
|
||||
.Build())
|
||||
.Build();
|
||||
|
||||
ResourceTable new_table;
|
||||
pb::ResourceTable pb_table;
|
||||
MockFileCollection files;
|
||||
std::string error;
|
||||
SerializeTableToPb(*table, &pb_table, context->GetDiagnostics());
|
||||
ASSERT_TRUE(DeserializeTableFromPb(pb_table, &files, &new_table, &error));
|
||||
EXPECT_THAT(error, IsEmpty());
|
||||
|
||||
Macro* deserialized = test::GetValue<Macro>(&new_table, "com.app.a:macro/foo");
|
||||
ASSERT_THAT(deserialized, NotNull());
|
||||
EXPECT_THAT(deserialized->raw_value, Eq(original->raw_value));
|
||||
EXPECT_THAT(deserialized->style_string.str, Eq(original->style_string.str));
|
||||
EXPECT_THAT(deserialized->style_string.spans, Eq(original->style_string.spans));
|
||||
EXPECT_THAT(deserialized->untranslatable_sections, Eq(original->untranslatable_sections));
|
||||
EXPECT_THAT(deserialized->alias_namespaces, Eq(original->alias_namespaces));
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
@@ -616,8 +616,9 @@ bool JavaClassGenerator::Generate(const StringPiece& package_name_to_generate,
|
||||
|
||||
for (const auto& package : table_->packages) {
|
||||
for (const auto& type : package->types) {
|
||||
if (type->type == ResourceType::kAttrPrivate) {
|
||||
// We generate these as part of the kAttr type, so skip them here.
|
||||
if (type->type == ResourceType::kAttrPrivate || type->type == ResourceType::kMacro) {
|
||||
// We generate kAttrPrivate as part of the kAttr type, so skip them here.
|
||||
// Macros are not actual resources, so skip them as well.
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -570,4 +570,25 @@ TEST(JavaClassGeneratorTest, SortsDynamicAttributesAfterFrameworkAttributes) {
|
||||
EXPECT_THAT(output, HasSubstr("public static final int MyStyleable_dynamic_attr=1;"));
|
||||
}
|
||||
|
||||
TEST(JavaClassGeneratorTest, SkipMacros) {
|
||||
std::unique_ptr<ResourceTable> table =
|
||||
test::ResourceTableBuilder()
|
||||
.AddValue("android:macro/bar", ResourceId(0x01010000), test::AttributeBuilder().Build())
|
||||
.Build();
|
||||
|
||||
std::unique_ptr<IAaptContext> context =
|
||||
test::ContextBuilder()
|
||||
.AddSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get()))
|
||||
.SetNameManglerPolicy(NameManglerPolicy{"android"})
|
||||
.Build();
|
||||
JavaClassGenerator generator(context.get(), table.get(), {});
|
||||
|
||||
std::string output;
|
||||
StringOutputStream out(&output);
|
||||
EXPECT_TRUE(generator.Generate("android", &out));
|
||||
out.Flush();
|
||||
|
||||
EXPECT_THAT(output, Not(HasSubstr("bar")));
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
@@ -264,7 +264,7 @@ TEST(ProguardRulesTest, IncludedLayoutRulesAreConditional) {
|
||||
</View>)");
|
||||
foo_layout->file.name = test::ParseNameOrDie("com.foo:layout/foo");
|
||||
|
||||
XmlReferenceLinker xml_linker;
|
||||
XmlReferenceLinker xml_linker(nullptr);
|
||||
ASSERT_TRUE(xml_linker.Consume(context.get(), bar_layout.get()));
|
||||
ASSERT_TRUE(xml_linker.Consume(context.get(), foo_layout.get()));
|
||||
|
||||
|
||||
@@ -133,12 +133,14 @@ class XmlNamespaceRemover : public IXmlResourceConsumer {
|
||||
// Once an XmlResource is processed by this linker, it is ready to be flattened.
|
||||
class XmlReferenceLinker : public IXmlResourceConsumer {
|
||||
public:
|
||||
XmlReferenceLinker() = default;
|
||||
explicit XmlReferenceLinker(ResourceTable* table) : table_(table) {
|
||||
}
|
||||
|
||||
bool Consume(IAaptContext* context, xml::XmlResource* resource) override;
|
||||
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(XmlReferenceLinker);
|
||||
ResourceTable* table_;
|
||||
};
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include "androidfw/ResourceTypes.h"
|
||||
|
||||
#include "Diagnostics.h"
|
||||
#include "ResourceParser.h"
|
||||
#include "ResourceTable.h"
|
||||
#include "ResourceUtils.h"
|
||||
#include "ResourceValues.h"
|
||||
@@ -37,128 +38,153 @@ using ::android::StringPiece;
|
||||
using ::android::base::StringPrintf;
|
||||
|
||||
namespace aapt {
|
||||
|
||||
namespace {
|
||||
|
||||
// The ReferenceLinkerVisitor will follow all references and make sure they point
|
||||
// to resources that actually exist, either in the local resource table, or as external
|
||||
// symbols. Once the target resource has been found, the ID of the resource will be assigned
|
||||
// to the reference object.
|
||||
//
|
||||
// NOTE: All of the entries in the ResourceTable must be assigned IDs.
|
||||
class ReferenceLinkerVisitor : public DescendingValueVisitor {
|
||||
public:
|
||||
using DescendingValueVisitor::Visit;
|
||||
|
||||
ReferenceLinkerVisitor(const CallSite& callsite, IAaptContext* context, SymbolTable* symbols,
|
||||
StringPool* string_pool, xml::IPackageDeclStack* decl)
|
||||
: callsite_(callsite),
|
||||
context_(context),
|
||||
symbols_(symbols),
|
||||
package_decls_(decl),
|
||||
string_pool_(string_pool) {}
|
||||
|
||||
void Visit(Reference* ref) override {
|
||||
if (!ReferenceLinker::LinkReference(callsite_, ref, context_, symbols_, package_decls_)) {
|
||||
error_ = true;
|
||||
}
|
||||
struct LoggingResourceName {
|
||||
LoggingResourceName(const Reference& ref, const CallSite& callsite,
|
||||
const xml::IPackageDeclStack* decls)
|
||||
: ref_(ref), callsite_(callsite), decls_(decls) {
|
||||
}
|
||||
|
||||
// We visit the Style specially because during this phase, values of attributes are
|
||||
// all RawString values. Now that we are expected to resolve all symbols, we can
|
||||
// lookup the attributes to find out which types are allowed for the attributes' values.
|
||||
void Visit(Style* style) override {
|
||||
if (style->parent) {
|
||||
Visit(&style->parent.value());
|
||||
const Reference& ref_;
|
||||
const CallSite& callsite_;
|
||||
const xml::IPackageDeclStack* decls_;
|
||||
};
|
||||
|
||||
inline ::std::ostream& operator<<(::std::ostream& out, const LoggingResourceName& name) {
|
||||
if (!name.ref_.name) {
|
||||
out << name.ref_.id.value();
|
||||
return out;
|
||||
}
|
||||
|
||||
out << name.ref_.name.value();
|
||||
|
||||
Reference fully_qualified = name.ref_;
|
||||
xml::ResolvePackage(name.decls_, &fully_qualified);
|
||||
|
||||
ResourceName& full_name = fully_qualified.name.value();
|
||||
if (full_name.package.empty()) {
|
||||
full_name.package = name.callsite_.package;
|
||||
}
|
||||
|
||||
if (full_name != name.ref_.name.value()) {
|
||||
out << " (aka " << full_name << ")";
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::unique_ptr<Reference> ReferenceLinkerTransformer::TransformDerived(const Reference* value) {
|
||||
auto linked_item =
|
||||
ReferenceLinker::LinkReference(callsite_, *value, context_, symbols_, table_, package_decls_);
|
||||
if (linked_item) {
|
||||
auto linked_item_ptr = linked_item.release();
|
||||
if (auto ref = ValueCast<Reference>(linked_item_ptr)) {
|
||||
return std::unique_ptr<Reference>(ref);
|
||||
}
|
||||
context_->GetDiagnostics()->Error(DiagMessage(value->GetSource())
|
||||
<< "value of '"
|
||||
<< LoggingResourceName(*value, callsite_, package_decls_)
|
||||
<< "' must be a resource reference");
|
||||
delete linked_item_ptr;
|
||||
}
|
||||
|
||||
for (Style::Entry& entry : style->entries) {
|
||||
std::string err_str;
|
||||
error_ = true;
|
||||
return CloningValueTransformer::TransformDerived(value);
|
||||
}
|
||||
|
||||
// Transform the attribute reference so that it is using the fully qualified package
|
||||
// name. This will also mark the reference as being able to see private resources if
|
||||
// there was a '*' in the reference or if the package came from the private namespace.
|
||||
Reference transformed_reference = entry.key;
|
||||
ResolvePackage(package_decls_, &transformed_reference);
|
||||
std::unique_ptr<Style> ReferenceLinkerTransformer::TransformDerived(const Style* style) {
|
||||
// We visit the Style specially because during this phase, values of attributes are either
|
||||
// RawString or Reference values. Now that we are expected to resolve all symbols, we can lookup
|
||||
// the attributes to find out which types are allowed for the attributes' values.
|
||||
auto new_style = CloningValueTransformer::TransformDerived(style);
|
||||
if (new_style->parent) {
|
||||
new_style->parent = *TransformDerived(&style->parent.value());
|
||||
}
|
||||
|
||||
// Find the attribute in the symbol table and check if it is visible from this callsite.
|
||||
const SymbolTable::Symbol* symbol = ReferenceLinker::ResolveAttributeCheckVisibility(
|
||||
transformed_reference, callsite_, context_, symbols_, &err_str);
|
||||
if (symbol) {
|
||||
// Assign our style key the correct ID. The ID may not exist.
|
||||
entry.key.id = symbol->id;
|
||||
for (Style::Entry& entry : new_style->entries) {
|
||||
std::string err_str;
|
||||
|
||||
// Try to convert the value to a more specific, typed value based on the attribute it is
|
||||
// set to.
|
||||
entry.value = ParseValueWithAttribute(std::move(entry.value), symbol->attribute.get());
|
||||
// Transform the attribute reference so that it is using the fully qualified package
|
||||
// name. This will also mark the reference as being able to see private resources if
|
||||
// there was a '*' in the reference or if the package came from the private namespace.
|
||||
Reference transformed_reference = entry.key;
|
||||
ResolvePackage(package_decls_, &transformed_reference);
|
||||
|
||||
// Link/resolve the final value (mostly if it's a reference).
|
||||
entry.value->Accept(this);
|
||||
// Find the attribute in the symbol table and check if it is visible from this callsite.
|
||||
const SymbolTable::Symbol* symbol = ReferenceLinker::ResolveAttributeCheckVisibility(
|
||||
transformed_reference, callsite_, context_, symbols_, &err_str);
|
||||
if (symbol) {
|
||||
// Assign our style key the correct ID. The ID may not exist.
|
||||
entry.key.id = symbol->id;
|
||||
|
||||
// Now verify that the type of this item is compatible with the
|
||||
// attribute it is defined for. We pass `nullptr` as the DiagMessage so that this
|
||||
// check is fast and we avoid creating a DiagMessage when the match is successful.
|
||||
if (!symbol->attribute->Matches(*entry.value, nullptr)) {
|
||||
// The actual type of this item is incompatible with the attribute.
|
||||
DiagMessage msg(entry.key.GetSource());
|
||||
// Link/resolve the final value if it's a reference.
|
||||
entry.value = entry.value->Transform(*this);
|
||||
|
||||
// Call the matches method again, this time with a DiagMessage so we fill in the actual
|
||||
// error message.
|
||||
symbol->attribute->Matches(*entry.value, &msg);
|
||||
context_->GetDiagnostics()->Error(msg);
|
||||
error_ = true;
|
||||
}
|
||||
// Try to convert the value to a more specific, typed value based on the attribute it is
|
||||
// set to.
|
||||
entry.value = ParseValueWithAttribute(std::move(entry.value), symbol->attribute.get());
|
||||
|
||||
} else {
|
||||
// Now verify that the type of this item is compatible with the
|
||||
// attribute it is defined for. We pass `nullptr` as the DiagMessage so that this
|
||||
// check is fast and we avoid creating a DiagMessage when the match is successful.
|
||||
if (!symbol->attribute->Matches(*entry.value, nullptr)) {
|
||||
// The actual type of this item is incompatible with the attribute.
|
||||
DiagMessage msg(entry.key.GetSource());
|
||||
msg << "style attribute '";
|
||||
ReferenceLinker::WriteResourceName(entry.key, callsite_, package_decls_, &msg);
|
||||
msg << "' " << err_str;
|
||||
|
||||
// Call the matches method again, this time with a DiagMessage so we fill in the actual
|
||||
// error message.
|
||||
symbol->attribute->Matches(*entry.value, &msg);
|
||||
context_->GetDiagnostics()->Error(msg);
|
||||
error_ = true;
|
||||
}
|
||||
} else {
|
||||
context_->GetDiagnostics()->Error(DiagMessage(entry.key.GetSource())
|
||||
<< "style attribute '"
|
||||
<< LoggingResourceName(entry.key, callsite_, package_decls_)
|
||||
<< "' " << err_str);
|
||||
|
||||
error_ = true;
|
||||
}
|
||||
}
|
||||
return new_style;
|
||||
}
|
||||
|
||||
bool HasError() {
|
||||
return error_;
|
||||
std::unique_ptr<Item> ReferenceLinkerTransformer::TransformItem(const Reference* value) {
|
||||
auto linked_value =
|
||||
ReferenceLinker::LinkReference(callsite_, *value, context_, symbols_, table_, package_decls_);
|
||||
if (linked_value) {
|
||||
return linked_value;
|
||||
}
|
||||
error_ = true;
|
||||
return CloningValueTransformer::TransformDerived(value);
|
||||
}
|
||||
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(ReferenceLinkerVisitor);
|
||||
// Transform a RawString value into a more specific, appropriate value, based on the
|
||||
// Attribute. If a non RawString value is passed in, this is an identity transform.
|
||||
std::unique_ptr<Item> ReferenceLinkerTransformer::ParseValueWithAttribute(
|
||||
std::unique_ptr<Item> value, const Attribute* attr) {
|
||||
if (RawString* raw_string = ValueCast<RawString>(value.get())) {
|
||||
std::unique_ptr<Item> transformed =
|
||||
ResourceUtils::TryParseItemForAttribute(*raw_string->value, attr);
|
||||
|
||||
// Transform a RawString value into a more specific, appropriate value, based on the
|
||||
// Attribute. If a non RawString value is passed in, this is an identity transform.
|
||||
std::unique_ptr<Item> ParseValueWithAttribute(std::unique_ptr<Item> value,
|
||||
const Attribute* attr) {
|
||||
if (RawString* raw_string = ValueCast<RawString>(value.get())) {
|
||||
std::unique_ptr<Item> transformed =
|
||||
ResourceUtils::TryParseItemForAttribute(*raw_string->value, attr);
|
||||
|
||||
// If we could not parse as any specific type, try a basic STRING.
|
||||
if (!transformed && (attr->type_mask & android::ResTable_map::TYPE_STRING)) {
|
||||
StringBuilder string_builder;
|
||||
string_builder.AppendText(*raw_string->value);
|
||||
if (string_builder) {
|
||||
transformed =
|
||||
util::make_unique<String>(string_pool_->MakeRef(string_builder.to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
if (transformed) {
|
||||
return transformed;
|
||||
// If we could not parse as any specific type, try a basic STRING.
|
||||
if (!transformed && (attr->type_mask & android::ResTable_map::TYPE_STRING)) {
|
||||
StringBuilder string_builder;
|
||||
string_builder.AppendText(*raw_string->value);
|
||||
if (string_builder) {
|
||||
transformed = util::make_unique<String>(pool_->MakeRef(string_builder.to_string()));
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
const CallSite& callsite_;
|
||||
IAaptContext* context_;
|
||||
SymbolTable* symbols_;
|
||||
xml::IPackageDeclStack* package_decls_;
|
||||
StringPool* string_pool_;
|
||||
bool error_ = false;
|
||||
};
|
||||
if (transformed) {
|
||||
return transformed;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
class EmptyDeclStack : public xml::IPackageDeclStack {
|
||||
public:
|
||||
@@ -175,6 +201,27 @@ class EmptyDeclStack : public xml::IPackageDeclStack {
|
||||
DISALLOW_COPY_AND_ASSIGN(EmptyDeclStack);
|
||||
};
|
||||
|
||||
struct MacroDeclStack : public xml::IPackageDeclStack {
|
||||
explicit MacroDeclStack(std::vector<Macro::Namespace> namespaces)
|
||||
: alias_namespaces_(std::move(namespaces)) {
|
||||
}
|
||||
|
||||
Maybe<xml::ExtractedPackage> TransformPackageAlias(const StringPiece& alias) const override {
|
||||
if (alias.empty()) {
|
||||
return xml::ExtractedPackage{{}, true /*private*/};
|
||||
}
|
||||
for (auto it = alias_namespaces_.rbegin(); it != alias_namespaces_.rend(); ++it) {
|
||||
if (alias == StringPiece(it->alias)) {
|
||||
return xml::ExtractedPackage{it->package_name, it->is_private};
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<Macro::Namespace> alias_namespaces_;
|
||||
};
|
||||
|
||||
// The symbol is visible if it is public, or if the reference to it is requesting private access
|
||||
// or if the callsite comes from the same package.
|
||||
bool IsSymbolVisible(const SymbolTable::Symbol& symbol, const Reference& ref,
|
||||
@@ -220,8 +267,6 @@ const SymbolTable::Symbol* ReferenceLinker::ResolveSymbol(const Reference& refer
|
||||
// If the callsite package is the same as the current compilation package,
|
||||
// check the feature split dependencies as well. Feature split resources
|
||||
// can be referenced without a namespace, just like the base package.
|
||||
// TODO: modify the package name of included splits instead of having the
|
||||
// symbol table look up the resource in in every package. b/136105066
|
||||
if (callsite.package == context->GetCompilationPackage()) {
|
||||
const auto& split_name_dependencies = context->GetSplitNameDependencies();
|
||||
for (const std::string& split_name : split_name_dependencies) {
|
||||
@@ -295,29 +340,6 @@ Maybe<xml::AaptAttribute> ReferenceLinker::CompileXmlAttribute(const Reference&
|
||||
return xml::AaptAttribute(*symbol->attribute, symbol->id);
|
||||
}
|
||||
|
||||
void ReferenceLinker::WriteResourceName(const Reference& ref, const CallSite& callsite,
|
||||
const xml::IPackageDeclStack* decls, DiagMessage* out_msg) {
|
||||
CHECK(out_msg != nullptr);
|
||||
if (!ref.name) {
|
||||
*out_msg << ref.id.value();
|
||||
return;
|
||||
}
|
||||
|
||||
*out_msg << ref.name.value();
|
||||
|
||||
Reference fully_qualified = ref;
|
||||
xml::ResolvePackage(decls, &fully_qualified);
|
||||
|
||||
ResourceName& full_name = fully_qualified.name.value();
|
||||
if (full_name.package.empty()) {
|
||||
full_name.package = callsite.package;
|
||||
}
|
||||
|
||||
if (full_name != ref.name.value()) {
|
||||
*out_msg << " (aka " << full_name << ")";
|
||||
}
|
||||
}
|
||||
|
||||
void ReferenceLinker::WriteAttributeName(const Reference& ref, const CallSite& callsite,
|
||||
const xml::IPackageDeclStack* decls,
|
||||
DiagMessage* out_msg) {
|
||||
@@ -348,18 +370,71 @@ void ReferenceLinker::WriteAttributeName(const Reference& ref, const CallSite& c
|
||||
}
|
||||
}
|
||||
|
||||
bool ReferenceLinker::LinkReference(const CallSite& callsite, Reference* reference,
|
||||
IAaptContext* context, SymbolTable* symbols,
|
||||
const xml::IPackageDeclStack* decls) {
|
||||
CHECK(reference != nullptr);
|
||||
if (!reference->name && !reference->id) {
|
||||
std::unique_ptr<Item> ReferenceLinker::LinkReference(const CallSite& callsite,
|
||||
const Reference& reference,
|
||||
IAaptContext* context, SymbolTable* symbols,
|
||||
ResourceTable* table,
|
||||
const xml::IPackageDeclStack* decls) {
|
||||
if (!reference.name && !reference.id) {
|
||||
// This is @null.
|
||||
return true;
|
||||
return std::make_unique<Reference>(reference);
|
||||
}
|
||||
|
||||
Reference transformed_reference = *reference;
|
||||
Reference transformed_reference = reference;
|
||||
xml::ResolvePackage(decls, &transformed_reference);
|
||||
|
||||
if (transformed_reference.name.value().type == ResourceType::kMacro) {
|
||||
if (transformed_reference.name.value().package.empty()) {
|
||||
transformed_reference.name.value().package = callsite.package;
|
||||
}
|
||||
|
||||
auto result = table->FindResource(transformed_reference.name.value());
|
||||
if (!result || result.value().entry->values.empty()) {
|
||||
context->GetDiagnostics()->Error(
|
||||
DiagMessage(reference.GetSource())
|
||||
<< "failed to find definition for "
|
||||
<< LoggingResourceName(transformed_reference, callsite, decls));
|
||||
return {};
|
||||
}
|
||||
|
||||
auto& macro_values = result.value().entry->values;
|
||||
CHECK(macro_values.size() == 1) << "Macros can only be defined in the default configuration.";
|
||||
|
||||
auto macro = ValueCast<Macro>(macro_values[0]->value.get());
|
||||
CHECK(macro != nullptr) << "Value of macro resource is not a Macro (actual "
|
||||
<< *macro_values[0]->value << ")";
|
||||
|
||||
// Re-create the state used to parse the macro tag to compile the macro contents as if it was
|
||||
// defined inline
|
||||
uint32_t type_flags = 0;
|
||||
if (reference.type_flags.has_value()) {
|
||||
type_flags = reference.type_flags.value();
|
||||
}
|
||||
|
||||
MacroDeclStack namespace_stack(macro->alias_namespaces);
|
||||
FlattenedXmlSubTree sub_tree{.raw_value = macro->raw_value,
|
||||
.style_string = macro->style_string,
|
||||
.untranslatable_sections = macro->untranslatable_sections,
|
||||
.namespace_resolver = &namespace_stack,
|
||||
.source = macro->GetSource()};
|
||||
|
||||
auto new_value = ResourceParser::ParseXml(sub_tree, type_flags, reference.allow_raw, *table,
|
||||
macro_values[0]->config, *context->GetDiagnostics());
|
||||
if (new_value == nullptr) {
|
||||
context->GetDiagnostics()->Error(
|
||||
DiagMessage(reference.GetSource())
|
||||
<< "failed to substitute macro "
|
||||
<< LoggingResourceName(transformed_reference, callsite, decls)
|
||||
<< ": failed to parse contents as one of type(s) " << Attribute::MaskString(type_flags));
|
||||
return {};
|
||||
}
|
||||
|
||||
if (auto ref = ValueCast<Reference>(new_value.get())) {
|
||||
return LinkReference(callsite, *ref, context, symbols, table, decls);
|
||||
}
|
||||
return new_value;
|
||||
}
|
||||
|
||||
std::string err_str;
|
||||
const SymbolTable::Symbol* s =
|
||||
ResolveSymbolCheckVisibility(transformed_reference, callsite, context, symbols, &err_str);
|
||||
@@ -367,17 +442,17 @@ bool ReferenceLinker::LinkReference(const CallSite& callsite, Reference* referen
|
||||
// The ID may not exist. This is fine because of the possibility of building
|
||||
// against libraries without assigned IDs.
|
||||
// Ex: Linking against own resources when building a static library.
|
||||
reference->id = s->id;
|
||||
reference->is_dynamic = s->is_dynamic;
|
||||
return true;
|
||||
auto new_ref = std::make_unique<Reference>(reference);
|
||||
new_ref->id = s->id;
|
||||
new_ref->is_dynamic = s->is_dynamic;
|
||||
return std::move(new_ref);
|
||||
}
|
||||
|
||||
DiagMessage error_msg(reference->GetSource());
|
||||
error_msg << "resource ";
|
||||
WriteResourceName(*reference, callsite, decls, &error_msg);
|
||||
error_msg << " " << err_str;
|
||||
context->GetDiagnostics()->Error(error_msg);
|
||||
return false;
|
||||
context->GetDiagnostics()->Error(DiagMessage(reference.GetSource())
|
||||
<< "resource "
|
||||
<< LoggingResourceName(transformed_reference, callsite, decls)
|
||||
<< " " << err_str);
|
||||
return {};
|
||||
}
|
||||
|
||||
bool ReferenceLinker::Consume(IAaptContext* context, ResourceTable* table) {
|
||||
@@ -412,14 +487,15 @@ bool ReferenceLinker::Consume(IAaptContext* context, ResourceTable* table) {
|
||||
|
||||
// The context of this resource is the package in which it is defined.
|
||||
const CallSite callsite{name.package};
|
||||
ReferenceLinkerVisitor visitor(callsite, context, context->GetExternalSymbols(),
|
||||
&table->string_pool, &decl_stack);
|
||||
ReferenceLinkerTransformer reference_transformer(callsite, context,
|
||||
context->GetExternalSymbols(),
|
||||
&table->string_pool, table, &decl_stack);
|
||||
|
||||
for (auto& config_value : entry->values) {
|
||||
config_value->value->Accept(&visitor);
|
||||
config_value->value = config_value->value->Transform(reference_transformer);
|
||||
}
|
||||
|
||||
if (visitor.HasError()) {
|
||||
if (reference_transformer.HasError()) {
|
||||
error = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,41 @@
|
||||
|
||||
namespace aapt {
|
||||
|
||||
// A ValueTransformer that returns fully linked versions of resource and macro references.
|
||||
class ReferenceLinkerTransformer : public CloningValueTransformer {
|
||||
public:
|
||||
ReferenceLinkerTransformer(const CallSite& callsite, IAaptContext* context, SymbolTable* symbols,
|
||||
StringPool* string_pool, ResourceTable* table,
|
||||
xml::IPackageDeclStack* decl)
|
||||
: CloningValueTransformer(string_pool),
|
||||
callsite_(callsite),
|
||||
context_(context),
|
||||
symbols_(symbols),
|
||||
table_(table),
|
||||
package_decls_(decl) {
|
||||
}
|
||||
|
||||
std::unique_ptr<Reference> TransformDerived(const Reference* value) override;
|
||||
std::unique_ptr<Item> TransformItem(const Reference* value) override;
|
||||
std::unique_ptr<Style> TransformDerived(const Style* value) override;
|
||||
|
||||
bool HasError() {
|
||||
return error_;
|
||||
}
|
||||
|
||||
private:
|
||||
// Transform a RawString value into a more specific, appropriate value, based on the
|
||||
// Attribute. If a non RawString value is passed in, this is an identity transform.
|
||||
std::unique_ptr<Item> ParseValueWithAttribute(std::unique_ptr<Item> value, const Attribute* attr);
|
||||
|
||||
const CallSite& callsite_;
|
||||
IAaptContext* context_;
|
||||
SymbolTable* symbols_;
|
||||
ResourceTable* table_;
|
||||
xml::IPackageDeclStack* package_decls_;
|
||||
bool error_ = false;
|
||||
};
|
||||
|
||||
// Resolves all references to resources in the ResourceTable and assigns them IDs.
|
||||
// The ResourceTable must already have IDs assigned to each resource.
|
||||
// Once the ResourceTable is processed by this linker, it is ready to be flattened.
|
||||
@@ -70,19 +105,28 @@ class ReferenceLinker : public IResourceTableConsumer {
|
||||
|
||||
// Writes the resource name to the DiagMessage, using the
|
||||
// "orig_name (aka <transformed_name>)" syntax.
|
||||
static void WriteResourceName(const Reference& orig, const CallSite& callsite,
|
||||
const xml::IPackageDeclStack* decls, DiagMessage* out_msg);
|
||||
/*static void WriteResourceName(const Reference& orig, const CallSite& callsite,
|
||||
const xml::IPackageDeclStack* decls, DiagMessage* out_msg);*/
|
||||
|
||||
// Same as WriteResourceName but omits the 'attr' part.
|
||||
static void WriteAttributeName(const Reference& ref, const CallSite& callsite,
|
||||
const xml::IPackageDeclStack* decls, DiagMessage* out_msg);
|
||||
|
||||
// Transforms the package name of the reference to the fully qualified package name using
|
||||
// the xml::IPackageDeclStack, then mangles and looks up the symbol. If the symbol is visible
|
||||
// to the reference at the callsite, the reference is updated with an ID.
|
||||
// Returns false on failure, and an error message is logged to the IDiagnostics in the context.
|
||||
static bool LinkReference(const CallSite& callsite, Reference* reference, IAaptContext* context,
|
||||
SymbolTable* symbols, const xml::IPackageDeclStack* decls);
|
||||
// Returns a fully linked version a resource reference.
|
||||
//
|
||||
// If the reference points to a non-macro resource, the xml::IPackageDeclStack is used to
|
||||
// determine the fully qualified name of the referenced resource. If the symbol is visible
|
||||
// to the reference at the callsite, a copy of the reference with an updated updated ID is
|
||||
// returned.
|
||||
//
|
||||
// If the reference points to a macro, the ResourceTable is used to find the macro definition and
|
||||
// substitute its contents in place of the reference.
|
||||
//
|
||||
// Returns nullptr on failure, and an error message is logged to the IDiagnostics in the context.
|
||||
static std::unique_ptr<Item> LinkReference(const CallSite& callsite, const Reference& reference,
|
||||
IAaptContext* context, SymbolTable* symbols,
|
||||
ResourceTable* table,
|
||||
const xml::IPackageDeclStack* decls);
|
||||
|
||||
// Links all references in the ResourceTable.
|
||||
bool Consume(IAaptContext* context, ResourceTable* table) override;
|
||||
|
||||
@@ -365,4 +365,22 @@ TEST(ReferenceLinkerTest, ReferenceSymbolFromOtherSplit) {
|
||||
EXPECT_THAT(s, IsNull());
|
||||
}
|
||||
|
||||
TEST(ReferenceLinkerTest, MacroFailToFindDefinition) {
|
||||
std::unique_ptr<ResourceTable> table =
|
||||
test::ResourceTableBuilder()
|
||||
.AddReference("com.app.test:string/foo", ResourceId(0x7f020000), "com.app.test:macro/bar")
|
||||
.Build();
|
||||
|
||||
std::unique_ptr<IAaptContext> context =
|
||||
test::ContextBuilder()
|
||||
.SetCompilationPackage("com.app.test")
|
||||
.SetPackageId(0x7f)
|
||||
.SetNameManglerPolicy(NameManglerPolicy{"com.app.test"})
|
||||
.AddSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get()))
|
||||
.Build();
|
||||
|
||||
ReferenceLinker linker;
|
||||
ASSERT_FALSE(linker.Consume(context.get(), table.get()));
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
@@ -82,7 +82,7 @@ TEST_F(XmlCompatVersionerTest, NoRulesOnlyStripsAndCopies) {
|
||||
app:foo="16dp"
|
||||
foo="bar"/>)");
|
||||
|
||||
XmlReferenceLinker linker;
|
||||
XmlReferenceLinker linker(nullptr);
|
||||
ASSERT_TRUE(linker.Consume(context_.get(), doc.get()));
|
||||
|
||||
XmlCompatVersioner::Rules rules;
|
||||
@@ -121,7 +121,7 @@ TEST_F(XmlCompatVersionerTest, SingleRule) {
|
||||
app:foo="16dp"
|
||||
foo="bar"/>)");
|
||||
|
||||
XmlReferenceLinker linker;
|
||||
XmlReferenceLinker linker(nullptr);
|
||||
ASSERT_TRUE(linker.Consume(context_.get(), doc.get()));
|
||||
|
||||
XmlCompatVersioner::Rules rules;
|
||||
@@ -181,7 +181,7 @@ TEST_F(XmlCompatVersionerTest, ChainedRule) {
|
||||
<View xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:paddingHorizontal="24dp" />)");
|
||||
|
||||
XmlReferenceLinker linker;
|
||||
XmlReferenceLinker linker(nullptr);
|
||||
ASSERT_TRUE(linker.Consume(context_.get(), doc.get()));
|
||||
|
||||
XmlCompatVersioner::Rules rules;
|
||||
@@ -256,7 +256,7 @@ TEST_F(XmlCompatVersionerTest, DegradeRuleOverridesExistingAttribute) {
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp"/>)");
|
||||
|
||||
XmlReferenceLinker linker;
|
||||
XmlReferenceLinker linker(nullptr);
|
||||
ASSERT_TRUE(linker.Consume(context_.get(), doc.get()));
|
||||
|
||||
Item* padding_horizontal_value =
|
||||
|
||||
@@ -33,49 +33,18 @@ namespace aapt {
|
||||
|
||||
namespace {
|
||||
|
||||
// Visits all references (including parents of styles, references in styles, arrays, etc) and
|
||||
// links their symbolic name to their Resource ID, performing mangling and package aliasing
|
||||
// as needed.
|
||||
class ReferenceVisitor : public DescendingValueVisitor {
|
||||
public:
|
||||
using DescendingValueVisitor::Visit;
|
||||
|
||||
ReferenceVisitor(const CallSite& callsite, IAaptContext* context, SymbolTable* symbols,
|
||||
xml::IPackageDeclStack* decls)
|
||||
: callsite_(callsite), context_(context), symbols_(symbols), decls_(decls), error_(false) {}
|
||||
|
||||
void Visit(Reference* ref) override {
|
||||
if (!ReferenceLinker::LinkReference(callsite_, ref, context_, symbols_, decls_)) {
|
||||
error_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool HasError() const {
|
||||
return error_;
|
||||
}
|
||||
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(ReferenceVisitor);
|
||||
|
||||
const CallSite& callsite_;
|
||||
IAaptContext* context_;
|
||||
SymbolTable* symbols_;
|
||||
xml::IPackageDeclStack* decls_;
|
||||
bool error_;
|
||||
};
|
||||
|
||||
// Visits each xml Element and compiles the attributes within.
|
||||
class XmlVisitor : public xml::PackageAwareVisitor {
|
||||
public:
|
||||
using xml::PackageAwareVisitor::Visit;
|
||||
|
||||
XmlVisitor(const Source& source, const CallSite& callsite, IAaptContext* context,
|
||||
SymbolTable* symbols)
|
||||
XmlVisitor(const Source& source, StringPool* pool, const CallSite& callsite,
|
||||
IAaptContext* context, ResourceTable* table, SymbolTable* symbols)
|
||||
: source_(source),
|
||||
callsite_(callsite),
|
||||
context_(context),
|
||||
symbols_(symbols),
|
||||
reference_visitor_(callsite, context, symbols, this) {
|
||||
reference_transformer_(callsite, context, symbols, pool, table, this) {
|
||||
}
|
||||
|
||||
void Visit(xml::Element* el) override {
|
||||
@@ -127,7 +96,7 @@ class XmlVisitor : public xml::PackageAwareVisitor {
|
||||
if (attr.compiled_value) {
|
||||
// With a compiledValue, we must resolve the reference and assign it an ID.
|
||||
attr.compiled_value->SetSource(source);
|
||||
attr.compiled_value->Accept(&reference_visitor_);
|
||||
attr.compiled_value = attr.compiled_value->Transform(reference_transformer_);
|
||||
} else if ((attribute->type_mask & android::ResTable_map::TYPE_STRING) == 0) {
|
||||
// We won't be able to encode this as a string.
|
||||
DiagMessage msg(source);
|
||||
@@ -143,7 +112,7 @@ class XmlVisitor : public xml::PackageAwareVisitor {
|
||||
}
|
||||
|
||||
bool HasError() {
|
||||
return error_ || reference_visitor_.HasError();
|
||||
return error_ || reference_transformer_.HasError();
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -154,7 +123,7 @@ class XmlVisitor : public xml::PackageAwareVisitor {
|
||||
IAaptContext* context_;
|
||||
SymbolTable* symbols_;
|
||||
|
||||
ReferenceVisitor reference_visitor_;
|
||||
ReferenceLinkerTransformer reference_transformer_;
|
||||
bool error_ = false;
|
||||
};
|
||||
|
||||
@@ -173,7 +142,8 @@ bool XmlReferenceLinker::Consume(IAaptContext* context, xml::XmlResource* resour
|
||||
callsite.package = context->GetCompilationPackage();
|
||||
}
|
||||
|
||||
XmlVisitor visitor(resource->file.source, callsite, context, context->GetExternalSymbols());
|
||||
XmlVisitor visitor(resource->file.source, &resource->string_pool, callsite, context, table_,
|
||||
context->GetExternalSymbols());
|
||||
if (resource->root) {
|
||||
resource->root->Accept(&visitor);
|
||||
return !visitor.HasError();
|
||||
|
||||
@@ -91,7 +91,7 @@ TEST_F(XmlReferenceLinkerTest, LinkBasicAttributes) {
|
||||
nonAaptAttrRef="@id/id"
|
||||
class="hello" />)");
|
||||
|
||||
XmlReferenceLinker linker;
|
||||
XmlReferenceLinker linker(nullptr);
|
||||
ASSERT_TRUE(linker.Consume(context_.get(), doc.get()));
|
||||
|
||||
xml::Element* view_el = doc->root.get();
|
||||
@@ -144,7 +144,7 @@ TEST_F(XmlReferenceLinkerTest, PrivateSymbolsAreNotLinked) {
|
||||
<View xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:colorAccent="@android:color/hidden" />)");
|
||||
|
||||
XmlReferenceLinker linker;
|
||||
XmlReferenceLinker linker(nullptr);
|
||||
ASSERT_FALSE(linker.Consume(context_.get(), doc.get()));
|
||||
}
|
||||
|
||||
@@ -153,7 +153,7 @@ TEST_F(XmlReferenceLinkerTest, PrivateSymbolsAreLinkedWhenReferenceHasStarPrefix
|
||||
<View xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:colorAccent="@*android:color/hidden" />)");
|
||||
|
||||
XmlReferenceLinker linker;
|
||||
XmlReferenceLinker linker(nullptr);
|
||||
ASSERT_TRUE(linker.Consume(context_.get(), doc.get()));
|
||||
}
|
||||
|
||||
@@ -162,7 +162,7 @@ TEST_F(XmlReferenceLinkerTest, LinkMangledAttributes) {
|
||||
<View xmlns:support="http://schemas.android.com/apk/res/com.android.support"
|
||||
support:colorAccent="#ff0000" />)");
|
||||
|
||||
XmlReferenceLinker linker;
|
||||
XmlReferenceLinker linker(nullptr);
|
||||
ASSERT_TRUE(linker.Consume(context_.get(), doc.get()));
|
||||
|
||||
xml::Element* view_el = doc->root.get();
|
||||
@@ -181,7 +181,7 @@ TEST_F(XmlReferenceLinkerTest, LinkAutoResReference) {
|
||||
<View xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
app:colorAccent="@app:color/red" />)");
|
||||
|
||||
XmlReferenceLinker linker;
|
||||
XmlReferenceLinker linker(nullptr);
|
||||
ASSERT_TRUE(linker.Consume(context_.get(), doc.get()));
|
||||
|
||||
xml::Element* view_el = doc->root.get();
|
||||
@@ -203,7 +203,7 @@ TEST_F(XmlReferenceLinkerTest, LinkViewWithShadowedPackageAlias) {
|
||||
<View xmlns:app="http://schemas.android.com/apk/res/com.app.test" app:attr="@app:id/id"/>
|
||||
</View>)");
|
||||
|
||||
XmlReferenceLinker linker;
|
||||
XmlReferenceLinker linker(nullptr);
|
||||
ASSERT_TRUE(linker.Consume(context_.get(), doc.get()));
|
||||
|
||||
xml::Element* view_el = doc->root.get();
|
||||
@@ -239,7 +239,7 @@ TEST_F(XmlReferenceLinkerTest, LinkViewWithLocalPackageAndAliasOfTheSameName) {
|
||||
<View xmlns:android="http://schemas.android.com/apk/res/com.app.test"
|
||||
android:attr="@id/id"/>)");
|
||||
|
||||
XmlReferenceLinker linker;
|
||||
XmlReferenceLinker linker(nullptr);
|
||||
ASSERT_TRUE(linker.Consume(context_.get(), doc.get()));
|
||||
|
||||
xml::Element* view_el = doc->root.get();
|
||||
@@ -261,7 +261,7 @@ TEST_F(XmlReferenceLinkerTest, AddAngleOnGradientForAndroidQ) {
|
||||
std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDomForPackageName(context_.get(), R"(
|
||||
<gradient />)");
|
||||
|
||||
XmlReferenceLinker linker;
|
||||
XmlReferenceLinker linker(nullptr);
|
||||
ASSERT_TRUE(linker.Consume(context_.get(), doc.get()));
|
||||
|
||||
xml::Element* gradient_el = doc->root.get();
|
||||
@@ -283,7 +283,7 @@ TEST_F(XmlReferenceLinkerTest, DoNotOverwriteAngleOnGradientForAndroidQ) {
|
||||
<gradient xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:angle="90"/>)");
|
||||
|
||||
XmlReferenceLinker linker;
|
||||
XmlReferenceLinker linker(nullptr);
|
||||
ASSERT_TRUE(linker.Consume(context_.get(), doc.get()));
|
||||
|
||||
xml::Element* gradient_el = doc->root.get();
|
||||
@@ -305,7 +305,7 @@ TEST_F(XmlReferenceLinkerTest, DoNotOverwriteAngleOnGradientForPostAndroidQ) {
|
||||
<gradient xmlns:android="http://schemas.android.com/apk/res/android" />)");
|
||||
context_->SetMinSdkVersion(30);
|
||||
|
||||
XmlReferenceLinker linker;
|
||||
XmlReferenceLinker linker(nullptr);
|
||||
ASSERT_TRUE(linker.Consume(context_.get(), doc.get()));
|
||||
|
||||
xml::Element* gradient_el = doc->root.get();
|
||||
|
||||
@@ -177,6 +177,10 @@ const std::string& XmlPullParser::element_name() const {
|
||||
return event_queue_.front().data2;
|
||||
}
|
||||
|
||||
const std::vector<XmlPullParser::PackageDecl>& XmlPullParser::package_decls() const {
|
||||
return package_aliases_;
|
||||
}
|
||||
|
||||
XmlPullParser::const_iterator XmlPullParser::begin_attributes() const {
|
||||
return event_queue_.front().attributes.begin();
|
||||
}
|
||||
|
||||
@@ -123,6 +123,13 @@ class XmlPullParser : public IPackageDeclStack {
|
||||
*/
|
||||
Maybe<ExtractedPackage> TransformPackageAlias(const android::StringPiece& alias) const override;
|
||||
|
||||
struct PackageDecl {
|
||||
std::string prefix;
|
||||
ExtractedPackage package;
|
||||
};
|
||||
|
||||
const std::vector<PackageDecl>& package_decls() const;
|
||||
|
||||
//
|
||||
// Remaining methods are for retrieving information about attributes
|
||||
// associated with a StartElement.
|
||||
@@ -180,11 +187,6 @@ class XmlPullParser : public IPackageDeclStack {
|
||||
const std::string empty_;
|
||||
size_t depth_;
|
||||
std::stack<std::string> namespace_uris_;
|
||||
|
||||
struct PackageDecl {
|
||||
std::string prefix;
|
||||
ExtractedPackage package;
|
||||
};
|
||||
std::vector<PackageDecl> package_aliases_;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user