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

Commit e116ebe0 authored by Adam Lesinski's avatar Adam Lesinski
Browse files

AAPT2: Change proto format to reduce usage of StringPool

The StringPool class is a binary blob and makes it difficult to
modify the proto files from external tools (like bundle-tool).

Size increase of full build is negligible.

Test: make aapt2_tests
Bug: 65645766
Change-Id: I984755170c315730ab751b51133f8fb2f614f6af
Merged-In: I984755170c315730ab751b51133f8fb2f614f6af
parent ee0e486d
Loading
Loading
Loading
Loading
+33 −25
Original line number Diff line number Diff line
@@ -67,21 +67,12 @@ message CompiledFile {

// Top level message representing a resource table.
message ResourceTable {
  // The string pool containing string values (strings that actually end up in the binary ARSC
  // file) referenced throughout the resource table.
  optional StringPool string_pool = 1;

  // The string pool containing source paths referenced throughout the resource table. This does
  // not end up in the final binary ARSC file.
  optional StringPool source_pool = 2;

  // The string pool containing the names of unresolved symbols. This does not end up in the final
  // binary ARSC file. Unresolved symbols are just resource names that haven't had a resource ID
  // assigned to them, therefore can't be referenced by resource ID.
  optional StringPool symbol_pool = 3;
  optional StringPool source_pool = 1;

  // Resource definitions corresponding to an Android package.
  repeated Package packages = 4;
  repeated Package packages = 2;
}

// Defines resources for an Android package.
@@ -202,9 +193,10 @@ message Item {
  optional Reference ref = 1;
  optional String str = 2;
  optional RawString raw_str = 3;
  optional FileReference file = 4;
  optional Id id = 5;
  optional Primitive prim = 6;
  optional StyledString styled_str = 4;
  optional FileReference file = 5;
  optional Id id = 6;
  optional Primitive prim = 7;
}

// A CompoundValue is an abstract type. It represents a value that is a made of other values.
@@ -233,10 +225,8 @@ message Reference {
  // The resource ID (0xPPTTEEEE) of the resource being referred.
  optional uint32 id = 2;

  // If the resource ID is not resolved, the index into the symbol string pool where the name of
  // the reference is stored. The symbol string pool is located at the top level ResourceTable
  // message.
  optional uint32 symbol_idx = 3;
  // The optional resource name.
  optional string name = 3;

  // Whether this reference is referencing a private resource (@*package:type/entry).
  optional bool private = 4;
@@ -249,23 +239,41 @@ message Id {

// A value that is a string.
message String {
  // The index into the values string pool, located at the top level ResourceTable message.
  optional uint32 idx = 1;
  optional string value = 1;
}

// A value that is a raw string, which is unescaped/uninterpreted. This is typically used to
// represent the value of a style attribute before the attribute is compiled and the set of
// allowed values is known.
message RawString {
  // The index into the values string pool, located at the top level ResourceTable message.
  optional uint32 idx = 1;
  optional string value = 1;
}

// A string with styling information, like html tags that specify boldness, italics, etc.
message StyledString {
  // The raw text of the string.
  optional string value = 1;

  // A Span marks a region of the string text that is styled.
  message Span {
    // The name of the tag, and its attributes, encoded as follows:
    // tag_name;attr1=value1;attr2=value2;[...]
    optional string tag = 1;

    // The first character position this span applies to, in UTF-16 offset.
    optional uint32 first_char = 2;

    // The last character position this span applies to, in UTF-16 offset.
    optional uint32 last_char = 3;
  }

  repeated Span span = 2;
}

// A value that is a reference to an external entity, like an XML file or a PNG.
message FileReference {
  // The index into the values string pool, located at the top level ResourceTable message. This
  // represents the path to the file within an APK (typically res/type-config/entry.ext).
  optional uint32 path_idx = 1;
  // Path to a file within the APK (typically res/type-config/entry.ext).
  optional string path = 1;
}

// A value that represents a primitive data type (float, int, boolean, etc.).
+33 −86
Original line number Diff line number Diff line
@@ -55,15 +55,10 @@ class ReferenceIdToNameVisitor : public ValueVisitor {

class PackagePbDeserializer {
 public:
  PackagePbDeserializer(const android::ResStringPool* valuePool,
                        const android::ResStringPool* sourcePool,
                        const android::ResStringPool* symbolPool,
                        const Source& source, IDiagnostics* diag)
      : value_pool_(valuePool),
        source_pool_(sourcePool),
        symbol_pool_(symbolPool),
        source_(source),
        diag_(diag) {}
  PackagePbDeserializer(const android::ResStringPool* sourcePool, const Source& source,
                        IDiagnostics* diag)
      : source_pool_(sourcePool), source_(source), diag_(diag) {
  }

 public:
  bool DeserializeFromPb(const pb::Package& pbPackage, ResourceTable* table) {
@@ -87,8 +82,7 @@ class PackagePbDeserializer {
      for (const pb::Entry& pbEntry : pbType.entries()) {
        ResourceEntry* entry = type->FindOrCreateEntry(pbEntry.name());

        // Deserialize the symbol status (public/private with source and
        // comments).
        // Deserialize the symbol status (public/private with source and comments).
        if (pbEntry.has_symbol_status()) {
          const pb::SymbolStatus& pbStatus = pbEntry.symbol_status();
          if (pbStatus.has_source()) {
@@ -161,8 +155,7 @@ class PackagePbDeserializer {

 private:
  std::unique_ptr<Item> DeserializeItemFromPb(const pb::Item& pb_item,
                                              const ConfigDescription& config,
                                              StringPool* pool) {
                                              const ConfigDescription& config, StringPool* pool) {
    if (pb_item.has_ref()) {
      const pb::Reference& pb_ref = pb_item.ref();
      std::unique_ptr<Reference> ref = util::make_unique<Reference>();
@@ -173,45 +166,32 @@ class PackagePbDeserializer {

    } else if (pb_item.has_prim()) {
      const pb::Primitive& pb_prim = pb_item.prim();
      android::Res_value prim = {};
      prim.dataType = static_cast<uint8_t>(pb_prim.type());
      prim.data = pb_prim.data();
      return util::make_unique<BinaryPrimitive>(prim);
      return util::make_unique<BinaryPrimitive>(static_cast<uint8_t>(pb_prim.type()),
                                                pb_prim.data());

    } else if (pb_item.has_id()) {
      return util::make_unique<Id>();

    } else if (pb_item.has_str()) {
      const uint32_t idx = pb_item.str().idx();
      const std::string str = util::GetString(*value_pool_, idx);

      const android::ResStringPool_span* spans = value_pool_->styleAt(idx);
      if (spans && spans->name.index != android::ResStringPool_span::END) {
        StyleString style_str = {str};
        while (spans->name.index != android::ResStringPool_span::END) {
          style_str.spans.push_back(
              Span{util::GetString(*value_pool_, spans->name.index),
                   spans->firstChar, spans->lastChar});
          spans++;
        }
        return util::make_unique<StyledString>(pool->MakeRef(
            style_str, StringPool::Context(StringPool::Context::kNormalPriority, config)));
      }
      return util::make_unique<String>(
          pool->MakeRef(str, StringPool::Context(config)));
          pool->MakeRef(pb_item.str().value(), StringPool::Context(config)));

    } else if (pb_item.has_raw_str()) {
      const uint32_t idx = pb_item.raw_str().idx();
      const std::string str = util::GetString(*value_pool_, idx);
      return util::make_unique<RawString>(
          pool->MakeRef(str, StringPool::Context(config)));
          pool->MakeRef(pb_item.raw_str().value(), StringPool::Context(config)));

    } else if (pb_item.has_styled_str()) {
      const pb::StyledString& pb_str = pb_item.styled_str();
      StyleString style_str{pb_str.value()};
      for (const pb::StyledString::Span& pb_span : pb_str.span()) {
        style_str.spans.push_back(Span{pb_span.tag(), pb_span.first_char(), pb_span.last_char()});
      }
      return util::make_unique<StyledString>(pool->MakeRef(
          style_str, StringPool::Context(StringPool::Context::kNormalPriority, config)));

    } else if (pb_item.has_file()) {
      const uint32_t idx = pb_item.file().path_idx();
      const std::string str = util::GetString(*value_pool_, idx);
      return util::make_unique<FileReference>(pool->MakeRef(
          str,
          StringPool::Context(StringPool::Context::kHighPriority, config)));
          pb_item.file().path(), StringPool::Context(StringPool::Context::kHighPriority, config)));

    } else {
      diag_->Error(DiagMessage(source_) << "unknown item");
@@ -255,15 +235,13 @@ class PackagePbDeserializer {
        std::unique_ptr<Style> style = util::make_unique<Style>();
        if (pb_style.has_parent()) {
          style->parent = Reference();
          if (!DeserializeReferenceFromPb(pb_style.parent(),
                                          &style->parent.value())) {
          if (!DeserializeReferenceFromPb(pb_style.parent(), &style->parent.value())) {
            return {};
          }

          if (pb_style.has_parent_source()) {
            Source parent_source;
            DeserializeSourceFromPb(pb_style.parent_source(), *source_pool_,
                                    &parent_source);
            DeserializeSourceFromPb(pb_style.parent_source(), *source_pool_, &parent_source);
            style->parent.value().SetSource(std::move(parent_source));
          }
        }
@@ -300,8 +278,7 @@ class PackagePbDeserializer {
        const pb::Array& pb_array = pb_compound_value.array();
        std::unique_ptr<Array> array = util::make_unique<Array>();
        for (const pb::Array_Entry& pb_entry : pb_array.entries()) {
          std::unique_ptr<Item> item =
              DeserializeItemFromPb(pb_entry.item(), config, pool);
          std::unique_ptr<Item> item = DeserializeItemFromPb(pb_entry.item(), config, pool);
          if (!item) {
            return {};
          }
@@ -316,8 +293,7 @@ class PackagePbDeserializer {
        std::unique_ptr<Plural> plural = util::make_unique<Plural>();
        for (const pb::Plural_Entry& pb_entry : pb_plural.entries()) {
          size_t pluralIdx = DeserializePluralEnumFromPb(pb_entry.arity());
          plural->values[pluralIdx] =
              DeserializeItemFromPb(pb_entry.item(), config, pool);
          plural->values[pluralIdx] = DeserializeItemFromPb(pb_entry.item(), config, pool);
          if (!plural->values[pluralIdx]) {
            return {};
          }
@@ -350,11 +326,10 @@ class PackagePbDeserializer {
      out_ref->id = ResourceId(pb_ref.id());
    }

    if (pb_ref.has_symbol_idx()) {
      const std::string str_symbol = util::GetString(*symbol_pool_, pb_ref.symbol_idx());
    if (pb_ref.has_name()) {
      ResourceNameRef name_ref;
      if (!ResourceUtils::ParseResourceName(str_symbol, &name_ref, nullptr)) {
        diag_->Error(DiagMessage(source_) << "invalid reference name '" << str_symbol << "'");
      if (!ResourceUtils::ParseResourceName(pb_ref.name(), &name_ref, nullptr)) {
        diag_->Error(DiagMessage(source_) << "invalid reference name '" << pb_ref.name() << "'");
        return false;
      }

@@ -377,41 +352,24 @@ class PackagePbDeserializer {
  }

 private:
  const android::ResStringPool* value_pool_;
  const android::ResStringPool* source_pool_;
  const android::ResStringPool* symbol_pool_;
  const Source source_;
  IDiagnostics* diag_;
};

}  // namespace

std::unique_ptr<ResourceTable> DeserializeTableFromPb(
    const pb::ResourceTable& pb_table, const Source& source,
    IDiagnostics* diag) {
  // We import the android namespace because on Windows NO_ERROR is a macro, not
  // an enum, which
std::unique_ptr<ResourceTable> DeserializeTableFromPb(const pb::ResourceTable& pb_table,
                                                      const Source& source, IDiagnostics* diag) {
  // We import the android namespace because on Windows NO_ERROR is a macro, not an enum, which
  // causes errors when qualifying it with android::
  using namespace android;

  std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>();

  if (!pb_table.has_string_pool()) {
    diag->Error(DiagMessage(source) << "no string pool found");
    return {};
  }

  ResStringPool value_pool;
  status_t result = value_pool.setTo(pb_table.string_pool().data().data(),
                                     pb_table.string_pool().data().size());
  if (result != NO_ERROR) {
    diag->Error(DiagMessage(source) << "invalid string pool");
    return {};
  }

  ResStringPool source_pool;
  if (pb_table.has_source_pool()) {
    result = source_pool.setTo(pb_table.source_pool().data().data(),
    status_t result = source_pool.setTo(pb_table.source_pool().data().data(),
                                        pb_table.source_pool().data().size());
    if (result != NO_ERROR) {
      diag->Error(DiagMessage(source) << "invalid source pool");
@@ -419,18 +377,7 @@ std::unique_ptr<ResourceTable> DeserializeTableFromPb(
    }
  }

  ResStringPool symbol_pool;
  if (pb_table.has_symbol_pool()) {
    result = symbol_pool.setTo(pb_table.symbol_pool().data().data(),
                               pb_table.symbol_pool().data().size());
    if (result != NO_ERROR) {
      diag->Error(DiagMessage(source) << "invalid symbol pool");
      return {};
    }
  }

  PackagePbDeserializer package_pb_deserializer(&value_pool, &source_pool,
                                                &symbol_pool, source, diag);
  PackagePbDeserializer package_pb_deserializer(&source_pool, source, diag);
  for (const pb::Package& pb_package : pb_table.packages()) {
    if (!package_pb_deserializer.DeserializeFromPb(pb_package, table.get())) {
      return {};
+36 −43
Original line number Diff line number Diff line
@@ -24,9 +24,9 @@

#include "android-base/logging.h"

using google::protobuf::io::CodedOutputStream;
using google::protobuf::io::CodedInputStream;
using google::protobuf::io::ZeroCopyOutputStream;
using ::google::protobuf::io::CodedInputStream;
using ::google::protobuf::io::CodedOutputStream;
using ::google::protobuf::io::ZeroCopyOutputStream;

namespace aapt {

@@ -36,46 +36,46 @@ class PbSerializerVisitor : public RawValueVisitor {
 public:
  using RawValueVisitor::Visit;

  /**
   * Constructor to use when expecting to serialize any value.
   */
  PbSerializerVisitor(StringPool* source_pool, StringPool* symbol_pool,
                      pb::Value* out_pb_value)
      : source_pool_(source_pool),
        symbol_pool_(symbol_pool),
        out_pb_value_(out_pb_value),
        out_pb_item_(nullptr) {}

  /**
   * Constructor to use when expecting to serialize an Item.
   */
  PbSerializerVisitor(StringPool* sourcePool, StringPool* symbolPool,
                      pb::Item* outPbItem)
      : source_pool_(sourcePool),
        symbol_pool_(symbolPool),
        out_pb_value_(nullptr),
        out_pb_item_(outPbItem) {}
  // Constructor to use when expecting to serialize any value.
  PbSerializerVisitor(StringPool* source_pool, pb::Value* out_pb_value)
      : source_pool_(source_pool), out_pb_value_(out_pb_value), out_pb_item_(nullptr) {
  }

  // Constructor to use when expecting to serialize an Item.
  PbSerializerVisitor(StringPool* sourcePool, pb::Item* outPbItem)
      : source_pool_(sourcePool), out_pb_value_(nullptr), out_pb_item_(outPbItem) {
  }

  void Visit(Reference* ref) override {
    SerializeReferenceToPb(*ref, pb_item()->mutable_ref());
  }

  void Visit(String* str) override {
    pb_item()->mutable_str()->set_idx(str->value.index());
    pb_item()->mutable_str()->set_value(*str->value);
  }

  void Visit(RawString* str) override {
    pb_item()->mutable_raw_str()->set_value(*str->value);
  }

  void Visit(StyledString* str) override {
    pb_item()->mutable_str()->set_idx(str->value.index());
    pb::StyledString* pb_str = pb_item()->mutable_styled_str();
    pb_str->set_value(str->value->value);

    for (const StringPool::Span& span : str->value->spans) {
      pb::StyledString::Span* pb_span = pb_str->add_span();
      pb_span->set_tag(*span.name);
      pb_span->set_first_char(span.first_char);
      pb_span->set_last_char(span.last_char);
    }
  }

  void Visit(FileReference* file) override {
    pb_item()->mutable_file()->set_path_idx(file->path.index());
    pb_item()->mutable_file()->set_path(*file->path);
  }

  void Visit(Id* id) override { pb_item()->mutable_id(); }

  void Visit(RawString* raw_str) override {
    pb_item()->mutable_raw_str()->set_idx(raw_str->value.index());
  void Visit(Id* /*id*/) override {
    pb_item()->mutable_id();
  }

  void Visit(BinaryPrimitive* prim) override {
@@ -119,7 +119,7 @@ class PbSerializerVisitor : public RawValueVisitor {

      pb::Item* pb_item = pb_entry->mutable_item();
      SerializeItemCommonToPb(entry.key, pb_entry);
      PbSerializerVisitor sub_visitor(source_pool_, symbol_pool_, pb_item);
      PbSerializerVisitor sub_visitor(source_pool_, pb_item);
      entry.value->Accept(&sub_visitor);
    }
  }
@@ -138,8 +138,7 @@ class PbSerializerVisitor : public RawValueVisitor {
    for (auto& value : array->items) {
      pb::Array_Entry* pb_entry = pb_array->add_entries();
      SerializeItemCommonToPb(*value, pb_entry);
      PbSerializerVisitor sub_visitor(source_pool_, symbol_pool_,
                                      pb_entry->mutable_item());
      PbSerializerVisitor sub_visitor(source_pool_, pb_entry->mutable_item());
      value->Accept(&sub_visitor);
    }
  }
@@ -157,7 +156,7 @@ class PbSerializerVisitor : public RawValueVisitor {
      pb_entry->set_arity(SerializePluralEnumToPb(i));
      pb::Item* pb_element = pb_entry->mutable_item();
      SerializeItemCommonToPb(*plural->values[i], pb_entry);
      PbSerializerVisitor sub_visitor(source_pool_, symbol_pool_, pb_element);
      PbSerializerVisitor sub_visitor(source_pool_, pb_element);
      plural->values[i]->Accept(&sub_visitor);
    }
  }
@@ -179,8 +178,7 @@ class PbSerializerVisitor : public RawValueVisitor {

  template <typename T>
  void SerializeItemCommonToPb(const Item& item, T* pb_item) {
    SerializeSourceToPb(item.GetSource(), source_pool_,
                        pb_item->mutable_source());
    SerializeSourceToPb(item.GetSource(), source_pool_, pb_item->mutable_source());
    if (!item.GetComment().empty()) {
      pb_item->set_comment(item.GetComment());
    }
@@ -192,8 +190,7 @@ class PbSerializerVisitor : public RawValueVisitor {
    }

    if (ref.name) {
      StringPool::Ref symbol_ref = symbol_pool_->MakeRef(ref.name.value().ToString());
      pb_ref->set_symbol_idx(static_cast<uint32_t>(symbol_ref.index()));
      pb_ref->set_name(ref.name.value().ToString());
    }

    pb_ref->set_private_(ref.private_reference);
@@ -201,7 +198,6 @@ class PbSerializerVisitor : public RawValueVisitor {
  }

  StringPool* source_pool_;
  StringPool* symbol_pool_;
  pb::Value* out_pb_value_;
  pb::Item* out_pb_item_;
};
@@ -220,9 +216,7 @@ std::unique_ptr<pb::ResourceTable> SerializeTableToPb(ResourceTable* table) {
  });

  auto pb_table = util::make_unique<pb::ResourceTable>();
  SerializeStringPoolToPb(table->string_pool, pb_table->mutable_string_pool());

  StringPool source_pool, symbol_pool;
  StringPool source_pool;

  for (auto& package : table->packages) {
    pb::Package* pb_package = pb_table->add_packages();
@@ -270,7 +264,7 @@ std::unique_ptr<pb::ResourceTable> SerializeTableToPb(ResourceTable* table) {
            pb_value->set_weak(true);
          }

          PbSerializerVisitor visitor(&source_pool, &symbol_pool, pb_value);
          PbSerializerVisitor visitor(&source_pool, pb_value);
          config_value->value->Accept(&visitor);
        }
      }
@@ -278,7 +272,6 @@ std::unique_ptr<pb::ResourceTable> SerializeTableToPb(ResourceTable* table) {
  }

  SerializeStringPoolToPb(source_pool, pb_table->mutable_source_pool());
  SerializeStringPoolToPb(symbol_pool, pb_table->mutable_symbol_pool());
  return pb_table;
}

+34 −15
Original line number Diff line number Diff line
@@ -20,7 +20,9 @@
#include "test/Test.h"

using ::google::protobuf::io::StringOutputStream;
using ::testing::Eq;
using ::testing::NotNull;
using ::testing::SizeIs;

namespace aapt {

@@ -38,12 +40,12 @@ TEST(TableProtoSerializer, SerializeSinglePackage) {

  Symbol public_symbol;
  public_symbol.state = SymbolState::kPublic;
  ASSERT_TRUE(table->SetSymbolState(
      test::ParseNameOrDie("com.app.a:layout/main"), ResourceId(0x7f020000),
      public_symbol, context->GetDiagnostics()));
  ASSERT_TRUE(table->SetSymbolState(test::ParseNameOrDie("com.app.a:layout/main"),
                                    ResourceId(0x7f020000), public_symbol,
                                    context->GetDiagnostics()));

  Id* id = test::GetValue<Id>(table.get(), "com.app.a:id/foo");
  ASSERT_NE(nullptr, id);
  ASSERT_THAT(id, NotNull());

  // Make a plural.
  std::unique_ptr<Plural> plural = util::make_unique<Plural>();
@@ -52,6 +54,15 @@ TEST(TableProtoSerializer, SerializeSinglePackage) {
                                 ConfigDescription{}, {}, std::move(plural),
                                 context->GetDiagnostics()));

  // Make a styled string.
  StyleString style_string;
  style_string.str = "hello";
  style_string.spans.push_back(Span{"b", 0u, 4u});
  ASSERT_TRUE(
      table->AddResource(test::ParseNameOrDie("com.app.a:string/styled"), ConfigDescription{}, {},
                         util::make_unique<StyledString>(table->string_pool.MakeRef(style_string)),
                         context->GetDiagnostics()));

  // Make a resource with different products.
  ASSERT_TRUE(table->AddResource(
      test::ParseNameOrDie("com.app.a:integer/one"),
@@ -65,9 +76,8 @@ TEST(TableProtoSerializer, SerializeSinglePackage) {
      context->GetDiagnostics()));

  // Make a reference with both resource name and resource ID.
  // The reference should point to a resource outside of this table to test that
  // both
  // name and id get serialized.
  // The reference should point to a resource outside of this table to test that both name and id
  // get serialized.
  Reference expected_ref;
  expected_ref.name = test::ParseNameOrDie("android:layout/main");
  expected_ref.id = ResourceId(0x01020000);
@@ -85,36 +95,45 @@ TEST(TableProtoSerializer, SerializeSinglePackage) {

  Id* new_id = test::GetValue<Id>(new_table.get(), "com.app.a:id/foo");
  ASSERT_THAT(new_id, NotNull());
  EXPECT_EQ(id->IsWeak(), new_id->IsWeak());
  EXPECT_THAT(new_id->IsWeak(), Eq(id->IsWeak()));

  Maybe<ResourceTable::SearchResult> result =
      new_table->FindResource(test::ParseNameOrDie("com.app.a:layout/main"));
  ASSERT_TRUE(result);
  EXPECT_EQ(SymbolState::kPublic, result.value().type->symbol_status.state);
  EXPECT_EQ(SymbolState::kPublic, result.value().entry->symbol_status.state);

  EXPECT_THAT(result.value().type->symbol_status.state, Eq(SymbolState::kPublic));
  EXPECT_THAT(result.value().entry->symbol_status.state, Eq(SymbolState::kPublic));

  result = new_table->FindResource(test::ParseNameOrDie("com.app.a:bool/foo"));
  ASSERT_TRUE(result);
  EXPECT_EQ(SymbolState::kUndefined, result.value().entry->symbol_status.state);
  EXPECT_THAT(result.value().entry->symbol_status.state, Eq(SymbolState::kUndefined));
  EXPECT_TRUE(result.value().entry->symbol_status.allow_new);

  // Find the product-dependent values
  BinaryPrimitive* prim = test::GetValueForConfigAndProduct<BinaryPrimitive>(
      new_table.get(), "com.app.a:integer/one", test::ParseConfigOrDie("land"), "");
  ASSERT_THAT(prim, NotNull());
  EXPECT_EQ(123u, prim->value.data);
  EXPECT_THAT(prim->value.data, Eq(123u));

  prim = test::GetValueForConfigAndProduct<BinaryPrimitive>(
      new_table.get(), "com.app.a:integer/one", test::ParseConfigOrDie("land"), "tablet");
  ASSERT_THAT(prim, NotNull());
  EXPECT_EQ(321u, prim->value.data);
  EXPECT_THAT(prim->value.data, Eq(321u));

  Reference* actual_ref = test::GetValue<Reference>(new_table.get(), "com.app.a:layout/abc");
  ASSERT_THAT(actual_ref, NotNull());
  ASSERT_TRUE(actual_ref->name);
  ASSERT_TRUE(actual_ref->id);
  EXPECT_EQ(expected_ref.name.value(), actual_ref->name.value());
  EXPECT_EQ(expected_ref.id.value(), actual_ref->id.value());
  EXPECT_THAT(*actual_ref, Eq(expected_ref));

  StyledString* actual_styled_str =
      test::GetValue<StyledString>(new_table.get(), "com.app.a:string/styled");
  ASSERT_THAT(actual_styled_str, NotNull());
  EXPECT_THAT(actual_styled_str->value->value, Eq("hello"));
  ASSERT_THAT(actual_styled_str->value->spans, SizeIs(1u));
  EXPECT_THAT(*actual_styled_str->value->spans[0].name, Eq("b"));
  EXPECT_THAT(actual_styled_str->value->spans[0].first_char, Eq(0u));
  EXPECT_THAT(actual_styled_str->value->spans[0].last_char, Eq(4u));
}

TEST(TableProtoSerializer, SerializeFileHeader) {