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

Commit 2e9bec11 authored by Ryan Mitchell's avatar Ryan Mitchell
Browse files

Add staging-public-group to aapt2

staging-public-group is a tag for putting resources that have been
added during platform development, but have not yet been finalized,
into a separate resource id namespace.

R.java fields of staged resources are non-final, so when the SDK is
finalized, applications using the android R.java will automatically
use the new finalized resource id without having to recompile.

Staged resources can exist either in the same type id as the type's
non-staged counterpart or in a separate type id. Multiple
staging-public-group tags each with a different type id can exist
simultaneously, which allows for multiple versions of the platform
to be developed at once.

Bug: 183411093
Test: aapt2_tests

Change-Id: Ibb6c84c3626751e33c6097f35a03e306bb85616a
parent 1d008d1d
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -1379,6 +1379,11 @@ struct ResTable_typeSpec
    enum : uint32_t {
        // Additional flag indicating an entry is public.
        SPEC_PUBLIC = 0x40000000u,

        // Additional flag indicating the resource id for this resource may change in a future
        // build. If this flag is set, the SPEC_PUBLIC flag is also set since the resource must be
        // public to be exposed as an API to other applications.
        SPEC_STAGED_API = 0x20000000u,
    };
};

+65 −47
Original line number Diff line number Diff line
@@ -42,6 +42,10 @@ using ::android::StringPiece;
using android::idmap2::policy::kPolicyStringToFlag;

