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

Commit 56a9d29c authored by Ryan Mitchell's avatar Ryan Mitchell Committed by Android (Google) Code Review
Browse files

Merge "Add staging-public-group to aapt2" into sc-dev

parents ddf368aa 2e9bec11
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