Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 326e35ff authored by Ryan Mitchell's avatar Ryan Mitchell
Browse files

Add <macro> tag to aapt2

AAPT2 Macros are compile-time resources definitions that are expanded
when referenced during the link phase.

A macro must be defined in the res/values.xml directory. A macro
definition for a macro named "foo" looks like the following:
 <macro name="foo">contents</macro>

When "@macro/foo" is used in the res/values directory or in a compiled
XML file, the contents of the macro replace the macro reference and
then the substituted contents are compiled and linked. If the macro
contents reference xml namespaces from its original definition, the
namespaces of the original macro definition will be used to determine
which package is being referenced.

Macros can be used anywhere resources can be referenced using the
@package:type/entry syntax.

Macros are not included in the final resource table or the R.java since
they are not actual resources.

Bug: 175616308
Test: aapt2_tests
Change-Id: I48b29ab6564357b32b4b4e32bff7ef06036382bc
parent ff68a9ad
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -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},
+1 −0
Original line number Diff line number Diff line
@@ -57,6 +57,7 @@ enum class ResourceType {
  kInteger,
  kInterpolator,
  kLayout,
  kMacro,
  kMenu,
  kMipmap,
  kNavigation,
+85 −25
Original line number Diff line number Diff line
@@ -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,6 +736,24 @@ bool ResourceParser::ParseItem(xml::XmlPullParser* parser,
  return true;
}

std::optional<FlattenedXmlSubTree> ResourceParser::CreateFlattenSubTree(
    xml::XmlPullParser* parser) {
  const size_t begin_xml_line = parser->line_number();

  std::string raw_value;
  StyleString style_string;
  std::vector<UntranslatableSection> untranslatable_sections;
  if (!FlattenXmlSubtree(parser, &raw_value, &style_string, &untranslatable_sections)) {
    return {};
  }

  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
@@ -733,42 +761,46 @@ bool ResourceParser::ParseItem(xml::XmlPullParser* parser,
 * 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,
std::unique_ptr<Item> ResourceParser::ParseXml(xml::XmlPullParser* parser, const uint32_t type_mask,
                                               const bool allow_raw_value) {
  const size_t begin_xml_line = parser->line_number();

  std::string raw_value;
  StyleString style_string;
  std::vector<UntranslatableSection> untranslatable_sections;
  if (!FlattenXmlSubtree(parser, &raw_value, &style_string, &untranslatable_sections)) {
  auto sub_tree = CreateFlattenSubTree(parser);
  if (!sub_tree.has_value()) {
    return {};
  }
  return ParseXml(sub_tree.value(), type_mask, allow_raw_value, *table_, config_, *diag_);
}

  if (!style_string.spans.empty()) {
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)
+17 −3
Original line number Diff line number Diff line
@@ -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);
+84 −0
Original line number Diff line number Diff line
@@ -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) {
Loading