namespace aapt {
namespace {
constexpr const char* kPublicGroupTag = "public-group";
constexpr const char* kStagingPublicGroupTag = "staging-public-group";
}  // namespace

constexpr const char* sXliffNamespaceUri = "urn:oasis:names:tc:xliff:document:1.2";

@@ -102,6 +106,7 @@ struct ParsedResource {

  ResourceId id;
  Visibility::Level visibility_level = Visibility::Level::kUndefined;
  bool staged_api = false;
  bool allow_new = false;
  Maybe<OverlayableItem> overlayable_item;

@@ -122,6 +127,7 @@ static bool AddResourcesToTable(ResourceTable* table, IDiagnostics* diag, Parsed
  if (res->visibility_level != Visibility::Level::kUndefined) {
    Visibility visibility;
    visibility.level = res->visibility_level;
    visibility.staged_api = res->staged_api;
    visibility.source = res->source;
    visibility.comment = res->comment;
    res_builder.SetVisibility(visibility);
@@ -525,6 +531,7 @@ bool ResourceParser::ParseResource(xml::XmlPullParser* parser,
      {"plurals", std::mem_fn(&ResourceParser::ParsePlural)},
      {"public", std::mem_fn(&ResourceParser::ParsePublic)},
      {"public-group", std::mem_fn(&ResourceParser::ParsePublicGroup)},
      {"staging-public-group", std::mem_fn(&ResourceParser::ParseStagingPublicGroup)},
      {"string-array", std::mem_fn(&ResourceParser::ParseStringArray)},
      {"style", std::bind(&ResourceParser::ParseStyle, std::placeholders::_1, ResourceType::kStyle,
                          std::placeholders::_2, std::placeholders::_3)},
@@ -653,7 +660,8 @@ bool ResourceParser::ParseResource(xml::XmlPullParser* parser,
    const auto bag_iter = elToBagMap.find(resource_type);
    if (bag_iter != elToBagMap.end()) {
      // Ensure we have a name (unless this is a <public-group> or <overlayable>).
      if (resource_type != "public-group" && resource_type != "overlayable") {
      if (resource_type != kPublicGroupTag && resource_type != kStagingPublicGroupTag &&
          resource_type != "overlayable") {
        if (!maybe_name) {
          diag_->Error(DiagMessage(out_resource->source)
                       << "<" << parser->element_name() << "> missing 'name' attribute");
@@ -890,54 +898,45 @@ bool ResourceParser::ParsePublic(xml::XmlPullParser* parser, ParsedResource* out
  return true;
}

bool ResourceParser::ParsePublicGroup(xml::XmlPullParser* parser, ParsedResource* out_resource) {
  if (options_.visibility) {
    diag_->Error(DiagMessage(out_resource->source)
                 << "<public-group> tag not allowed with --visibility flag");
    return false;
  }

template <typename Func>
bool static ParseGroupImpl(xml::XmlPullParser* parser, ParsedResource* out_resource,
                           const char* tag_name, IDiagnostics* diag, Func&& func) {
  if (out_resource->config != ConfigDescription::DefaultConfig()) {
    diag_->Warn(DiagMessage(out_resource->source)
                << "ignoring configuration '" << out_resource->config
                << "' for <public-group> tag");
    diag->Warn(DiagMessage(out_resource->source)
               << "ignoring configuration '" << out_resource->config << "' for <" << tag_name
               << "> tag");
  }

  Maybe<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type");
  if (!maybe_type) {
    diag_->Error(DiagMessage(out_resource->source)
                 << "<public-group> must have a 'type' attribute");
    diag->Error(DiagMessage(out_resource->source)
                << "<" << tag_name << "> must have a 'type' attribute");
    return false;
  }

  const ResourceType* parsed_type = ParseResourceType(maybe_type.value());
  if (!parsed_type) {
    diag_->Error(DiagMessage(out_resource->source) << "invalid resource type '"
                                                   << maybe_type.value()
                                                   << "' in <public-group>");
    diag->Error(DiagMessage(out_resource->source)
                << "invalid resource type '" << maybe_type.value() << "' in <" << tag_name << ">");
    return false;
  }

  Maybe<StringPiece> maybe_id_str =
      xml::FindNonEmptyAttribute(parser, "first-id");
  Maybe<StringPiece> maybe_id_str = xml::FindNonEmptyAttribute(parser, "first-id");
  if (!maybe_id_str) {
    diag_->Error(DiagMessage(out_resource->source)
                 << "<public-group> must have a 'first-id' attribute");
    diag->Error(DiagMessage(out_resource->source)
                << "<" << tag_name << "> must have a 'first-id' attribute");
    return false;
  }

  Maybe<ResourceId> maybe_id =
      ResourceUtils::ParseResourceId(maybe_id_str.value());
  Maybe<ResourceId> maybe_id = ResourceUtils::ParseResourceId(maybe_id_str.value());
  if (!maybe_id) {
    diag_->Error(DiagMessage(out_resource->source) << "invalid resource ID '"
                                                   << maybe_id_str.value()
                                                   << "' in <public-group>");
    diag->Error(DiagMessage(out_resource->source)
                << "invalid resource ID '" << maybe_id_str.value() << "' in <" << tag_name << ">");
    return false;
  }

  ResourceId next_id = maybe_id.value();

  std::string comment;
  ResourceId next_id = maybe_id.value();
  bool error = false;
  const size_t depth = parser->depth();
  while (xml::XmlPullParser::NextChildNode(parser, depth)) {
@@ -949,53 +948,72 @@ bool ResourceParser::ParsePublicGroup(xml::XmlPullParser* parser, ParsedResource
      continue;
    }

    const Source item_source = source_.WithLine(parser->line_number());
    const Source item_source = out_resource->source.WithLine(parser->line_number());
    const std::string& element_namespace = parser->element_namespace();
    const std::string& element_name = parser->element_name();
    if (element_namespace.empty() && element_name == "public") {
      Maybe<StringPiece> maybe_name =
          xml::FindNonEmptyAttribute(parser, "name");
      auto maybe_name = xml::FindNonEmptyAttribute(parser, "name");
      if (!maybe_name) {
        diag_->Error(DiagMessage(item_source)
                     << "<public> must have a 'name' attribute");
        diag->Error(DiagMessage(item_source) << "<public> must have a 'name' attribute");
        error = true;
        continue;
      }

      if (xml::FindNonEmptyAttribute(parser, "id")) {
        diag_->Error(DiagMessage(item_source)
                     << "'id' is ignored within <public-group>");
        diag->Error(DiagMessage(item_source) << "'id' is ignored within <" << tag_name << ">");
        error = true;
        continue;
      }

      if (xml::FindNonEmptyAttribute(parser, "type")) {
        diag_->Error(DiagMessage(item_source)
                     << "'type' is ignored within <public-group>");
        diag->Error(DiagMessage(item_source) << "'type' is ignored within <" << tag_name << ">");
        error = true;
        continue;
      }

      ParsedResource child_resource;
      child_resource.name.type = *parsed_type;
      child_resource.name.entry = maybe_name.value().to_string();
      child_resource.id = next_id;
      // NOLINTNEXTLINE(bugprone-use-after-move) move+reset comment
      child_resource.comment = std::move(comment);
      child_resource.source = item_source;
      child_resource.visibility_level = Visibility::Level::kPublic;
      out_resource->child_resources.push_back(std::move(child_resource));
      ParsedResource& entry_res = out_resource->child_resources.emplace_back(ParsedResource{
          .name = ResourceName{{}, *parsed_type, maybe_name.value().to_string()},
          .source = item_source,
          .id = next_id,
          .comment = std::move(comment),
      });

      next_id.id += 1;
      // Execute group specific code.
      func(entry_res, next_id);

      next_id.id++;
    } else if (!ShouldIgnoreElement(element_namespace, element_name)) {
      diag_->Error(DiagMessage(item_source) << ":" << element_name << ">");
      diag->Error(DiagMessage(item_source) << ":" << element_name << ">");
      error = true;
    }
  }
  return !error;
}

bool ResourceParser::ParseStagingPublicGroup(xml::XmlPullParser* parser,
                                             ParsedResource* out_resource) {
  return ParseGroupImpl(parser, out_resource, kStagingPublicGroupTag, diag_,
                        [](ParsedResource& parsed_entry, ResourceId id) {
                          parsed_entry.id = id;
                          parsed_entry.staged_api = true;
                          parsed_entry.visibility_level = Visibility::Level::kPublic;
                        });
}

bool ResourceParser::ParsePublicGroup(xml::XmlPullParser* parser, ParsedResource* out_resource) {
  if (options_.visibility) {
    diag_->Error(DiagMessage(out_resource->source)
                 << "<" << kPublicGroupTag << "> tag not allowed with --visibility flag");
    return false;
  }

  return ParseGroupImpl(parser, out_resource, kPublicGroupTag, diag_,
                        [](ParsedResource& parsed_entry, ResourceId id) {
                          parsed_entry.id = id;
                          parsed_entry.visibility_level = Visibility::Level::kPublic;
                        });
}

bool ResourceParser::ParseSymbolImpl(xml::XmlPullParser* parser,
                                     ParsedResource* out_resource) {
  Maybe<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type");
+1 −0
Original line number Diff line number Diff line
@@ -99,6 +99,7 @@ class ResourceParser {

  bool ParsePublic(xml::XmlPullParser* parser, ParsedResource* out_resource);
  bool ParsePublicGroup(xml::XmlPullParser* parser, ParsedResource* out_resource);
  bool ParseStagingPublicGroup(xml::XmlPullParser* parser, ParsedResource* out_resource);
  bool ParseSymbolImpl(xml::XmlPullParser* parser, ParsedResource* out_resource);
  bool ParseSymbol(xml::XmlPullParser* parser, ParsedResource* out_resource);
  bool ParseOverlayable(xml::XmlPullParser* parser, ParsedResource* out_resource);
+25 −0
Original line number Diff line number Diff line
@@ -840,6 +840,31 @@ TEST_F(ResourceParserTest, AutoIncrementIdsInPublicGroup) {
  EXPECT_THAT(result.value().entry->id.value(), Eq(ResourceId(0x01010041)));
}

TEST_F(ResourceParserTest, StagingPublicGroup) {
  std::string input = R"(
      <staging-public-group type="attr" first-id="0x01ff0049">
        <public name="foo" />
        <public name="bar" />
      </staging-public-group>)";
  ASSERT_TRUE(TestParse(input));

  Maybe<ResourceTable::SearchResult> result = table_.FindResource(test::ParseNameOrDie("attr/foo"));
  ASSERT_TRUE(result);

  ASSERT_TRUE(result.value().entry->id);
  EXPECT_THAT(result.value().entry->id.value(), Eq(ResourceId(0x01ff0049)));
  EXPECT_THAT(result.value().entry->visibility.level, Eq(Visibility::Level::kPublic));
  EXPECT_TRUE(result.value().entry->visibility.staged_api);

  result = table_.FindResource(test::ParseNameOrDie("attr/bar"));
  ASSERT_TRUE(result);

  ASSERT_TRUE(result.value().entry->id);
  EXPECT_THAT(result.value().entry->id.value(), Eq(ResourceId(0x01ff004a)));
  EXPECT_THAT(result.value().entry->visibility.level, Eq(Visibility::Level::kPublic));
  EXPECT_TRUE(result.value().entry->visibility.staged_api);
}

TEST_F(ResourceParserTest, StrongestSymbolVisibilityWins) {
  std::string input = R"(
      <!-- private -->
+98 −41
Original line number Diff line number Diff line
@@ -46,11 +46,6 @@ bool less_than_type(const std::unique_ptr<ResourceTableType>& lhs, ResourceType
  return lhs->type < rhs;
}

template <typename T>
bool less_than_type_and_id(const T& lhs, const std::pair<ResourceType, Maybe<uint8_t>>& rhs) {
  return lhs.id != rhs.second ? lhs.id < rhs.second : lhs.type < rhs.first;
}

template <typename T>
bool less_than_struct_with_name(const std::unique_ptr<T>& lhs, const StringPiece& rhs) {
  return lhs->name.compare(0, lhs->name.size(), rhs.data(), rhs.size()) < 0;
@@ -80,12 +75,6 @@ bool less_than_struct_with_name_and_id(const T& lhs,
  return lhs.name.compare(0, lhs.name.size(), rhs.first.data(), rhs.first.size()) < 0;
}

template <typename T, typename U>
bool less_than_struct_with_name_and_id_pointer(const T* lhs,
                                               const std::pair<std::string_view, Maybe<U>>& rhs) {
  return less_than_struct_with_name_and_id(*lhs, rhs);
}

template <typename T, typename Func, typename Elements>
T* FindElementsRunAction(const android::StringPiece& name, Elements& entries, Func action) {
  const auto iter =
@@ -307,49 +296,113 @@ ResourceTable::CollisionResult ResourceTable::ResolveValueCollision(Value* exist
  return CollisionResult::kConflict;
}

template <typename T, typename Comparer>
struct SortedVectorInserter : public Comparer {
  std::pair<bool, typename std::vector<T>::iterator> LowerBound(std::vector<T>& el,
                                                                const T& value) {
    auto it = std::lower_bound(el.begin(), el.end(), value, [&](auto& lhs, auto& rhs) {
      return Comparer::operator()(lhs, rhs);
    });
    bool found =
        it != el.end() && !Comparer::operator()(*it, value) && !Comparer::operator()(value, *it);
    return std::make_pair(found, it);
  }

  T* Insert(std::vector<T>& el, T&& value) {
    auto [found, it] = LowerBound(el, value);
    if (found) {
      return &*it;
    }
    return &*el.insert(it, std::move(value));
  }
};

struct PackageViewComparer {
  bool operator()(const ResourceTablePackageView& lhs, const ResourceTablePackageView& rhs) {
    return less_than_struct_with_name_and_id<ResourceTablePackageView, uint8_t>(
        lhs, std::make_pair(rhs.name, rhs.id));
  }
};

struct TypeViewComparer {
  bool operator()(const ResourceTableTypeView& lhs, const ResourceTableTypeView& rhs) {
    return lhs.id != rhs.id ? lhs.id < rhs.id : lhs.type < rhs.type;
  }
};

struct EntryViewComparer {
  bool operator()(const ResourceEntry* lhs, const ResourceEntry* rhs) {
    return less_than_struct_with_name_and_id<ResourceEntry, ResourceId>(
        *lhs, std::make_pair(rhs->name, rhs->id));
  }
};

ResourceTableView ResourceTable::GetPartitionedView() const {
  ResourceTableView view;
  SortedVectorInserter<ResourceTablePackageView, PackageViewComparer> package_inserter;
  SortedVectorInserter<ResourceTableTypeView, TypeViewComparer> type_inserter;
  SortedVectorInserter<const ResourceEntry*, EntryViewComparer> entry_inserter;

  for (const auto& package : packages) {
    for (const auto& type : package->types) {
      for (const auto& entry : type->entries) {
        std::pair<std::string_view, Maybe<uint8_t>> package_key(package->name, {});
        std::pair<std::string_view, Maybe<ResourceId>> entry_key(entry->name, {});
        std::pair<ResourceType, Maybe<uint8_t>> type_key(type->type, {});
        if (entry->id) {
          // If the entry has a defined id, use the id to determine insertion position.
          package_key.second = entry->id.value().package_id();
          type_key.second = entry->id.value().type_id();
          entry_key.second = entry->id.value();
        ResourceTablePackageView new_package{
            package->name, entry->id ? entry->id.value().package_id() : Maybe<uint8_t>{}};
        auto view_package = package_inserter.Insert(view.packages, std::move(new_package));

        ResourceTableTypeView new_type{type->type,
                                       entry->id ? entry->id.value().type_id() : Maybe<uint8_t>{}};
        auto view_type = type_inserter.Insert(view_package->types, std::move(new_type));

        if (entry->visibility.level == Visibility::Level::kPublic) {
          // Only mark the type visibility level as public, it doesn't care about being private.
          view_type->visibility_level = Visibility::Level::kPublic;
        }

        auto package_it =
            std::lower_bound(view.packages.begin(), view.packages.end(), package_key,
                             less_than_struct_with_name_and_id<ResourceTablePackageView, uint8_t>);
        if (package_it == view.packages.end() || package_it->name != package_key.first ||
            package_it->id != package_key.second) {
          ResourceTablePackageView new_package{std::string(package_key.first), package_key.second};
          package_it = view.packages.insert(package_it, new_package);
        entry_inserter.Insert(view_type->entries, entry.get());
      }
    }
  }

        auto type_it = std::lower_bound(package_it->types.begin(), package_it->types.end(),
                                        type_key, less_than_type_and_id<ResourceTableTypeView>);
        if (type_it == package_it->types.end() || type_key.first != type_it->type ||
            type_it->id != type_key.second) {
          ResourceTableTypeView new_type{type_key.first, type_key.second};
          type_it = package_it->types.insert(type_it, new_type);
  // The android runtime does not support querying resources when the there are multiple type ids
  // for the same resource type within the same package. For this reason, if there are types with
  // multiple type ids, each type needs to exist in its own package in order to be queried by name.
  std::vector<ResourceTablePackageView> new_packages;
  for (auto& package : view.packages) {
    // If a new package was already created for a different type within this package, then
    // we can reuse those packages for other types that need to be extracted from this package.
    // `start_index` is the index of the first newly created package that can be reused.
    const size_t start_index = new_packages.size();
    std::map<ResourceType, size_t> type_new_package_index;
    for (auto type_it = package.types.begin(); type_it != package.types.end();) {
      auto& type = *type_it;
      auto type_index_iter = type_new_package_index.find(type.type);
      if (type_index_iter == type_new_package_index.end()) {
        // First occurrence of the resource type in this package. Keep it in this package.
        type_new_package_index.insert(type_index_iter, std::make_pair(type.type, start_index));
        ++type_it;
        continue;
      }

        if (entry->visibility.level == Visibility::Level::kPublic) {
          // Only mark the type visibility level as public, it doesn't care about being private.
          type_it->visibility_level = Visibility::Level::kPublic;
      // The resource type has already been seen for this package, so this type must be extracted to
      // a new separate package.
      const size_t index = type_index_iter->second;
      if (new_packages.size() == index) {
        new_packages.emplace_back(ResourceTablePackageView{package.name, package.id});
        type_new_package_index[type.type] = index + 1;
      }

        auto entry_it =
            std::lower_bound(type_it->entries.begin(), type_it->entries.end(), entry_key,
                             less_than_struct_with_name_and_id_pointer<ResourceEntry, ResourceId>);
        type_it->entries.insert(entry_it, entry.get());
      // Move the type into a new package
      auto& other_package = new_packages[index];
      type_inserter.Insert(other_package.types, std::move(type));
      type_it = package.types.erase(type_it);
    }
  }

  for (auto& new_package : new_packages) {
    // Insert newly created packages after their original packages
    auto [_, it] = package_inserter.LowerBound(view.packages, new_package);
    view.packages.insert(++it, std::move(new_package));
  }

  return view;
@@ -424,6 +477,10 @@ bool ResourceTable::AddResource(NewResource&& res, IDiagnostics* diag) {
      // This symbol definition takes precedence, replace.
      entry->visibility = res.visibility.value();
    }

    if (res.visibility->staged_api) {
      entry->visibility.staged_api = entry->visibility.staged_api;
    }
  }

  if (res.overlayable.has_value()) {
Loading