diff options
Diffstat (limited to 'tools')
133 files changed, 6678 insertions, 2268 deletions
diff --git a/tools/aapt/Android.bp b/tools/aapt/Android.bp index c75ba71c4432..a19d183d617b 100644 --- a/tools/aapt/Android.bp +++ b/tools/aapt/Android.bp @@ -124,6 +124,9 @@ cc_binary_host { srcs: ["Main.cpp"], use_version_lib: true, static_libs: ["libaapt"], + dist: { + targets: ["aapt2_artifacts"], + }, } // ========================================================== diff --git a/tools/aapt/Command.cpp b/tools/aapt/Command.cpp index 21386b88ce2c..812e2087f36b 100644 --- a/tools/aapt/Command.cpp +++ b/tools/aapt/Command.cpp @@ -969,6 +969,8 @@ int doDump(Bundle* bundle) densities.add(dens); } + std::vector<ResXMLParser::ResXMLPosition> tagsToSkip; + size_t len; ResXMLTree::event_code_t code; int depth = 0; @@ -1091,6 +1093,42 @@ int doDump(Bundle* bundle) Vector<FeatureGroup> featureGroups; KeyedVector<String8, ImpliedFeature> impliedFeatures; + { + int curDepth = 0; + ResXMLParser::ResXMLPosition initialPos; + tree.getPosition(&initialPos); + + // Find all of the "uses-sdk" tags within the "manifest" tag. + std::vector<ResXMLParser::ResXMLPosition> usesSdkTagPositions; + ResXMLParser::ResXMLPosition curPos; + while ((code = tree.next()) != ResXMLTree::END_DOCUMENT && + code != ResXMLTree::BAD_DOCUMENT) { + if (code == ResXMLTree::END_TAG) { + curDepth--; + continue; + } + if (code == ResXMLTree::START_TAG) { + curDepth++; + } + const char16_t* ctag16 = tree.getElementName(&len); + if (ctag16 == NULL || String8(ctag16) != "uses-sdk" || curDepth != 2) { + continue; + } + + tree.getPosition(&curPos); + usesSdkTagPositions.emplace_back(curPos); + } + + // Skip all "uses-sdk" tags besides the very last tag. The android runtime only uses + // the attribute values from the last defined tag. + for (size_t i = 1; i < usesSdkTagPositions.size(); i++) { + tagsToSkip.emplace_back(usesSdkTagPositions[i - 1]); + } + + // Reset the position before parsing. + tree.setPosition(initialPos); + } + while ((code=tree.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) { if (code == ResXMLTree::END_TAG) { @@ -1202,8 +1240,25 @@ int doDump(Bundle* bundle) if (code != ResXMLTree::START_TAG) { continue; } + depth++; + // If this tag should be skipped, skip to the end of this tag. + ResXMLParser::ResXMLPosition curPos; + tree.getPosition(&curPos); + if (std::find(tagsToSkip.begin(), tagsToSkip.end(), curPos) != tagsToSkip.end()) { + const int breakDepth = depth - 1; + while ((code = tree.next()) != ResXMLTree::END_DOCUMENT && + code != ResXMLTree::BAD_DOCUMENT) { + if (code == ResXMLTree::END_TAG && --depth == breakDepth) { + break; + } else if (code == ResXMLTree::START_TAG) { + depth++; + } + } + continue; + } + const char16_t* ctag16 = tree.getElementName(&len); if (ctag16 == NULL) { SourcePos(manifestFile, tree.getLineNumber()).error( diff --git a/tools/aapt2/Android.bp b/tools/aapt2/Android.bp index 5c5b3c32876c..12dc156f75be 100644 --- a/tools/aapt2/Android.bp +++ b/tools/aapt2/Android.bp @@ -162,6 +162,7 @@ cc_library_host_static { "Configuration.proto", "Resources.proto", "ResourcesInternal.proto", + "ValueTransformer.cpp", ], proto: { export_proto_headers: true, @@ -196,9 +197,9 @@ cc_test_host { ], defaults: ["aapt2_defaults"], data: [ - "integration-tests/CompileTest/**/*", - "integration-tests/CommandTests/**/*", - "integration-tests/ConvertTest/**/*" + "integration-tests/CompileTest/**/*", + "integration-tests/CommandTests/**/*", + "integration-tests/ConvertTest/**/*", ], } @@ -211,6 +212,9 @@ cc_binary_host { use_version_lib: true, static_libs: ["libaapt2"], defaults: ["aapt2_defaults"], + dist: { + targets: ["aapt2_artifacts"], + }, } // ========================================================== @@ -229,6 +233,9 @@ genrule { "cp $(in) $(genDir)/protos && " + "$(location :soong_zip) -o $(out) -C $(genDir)/protos -D $(genDir)/protos", dist: { - targets: ["sdk_repo"], + targets: [ + "sdk_repo", + "aapt2_artifacts", + ], }, } diff --git a/tools/aapt2/Debug.cpp b/tools/aapt2/Debug.cpp index 82da24959521..ef3a62f4efcc 100644 --- a/tools/aapt2/Debug.cpp +++ b/tools/aapt2/Debug.cpp @@ -256,57 +256,41 @@ class ValueBodyPrinter : public ConstValueVisitor { void Debug::PrintTable(const ResourceTable& table, const DebugPrintTableOptions& options, Printer* printer) { - for (const auto& package : table.packages) { - ValueHeadlinePrinter headline_printer(package->name, printer); - ValueBodyPrinter body_printer(package->name, printer); + const auto table_view = table.GetPartitionedView(); + for (const auto& package : table_view.packages) { + ValueHeadlinePrinter headline_printer(package.name, printer); + ValueBodyPrinter body_printer(package.name, printer); printer->Print("Package name="); - printer->Print(package->name); - if (package->id) { - printer->Print(StringPrintf(" id=%02x", package->id.value())); + printer->Print(package.name); + if (package.id) { + printer->Print(StringPrintf(" id=%02x", package.id.value())); } printer->Println(); printer->Indent(); - for (const auto& type : package->types) { + for (const auto& type : package.types) { printer->Print("type "); - printer->Print(to_string(type->type)); - if (type->id) { - printer->Print(StringPrintf(" id=%02x", type->id.value())); - } - printer->Println(StringPrintf(" entryCount=%zd", type->entries.size())); - - std::vector<const ResourceEntry*> sorted_entries; - for (const auto& entry : type->entries) { - auto iter = std::lower_bound( - sorted_entries.begin(), sorted_entries.end(), entry.get(), - [](const ResourceEntry* a, const ResourceEntry* b) -> bool { - if (a->id && b->id) { - return a->id.value() < b->id.value(); - } else if (a->id) { - return true; - } else { - return false; - } - }); - sorted_entries.insert(iter, entry.get()); + printer->Print(to_string(type.type)); + if (type.id) { + printer->Print(StringPrintf(" id=%02x", type.id.value())); } + printer->Println(StringPrintf(" entryCount=%zd", type.entries.size())); printer->Indent(); - for (const ResourceEntry* entry : sorted_entries) { - const ResourceId id(package->id.value_or_default(0), type->id.value_or_default(0), - entry->id.value_or_default(0)); - + for (const ResourceTableEntryView& entry : type.entries) { printer->Print("resource "); - printer->Print(id.to_string()); + printer->Print(ResourceId(package.id.value_or_default(0), type.id.value_or_default(0), + entry.id.value_or_default(0)) + .to_string()); printer->Print(" "); // Write the name without the package (this is obvious and too verbose). - printer->Print(to_string(type->type)); + printer->Print(to_string(type.type)); printer->Print("/"); - printer->Print(entry->name); + printer->Print(entry.name); - switch (entry->visibility.level) { + switch (entry.visibility.level) { case Visibility::Level::kPublic: printer->Print(" PUBLIC"); break; @@ -318,15 +302,24 @@ void Debug::PrintTable(const ResourceTable& table, const DebugPrintTableOptions& break; } - if (entry->overlayable_item) { + if (entry.visibility.staged_api) { + printer->Print(" STAGED"); + } + + if (entry.overlayable_item) { printer->Print(" OVERLAYABLE"); } + if (entry.staged_id) { + printer->Print(" STAGED_ID="); + printer->Print(entry.staged_id.value().id.to_string()); + } + printer->Println(); if (options.show_values) { printer->Indent(); - for (const auto& value : entry->values) { + for (const auto& value : entry.values) { printer->Print("("); printer->Print(value->config.to_string()); printer->Print(") "); diff --git a/tools/aapt2/LoadedApk.cpp b/tools/aapt2/LoadedApk.cpp index 45719ef474cd..830bc5fa36aa 100644 --- a/tools/aapt2/LoadedApk.cpp +++ b/tools/aapt2/LoadedApk.cpp @@ -113,7 +113,7 @@ std::unique_ptr<LoadedApk> LoadedApk::LoadProtoApkFromFileCollection( } std::string error; - table = util::make_unique<ResourceTable>(/** validate_resources **/ false); + table = util::make_unique<ResourceTable>(ResourceTable::Validation::kDisabled); if (!DeserializeTableFromPb(pb_table, collection.get(), table.get(), &error)) { diag->Error(DiagMessage(source) << "failed to deserialize " << kProtoResourceTablePath << ": " << error); @@ -157,7 +157,7 @@ std::unique_ptr<LoadedApk> LoadedApk::LoadBinaryApkFromFileCollection( io::IFile* table_file = collection->FindFile(kApkResourceTablePath); if (table_file != nullptr) { - table = util::make_unique<ResourceTable>(/** validate_resources **/ false); + table = util::make_unique<ResourceTable>(ResourceTable::Validation::kDisabled); std::unique_ptr<io::IData> data = table_file->OpenAsData(); if (data == nullptr) { diag->Error(DiagMessage(source) << "failed to open " << kApkResourceTablePath); @@ -267,8 +267,14 @@ bool LoadedApk::WriteToArchive(IAaptContext* context, ResourceTable* split_table return false; } } else if (format_ == ApkFormat::kProto && path == kProtoResourceTablePath) { + SerializeTableOptions proto_serialize_options; + proto_serialize_options.collapse_key_stringpool = + options.collapse_key_stringpool; + proto_serialize_options.name_collapse_exemptions = + options.name_collapse_exemptions; pb::ResourceTable pb_table; - SerializeTableToPb(*split_table, &pb_table, context->GetDiagnostics()); + SerializeTableToPb(*split_table, &pb_table, context->GetDiagnostics(), + proto_serialize_options); if (!io::CopyProtoToArchive(context, &pb_table, path, diff --git a/tools/aapt2/Resource.cpp b/tools/aapt2/Resource.cpp index b78f48ce7f17..6364ccdd09e5 100644 --- a/tools/aapt2/Resource.cpp +++ b/tools/aapt2/Resource.cpp @@ -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}, diff --git a/tools/aapt2/Resource.h b/tools/aapt2/Resource.h index 4e051a37f3ed..307c21d9dc96 100644 --- a/tools/aapt2/Resource.h +++ b/tools/aapt2/Resource.h @@ -57,6 +57,7 @@ enum class ResourceType { kInteger, kInterpolator, kLayout, + kMacro, kMenu, kMipmap, kNavigation, @@ -138,7 +139,7 @@ struct ResourceId { uint32_t id; ResourceId(); - ResourceId(const ResourceId& rhs); + ResourceId(const ResourceId& rhs) = default; ResourceId(uint32_t res_id); // NOLINT(google-explicit-constructor) ResourceId(uint8_t p, uint8_t t, uint16_t e); @@ -222,8 +223,6 @@ bool operator<(const ResourceKeyRef& a, const ResourceKeyRef& b); inline ResourceId::ResourceId() : id(0) {} -inline ResourceId::ResourceId(const ResourceId& rhs) : id(rhs.id) {} - inline ResourceId::ResourceId(uint32_t res_id) : id(res_id) {} inline ResourceId::ResourceId(uint8_t p, uint8_t t, uint16_t e) @@ -274,6 +273,19 @@ inline std::string to_string(const ResourceId& id) { return id.to_string(); } +// Helper to compare resource IDs, moving dynamic IDs after framework IDs. +inline bool cmp_ids_dynamic_after_framework(const ResourceId& a, const ResourceId& b) { + // If one of a and b is from the framework package (package ID 0x01), and the + // other is a dynamic ID (package ID 0x00), then put the dynamic ID after the + // framework ID. This ensures that when AssetManager resolves the dynamic IDs, + // they will be in sorted order as expected by AssetManager. + if ((a.package_id() == kFrameworkPackageId && b.package_id() == 0x00) || + (a.package_id() == 0x00 && b.package_id() == kFrameworkPackageId)) { + return b < a; + } + return a < b; +} + // // ResourceType implementation. // diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp index 931a14b1f650..f1e2da9f41e2 100644 --- a/tools/aapt2/ResourceParser.cpp +++ b/tools/aapt2/ResourceParser.cpp @@ -42,6 +42,11 @@ using ::android::StringPiece; using android::idmap2::policy::kPolicyStringToFlag; namespace aapt { +namespace { +constexpr const char* kPublicGroupTag = "public-group"; +constexpr const char* kStagingPublicGroupTag = "staging-public-group"; +constexpr const char* kStagingPublicGroupFinalTag = "staging-public-group-final"; +} // namespace constexpr const char* sXliffNamespaceUri = "urn:oasis:names:tc:xliff:document:1.2"; @@ -102,8 +107,10 @@ struct ParsedResource { ResourceId id; Visibility::Level visibility_level = Visibility::Level::kUndefined; + bool staged_api = false; bool allow_new = false; Maybe<OverlayableItem> overlayable_item; + Maybe<StagedId> staged_alias; std::string comment; std::unique_ptr<Value> value; @@ -118,43 +125,48 @@ static bool AddResourcesToTable(ResourceTable* table, IDiagnostics* diag, Parsed res->comment = trimmed_comment.to_string(); } + NewResourceBuilder res_builder(res->name); 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; - if (!table->SetVisibilityWithId(res->name, visibility, res->id, diag)) { - return false; - } + res_builder.SetVisibility(visibility); + } + + if (res->id.is_valid()) { + res_builder.SetId(res->id); } if (res->allow_new) { AllowNew allow_new; allow_new.source = res->source; allow_new.comment = res->comment; - if (!table->SetAllowNew(res->name, allow_new, diag)) { - return false; - } + res_builder.SetAllowNew(allow_new); } if (res->overlayable_item) { - if (!table->SetOverlayable(res->name, res->overlayable_item.value(), diag)) { - return false; - } + res_builder.SetOverlayable(res->overlayable_item.value()); } if (res->value != nullptr) { // Attach the comment, source and config to the value. res->value->SetComment(std::move(res->comment)); res->value->SetSource(std::move(res->source)); + res_builder.SetValue(std::move(res->value), res->config, res->product); + } - if (!table->AddResourceWithId(res->name, res->id, res->config, res->product, - std::move(res->value), diag)) { - return false; - } + if (res->staged_alias) { + res_builder.SetStagedId(res->staged_alias.value()); } bool error = false; + if (!res->name.entry.empty()) { + if (!table->AddResource(res_builder.Build(), diag)) { + return false; + } + } for (ParsedResource& child : res->child_resources) { error |= !AddResourcesToTable(table, diag, &child); } @@ -342,7 +354,7 @@ bool ResourceParser::FlattenXmlSubtree( } } - // Sanity check to make sure we processed all the nodes. + // Validity check to make sure we processed all the nodes. CHECK(node_stack.size() == 1u); CHECK(node_stack.back() == &root); @@ -525,6 +537,8 @@ 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)}, + {"staging-public-group-final", std::mem_fn(&ResourceParser::ParseStagingPublicGroupFinal)}, {"string-array", std::mem_fn(&ResourceParser::ParseStringArray)}, {"style", std::bind(&ResourceParser::ParseStyle, std::placeholders::_1, ResourceType::kStyle, std::placeholders::_2, std::placeholders::_3)}, @@ -620,6 +634,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) { @@ -653,7 +677,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 != kStagingPublicGroupFinalTag && resource_type != "overlayable") { if (!maybe_name) { diag_->Error(DiagMessage(out_resource->source) << "<" << parser->element_name() << "> missing 'name' attribute"); @@ -718,6 +743,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 @@ -725,42 +768,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(name, {}, {}, std::move(id), 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; } @@ -769,17 +816,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(); } @@ -842,6 +888,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) @@ -890,54 +965,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 +1015,79 @@ 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, + .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::ParseStagingPublicGroupFinal(xml::XmlPullParser* parser, + ParsedResource* out_resource) { + return ParseGroupImpl(parser, out_resource, kStagingPublicGroupFinalTag, diag_, + [](ParsedResource& parsed_entry, ResourceId id) { + parsed_entry.staged_alias = StagedId{id, parsed_entry.source}; + }); +} + +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"); diff --git a/tools/aapt2/ResourceParser.h b/tools/aapt2/ResourceParser.h index 9d3ecc866c5d..261499781638 100644 --- a/tools/aapt2/ResourceParser.h +++ b/tools/aapt2/ResourceParser.h @@ -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,9 +111,11 @@ 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); + bool ParseStagingPublicGroupFinal(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); @@ -107,8 +124,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); diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp index 9b70079a98c9..279ebcba2f71 100644 --- a/tools/aapt2/ResourceParser_test.cpp +++ b/tools/aapt2/ResourceParser_test.cpp @@ -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) { @@ -831,25 +915,38 @@ TEST_F(ResourceParserTest, AutoIncrementIdsInPublicGroup) { 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(0x01010040))); - ASSERT_TRUE(result.value().package->id); - ASSERT_TRUE(result.value().type->id); + result = table_.FindResource(test::ParseNameOrDie("attr/bar")); + ASSERT_TRUE(result); ASSERT_TRUE(result.value().entry->id); - ResourceId actual_id(result.value().package->id.value(), - result.value().type->id.value(), - result.value().entry->id.value()); - EXPECT_THAT(actual_id, Eq(ResourceId(0x01010040))); + 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().package->id); - ASSERT_TRUE(result.value().type->id); ASSERT_TRUE(result.value().entry->id); - actual_id = ResourceId(result.value().package->id.value(), - result.value().type->id.value(), - result.value().entry->id.value()); - EXPECT_THAT(actual_id, Eq(ResourceId(0x01010041))); + 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) { diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp index e0a9a31eee8b..8ab1493c6ab3 100644 --- a/tools/aapt2/ResourceTable.cpp +++ b/tools/aapt2/ResourceTable.cpp @@ -18,20 +18,18 @@ #include <algorithm> #include <memory> -#include <string> #include <tuple> #include "android-base/logging.h" -#include "android-base/stringprintf.h" #include "androidfw/ConfigDescription.h" #include "androidfw/ResourceTypes.h" -#include "Debug.h" #include "NameMangler.h" +#include "ResourceUtils.h" #include "ResourceValues.h" #include "ValueVisitor.h" -#include "trace/TraceBuffer.h" #include "text/Unicode.h" +#include "trace/TraceBuffer.h" #include "util/Util.h" using ::aapt::text::IsValidResourceEntryName; @@ -43,154 +41,130 @@ namespace aapt { const char* Overlayable::kActorScheme = "overlay"; -static bool less_than_type_and_id(const std::unique_ptr<ResourceTableType>& lhs, - const std::pair<ResourceType, Maybe<uint8_t>>& rhs) { - return lhs->type < rhs.first || (lhs->type == rhs.first && rhs.second && lhs->id < rhs.second); +namespace { +bool less_than_type(const std::unique_ptr<ResourceTableType>& lhs, ResourceType rhs) { + return lhs->type < rhs; } template <typename T> -static bool less_than_struct_with_name(const std::unique_ptr<T>& lhs, const StringPiece& rhs) { +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; } template <typename T> -static bool less_than_struct_with_name_and_id(const std::unique_ptr<T>& lhs, - const std::pair<StringPiece, Maybe<uint16_t>>& rhs) { - int name_cmp = lhs->name.compare(0, lhs->name.size(), rhs.first.data(), rhs.first.size()); - return name_cmp < 0 || (name_cmp == 0 && rhs.second && lhs->id < rhs.second); +bool greater_than_struct_with_name(const StringPiece& lhs, const std::unique_ptr<T>& rhs) { + return rhs->name.compare(0, rhs->name.size(), lhs.data(), lhs.size()) > 0; } -ResourceTablePackage* ResourceTable::FindPackage(const StringPiece& name) const { - const auto last = packages.end(); - auto iter = std::lower_bound(packages.begin(), last, name, - less_than_struct_with_name<ResourceTablePackage>); - if (iter != last && name == (*iter)->name) { - return iter->get(); +template <typename T> +struct NameEqualRange { + bool operator()(const std::unique_ptr<T>& lhs, const StringPiece& rhs) const { + return less_than_struct_with_name<T>(lhs, rhs); } - return nullptr; -} - -ResourceTablePackage* ResourceTable::FindPackageById(uint8_t id) const { - for (auto& package : packages) { - if (package->id && package->id.value() == id) { - return package.get(); - } + bool operator()(const StringPiece& lhs, const std::unique_ptr<T>& rhs) const { + return greater_than_struct_with_name<T>(lhs, rhs); } - return nullptr; -} +}; -ResourceTablePackage* ResourceTable::CreatePackage(const StringPiece& name, Maybe<uint8_t> id) { - TRACE_CALL(); - ResourceTablePackage* package = FindOrCreatePackage(name); - if (id && !package->id) { - package->id = id; - return package; +template <typename T, typename U> +bool less_than_struct_with_name_and_id(const T& lhs, + const std::pair<std::string_view, Maybe<U>>& rhs) { + if (lhs.id != rhs.second) { + return lhs.id < rhs.second; } + return lhs.name.compare(0, lhs.name.size(), rhs.first.data(), rhs.first.size()) < 0; +} - if (id && package->id && package->id.value() != id.value()) { - return nullptr; - } - return package; +template <typename T, typename Func, typename Elements> +T* FindElementsRunAction(const android::StringPiece& name, Elements& entries, Func action) { + const auto iter = + std::lower_bound(entries.begin(), entries.end(), name, less_than_struct_with_name<T>); + const bool found = iter != entries.end() && name == (*iter)->name; + return action(found, iter); } -ResourceTablePackage* ResourceTable::CreatePackageAllowingDuplicateNames(const StringPiece& name, - const Maybe<uint8_t> id) { - const auto last = packages.end(); - auto iter = std::lower_bound(packages.begin(), last, std::make_pair(name, id), - less_than_struct_with_name_and_id<ResourceTablePackage>); +struct ConfigKey { + const ConfigDescription* config; + const StringPiece& product; +}; - if (iter != last && name == (*iter)->name && id == (*iter)->id) { - return iter->get(); +template <typename T> +bool lt_config_key_ref(const T& lhs, const ConfigKey& rhs) { + int cmp = lhs->config.compare(*rhs.config); + if (cmp == 0) { + cmp = StringPiece(lhs->product).compare(rhs.product); } - - std::unique_ptr<ResourceTablePackage> new_package = util::make_unique<ResourceTablePackage>(); - new_package->name = name.to_string(); - new_package->id = id; - return packages.emplace(iter, std::move(new_package))->get(); + return cmp < 0; } -ResourceTablePackage* ResourceTable::FindOrCreatePackage(const StringPiece& name) { - const auto last = packages.end(); - auto iter = std::lower_bound(packages.begin(), last, name, - less_than_struct_with_name<ResourceTablePackage>); - if (iter != last && name == (*iter)->name) { - return iter->get(); - } +} // namespace - std::unique_ptr<ResourceTablePackage> new_package = util::make_unique<ResourceTablePackage>(); - new_package->name = name.to_string(); - return packages.emplace(iter, std::move(new_package))->get(); +ResourceTable::ResourceTable(ResourceTable::Validation validation) : validation_(validation) { } -ResourceTableType* ResourceTablePackage::FindType(ResourceType type, const Maybe<uint8_t> id) { - const auto last = types.end(); - auto iter = std::lower_bound(types.begin(), last, std::make_pair(type, id), - less_than_type_and_id); - if (iter != last && (*iter)->type == type && (!id || id == (*iter)->id)) { - return iter->get(); - } - return nullptr; +ResourceTablePackage* ResourceTable::FindPackage(const android::StringPiece& name) const { + return FindElementsRunAction<ResourceTablePackage>( + name, packages, [&](bool found, auto& iter) { return found ? iter->get() : nullptr; }); } -ResourceTableType* ResourceTablePackage::FindOrCreateType(ResourceType type, - const Maybe<uint8_t> id) { - const auto last = types.end(); - auto iter = std::lower_bound(types.begin(), last, std::make_pair(type, id), - less_than_type_and_id); - if (iter != last && (*iter)->type == type && (!id || id == (*iter)->id)) { - return iter->get(); - } +ResourceTablePackage* ResourceTable::FindOrCreatePackage(const android::StringPiece& name) { + return FindElementsRunAction<ResourceTablePackage>(name, packages, [&](bool found, auto& iter) { + return found ? iter->get() : packages.emplace(iter, new ResourceTablePackage(name))->get(); + }); +} - auto new_type = new ResourceTableType(type); - new_type->id = id; - return types.emplace(iter, std::move(new_type))->get(); +template <typename Func, typename Elements> +static ResourceTableType* FindTypeRunAction(ResourceType type, Elements& entries, Func action) { + const auto iter = std::lower_bound(entries.begin(), entries.end(), type, less_than_type); + const bool found = iter != entries.end() && type == (*iter)->type; + return action(found, iter); } -ResourceEntry* ResourceTableType::FindEntry(const StringPiece& name, const Maybe<uint16_t> id) { - const auto last = entries.end(); - auto iter = std::lower_bound(entries.begin(), last, std::make_pair(name, id), - less_than_struct_with_name_and_id<ResourceEntry>); - if (iter != last && name == (*iter)->name && (!id || id == (*iter)->id)) { - return iter->get(); - } - return nullptr; +ResourceTableType* ResourceTablePackage::FindType(ResourceType type) const { + return FindTypeRunAction(type, types, + [&](bool found, auto& iter) { return found ? iter->get() : nullptr; }); } -ResourceEntry* ResourceTableType::FindOrCreateEntry(const StringPiece& name, - const Maybe<uint16_t > id) { - auto last = entries.end(); - auto iter = std::lower_bound(entries.begin(), last, std::make_pair(name, id), - less_than_struct_with_name_and_id<ResourceEntry>); - if (iter != last && name == (*iter)->name && (!id || id == (*iter)->id)) { - return iter->get(); - } +ResourceTableType* ResourceTablePackage::FindOrCreateType(ResourceType type) { + return FindTypeRunAction(type, types, [&](bool found, auto& iter) { + return found ? iter->get() : types.emplace(iter, new ResourceTableType(type))->get(); + }); +} - auto new_entry = new ResourceEntry(name); - new_entry->id = id; - return entries.emplace(iter, std::move(new_entry))->get(); +ResourceEntry* ResourceTableType::CreateEntry(const android::StringPiece& name) { + return FindElementsRunAction<ResourceEntry>(name, entries, [&](bool found, auto& iter) { + return entries.emplace(iter, new ResourceEntry(name))->get(); + }); } -ResourceConfigValue* ResourceEntry::FindValue(const ConfigDescription& config) { - return FindValue(config, StringPiece()); +ResourceEntry* ResourceTableType::FindEntry(const android::StringPiece& name) const { + return FindElementsRunAction<ResourceEntry>( + name, entries, [&](bool found, auto& iter) { return found ? iter->get() : nullptr; }); } -struct ConfigKey { - const ConfigDescription* config; - const StringPiece& product; -}; +ResourceEntry* ResourceTableType::FindOrCreateEntry(const android::StringPiece& name) { + return FindElementsRunAction<ResourceEntry>(name, entries, [&](bool found, auto& iter) { + return found ? iter->get() : entries.emplace(iter, new ResourceEntry(name))->get(); + }); +} -bool lt_config_key_ref(const std::unique_ptr<ResourceConfigValue>& lhs, const ConfigKey& rhs) { - int cmp = lhs->config.compare(*rhs.config); - if (cmp == 0) { - cmp = StringPiece(lhs->product).compare(rhs.product); +ResourceConfigValue* ResourceEntry::FindValue(const ConfigDescription& config, + android::StringPiece product) { + auto iter = std::lower_bound(values.begin(), values.end(), ConfigKey{&config, product}, + lt_config_key_ref<std::unique_ptr<ResourceConfigValue>>); + if (iter != values.end()) { + ResourceConfigValue* value = iter->get(); + if (value->config == config && StringPiece(value->product) == product) { + return value; + } } - return cmp < 0; + return nullptr; } -ResourceConfigValue* ResourceEntry::FindValue(const ConfigDescription& config, - const StringPiece& product) { +const ResourceConfigValue* ResourceEntry::FindValue(const android::ConfigDescription& config, + android::StringPiece product) const { auto iter = std::lower_bound(values.begin(), values.end(), ConfigKey{&config, product}, - lt_config_key_ref); + lt_config_key_ref<std::unique_ptr<ResourceConfigValue>>); if (iter != values.end()) { ResourceConfigValue* value = iter->get(); if (value->config == config && StringPiece(value->product) == product) { @@ -203,7 +177,7 @@ ResourceConfigValue* ResourceEntry::FindValue(const ConfigDescription& config, ResourceConfigValue* ResourceEntry::FindOrCreateValue(const ConfigDescription& config, const StringPiece& product) { auto iter = std::lower_bound(values.begin(), values.end(), ConfigKey{&config, product}, - lt_config_key_ref); + lt_config_key_ref<std::unique_ptr<ResourceConfigValue>>); if (iter != values.end()) { ResourceConfigValue* value = iter->get(); if (value->config == config && StringPiece(value->product) == product) { @@ -323,307 +297,335 @@ ResourceTable::CollisionResult ResourceTable::ResolveValueCollision(Value* exist return CollisionResult::kConflict; } -ResourceTable::CollisionResult ResourceTable::IgnoreCollision(Value* /** existing **/, - Value* /** incoming **/) { - return CollisionResult::kKeepBoth; -} - -static StringPiece ResourceNameValidator(const StringPiece& name) { - if (!IsValidResourceEntryName(name)) { - return name; +namespace { +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); } - return {}; -} -static StringPiece SkipNameValidator(const StringPiece& /*name*/) { - return {}; -} - -bool ResourceTable::AddResource(const ResourceNameRef& name, - const ConfigDescription& config, - const StringPiece& product, - std::unique_ptr<Value> value, - IDiagnostics* diag) { - return AddResourceImpl(name, ResourceId{}, config, product, std::move(value), - (validate_resources_ ? ResourceNameValidator : SkipNameValidator), - (validate_resources_ ? ResolveValueCollision : IgnoreCollision), diag); -} - -bool ResourceTable::AddResourceWithId(const ResourceNameRef& name, const ResourceId& res_id, - const ConfigDescription& config, const StringPiece& product, - std::unique_ptr<Value> value, IDiagnostics* diag) { - return AddResourceImpl(name, res_id, config, product, std::move(value), - (validate_resources_ ? ResourceNameValidator : SkipNameValidator), - (validate_resources_ ? ResolveValueCollision : IgnoreCollision), diag); -} - -bool ResourceTable::AddResourceMangled(const ResourceNameRef& name, const ConfigDescription& config, - const StringPiece& product, std::unique_ptr<Value> value, - IDiagnostics* diag) { - return AddResourceImpl(name, ResourceId{}, config, product, std::move(value), SkipNameValidator, - (validate_resources_ ? ResolveValueCollision : IgnoreCollision), diag); -} - -bool ResourceTable::AddResourceWithIdMangled(const ResourceNameRef& name, const ResourceId& id, - const ConfigDescription& config, - const StringPiece& product, - std::unique_ptr<Value> value, IDiagnostics* diag) { - return AddResourceImpl(name, id, config, product, std::move(value), SkipNameValidator, - (validate_resources_ ? ResolveValueCollision : IgnoreCollision), diag); -} - -bool ResourceTable::ValidateName(NameValidator name_validator, const ResourceNameRef& name, - const Source& source, IDiagnostics* diag) { - const StringPiece bad_char = name_validator(name.entry); - if (!bad_char.empty()) { - diag->Error(DiagMessage(source) << "resource '" << name << "' has invalid entry name '" - << name.entry << "'. Invalid character '" << bad_char << "'"); - return false; - } - return true; -} - -bool ResourceTable::AddResourceImpl(const ResourceNameRef& name, const ResourceId& res_id, - const ConfigDescription& config, const StringPiece& product, - std::unique_ptr<Value> value, NameValidator name_validator, - const CollisionResolverFunc& conflict_resolver, - IDiagnostics* diag) { - CHECK(value != nullptr); - CHECK(diag != nullptr); - - const Source& source = value->GetSource(); - if (!ValidateName(name_validator, name, source, diag)) { - return false; + T* Insert(std::vector<T>& el, T&& value) { + auto [found, it] = LowerBound(el, value); + if (found) { + return &*it; + } + return &*el.insert(it, std::forward<T>(value)); } +}; - // Check for package names appearing twice with two different package ids - ResourceTablePackage* package = FindOrCreatePackage(name.package); - if (res_id.is_valid() && package->id && package->id.value() != res_id.package_id()) { - diag->Error(DiagMessage(source) - << "trying to add resource '" << name << "' with ID " << res_id - << " but package '" << package->name << "' already has ID " - << StringPrintf("%02x", package->id.value())); - return false; +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)); } +}; - // Whether or not to error on duplicate resources - bool check_id = validate_resources_ && res_id.is_valid(); - // Whether or not to create a duplicate resource if the id does not match - bool use_id = !validate_resources_ && res_id.is_valid(); - - ResourceTableType* type = package->FindOrCreateType(name.type, use_id ? res_id.type_id() - : Maybe<uint8_t>()); - - // Check for types appearing twice with two different type ids - if (check_id && type->id && type->id.value() != res_id.type_id()) { - diag->Error(DiagMessage(source) - << "trying to add resource '" << name << "' with ID " << res_id - << " but type '" << type->type << "' already has ID " - << StringPrintf("%02x", type->id.value())); - return false; +struct TypeViewComparer { + bool operator()(const ResourceTableTypeView& lhs, const ResourceTableTypeView& rhs) { + return lhs.id != rhs.id ? lhs.id < rhs.id : lhs.type < rhs.type; } +}; - ResourceEntry* entry = type->FindOrCreateEntry(name.entry, use_id ? res_id.entry_id() - : Maybe<uint16_t>()); - - // Check for entries appearing twice with two different entry ids - if (check_id && entry->id && entry->id.value() != res_id.entry_id()) { - diag->Error(DiagMessage(source) - << "trying to add resource '" << name << "' with ID " << res_id - << " but resource already has ID " - << ResourceId(package->id.value(), type->id.value(), entry->id.value())); - return false; +struct EntryViewComparer { + bool operator()(const ResourceTableEntryView& lhs, const ResourceTableEntryView& rhs) { + return less_than_struct_with_name_and_id<ResourceTableEntryView, uint16_t>( + lhs, std::make_pair(rhs.name, rhs.id)); } +}; - ResourceConfigValue* config_value = entry->FindOrCreateValue(config, product); - if (!config_value->value) { - // Resource does not exist, add it now. - config_value->value = std::move(value); - } else { - switch (conflict_resolver(config_value->value.get(), value.get())) { - case CollisionResult::kKeepBoth: - // Insert the value ignoring for duplicate configurations - entry->values.push_back(util::make_unique<ResourceConfigValue>(config, product)); - entry->values.back()->value = std::move(value); - break; +void InsertEntryIntoTableView(ResourceTableView& table, const ResourceTablePackage* package, + const ResourceTableType* type, const std::string& entry_name, + const Maybe<ResourceId>& id, const Visibility& visibility, + const Maybe<AllowNew>& allow_new, + const Maybe<OverlayableItem>& overlayable_item, + const Maybe<StagedId>& staged_id, + const std::vector<std::unique_ptr<ResourceConfigValue>>& values) { + SortedVectorInserter<ResourceTablePackageView, PackageViewComparer> package_inserter; + SortedVectorInserter<ResourceTableTypeView, TypeViewComparer> type_inserter; + SortedVectorInserter<ResourceTableEntryView, EntryViewComparer> entry_inserter; - case CollisionResult::kTakeNew: - // Take the incoming value. - config_value->value = std::move(value); - break; + ResourceTablePackageView new_package{package->name, + id ? id.value().package_id() : Maybe<uint8_t>{}}; + auto view_package = package_inserter.Insert(table.packages, std::move(new_package)); - case CollisionResult::kConflict: - diag->Error(DiagMessage(source) << "duplicate value for resource '" << name << "' " - << "with config '" << config << "'"); - diag->Error(DiagMessage(source) << "resource previously defined here"); - return false; + ResourceTableTypeView new_type{type->type, id ? id.value().type_id() : Maybe<uint8_t>{}}; + auto view_type = type_inserter.Insert(view_package->types, std::move(new_type)); - case CollisionResult::kKeepOriginal: - break; - } + if (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; } - if (res_id.is_valid()) { - package->id = res_id.package_id(); - type->id = res_id.type_id(); - entry->id = res_id.entry_id(); + ResourceTableEntryView new_entry{.name = entry_name, + .id = id ? id.value().entry_id() : Maybe<uint16_t>{}, + .visibility = visibility, + .allow_new = allow_new, + .overlayable_item = overlayable_item, + .staged_id = staged_id}; + for (auto& value : values) { + new_entry.values.emplace_back(value.get()); } - return true; -} - -bool ResourceTable::GetValidateResources() { - return validate_resources_; + entry_inserter.Insert(view_type->entries, std::move(new_entry)); } +} // namespace -bool ResourceTable::SetVisibility(const ResourceNameRef& name, const Visibility& visibility, - IDiagnostics* diag) { - return SetVisibilityImpl(name, visibility, {}, ResourceNameValidator, diag); +const ResourceConfigValue* ResourceTableEntryView::FindValue(const ConfigDescription& config, + android::StringPiece product) const { + auto iter = std::lower_bound(values.begin(), values.end(), ConfigKey{&config, product}, + lt_config_key_ref<const ResourceConfigValue*>); + if (iter != values.end()) { + const ResourceConfigValue* value = *iter; + if (value->config == config && StringPiece(value->product) == product) { + return value; + } + } + return nullptr; } -bool ResourceTable::SetVisibilityWithId(const ResourceNameRef& name, const Visibility& visibility, - const ResourceId& res_id, IDiagnostics* diag) { - return SetVisibilityImpl(name, visibility, res_id, ResourceNameValidator, diag); -} +ResourceTableView ResourceTable::GetPartitionedView(const ResourceTableViewOptions& options) const { + ResourceTableView view; + for (const auto& package : packages) { + for (const auto& type : package->types) { + for (const auto& entry : type->entries) { + InsertEntryIntoTableView(view, package.get(), type.get(), entry->name, entry->id, + entry->visibility, entry->allow_new, entry->overlayable_item, + entry->staged_id, entry->values); + + if (options.create_alias_entries && entry->staged_id) { + auto alias_id = entry->staged_id.value().id; + InsertEntryIntoTableView(view, package.get(), type.get(), entry->name, alias_id, + entry->visibility, entry->allow_new, entry->overlayable_item, {}, + entry->values); + } + } + } + } -bool ResourceTable::SetVisibilityWithIdMangled(const ResourceNameRef& name, - const Visibility& visibility, - const ResourceId& res_id, IDiagnostics* diag) { - return SetVisibilityImpl(name, visibility, res_id, SkipNameValidator, diag); -} + // 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; + SortedVectorInserter<ResourceTablePackageView, PackageViewComparer> package_inserter; + SortedVectorInserter<ResourceTableTypeView, TypeViewComparer> type_inserter; + 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; + } -bool ResourceTable::SetVisibilityImpl(const ResourceNameRef& name, const Visibility& visibility, - const ResourceId& res_id, NameValidator name_validator, - IDiagnostics* diag) { - CHECK(diag != nullptr); + // 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; + } - const Source& source = visibility.source; - if (!ValidateName(name_validator, name, source, diag)) { - return false; + // 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); + } } - // Check for package names appearing twice with two different package ids - ResourceTablePackage* package = FindOrCreatePackage(name.package); - if (res_id.is_valid() && package->id && package->id.value() != res_id.package_id()) { - diag->Error(DiagMessage(source) - << "trying to add resource '" << name << "' with ID " << res_id - << " but package '" << package->name << "' already has ID " - << StringPrintf("%02x", package->id.value())); - return false; + 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)); } - // Whether or not to error on duplicate resources - bool check_id = validate_resources_ && res_id.is_valid(); - // Whether or not to create a duplicate resource if the id does not match - bool use_id = !validate_resources_ && res_id.is_valid(); + return view; +} - ResourceTableType* type = package->FindOrCreateType(name.type, use_id ? res_id.type_id() - : Maybe<uint8_t>()); +bool ResourceTable::AddResource(NewResource&& res, IDiagnostics* diag) { + CHECK(diag != nullptr) << "Diagnostic pointer is null"; - // Check for types appearing twice with two different type ids - if (check_id && type->id && type->id.value() != res_id.type_id()) { + const bool validate = validation_ == Validation::kEnabled; + const Source source = res.value ? res.value->GetSource() : Source{}; + if (validate && !res.allow_mangled && !IsValidResourceEntryName(res.name.entry)) { diag->Error(DiagMessage(source) - << "trying to add resource '" << name << "' with ID " << res_id - << " but type '" << type->type << "' already has ID " - << StringPrintf("%02x", type->id.value())); + << "resource '" << res.name << "' has invalid entry name '" << res.name.entry); return false; } - ResourceEntry* entry = type->FindOrCreateEntry(name.entry, use_id ? res_id.entry_id() - : Maybe<uint16_t>()); - - // Check for entries appearing twice with two different entry ids - if (check_id && entry->id && entry->id.value() != res_id.entry_id()) { - diag->Error(DiagMessage(source) - << "trying to add resource '" << name << "' with ID " << res_id - << " but resource already has ID " - << ResourceId(package->id.value(), type->id.value(), entry->id.value())); + if (res.id.has_value() && !res.id->first.is_valid()) { + diag->Error(DiagMessage(source) << "trying to add resource '" << res.name << "' with ID " + << res.id->first << " but that ID is invalid"); return false; } - if (res_id.is_valid()) { - package->id = res_id.package_id(); - type->id = res_id.type_id(); - entry->id = res_id.entry_id(); - } - - // Only mark the type visibility level as public, it doesn't care about being private. - if (visibility.level == Visibility::Level::kPublic) { - type->visibility_level = Visibility::Level::kPublic; - } + auto package = FindOrCreatePackage(res.name.package); + auto type = package->FindOrCreateType(res.name.type); + auto entry_it = std::equal_range(type->entries.begin(), type->entries.end(), res.name.entry, + NameEqualRange<ResourceEntry>{}); + const size_t entry_count = std::distance(entry_it.first, entry_it.second); - if (visibility.level == Visibility::Level::kUndefined && - entry->visibility.level != Visibility::Level::kUndefined) { - // We can't undefine a symbol (remove its visibility). Ignore. - return true; + ResourceEntry* entry; + if (entry_count == 0) { + // Adding a new resource + entry = type->CreateEntry(res.name.entry); + } else if (entry_count == 1) { + // Assume that the existing resource is being modified + entry = entry_it.first->get(); + } else { + // Multiple resources with the same name exist in the resource table. The only way to + // distinguish between them is using resource id since each resource should have a unique id. + CHECK(res.id.has_value()) << "ambiguous modification of resource entry '" << res.name + << "' without specifying a resource id."; + entry = entry_it.first->get(); + for (auto it = entry_it.first; it != entry_it.second; ++it) { + CHECK((bool)(*it)->id) << "ambiguous modification of resource entry '" << res.name + << "' with multiple entries without resource ids"; + if ((*it)->id == res.id->first) { + entry = it->get(); + break; + } + } } - if (visibility.level < entry->visibility.level) { - // We can't downgrade public to private. Ignore. - return true; + if (res.id.has_value()) { + if (entry->id && entry->id.value() != res.id->first) { + if (res.id->second != OnIdConflict::CREATE_ENTRY) { + diag->Error(DiagMessage(source) + << "trying to add resource '" << res.name << "' with ID " << res.id->first + << " but resource already has ID " << entry->id.value()); + return false; + } + entry = type->CreateEntry(res.name.entry); + } + entry->id = res.id->first; } - // This symbol definition takes precedence, replace. - entry->visibility = visibility; - return true; -} - -bool ResourceTable::SetAllowNew(const ResourceNameRef& name, const AllowNew& allow_new, - IDiagnostics* diag) { - return SetAllowNewImpl(name, allow_new, ResourceNameValidator, diag); -} + if (res.visibility.has_value()) { + // Only mark the type visibility level as public, it doesn't care about being private. + if (res.visibility->level == Visibility::Level::kPublic) { + type->visibility_level = Visibility::Level::kPublic; + } -bool ResourceTable::SetAllowNewMangled(const ResourceNameRef& name, const AllowNew& allow_new, - IDiagnostics* diag) { - return SetAllowNewImpl(name, allow_new, SkipNameValidator, diag); -} + if (res.visibility->level > entry->visibility.level) { + // This symbol definition takes precedence, replace. + entry->visibility = res.visibility.value(); + } -bool ResourceTable::SetAllowNewImpl(const ResourceNameRef& name, const AllowNew& allow_new, - NameValidator name_validator, IDiagnostics* diag) { - CHECK(diag != nullptr); + if (res.visibility->staged_api) { + entry->visibility.staged_api = entry->visibility.staged_api; + } + } - if (!ValidateName(name_validator, name, allow_new.source, diag)) { - return false; + if (res.overlayable.has_value()) { + if (entry->overlayable_item) { + diag->Error(DiagMessage(res.overlayable->source) + << "duplicate overlayable declaration for resource '" << res.name << "'"); + diag->Error(DiagMessage(entry->overlayable_item.value().source) + << "previous declaration here"); + return false; + } + entry->overlayable_item = res.overlayable.value(); + } + + if (res.allow_new.has_value()) { + entry->allow_new = res.allow_new.value(); + } + + if (res.staged_id.has_value()) { + entry->staged_id = res.staged_id.value(); + } + + if (res.value != nullptr) { + auto config_value = entry->FindOrCreateValue(res.config, res.product); + if (!config_value->value) { + // Resource does not exist, add it now. + config_value->value = std::move(res.value); + } else { + // When validation is enabled, ensure that a resource cannot have multiple values defined for + // the same configuration. + auto result = validate ? ResolveValueCollision(config_value->value.get(), res.value.get()) + : CollisionResult::kKeepBoth; + switch (result) { + case CollisionResult::kKeepBoth: + // Insert the value ignoring for duplicate configurations + entry->values.push_back(util::make_unique<ResourceConfigValue>(res.config, res.product)); + entry->values.back()->value = std::move(res.value); + break; + + case CollisionResult::kTakeNew: + // Take the incoming value. + config_value->value = std::move(res.value); + break; + + case CollisionResult::kConflict: + diag->Error(DiagMessage(source) << "duplicate value for resource '" << res.name << "' " + << "with config '" << res.config << "'"); + diag->Error(DiagMessage(source) << "resource previously defined here"); + return false; + + case CollisionResult::kKeepOriginal: + break; + } + } } - ResourceTablePackage* package = FindOrCreatePackage(name.package); - ResourceTableType* type = package->FindOrCreateType(name.type); - ResourceEntry* entry = type->FindOrCreateEntry(name.entry); - entry->allow_new = allow_new; return true; } -bool ResourceTable::SetOverlayable(const ResourceNameRef& name, const OverlayableItem& overlayable, - IDiagnostics* diag) { - return SetOverlayableImpl(name, overlayable, ResourceNameValidator, diag); -} +Maybe<ResourceTable::SearchResult> ResourceTable::FindResource(const ResourceNameRef& name) const { + ResourceTablePackage* package = FindPackage(name.package); + if (package == nullptr) { + return {}; + } -bool ResourceTable::SetOverlayableImpl(const ResourceNameRef& name, - const OverlayableItem& overlayable, - NameValidator name_validator, IDiagnostics *diag) { - CHECK(diag != nullptr); + ResourceTableType* type = package->FindType(name.type); + if (type == nullptr) { + return {}; + } - if (!ValidateName(name_validator, name, overlayable.source, diag)) { - return false; + ResourceEntry* entry = type->FindEntry(name.entry); + if (entry == nullptr) { + return {}; } + return SearchResult{package, type, entry}; +} - ResourceTablePackage* package = FindOrCreatePackage(name.package); - ResourceTableType* type = package->FindOrCreateType(name.type); - ResourceEntry* entry = type->FindOrCreateEntry(name.entry); +Maybe<ResourceTable::SearchResult> ResourceTable::FindResource(const ResourceNameRef& name, + ResourceId id) const { + ResourceTablePackage* package = FindPackage(name.package); + if (package == nullptr) { + return {}; + } - if (entry->overlayable_item) { - diag->Error(DiagMessage(overlayable.source) - << "duplicate overlayable declaration for resource '" << name << "'"); - diag->Error(DiagMessage(entry->overlayable_item.value().source) - << "previous declaration here"); - return false; + ResourceTableType* type = package->FindType(name.type); + if (type == nullptr) { + return {}; } - entry->overlayable_item = overlayable; - return true; + auto entry_it = std::equal_range(type->entries.begin(), type->entries.end(), name.entry, + NameEqualRange<ResourceEntry>{}); + for (auto it = entry_it.first; it != entry_it.second; ++it) { + if ((*it)->id == id) { + return SearchResult{package, type, it->get()}; + } + } + return {}; } -Maybe<ResourceTable::SearchResult> ResourceTable::FindResource(const ResourceNameRef& name) const { +bool ResourceTable::RemoveResource(const ResourceNameRef& name, ResourceId id) const { ResourceTablePackage* package = FindPackage(name.package); if (package == nullptr) { return {}; @@ -634,24 +636,28 @@ Maybe<ResourceTable::SearchResult> ResourceTable::FindResource(const ResourceNam return {}; } - ResourceEntry* entry = type->FindEntry(name.entry); - if (entry == nullptr) { - return {}; + auto entry_it = std::equal_range(type->entries.begin(), type->entries.end(), name.entry, + NameEqualRange<ResourceEntry>{}); + for (auto it = entry_it.first; it != entry_it.second; ++it) { + if ((*it)->id == id) { + type->entries.erase(it); + return true; + } } - return SearchResult{package, type, entry}; + return false; } std::unique_ptr<ResourceTable> ResourceTable::Clone() const { std::unique_ptr<ResourceTable> new_table = util::make_unique<ResourceTable>(); + CloningValueTransformer cloner(&new_table->string_pool); for (const auto& pkg : packages) { - ResourceTablePackage* new_pkg = new_table->CreatePackage(pkg->name, pkg->id); + ResourceTablePackage* new_pkg = new_table->FindOrCreatePackage(pkg->name); for (const auto& type : pkg->types) { ResourceTableType* new_type = new_pkg->FindOrCreateType(type->type); - new_type->id = type->id; new_type->visibility_level = type->visibility_level; for (const auto& entry : type->entries) { - ResourceEntry* new_entry = new_type->FindOrCreateEntry(entry->name); + ResourceEntry* new_entry = new_type->CreateEntry(entry->name); new_entry->id = entry->id; new_entry->visibility = entry->visibility; new_entry->allow_new = entry->allow_new; @@ -660,7 +666,7 @@ std::unique_ptr<ResourceTable> ResourceTable::Clone() const { for (const auto& config_value : entry->values) { ResourceConfigValue* new_value = new_entry->FindOrCreateValue(config_value->config, config_value->product); - new_value->value.reset(config_value->value->Clone(&new_table->string_pool)); + new_value->value = config_value->value->Transform(cloner); } } } @@ -668,4 +674,56 @@ std::unique_ptr<ResourceTable> ResourceTable::Clone() const { return new_table; } +NewResourceBuilder::NewResourceBuilder(const ResourceNameRef& name) { + res_.name = name.ToResourceName(); +} + +NewResourceBuilder::NewResourceBuilder(const std::string& name) { + ResourceNameRef ref; + CHECK(ResourceUtils::ParseResourceName(name, &ref)) << "invalid resource name: " << name; + res_.name = ref.ToResourceName(); +} + +NewResourceBuilder& NewResourceBuilder::SetValue(std::unique_ptr<Value> value, + android::ConfigDescription config, + std::string product) { + res_.value = std::move(value); + res_.config = std::move(config); + res_.product = std::move(product); + return *this; +} + +NewResourceBuilder& NewResourceBuilder::SetId(ResourceId id, OnIdConflict on_conflict) { + res_.id = std::make_pair(id, on_conflict); + return *this; +} + +NewResourceBuilder& NewResourceBuilder::SetVisibility(Visibility visibility) { + res_.visibility = std::move(visibility); + return *this; +} + +NewResourceBuilder& NewResourceBuilder::SetOverlayable(OverlayableItem overlayable) { + res_.overlayable = std::move(overlayable); + return *this; +} +NewResourceBuilder& NewResourceBuilder::SetAllowNew(AllowNew allow_new) { + res_.allow_new = std::move(allow_new); + return *this; +} + +NewResourceBuilder& NewResourceBuilder::SetStagedId(StagedId staged_alias) { + res_.staged_id = std::move(staged_alias); + return *this; +} + +NewResourceBuilder& NewResourceBuilder::SetAllowMangled(bool allow_mangled) { + res_.allow_mangled = allow_mangled; + return *this; +} + +NewResource NewResourceBuilder::Build() { + return std::move(res_); +} + } // namespace aapt diff --git a/tools/aapt2/ResourceTable.h b/tools/aapt2/ResourceTable.h index 93a7a314d6ba..bae1d827a841 100644 --- a/tools/aapt2/ResourceTable.h +++ b/tools/aapt2/ResourceTable.h @@ -51,6 +51,11 @@ struct Visibility { Level level = Level::kUndefined; Source source; std::string comment; + + // Indicates that the resource id may change across builds and that the public R.java identifier + // for this resource should not be final. This is set to `true` for resources in `staging-group` + // tags. + bool staged_api = false; }; // Represents <add-resource> in an overlay. @@ -59,6 +64,12 @@ struct AllowNew { std::string comment; }; +// Represents the staged resource id of a finalized resource. +struct StagedId { + ResourceId id; + Source source; +}; + struct Overlayable { Overlayable() = default; Overlayable(const android::StringPiece& name, const android::StringPiece& actor) @@ -109,7 +120,7 @@ class ResourceEntry { const std::string name; // The entry ID for this resource (the EEEE in 0xPPTTEEEE). - Maybe<uint16_t> id; + Maybe<ResourceId> id; // Whether this resource is public (and must maintain the same entry ID across builds). Visibility visibility; @@ -119,15 +130,18 @@ class ResourceEntry { // The declarations of this resource as overlayable for RROs Maybe<OverlayableItem> overlayable_item; + // The staged resource id for a finalized resource. + Maybe<StagedId> staged_id; + // The resource's values for each configuration. std::vector<std::unique_ptr<ResourceConfigValue>> values; explicit ResourceEntry(const android::StringPiece& name) : name(name.to_string()) {} - ResourceConfigValue* FindValue(const android::ConfigDescription& config); - ResourceConfigValue* FindValue(const android::ConfigDescription& config, - const android::StringPiece& product); + android::StringPiece product = {}); + const ResourceConfigValue* FindValue(const android::ConfigDescription& config, + android::StringPiece product = {}) const; ResourceConfigValue* FindOrCreateValue(const android::ConfigDescription& config, const android::StringPiece& product); @@ -156,9 +170,6 @@ class ResourceTableType { // The logical type of resource (string, drawable, layout, etc.). const ResourceType type; - // The type ID for this resource (the TT in 0xPPTTEEEE). - Maybe<uint8_t> id; - // Whether this type is public (and must maintain the same type ID across builds). Visibility::Level visibility_level = Visibility::Level::kUndefined; @@ -167,10 +178,9 @@ class ResourceTableType { explicit ResourceTableType(const ResourceType type) : type(type) {} - ResourceEntry* FindEntry(const android::StringPiece& name, - Maybe<uint16_t> id = Maybe<uint16_t>()); - ResourceEntry* FindOrCreateEntry(const android::StringPiece& name, - Maybe<uint16_t> id = Maybe<uint16_t>()); + ResourceEntry* CreateEntry(const android::StringPiece& name); + ResourceEntry* FindEntry(const android::StringPiece& name) const; + ResourceEntry* FindOrCreateEntry(const android::StringPiece& name); private: DISALLOW_COPY_AND_ASSIGN(ResourceTableType); @@ -180,70 +190,118 @@ class ResourceTablePackage { public: std::string name; - // The package ID (the PP in 0xPPTTEEEE). - Maybe<uint8_t> id; - std::vector<std::unique_ptr<ResourceTableType>> types; + explicit ResourceTablePackage(const android::StringPiece& name) : name(name.to_string()) { + } + ResourceTablePackage() = default; - ResourceTableType* FindType(ResourceType type, Maybe<uint8_t> id = Maybe<uint8_t>()); - ResourceTableType* FindOrCreateType(const ResourceType type, - Maybe<uint8_t> id = Maybe<uint8_t>()); + ResourceTableType* FindType(ResourceType type) const; + ResourceTableType* FindOrCreateType(ResourceType type); private: DISALLOW_COPY_AND_ASSIGN(ResourceTablePackage); }; -// The container and index for all resources defined for an app. -class ResourceTable { - public: - ResourceTable() = default; - explicit ResourceTable(bool validate_resources) : validate_resources_(validate_resources) {} +struct ResourceTableEntryView { + std::string name; + Maybe<uint16_t> id; + Visibility visibility; + Maybe<AllowNew> allow_new; + Maybe<OverlayableItem> overlayable_item; + Maybe<StagedId> staged_id; + std::vector<const ResourceConfigValue*> values; - enum class CollisionResult { kKeepBoth, kKeepOriginal, kConflict, kTakeNew }; + const ResourceConfigValue* FindValue(const android::ConfigDescription& config, + android::StringPiece product = {}) const; +}; + +struct ResourceTableTypeView { + ResourceType type; + Maybe<uint8_t> id; + Visibility::Level visibility_level = Visibility::Level::kUndefined; - using CollisionResolverFunc = std::function<CollisionResult(Value*, Value*)>; + // Entries sorted in ascending entry id order. If ids have not been assigned, the entries are + // sorted lexicographically. + std::vector<ResourceTableEntryView> entries; +}; - // When a collision of resources occurs, this method decides which value to keep. - static CollisionResult ResolveValueCollision(Value* existing, Value* incoming); +struct ResourceTablePackageView { + std::string name; + Maybe<uint8_t> id; + // Types sorted in ascending type id order. If ids have not been assigned, the types are sorted by + // their declaration order in the ResourceType enum. + std::vector<ResourceTableTypeView> types; +}; - // When a collision of resources occurs, this method keeps both values - static CollisionResult IgnoreCollision(Value* existing, Value* incoming); +struct ResourceTableViewOptions { + bool create_alias_entries = false; +}; - bool AddResource(const ResourceNameRef& name, const android::ConfigDescription& config, - const android::StringPiece& product, std::unique_ptr<Value> value, - IDiagnostics* diag); +struct ResourceTableView { + // Packages sorted in ascending package id order. If ids have not been assigned, the packages are + // sorted lexicographically. + std::vector<ResourceTablePackageView> packages; +}; - bool AddResourceWithId(const ResourceNameRef& name, const ResourceId& res_id, - const android::ConfigDescription& config, - const android::StringPiece& product, std::unique_ptr<Value> value, - IDiagnostics* diag); +enum class OnIdConflict { + // If the resource entry already exists but has a different resource id, the resource value will + // not be added to the table. + ERROR, - // Same as AddResource, but doesn't verify the validity of the name. This is used - // when loading resources from an existing binary resource table that may have mangled names. - bool AddResourceMangled(const ResourceNameRef& name, const android::ConfigDescription& config, - const android::StringPiece& product, std::unique_ptr<Value> value, - IDiagnostics* diag); + // If the resource entry already exists but has a different resource id, create a new resource + // with this resource name and id combination. + CREATE_ENTRY, +}; - bool AddResourceWithIdMangled(const ResourceNameRef& name, const ResourceId& id, - const android::ConfigDescription& config, - const android::StringPiece& product, std::unique_ptr<Value> value, - IDiagnostics* diag); +struct NewResource { + ResourceName name; + std::unique_ptr<Value> value; + android::ConfigDescription config; + std::string product; + std::optional<std::pair<ResourceId, OnIdConflict>> id; + std::optional<Visibility> visibility; + std::optional<OverlayableItem> overlayable; + std::optional<AllowNew> allow_new; + std::optional<StagedId> staged_id; + bool allow_mangled = false; +}; - bool GetValidateResources(); +struct NewResourceBuilder { + explicit NewResourceBuilder(const ResourceNameRef& name); + explicit NewResourceBuilder(const std::string& name); + NewResourceBuilder& SetValue(std::unique_ptr<Value> value, android::ConfigDescription config = {}, + std::string product = {}); + NewResourceBuilder& SetId(ResourceId id, OnIdConflict on_conflict = OnIdConflict::ERROR); + NewResourceBuilder& SetVisibility(Visibility id); + NewResourceBuilder& SetOverlayable(OverlayableItem overlayable); + NewResourceBuilder& SetAllowNew(AllowNew allow_new); + NewResourceBuilder& SetStagedId(StagedId id); + NewResourceBuilder& SetAllowMangled(bool allow_mangled); + NewResource Build(); - bool SetVisibility(const ResourceNameRef& name, const Visibility& visibility, IDiagnostics* diag); - bool SetVisibilityWithId(const ResourceNameRef& name, const Visibility& visibility, - const ResourceId& res_id, IDiagnostics* diag); - bool SetVisibilityWithIdMangled(const ResourceNameRef& name, const Visibility& visibility, - const ResourceId& res_id, IDiagnostics* diag); + private: + NewResource res_; +}; - bool SetOverlayable(const ResourceNameRef& name, const OverlayableItem& overlayable, - IDiagnostics *diag); +// The container and index for all resources defined for an app. +class ResourceTable { + public: + enum class Validation { + kEnabled, + kDisabled, + }; + + enum class CollisionResult { kKeepBoth, kKeepOriginal, kConflict, kTakeNew }; - bool SetAllowNew(const ResourceNameRef& name, const AllowNew& allow_new, IDiagnostics* diag); - bool SetAllowNewMangled(const ResourceNameRef& name, const AllowNew& allow_new, - IDiagnostics* diag); + ResourceTable() = default; + explicit ResourceTable(Validation validation); + + bool AddResource(NewResource&& res, IDiagnostics* diag); + + // Retrieves a sorted a view of the packages, types, and entries sorted in ascending resource id + // order. + ResourceTableView GetPartitionedView(const ResourceTableViewOptions& options = {}) const; struct SearchResult { ResourceTablePackage* package; @@ -252,23 +310,20 @@ class ResourceTable { }; Maybe<SearchResult> FindResource(const ResourceNameRef& name) const; + Maybe<SearchResult> FindResource(const ResourceNameRef& name, ResourceId id) const; + bool RemoveResource(const ResourceNameRef& name, ResourceId id) const; // Returns the package struct with the given name, or nullptr if such a package does not // exist. The empty string is a valid package and typically is used to represent the // 'current' package before it is known to the ResourceTable. ResourceTablePackage* FindPackage(const android::StringPiece& name) const; - - ResourceTablePackage* FindPackageById(uint8_t id) const; - - ResourceTablePackage* CreatePackage(const android::StringPiece& name, Maybe<uint8_t> id = {}); - - // Attempts to find a package having the specified name and ID. If not found, a new package - // of the specified parameters is created and returned. - ResourceTablePackage* CreatePackageAllowingDuplicateNames(const android::StringPiece& name, - const Maybe<uint8_t> id); + ResourceTablePackage* FindOrCreatePackage(const android::StringPiece& name); std::unique_ptr<ResourceTable> Clone() const; + // When a collision of resources occurs, this method decides which value to keep. + static CollisionResult ResolveValueCollision(Value* existing, Value* incoming); + // The string pool used by this resource table. Values that reference strings must use // this pool to create their strings. // NOTE: `string_pool` must come before `packages` so that it is destroyed after. @@ -286,36 +341,9 @@ class ResourceTable { std::map<size_t, std::string> included_packages_; private: - // The function type that validates a symbol name. Returns a non-empty StringPiece representing - // the offending character (which may be more than one byte in UTF-8). Returns an empty string - // if the name was valid. - using NameValidator = android::StringPiece(const android::StringPiece&); - - ResourceTablePackage* FindOrCreatePackage(const android::StringPiece& name); - - bool ValidateName(NameValidator validator, const ResourceNameRef& name, const Source& source, - IDiagnostics* diag); - - bool AddResourceImpl(const ResourceNameRef& name, const ResourceId& res_id, - const android::ConfigDescription& config, - const android::StringPiece& product, std::unique_ptr<Value> value, - NameValidator name_validator, const CollisionResolverFunc& conflict_resolver, - IDiagnostics* diag); - - bool SetVisibilityImpl(const ResourceNameRef& name, const Visibility& visibility, - const ResourceId& res_id, NameValidator name_validator, - IDiagnostics* diag); - - bool SetAllowNewImpl(const ResourceNameRef& name, const AllowNew& allow_new, - NameValidator name_validator, IDiagnostics* diag); - - bool SetOverlayableImpl(const ResourceNameRef &name, const OverlayableItem& overlayable, - NameValidator name_validator, IDiagnostics *diag); - - // Controls whether the table validates resource names and prevents duplicate resource names - bool validate_resources_ = true; - DISALLOW_COPY_AND_ASSIGN(ResourceTable); + + Validation validation_ = Validation::kEnabled; }; } // namespace aapt diff --git a/tools/aapt2/ResourceTable_test.cpp b/tools/aapt2/ResourceTable_test.cpp index 9271a7e6bae1..38391c99f55a 100644 --- a/tools/aapt2/ResourceTable_test.cpp +++ b/tools/aapt2/ResourceTable_test.cpp @@ -37,31 +37,32 @@ namespace aapt { TEST(ResourceTableTest, FailToAddResourceWithBadName) { ResourceTable table; - EXPECT_FALSE(table.AddResource( - test::ParseNameOrDie("android:id/hey,there"), ConfigDescription{}, "", - test::ValueBuilder<Id>().SetSource("test.xml", 21u).Build(), - test::GetDiagnostics())); + EXPECT_FALSE( + table.AddResource(NewResourceBuilder(test::ParseNameOrDie("android:id/hey,there")).Build(), + test::GetDiagnostics())); - EXPECT_FALSE(table.AddResource( - test::ParseNameOrDie("android:id/hey:there"), ConfigDescription{}, "", - test::ValueBuilder<Id>().SetSource("test.xml", 21u).Build(), - test::GetDiagnostics())); + EXPECT_FALSE( + table.AddResource(NewResourceBuilder(test::ParseNameOrDie("android:id/hey:there")).Build(), + test::GetDiagnostics())); } TEST(ResourceTableTest, AddResourceWithWeirdNameWhenAddingMangledResources) { ResourceTable table; - EXPECT_TRUE(table.AddResourceMangled( - test::ParseNameOrDie("android:id/heythere "), ConfigDescription{}, "", - test::ValueBuilder<Id>().SetSource("test.xml", 21u).Build(), test::GetDiagnostics())); + EXPECT_TRUE( + table.AddResource(NewResourceBuilder(test::ParseNameOrDie("android:id/heythere ")) + .SetAllowMangled(true) + .Build(), + test::GetDiagnostics())); } TEST(ResourceTableTest, AddOneResource) { ResourceTable table; EXPECT_TRUE(table.AddResource( - test::ParseNameOrDie("android:attr/id"), ConfigDescription{}, "", - test::ValueBuilder<Id>().SetSource("test/path/file.xml", 23u).Build(), + NewResourceBuilder(test::ParseNameOrDie("android:attr/id")) + .SetValue(test::ValueBuilder<Id>().SetSource("test/path/file.xml", 23u).Build()) + .Build(), test::GetDiagnostics())); EXPECT_THAT(test::GetValue<Id>(&table, "android:attr/id"), NotNull()); @@ -70,32 +71,36 @@ TEST(ResourceTableTest, AddOneResource) { TEST(ResourceTableTest, AddMultipleResources) { ResourceTable table; - ConfigDescription config; ConfigDescription language_config; memcpy(language_config.language, "pl", sizeof(language_config.language)); EXPECT_TRUE(table.AddResource( - test::ParseNameOrDie("android:attr/layout_width"), config, "", - test::ValueBuilder<Id>().SetSource("test/path/file.xml", 10u).Build(), - test::GetDiagnostics())); - - EXPECT_TRUE(table.AddResource( - test::ParseNameOrDie("android:attr/id"), config, "", - test::ValueBuilder<Id>().SetSource("test/path/file.xml", 12u).Build(), + NewResourceBuilder(test::ParseNameOrDie("android:attr/layout_width")) + .SetValue(test::ValueBuilder<Id>().SetSource("test/path/file.xml", 10u).Build()) + .Build(), test::GetDiagnostics())); EXPECT_TRUE(table.AddResource( - test::ParseNameOrDie("android:string/ok"), config, "", - test::ValueBuilder<Id>().SetSource("test/path/file.xml", 14u).Build(), + NewResourceBuilder(test::ParseNameOrDie("android:attr/id")) + .SetValue(test::ValueBuilder<Id>().SetSource("test/path/file.xml", 12u).Build()) + .Build(), test::GetDiagnostics())); EXPECT_TRUE(table.AddResource( - test::ParseNameOrDie("android:string/ok"), language_config, "", - test::ValueBuilder<BinaryPrimitive>(android::Res_value{}) - .SetSource("test/path/file.xml", 20u) + NewResourceBuilder(test::ParseNameOrDie("android:string/ok")) + .SetValue(test::ValueBuilder<Id>().SetSource("test/path/file.xml", 14u).Build()) .Build(), test::GetDiagnostics())); + EXPECT_TRUE( + table.AddResource(NewResourceBuilder(test::ParseNameOrDie("android:string/ok")) + .SetValue(test::ValueBuilder<BinaryPrimitive>(android::Res_value{}) + .SetSource("test/path/file.xml", 20u) + .Build(), + language_config) + .Build(), + test::GetDiagnostics())); + EXPECT_THAT(test::GetValue<Id>(&table, "android:attr/layout_width"), NotNull()); EXPECT_THAT(test::GetValue<Id>(&table, "android:attr/id"), NotNull()); EXPECT_THAT(test::GetValue<Id>(&table, "android:string/ok"), NotNull()); @@ -104,17 +109,19 @@ TEST(ResourceTableTest, AddMultipleResources) { TEST(ResourceTableTest, OverrideWeakResourceValue) { ResourceTable table; - - ASSERT_TRUE(table.AddResource(test::ParseNameOrDie("android:attr/foo"), ConfigDescription{}, "", - test::AttributeBuilder().SetWeak(true).Build(), + ASSERT_TRUE(table.AddResource(NewResourceBuilder(test::ParseNameOrDie("android:attr/foo")) + .SetValue(test::AttributeBuilder().SetWeak(true).Build()) + .Build(), test::GetDiagnostics())); Attribute* attr = test::GetValue<Attribute>(&table, "android:attr/foo"); ASSERT_THAT(attr, NotNull()); EXPECT_TRUE(attr->IsWeak()); - ASSERT_TRUE(table.AddResource(test::ParseNameOrDie("android:attr/foo"), ConfigDescription{}, "", - util::make_unique<Attribute>(), test::GetDiagnostics())); + ASSERT_TRUE(table.AddResource(NewResourceBuilder(test::ParseNameOrDie("android:attr/foo")) + .SetValue(util::make_unique<Attribute>()) + .Build(), + test::GetDiagnostics())); attr = test::GetValue<Attribute>(&table, "android:attr/foo"); ASSERT_THAT(attr, NotNull()); @@ -130,23 +137,27 @@ TEST(ResourceTableTest, AllowCompatibleDuplicateAttributes) { Attribute attr_two(android::ResTable_map::TYPE_STRING | android::ResTable_map::TYPE_REFERENCE); attr_two.SetWeak(true); - ASSERT_TRUE(table.AddResource(name, ConfigDescription{}, "", - util::make_unique<Attribute>(attr_one), test::GetDiagnostics())); - ASSERT_TRUE(table.AddResource(name, ConfigDescription{}, "", - util::make_unique<Attribute>(attr_two), test::GetDiagnostics())); + ASSERT_TRUE(table.AddResource( + NewResourceBuilder(name).SetValue(util::make_unique<Attribute>(attr_one)).Build(), + test::GetDiagnostics())); + ASSERT_TRUE(table.AddResource( + NewResourceBuilder(name).SetValue(util::make_unique<Attribute>(attr_two)).Build(), + test::GetDiagnostics())); } TEST(ResourceTableTest, ProductVaryingValues) { ResourceTable table; + ASSERT_TRUE(table.AddResource( + NewResourceBuilder(test::ParseNameOrDie("android:string/foo")) + .SetValue(util::make_unique<Id>(), test::ParseConfigOrDie("land"), "tablet") + .Build(), + test::GetDiagnostics())); - EXPECT_TRUE(table.AddResource(test::ParseNameOrDie("android:string/foo"), - test::ParseConfigOrDie("land"), "tablet", - util::make_unique<Id>(), - test::GetDiagnostics())); - EXPECT_TRUE(table.AddResource(test::ParseNameOrDie("android:string/foo"), - test::ParseConfigOrDie("land"), "phone", - util::make_unique<Id>(), - test::GetDiagnostics())); + ASSERT_TRUE(table.AddResource( + NewResourceBuilder(test::ParseNameOrDie("android:string/foo")) + .SetValue(util::make_unique<Id>(), test::ParseConfigOrDie("land"), "phone") + .Build(), + test::GetDiagnostics())); EXPECT_THAT(test::GetValueForConfigAndProduct<Id>(&table, "android:string/foo",test::ParseConfigOrDie("land"), "tablet"), NotNull()); EXPECT_THAT(test::GetValueForConfigAndProduct<Id>(&table, "android:string/foo",test::ParseConfigOrDie("land"), "phone"), NotNull()); @@ -203,22 +214,26 @@ TEST(ResourceTableTest, SetVisibility) { Visibility visibility; visibility.level = Visibility::Level::kPrivate; visibility.comment = "private"; - ASSERT_TRUE(table.SetVisibility(name, visibility, test::GetDiagnostics())); + ASSERT_TRUE(table.AddResource(NewResourceBuilder(name).SetVisibility(visibility).Build(), + test::GetDiagnostics())); ASSERT_TRUE(VisibilityOfResource(table, name, Level::kPrivate, "private")); visibility.level = Visibility::Level::kUndefined; visibility.comment = "undefined"; - ASSERT_TRUE(table.SetVisibility(name, visibility, test::GetDiagnostics())); + ASSERT_TRUE(table.AddResource(NewResourceBuilder(name).SetVisibility(visibility).Build(), + test::GetDiagnostics())); ASSERT_TRUE(VisibilityOfResource(table, name, Level::kPrivate, "private")); visibility.level = Visibility::Level::kPublic; visibility.comment = "public"; - ASSERT_TRUE(table.SetVisibility(name, visibility, test::GetDiagnostics())); + ASSERT_TRUE(table.AddResource(NewResourceBuilder(name).SetVisibility(visibility).Build(), + test::GetDiagnostics())); ASSERT_TRUE(VisibilityOfResource(table, name, Level::kPublic, "public")); visibility.level = Visibility::Level::kPrivate; visibility.comment = "private"; - ASSERT_TRUE(table.SetVisibility(name, visibility, test::GetDiagnostics())); + ASSERT_TRUE(table.AddResource(NewResourceBuilder(name).SetVisibility(visibility).Build(), + test::GetDiagnostics())); ASSERT_TRUE(VisibilityOfResource(table, name, Level::kPublic, "public")); } @@ -230,14 +245,16 @@ TEST(ResourceTableTest, SetAllowNew) { Maybe<ResourceTable::SearchResult> result; allow_new.comment = "first"; - ASSERT_TRUE(table.SetAllowNew(name, allow_new, test::GetDiagnostics())); + ASSERT_TRUE(table.AddResource(NewResourceBuilder(name).SetAllowNew(allow_new).Build(), + test::GetDiagnostics())); result = table.FindResource(name); ASSERT_TRUE(result); ASSERT_TRUE(result.value().entry->allow_new); ASSERT_THAT(result.value().entry->allow_new.value().comment, StrEq("first")); allow_new.comment = "second"; - ASSERT_TRUE(table.SetAllowNew(name, allow_new, test::GetDiagnostics())); + ASSERT_TRUE(table.AddResource(NewResourceBuilder(name).SetAllowNew(allow_new).Build(), + test::GetDiagnostics())); result = table.FindResource(name); ASSERT_TRUE(result); ASSERT_TRUE(result.value().entry->allow_new); @@ -255,7 +272,8 @@ TEST(ResourceTableTest, SetOverlayable) { overlayable_item.source = Source("res/values/overlayable.xml", 42); const ResourceName name = test::ParseNameOrDie("android:string/foo"); - ASSERT_TRUE(table.SetOverlayable(name, overlayable_item, test::GetDiagnostics())); + ASSERT_TRUE(table.AddResource(NewResourceBuilder(name).SetOverlayable(overlayable_item).Build(), + test::GetDiagnostics())); Maybe<ResourceTable::SearchResult> search_result = table.FindResource(name); ASSERT_TRUE(search_result); @@ -280,17 +298,20 @@ TEST(ResourceTableTest, SetMultipleOverlayableResources) { auto group = std::make_shared<Overlayable>("Name", "overlay://theme"); OverlayableItem overlayable(group); overlayable.policies = PolicyFlags::PRODUCT_PARTITION; - ASSERT_TRUE(table.SetOverlayable(foo, overlayable, test::GetDiagnostics())); + ASSERT_TRUE(table.AddResource(NewResourceBuilder(foo).SetOverlayable(overlayable).Build(), + test::GetDiagnostics())); const ResourceName bar = test::ParseNameOrDie("android:string/bar"); OverlayableItem overlayable2(group); overlayable2.policies = PolicyFlags::PRODUCT_PARTITION; - ASSERT_TRUE(table.SetOverlayable(bar, overlayable2, test::GetDiagnostics())); + ASSERT_TRUE(table.AddResource(NewResourceBuilder(bar).SetOverlayable(overlayable2).Build(), + test::GetDiagnostics())); const ResourceName baz = test::ParseNameOrDie("android:string/baz"); OverlayableItem overlayable3(group); overlayable3.policies = PolicyFlags::VENDOR_PARTITION; - ASSERT_TRUE(table.SetOverlayable(baz, overlayable3, test::GetDiagnostics())); + ASSERT_TRUE(table.AddResource(NewResourceBuilder(baz).SetOverlayable(overlayable3).Build(), + test::GetDiagnostics())); } TEST(ResourceTableTest, SetOverlayableDifferentResourcesDifferentName) { @@ -299,12 +320,14 @@ TEST(ResourceTableTest, SetOverlayableDifferentResourcesDifferentName) { const ResourceName foo = test::ParseNameOrDie("android:string/foo"); OverlayableItem overlayable_item(std::make_shared<Overlayable>("Name", "overlay://theme")); overlayable_item.policies = PolicyFlags::PRODUCT_PARTITION; - ASSERT_TRUE(table.SetOverlayable(foo, overlayable_item, test::GetDiagnostics())); + ASSERT_TRUE(table.AddResource(NewResourceBuilder(foo).SetOverlayable(overlayable_item).Build(), + test::GetDiagnostics())); const ResourceName bar = test::ParseNameOrDie("android:string/bar"); OverlayableItem overlayable_item2(std::make_shared<Overlayable>("Name2", "overlay://theme")); overlayable_item2.policies = PolicyFlags::PRODUCT_PARTITION; - ASSERT_TRUE(table.SetOverlayable(bar, overlayable_item2, test::GetDiagnostics())); + ASSERT_TRUE(table.AddResource(NewResourceBuilder(bar).SetOverlayable(overlayable_item2).Build(), + test::GetDiagnostics())); } TEST(ResourceTableTest, SetOverlayableSameResourcesFail) { @@ -313,10 +336,12 @@ TEST(ResourceTableTest, SetOverlayableSameResourcesFail) { auto overlayable = std::make_shared<Overlayable>("Name", "overlay://theme"); OverlayableItem overlayable_item(overlayable); - ASSERT_TRUE(table.SetOverlayable(name, overlayable_item, test::GetDiagnostics())); + ASSERT_TRUE(table.AddResource(NewResourceBuilder(name).SetOverlayable(overlayable_item).Build(), + test::GetDiagnostics())); OverlayableItem overlayable_item2(overlayable); - ASSERT_FALSE(table.SetOverlayable(name, overlayable_item2, test::GetDiagnostics())); + ASSERT_FALSE(table.AddResource(NewResourceBuilder(name).SetOverlayable(overlayable_item2).Build(), + test::GetDiagnostics())); } TEST(ResourceTableTest, SetOverlayableSameResourcesDifferentNameFail) { @@ -325,51 +350,38 @@ TEST(ResourceTableTest, SetOverlayableSameResourcesDifferentNameFail) { auto overlayable = std::make_shared<Overlayable>("Name", "overlay://theme"); OverlayableItem overlayable_item(overlayable); - ASSERT_TRUE(table.SetOverlayable(name, overlayable_item, test::GetDiagnostics())); + ASSERT_TRUE(table.AddResource(NewResourceBuilder(name).SetOverlayable(overlayable_item).Build(), + test::GetDiagnostics())); auto overlayable2 = std::make_shared<Overlayable>("Other", "overlay://theme"); OverlayableItem overlayable_item2(overlayable2); - ASSERT_FALSE(table.SetOverlayable(name, overlayable_item2, test::GetDiagnostics())); + ASSERT_FALSE(table.AddResource(NewResourceBuilder(name).SetOverlayable(overlayable_item2).Build(), + test::GetDiagnostics())); } -TEST(ResourceTableTest, AllowDuplictaeResourcesNames) { - ResourceTable table(/* validate_resources */ false); - - const ResourceName foo_name = test::ParseNameOrDie("android:bool/foo"); - ASSERT_TRUE(table.AddResourceWithId(foo_name, ResourceId(0x7f0100ff), ConfigDescription{} , "", - test::BuildPrimitive(android::Res_value::TYPE_INT_BOOLEAN, 0), - test::GetDiagnostics())); - ASSERT_TRUE(table.AddResourceWithId(foo_name, ResourceId(0x7f010100), ConfigDescription{} , "", - test::BuildPrimitive(android::Res_value::TYPE_INT_BOOLEAN, 1), - test::GetDiagnostics())); - - ASSERT_TRUE(table.SetVisibilityWithId(foo_name, Visibility{Visibility::Level::kPublic}, - ResourceId(0x7f0100ff), test::GetDiagnostics())); - ASSERT_TRUE(table.SetVisibilityWithId(foo_name, Visibility{Visibility::Level::kPrivate}, - ResourceId(0x7f010100), test::GetDiagnostics())); - - auto package = table.FindPackageById(0x7f); - ASSERT_THAT(package, NotNull()); - auto type = package->FindType(ResourceType::kBool); - ASSERT_THAT(type, NotNull()); - - auto entry1 = type->FindEntry("foo", 0x00ff); - ASSERT_THAT(entry1, NotNull()); - ASSERT_THAT(entry1->id, Eq(0x00ff)); - ASSERT_THAT(entry1->values[0], NotNull()); - ASSERT_THAT(entry1->values[0]->value, NotNull()); - ASSERT_THAT(ValueCast<BinaryPrimitive>(entry1->values[0]->value.get()), NotNull()); - ASSERT_THAT(ValueCast<BinaryPrimitive>(entry1->values[0]->value.get())->value.data, Eq(0u)); - ASSERT_THAT(entry1->visibility.level, Visibility::Level::kPublic); - - auto entry2 = type->FindEntry("foo", 0x0100); - ASSERT_THAT(entry2, NotNull()); - ASSERT_THAT(entry2->id, Eq(0x0100)); - ASSERT_THAT(entry2->values[0], NotNull()); - ASSERT_THAT(entry1->values[0]->value, NotNull()); - ASSERT_THAT(ValueCast<BinaryPrimitive>(entry2->values[0]->value.get()), NotNull()); - ASSERT_THAT(ValueCast<BinaryPrimitive>(entry2->values[0]->value.get())->value.data, Eq(1u)); - ASSERT_THAT(entry2->visibility.level, Visibility::Level::kPrivate); +TEST(ResourceTableTest, ConflictingIds) { + ResourceTable table; + const ResourceName name = test::ParseNameOrDie("android:string/foo"); + ASSERT_TRUE(table.AddResource(NewResourceBuilder(name).SetId(0x01010000).Build(), + test::GetDiagnostics())); + ASSERT_FALSE(table.AddResource(NewResourceBuilder(name).SetId(0x01010001).Build(), + test::GetDiagnostics())); +} + +TEST(ResourceTableTest, ConflictingIdsCreateEntry) { + ResourceTable table; + const ResourceName name = test::ParseNameOrDie("android:string/foo"); + ASSERT_TRUE(table.AddResource( + NewResourceBuilder(name).SetId(0x01010000, OnIdConflict::CREATE_ENTRY).Build(), + test::GetDiagnostics())); + ASSERT_TRUE(table.AddResource( + NewResourceBuilder(name).SetId(0x01010001, OnIdConflict::CREATE_ENTRY).Build(), + test::GetDiagnostics())); + + // Non-ambiguous query + ASSERT_TRUE(table.AddResource( + NewResourceBuilder(name).SetId(0x01010001).SetValue(std::make_unique<Id>()).Build(), + test::GetDiagnostics())); } } // namespace aapt diff --git a/tools/aapt2/ResourceUtils.cpp b/tools/aapt2/ResourceUtils.cpp index f26e995aa4f9..e0e80ac02dea 100644 --- a/tools/aapt2/ResourceUtils.cpp +++ b/tools/aapt2/ResourceUtils.cpp @@ -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; @@ -740,7 +743,7 @@ std::unique_ptr<Item> ParseBinaryResValue(const ResourceType& type, const Config if (type == ResourceType::kId) { if (res_value.dataType != android::Res_value::TYPE_REFERENCE && res_value.dataType != android::Res_value::TYPE_DYNAMIC_REFERENCE) { - // plain "id" resources are actually encoded as dummy values (aapt1 uses an empty string, + // plain "id" resources are actually encoded as unused values (aapt1 uses an empty string, // while aapt2 uses a false boolean). return util::make_unique<Id>(); } diff --git a/tools/aapt2/ResourceUtils.h b/tools/aapt2/ResourceUtils.h index f77766ee9061..be493db8cee0 100644 --- a/tools/aapt2/ResourceUtils.h +++ b/tools/aapt2/ResourceUtils.h @@ -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); diff --git a/tools/aapt2/ResourceValues.cpp b/tools/aapt2/ResourceValues.cpp index 4f0fa8ae29ba..2a90f267f185 100644 --- a/tools/aapt2/ResourceValues.cpp +++ b/tools/aapt2/ResourceValues.cpp @@ -47,6 +47,14 @@ std::ostream& operator<<(std::ostream& out, const Value& value) { return out; } +std::unique_ptr<Value> Value::Transform(ValueTransformer& transformer) const { + return std::unique_ptr<Value>(this->TransformValueImpl(transformer)); +} + +std::unique_ptr<Item> Item::Transform(ValueTransformer& transformer) const { + return std::unique_ptr<Item>(this->TransformItemImpl(transformer)); +} + template <typename Derived> void BaseValue<Derived>::Accept(ValueVisitor* visitor) { visitor->Visit(static_cast<Derived*>(this)); @@ -77,13 +85,6 @@ bool RawString::Equals(const Value* value) const { return *this->value == *other->value; } -RawString* RawString::Clone(StringPool* new_pool) const { - RawString* rs = new RawString(new_pool->MakeRef(value)); - rs->comment_ = comment_; - rs->source_ = source_; - return rs; -} - bool RawString::Flatten(android::Res_value* out_value) const { out_value->dataType = android::Res_value::TYPE_STRING; out_value->data = util::HostToDevice32(static_cast<uint32_t>(value.index())); @@ -110,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; @@ -136,10 +140,6 @@ bool Reference::Flatten(android::Res_value* out_value) const { return true; } -Reference* Reference::Clone(StringPool* /*new_pool*/) const { - return new Reference(*this); -} - void Reference::Print(std::ostream* out) const { if (reference_type == Type::kResource) { *out << "(reference) @"; @@ -220,10 +220,6 @@ bool Id::Flatten(android::Res_value* out) const { return true; } -Id* Id::Clone(StringPool* /*new_pool*/) const { - return new Id(*this); -} - void Id::Print(std::ostream* out) const { *out << "(id)"; } @@ -266,14 +262,6 @@ bool String::Flatten(android::Res_value* out_value) const { return true; } -String* String::Clone(StringPool* new_pool) const { - String* str = new String(new_pool->MakeRef(value)); - str->comment_ = comment_; - str->source_ = source_; - str->untranslatable_sections = untranslatable_sections; - return str; -} - void String::Print(std::ostream* out) const { *out << "(string) \"" << *value << "\""; } @@ -321,14 +309,6 @@ bool StyledString::Flatten(android::Res_value* out_value) const { return true; } -StyledString* StyledString::Clone(StringPool* new_pool) const { - StyledString* str = new StyledString(new_pool->MakeRef(value)); - str->comment_ = comment_; - str->source_ = source_; - str->untranslatable_sections = untranslatable_sections; - return str; -} - void StyledString::Print(std::ostream* out) const { *out << "(styled string) \"" << value->value << "\""; for (const StringPool::Span& span : value->spans) { @@ -357,15 +337,6 @@ bool FileReference::Flatten(android::Res_value* out_value) const { return true; } -FileReference* FileReference::Clone(StringPool* new_pool) const { - FileReference* fr = new FileReference(new_pool->MakeRef(path)); - fr->file = file; - fr->type = type; - fr->comment_ = comment_; - fr->source_ = source_; - return fr; -} - void FileReference::Print(std::ostream* out) const { *out << "(file) " << *path; switch (type) { @@ -406,10 +377,6 @@ bool BinaryPrimitive::Flatten(::android::Res_value* out_value) const { return true; } -BinaryPrimitive* BinaryPrimitive::Clone(StringPool* /*new_pool*/) const { - return new BinaryPrimitive(*this); -} - void BinaryPrimitive::Print(std::ostream* out) const { *out << StringPrintf("(primitive) type=0x%02x data=0x%08x", value.dataType, value.data); } @@ -587,11 +554,7 @@ bool Attribute::IsCompatibleWith(const Attribute& attr) const { return this_type_mask == that_type_mask; } -Attribute* Attribute::Clone(StringPool* /*new_pool*/) const { - return new Attribute(*this); -} - -std::string Attribute::MaskString() const { +std::string Attribute::MaskString(uint32_t type_mask) { if (type_mask == android::ResTable_map::TYPE_ANY) { return "any"; } @@ -690,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(); @@ -893,18 +860,6 @@ bool Style::Equals(const Value* value) const { }); } -Style* Style::Clone(StringPool* new_pool) const { - Style* style = new Style(); - style->parent = parent; - style->parent_inferred = parent_inferred; - style->comment_ = comment_; - style->source_ = source_; - for (auto& entry : entries) { - style->entries.push_back(Entry{entry.key, std::unique_ptr<Item>(entry.value->Clone(new_pool))}); - } - return style; -} - void Style::Print(std::ostream* out) const { *out << "(style) "; if (parent && parent.value().name) { @@ -920,7 +875,8 @@ void Style::Print(std::ostream* out) const { Style::Entry CloneEntry(const Style::Entry& entry, StringPool* pool) { Style::Entry cloned_entry{entry.key}; if (entry.value != nullptr) { - cloned_entry.value.reset(entry.value->Clone(pool)); + CloningValueTransformer cloner(pool); + cloned_entry.value = entry.value->Transform(cloner); } return cloned_entry; } @@ -993,16 +949,6 @@ bool Array::Equals(const Value* value) const { }); } -Array* Array::Clone(StringPool* new_pool) const { - Array* array = new Array(); - array->comment_ = comment_; - array->source_ = source_; - for (auto& item : elements) { - array->elements.emplace_back(std::unique_ptr<Item>(item->Clone(new_pool))); - } - return array; -} - void Array::Print(std::ostream* out) const { *out << "(array) [" << util::Joiner(elements, ", ") << "]"; } @@ -1030,19 +976,6 @@ bool Plural::Equals(const Value* value) const { return true; } -Plural* Plural::Clone(StringPool* new_pool) const { - Plural* p = new Plural(); - p->comment_ = comment_; - p->source_ = source_; - const size_t count = values.size(); - for (size_t i = 0; i < count; i++) { - if (values[i]) { - p->values[i] = std::unique_ptr<Item>(values[i]->Clone(new_pool)); - } - } - return p; -} - void Plural::Print(std::ostream* out) const { *out << "(plural)"; if (values[Zero]) { @@ -1086,15 +1019,26 @@ bool Styleable::Equals(const Value* value) const { }); } -Styleable* Styleable::Clone(StringPool* /*new_pool*/) const { - return new Styleable(*this); -} - void Styleable::Print(std::ostream* out) const { *out << "(styleable) " << " [" << 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; @@ -1126,4 +1070,110 @@ void Styleable::MergeWith(Styleable* other) { entries.insert(entries.end(), references.begin(), references.end()); } +template <typename T> +std::unique_ptr<T> CopyValueFields(std::unique_ptr<T> new_value, const T* value) { + new_value->SetSource(value->GetSource()); + new_value->SetComment(value->GetComment()); + return new_value; +} + +CloningValueTransformer::CloningValueTransformer(StringPool* new_pool) + : ValueTransformer(new_pool) { +} + +std::unique_ptr<Reference> CloningValueTransformer::TransformDerived(const Reference* value) { + return std::make_unique<Reference>(*value); +} + +std::unique_ptr<Id> CloningValueTransformer::TransformDerived(const Id* value) { + return std::make_unique<Id>(*value); +} + +std::unique_ptr<RawString> CloningValueTransformer::TransformDerived(const RawString* value) { + auto new_value = std::make_unique<RawString>(pool_->MakeRef(value->value)); + return CopyValueFields(std::move(new_value), value); +} + +std::unique_ptr<String> CloningValueTransformer::TransformDerived(const String* value) { + auto new_value = std::make_unique<String>(pool_->MakeRef(value->value)); + new_value->untranslatable_sections = value->untranslatable_sections; + return CopyValueFields(std::move(new_value), value); +} + +std::unique_ptr<StyledString> CloningValueTransformer::TransformDerived(const StyledString* value) { + auto new_value = std::make_unique<StyledString>(pool_->MakeRef(value->value)); + new_value->untranslatable_sections = value->untranslatable_sections; + return CopyValueFields(std::move(new_value), value); +} + +std::unique_ptr<FileReference> CloningValueTransformer::TransformDerived( + const FileReference* value) { + auto new_value = std::make_unique<FileReference>(pool_->MakeRef(value->path)); + new_value->file = value->file; + new_value->type = value->type; + return CopyValueFields(std::move(new_value), value); +} + +std::unique_ptr<BinaryPrimitive> CloningValueTransformer::TransformDerived( + const BinaryPrimitive* value) { + return std::make_unique<BinaryPrimitive>(*value); +} + +std::unique_ptr<Attribute> CloningValueTransformer::TransformDerived(const Attribute* value) { + auto new_value = std::make_unique<Attribute>(); + new_value->type_mask = value->type_mask; + new_value->min_int = value->min_int; + new_value->max_int = value->max_int; + for (const Attribute::Symbol& s : value->symbols) { + new_value->symbols.emplace_back(Attribute::Symbol{ + .symbol = *s.symbol.Transform(*this), + .value = s.value, + .type = s.type, + }); + } + return CopyValueFields(std::move(new_value), value); +} + +std::unique_ptr<Style> CloningValueTransformer::TransformDerived(const Style* value) { + auto new_value = std::make_unique<Style>(); + new_value->parent = value->parent; + new_value->parent_inferred = value->parent_inferred; + for (auto& entry : value->entries) { + new_value->entries.push_back(Style::Entry{entry.key, entry.value->Transform(*this)}); + } + return CopyValueFields(std::move(new_value), value); +} + +std::unique_ptr<Array> CloningValueTransformer::TransformDerived(const Array* value) { + auto new_value = std::make_unique<Array>(); + for (auto& item : value->elements) { + new_value->elements.emplace_back(item->Transform(*this)); + } + return CopyValueFields(std::move(new_value), value); +} + +std::unique_ptr<Plural> CloningValueTransformer::TransformDerived(const Plural* value) { + auto new_value = std::make_unique<Plural>(); + const size_t count = value->values.size(); + for (size_t i = 0; i < count; i++) { + if (value->values[i]) { + new_value->values[i] = value->values[i]->Transform(*this); + } + } + return CopyValueFields(std::move(new_value), value); +} + +std::unique_ptr<Styleable> CloningValueTransformer::TransformDerived(const Styleable* value) { + auto new_value = std::make_unique<Styleable>(); + for (const Reference& s : value->entries) { + new_value->entries.emplace_back(*s.Transform(*this)); + } + 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 diff --git a/tools/aapt2/ResourceValues.h b/tools/aapt2/ResourceValues.h index fe0883be50aa..d903b7e1b8b3 100644 --- a/tools/aapt2/ResourceValues.h +++ b/tools/aapt2/ResourceValues.h @@ -28,6 +28,7 @@ #include "Diagnostics.h" #include "Resource.h" #include "StringPool.h" +#include "ValueTransformer.h" #include "io/File.h" #include "text/Printer.h" #include "util/Maybe.h" @@ -100,9 +101,8 @@ class Value { // Calls the appropriate overload of ConstValueVisitor. virtual void Accept(ConstValueVisitor* visitor) const = 0; - // Clone the value. `new_pool` is the new StringPool that - // any resources with strings should use when copying their string. - virtual Value* Clone(StringPool* new_pool) const = 0; + // Transform this Value into another Value using the transformer. + std::unique_ptr<Value> Transform(ValueTransformer& transformer) const; // Human readable printout of this value. virtual void Print(std::ostream* out) const = 0; @@ -118,6 +118,9 @@ class Value { std::string comment_; bool weak_ = false; bool translatable_ = true; + + private: + virtual Value* TransformValueImpl(ValueTransformer& transformer) const = 0; }; // Inherit from this to get visitor accepting implementations for free. @@ -129,12 +132,15 @@ struct BaseValue : public Value { // A resource item with a single value. This maps to android::ResTable_entry. struct Item : public Value { - // Clone the Item. - virtual Item* Clone(StringPool* new_pool) const override = 0; - // Fills in an android::Res_value structure with this Item's binary representation. // Returns false if an error occurred. virtual bool Flatten(android::Res_value* out_value) const = 0; + + // Transform this Item into another Item using the transformer. + std::unique_ptr<Item> Transform(ValueTransformer& transformer) const; + + private: + virtual Item* TransformItemImpl(ValueTransformer& transformer) const = 0; }; // Inherit from this to get visitor accepting implementations for free. @@ -147,17 +153,19 @@ struct BaseItem : public Item { // A reference to another resource. This maps to android::Res_value::TYPE_REFERENCE. // A reference can be symbolic (with the name set to a valid resource name) or be // numeric (the id is set to a valid resource ID). -struct Reference : public BaseItem<Reference> { - enum class Type { +struct Reference : public TransformableItem<Reference, BaseItem<Reference>> { + enum class Type : uint8_t { kResource, kAttribute, }; Maybe<ResourceName> name; Maybe<ResourceId> id; + std::optional<uint32_t> type_flags; Reference::Type reference_type; bool private_reference = false; bool is_dynamic = false; + bool allow_raw = false; Reference(); explicit Reference(const ResourceNameRef& n, Type type = Type::kResource); @@ -166,7 +174,6 @@ struct Reference : public BaseItem<Reference> { bool Equals(const Value* value) const override; bool Flatten(android::Res_value* out_value) const override; - Reference* Clone(StringPool* new_pool) const override; void Print(std::ostream* out) const override; void PrettyPrint(text::Printer* printer) const override; @@ -178,27 +185,25 @@ bool operator<(const Reference&, const Reference&); bool operator==(const Reference&, const Reference&); // An ID resource. Has no real value, just a place holder. -struct Id : public BaseItem<Id> { +struct Id : public TransformableItem<Id, BaseItem<Id>> { Id() { weak_ = true; } bool Equals(const Value* value) const override; bool Flatten(android::Res_value* out) const override; - Id* Clone(StringPool* new_pool) const override; void Print(std::ostream* out) const override; }; // A raw, unprocessed string. This may contain quotations, escape sequences, and whitespace. // This shall *NOT* end up in the final resource table. -struct RawString : public BaseItem<RawString> { +struct RawString : public TransformableItem<RawString, BaseItem<RawString>> { StringPool::Ref value; explicit RawString(const StringPool::Ref& ref); bool Equals(const Value* value) const override; bool Flatten(android::Res_value* out_value) const override; - RawString* Clone(StringPool* new_pool) const override; void Print(std::ostream* out) const override; }; @@ -220,7 +225,7 @@ inline bool operator!=(const UntranslatableSection& a, const UntranslatableSecti return a.start != b.start || a.end != b.end; } -struct String : public BaseItem<String> { +struct String : public TransformableItem<String, BaseItem<String>> { StringPool::Ref value; // Sections of the string to NOT translate. Mainly used @@ -232,12 +237,11 @@ struct String : public BaseItem<String> { bool Equals(const Value* value) const override; bool Flatten(android::Res_value* out_value) const override; - String* Clone(StringPool* new_pool) const override; void Print(std::ostream* out) const override; void PrettyPrint(text::Printer* printer) const override; }; -struct StyledString : public BaseItem<StyledString> { +struct StyledString : public TransformableItem<StyledString, BaseItem<StyledString>> { StringPool::StyleRef value; // Sections of the string to NOT translate. Mainly used @@ -249,11 +253,10 @@ struct StyledString : public BaseItem<StyledString> { bool Equals(const Value* value) const override; bool Flatten(android::Res_value* out_value) const override; - StyledString* Clone(StringPool* new_pool) const override; void Print(std::ostream* out) const override; }; -struct FileReference : public BaseItem<FileReference> { +struct FileReference : public TransformableItem<FileReference, BaseItem<FileReference>> { StringPool::Ref path; // A handle to the file object from which this file can be read. @@ -269,12 +272,11 @@ struct FileReference : public BaseItem<FileReference> { bool Equals(const Value* value) const override; bool Flatten(android::Res_value* out_value) const override; - FileReference* Clone(StringPool* new_pool) const override; void Print(std::ostream* out) const override; }; // Represents any other android::Res_value. -struct BinaryPrimitive : public BaseItem<BinaryPrimitive> { +struct BinaryPrimitive : public TransformableItem<BinaryPrimitive, BaseItem<BinaryPrimitive>> { android::Res_value value; BinaryPrimitive() = default; @@ -283,12 +285,11 @@ struct BinaryPrimitive : public BaseItem<BinaryPrimitive> { bool Equals(const Value* value) const override; bool Flatten(android::Res_value* out_value) const override; - BinaryPrimitive* Clone(StringPool* new_pool) const override; void Print(std::ostream* out) const override; void PrettyPrint(text::Printer* printer) const override; }; -struct Attribute : public BaseValue<Attribute> { +struct Attribute : public TransformableValue<Attribute, BaseValue<Attribute>> { struct Symbol { Reference symbol; uint32_t value; @@ -311,13 +312,14 @@ struct Attribute : public BaseValue<Attribute> { // TYPE_ENUMS are never compatible. bool IsCompatibleWith(const Attribute& attr) const; - Attribute* Clone(StringPool* new_pool) const override; 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; }; -struct Style : public BaseValue<Style> { +struct Style : public TransformableValue<Style, BaseValue<Style>> { struct Entry { Reference key; std::unique_ptr<Item> value; @@ -333,7 +335,6 @@ struct Style : public BaseValue<Style> { std::vector<Entry> entries; bool Equals(const Value* value) const override; - Style* Clone(StringPool* new_pool) const override; void Print(std::ostream* out) const override; // Merges `style` into this Style. All identical attributes of `style` take precedence, including @@ -341,33 +342,52 @@ struct Style : public BaseValue<Style> { void MergeWith(Style* style, StringPool* pool); }; -struct Array : public BaseValue<Array> { +struct Array : public TransformableValue<Array, BaseValue<Array>> { std::vector<std::unique_ptr<Item>> elements; bool Equals(const Value* value) const override; - Array* Clone(StringPool* new_pool) const override; void Print(std::ostream* out) const override; }; -struct Plural : public BaseValue<Plural> { +struct Plural : public TransformableValue<Plural, BaseValue<Plural>> { enum { Zero = 0, One, Two, Few, Many, Other, Count }; std::array<std::unique_ptr<Item>, Count> values; bool Equals(const Value* value) const override; - Plural* Clone(StringPool* new_pool) const override; void Print(std::ostream* out) const override; }; -struct Styleable : public BaseValue<Styleable> { +struct Styleable : public TransformableValue<Styleable, BaseValue<Styleable>> { std::vector<Reference> entries; bool Equals(const Value* value) const override; - Styleable* Clone(StringPool* newPool) const override; void Print(std::ostream* out) const override; 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) { @@ -379,6 +399,24 @@ typename std::enable_if<std::is_base_of<Value, T>::value, std::ostream&>::type o return out; } +struct CloningValueTransformer : public ValueTransformer { + explicit CloningValueTransformer(StringPool* new_pool); + + std::unique_ptr<Reference> TransformDerived(const Reference* value) override; + std::unique_ptr<Id> TransformDerived(const Id* value) override; + std::unique_ptr<RawString> TransformDerived(const RawString* value) override; + std::unique_ptr<String> TransformDerived(const String* value) override; + std::unique_ptr<StyledString> TransformDerived(const StyledString* value) override; + std::unique_ptr<FileReference> TransformDerived(const FileReference* value) override; + std::unique_ptr<BinaryPrimitive> TransformDerived(const BinaryPrimitive* value) override; + std::unique_ptr<Attribute> TransformDerived(const Attribute* value) override; + std::unique_ptr<Style> TransformDerived(const Style* value) override; + 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 #endif // AAPT_RESOURCE_VALUES_H diff --git a/tools/aapt2/ResourceValues_test.cpp b/tools/aapt2/ResourceValues_test.cpp index c4a1108ac62a..c75a4b99e138 100644 --- a/tools/aapt2/ResourceValues_test.cpp +++ b/tools/aapt2/ResourceValues_test.cpp @@ -62,7 +62,8 @@ TEST(ResourceValuesTest, PluralClone) { a.values[Plural::One] = util::make_unique<String>(pool.MakeRef("one")); a.values[Plural::Other] = util::make_unique<String>(pool.MakeRef("other")); - std::unique_ptr<Plural> b(a.Clone(&pool)); + CloningValueTransformer cloner(&pool); + std::unique_ptr<Plural> b(a.Transform(cloner)); EXPECT_TRUE(a.Equals(b.get())); } @@ -97,7 +98,8 @@ TEST(ResourceValuesTest, ArrayClone) { a.elements.push_back(util::make_unique<String>(pool.MakeRef("one"))); a.elements.push_back(util::make_unique<String>(pool.MakeRef("two"))); - std::unique_ptr<Array> b(a.Clone(&pool)); + CloningValueTransformer cloner(&pool); + std::unique_ptr<Array> b(a.Transform(cloner)); EXPECT_TRUE(a.Equals(b.get())); } @@ -160,7 +162,8 @@ TEST(ResourceValuesTest, StyleClone) { .AddItem("android:attr/bar", ResourceUtils::TryParseInt("2")) .Build(); - std::unique_ptr<Style> b(a->Clone(nullptr)); + CloningValueTransformer cloner(nullptr); + std::unique_ptr<Style> b(a->Transform(cloner)); EXPECT_TRUE(a->Equals(b.get())); } @@ -174,7 +177,8 @@ TEST(ResourcesValuesTest, StringClones) { EXPECT_THAT(pool_a.strings()[0]->context.config, Eq(test::ParseConfigOrDie("en"))); EXPECT_THAT(pool_a.strings()[0]->value, StrEq("hello")); - std::unique_ptr<String> str_b(str_a.Clone(&pool_b)); + CloningValueTransformer cloner(&pool_b); + str_a.Transform(cloner); ASSERT_THAT(pool_b, SizeIs(1u)); EXPECT_THAT(pool_b.strings()[0]->context.config, Eq(test::ParseConfigOrDie("en"))); EXPECT_THAT(pool_b.strings()[0]->value, StrEq("hello")); diff --git a/tools/aapt2/Resources.proto b/tools/aapt2/Resources.proto index b1e1a77e1224..95b794964068 100644 --- a/tools/aapt2/Resources.proto +++ b/tools/aapt2/Resources.proto @@ -132,6 +132,11 @@ message Visibility { // The comment associated with the <public> tag. string comment = 3; + + // Indicates that the resource id may change across builds and that the public R.java identifier + // for this resource should not be final. This is set to `true` for resources in `staging-group` + // tags. + bool staged_api = 4; } // Whether a resource comes from a compile-time overlay and is explicitly allowed to not overlay an @@ -185,6 +190,12 @@ message OverlayableItem { uint32 overlayable_idx = 4; } +// The staged resource ID definition of a finalized resource. +message StagedId { + Source source = 1; + uint32 staged_id = 2; +} + // An entry ID in the range [0x0000, 0xffff]. message EntryId { uint32 id = 1; @@ -217,6 +228,9 @@ message Entry { // The set of values defined for this entry, each corresponding to a different // configuration/variant. repeated ConfigValue config_value = 6; + + // The staged resource ID of this finalized resource. + StagedId staged_id = 7; } // A Configuration/Value pair. @@ -268,6 +282,7 @@ message CompoundValue { Styleable styleable = 3; Array array = 4; Plural plural = 5; + MacroBody macro = 6; } } @@ -299,6 +314,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 @@ -586,3 +608,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; +}
\ No newline at end of file diff --git a/tools/aapt2/StringPool.h b/tools/aapt2/StringPool.h index 1006ca970dc5..3457e0b41859 100644 --- a/tools/aapt2/StringPool.h +++ b/tools/aapt2/StringPool.h @@ -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 { diff --git a/tools/aapt2/ValueTransformer.cpp b/tools/aapt2/ValueTransformer.cpp new file mode 100644 index 000000000000..2d7996b9d880 --- /dev/null +++ b/tools/aapt2/ValueTransformer.cpp @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ValueTransformer.h" + +#include "ResourceValues.h" + +namespace aapt { + +#define VALUE_CREATE_VALUE_DECL(T) \ + std::unique_ptr<Value> ValueTransformer::TransformValue(const T* value) { \ + return TransformDerived(value); \ + } + +#define VALUE_CREATE_ITEM_DECL(T) \ + std::unique_ptr<Item> ValueTransformer::TransformItem(const T* value) { \ + return TransformDerived(value); \ + } \ + std::unique_ptr<Value> ValueTransformer::TransformValue(const T* value) { \ + return TransformItem(value); \ + } + +VALUE_CREATE_ITEM_DECL(Id); +VALUE_CREATE_ITEM_DECL(Reference); +VALUE_CREATE_ITEM_DECL(RawString); +VALUE_CREATE_ITEM_DECL(String); +VALUE_CREATE_ITEM_DECL(StyledString); +VALUE_CREATE_ITEM_DECL(FileReference); +VALUE_CREATE_ITEM_DECL(BinaryPrimitive); + +VALUE_CREATE_VALUE_DECL(Attribute); +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
\ No newline at end of file diff --git a/tools/aapt2/ValueTransformer.h b/tools/aapt2/ValueTransformer.h new file mode 100644 index 000000000000..6fc4a191b04b --- /dev/null +++ b/tools/aapt2/ValueTransformer.h @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AAPT_VALUE_TRANSFORMER_H +#define AAPT_VALUE_TRANSFORMER_H + +#include <memory> + +#include "StringPool.h" + +namespace aapt { + +class Value; +struct Item; +struct Reference; +struct Id; +struct RawString; +struct String; +struct StyledString; +struct FileReference; +struct BinaryPrimitive; +struct Attribute; +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; \ + virtual std::unique_ptr<Value> TransformValue(const T* value); + +#define AAPT_TRANSFORM_ITEM(T) \ + virtual std::unique_ptr<Item> TransformItem(const T* value); \ + AAPT_TRANSFORM_VALUE(T) + +/** + * An interface for consuming a Value type and transforming it into another Value. + * + * The interface defines 2 methods for each type (T) that inherits from TransformableValue: + * std::unique_ptr<T> TransformDerived(const T*) + * std::unique_ptr<Value> TransformValue(const T*) + * + * The interface defines 3 method for each type (T) that inherits from TransformableItem: + * std::unique_ptr<T> TransformDerived(const T*) + * std::unique_ptr<Item> TransformItem(const T*) + * std::unique_ptr<Value> TransformValue(const T*) + * + * TransformDerived is invoked when Transform is invoked on the derived type T. + * TransformItem is invoked when Transform is invoked on an Item type. + * TransformValue is invoked when Transform is invoked on a Value type. + * + * ValueTransformerImpl transformer(&string_pool); + * T* derived = ...; + * std::unique_ptr<T> new_type = derived->Transform(transformer); // Invokes TransformDerived + * + * Item* item = derived; + * std::unique_ptr<Item> new_item = item->TransformItem(transformer); // Invokes TransformItem + * + * Value* value = item; + * std::unique_ptr<Value> new_value = value->TransformValue(transformer); // Invokes TransformValue + * + * For types that inherit from AbstractTransformableItem, the default implementation of + * TransformValue invokes TransformItem which invokes TransformDerived. + * + * For types that inherit from AbstractTransformableValue, the default implementation of + * TransformValue invokes TransformDerived. + */ +struct ValueTransformer { + // `new_pool` is the new StringPool that newly created Values should use for string storing string + // values. + explicit ValueTransformer(StringPool* new_pool); + virtual ~ValueTransformer() = default; + + AAPT_TRANSFORM_ITEM(Id); + AAPT_TRANSFORM_ITEM(Reference); + AAPT_TRANSFORM_ITEM(RawString); + AAPT_TRANSFORM_ITEM(String); + AAPT_TRANSFORM_ITEM(StyledString); + AAPT_TRANSFORM_ITEM(FileReference); + AAPT_TRANSFORM_ITEM(BinaryPrimitive); + + AAPT_TRANSFORM_VALUE(Attribute); + AAPT_TRANSFORM_VALUE(Style); + AAPT_TRANSFORM_VALUE(Array); + AAPT_TRANSFORM_VALUE(Plural); + AAPT_TRANSFORM_VALUE(Styleable); + AAPT_TRANSFORM_VALUE(Macro); + + protected: + StringPool* const pool_; +}; + +#undef AAPT_TRANSFORM_VALUE +#undef AAPT_TRANSFORM_ITEM + +template <typename Derived, typename Base> +struct TransformableValue : public Base { + // Transform this Derived into another Derived using the transformer. + std::unique_ptr<Derived> Transform(ValueTransformer& transformer) const; + + private: + Value* TransformValueImpl(ValueTransformer& transformer) const override; +}; + +template <typename Derived, typename Base> +struct TransformableItem : public TransformableValue<Derived, Base> { + private: + Item* TransformItemImpl(ValueTransformer& transformer) const override; +}; + +} // namespace aapt + +// Implementation +#include "ValueTransformer_inline.h" + +#endif // AAPT_VALUE_TRANSFORMER_H
\ No newline at end of file diff --git a/tools/aapt2/ValueTransformer_inline.h b/tools/aapt2/ValueTransformer_inline.h new file mode 100644 index 000000000000..c6c07c0fd6f9 --- /dev/null +++ b/tools/aapt2/ValueTransformer_inline.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AAPT_VALUE_TRANSFORMER_IMPL_H +#define AAPT_VALUE_TRANSFORMER_IMPL_H + +namespace aapt { + +inline ValueTransformer::ValueTransformer(StringPool* new_pool) : pool_(new_pool) { +} + +template <typename Derived, typename Base> +inline std::unique_ptr<Derived> TransformableValue<Derived, Base>::Transform( + ValueTransformer& transformer) const { + return transformer.TransformDerived(static_cast<const Derived*>(this)); +} + +template <typename Derived, typename Base> +Value* TransformableValue<Derived, Base>::TransformValueImpl(ValueTransformer& transformer) const { + auto self = static_cast<const Derived*>(this); + auto transformed = transformer.TransformValue(self); + return transformed.release(); +} + +template <typename Derived, typename Base> +Item* TransformableItem<Derived, Base>::TransformItemImpl(ValueTransformer& transformer) const { + auto self = static_cast<const Derived*>(this); + auto transformed = transformer.TransformItem(self); + return transformed.release(); +} + +} // namespace aapt + +#endif // AAPT_VALUE_TRANSFORMER_IMPL_H
\ No newline at end of file diff --git a/tools/aapt2/ValueVisitor.h b/tools/aapt2/ValueVisitor.h index 4e74ec366dab..d0c9d89b6f0c 100644 --- a/tools/aapt2/ValueVisitor.h +++ b/tools/aapt2/ValueVisitor.h @@ -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. diff --git a/tools/aapt2/cmd/Compile.cpp b/tools/aapt2/cmd/Compile.cpp index 32686538c10d..cd5015e81203 100644 --- a/tools/aapt2/cmd/Compile.cpp +++ b/tools/aapt2/cmd/Compile.cpp @@ -75,8 +75,10 @@ struct ResourcePathData { }; // Resource file paths are expected to look like: [--/res/]type[-config]/name -static Maybe<ResourcePathData> ExtractResourcePathData(const std::string& path, const char dir_sep, - std::string* out_error) { +static Maybe<ResourcePathData> ExtractResourcePathData(const std::string& path, + const char dir_sep, + std::string* out_error, + const CompileOptions& options) { std::vector<std::string> parts = util::Split(path, dir_sep); if (parts.size() < 2) { if (out_error) *out_error = "bad resource path"; @@ -121,7 +123,11 @@ static Maybe<ResourcePathData> ExtractResourcePathData(const std::string& path, } } - return ResourcePathData{Source(path), dir_str.to_string(), name.to_string(), + const Source res_path = options.source_path + ? StringPiece(options.source_path.value()) + : StringPiece(path); + + return ResourcePathData{res_path, dir_str.to_string(), name.to_string(), extension.to_string(), config_str.to_string(), config}; } @@ -184,17 +190,6 @@ static bool CompileTable(IAaptContext* context, const CompileOptions& options, } } - // Ensure we have the compilation package at least. - table.CreatePackage(context->GetCompilationPackage()); - - // Assign an ID to any package that has resources. - for (auto& pkg : table.packages) { - if (!pkg->id) { - // If no package ID was set while parsing (public identifiers), auto assign an ID. - pkg->id = context->GetPackageId(); - } - } - // Create the file/zip entry. if (!writer->StartEntry(output_path, 0)) { context->GetDiagnostics()->Error(DiagMessage(output_path) << "failed to open"); @@ -667,7 +662,8 @@ int Compile(IAaptContext* context, io::IFileCollection* inputs, IArchiveWriter* // Extract resource type information from the full path std::string err_str; ResourcePathData path_data; - if (auto maybe_path_data = ExtractResourcePathData(path, inputs->GetDirSeparator(), &err_str)) { + if (auto maybe_path_data = ExtractResourcePathData( + path, inputs->GetDirSeparator(), &err_str, options)) { path_data = maybe_path_data.value(); } else { context->GetDiagnostics()->Error(DiagMessage(file->GetSource()) << err_str); @@ -747,6 +743,11 @@ int CompileCommand::Action(const std::vector<std::string>& args) { context.GetDiagnostics()->Error(DiagMessage() << "only one of --dir and --zip can be specified"); return 1; + } else if ((options_.res_dir || options_.res_zip) && + options_.source_path && args.size() > 1) { + context.GetDiagnostics()->Error(DiagMessage(kPath) + << "Cannot use an overriding source path with multiple files."); + return 1; } else if (options_.res_dir) { if (!args.empty()) { context.GetDiagnostics()->Error(DiagMessage() << "files given but --dir specified"); diff --git a/tools/aapt2/cmd/Compile.h b/tools/aapt2/cmd/Compile.h index 1752a1adac24..1bc1f6651f85 100644 --- a/tools/aapt2/cmd/Compile.h +++ b/tools/aapt2/cmd/Compile.h @@ -28,6 +28,7 @@ namespace aapt { struct CompileOptions { std::string output_path; + Maybe<std::string> source_path; Maybe<std::string> res_dir; Maybe<std::string> res_zip; Maybe<std::string> generate_text_symbols_path; @@ -69,6 +70,9 @@ class CompileCommand : public Command { AddOptionalSwitch("-v", "Enables verbose logging", &options_.verbose); AddOptionalFlag("--trace-folder", "Generate systrace json trace fragment to specified folder.", &trace_folder_); + AddOptionalFlag("--source-path", + "Sets the compiled resource file source file path to the given string.", + &options_.source_path); } int Action(const std::vector<std::string>& args) override; diff --git a/tools/aapt2/cmd/Compile_test.cpp b/tools/aapt2/cmd/Compile_test.cpp index ae9f7926cf09..89757134f3a7 100644 --- a/tools/aapt2/cmd/Compile_test.cpp +++ b/tools/aapt2/cmd/Compile_test.cpp @@ -24,6 +24,7 @@ #include "io/ZipArchive.h" #include "java/AnnotationProcessor.h" #include "test/Test.h" +#include "format/proto/ProtoDeserialize.h" namespace aapt { @@ -216,6 +217,7 @@ static void AssertTranslations(CommandTestFixture *ctf, std::string file_name, }, compiled_files_dir, &diag)); std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(out_apk, &diag); + ASSERT_NE(apk, nullptr); ResourceTable* table = apk->GetResourceTable(); ASSERT_NE(table, nullptr); @@ -252,4 +254,93 @@ TEST_F(CompilerTest, DoNotTranslateTest) { AssertTranslations(this, "donottranslate", expected_not_translatable); AssertTranslations(this, "donottranslate_foo", expected_not_translatable); } + +TEST_F(CompilerTest, RelativePathTest) { + StdErrDiagnostics diag; + const std::string res_path = BuildPath( + {android::base::Dirname(android::base::GetExecutablePath()), + "integration-tests", "CompileTest", "res"}); + + const std::string path_values_colors = GetTestPath("values/colors.xml"); + WriteFile(path_values_colors, "<resources>" + "<color name=\"color_one\">#008577</color>" + "</resources>"); + + const std::string path_layout_layout_one = GetTestPath("layout/layout_one.xml"); + WriteFile(path_layout_layout_one, "<LinearLayout " + "xmlns:android=\"http://schemas.android.com/apk/res/android\">" + "<TextBox android:id=\"@+id/text_one\" android:background=\"@color/color_one\"/>" + "</LinearLayout>"); + + const std::string compiled_files_dir = BuildPath( + {android::base::Dirname(android::base::GetExecutablePath()), + "integration-tests", "CompileTest", "compiled"}); + CHECK(file::mkdirs(compiled_files_dir.data())); + + const std::string path_values_colors_out = + BuildPath({compiled_files_dir,"values_colors.arsc.flat"}); + const std::string path_layout_layout_one_out = + BuildPath({compiled_files_dir, "layout_layout_one.flat"}); + ::android::base::utf8::unlink(path_values_colors_out.c_str()); + ::android::base::utf8::unlink(path_layout_layout_one_out.c_str()); + const std::string apk_path = BuildPath( + {android::base::Dirname(android::base::GetExecutablePath()), + "integration-tests", "CompileTest", "out.apk"}); + + const std::string source_set_res = BuildPath({"main", "res"}); + const std::string relative_path_values_colors = + BuildPath({source_set_res, "values", "colors.xml"}); + const std::string relative_path_layout_layout_one = + BuildPath({source_set_res, "layout", "layout_one.xml"}); + + CompileCommand(&diag).Execute({ + path_values_colors, + "-o", + compiled_files_dir, + "--source-path", + relative_path_values_colors}, + &std::cerr); + + CompileCommand(&diag).Execute({ + path_layout_layout_one, + "-o", + compiled_files_dir, + "--source-path", + relative_path_layout_layout_one}, + &std::cerr); + + std::ifstream ifs_values(path_values_colors_out); + std::string content_values((std::istreambuf_iterator<char>(ifs_values)), + (std::istreambuf_iterator<char>())); + ASSERT_NE(content_values.find(relative_path_values_colors), -1); + ASSERT_EQ(content_values.find(path_values_colors), -1); + + ASSERT_TRUE(Link({"-o", apk_path, + "--manifest", GetDefaultManifest(), + "--proto-format"}, + compiled_files_dir, &diag)); + + std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(apk_path, &diag); + ResourceTable* resource_table = apk.get()->GetResourceTable(); + const std::vector<std::unique_ptr<StringPool::Entry>>& pool_strings = + resource_table->string_pool.strings(); + + ASSERT_EQ(pool_strings.size(), 2); + ASSERT_EQ(pool_strings[0]->value, "res/layout/layout_one.xml"); + ASSERT_EQ(pool_strings[1]->value, "res/layout-v1/layout_one.xml"); + + // Check resources.pb contains relative sources. + io::IFile* proto_file = + apk.get()->GetFileCollection()->FindFile("resources.pb"); + std::unique_ptr<io::InputStream> proto_stream = proto_file->OpenInputStream(); + io::ProtoInputStreamReader proto_reader(proto_stream.get()); + pb::ResourceTable pb_table; + proto_reader.ReadMessage(&pb_table); + + const std::string pool_strings_proto = pb_table.source_pool().data(); + + ASSERT_NE(pool_strings_proto.find(relative_path_values_colors), -1); + ASSERT_NE(pool_strings_proto.find(relative_path_layout_layout_one), -1); +} + } // namespace aapt diff --git a/tools/aapt2/cmd/Diff.cpp b/tools/aapt2/cmd/Diff.cpp index d56994e3ae24..3950f337b575 100644 --- a/tools/aapt2/cmd/Diff.cpp +++ b/tools/aapt2/cmd/Diff.cpp @@ -83,7 +83,7 @@ static void EmitDiffLine(const Source& source, const StringPiece& message) { } static bool IsSymbolVisibilityDifferent(const Visibility& vis_a, const Visibility& vis_b) { - return vis_a.level != vis_b.level; + return vis_a.level != vis_b.level || vis_a.staged_api != vis_b.staged_api; } template <typename Id> @@ -95,17 +95,17 @@ static bool IsIdDiff(const Visibility::Level& level_a, const Maybe<Id>& id_a, return false; } -static bool EmitResourceConfigValueDiff(IAaptContext* context, LoadedApk* apk_a, - ResourceTablePackage* pkg_a, ResourceTableType* type_a, - ResourceEntry* entry_a, ResourceConfigValue* config_value_a, - LoadedApk* apk_b, ResourceTablePackage* pkg_b, - ResourceTableType* type_b, ResourceEntry* entry_b, - ResourceConfigValue* config_value_b) { +static bool EmitResourceConfigValueDiff( + IAaptContext* context, LoadedApk* apk_a, const ResourceTablePackageView& pkg_a, + const ResourceTableTypeView& type_a, const ResourceTableEntryView& entry_a, + const ResourceConfigValue* config_value_a, LoadedApk* apk_b, + const ResourceTablePackageView& pkg_b, const ResourceTableTypeView& type_b, + const ResourceTableEntryView& entry_b, const ResourceConfigValue* config_value_b) { Value* value_a = config_value_a->value.get(); Value* value_b = config_value_b->value.get(); if (!value_a->Equals(value_b)) { std::stringstream str_stream; - str_stream << "value " << pkg_a->name << ":" << type_a->type << "/" << entry_a->name + str_stream << "value " << pkg_a.name << ":" << type_a.type << "/" << entry_a.name << " config=" << config_value_a->config << " does not match:\n"; value_a->Print(&str_stream); str_stream << "\n vs \n"; @@ -117,64 +117,82 @@ static bool EmitResourceConfigValueDiff(IAaptContext* context, LoadedApk* apk_a, } static bool EmitResourceEntryDiff(IAaptContext* context, LoadedApk* apk_a, - ResourceTablePackage* pkg_a, ResourceTableType* type_a, - ResourceEntry* entry_a, LoadedApk* apk_b, - ResourceTablePackage* pkg_b, ResourceTableType* type_b, - ResourceEntry* entry_b) { + const ResourceTablePackageView& pkg_a, + const ResourceTableTypeView& type_a, + const ResourceTableEntryView& entry_a, LoadedApk* apk_b, + const ResourceTablePackageView& pkg_b, + const ResourceTableTypeView& type_b, + const ResourceTableEntryView& entry_b) { bool diff = false; - for (std::unique_ptr<ResourceConfigValue>& config_value_a : entry_a->values) { - ResourceConfigValue* config_value_b = entry_b->FindValue(config_value_a->config); + for (const ResourceConfigValue* config_value_a : entry_a.values) { + auto config_value_b = entry_b.FindValue(config_value_a->config); if (!config_value_b) { std::stringstream str_stream; - str_stream << "missing " << pkg_a->name << ":" << type_a->type << "/" << entry_a->name + str_stream << "missing " << pkg_a.name << ":" << type_a.type << "/" << entry_a.name << " config=" << config_value_a->config; EmitDiffLine(apk_b->GetSource(), str_stream.str()); diff = true; } else { - diff |= - EmitResourceConfigValueDiff(context, apk_a, pkg_a, type_a, entry_a, config_value_a.get(), - apk_b, pkg_b, type_b, entry_b, config_value_b); + diff |= EmitResourceConfigValueDiff(context, apk_a, pkg_a, type_a, entry_a, config_value_a, + apk_b, pkg_b, type_b, entry_b, config_value_b); } } // Check for any newly added config values. - for (std::unique_ptr<ResourceConfigValue>& config_value_b : entry_b->values) { - ResourceConfigValue* config_value_a = entry_a->FindValue(config_value_b->config); + for (const ResourceConfigValue* config_value_b : entry_b.values) { + auto config_value_a = entry_a.FindValue(config_value_b->config); if (!config_value_a) { std::stringstream str_stream; - str_stream << "new config " << pkg_b->name << ":" << type_b->type << "/" << entry_b->name + str_stream << "new config " << pkg_b.name << ":" << type_b.type << "/" << entry_b.name << " config=" << config_value_b->config; EmitDiffLine(apk_b->GetSource(), str_stream.str()); diff = true; } } - return false; + return diff; } static bool EmitResourceTypeDiff(IAaptContext* context, LoadedApk* apk_a, - ResourceTablePackage* pkg_a, ResourceTableType* type_a, - LoadedApk* apk_b, ResourceTablePackage* pkg_b, - ResourceTableType* type_b) { + const ResourceTablePackageView& pkg_a, + const ResourceTableTypeView& type_a, LoadedApk* apk_b, + const ResourceTablePackageView& pkg_b, + const ResourceTableTypeView& type_b) { bool diff = false; - for (std::unique_ptr<ResourceEntry>& entry_a : type_a->entries) { - ResourceEntry* entry_b = type_b->FindEntry(entry_a->name); - if (!entry_b) { + auto entry_a_iter = type_a.entries.begin(); + auto entry_b_iter = type_b.entries.begin(); + while (entry_a_iter != type_a.entries.end() || entry_b_iter != type_b.entries.end()) { + if (entry_b_iter == type_b.entries.end()) { + // Type A contains a type that type B does not have. + std::stringstream str_stream; + str_stream << "missing " << pkg_a.name << ":" << type_a.type << "/" << entry_a_iter->name; + EmitDiffLine(apk_a->GetSource(), str_stream.str()); + diff = true; + } else if (entry_a_iter == type_a.entries.end()) { + // Type B contains a type that type A does not have. std::stringstream str_stream; - str_stream << "missing " << pkg_a->name << ":" << type_a->type << "/" << entry_a->name; + str_stream << "new entry " << pkg_b.name << ":" << type_b.type << "/" << entry_b_iter->name; EmitDiffLine(apk_b->GetSource(), str_stream.str()); diff = true; } else { - if (IsSymbolVisibilityDifferent(entry_a->visibility, entry_b->visibility)) { + const auto& entry_a = *entry_a_iter; + const auto& entry_b = *entry_b_iter; + if (IsSymbolVisibilityDifferent(entry_a.visibility, entry_b.visibility)) { std::stringstream str_stream; - str_stream << pkg_a->name << ":" << type_a->type << "/" << entry_a->name + str_stream << pkg_a.name << ":" << type_a.type << "/" << entry_a.name << " has different visibility ("; - if (entry_b->visibility.level == Visibility::Level::kPublic) { + if (entry_b.visibility.staged_api) { + str_stream << "STAGED "; + } + if (entry_b.visibility.level == Visibility::Level::kPublic) { str_stream << "PUBLIC"; } else { str_stream << "PRIVATE"; } str_stream << " vs "; - if (entry_a->visibility.level == Visibility::Level::kPublic) { + if (entry_a.visibility.staged_api) { + str_stream << "STAGED "; + } + if (entry_a.visibility.level == Visibility::Level::kPublic) { str_stream << "PUBLIC"; } else { str_stream << "PRIVATE"; @@ -182,19 +200,19 @@ static bool EmitResourceTypeDiff(IAaptContext* context, LoadedApk* apk_a, str_stream << ")"; EmitDiffLine(apk_b->GetSource(), str_stream.str()); diff = true; - } else if (IsIdDiff(entry_a->visibility.level, entry_a->id, entry_b->visibility.level, - entry_b->id)) { + } else if (IsIdDiff(entry_a.visibility.level, entry_a.id, entry_b.visibility.level, + entry_b.id)) { std::stringstream str_stream; - str_stream << pkg_a->name << ":" << type_a->type << "/" << entry_a->name + str_stream << pkg_a.name << ":" << type_a.type << "/" << entry_a.name << " has different public ID ("; - if (entry_b->id) { - str_stream << "0x" << std::hex << entry_b->id.value(); + if (entry_b.id) { + str_stream << "0x" << std::hex << entry_b.id.value(); } else { str_stream << "none"; } str_stream << " vs "; - if (entry_a->id) { - str_stream << "0x " << std::hex << entry_a->id.value(); + if (entry_a.id) { + str_stream << "0x " << std::hex << entry_a.id.value(); } else { str_stream << "none"; } @@ -202,46 +220,51 @@ static bool EmitResourceTypeDiff(IAaptContext* context, LoadedApk* apk_a, EmitDiffLine(apk_b->GetSource(), str_stream.str()); diff = true; } - diff |= EmitResourceEntryDiff(context, apk_a, pkg_a, type_a, entry_a.get(), apk_b, pkg_b, - type_b, entry_b); + diff |= EmitResourceEntryDiff(context, apk_a, pkg_a, type_a, entry_a, apk_b, pkg_b, type_b, + entry_b); } - } - - // Check for any newly added entries. - for (std::unique_ptr<ResourceEntry>& entry_b : type_b->entries) { - ResourceEntry* entry_a = type_a->FindEntry(entry_b->name); - if (!entry_a) { - std::stringstream str_stream; - str_stream << "new entry " << pkg_b->name << ":" << type_b->type << "/" << entry_b->name; - EmitDiffLine(apk_b->GetSource(), str_stream.str()); - diff = true; + if (entry_a_iter != type_a.entries.end()) { + ++entry_a_iter; + } + if (entry_b_iter != type_b.entries.end()) { + ++entry_b_iter; } } return diff; } static bool EmitResourcePackageDiff(IAaptContext* context, LoadedApk* apk_a, - ResourceTablePackage* pkg_a, LoadedApk* apk_b, - ResourceTablePackage* pkg_b) { + const ResourceTablePackageView& pkg_a, LoadedApk* apk_b, + const ResourceTablePackageView& pkg_b) { bool diff = false; - for (std::unique_ptr<ResourceTableType>& type_a : pkg_a->types) { - ResourceTableType* type_b = pkg_b->FindType(type_a->type); - if (!type_b) { + auto type_a_iter = pkg_a.types.begin(); + auto type_b_iter = pkg_b.types.begin(); + while (type_a_iter != pkg_a.types.end() || type_b_iter != pkg_b.types.end()) { + if (type_b_iter == pkg_b.types.end()) { + // Type A contains a type that type B does not have. std::stringstream str_stream; - str_stream << "missing " << pkg_a->name << ":" << type_a->type; + str_stream << "missing " << pkg_a.name << ":" << type_a_iter->type; EmitDiffLine(apk_a->GetSource(), str_stream.str()); diff = true; + } else if (type_a_iter == pkg_a.types.end()) { + // Type B contains a type that type A does not have. + std::stringstream str_stream; + str_stream << "new type " << pkg_b.name << ":" << type_b_iter->type; + EmitDiffLine(apk_b->GetSource(), str_stream.str()); + diff = true; } else { - if (type_a->visibility_level != type_b->visibility_level) { + const auto& type_a = *type_a_iter; + const auto& type_b = *type_b_iter; + if (type_a.visibility_level != type_b.visibility_level) { std::stringstream str_stream; - str_stream << pkg_a->name << ":" << type_a->type << " has different visibility ("; - if (type_b->visibility_level == Visibility::Level::kPublic) { + str_stream << pkg_a.name << ":" << type_a.type << " has different visibility ("; + if (type_b.visibility_level == Visibility::Level::kPublic) { str_stream << "PUBLIC"; } else { str_stream << "PRIVATE"; } str_stream << " vs "; - if (type_a->visibility_level == Visibility::Level::kPublic) { + if (type_a.visibility_level == Visibility::Level::kPublic) { str_stream << "PUBLIC"; } else { str_stream << "PRIVATE"; @@ -249,18 +272,17 @@ static bool EmitResourcePackageDiff(IAaptContext* context, LoadedApk* apk_a, str_stream << ")"; EmitDiffLine(apk_b->GetSource(), str_stream.str()); diff = true; - } else if (IsIdDiff(type_a->visibility_level, type_a->id, type_b->visibility_level, - type_b->id)) { + } else if (IsIdDiff(type_a.visibility_level, type_a.id, type_b.visibility_level, type_b.id)) { std::stringstream str_stream; - str_stream << pkg_a->name << ":" << type_a->type << " has different public ID ("; - if (type_b->id) { - str_stream << "0x" << std::hex << type_b->id.value(); + str_stream << pkg_a.name << ":" << type_a.type << " has different public ID ("; + if (type_b.id) { + str_stream << "0x" << std::hex << type_b.id.value(); } else { str_stream << "none"; } str_stream << " vs "; - if (type_a->id) { - str_stream << "0x " << std::hex << type_a->id.value(); + if (type_a.id) { + str_stream << "0x " << std::hex << type_a.id.value(); } else { str_stream << "none"; } @@ -268,47 +290,52 @@ static bool EmitResourcePackageDiff(IAaptContext* context, LoadedApk* apk_a, EmitDiffLine(apk_b->GetSource(), str_stream.str()); diff = true; } - diff |= EmitResourceTypeDiff(context, apk_a, pkg_a, type_a.get(), apk_b, pkg_b, type_b); + diff |= EmitResourceTypeDiff(context, apk_a, pkg_a, type_a, apk_b, pkg_b, type_b); } - } - - // Check for any newly added types. - for (std::unique_ptr<ResourceTableType>& type_b : pkg_b->types) { - ResourceTableType* type_a = pkg_a->FindType(type_b->type); - if (!type_a) { - std::stringstream str_stream; - str_stream << "new type " << pkg_b->name << ":" << type_b->type; - EmitDiffLine(apk_b->GetSource(), str_stream.str()); - diff = true; + if (type_a_iter != pkg_a.types.end()) { + ++type_a_iter; + } + if (type_b_iter != pkg_b.types.end()) { + ++type_b_iter; } } return diff; } static bool EmitResourceTableDiff(IAaptContext* context, LoadedApk* apk_a, LoadedApk* apk_b) { - ResourceTable* table_a = apk_a->GetResourceTable(); - ResourceTable* table_b = apk_b->GetResourceTable(); + const auto table_a = apk_a->GetResourceTable()->GetPartitionedView(); + const auto table_b = apk_b->GetResourceTable()->GetPartitionedView(); bool diff = false; - for (std::unique_ptr<ResourceTablePackage>& pkg_a : table_a->packages) { - ResourceTablePackage* pkg_b = table_b->FindPackage(pkg_a->name); - if (!pkg_b) { + auto package_a_iter = table_a.packages.begin(); + auto package_b_iter = table_b.packages.begin(); + while (package_a_iter != table_a.packages.end() || package_b_iter != table_b.packages.end()) { + if (package_b_iter == table_b.packages.end()) { + // Table A contains a package that table B does not have. + std::stringstream str_stream; + str_stream << "missing package " << package_a_iter->name; + EmitDiffLine(apk_a->GetSource(), str_stream.str()); + diff = true; + } else if (package_a_iter == table_a.packages.end()) { + // Table B contains a package that table A does not have. std::stringstream str_stream; - str_stream << "missing package " << pkg_a->name; + str_stream << "new package " << package_b_iter->name; EmitDiffLine(apk_b->GetSource(), str_stream.str()); diff = true; } else { - if (pkg_a->id != pkg_b->id) { + const auto& package_a = *package_a_iter; + const auto& package_b = *package_b_iter; + if (package_a.id != package_b.id) { std::stringstream str_stream; - str_stream << "package '" << pkg_a->name << "' has different id ("; - if (pkg_b->id) { - str_stream << "0x" << std::hex << pkg_b->id.value(); + str_stream << "package '" << package_a.name << "' has different id ("; + if (package_b.id) { + str_stream << "0x" << std::hex << package_b.id.value(); } else { str_stream << "none"; } str_stream << " vs "; - if (pkg_a->id) { - str_stream << "0x" << std::hex << pkg_a->id.value(); + if (package_a.id) { + str_stream << "0x" << std::hex << package_b.id.value(); } else { str_stream << "none"; } @@ -316,20 +343,16 @@ static bool EmitResourceTableDiff(IAaptContext* context, LoadedApk* apk_a, Loade EmitDiffLine(apk_b->GetSource(), str_stream.str()); diff = true; } - diff |= EmitResourcePackageDiff(context, apk_a, pkg_a.get(), apk_b, pkg_b); + diff |= EmitResourcePackageDiff(context, apk_a, package_a, apk_b, package_b); } - } - - // Check for any newly added packages. - for (std::unique_ptr<ResourceTablePackage>& pkg_b : table_b->packages) { - ResourceTablePackage* pkg_a = table_a->FindPackage(pkg_b->name); - if (!pkg_a) { - std::stringstream str_stream; - str_stream << "new package " << pkg_b->name; - EmitDiffLine(apk_b->GetSource(), str_stream.str()); - diff = true; + if (package_a_iter != table_a.packages.end()) { + ++package_a_iter; + } + if (package_b_iter != table_b.packages.end()) { + ++package_b_iter; } } + return diff; } diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp index faabe461648a..e4d0f3b6bd23 100644 --- a/tools/aapt2/cmd/Link.cpp +++ b/tools/aapt2/cmd/Link.cpp @@ -25,6 +25,7 @@ #include <vector> #include "android-base/errors.h" +#include "android-base/expected.h" #include "android-base/file.h" #include "android-base/stringprintf.h" #include "androidfw/Locale.h" @@ -74,10 +75,27 @@ using ::aapt::io::FileInputStream; using ::android::ConfigDescription; using ::android::StringPiece; +using ::android::base::expected; using ::android::base::StringPrintf; +using ::android::base::unexpected; namespace aapt { +namespace { + +expected<ResourceTablePackage*, const char*> GetStaticLibraryPackage(ResourceTable* table) { + // Resource tables built by aapt2 always contain one package. This is a post condition of + // VerifyNoExternalPackages. + if (table->packages.size() != 1u) { + return unexpected("static library contains more than one package"); + } + return table->packages.back().get(); +} + +} // namespace + +constexpr uint8_t kAndroidPackageId = 0x01; + class LinkContext : public IAaptContext { public: explicit LinkContext(IDiagnostics* diagnostics) @@ -444,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 {}; } @@ -631,13 +649,18 @@ bool ResourceFileFlattener::Flatten(ResourceTable* table, IArchiveWriter* archiv const ResourceFile& file = doc->file; dst_path = ResourceUtils::BuildResourceFileName(file, context_->GetNameMangler()); - std::unique_ptr<FileReference> file_ref = + auto file_ref = util::make_unique<FileReference>(table->string_pool.MakeRef(dst_path)); file_ref->SetSource(doc->file.source); + // Update the output format of this XML file. file_ref->type = XmlFileTypeForOutputFormat(options_.output_format); - if (!table->AddResourceMangled(file.name, file.config, {}, std::move(file_ref), - context_->GetDiagnostics())) { + bool result = table->AddResource(NewResourceBuilder(file.name) + .SetValue(std::move(file_ref), file.config) + .SetAllowMangled(true) + .Build(), + context_->GetDiagnostics()); + if (!result) { return false; } } @@ -840,18 +863,15 @@ class Linker { ResourceTable* table = static_apk->GetResourceTable(); // If we are using --no-static-lib-packages, we need to rename the package of this table to - // our compilation package. - if (options_.no_static_lib_packages) { - // Since package names can differ, and multiple packages can exist in a ResourceTable, - // we place the requirement that all static libraries are built with the package - // ID 0x7f. So if one is not found, this is an error. - if (ResourceTablePackage* pkg = table->FindPackageById(kAppPackageId)) { - pkg->name = context_->GetCompilationPackage(); - } else { - context_->GetDiagnostics()->Error(DiagMessage(path) - << "no package with ID 0x7f found in static library"); + // our compilation package so the symbol package name does not get mangled into the entry + // name. + if (options_.no_static_lib_packages && !table->packages.empty()) { + auto lib_package_result = GetStaticLibraryPackage(table); + if (!lib_package_result.has_value()) { + context_->GetDiagnostics()->Error(DiagMessage(path) << lib_package_result.error()); return false; } + lib_package_result.value()->name = context_->GetCompilationPackage(); } context_->GetExternalSymbols()->AppendSource( @@ -980,8 +1000,7 @@ class Linker { // stripped, or there is an error and false is returned. bool VerifyNoExternalPackages() { auto is_ext_package_func = [&](const std::unique_ptr<ResourceTablePackage>& pkg) -> bool { - return context_->GetCompilationPackage() != pkg->name || !pkg->id || - pkg->id.value() != context_->GetPackageId(); + return context_->GetCompilationPackage() != pkg->name; }; bool error = false; @@ -1025,19 +1044,11 @@ class Linker { bool VerifyNoIdsSet() { for (const auto& package : final_table_.packages) { for (const auto& type : package->types) { - if (type->id) { - context_->GetDiagnostics()->Error(DiagMessage() << "type " << type->type << " has ID " - << StringPrintf("%02x", type->id.value()) - << " assigned"); - return false; - } - for (const auto& entry : type->entries) { if (entry->id) { ResourceNameRef res_name(package->name, type->type, entry->name); - context_->GetDiagnostics()->Error( - DiagMessage() << "entry " << res_name << " has ID " - << StringPrintf("%02x", entry->id.value()) << " assigned"); + context_->GetDiagnostics()->Error(DiagMessage() << "resource " << res_name << " has ID " + << entry->id.value() << " assigned"); return false; } } @@ -1311,12 +1322,17 @@ class Linker { } ResourceTable* table = apk->GetResourceTable(); - ResourceTablePackage* pkg = table->FindPackageById(kAppPackageId); - if (!pkg) { - context_->GetDiagnostics()->Error(DiagMessage(input) << "static library has no package"); + if (table->packages.empty()) { + return true; + } + + auto lib_package_result = GetStaticLibraryPackage(table); + if (!lib_package_result.has_value()) { + context_->GetDiagnostics()->Error(DiagMessage(input) << lib_package_result.error()); return false; } + ResourceTablePackage* pkg = lib_package_result.value(); bool result; if (options_.no_static_lib_packages) { // Merge all resources as if they were in the compilation package. This is the old behavior @@ -1363,11 +1379,11 @@ class Linker { res_name = mangled_name.value(); } - std::unique_ptr<Id> id = util::make_unique<Id>(); + auto id = util::make_unique<Id>(); id->SetSource(source.WithLine(exported_symbol.line)); - bool result = - final_table_.AddResourceMangled(res_name, ConfigDescription::DefaultConfig(), - std::string(), std::move(id), context_->GetDiagnostics()); + bool result = final_table_.AddResource( + NewResourceBuilder(res_name).SetValue(std::move(id)).SetAllowMangled(true).Build(), + context_->GetDiagnostics()); if (!result) { return false; } @@ -1389,7 +1405,7 @@ class Linker { return MergeExportedSymbols(compiled_file.source, compiled_file.exported_symbols); } - // Takes a path to load as a ZIP file and merges the files within into the master ResourceTable. + // Takes a path to load as a ZIP file and merges the files within into the main ResourceTable. // If override is true, conflicting resources are allowed to override each other, in order of last // seen. // An io::IFileCollection is created from the ZIP file and added to the set of @@ -1420,7 +1436,7 @@ class Linker { return !error; } - // Takes a path to load and merge into the master ResourceTable. If override is true, + // Takes a path to load and merge into the main ResourceTable. If override is true, // conflicting resources are allowed to override each other, in order of last seen. // If the file path ends with .flata, .jar, .jack, or .zip the file is treated // as ZIP archive and the files within are merged individually. @@ -1437,7 +1453,7 @@ class Linker { return MergeFile(file, override); } - // Takes an AAPT Container file (.apc/.flat) to load and merge into the master ResourceTable. + // Takes an AAPT Container file (.apc/.flat) to load and merge into the main ResourceTable. // If override is true, conflicting resources are allowed to override each other, in order of last // seen. // All other file types are ignored. This is because these files could be coming from a zip, @@ -1566,32 +1582,37 @@ class Linker { return true; } + ResourceEntry* ResolveTableEntry(LinkContext* context, ResourceTable* table, + Reference* reference) { + if (!reference || !reference->name) { + return nullptr; + } + auto name_ref = ResourceNameRef(reference->name.value()); + if (name_ref.package.empty()) { + name_ref.package = context->GetCompilationPackage(); + } + const auto search_result = table->FindResource(name_ref); + if (!search_result) { + return nullptr; + } + return search_result.value().entry; + } + void AliasAdaptiveIcon(xml::XmlResource* manifest, ResourceTable* table) { - xml::Element* application = manifest->root->FindChild("", "application"); + const xml::Element* application = manifest->root->FindChild("", "application"); if (!application) { return; } - xml::Attribute* icon = application->FindAttribute(xml::kSchemaAndroid, "icon"); - xml::Attribute* round_icon = application->FindAttribute(xml::kSchemaAndroid, "roundIcon"); + const xml::Attribute* icon = application->FindAttribute(xml::kSchemaAndroid, "icon"); + const xml::Attribute* round_icon = application->FindAttribute(xml::kSchemaAndroid, "roundIcon"); if (!icon || !round_icon) { return; } // Find the icon resource defined within the application. - auto icon_reference = ValueCast<Reference>(icon->compiled_value.get()); - if (!icon_reference || !icon_reference->name) { - return; - } - auto package = table->FindPackageById(icon_reference->id.value().package_id()); - if (!package) { - return; - } - auto type = package->FindType(icon_reference->name.value().type); - if (!type) { - return; - } - auto icon_entry = type->FindEntry(icon_reference->name.value().entry); + const auto icon_reference = ValueCast<Reference>(icon->compiled_value.get()); + const auto icon_entry = ResolveTableEntry(context_, table, icon_reference); if (!icon_entry) { return; } @@ -1607,19 +1628,8 @@ class Linker { } // Find the roundIcon resource defined within the application. - auto round_icon_reference = ValueCast<Reference>(round_icon->compiled_value.get()); - if (!round_icon_reference || !round_icon_reference->name) { - return; - } - package = table->FindPackageById(round_icon_reference->id.value().package_id()); - if (!package) { - return; - } - type = package->FindType(round_icon_reference->name.value().type); - if (!type) { - return; - } - auto round_icon_entry = type->FindEntry(round_icon_reference->name.value().entry); + const auto round_icon_reference = ValueCast<Reference>(round_icon->compiled_value.get()); + const auto round_icon_entry = ResolveTableEntry(context_, table, round_icon_reference); if (!round_icon_entry) { return; } @@ -1646,13 +1656,64 @@ class Linker { << " with config \"" << config_value->config << "\" for round icon compatibility"); - auto value = icon_reference->Clone(&table->string_pool); - auto round_config_value = round_icon_entry->FindOrCreateValue( - config_value->config, config_value->product); - round_config_value->value.reset(value); + CloningValueTransformer cloner(&table->string_pool); + auto value = icon_reference->Transform(cloner); + auto round_config_value = + round_icon_entry->FindOrCreateValue(config_value->config, config_value->product); + round_config_value->value = std::move(value); } } + bool VerifySharedUserId(xml::XmlResource* manifest, ResourceTable* table) { + const xml::Element* manifest_el = xml::FindRootElement(manifest->root.get()); + if (manifest_el == nullptr) { + return true; + } + if (!manifest_el->namespace_uri.empty() || manifest_el->name != "manifest") { + return true; + } + const xml::Attribute* attr = manifest_el->FindAttribute(xml::kSchemaAndroid, "sharedUserId"); + if (!attr) { + return true; + } + const auto validate = [&](const std::string& shared_user_id) -> bool { + if (util::IsAndroidSharedUserId(context_->GetCompilationPackage(), shared_user_id)) { + return true; + } + DiagMessage error_msg(manifest_el->line_number); + error_msg << "attribute 'sharedUserId' in <manifest> tag is not a valid shared user id: '" + << shared_user_id << "'"; + if (options_.manifest_fixer_options.warn_validation) { + // Treat the error only as a warning. + context_->GetDiagnostics()->Warn(error_msg); + return true; + } + context_->GetDiagnostics()->Error(error_msg); + return false; + }; + // If attr->compiled_value is not null, check if it is a ref + if (attr->compiled_value) { + const auto ref = ValueCast<Reference>(attr->compiled_value.get()); + if (ref == nullptr) { + return true; + } + const auto shared_user_id_entry = ResolveTableEntry(context_, table, ref); + if (!shared_user_id_entry) { + return true; + } + for (const auto& value : shared_user_id_entry->values) { + const auto str_value = ValueCast<String>(value->value.get()); + if (str_value != nullptr && !validate(*str_value->value)) { + return false; + } + } + return true; + } + + // Fallback to checking the raw value + return validate(attr->value); + } + // Writes the AndroidManifest, ResourceTable, and all XML files referenced by the ResourceTable // to the IArchiveWriter. bool WriteApk(IArchiveWriter* writer, proguard::KeepSet* keep_set, xml::XmlResource* manifest, @@ -1674,6 +1735,11 @@ class Linker { // See (b/34829129) AliasAdaptiveIcon(manifest, table); + // Verify the shared user id here to handle the case of reference value. + if (!VerifySharedUserId(manifest, table)) { + return false; + } + ResourceFileFlattenerOptions file_flattener_options; file_flattener_options.keep_raw_values = keep_raw_values; file_flattener_options.do_not_compress_anything = options_.do_not_compress_anything; @@ -1708,15 +1774,13 @@ class Linker { context_->GetPackageId() != kAppPackageId && context_->GetPackageId() != kFrameworkPackageId) || (!options_.allow_reserved_package_id && context_->GetPackageId() > kAppPackageId); - if (isSplitPackage && - included_feature_base_ == make_value(context_->GetCompilationPackage())) { + if (isSplitPackage && included_feature_base_ == context_->GetCompilationPackage()) { // The base APK is included, and this is a feature split. If the base package is // the same as this package, then we are building an old style Android Instant Apps feature // split and must apply this workaround to avoid requiring namespaces support. - package_to_rewrite = table->FindPackage(context_->GetCompilationPackage()); - if (package_to_rewrite != nullptr) { - CHECK_EQ(1u, table->packages.size()) << "can't change name of package when > 1 package"; - + if (!table->packages.empty() && + table->packages.back()->name == context_->GetCompilationPackage()) { + package_to_rewrite = table->packages.back().get(); std::string new_package_name = StringPrintf("%s.%s", package_to_rewrite->name.c_str(), app_info_.split_name.value_or_default("feature").c_str()); @@ -1735,9 +1799,12 @@ class Linker { if (package_to_rewrite != nullptr) { // Change the name back. package_to_rewrite->name = context_->GetCompilationPackage(); - if (package_to_rewrite->id) { - table->included_packages_.erase(package_to_rewrite->id.value()); - } + + // TableFlattener creates an `included_packages_` mapping entry for each package with a + // non-standard package id (not 0x01 or 0x7f). Since this is a feature split and not a shared + // library, do not include a mapping from the feature package name to the feature package id + // in the feature's dynamic reference table. + table->included_packages_.erase(context_->GetPackageId()); } if (!success) { @@ -1797,7 +1864,7 @@ class Linker { // Override the package ID when it is "android". if (context_->GetCompilationPackage() == "android") { - context_->SetPackageId(0x01); + context_->SetPackageId(kAndroidPackageId); // Verify we're building a regular app. if (context_->GetPackageType() != PackageType::kApp) { @@ -1854,7 +1921,8 @@ class Linker { if (context_->GetPackageType() != PackageType::kStaticLib) { PrivateAttributeMover mover; - if (!mover.Consume(context_, &final_table_)) { + if (context_->GetPackageId() == kAndroidPackageId && + !mover.Consume(context_, &final_table_)) { context_->GetDiagnostics()->Error(DiagMessage() << "failed moving private attributes"); return 1; } @@ -1873,8 +1941,7 @@ class Linker { for (auto& entry : type->entries) { ResourceName name(package->name, type->type, entry->name); // The IDs are guaranteed to exist. - options_.stable_id_map[std::move(name)] = - ResourceId(package->id.value(), type->id.value(), entry->id.value()); + options_.stable_id_map[std::move(name)] = entry->id.value(); } } } @@ -2045,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"); @@ -2076,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)) { @@ -2211,6 +2278,16 @@ int LinkCommand::Action(const std::vector<std::string>& args) { return 1; } + if (shared_lib_ && options_.private_symbols) { + // If a shared library styleable in a public R.java uses a private attribute, attempting to + // reference the private attribute within the styleable array will cause a link error because + // the private attribute will not be emitted in the public R.java. + context.GetDiagnostics()->Error(DiagMessage() + << "--shared-lib cannot currently be used in combination with" + << " --private-symbols"); + return 1; + } + if (options_.merge_only && !static_lib_) { context.GetDiagnostics()->Error( DiagMessage() << "the --merge-only flag can be only used when building a static library"); diff --git a/tools/aapt2/cmd/Link.h b/tools/aapt2/cmd/Link.h index 852b1244cd6e..768b4b2c7bfd 100644 --- a/tools/aapt2/cmd/Link.h +++ b/tools/aapt2/cmd/Link.h @@ -190,8 +190,11 @@ class LinkCommand : public Command { AddOptionalFlag("--version-name", "Version name to inject into the AndroidManifest.xml if none is present.", &options_.manifest_fixer_options.version_name_default); + AddOptionalFlag("--revision-code", + "Revision code (integer) to inject into the AndroidManifest.xml if none is\n" + "present.", &options_.manifest_fixer_options.revision_code_default); AddOptionalSwitch("--replace-version", - "If --version-code and/or --version-name are specified, these\n" + "If --version-code, --version-name, and/or --revision-code are specified, these\n" "values will replace any value already in the manifest. By\n" "default, nothing is changed if the manifest already defines\n" "these attributes.", diff --git a/tools/aapt2/cmd/Link_test.cpp b/tools/aapt2/cmd/Link_test.cpp index 062dd8eac975..430c184ef87d 100644 --- a/tools/aapt2/cmd/Link_test.cpp +++ b/tools/aapt2/cmd/Link_test.cpp @@ -14,14 +14,19 @@ * limitations under the License. */ -#include "AppInfo.h" #include "Link.h" +#include <android-base/file.h> + +#include "AppInfo.h" #include "LoadedApk.h" #include "test/Test.h" using testing::Eq; +using testing::HasSubstr; +using testing::IsNull; using testing::Ne; +using testing::NotNull; namespace aapt { @@ -44,6 +49,8 @@ TEST_F(LinkTest, RemoveRawXmlStrings) { // Load the binary xml tree android::ResXMLTree tree; std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(out_apk, &diag); + ASSERT_THAT(apk, Ne(nullptr)); + std::unique_ptr<io::IData> data = OpenFileAsData(apk.get(), "res/xml/test.xml"); ASSERT_THAT(data, Ne(nullptr)); AssertLoadXml(apk.get(), data.get(), &tree); @@ -70,6 +77,8 @@ TEST_F(LinkTest, KeepRawXmlStrings) { // Load the binary xml tree android::ResXMLTree tree; std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(out_apk, &diag); + ASSERT_THAT(apk, Ne(nullptr)); + std::unique_ptr<io::IData> data = OpenFileAsData(apk.get(), "res/xml/test.xml"); ASSERT_THAT(data, Ne(nullptr)); AssertLoadXml(apk.get(), data.get(), &tree); @@ -205,6 +214,8 @@ TEST_F(LinkTest, OverlayStyles) { ASSERT_TRUE(Link(link_args, compiled_files_dir, &diag)); std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(out_apk, &diag); + ASSERT_THAT(apk, Ne(nullptr)); + const Style* actual_style = test::GetValue<Style>( apk->GetResourceTable(), std::string(kDefaultPackageName) + ":style/MyStyle"); ASSERT_NE(actual_style, nullptr); @@ -247,6 +258,8 @@ TEST_F(LinkTest, OverrideStylesInsteadOfOverlaying) { ASSERT_TRUE(Link(link_args, compiled_files_dir, &diag)); std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(out_apk, &diag); + ASSERT_THAT(apk, Ne(nullptr)); + const Style* actual_style = test::GetValue<Style>( apk->GetResourceTable(), std::string(kDefaultPackageName) + ":style/MyStyle"); ASSERT_NE(actual_style, nullptr); @@ -317,4 +330,457 @@ TEST_F(LinkTest, AppInfoWithUsesSplit) { ASSERT_TRUE(Link(link_args, feature2_files_dir, &diag)); } +TEST_F(LinkTest, SharedLibraryAttributeRJava) { + StdErrDiagnostics diag; + const std::string lib_values = + R"(<resources> + <attr name="foo"/> + <public type="attr" name="foo" id="0x00010001"/> + <declare-styleable name="LibraryStyleable"> + <attr name="foo" /> + </declare-styleable> + </resources>)"; + + const std::string client_values = + R"(<resources> + <attr name="bar" /> + <declare-styleable name="ClientStyleable"> + <attr name="com.example.lib:foo" /> + <attr name="bar" /> + </declare-styleable> + </resources>)"; + + // Build a library with a public attribute + const std::string lib_res = GetTestPath("library-res"); + ASSERT_TRUE(CompileFile(GetTestPath("res/values/values.xml"), lib_values, lib_res, &diag)); + + const std::string lib_apk = GetTestPath("library.apk"); + const std::string lib_java = GetTestPath("library_java"); + // clang-format off + auto lib_manifest = ManifestBuilder(this) + .SetPackageName("com.example.lib") + .Build(); + + auto lib_link_args = LinkCommandBuilder(this) + .SetManifestFile(lib_manifest) + .AddFlag("--shared-lib") + .AddParameter("--java", lib_java) + .AddCompiledResDir(lib_res, &diag) + .Build(lib_apk); + // clang-format on + ASSERT_TRUE(Link(lib_link_args, &diag)); + + const std::string lib_r_java = lib_java + "/com/example/lib/R.java"; + std::string lib_r_contents; + ASSERT_TRUE(android::base::ReadFileToString(lib_r_java, &lib_r_contents)); + EXPECT_THAT(lib_r_contents, HasSubstr(" public static int foo=0x00010001;")); + EXPECT_THAT(lib_r_contents, HasSubstr(" com.example.lib.R.attr.foo")); + + // Build a client that uses the library attribute in a declare-styleable + const std::string client_res = GetTestPath("client-res"); + ASSERT_TRUE(CompileFile(GetTestPath("res/values/values.xml"), client_values, client_res, &diag)); + + const std::string client_apk = GetTestPath("client.apk"); + const std::string client_java = GetTestPath("client_java"); + // clang-format off + auto client_manifest = ManifestBuilder(this) + .SetPackageName("com.example.client") + .Build(); + + auto client_link_args = LinkCommandBuilder(this) + .SetManifestFile(client_manifest) + .AddParameter("--java", client_java) + .AddParameter("-I", lib_apk) + .AddCompiledResDir(client_res, &diag) + .Build(client_apk); + // clang-format on + ASSERT_TRUE(Link(client_link_args, &diag)); + + const std::string client_r_java = client_java + "/com/example/client/R.java"; + std::string client_r_contents; + ASSERT_TRUE(android::base::ReadFileToString(client_r_java, &client_r_contents)); + EXPECT_THAT(client_r_contents, HasSubstr(" com.example.lib.R.attr.foo, 0x7f010000")); +} + +struct SourceXML { + std::string res_file_path; + std::string file_contents; +}; + +static void BuildApk(const std::vector<SourceXML>& source_files, const std::string& apk_path, + LinkCommandBuilder&& link_args, CommandTestFixture* fixture, + IDiagnostics* diag) { + TemporaryDir res_dir; + TemporaryDir compiled_res_dir; + for (auto& source_file : source_files) { + ASSERT_TRUE(fixture->CompileFile(res_dir.path + source_file.res_file_path, + source_file.file_contents, compiled_res_dir.path, diag)); + } + ASSERT_TRUE(fixture->Link( + link_args.AddCompiledResDir(compiled_res_dir.path, diag).Build(apk_path), diag)); +} + +static void BuildSDK(const std::vector<SourceXML>& source_files, const std::string& apk_path, + const std::string& java_root_path, CommandTestFixture* fixture, + IDiagnostics* diag) { + auto android_manifest = ManifestBuilder(fixture).SetPackageName("android").Build(); + + auto android_link_args = LinkCommandBuilder(fixture) + .SetManifestFile(android_manifest) + .AddParameter("--private-symbols", "com.android.internal") + .AddParameter("--java", java_root_path); + + BuildApk(source_files, apk_path, std::move(android_link_args), fixture, diag); +} + +static void BuildNonFinalizedSDK(const std::string& apk_path, const std::string& java_path, + CommandTestFixture* fixture, IDiagnostics* diag) { + const std::string android_values = + R"(<resources> + <public type="attr" name="finalized_res" id="0x01010001"/> + + <!-- S staged attributes (support staged resources in the same type id) --> + <staging-public-group type="attr" first-id="0x01010050"> + <public name="staged_s_res" /> + </staging-public-group> + + <staging-public-group type="string" first-id="0x01fd0080"> + <public name="staged_s_string" /> + </staging-public-group> + + <!-- SV2 staged attributes (support staged resources in a separate type id) --> + <staging-public-group type="attr" first-id="0x01ff0049"> + <public name="staged_s2_res" /> + </staging-public-group> + + <!-- T staged attributes (support staged resources in multiple separate type ids) --> + <staging-public-group type="attr" first-id="0x01fe0063"> + <public name="staged_t_res" /> + </staging-public-group> + + <attr name="finalized_res" /> + <attr name="staged_s_res" /> + <attr name="staged_s2_res" /> + <attr name="staged_t_res" /> + <string name="staged_s_string">Hello</string> + </resources>)"; + + SourceXML source_xml{.res_file_path = "/res/values/values.xml", .file_contents = android_values}; + BuildSDK({source_xml}, apk_path, java_path, fixture, diag); +} + +static void BuildFinalizedSDK(const std::string& apk_path, const std::string& java_path, + CommandTestFixture* fixture, IDiagnostics* diag) { + const std::string android_values = + R"(<resources> + <public type="attr" name="finalized_res" id="0x01010001"/> + <public type="attr" name="staged_s_res" id="0x01010002"/> + <public type="attr" name="staged_s2_res" id="0x01010003"/> + <public type="string" name="staged_s_string" id="0x01020000"/> + + <!-- S staged attributes (support staged resources in the same type id) --> + <staging-public-group-final type="attr" first-id="0x01010050"> + <public name="staged_s_res" /> + </staging-public-group-final> + + <staging-public-group-final type="string" first-id="0x01fd0080"> + <public name="staged_s_string" /> + </staging-public-group-final> + + <!-- SV2 staged attributes (support staged resources in a separate type id) --> + <staging-public-group-final type="attr" first-id="0x01ff0049"> + <public name="staged_s2_res" /> + </staging-public-group-final> + + <!-- T staged attributes (support staged resources in multiple separate type ids) --> + <staging-public-group type="attr" first-id="0x01fe0063"> + <public name="staged_t_res" /> + </staging-public-group> + + <attr name="finalized_res" /> + <attr name="staged_s_res" /> + <attr name="staged_s2_res" /> + <attr name="staged_t_res" /> + <string name="staged_s_string">Hello</string> + </resources>)"; + + SourceXML source_xml{.res_file_path = "/res/values/values.xml", .file_contents = android_values}; + BuildSDK({source_xml}, apk_path, java_path, fixture, diag); +} + +static void BuildAppAgainstSDK(const std::string& apk_path, const std::string& java_path, + const std::string& sdk_path, CommandTestFixture* fixture, + IDiagnostics* diag) { + const std::string app_values = + R"(<resources xmlns:android="http://schemas.android.com/apk/res/android"> + <attr name="bar" /> + <style name="MyStyle"> + <item name="android:staged_s_res">@android:string/staged_s_string</item> + </style> + <declare-styleable name="ClientStyleable"> + <attr name="android:finalized_res" /> + <attr name="android:staged_s_res" /> + <attr name="bar" /> + </declare-styleable> + <public name="MyStyle" type="style" id="0x7f020000" /> + </resources>)"; + + SourceXML source_xml{.res_file_path = "/res/values/values.xml", .file_contents = app_values}; + + auto app_manifest = ManifestBuilder(fixture).SetPackageName("com.example.app").Build(); + + auto app_link_args = LinkCommandBuilder(fixture) + .SetManifestFile(app_manifest) + .AddParameter("--java", java_path) + .AddParameter("-I", sdk_path); + + BuildApk({source_xml}, apk_path, std::move(app_link_args), fixture, diag); +} + +TEST_F(LinkTest, StagedAndroidApi) { + StdErrDiagnostics diag; + const std::string android_apk = GetTestPath("android.apk"); + const std::string android_java = GetTestPath("android-java"); + BuildNonFinalizedSDK(android_apk, android_java, this, &diag); + + const std::string android_r_java = android_java + "/android/R.java"; + std::string android_r_contents; + ASSERT_TRUE(android::base::ReadFileToString(android_r_java, &android_r_contents)); + EXPECT_THAT(android_r_contents, HasSubstr("public static final int finalized_res=0x01010001;")); + EXPECT_THAT( + android_r_contents, + HasSubstr("public static final int staged_s_res; static { staged_s_res=0x01010050; }")); + EXPECT_THAT( + android_r_contents, + HasSubstr("public static final int staged_s_string; static { staged_s_string=0x01fd0080; }")); + EXPECT_THAT( + android_r_contents, + HasSubstr("public static final int staged_s2_res; static { staged_s2_res=0x01ff0049; }")); + EXPECT_THAT( + android_r_contents, + HasSubstr("public static final int staged_t_res; static { staged_t_res=0x01fe0063; }")); + + const std::string app_apk = GetTestPath("app.apk"); + const std::string app_java = GetTestPath("app-java"); + BuildAppAgainstSDK(app_apk, app_java, android_apk, this, &diag); + + const std::string client_r_java = app_java + "/com/example/app/R.java"; + std::string client_r_contents; + ASSERT_TRUE(android::base::ReadFileToString(client_r_java, &client_r_contents)); + EXPECT_THAT(client_r_contents, HasSubstr(" 0x01010001, android.R.attr.staged_s_res, 0x7f010000")); + + // Test that the resource ids of staged and non-staged resource can be retrieved + android::AssetManager2 am; + auto android_asset = android::ApkAssets::Load(android_apk); + ASSERT_THAT(android_asset, NotNull()); + ASSERT_TRUE(am.SetApkAssets({android_asset.get()})); + + auto result = am.GetResourceId("android:attr/finalized_res"); + ASSERT_TRUE(result.has_value()); + EXPECT_THAT(*result, Eq(0x01010001)); + + result = am.GetResourceId("android:attr/staged_s_res"); + ASSERT_TRUE(result.has_value()); + EXPECT_THAT(*result, Eq(0x01010050)); + + result = am.GetResourceId("android:string/staged_s_string"); + ASSERT_TRUE(result.has_value()); + EXPECT_THAT(*result, Eq(0x01fd0080)); + + result = am.GetResourceId("android:attr/staged_s2_res"); + ASSERT_TRUE(result.has_value()); + EXPECT_THAT(*result, Eq(0x01ff0049)); + + result = am.GetResourceId("android:attr/staged_t_res"); + ASSERT_TRUE(result.has_value()); + EXPECT_THAT(*result, Eq(0x01fe0063)); +} + +TEST_F(LinkTest, FinalizedAndroidApi) { + StdErrDiagnostics diag; + const std::string android_apk = GetTestPath("android.apk"); + const std::string android_java = GetTestPath("android-java"); + BuildFinalizedSDK(android_apk, android_java, this, &diag); + + const std::string android_r_java = android_java + "/android/R.java"; + std::string android_r_contents; + ASSERT_TRUE(android::base::ReadFileToString(android_r_java, &android_r_contents)); + EXPECT_THAT(android_r_contents, HasSubstr("public static final int finalized_res=0x01010001;")); + EXPECT_THAT(android_r_contents, HasSubstr("public static final int staged_s_res=0x01010002;")); + EXPECT_THAT(android_r_contents, HasSubstr("public static final int staged_s_string=0x01020000;")); + EXPECT_THAT(android_r_contents, HasSubstr("public static final int staged_s2_res=0x01010003;")); + EXPECT_THAT( + android_r_contents, + HasSubstr("public static final int staged_t_res; static { staged_t_res=0x01fe0063; }")); + ; + + // Build an application against the non-finalized SDK and then load it into an AssetManager with + // the finalized SDK. + const std::string non_finalized_android_apk = GetTestPath("non-finalized-android.apk"); + const std::string non_finalized_android_java = GetTestPath("non-finalized-android-java"); + BuildNonFinalizedSDK(non_finalized_android_apk, non_finalized_android_java, this, &diag); + + const std::string app_apk = GetTestPath("app.apk"); + const std::string app_java = GetTestPath("app-java"); + BuildAppAgainstSDK(app_apk, app_java, non_finalized_android_apk, this, &diag); + + android::AssetManager2 am; + auto android_asset = android::ApkAssets::Load(android_apk); + auto app_against_non_final = android::ApkAssets::Load(app_apk); + ASSERT_THAT(android_asset, NotNull()); + ASSERT_THAT(app_against_non_final, NotNull()); + ASSERT_TRUE(am.SetApkAssets({android_asset.get(), app_against_non_final.get()})); + + auto result = am.GetResourceId("android:attr/finalized_res"); + ASSERT_TRUE(result.has_value()); + EXPECT_THAT(*result, Eq(0x01010001)); + + result = am.GetResourceId("android:attr/staged_s_res"); + ASSERT_TRUE(result.has_value()); + EXPECT_THAT(*result, Eq(0x01010002)); + + result = am.GetResourceId("android:string/staged_s_string"); + ASSERT_TRUE(result.has_value()); + EXPECT_THAT(*result, Eq(0x01020000)); + + result = am.GetResourceId("android:attr/staged_s2_res"); + ASSERT_TRUE(result.has_value()); + EXPECT_THAT(*result, Eq(0x01010003)); + + { + auto style = am.GetBag(0x7f020000); + ASSERT_TRUE(style.has_value()); + + auto& entry = (*style)->entries[0]; + EXPECT_THAT(entry.key, Eq(0x01010002)); + EXPECT_THAT(entry.value.dataType, Eq(android::Res_value::TYPE_REFERENCE)); + EXPECT_THAT(entry.value.data, Eq(0x01020000)); + } + + // Re-compile the application against the finalized SDK and then load it into an AssetManager with + // the finalized SDK. + const std::string app_apk_respin = GetTestPath("app-respin.apk"); + const std::string app_java_respin = GetTestPath("app-respin-java"); + BuildAppAgainstSDK(app_apk_respin, app_java_respin, android_apk, this, &diag); + + auto app_against_final = android::ApkAssets::Load(app_apk_respin); + ASSERT_THAT(app_against_final, NotNull()); + ASSERT_TRUE(am.SetApkAssets({android_asset.get(), app_against_final.get()})); + + { + auto style = am.GetBag(0x7f020000); + ASSERT_TRUE(style.has_value()); + + auto& entry = (*style)->entries[0]; + EXPECT_THAT(entry.key, Eq(0x01010002)); + EXPECT_THAT(entry.value.dataType, Eq(android::Res_value::TYPE_REFERENCE)); + EXPECT_THAT(entry.value.data, Eq(0x01020000)); + } +} + +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 diff --git a/tools/aapt2/cmd/Optimize.cpp b/tools/aapt2/cmd/Optimize.cpp index e36668e5a043..5b18a3789d76 100644 --- a/tools/aapt2/cmd/Optimize.cpp +++ b/tools/aapt2/cmd/Optimize.cpp @@ -132,8 +132,8 @@ class Optimizer { if (context_->IsVerbose()) { context_->GetDiagnostics()->Note(DiagMessage() << "Optimizing APK..."); } - if (!options_.resources_blacklist.empty()) { - ResourceFilter filter(options_.resources_blacklist); + if (!options_.resources_exclude_list.empty()) { + ResourceFilter filter(options_.resources_exclude_list); if (!filter.Consume(context_, apk->GetResourceTable())) { context_->GetDiagnostics()->Error(DiagMessage() << "failed filtering resources"); return 1; @@ -328,7 +328,7 @@ bool ParseConfig(const std::string& content, IAaptContext* context, OptimizeOpti } for (StringPiece directive : util::Tokenize(directives, ',')) { if (directive == "remove") { - options->resources_blacklist.insert(resource_name.ToResourceName()); + options->resources_exclude_list.insert(resource_name.ToResourceName()); } else if (directive == "no_collapse" || directive == "no_obfuscate") { options->table_flattener_options.name_collapse_exemptions.insert( resource_name.ToResourceName()); diff --git a/tools/aapt2/cmd/Optimize.h b/tools/aapt2/cmd/Optimize.h index 5070ccc8afbf..3afc46b04af6 100644 --- a/tools/aapt2/cmd/Optimize.h +++ b/tools/aapt2/cmd/Optimize.h @@ -36,8 +36,8 @@ struct OptimizeOptions { // Details of the app extracted from the AndroidManifest.xml AppInfo app_info; - // Blacklist of unused resources that should be removed from the apk. - std::unordered_set<ResourceName> resources_blacklist; + // Exclude list of unused resources that should be removed from the apk. + std::unordered_set<ResourceName> resources_exclude_list; // Split APK options. TableSplitterOptions table_splitter_options; diff --git a/tools/aapt2/compile/IdAssigner.cpp b/tools/aapt2/compile/IdAssigner.cpp index 17c22c5aac6b..339b8af5d536 100644 --- a/tools/aapt2/compile/IdAssigner.cpp +++ b/tools/aapt2/compile/IdAssigner.cpp @@ -17,76 +17,140 @@ #include "compile/IdAssigner.h" #include <map> +#include <unordered_map> +#include "android-base/expected.h" #include "android-base/logging.h" #include "ResourceTable.h" #include "process/IResourceTableConsumer.h" #include "util/Util.h" +using android::base::expected; +using android::base::unexpected; + namespace aapt { -/** - * Assigns the intended ID to the ResourceTablePackage, ResourceTableType, and - * ResourceEntry, - * as long as there is no existing ID or the ID is the same. - */ -static bool AssignId(IDiagnostics* diag, const ResourceId& id, - const ResourceName& name, ResourceTablePackage* pkg, - ResourceTableType* type, ResourceEntry* entry) { - if (pkg->id.value() == id.package_id()) { - if (!type->id || type->id.value() == id.type_id()) { - type->id = id.type_id(); - - if (!entry->id || entry->id.value() == id.entry_id()) { - entry->id = id.entry_id(); - return true; - } - } +namespace { +template <typename T> +using Result = expected<T, std::string>; + +template <typename Id, typename Key> +struct NextIdFinder { + explicit NextIdFinder(Id start_id = 0u) : next_id_(start_id){}; + + // Attempts to reserve an identifier for the specified key. + // If the identifier is already reserved by a different key, an error message is returned. + // Reserving identifiers must be completed before `NextId` is called for the first time. + Result<Id> ReserveId(Key key, Id id); + + // Retrieves the next available identifier that has not been reserved. + std::optional<Id> NextId(); + + private: + // Attempts to set `next_id_` to the next available identifier that has not been reserved. + // Returns whether there were any available identifiers. + std::optional<Id> SkipToNextAvailableId(); + + Id next_id_; + bool next_id_called_ = false; + bool exhausted_ = false; + std::map<Id, Key> pre_assigned_ids_; + typename std::map<Id, Key>::iterator next_preassigned_id_; +}; + +struct TypeGroup { + explicit TypeGroup(uint8_t package_id, uint8_t type_id) + : package_id_(package_id), type_id_(type_id){}; + + // Attempts to reserve the resource id for the specified resource name. + // If the id is already reserved by a different name, an error message is returned. + // Reserving identifiers must be completed before `NextId` is called for the first time. + Result<std::monostate> ReserveId(const ResourceName& name, ResourceId id); + + // Retrieves the next available resource id that has not been reserved. + Result<ResourceId> NextId(); + + private: + uint8_t package_id_; + uint8_t type_id_; + NextIdFinder<uint16_t, ResourceName> next_entry_id_; +}; + +struct ResourceTypeKey { + ResourceType type; + uint8_t id; + + bool operator<(const ResourceTypeKey& other) const { + return (type != other.type) ? type < other.type : id < other.id; + } + + bool operator==(const ResourceTypeKey& other) const { + return type == other.type && id == other.id; + } + + bool operator!=(const ResourceTypeKey& other) const { + return !(*this == other); } +}; - const ResourceId existing_id(pkg->id.value(), type->id ? type->id.value() : 0, - entry->id ? entry->id.value() : 0); - diag->Error(DiagMessage() << "can't assign ID " << id << " to resource " - << name << " with conflicting ID " << existing_id); - return false; +::std::ostream& operator<<(::std::ostream& out, const ResourceTypeKey& type) { + return out << type.type; } -bool IdAssigner::Consume(IAaptContext* context, ResourceTable* table) { - std::map<ResourceId, ResourceName> assigned_ids; +struct IdAssignerContext { + IdAssignerContext(std::string package_name, uint8_t package_id) + : package_name_(std::move(package_name)), package_id_(package_id) { + } - for (auto& package : table->packages) { - CHECK(bool(package->id)) << "packages must have manually assigned IDs"; + // Attempts to reserve the resource id for the specified resource name. + // Returns whether the id was reserved successfully. + // Reserving identifiers must be completed before `NextId` is called for the first time. + bool ReserveId(const ResourceName& name, ResourceId id, const Visibility& visibility, + IDiagnostics* diag); + + // Retrieves the next available resource id that has not been reserved. + std::optional<ResourceId> NextId(const ResourceName& name, IDiagnostics* diag); + + private: + std::string package_name_; + uint8_t package_id_; + std::map<ResourceTypeKey, TypeGroup> types_; + std::map<ResourceType, uint8_t> non_staged_type_ids_; + NextIdFinder<uint8_t, ResourceTypeKey> type_id_finder_ = + NextIdFinder<uint8_t, ResourceTypeKey>(1); +}; +} // namespace + +bool IdAssigner::Consume(IAaptContext* context, ResourceTable* table) { + IdAssignerContext assigned_ids(context->GetCompilationPackage(), context->GetPackageId()); + for (auto& package : table->packages) { for (auto& type : package->types) { for (auto& entry : type->entries) { const ResourceName name(package->name, type->type, entry->name); + if (entry->id && !assigned_ids.ReserveId(name, entry->id.value(), entry->visibility, + context->GetDiagnostics())) { + return false; + } + + auto v = entry->visibility; + v.staged_api = true; + if (entry->staged_id && !assigned_ids.ReserveId(name, entry->staged_id.value().id, v, + context->GetDiagnostics())) { + return false; + } if (assigned_id_map_) { // Assign the pre-assigned stable ID meant for this resource. const auto iter = assigned_id_map_->find(name); if (iter != assigned_id_map_->end()) { const ResourceId assigned_id = iter->second; - const bool result = - AssignId(context->GetDiagnostics(), assigned_id, name, - package.get(), type.get(), entry.get()); - if (!result) { + if (!assigned_ids.ReserveId(name, assigned_id, entry->visibility, + context->GetDiagnostics())) { return false; } - } - } - - if (package->id && type->id && entry->id) { - // If the ID is set for this resource, then reserve it. - ResourceId resource_id(package->id.value(), type->id.value(), - entry->id.value()); - auto result = assigned_ids.insert({resource_id, name}); - const ResourceName& existing_name = result.first->second; - if (!result.second) { - context->GetDiagnostics()->Error( - DiagMessage() << "resource " << name << " has same ID " - << resource_id << " as " << existing_name); - return false; + entry->id = assigned_id; } } } @@ -94,125 +158,185 @@ bool IdAssigner::Consume(IAaptContext* context, ResourceTable* table) { } if (assigned_id_map_) { - // Reserve all the IDs mentioned in the stable ID map. That way we won't - // assign - // IDs that were listed in the map if they don't exist in the table. + // Reserve all the IDs mentioned in the stable ID map. That way we won't assig IDs that were + // listed in the map if they don't exist in the table. for (const auto& stable_id_entry : *assigned_id_map_) { const ResourceName& pre_assigned_name = stable_id_entry.first; const ResourceId& pre_assigned_id = stable_id_entry.second; - auto result = assigned_ids.insert({pre_assigned_id, pre_assigned_name}); - const ResourceName& existing_name = result.first->second; - if (!result.second && existing_name != pre_assigned_name) { - context->GetDiagnostics()->Error( - DiagMessage() << "stable ID " << pre_assigned_id << " for resource " - << pre_assigned_name - << " is already taken by resource " << existing_name); + if (!assigned_ids.ReserveId(pre_assigned_name, pre_assigned_id, {}, + context->GetDiagnostics())) { return false; } } } - // Assign any resources without IDs the next available ID. Gaps will be filled - // if possible, + // Assign any resources without IDs the next available ID. Gaps will be filled if possible, // unless those IDs have been reserved. - - const auto assigned_ids_iter_end = assigned_ids.end(); for (auto& package : table->packages) { - CHECK(bool(package->id)) << "packages must have manually assigned IDs"; - - // Build a half filled ResourceId object, which will be used to find the - // closest matching - // reserved ID in the assignedId map. From that point the next available - // type ID can be - // found. - ResourceId resource_id(package->id.value(), 0, 0); - uint8_t next_expected_type_id = 1; - - // Find the closest matching ResourceId that is <= the one with only the - // package set. - auto next_type_iter = assigned_ids.lower_bound(resource_id); for (auto& type : package->types) { - if (!type->id) { - // We need to assign a type ID. Iterate over the reserved IDs until we - // find - // some type ID that is a distance of 2 greater than the last one we've - // seen. - // That means there is an available type ID between these reserved IDs. - while (next_type_iter != assigned_ids_iter_end) { - if (next_type_iter->first.package_id() != package->id.value()) { - break; - } - - const uint8_t type_id = next_type_iter->first.type_id(); - if (type_id > next_expected_type_id) { - // There is a gap in the type IDs, so use the missing one. - type->id = next_expected_type_id++; - break; - } + for (auto& entry : type->entries) { + const ResourceName name(package->name, type->type, entry->name); + if (entry->id) { + continue; + } + auto id = assigned_ids.NextId(name, context->GetDiagnostics()); + if (!id.has_value()) { + return false; + } + entry->id = id.value(); + } + } + } + return true; +} - // Set our expectation to be the next type ID after the reserved one - // we - // just saw. - next_expected_type_id = type_id + 1; +namespace { +template <typename Id, typename Key> +Result<Id> NextIdFinder<Id, Key>::ReserveId(Key key, Id id) { + CHECK(!next_id_called_) << "ReserveId cannot be called after NextId"; + auto assign_result = pre_assigned_ids_.emplace(id, key); + if (!assign_result.second && assign_result.first->second != key) { + std::stringstream error; + error << "ID is already assigned to " << assign_result.first->second; + return unexpected(error.str()); + } + return id; +} - // Move to the next reserved ID. - ++next_type_iter; - } +template <typename Id, typename Key> +std::optional<Id> NextIdFinder<Id, Key>::NextId() { + if (!next_id_called_) { + next_id_called_ = true; + next_preassigned_id_ = pre_assigned_ids_.begin(); + } + return SkipToNextAvailableId(); +} - if (!type->id) { - // We must have hit the end of the reserved IDs and not found a gap. - // That means the next ID is available. - type->id = next_expected_type_id++; - } +template <typename Id, typename Key> +std::optional<Id> NextIdFinder<Id, Key>::SkipToNextAvailableId() { + if (exhausted_) { + return {}; + } + while (next_preassigned_id_ != pre_assigned_ids_.end()) { + if (next_preassigned_id_->first == next_id_) { + if (next_id_ == std::numeric_limits<Id>::max()) { + // The last identifier was reserved so there are no more available identifiers. + exhausted_ = true; + return {}; } + ++next_id_; + ++next_preassigned_id_; + continue; + } + CHECK(next_preassigned_id_->first > next_id_) << "Preassigned IDs are not in sorted order"; + break; + } + if (next_id_ == std::numeric_limits<Id>::max()) { + // There are no more identifiers after this one, but this one is still available so return it. + exhausted_ = true; + } + return next_id_++; +} - resource_id = ResourceId(package->id.value(), type->id.value(), 0); - uint16_t next_expected_entry_id = 0; +Result<std::monostate> TypeGroup::ReserveId(const ResourceName& name, ResourceId id) { + if (type_id_ != id.type_id()) { + // Currently there cannot be multiple type ids for a single type. + std::stringstream error; + error << "type '" << name.type << "' already has ID " << std::hex << (int)type_id_; + return unexpected(error.str()); + } - // Find the closest matching ResourceId that is <= the one with only the - // package - // and type set. - auto next_entry_iter = assigned_ids.lower_bound(resource_id); - for (auto& entry : type->entries) { - if (!entry->id) { - // We need to assign an entry ID. Iterate over the reserved IDs until - // we find - // some entry ID that is a distance of 2 greater than the last one - // we've seen. - // That means there is an available entry ID between these reserved - // IDs. - while (next_entry_iter != assigned_ids_iter_end) { - if (next_entry_iter->first.package_id() != package->id.value() || - next_entry_iter->first.type_id() != type->id.value()) { - break; - } + auto assign_result = next_entry_id_.ReserveId(name, id.entry_id()); + if (!assign_result.has_value()) { + std::stringstream error; + error << "entry " << assign_result.error(); + return unexpected(error.str()); + } + return {}; +} - const uint16_t entry_id = next_entry_iter->first.entry_id(); - if (entry_id > next_expected_entry_id) { - // There is a gap in the entry IDs, so use the missing one. - entry->id = next_expected_entry_id++; - break; - } +Result<ResourceId> TypeGroup::NextId() { + auto entry_id = next_entry_id_.NextId(); + if (!entry_id.has_value()) { + std::stringstream error; + error << "resource type ID has exceeded the maximum number of resource entries (" + << (std::numeric_limits<uint16_t>::max() + 1u) << ")"; + return unexpected(error.str()); + } + return ResourceId(package_id_, type_id_, entry_id.value()); +} - // Set our expectation to be the next type ID after the reserved one - // we - // just saw. - next_expected_entry_id = entry_id + 1; +bool IdAssignerContext::ReserveId(const ResourceName& name, ResourceId id, + const Visibility& visibility, IDiagnostics* diag) { + if (package_id_ != id.package_id()) { + diag->Error(DiagMessage() << "can't assign ID " << id << " to resource " << name + << " because package already has ID " << std::hex + << (int)id.package_id()); + return false; + } - // Move to the next reserved entry ID. - ++next_entry_iter; - } + auto key = ResourceTypeKey{name.type, id.type_id()}; + auto type = types_.find(key); + if (type == types_.end()) { + // The type has not been assigned an id yet. Ensure that the specified id is not being used by + // another type. + auto assign_result = type_id_finder_.ReserveId(key, id.type_id()); + if (!assign_result.has_value()) { + diag->Error(DiagMessage() << "can't assign ID " << id << " to resource " << name + << " because type " << assign_result.error()); + return false; + } + type = types_.emplace(key, TypeGroup(package_id_, id.type_id())).first; + } - if (!entry->id) { - // We must have hit the end of the reserved IDs and not found a gap. - // That means the next ID is available. - entry->id = next_expected_entry_id++; - } - } - } + if (!visibility.staged_api) { + // Ensure that non-staged resources can only exist in one type ID. + auto non_staged_type = non_staged_type_ids_.emplace(name.type, id.type_id()); + if (!non_staged_type.second && non_staged_type.first->second != id.type_id()) { + diag->Error(DiagMessage() << "can't assign ID " << id << " to resource " << name + << " because type already has ID " << std::hex + << (int)id.type_id()); + return false; } } + + auto assign_result = type->second.ReserveId(name, id); + if (!assign_result.has_value()) { + diag->Error(DiagMessage() << "can't assign ID " << id << " to resource " << name << " because " + << assign_result.error()); + return false; + } + return true; } +std::optional<ResourceId> IdAssignerContext::NextId(const ResourceName& name, IDiagnostics* diag) { + // The package name is not known during the compile stage. + // Resources without a package name are considered a part of the app being linked. + CHECK(name.package.empty() || name.package == package_name_); + + // Find the type id for non-staged resources of this type. + auto non_staged_type = non_staged_type_ids_.find(name.type); + if (non_staged_type == non_staged_type_ids_.end()) { + auto next_type_id = type_id_finder_.NextId(); + CHECK(next_type_id.has_value()) << "resource type IDs allocated have exceeded maximum (256)"; + non_staged_type = non_staged_type_ids_.emplace(name.type, *next_type_id).first; + } + + ResourceTypeKey key{name.type, non_staged_type->second}; + auto type = types_.find(key); + if (type == types_.end()) { + type = types_.emplace(key, TypeGroup(package_id_, key.id)).first; + } + + auto assign_result = type->second.NextId(); + if (!assign_result.has_value()) { + diag->Error(DiagMessage() << "can't assign resource ID to resource " << name << " because " + << assign_result.error()); + return {}; + } + return assign_result.value(); +} +} // namespace + } // namespace aapt diff --git a/tools/aapt2/compile/IdAssigner_test.cpp b/tools/aapt2/compile/IdAssigner_test.cpp index 5cff0048c062..663776645990 100644 --- a/tools/aapt2/compile/IdAssigner_test.cpp +++ b/tools/aapt2/compile/IdAssigner_test.cpp @@ -20,42 +20,40 @@ namespace aapt { -::testing::AssertionResult VerifyIds(ResourceTable* table); +struct IdAssignerTests : public ::testing::Test { + void SetUp() override { + context = test::ContextBuilder().SetCompilationPackage("android").SetPackageId(0x01).Build(); + } + std::unique_ptr<IAaptContext> context; +}; -TEST(IdAssignerTest, AssignIds) { - std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .AddSimple("android:attr/foo") - .AddSimple("android:attr/bar") - .AddSimple("android:id/foo") - .SetPackageId("android", 0x01) - .Build(); +::testing::AssertionResult VerifyIds(ResourceTable* table); - std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); +TEST_F(IdAssignerTests, AssignIds) { + auto table = test::ResourceTableBuilder() + .AddSimple("android:attr/foo") + .AddSimple("android:attr/bar") + .AddSimple("android:id/foo") + .Build(); IdAssigner assigner; ASSERT_TRUE(assigner.Consume(context.get(), table.get())); ASSERT_TRUE(VerifyIds(table.get())); } -TEST(IdAssignerTest, AssignIdsWithReservedIds) { - std::unique_ptr<ResourceTable> table = - test::ResourceTableBuilder() - .AddSimple("android:id/foo", ResourceId(0x01010000)) - .AddSimple("android:dimen/two") - .AddSimple("android:integer/three") - .AddSimple("android:string/five") - .AddSimple("android:attr/fun", ResourceId(0x01040000)) - .AddSimple("android:attr/foo", ResourceId(0x01040006)) - .AddSimple("android:attr/bar") - .AddSimple("android:attr/baz") - .AddSimple("app:id/biz") - .SetPackageId("android", 0x01) - .SetPackageId("app", 0x7f) - .Build(); - - std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); - IdAssigner assigner; +TEST_F(IdAssignerTests, AssignIdsWithReservedIds) { + auto table = test::ResourceTableBuilder() + .AddSimple("android:id/foo", ResourceId(0x01010000)) + .AddSimple("android:dimen/two") + .AddSimple("android:integer/three") + .AddSimple("android:string/five") + .AddSimple("android:attr/fun", ResourceId(0x01040000)) + .AddSimple("android:attr/foo", ResourceId(0x01040006)) + .AddSimple("android:attr/bar") + .AddSimple("android:attr/baz") + .Build(); + IdAssigner assigner; ASSERT_TRUE(assigner.Consume(context.get(), table.get())); ASSERT_TRUE(VerifyIds(table.get())); @@ -65,12 +63,12 @@ TEST(IdAssignerTest, AssignIdsWithReservedIds) { maybe_result = table->FindResource(test::ParseNameOrDie("android:dimen/two")); ASSERT_TRUE(maybe_result); - EXPECT_EQ(make_value<uint8_t>(2), maybe_result.value().type->id); + EXPECT_EQ(make_value<ResourceId>(0x01020000), maybe_result.value().entry->id); maybe_result = table->FindResource(test::ParseNameOrDie("android:integer/three")); ASSERT_TRUE(maybe_result); - EXPECT_EQ(make_value<uint8_t>(3), maybe_result.value().type->id); + EXPECT_EQ(make_value<ResourceId>(0x01030000), maybe_result.value().entry->id); // Expect to bypass the reserved 0x0104XXXX IDs and use the next 0x0105XXXX // IDs. @@ -78,107 +76,133 @@ TEST(IdAssignerTest, AssignIdsWithReservedIds) { maybe_result = table->FindResource(test::ParseNameOrDie("android:string/five")); ASSERT_TRUE(maybe_result); - EXPECT_EQ(make_value<uint8_t>(5), maybe_result.value().type->id); + EXPECT_EQ(make_value<ResourceId>(0x01050000), maybe_result.value().entry->id); // Expect to fill in the gaps between 0x01040000 and 0x01040006. maybe_result = table->FindResource(test::ParseNameOrDie("android:attr/bar")); ASSERT_TRUE(maybe_result); - EXPECT_EQ(make_value<uint16_t>(1), maybe_result.value().entry->id); + EXPECT_EQ(make_value<ResourceId>(0x01040001), maybe_result.value().entry->id); maybe_result = table->FindResource(test::ParseNameOrDie("android:attr/baz")); ASSERT_TRUE(maybe_result); - EXPECT_EQ(make_value<uint16_t>(2), maybe_result.value().entry->id); + EXPECT_EQ(make_value<ResourceId>(0x01040002), maybe_result.value().entry->id); } -TEST(IdAssignerTest, FailWhenNonUniqueIdsAssigned) { - std::unique_ptr<ResourceTable> table = - test::ResourceTableBuilder() - .AddSimple("android:attr/foo", ResourceId(0x01040006)) - .AddSimple("android:attr/bar", ResourceId(0x01040006)) - .SetPackageId("android", 0x01) - .SetPackageId("app", 0x7f) - .Build(); +TEST_F(IdAssignerTests, FailWhenNonUniqueIdsAssigned) { + auto table = test::ResourceTableBuilder() + .AddSimple("android:attr/foo", ResourceId(0x01040006)) + .AddSimple("android:attr/bar", ResourceId(0x01040006)) + .Build(); + IdAssigner assigner; + ASSERT_FALSE(assigner.Consume(context.get(), table.get())); +} - std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); +TEST_F(IdAssignerTests, FailWhenNonUniqueTypeIdsAssigned) { + auto table = test::ResourceTableBuilder() + .AddSimple("android:string/foo", ResourceId(0x01040000)) + .AddSimple("android:attr/bar", ResourceId(0x01040006)) + .Build(); IdAssigner assigner; + ASSERT_FALSE(assigner.Consume(context.get(), table.get())); +} +TEST_F(IdAssignerTests, FailWhenTypeHasTwoNonStagedIds) { + auto table = test::ResourceTableBuilder() + .AddSimple("android:attr/foo", ResourceId(0x01050000)) + .AddSimple("android:attr/bar", ResourceId(0x01040006)) + .Build(); + IdAssigner assigner; ASSERT_FALSE(assigner.Consume(context.get(), table.get())); } -TEST(IdAssignerTest, AssignIdsWithIdMap) { - std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .AddSimple("android:attr/foo") - .AddSimple("android:attr/bar") - .SetPackageId("android", 0x01) - .Build(); +TEST_F(IdAssignerTests, FailWhenTypeHasTwoNonStagedIdsRegardlessOfStagedId) { + auto table = test::ResourceTableBuilder() + .AddSimple("android:attr/foo", ResourceId(0x01050000)) + .AddSimple("android:attr/bar", ResourceId(0x01ff0006)) + .Add(NewResourceBuilder("android:attr/staged_baz") + .SetId(0x01ff0000) + .SetVisibility({.staged_api = true}) + .Build()) + .Build(); + IdAssigner assigner; + ASSERT_FALSE(assigner.Consume(context.get(), table.get())); +} - std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); +TEST_F(IdAssignerTests, AssignIdsWithIdMap) { + auto table = test::ResourceTableBuilder() + .AddSimple("android:attr/foo") + .AddSimple("android:attr/bar") + .Build(); std::unordered_map<ResourceName, ResourceId> id_map = { {test::ParseNameOrDie("android:attr/foo"), ResourceId(0x01010002)}}; IdAssigner assigner(&id_map); ASSERT_TRUE(assigner.Consume(context.get(), table.get())); ASSERT_TRUE(VerifyIds(table.get())); - Maybe<ResourceTable::SearchResult> result = - table->FindResource(test::ParseNameOrDie("android:attr/foo")); + auto result = table->FindResource(test::ParseNameOrDie("android:attr/foo")); ASSERT_TRUE(result); const ResourceTable::SearchResult& search_result = result.value(); - EXPECT_EQ(make_value<uint8_t>(0x01), search_result.package->id); - EXPECT_EQ(make_value<uint8_t>(0x01), search_result.type->id); - EXPECT_EQ(make_value<uint16_t>(0x0002), search_result.entry->id); + EXPECT_EQ(make_value<ResourceId>(0x01010002), search_result.entry->id); } -::testing::AssertionResult VerifyIds(ResourceTable* table) { - std::set<uint8_t> package_ids; - for (auto& package : table->packages) { - if (!package->id) { - return ::testing::AssertionFailure() << "package " << package->name - << " has no ID"; - } - - if (!package_ids.insert(package->id.value()).second) { - return ::testing::AssertionFailure() - << "package " << package->name << " has non-unique ID " << std::hex - << (int)package->id.value() << std::dec; - } +TEST_F(IdAssignerTests, UseAllEntryIds) { + ResourceTable table; + const size_t max_entry_id = std::numeric_limits<uint16_t>::max(); + for (size_t i = 0; i <= max_entry_id; i++) { + ASSERT_TRUE( + table.AddResource(NewResourceBuilder("android:attr/res" + std::to_string(i)).Build(), + context->GetDiagnostics())); } + IdAssigner assigner; + ASSERT_TRUE(assigner.Consume(context.get(), &table)); +} - for (auto& package : table->packages) { - std::set<uint8_t> type_ids; - for (auto& type : package->types) { - if (!type->id) { - return ::testing::AssertionFailure() << "type " << type->type - << " of package " << package->name - << " has no ID"; - } +TEST_F(IdAssignerTests, ExaustEntryIds) { + ResourceTable table; + const size_t max_entry_id = std::numeric_limits<uint16_t>::max() + 1u; + for (size_t i = 0; i <= max_entry_id; i++) { + ASSERT_TRUE( + table.AddResource(NewResourceBuilder("android:attr/res" + std::to_string(i)).Build(), + context->GetDiagnostics())); + } + IdAssigner assigner; + ASSERT_FALSE(assigner.Consume(context.get(), &table)); +} - if (!type_ids.insert(type->id.value()).second) { - return ::testing::AssertionFailure() - << "type " << type->type << " of package " << package->name - << " has non-unique ID " << std::hex << (int)type->id.value() - << std::dec; - } - } +TEST_F(IdAssignerTests, ExaustEntryIdsLastIdIsPublic) { + ResourceTable table; + ASSERT_TRUE(table.AddResource(NewResourceBuilder("android:attr/res").SetId(0x0101ffff).Build(), + context->GetDiagnostics())); + const size_t max_entry_id = std::numeric_limits<uint16_t>::max(); + for (size_t i = 0; i <= max_entry_id; i++) { + ASSERT_TRUE( + table.AddResource(NewResourceBuilder("android:attr/res" + std::to_string(i)).Build(), + context->GetDiagnostics())); + } + IdAssigner assigner; + ASSERT_FALSE(assigner.Consume(context.get(), &table)); +} +::testing::AssertionResult VerifyIds(ResourceTable* table) { + std::set<ResourceId> seen_ids; + for (auto& package : table->packages) { for (auto& type : package->types) { - std::set<uint16_t> entry_ids; for (auto& entry : type->entries) { if (!entry->id) { return ::testing::AssertionFailure() - << "entry " << entry->name << " of type " << type->type - << " of package " << package->name << " has no ID"; + << "resource " << ResourceNameRef(package->name, type->type, entry->name) + << " has no ID"; } - - if (!entry_ids.insert(entry->id.value()).second) { + if (!seen_ids.insert(entry->id.value()).second) { return ::testing::AssertionFailure() - << "entry " << entry->name << " of type " << type->type - << " of package " << package->name << " has non-unique ID " - << std::hex << (int)entry->id.value() << std::dec; + << "resource " << ResourceNameRef(package->name, type->type, entry->name) + << " has a non-unique ID" << std::hex << entry->id.value() << std::dec; } } } } + return ::testing::AssertionSuccess() << "all IDs are unique and assigned"; } diff --git a/tools/aapt2/compile/PngChunkFilter.cpp b/tools/aapt2/compile/PngChunkFilter.cpp index bc2e6990433c..4db2392b4eab 100644 --- a/tools/aapt2/compile/PngChunkFilter.cpp +++ b/tools/aapt2/compile/PngChunkFilter.cpp @@ -35,7 +35,7 @@ constexpr uint32_t u32(uint8_t a, uint8_t b, uint8_t c, uint8_t d) { ((uint32_t)d); } -// Whitelist of PNG chunk types that we want to keep in the resulting PNG. +// Allow list of PNG chunk types that we want to keep in the resulting PNG. enum PngChunkTypes { kPngChunkIHDR = u32(73, 72, 68, 82), kPngChunkIDAT = u32(73, 68, 65, 84), @@ -56,7 +56,7 @@ static uint32_t Peek32LE(const char* data) { return word; } -static bool IsPngChunkWhitelisted(uint32_t type) { +static bool IsPngChunkAllowed(uint32_t type) { switch (type) { case kPngChunkIHDR: case kPngChunkIDAT: @@ -128,7 +128,7 @@ bool PngChunkFilter::Next(const void** buffer, size_t* len) { // Do we strip this chunk? const uint32_t chunk_type = Peek32LE(data_.data() + window_end_ + sizeof(uint32_t)); - if (IsPngChunkWhitelisted(chunk_type)) { + if (IsPngChunkAllowed(chunk_type)) { // Advance the window to include this chunk. window_end_ += kMinChunkHeaderSize + chunk_len; diff --git a/tools/aapt2/compile/PseudolocaleGenerator.cpp b/tools/aapt2/compile/PseudolocaleGenerator.cpp index 5e0300b3071b..3f574ee8e897 100644 --- a/tools/aapt2/compile/PseudolocaleGenerator.cpp +++ b/tools/aapt2/compile/PseudolocaleGenerator.cpp @@ -226,6 +226,7 @@ class Visitor : public ValueVisitor { : pool_(pool), method_(method), localizer_(method) {} void Visit(Plural* plural) override { + CloningValueTransformer cloner(pool_); std::unique_ptr<Plural> localized = util::make_unique<Plural>(); for (size_t i = 0; i < plural->values.size(); i++) { Visitor sub_visitor(pool_, method_); @@ -234,7 +235,7 @@ class Visitor : public ValueVisitor { if (sub_visitor.item) { localized->values[i] = std::move(sub_visitor.item); } else { - localized->values[i] = std::unique_ptr<Item>(plural->values[i]->Clone(pool_)); + localized->values[i] = plural->values[i]->Transform(cloner); } } } diff --git a/tools/aapt2/compile/PseudolocaleGenerator_test.cpp b/tools/aapt2/compile/PseudolocaleGenerator_test.cpp index e816c86e20a8..432d7bfdad49 100644 --- a/tools/aapt2/compile/PseudolocaleGenerator_test.cpp +++ b/tools/aapt2/compile/PseudolocaleGenerator_test.cpp @@ -236,13 +236,14 @@ TEST(PseudolocaleGeneratorTest, PseudolocalizeOnlyDefaultConfigs) { TEST(PseudolocaleGeneratorTest, PluralsArePseudolocalized) { std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); - std::unique_ptr<ResourceTable> table = - test::ResourceTableBuilder().SetPackageId("com.pkg", 0x7F).Build(); + std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder().Build(); std::unique_ptr<Plural> plural = util::make_unique<Plural>(); plural->values = {util::make_unique<String>(table->string_pool.MakeRef("zero")), util::make_unique<String>(table->string_pool.MakeRef("one"))}; - ASSERT_TRUE(table->AddResource(test::ParseNameOrDie("com.pkg:plurals/foo"), ConfigDescription{}, - {}, std::move(plural), context->GetDiagnostics())); + ASSERT_TRUE(table->AddResource(NewResourceBuilder(test::ParseNameOrDie("com.pkg:plurals/foo")) + .SetValue(std::move(plural)) + .Build(), + context->GetDiagnostics())); std::unique_ptr<Plural> expected = util::make_unique<Plural>(); expected->values = {util::make_unique<String>(table->string_pool.MakeRef("[žéŕö one]")), util::make_unique<String>(table->string_pool.MakeRef("[öñé one]"))}; @@ -252,6 +253,7 @@ TEST(PseudolocaleGeneratorTest, PluralsArePseudolocalized) { const auto* actual = test::GetValueForConfig<Plural>(table.get(), "com.pkg:plurals/foo", test::ParseConfigOrDie("en-rXA")); + ASSERT_NE(nullptr, actual); EXPECT_TRUE(actual->Equals(expected.get())); } @@ -273,11 +275,14 @@ TEST(PseudolocaleGeneratorTest, RespectUntranslateableSections) { auto string = util::make_unique<String>(table->string_pool.MakeRef(original_style.str)); string->untranslatable_sections.push_back(UntranslatableSection{6u, 11u}); - ASSERT_TRUE(table->AddResource(test::ParseNameOrDie("android:string/foo"), ConfigDescription{}, - {} /* product */, std::move(styled_string), + ASSERT_TRUE(table->AddResource(NewResourceBuilder(test::ParseNameOrDie("android:string/foo")) + .SetValue(std::move(styled_string)) + .Build(), + context->GetDiagnostics())); + ASSERT_TRUE(table->AddResource(NewResourceBuilder(test::ParseNameOrDie("android:string/bar")) + .SetValue(std::move(string)) + .Build(), context->GetDiagnostics())); - ASSERT_TRUE(table->AddResource(test::ParseNameOrDie("android:string/bar"), ConfigDescription{}, - {} /* product */, std::move(string), context->GetDiagnostics())); } PseudolocaleGenerator generator; diff --git a/tools/aapt2/configuration/ConfigurationParser_test.cpp b/tools/aapt2/configuration/ConfigurationParser_test.cpp index 2ef8b999a192..e5b3107877cb 100644 --- a/tools/aapt2/configuration/ConfigurationParser_test.cpp +++ b/tools/aapt2/configuration/ConfigurationParser_test.cpp @@ -187,7 +187,7 @@ TEST_F(ConfigurationParserTest, ForPath_NoFile) { TEST_F(ConfigurationParserTest, ExtractConfiguration) { Maybe<PostProcessingConfiguration> maybe_config = - ExtractConfiguration(kValidConfig, "dummy.xml", &diag_); + ExtractConfiguration(kValidConfig, "fake.xml", &diag_); PostProcessingConfiguration config = maybe_config.value(); diff --git a/tools/aapt2/dump/DumpManifest.cpp b/tools/aapt2/dump/DumpManifest.cpp index 4a6bfd031284..f2c6b15bc0cd 100644 --- a/tools/aapt2/dump/DumpManifest.cpp +++ b/tools/aapt2/dump/DumpManifest.cpp @@ -79,8 +79,8 @@ enum { ISGAME_ATTR = 0x10103f4, VERSION_ATTR = 0x01010519, CERT_DIGEST_ATTR = 0x01010548, - REQUIRED_FEATURE_ATTR = 0x01010557, - REQUIRED_NOT_FEATURE_ATTR = 0x01010558, + REQUIRED_FEATURE_ATTR = 0x01010554, + REQUIRED_NOT_FEATURE_ATTR = 0x01010555, IS_STATIC_ATTR = 0x0101055a, REQUIRED_SYSTEM_PROPERTY_NAME_ATTR = 0x01010565, REQUIRED_SYSTEM_PROPERTY_VALUE_ATTR = 0x01010566, @@ -132,6 +132,13 @@ class ManifestExtractor { /** Adds an element to the list of children of the element. */ void AddChild(std::unique_ptr<Element>& child) { children_.push_back(std::move(child)); } + template <typename Predicate> + void Filter(Predicate&& func) { + children_.erase(std::remove_if(children_.begin(), children_.end(), + [&](const auto& e) { return func(e.get()); }), + children_.end()); + } + /** Retrieves the list of children of the element. */ const std::vector<std::unique_ptr<Element>>& children() const { return children_; @@ -188,21 +195,17 @@ class ManifestExtractor { /** Retrieves the resource assigned to the specified resource id if one exists. */ Value* FindValueById(const ResourceTable* table, const ResourceId& res_id, - const ConfigDescription& config = DummyConfig()) { + const ConfigDescription& config = DefaultConfig()) { if (table) { for (auto& package : table->packages) { - if (package->id && package->id.value() == res_id.package_id()) { for (auto& type : package->types) { - if (type->id && type->id.value() == res_id.type_id()) { - for (auto& entry : type->entries) { - if (entry->id && entry->id.value() == res_id.entry_id()) { - if (auto value = BestConfigValue(entry.get(), config)) { - return value; - } + for (auto& entry : type->entries) { + if (entry->id && entry->id.value() == res_id.id) { + if (auto value = BestConfigValue(entry.get(), config)) { + return value; } } } - } } } } @@ -210,7 +213,7 @@ class ManifestExtractor { } /** Attempts to resolve the reference to a non-reference value. */ - Value* ResolveReference(Reference* ref, const ConfigDescription& config = DummyConfig()) { + Value* ResolveReference(Reference* ref, const ConfigDescription& config = DefaultConfig()) { const int kMaxIterations = 40; int i = 0; while (ref && ref->id && i++ < kMaxIterations) { @@ -231,10 +234,10 @@ class ManifestExtractor { * this will attempt to resolve the reference to an integer value. **/ int32_t* GetAttributeInteger(xml::Attribute* attr, - const ConfigDescription& config = DummyConfig()) { + const ConfigDescription& config = DefaultConfig()) { if (attr != nullptr) { if (attr->compiled_value) { - // Resolve references using the dummy configuration + // Resolve references using the configuration Value* value = attr->compiled_value.get(); if (ValueCast<Reference>(value)) { value = ResolveReference(ValueCast<Reference>(value), config); @@ -257,7 +260,7 @@ class ManifestExtractor { * exist or cannot be resolved to an integer value. **/ int32_t GetAttributeIntegerDefault(xml::Attribute* attr, int32_t def, - const ConfigDescription& config = DummyConfig()) { + const ConfigDescription& config = DefaultConfig()) { auto value = GetAttributeInteger(attr, config); if (value) { return *value; @@ -270,10 +273,10 @@ class ManifestExtractor { * this will attempt to resolve the reference to a string value. **/ const std::string* GetAttributeString(xml::Attribute* attr, - const ConfigDescription& config = DummyConfig()) { + const ConfigDescription& config = DefaultConfig()) { if (attr != nullptr) { if (attr->compiled_value) { - // Resolve references using the dummy configuration + // Resolve references using the configuration Value* value = attr->compiled_value.get(); if (ValueCast<Reference>(value)) { value = ResolveReference(ValueCast<Reference>(value), config); @@ -305,7 +308,7 @@ class ManifestExtractor { * exist or cannot be resolved to an string value. **/ std::string GetAttributeStringDefault(xml::Attribute* attr, std::string def, - const ConfigDescription& config = DummyConfig()) { + const ConfigDescription& config = DefaultConfig()) { auto value = GetAttributeString(attr, config); if (value) { return *value; @@ -322,7 +325,7 @@ class ManifestExtractor { friend Element; /** Creates a default configuration used to retrieve resources. */ - static ConfigDescription DummyConfig() { + static ConfigDescription DefaultConfig() { ConfigDescription config; config.orientation = android::ResTable_config::ORIENTATION_PORT; config.density = android::ResTable_config::DENSITY_MEDIUM; @@ -1063,17 +1066,23 @@ class UsesPermission : public ManifestExtractor::Element { public: UsesPermission() = default; std::string name; - std::string requiredFeature; - std::string requiredNotFeature; + std::vector<std::string> requiredFeatures; + std::vector<std::string> requiredNotFeatures; int32_t required = true; int32_t maxSdkVersion = -1; void Extract(xml::Element* element) override { name = GetAttributeStringDefault(FindAttribute(element, NAME_ATTR), ""); - requiredFeature = GetAttributeStringDefault( - FindAttribute(element, REQUIRED_FEATURE_ATTR), ""); - requiredNotFeature = GetAttributeStringDefault( - FindAttribute(element, REQUIRED_NOT_FEATURE_ATTR), ""); + std::string feature = + GetAttributeStringDefault(FindAttribute(element, REQUIRED_FEATURE_ATTR), ""); + if (!feature.empty()) { + requiredFeatures.push_back(feature); + } + feature = GetAttributeStringDefault(FindAttribute(element, REQUIRED_NOT_FEATURE_ATTR), ""); + if (!feature.empty()) { + requiredNotFeatures.push_back(feature); + } + required = GetAttributeIntegerDefault(FindAttribute(element, REQUIRED_ATTR), 1); maxSdkVersion = GetAttributeIntegerDefault( FindAttribute(element, MAX_SDK_VERSION_ATTR), -1); @@ -1090,13 +1099,13 @@ class UsesPermission : public ManifestExtractor::Element { if (maxSdkVersion >= 0) { printer->Print(StringPrintf(" maxSdkVersion='%d'", maxSdkVersion)); } - if (!requiredFeature.empty()) { - printer->Print(StringPrintf(" requiredFeature='%s'", requiredFeature.data())); + printer->Print("\n"); + for (const std::string& requiredFeature : requiredFeatures) { + printer->Print(StringPrintf(" required-feature='%s'\n", requiredFeature.data())); } - if (!requiredNotFeature.empty()) { - printer->Print(StringPrintf(" requiredNotFeature='%s'", requiredNotFeature.data())); + for (const std::string& requiredNotFeature : requiredNotFeatures) { + printer->Print(StringPrintf(" required-not-feature='%s'\n", requiredNotFeature.data())); } - printer->Print("\n"); if (required == 0) { printer->Print(StringPrintf("optional-permission: name='%s'", name.data())); if (maxSdkVersion >= 0) { @@ -1116,6 +1125,38 @@ class UsesPermission : public ManifestExtractor::Element { } }; +/** Represents <required-feature> elements. **/ +class RequiredFeature : public ManifestExtractor::Element { + public: + RequiredFeature() = default; + std::string name; + + void Extract(xml::Element* element) override { + name = GetAttributeStringDefault(FindAttribute(element, NAME_ATTR), ""); + auto parent_stack = extractor()->parent_stack(); + if (!name.empty() && ElementCast<UsesPermission>(parent_stack[0])) { + UsesPermission* uses_permission = ElementCast<UsesPermission>(parent_stack[0]); + uses_permission->requiredFeatures.push_back(name); + } + } +}; + +/** Represents <required-not-feature> elements. **/ +class RequiredNotFeature : public ManifestExtractor::Element { + public: + RequiredNotFeature() = default; + std::string name; + + void Extract(xml::Element* element) override { + name = GetAttributeStringDefault(FindAttribute(element, NAME_ATTR), ""); + auto parent_stack = extractor()->parent_stack(); + if (!name.empty() && ElementCast<UsesPermission>(parent_stack[0])) { + UsesPermission* uses_permission = ElementCast<UsesPermission>(parent_stack[0]); + uses_permission->requiredNotFeatures.push_back(name); + } + } +}; + /** Represents <uses-permission-sdk-23> elements. **/ class UsesPermissionSdk23 : public ManifestExtractor::Element { public: @@ -1405,6 +1446,29 @@ class UsesStaticLibrary : public ManifestExtractor::Element { } }; +/** Represents <uses-native-library> elements. **/ +class UsesNativeLibrary : public ManifestExtractor::Element { + public: + UsesNativeLibrary() = default; + std::string name; + int required; + + void Extract(xml::Element* element) override { + auto parent_stack = extractor()->parent_stack(); + if (parent_stack.size() > 0 && ElementCast<Application>(parent_stack[0])) { + name = GetAttributeStringDefault(FindAttribute(element, NAME_ATTR), ""); + required = GetAttributeIntegerDefault(FindAttribute(element, REQUIRED_ATTR), 1); + } + } + + void Print(text::Printer* printer) override { + if (!name.empty()) { + printer->Print(StringPrintf("uses-native-library%s:'%s'\n", + (required == 0) ? "-not-required" : "", name.data())); + } + } +}; + /** * Represents <meta-data> elements. These tags are only printed when a flag is passed in to * explicitly enable meta data printing. @@ -1790,6 +1854,41 @@ class SupportsGlTexture : public ManifestExtractor::Element { } }; +/** Represents <property> elements. **/ +class Property : public ManifestExtractor::Element { + public: + Property() = default; + std::string name; + std::string value; + const int* value_int; + std::string resource; + const int* resource_int; + + void Extract(xml::Element* element) override { + name = GetAttributeStringDefault(FindAttribute(element, NAME_ATTR), ""); + value = GetAttributeStringDefault(FindAttribute(element, VALUE_ATTR), ""); + value_int = GetAttributeInteger(FindAttribute(element, VALUE_ATTR)); + resource = GetAttributeStringDefault(FindAttribute(element, RESOURCE_ATTR), ""); + resource_int = GetAttributeInteger(FindAttribute(element, RESOURCE_ATTR)); + } + + void Print(text::Printer* printer) override { + printer->Print(StringPrintf("property: name='%s' ", name.data())); + if (!value.empty()) { + printer->Print(StringPrintf("value='%s' ", value.data())); + } else if (value_int) { + printer->Print(StringPrintf("value='%d' ", *value_int)); + } else { + if (!resource.empty()) { + printer->Print(StringPrintf("resource='%s' ", resource.data())); + } else if (resource_int) { + printer->Print(StringPrintf("resource='%d' ", *resource_int)); + } + } + printer->Print("\n"); + } +}; + /** Recursively prints the extracted badging element. */ static void Print(ManifestExtractor::Element* el, text::Printer* printer) { el->Print(printer); @@ -1822,7 +1921,8 @@ bool ManifestExtractor::Dump(text::Printer* printer, IDiagnostics* diag) { for (xml::Element* child : element->GetChildElements()) { if (child->name == "uses-permission" || child->name == "uses-permission-sdk-23" || child->name == "permission") { - auto permission_element = ManifestExtractor::Element::Inflate(this, child); + // Inflate the element and its descendants + auto permission_element = Visit(child); manifest->AddChild(permission_element); } } @@ -1848,7 +1948,7 @@ bool ManifestExtractor::Dump(text::Printer* printer, IDiagnostics* diag) { // Collect all the unique locales of the apk if (locales_.find(locale_str) == locales_.end()) { - ConfigDescription config = ManifestExtractor::DummyConfig(); + ConfigDescription config = ManifestExtractor::DefaultConfig(); config.setBcp47Locale(locale_str.data()); locales_.insert(std::make_pair(locale_str, config)); } @@ -1857,7 +1957,7 @@ bool ManifestExtractor::Dump(text::Printer* printer, IDiagnostics* diag) { uint16_t density = (value->config.density == 0) ? (uint16_t) 160 : value->config.density; if (densities_.find(density) == densities_.end()) { - ConfigDescription config = ManifestExtractor::DummyConfig(); + ConfigDescription config = ManifestExtractor::DefaultConfig(); config.density = density; densities_.insert(std::make_pair(density, config)); } @@ -1870,6 +1970,21 @@ bool ManifestExtractor::Dump(text::Printer* printer, IDiagnostics* diag) { // Extract badging information auto root = Visit(element); + // Filter out all "uses-sdk" tags besides the very last tag. The android runtime only uses the + // attribute values from the last defined tag. + std::vector<UsesSdkBadging*> filtered_uses_sdk_tags; + for (const auto& child : root->children()) { + if (auto uses_sdk = ElementCast<UsesSdkBadging>(child.get())) { + filtered_uses_sdk_tags.emplace_back(uses_sdk); + } + } + filtered_uses_sdk_tags.pop_back(); + + root->Filter([&](const ManifestExtractor::Element* e) { + return std::find(filtered_uses_sdk_tags.begin(), filtered_uses_sdk_tags.end(), e) != + filtered_uses_sdk_tags.end(); + }); + // Print the elements in order seen Print(root.get(), printer); @@ -2214,37 +2329,41 @@ T* ElementCast(ManifestExtractor::Element* element) { } const std::unordered_map<std::string, bool> kTagCheck = { - {"action", std::is_base_of<Action, T>::value}, - {"activity", std::is_base_of<Activity, T>::value}, - {"application", std::is_base_of<Application, T>::value}, - {"category", std::is_base_of<Category, T>::value}, - {"compatible-screens", std::is_base_of<CompatibleScreens, T>::value}, - {"feature-group", std::is_base_of<FeatureGroup, T>::value}, - {"input-type", std::is_base_of<InputType, T>::value}, - {"intent-filter", std::is_base_of<IntentFilter, T>::value}, - {"meta-data", std::is_base_of<MetaData, T>::value}, - {"manifest", std::is_base_of<Manifest, T>::value}, - {"original-package", std::is_base_of<OriginalPackage, T>::value}, - {"overlay", std::is_base_of<Overlay, T>::value}, - {"package-verifier", std::is_base_of<PackageVerifier, T>::value}, - {"permission", std::is_base_of<Permission, T>::value}, - {"provider", std::is_base_of<Provider, T>::value}, - {"receiver", std::is_base_of<Receiver, T>::value}, - {"screen", std::is_base_of<Screen, T>::value}, - {"service", std::is_base_of<Service, T>::value}, - {"supports-gl-texture", std::is_base_of<SupportsGlTexture, T>::value}, - {"supports-input", std::is_base_of<SupportsInput, T>::value}, - {"supports-screens", std::is_base_of<SupportsScreen, T>::value}, - {"uses-configuration", std::is_base_of<UsesConfiguarion, T>::value}, - {"uses-feature", std::is_base_of<UsesFeature, T>::value}, - {"uses-permission", std::is_base_of<UsesPermission, T>::value}, - {"uses-permission-sdk-23", std::is_base_of<UsesPermissionSdk23, T>::value}, - {"uses-library", std::is_base_of<UsesLibrary, T>::value}, - {"uses-package", std::is_base_of<UsesPackage, T>::value}, - {"static-library", std::is_base_of<StaticLibrary, T>::value}, - {"uses-static-library", std::is_base_of<UsesStaticLibrary, T>::value}, - {"additional-certificate", std::is_base_of<AdditionalCertificate, T>::value}, - {"uses-sdk", std::is_base_of<UsesSdkBadging, T>::value}, + {"action", std::is_base_of<Action, T>::value}, + {"activity", std::is_base_of<Activity, T>::value}, + {"additional-certificate", std::is_base_of<AdditionalCertificate, T>::value}, + {"application", std::is_base_of<Application, T>::value}, + {"category", std::is_base_of<Category, T>::value}, + {"compatible-screens", std::is_base_of<CompatibleScreens, T>::value}, + {"feature-group", std::is_base_of<FeatureGroup, T>::value}, + {"input-type", std::is_base_of<InputType, T>::value}, + {"intent-filter", std::is_base_of<IntentFilter, T>::value}, + {"meta-data", std::is_base_of<MetaData, T>::value}, + {"manifest", std::is_base_of<Manifest, T>::value}, + {"original-package", std::is_base_of<OriginalPackage, T>::value}, + {"overlay", std::is_base_of<Overlay, T>::value}, + {"package-verifier", std::is_base_of<PackageVerifier, T>::value}, + {"permission", std::is_base_of<Permission, T>::value}, + {"property", std::is_base_of<Property, T>::value}, + {"provider", std::is_base_of<Provider, T>::value}, + {"receiver", std::is_base_of<Receiver, T>::value}, + {"required-feature", std::is_base_of<RequiredFeature, T>::value}, + {"required-not-feature", std::is_base_of<RequiredNotFeature, T>::value}, + {"screen", std::is_base_of<Screen, T>::value}, + {"service", std::is_base_of<Service, T>::value}, + {"static-library", std::is_base_of<StaticLibrary, T>::value}, + {"supports-gl-texture", std::is_base_of<SupportsGlTexture, T>::value}, + {"supports-input", std::is_base_of<SupportsInput, T>::value}, + {"supports-screens", std::is_base_of<SupportsScreen, T>::value}, + {"uses-configuration", std::is_base_of<UsesConfiguarion, T>::value}, + {"uses-feature", std::is_base_of<UsesFeature, T>::value}, + {"uses-library", std::is_base_of<UsesLibrary, T>::value}, + {"uses-native-library", std::is_base_of<UsesNativeLibrary, T>::value}, + {"uses-package", std::is_base_of<UsesPackage, T>::value}, + {"uses-permission", std::is_base_of<UsesPermission, T>::value}, + {"uses-permission-sdk-23", std::is_base_of<UsesPermissionSdk23, T>::value}, + {"uses-sdk", std::is_base_of<UsesSdkBadging, T>::value}, + {"uses-static-library", std::is_base_of<UsesStaticLibrary, T>::value}, }; auto check = kTagCheck.find(element->tag()); @@ -2264,38 +2383,42 @@ std::unique_ptr<ManifestExtractor::Element> ManifestExtractor::Element::Inflate( const std::unordered_map<std::string, std::function<std::unique_ptr<ManifestExtractor::Element>()>> kTagCheck = { - {"action", &CreateType<Action>}, - {"activity", &CreateType<Activity>}, - {"application", &CreateType<Application>}, - {"category", &CreateType<Category>}, - {"compatible-screens", &CreateType<CompatibleScreens>}, - {"feature-group", &CreateType<FeatureGroup>}, - {"input-type", &CreateType<InputType>}, - {"intent-filter",&CreateType<IntentFilter>}, - {"manifest", &CreateType<Manifest>}, - {"meta-data", &CreateType<MetaData>}, - {"original-package", &CreateType<OriginalPackage>}, - {"overlay", &CreateType<Overlay>}, - {"package-verifier", &CreateType<PackageVerifier>}, - {"permission", &CreateType<Permission>}, - {"provider", &CreateType<Provider>}, - {"receiver", &CreateType<Receiver>}, - {"screen", &CreateType<Screen>}, - {"service", &CreateType<Service>}, - {"supports-gl-texture", &CreateType<SupportsGlTexture>}, - {"supports-input", &CreateType<SupportsInput>}, - {"supports-screens", &CreateType<SupportsScreen>}, - {"uses-configuration", &CreateType<UsesConfiguarion>}, - {"uses-feature", &CreateType<UsesFeature>}, - {"uses-permission", &CreateType<UsesPermission>}, - {"uses-permission-sdk-23", &CreateType<UsesPermissionSdk23>}, - {"uses-library", &CreateType<UsesLibrary>}, - {"static-library", &CreateType<StaticLibrary>}, - {"uses-static-library", &CreateType<UsesStaticLibrary>}, - {"uses-package", &CreateType<UsesPackage>}, - {"additional-certificate", &CreateType<AdditionalCertificate>}, - {"uses-sdk", &CreateType<UsesSdkBadging>}, - }; + {"action", &CreateType<Action>}, + {"activity", &CreateType<Activity>}, + {"additional-certificate", &CreateType<AdditionalCertificate>}, + {"application", &CreateType<Application>}, + {"category", &CreateType<Category>}, + {"compatible-screens", &CreateType<CompatibleScreens>}, + {"feature-group", &CreateType<FeatureGroup>}, + {"input-type", &CreateType<InputType>}, + {"intent-filter", &CreateType<IntentFilter>}, + {"manifest", &CreateType<Manifest>}, + {"meta-data", &CreateType<MetaData>}, + {"original-package", &CreateType<OriginalPackage>}, + {"overlay", &CreateType<Overlay>}, + {"package-verifier", &CreateType<PackageVerifier>}, + {"permission", &CreateType<Permission>}, + {"property", &CreateType<Property>}, + {"provider", &CreateType<Provider>}, + {"receiver", &CreateType<Receiver>}, + {"required-feature", &CreateType<RequiredFeature>}, + {"required-not-feature", &CreateType<RequiredNotFeature>}, + {"screen", &CreateType<Screen>}, + {"service", &CreateType<Service>}, + {"static-library", &CreateType<StaticLibrary>}, + {"supports-gl-texture", &CreateType<SupportsGlTexture>}, + {"supports-input", &CreateType<SupportsInput>}, + {"supports-screens", &CreateType<SupportsScreen>}, + {"uses-configuration", &CreateType<UsesConfiguarion>}, + {"uses-feature", &CreateType<UsesFeature>}, + {"uses-library", &CreateType<UsesLibrary>}, + {"uses-native-library", &CreateType<UsesNativeLibrary>}, + {"uses-package", &CreateType<UsesPackage>}, + {"uses-permission", &CreateType<UsesPermission>}, + {"uses-permission-sdk-23", &CreateType<UsesPermissionSdk23>}, + {"uses-sdk", &CreateType<UsesSdkBadging>}, + {"uses-static-library", &CreateType<UsesStaticLibrary>}, + }; // Attempt to map the xml tag to a element inflater std::unique_ptr<ManifestExtractor::Element> element; diff --git a/tools/aapt2/format/binary/BinaryResourceParser.cpp b/tools/aapt2/format/binary/BinaryResourceParser.cpp index cccd9faa9b39..72eaa3561a02 100644 --- a/tools/aapt2/format/binary/BinaryResourceParser.cpp +++ b/tools/aapt2/format/binary/BinaryResourceParser.cpp @@ -192,8 +192,7 @@ bool BinaryResourceParser::ParsePackage(const ResChunk_header* chunk) { std::u16string package_name = strcpy16_dtoh((const char16_t*)package_header->name, arraysize(package_header->name)); - ResourceTablePackage* package = - table_->CreatePackage(util::Utf16ToUtf8(package_name), static_cast<uint8_t>(package_id)); + ResourceTablePackage* package = table_->FindOrCreatePackage(util::Utf16ToUtf8(package_name)); if (!package) { diag_->Error(DiagMessage(source_) << "incompatible package '" << package_name << "' with ID " << package_id); @@ -232,13 +231,13 @@ bool BinaryResourceParser::ParsePackage(const ResChunk_header* chunk) { break; case android::RES_TABLE_TYPE_SPEC_TYPE: - if (!ParseTypeSpec(package, parser.chunk())) { + if (!ParseTypeSpec(package, parser.chunk(), package_id)) { return false; } break; case android::RES_TABLE_TYPE_TYPE: - if (!ParseType(package, parser.chunk())) { + if (!ParseType(package, parser.chunk(), package_id)) { return false; } break; @@ -255,6 +254,12 @@ bool BinaryResourceParser::ParsePackage(const ResChunk_header* chunk) { } break; + case android::RES_TABLE_STAGED_ALIAS_TYPE: + if (!ParseStagedAliases(parser.chunk())) { + return false; + } + break; + default: diag_->Warn(DiagMessage(source_) << "unexpected chunk type " @@ -276,7 +281,7 @@ bool BinaryResourceParser::ParsePackage(const ResChunk_header* chunk) { } bool BinaryResourceParser::ParseTypeSpec(const ResourceTablePackage* package, - const ResChunk_header* chunk) { + const ResChunk_header* chunk, uint8_t package_id) { if (type_pool_.getError() != NO_ERROR) { diag_->Error(DiagMessage(source_) << "missing type string pool"); return false; @@ -317,14 +322,14 @@ bool BinaryResourceParser::ParseTypeSpec(const ResourceTablePackage* package, const uint32_t* type_spec_flags = reinterpret_cast<const uint32_t*>( reinterpret_cast<uintptr_t>(type_spec) + util::DeviceToHost16(type_spec->header.headerSize)); for (size_t i = 0; i < entry_count; i++) { - ResourceId id(package->id.value_or_default(0x0), type_spec->id, static_cast<size_t>(i)); + ResourceId id(package_id, type_spec->id, static_cast<size_t>(i)); entry_type_spec_flags_[id] = util::DeviceToHost32(type_spec_flags[i]); } return true; } bool BinaryResourceParser::ParseType(const ResourceTablePackage* package, - const ResChunk_header* chunk) { + const ResChunk_header* chunk, uint8_t package_id) { if (type_pool_.getError() != NO_ERROR) { diag_->Error(DiagMessage(source_) << "missing type string pool"); return false; @@ -354,13 +359,9 @@ bool BinaryResourceParser::ParseType(const ResourceTablePackage* package, const std::string type_str = util::GetString(type_pool_, type->id - 1); const ResourceType* parsed_type = ParseResourceType(type_str); if (!parsed_type) { - // Be lenient on the name of the type if the table is lenient on resource validation. - bool log_error = table_->GetValidateResources(); - if (log_error) { - diag_->Error(DiagMessage(source_) << "invalid type name '" << type_str - << "' for type with ID " << type->id); - } - return !log_error; + diag_->Warn(DiagMessage(source_) + << "invalid type name '" << type_str << "' for type with ID " << type->id); + return true; } TypeVariant tv(type); @@ -372,7 +373,7 @@ bool BinaryResourceParser::ParseType(const ResourceTablePackage* package, const ResourceName name(package->name, *parsed_type, util::GetString(key_pool_, util::DeviceToHost32(entry->key.index))); - const ResourceId res_id(package->id.value(), type->id, static_cast<uint16_t>(it.index())); + const ResourceId res_id(package_id, type->id, static_cast<uint16_t>(it.index())); std::unique_ptr<Value> resource_value; if (entry->flags & ResTable_entry::FLAG_COMPLEX) { @@ -392,18 +393,21 @@ bool BinaryResourceParser::ParseType(const ResourceTablePackage* package, return false; } - if (!table_->AddResourceWithIdMangled(name, res_id, config, {}, std::move(resource_value), - diag_)) { - return false; - } + NewResourceBuilder res_builder(name); + res_builder.SetValue(std::move(resource_value), config) + .SetId(res_id, OnIdConflict::CREATE_ENTRY) + .SetAllowMangled(true); if (entry->flags & ResTable_entry::FLAG_PUBLIC) { - Visibility visibility; - visibility.level = Visibility::Level::kPublic; - if (!table_->SetVisibilityWithIdMangled(name, visibility, res_id, diag_)) { - return false; + Visibility visibility{Visibility::Level::kPublic}; + + auto spec_flags = entry_type_spec_flags_.find(res_id); + if (spec_flags != entry_type_spec_flags_.end() && + spec_flags->second & ResTable_typeSpec::SPEC_STAGED_API) { + visibility.staged_api = true; } + res_builder.SetVisibility(visibility); // Erase the ID from the map once processed, so that we don't mark the same symbol more than // once. entry_type_spec_flags_.erase(res_id); @@ -415,6 +419,10 @@ bool BinaryResourceParser::ParseType(const ResourceTablePackage* package, if (cache_iter == id_index_.end()) { id_index_.insert({res_id, name}); } + + if (!table_->AddResource(res_builder.Build(), diag_)) { + return false; + } } return true; } @@ -472,7 +480,12 @@ bool BinaryResourceParser::ParseOverlayable(const ResChunk_header* chunk) { OverlayableItem overlayable_item(overlayable); overlayable_item.policies = policy_header->policy_flags; - if (!table_->SetOverlayable(iter->second, overlayable_item, diag_)) { + if (!table_->AddResource(NewResourceBuilder(iter->second) + .SetId(res_id, OnIdConflict::CREATE_ENTRY) + .SetOverlayable(std::move(overlayable_item)) + .SetAllowMangled(true) + .Build(), + diag_)) { return false; } } @@ -482,6 +495,52 @@ bool BinaryResourceParser::ParseOverlayable(const ResChunk_header* chunk) { return true; } +bool BinaryResourceParser::ParseStagedAliases(const ResChunk_header* chunk) { + auto header = ConvertTo<ResTable_staged_alias_header>(chunk); + if (!header) { + diag_->Error(DiagMessage(source_) << "corrupt ResTable_staged_alias_header chunk"); + return false; + } + + const auto ref_begin = reinterpret_cast<const ResTable_staged_alias_entry*>( + ((uint8_t*)header) + util::DeviceToHost32(header->header.headerSize)); + const auto ref_end = ref_begin + util::DeviceToHost32(header->count); + for (auto ref_iter = ref_begin; ref_iter != ref_end; ++ref_iter) { + const auto staged_id = ResourceId(util::DeviceToHost32(ref_iter->stagedResId)); + const auto finalized_id = ResourceId(util::DeviceToHost32(ref_iter->finalizedResId)); + + // If the staged alias chunk comes before the type chunks, the resource ids and resource name + // pairing will not exist at this point. + const auto iter = id_index_.find(finalized_id); + if (iter == id_index_.cend()) { + diag_->Error(DiagMessage(source_) << "failed to find resource name for finalized" + << " resource ID " << finalized_id); + return false; + } + + // Set the staged id of the finalized resource. + const auto& resource_name = iter->second; + const StagedId staged_id_def{.id = staged_id}; + if (!table_->AddResource(NewResourceBuilder(resource_name) + .SetId(finalized_id, OnIdConflict::CREATE_ENTRY) + .SetStagedId(staged_id_def) + .SetAllowMangled(true) + .Build(), + diag_)) { + return false; + } + + // Since a the finalized resource entry is cloned and added to the resource table under the + // staged resource id, remove the cloned resource entry from the table. + if (!table_->RemoveResource(resource_name, staged_id)) { + diag_->Error(DiagMessage(source_) << "failed to find resource entry for staged " + << " resource ID " << staged_id); + return false; + } + } + return true; +} + std::unique_ptr<Item> BinaryResourceParser::ParseValue(const ResourceNameRef& name, const ConfigDescription& config, const android::Res_value& value) { diff --git a/tools/aapt2/format/binary/BinaryResourceParser.h b/tools/aapt2/format/binary/BinaryResourceParser.h index a2eee5006964..cd71d160703a 100644 --- a/tools/aapt2/format/binary/BinaryResourceParser.h +++ b/tools/aapt2/format/binary/BinaryResourceParser.h @@ -51,10 +51,13 @@ class BinaryResourceParser { bool ParseTable(const android::ResChunk_header* chunk); bool ParsePackage(const android::ResChunk_header* chunk); - bool ParseTypeSpec(const ResourceTablePackage* package, const android::ResChunk_header* chunk); - bool ParseType(const ResourceTablePackage* package, const android::ResChunk_header* chunk); + bool ParseTypeSpec(const ResourceTablePackage* package, const android::ResChunk_header* chunk, + uint8_t package_id); + bool ParseType(const ResourceTablePackage* package, const android::ResChunk_header* chunk, + uint8_t package_id); bool ParseLibrary(const android::ResChunk_header* chunk); bool ParseOverlayable(const android::ResChunk_header* chunk); + bool ParseStagedAliases(const android::ResChunk_header* chunk); std::unique_ptr<Item> ParseValue(const ResourceNameRef& name, const android::ConfigDescription& config, diff --git a/tools/aapt2/format/binary/TableFlattener.cpp b/tools/aapt2/format/binary/TableFlattener.cpp index 4784ecf3d12c..a9192e889c17 100644 --- a/tools/aapt2/format/binary/TableFlattener.cpp +++ b/tools/aapt2/format/binary/TableFlattener.cpp @@ -59,47 +59,35 @@ static void strcpy16_htod(uint16_t* dst, size_t len, const StringPiece16& src) { dst[i] = 0; } -static bool cmp_style_ids(ResourceId a, ResourceId b) { - // If one of a and b is from the framework package (package ID 0x01), and the - // other is a dynamic ID (package ID 0x00), then put the dynamic ID after the - // framework ID. This ensures that when AssetManager resolves the dynamic IDs, - // they will be in sorted order as expected by AssetManager. - if ((a.package_id() == kFrameworkPackageId && b.package_id() == 0x00) || - (a.package_id() == 0x00 && b.package_id() == kFrameworkPackageId)) { - return b < a; - } - return a < b; -} - -static bool cmp_style_entries(const Style::Entry& a, const Style::Entry& b) { - if (a.key.id) { - if (b.key.id) { - return cmp_style_ids(a.key.id.value(), b.key.id.value()); +static bool cmp_style_entries(const Style::Entry* a, const Style::Entry* b) { + if (a->key.id) { + if (b->key.id) { + return cmp_ids_dynamic_after_framework(a->key.id.value(), b->key.id.value()); } return true; - } else if (!b.key.id) { - return a.key.name.value() < b.key.name.value(); + } else if (!b->key.id) { + return a->key.name.value() < b->key.name.value(); } return false; } struct FlatEntry { - ResourceEntry* entry; - Value* value; + const ResourceTableEntryView* entry; + const Value* value; // The entry string pool index to the entry's name. uint32_t entry_key; }; -class MapFlattenVisitor : public ValueVisitor { +class MapFlattenVisitor : public ConstValueVisitor { public: - using ValueVisitor::Visit; + using ConstValueVisitor::Visit; MapFlattenVisitor(ResTable_entry_ext* out_entry, BigBuffer* buffer) : out_entry_(out_entry), buffer_(buffer) { } - void Visit(Attribute* attr) override { + void Visit(const Attribute* attr) override { { Reference key = Reference(ResourceId(ResTable_map::ATTR_TYPE)); BinaryPrimitive val(Res_value::TYPE_INT_DEC, attr->type_mask); @@ -118,13 +106,13 @@ class MapFlattenVisitor : public ValueVisitor { FlattenEntry(&key, &val); } - for (Attribute::Symbol& s : attr->symbols) { + for (const Attribute::Symbol& s : attr->symbols) { BinaryPrimitive val(s.type, s.value); FlattenEntry(&s.symbol, &val); } } - void Visit(Style* style) override { + void Visit(const Style* style) override { if (style->parent) { const Reference& parent_ref = style->parent.value(); CHECK(bool(parent_ref.id)) << "parent has no ID"; @@ -132,21 +120,26 @@ class MapFlattenVisitor : public ValueVisitor { } // Sort the style. - std::sort(style->entries.begin(), style->entries.end(), cmp_style_entries); + std::vector<const Style::Entry*> sorted_entries; + for (const auto& entry : style->entries) { + sorted_entries.emplace_back(&entry); + } - for (Style::Entry& entry : style->entries) { - FlattenEntry(&entry.key, entry.value.get()); + std::sort(sorted_entries.begin(), sorted_entries.end(), cmp_style_entries); + + for (const Style::Entry* entry : sorted_entries) { + FlattenEntry(&entry->key, entry->value.get()); } } - void Visit(Styleable* styleable) override { + void Visit(const Styleable* styleable) override { for (auto& attr_ref : styleable->entries) { BinaryPrimitive val(Res_value{}); FlattenEntry(&attr_ref, &val); } } - void Visit(Array* array) override { + void Visit(const Array* array) override { const size_t count = array->elements.size(); for (size_t i = 0; i < count; i++) { Reference key(android::ResTable_map::ATTR_MIN + i); @@ -154,7 +147,7 @@ class MapFlattenVisitor : public ValueVisitor { } } - void Visit(Plural* plural) override { + void Visit(const Plural* plural) override { const size_t count = plural->values.size(); for (size_t i = 0; i < count; i++) { if (!plural->values[i]) { @@ -208,16 +201,16 @@ class MapFlattenVisitor : public ValueVisitor { private: DISALLOW_COPY_AND_ASSIGN(MapFlattenVisitor); - void FlattenKey(Reference* key, ResTable_map* out_entry) { + void FlattenKey(const Reference* key, ResTable_map* out_entry) { CHECK(bool(key->id)) << "key has no ID"; out_entry->name.ident = util::HostToDevice32(key->id.value().id); } - void FlattenValue(Item* value, ResTable_map* out_entry) { + void FlattenValue(const Item* value, ResTable_map* out_entry) { CHECK(value->Flatten(&out_entry->value)) << "flatten failed"; } - void FlattenEntry(Reference* key, Item* value) { + void FlattenEntry(const Reference* key, Item* value) { ResTable_map* out_entry = buffer_->NextBlock<ResTable_map>(); FlattenKey(key, out_entry); FlattenValue(value, out_entry); @@ -238,7 +231,7 @@ struct OverlayableChunk { class PackageFlattener { public: - PackageFlattener(IAaptContext* context, ResourceTablePackage* package, + PackageFlattener(IAaptContext* context, const ResourceTablePackageView& package, const std::map<size_t, std::string>* shared_libs, bool use_sparse_entries, bool collapse_key_stringpool, const std::set<ResourceName>& name_collapse_exemptions) @@ -255,20 +248,20 @@ class PackageFlattener { TRACE_CALL(); ChunkWriter pkg_writer(buffer); ResTable_package* pkg_header = pkg_writer.StartChunk<ResTable_package>(RES_TABLE_PACKAGE_TYPE); - pkg_header->id = util::HostToDevice32(package_->id.value()); + pkg_header->id = util::HostToDevice32(package_.id.value()); // AAPT truncated the package name, so do the same. // Shared libraries require full package names, so don't truncate theirs. if (context_->GetPackageType() != PackageType::kApp && - package_->name.size() >= arraysize(pkg_header->name)) { - diag_->Error(DiagMessage() << "package name '" << package_->name + package_.name.size() >= arraysize(pkg_header->name)) { + diag_->Error(DiagMessage() << "package name '" << package_.name << "' is too long. " "Shared libraries cannot have truncated package names"); return false; } // Copy the package name in device endianness. - strcpy16_htod(pkg_header->name, arraysize(pkg_header->name), util::Utf8ToUtf16(package_->name)); + strcpy16_htod(pkg_header->name, arraysize(pkg_header->name), util::Utf8ToUtf16(package_.name)); // Serialize the types. We do this now so that our type and key strings // are populated. We write those first. @@ -285,7 +278,7 @@ class PackageFlattener { buffer->AppendBuffer(std::move(type_buffer)); // If there are libraries (or if the package ID is 0x00), encode a library chunk. - if (package_->id.value() == 0x00 || !shared_libs_->empty()) { + if (package_.id.value() == 0x00 || !shared_libs_->empty()) { FlattenLibrarySpec(buffer); } @@ -293,6 +286,10 @@ class PackageFlattener { return false; } + if (!FlattenAliases(buffer)) { + return false; + } + pkg_writer.Finish(); return true; } @@ -327,7 +324,7 @@ class PackageFlattener { } bool FlattenValue(FlatEntry* entry, BigBuffer* buffer) { - if (Item* item = ValueCast<Item>(entry->value)) { + if (const Item* item = ValueCast<Item>(entry->value)) { WriteEntry<ResTable_entry, true>(entry, buffer); Res_value* outValue = buffer->NextBlock<Res_value>(); CHECK(item->Flatten(outValue)) << "flatten failed"; @@ -341,7 +338,7 @@ class PackageFlattener { return true; } - bool FlattenConfig(const ResourceTableType* type, const ConfigDescription& config, + bool FlattenConfig(const ResourceTableTypeView& type, const ConfigDescription& config, const size_t num_total_entries, std::vector<FlatEntry>* entries, BigBuffer* buffer) { CHECK(num_total_entries != 0); @@ -349,7 +346,7 @@ class PackageFlattener { ChunkWriter type_writer(buffer); ResTable_type* type_header = type_writer.StartChunk<ResTable_type>(RES_TABLE_TYPE_TYPE); - type_header->id = type->id.value(); + type_header->id = type.id.value(); type_header->config = config; type_header->config.swapHtoD(); @@ -363,7 +360,7 @@ class PackageFlattener { if (!FlattenValue(&flat_entry, &values_buffer)) { diag_->Error(DiagMessage() << "failed to flatten resource '" - << ResourceNameRef(package_->name, type->type, flat_entry.entry->name) + << ResourceNameRef(package_.name, type.type, flat_entry.entry->name) << "' for configuration '" << config << "'"); return false; } @@ -411,55 +408,46 @@ class PackageFlattener { return true; } - std::vector<ResourceTableType*> CollectAndSortTypes() { - std::vector<ResourceTableType*> sorted_types; - for (auto& type : package_->types) { - if (type->type == ResourceType::kStyleable) { - // Styleables aren't real Resource Types, they are represented in the - // R.java file. - continue; - } - - CHECK(bool(type->id)) << "type must have an ID set"; - - sorted_types.push_back(type.get()); + bool FlattenAliases(BigBuffer* buffer) { + if (aliases_.empty()) { + return true; } - std::sort(sorted_types.begin(), sorted_types.end(), cmp_ids<ResourceTableType>); - return sorted_types; - } - std::vector<ResourceEntry*> CollectAndSortEntries(ResourceTableType* type) { - // Sort the entries by entry ID. - std::vector<ResourceEntry*> sorted_entries; - for (auto& entry : type->entries) { - CHECK(bool(entry->id)) << "entry must have an ID set"; - sorted_entries.push_back(entry.get()); + ChunkWriter alias_writer(buffer); + auto header = + alias_writer.StartChunk<ResTable_staged_alias_header>(RES_TABLE_STAGED_ALIAS_TYPE); + header->count = util::HostToDevice32(aliases_.size()); + + auto mapping = alias_writer.NextBlock<ResTable_staged_alias_entry>(aliases_.size()); + for (auto& p : aliases_) { + mapping->stagedResId = util::HostToDevice32(p.first); + mapping->finalizedResId = util::HostToDevice32(p.second); + ++mapping; } - std::sort(sorted_entries.begin(), sorted_entries.end(), cmp_ids<ResourceEntry>); - return sorted_entries; + alias_writer.Finish(); + return true; } bool FlattenOverlayable(BigBuffer* buffer) { std::set<ResourceId> seen_ids; std::map<std::string, OverlayableChunk> overlayable_chunks; - CHECK(bool(package_->id)) << "package must have an ID set when flattening <overlayable>"; - for (auto& type : package_->types) { - CHECK(bool(type->id)) << "type must have an ID set when flattening <overlayable>"; - for (auto& entry : type->entries) { - CHECK(bool(type->id)) << "entry must have an ID set when flattening <overlayable>"; - if (!entry->overlayable_item) { + CHECK(bool(package_.id)) << "package must have an ID set when flattening <overlayable>"; + for (auto& type : package_.types) { + CHECK(bool(type.id)) << "type must have an ID set when flattening <overlayable>"; + for (auto& entry : type.entries) { + CHECK(bool(type.id)) << "entry must have an ID set when flattening <overlayable>"; + if (!entry.overlayable_item) { continue; } - OverlayableItem& item = entry->overlayable_item.value(); + const OverlayableItem& item = entry.overlayable_item.value(); // Resource ids should only appear once in the resource table - ResourceId id = android::make_resid(package_->id.value(), type->id.value(), - entry->id.value()); + ResourceId id = android::make_resid(package_.id.value(), type.id.value(), entry.id.value()); CHECK(seen_ids.find(id) == seen_ids.end()) << "multiple overlayable definitions found for resource " - << ResourceName(package_->name, type->type, entry->name).to_string(); + << ResourceName(package_.name, type.type, entry.name).to_string(); seen_ids.insert(id); // Find the overlayable chunk with the specified name @@ -487,9 +475,8 @@ class PackageFlattener { if (item.policies == 0) { context_->GetDiagnostics()->Error(DiagMessage(item.overlayable->source) - << "overlayable " - << entry->name - << " does not specify policy"); + << "overlayable " << entry.name + << " does not specify policy"); return false; } @@ -554,14 +541,15 @@ class PackageFlattener { return true; } - bool FlattenTypeSpec(ResourceTableType* type, std::vector<ResourceEntry*>* sorted_entries, + bool FlattenTypeSpec(const ResourceTableTypeView& type, + const std::vector<ResourceTableEntryView>& sorted_entries, BigBuffer* buffer) { ChunkWriter type_spec_writer(buffer); ResTable_typeSpec* spec_header = type_spec_writer.StartChunk<ResTable_typeSpec>(RES_TABLE_TYPE_SPEC_TYPE); - spec_header->id = type->id.value(); + spec_header->id = type.id.value(); - if (sorted_entries->empty()) { + if (sorted_entries.empty()) { type_spec_writer.Finish(); return true; } @@ -569,7 +557,7 @@ class PackageFlattener { // We can't just take the size of the vector. There may be holes in the // entry ID space. // Since the entries are sorted by ID, the last one will be the biggest. - const size_t num_entries = sorted_entries->back()->id.value() + 1; + const size_t num_entries = sorted_entries.back().id.value() + 1; spec_header->entryCount = util::HostToDevice32(num_entries); @@ -577,22 +565,23 @@ class PackageFlattener { // show for which configuration axis the resource changes. uint32_t* config_masks = type_spec_writer.NextBlock<uint32_t>(num_entries); - const size_t actual_num_entries = sorted_entries->size(); - for (size_t entryIndex = 0; entryIndex < actual_num_entries; entryIndex++) { - ResourceEntry* entry = sorted_entries->at(entryIndex); + for (const ResourceTableEntryView& entry : sorted_entries) { + const uint16_t entry_id = entry.id.value(); // Populate the config masks for this entry. - - if (entry->visibility.level == Visibility::Level::kPublic) { - config_masks[entry->id.value()] |= util::HostToDevice32(ResTable_typeSpec::SPEC_PUBLIC); + uint32_t& entry_config_masks = config_masks[entry_id]; + if (entry.visibility.level == Visibility::Level::kPublic) { + entry_config_masks |= util::HostToDevice32(ResTable_typeSpec::SPEC_PUBLIC); + } + if (entry.visibility.staged_api) { + entry_config_masks |= util::HostToDevice32(ResTable_typeSpec::SPEC_STAGED_API); } - const size_t config_count = entry->values.size(); + const size_t config_count = entry.values.size(); for (size_t i = 0; i < config_count; i++) { - const ConfigDescription& config = entry->values[i]->config; + const ConfigDescription& config = entry.values[i]->config; for (size_t j = i + 1; j < config_count; j++) { - config_masks[entry->id.value()] |= - util::HostToDevice32(config.diff(entry->values[j]->config)); + config_masks[entry_id] |= util::HostToDevice32(config.diff(entry.values[j]->config)); } } } @@ -601,34 +590,30 @@ class PackageFlattener { } bool FlattenTypes(BigBuffer* buffer) { - // Sort the types by their IDs. They will be inserted into the StringPool in - // this order. - std::vector<ResourceTableType*> sorted_types = CollectAndSortTypes(); - size_t expected_type_id = 1; - for (ResourceTableType* type : sorted_types) { + for (const ResourceTableTypeView& type : package_.types) { + if (type.type == ResourceType::kStyleable || type.type == ResourceType::kMacro) { + // Styleables and macros are not real resource types. + continue; + } + // If there is a gap in the type IDs, fill in the StringPool // with empty values until we reach the ID we expect. - while (type->id.value() > expected_type_id) { + while (type.id.value() > expected_type_id) { std::stringstream type_name; type_name << "?" << expected_type_id; type_pool_.MakeRef(type_name.str()); expected_type_id++; } expected_type_id++; - type_pool_.MakeRef(to_string(type->type)); - - std::vector<ResourceEntry*> sorted_entries = CollectAndSortEntries(type); - if (sorted_entries.empty()) { - continue; - } + type_pool_.MakeRef(to_string(type.type)); - if (!FlattenTypeSpec(type, &sorted_entries, buffer)) { + if (!FlattenTypeSpec(type, type.entries, buffer)) { return false; } // Since the entries are sorted by ID, the last ID will be the largest. - const size_t num_entries = sorted_entries.back()->id.value() + 1; + const size_t num_entries = type.entries.back().id.value() + 1; // The binary resource table lists resource entries for each // configuration. @@ -641,20 +626,26 @@ class PackageFlattener { // hardcoded string uses characters which make it an invalid resource name const std::string obfuscated_resource_name = "0_resource_name_obfuscated"; - for (ResourceEntry* entry : sorted_entries) { + for (const ResourceTableEntryView& entry : type.entries) { + if (entry.staged_id) { + aliases_.insert(std::make_pair( + entry.staged_id.value().id.id, + ResourceId(package_.id.value(), type.id.value(), entry.id.value()).id)); + } + uint32_t local_key_index; - ResourceName resource_name({}, type->type, entry->name); + ResourceName resource_name({}, type.type, entry.name); if (!collapse_key_stringpool_ || name_collapse_exemptions_.find(resource_name) != name_collapse_exemptions_.end()) { - local_key_index = (uint32_t)key_pool_.MakeRef(entry->name).index(); + local_key_index = (uint32_t)key_pool_.MakeRef(entry.name).index(); } else { // resource isn't exempt from collapse, add it as obfuscated value local_key_index = (uint32_t)key_pool_.MakeRef(obfuscated_resource_name).index(); } // Group values by configuration. - for (auto& config_value : entry->values) { + for (auto& config_value : entry.values) { config_to_entry_list_map[config_value->config].push_back( - FlatEntry{entry, config_value->value.get(), local_key_index}); + FlatEntry{&entry, config_value->value.get(), local_key_index}); } } @@ -673,17 +664,17 @@ class PackageFlattener { ResTable_lib_header* lib_header = lib_writer.StartChunk<ResTable_lib_header>(RES_TABLE_LIBRARY_TYPE); - const size_t num_entries = (package_->id.value() == 0x00 ? 1 : 0) + shared_libs_->size(); + const size_t num_entries = (package_.id.value() == 0x00 ? 1 : 0) + shared_libs_->size(); CHECK(num_entries > 0); lib_header->count = util::HostToDevice32(num_entries); ResTable_lib_entry* lib_entry = buffer->NextBlock<ResTable_lib_entry>(num_entries); - if (package_->id.value() == 0x00) { + if (package_.id.value() == 0x00) { // Add this package lib_entry->packageId = util::HostToDevice32(0x00); strcpy16_htod(lib_entry->packageName, arraysize(lib_entry->packageName), - util::Utf8ToUtf16(package_->name)); + util::Utf8ToUtf16(package_.name)); ++lib_entry; } @@ -698,13 +689,14 @@ class PackageFlattener { IAaptContext* context_; IDiagnostics* diag_; - ResourceTablePackage* package_; + const ResourceTablePackageView package_; const std::map<size_t, std::string>* shared_libs_; bool use_sparse_entries_; StringPool type_pool_; StringPool key_pool_; bool collapse_key_stringpool_; const std::set<ResourceName>& name_collapse_exemptions_; + std::map<uint32_t, uint32_t> aliases_; }; } // namespace @@ -722,9 +714,11 @@ bool TableFlattener::Consume(IAaptContext* context, ResourceTable* table) { }); // Write the ResTable header. + const auto& table_view = + table->GetPartitionedView(ResourceTableViewOptions{.create_alias_entries = true}); ChunkWriter table_writer(buffer_); ResTable_header* table_header = table_writer.StartChunk<ResTable_header>(RES_TABLE_TYPE); - table_header->packageCount = util::HostToDevice32(table->packages.size()); + table_header->packageCount = util::HostToDevice32(table_view.packages.size()); // Flatten the values string pool. StringPool::FlattenUtf8(table_writer.buffer(), table->string_pool, @@ -733,24 +727,25 @@ bool TableFlattener::Consume(IAaptContext* context, ResourceTable* table) { BigBuffer package_buffer(1024); // Flatten each package. - for (auto& package : table->packages) { + for (auto& package : table_view.packages) { if (context->GetPackageType() == PackageType::kApp) { // Write a self mapping entry for this package if the ID is non-standard (0x7f). - const uint8_t package_id = package->id.value(); + CHECK((bool)package.id) << "Resource ids have not been assigned before flattening the table"; + const uint8_t package_id = package.id.value(); if (package_id != kFrameworkPackageId && package_id != kAppPackageId) { - auto result = table->included_packages_.insert({package_id, package->name}); - if (!result.second && result.first->second != package->name) { + auto result = table->included_packages_.insert({package_id, package.name}); + if (!result.second && result.first->second != package.name) { // A mapping for this package ID already exists, and is a different package. Error! context->GetDiagnostics()->Error( DiagMessage() << android::base::StringPrintf( "can't map package ID %02x to '%s'. Already mapped to '%s'", package_id, - package->name.c_str(), result.first->second.c_str())); + package.name.c_str(), result.first->second.c_str())); return false; } } } - PackageFlattener flattener(context, package.get(), &table->included_packages_, + PackageFlattener flattener(context, package, &table->included_packages_, options_.use_sparse_entries, options_.collapse_key_stringpool, options_.name_collapse_exemptions); if (!flattener.FlattenPackage(&package_buffer)) { diff --git a/tools/aapt2/format/binary/TableFlattener_test.cpp b/tools/aapt2/format/binary/TableFlattener_test.cpp index f8b8a1c4e35b..8139d7385092 100644 --- a/tools/aapt2/format/binary/TableFlattener_test.cpp +++ b/tools/aapt2/format/binary/TableFlattener_test.cpp @@ -155,7 +155,6 @@ class TableFlattenerTest : public ::testing::Test { TEST_F(TableFlattenerTest, FlattenFullyLinkedTable) { std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .SetPackageId("com.app.test", 0x7f) .AddSimple("com.app.test:id/one", ResourceId(0x7f020000)) .AddSimple("com.app.test:id/two", ResourceId(0x7f020001)) .AddValue("com.app.test:id/three", ResourceId(0x7f020002), @@ -204,7 +203,6 @@ TEST_F(TableFlattenerTest, FlattenFullyLinkedTable) { TEST_F(TableFlattenerTest, FlattenEntriesWithGapsInIds) { std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .SetPackageId("com.app.test", 0x7f) .AddSimple("com.app.test:id/one", ResourceId(0x7f020001)) .AddSimple("com.app.test:id/three", ResourceId(0x7f020003)) .Build(); @@ -225,7 +223,6 @@ TEST_F(TableFlattenerTest, FlattenMinMaxAttributes) { attr.max_int = 23; std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .SetPackageId("android", 0x01) .AddValue("android:attr/foo", ResourceId(0x01010000), util::make_unique<Attribute>(attr)) .Build(); @@ -248,7 +245,6 @@ TEST_F(TableFlattenerTest, FlattenArray) { 2u)); std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .SetPackageId("android", 0x01) .AddValue("android:array/foo", ResourceId(0x01010000), std::move(array)) .Build(); @@ -300,10 +296,10 @@ static std::unique_ptr<ResourceTable> BuildTableWithSparseEntries( IAaptContext* context, const ConfigDescription& sparse_config, float load) { std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .SetPackageId(context->GetCompilationPackage(), context->GetPackageId()) .Build(); // Add regular entries. + CloningValueTransformer cloner(&table->string_pool); int stride = static_cast<int>(1.0f / load); for (int i = 0; i < 100; i++) { const ResourceName name = test::ParseNameOrDie( @@ -311,15 +307,20 @@ static std::unique_ptr<ResourceTable> BuildTableWithSparseEntries( const ResourceId resid(context->GetPackageId(), 0x02, static_cast<uint16_t>(i)); const auto value = util::make_unique<BinaryPrimitive>(Res_value::TYPE_INT_DEC, static_cast<uint32_t>(i)); - CHECK(table->AddResourceWithId(name, resid, ConfigDescription::DefaultConfig(), "", - std::unique_ptr<Value>(value->Clone(nullptr)), - context->GetDiagnostics())); + CHECK(table->AddResource(NewResourceBuilder(name) + .SetId(resid) + .SetValue(std::unique_ptr<Value>(value->Transform(cloner))) + .Build(), + context->GetDiagnostics())); // Every few entries, write out a sparse_config value. This will give us the desired load. if (i % stride == 0) { - CHECK(table->AddResourceWithId(name, resid, sparse_config, "", - std::unique_ptr<Value>(value->Clone(nullptr)), - context->GetDiagnostics())); + CHECK(table->AddResource( + NewResourceBuilder(name) + .SetId(resid) + .SetValue(std::unique_ptr<Value>(value->Transform(cloner)), sparse_config) + .Build(), + context->GetDiagnostics())); } } return table; @@ -417,7 +418,6 @@ TEST_F(TableFlattenerTest, FlattenSharedLibrary) { test::ContextBuilder().SetCompilationPackage("lib").SetPackageId(0x00).Build(); std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .SetPackageId("lib", 0x00) .AddValue("lib:id/foo", ResourceId(0x00010000), util::make_unique<Id>()) .Build(); ResourceTable result; @@ -426,7 +426,7 @@ TEST_F(TableFlattenerTest, FlattenSharedLibrary) { Maybe<ResourceTable::SearchResult> search_result = result.FindResource(test::ParseNameOrDie("lib:id/foo")); ASSERT_TRUE(search_result); - EXPECT_EQ(0x00u, search_result.value().package->id.value()); + EXPECT_EQ(0x00u, search_result.value().entry->id.value().package_id()); auto iter = result.included_packages_.find(0x00); ASSERT_NE(result.included_packages_.end(), iter); @@ -438,7 +438,6 @@ TEST_F(TableFlattenerTest, FlattenSharedLibraryWithStyle) { test::ContextBuilder().SetCompilationPackage("lib").SetPackageId(0x00).Build(); std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .SetPackageId("lib", 0x00) .AddValue("lib:style/Theme", ResourceId(0x00030001), test::StyleBuilder() @@ -458,9 +457,7 @@ TEST_F(TableFlattenerTest, FlattenSharedLibraryWithStyle) { Maybe<ResourceTable::SearchResult> search_result = result.FindResource(test::ParseNameOrDie("lib:style/Theme")); ASSERT_TRUE(search_result); - EXPECT_EQ(0x00u, search_result.value().package->id.value()); - EXPECT_EQ(0x03u, search_result.value().type->id.value()); - EXPECT_EQ(0x01u, search_result.value().entry->id.value()); + EXPECT_EQ(0x00030001u, search_result.value().entry->id.value()); ASSERT_EQ(1u, search_result.value().entry->values.size()); Value* value = search_result.value().entry->values[0]->value.get(); Style* style = ValueCast<Style>(value); @@ -479,7 +476,6 @@ TEST_F(TableFlattenerTest, FlattenTableReferencingSharedLibraries) { test::ContextBuilder().SetCompilationPackage("app").SetPackageId(0x7f).Build(); std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .SetPackageId("app", 0x7f) .AddValue("app:id/foo", ResourceId(0x7f010000), test::BuildReference("lib_one:id/foo", ResourceId(0x02010000))) .AddValue("app:id/bar", ResourceId(0x7f010001), @@ -509,7 +505,6 @@ TEST_F(TableFlattenerTest, PackageWithNonStandardIdHasDynamicRefTable) { std::unique_ptr<IAaptContext> context = test::ContextBuilder().SetCompilationPackage("app").SetPackageId(0x80).Build(); std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .SetPackageId("app", 0x80) .AddSimple("app:id/foo", ResourceId(0x80010000)) .Build(); @@ -532,7 +527,6 @@ TEST_F(TableFlattenerTest, LongPackageNameIsTruncated) { test::ContextBuilder().SetCompilationPackage(kPackageName).SetPackageId(0x7f).Build(); std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .SetPackageId(kPackageName, 0x7f) .AddSimple(kPackageName + ":id/foo", ResourceId(0x7f010000)) .Build(); @@ -553,7 +547,6 @@ TEST_F(TableFlattenerTest, LongSharedLibraryPackageNameIsIllegal) { .Build(); std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .SetPackageId(kPackageName, 0x7f) .AddSimple(kPackageName + ":id/foo", ResourceId(0x7f010000)) .Build(); @@ -564,7 +557,6 @@ TEST_F(TableFlattenerTest, LongSharedLibraryPackageNameIsIllegal) { TEST_F(TableFlattenerTest, ObfuscatingResourceNamesNoNameCollapseExemptionsSucceeds) { std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .SetPackageId("com.app.test", 0x7f) .AddSimple("com.app.test:id/one", ResourceId(0x7f020000)) .AddSimple("com.app.test:id/two", ResourceId(0x7f020001)) .AddValue("com.app.test:id/three", ResourceId(0x7f020002), @@ -618,7 +610,6 @@ TEST_F(TableFlattenerTest, ObfuscatingResourceNamesNoNameCollapseExemptionsSucce TEST_F(TableFlattenerTest, ObfuscatingResourceNamesWithNameCollapseExemptionsSucceeds) { std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .SetPackageId("com.app.test", 0x7f) .AddSimple("com.app.test:id/one", ResourceId(0x7f020000)) .AddSimple("com.app.test:id/two", ResourceId(0x7f020001)) .AddValue("com.app.test:id/three", ResourceId(0x7f020002), @@ -680,7 +671,6 @@ TEST_F(TableFlattenerTest, FlattenOverlayable) { std::string name = "com.app.test:integer/overlayable"; std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .SetPackageId("com.app.test", 0x7f) .AddSimple(name, ResourceId(0x7f020000)) .SetOverlayable(name, overlayable_item) .Build(); @@ -717,7 +707,6 @@ TEST_F(TableFlattenerTest, FlattenMultipleOverlayablePolicies) { std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .SetPackageId("com.app.test", 0x7f) .AddSimple(name_zero, ResourceId(0x7f020000)) .SetOverlayable(name_zero, overlayable_item_zero) .AddSimple(name_one, ResourceId(0x7f020001)) @@ -780,7 +769,6 @@ TEST_F(TableFlattenerTest, FlattenMultipleOverlayable) { std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .SetPackageId("com.app.test", 0x7f) .AddSimple(name_zero, ResourceId(0x7f020000)) .SetOverlayable(name_zero, overlayable_item_zero) .AddSimple(name_one, ResourceId(0x7f020001)) @@ -842,7 +830,6 @@ TEST_F(TableFlattenerTest, FlattenOverlayableNoPolicyFails) { std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .SetPackageId("com.app.test", 0x7f) .AddSimple(name_zero, ResourceId(0x7f020000)) .SetOverlayable(name_zero, overlayable_item_zero) .Build(); diff --git a/tools/aapt2/format/binary/XmlFlattener_test.cpp b/tools/aapt2/format/binary/XmlFlattener_test.cpp index c24488b16153..d97e8882e5a2 100644 --- a/tools/aapt2/format/binary/XmlFlattener_test.cpp +++ b/tools/aapt2/format/binary/XmlFlattener_test.cpp @@ -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). diff --git a/tools/aapt2/format/proto/ProtoDeserialize.cpp b/tools/aapt2/format/proto/ProtoDeserialize.cpp index 7eb8ebd9a043..236c38167545 100644 --- a/tools/aapt2/format/proto/ProtoDeserialize.cpp +++ b/tools/aapt2/format/proto/ProtoDeserialize.cpp @@ -425,15 +425,9 @@ static bool DeserializePackageFromPb(const pb::Package& pb_package, const ResStr io::IFileCollection* files, const std::vector<std::shared_ptr<Overlayable>>& overlayables, ResourceTable* out_table, std::string* out_error) { - Maybe<uint8_t> id; - if (pb_package.has_package_id()) { - id = static_cast<uint8_t>(pb_package.package_id().id()); - } - std::map<ResourceId, ResourceNameRef> id_index; - ResourceTablePackage* pkg = - out_table->CreatePackageAllowingDuplicateNames(pb_package.package_name(), id); + ResourceTablePackage* pkg = out_table->FindOrCreatePackage(pb_package.package_name()); for (const pb::Type& pb_type : pb_package.type()) { const ResourceType* res_type = ParseResourceType(pb_type.name()); if (res_type == nullptr) { @@ -444,14 +438,15 @@ static bool DeserializePackageFromPb(const pb::Package& pb_package, const ResStr } ResourceTableType* type = pkg->FindOrCreateType(*res_type); - if (pb_type.has_type_id()) { - type->id = static_cast<uint8_t>(pb_type.type_id().id()); - } for (const pb::Entry& pb_entry : pb_type.entry()) { - ResourceEntry* entry = type->FindOrCreateEntry(pb_entry.name()); - if (pb_entry.has_entry_id()) { - entry->id = static_cast<uint16_t>(pb_entry.entry_id().id()); + ResourceEntry* entry = type->CreateEntry(pb_entry.name()); + const ResourceId resource_id( + pb_package.has_package_id() ? static_cast<uint8_t>(pb_package.package_id().id()) : 0u, + pb_type.has_type_id() ? static_cast<uint8_t>(pb_type.type_id().id()) : 0u, + pb_entry.has_entry_id() ? static_cast<uint16_t>(pb_entry.entry_id().id()) : 0u); + if (resource_id.id != 0u) { + entry->id = resource_id; } // Deserialize the symbol status (public/private with source and comments). @@ -461,6 +456,7 @@ static bool DeserializePackageFromPb(const pb::Package& pb_package, const ResStr DeserializeSourceFromPb(pb_visibility.source(), src_pool, &entry->visibility.source); } entry->visibility.comment = pb_visibility.comment(); + entry->visibility.staged_api = pb_visibility.staged_api(); const Visibility::Level level = DeserializeVisibilityFromPb(pb_visibility.level()); entry->visibility.level = level; @@ -490,8 +486,10 @@ static bool DeserializePackageFromPb(const pb::Package& pb_package, const ResStr // Find the overlayable to which this item belongs pb::OverlayableItem pb_overlayable_item = pb_entry.overlayable_item(); if (pb_overlayable_item.overlayable_idx() >= overlayables.size()) { - *out_error = android::base::StringPrintf("invalid overlayable_idx value %d", - pb_overlayable_item.overlayable_idx()); + *out_error = + android::base::StringPrintf("invalid overlayable_idx value %d for entry %s/%s", + pb_overlayable_item.overlayable_idx(), + pb_type.name().c_str(), pb_entry.name().c_str()); return false; } @@ -500,10 +498,20 @@ static bool DeserializePackageFromPb(const pb::Package& pb_package, const ResStr out_error)) { return false; } - entry->overlayable_item = std::move(overlayable_item); } + if (pb_entry.has_staged_id()) { + const pb::StagedId& pb_staged_id = pb_entry.staged_id(); + + StagedId staged_id; + if (pb_staged_id.has_source()) { + DeserializeSourceFromPb(pb_staged_id.source(), src_pool, &staged_id.source); + } + staged_id.id = pb_staged_id.staged_id(); + entry->staged_id = std::move(staged_id); + } + ResourceId resid(pb_package.package_id().id(), pb_type.type_id().id(), pb_entry.entry_id().id()); if (resid.is_valid()) { @@ -658,6 +666,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; } @@ -803,6 +843,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; diff --git a/tools/aapt2/format/proto/ProtoSerialize.cpp b/tools/aapt2/format/proto/ProtoSerialize.cpp index 831229ffa383..6042ba89bf8a 100644 --- a/tools/aapt2/format/proto/ProtoSerialize.cpp +++ b/tools/aapt2/format/proto/ProtoSerialize.cpp @@ -345,51 +345,71 @@ void SerializeTableToPb(const ResourceTable& table, pb::ResourceTable* out_table pb_fingerprint->set_version(util::GetToolFingerprint()); std::vector<Overlayable*> overlayables; - for (const std::unique_ptr<ResourceTablePackage>& package : table.packages) { + auto table_view = table.GetPartitionedView(); + for (const auto& package : table_view.packages) { pb::Package* pb_package = out_table->add_package(); - if (package->id) { - pb_package->mutable_package_id()->set_id(package->id.value()); + if (package.id) { + pb_package->mutable_package_id()->set_id(package.id.value()); } - pb_package->set_package_name(package->name); + pb_package->set_package_name(package.name); - for (const std::unique_ptr<ResourceTableType>& type : package->types) { + for (const auto& type : package.types) { pb::Type* pb_type = pb_package->add_type(); - if (type->id) { - pb_type->mutable_type_id()->set_id(type->id.value()); + if (type.id) { + pb_type->mutable_type_id()->set_id(type.id.value()); } - pb_type->set_name(to_string(type->type).to_string()); + pb_type->set_name(to_string(type.type).to_string()); - for (const std::unique_ptr<ResourceEntry>& entry : type->entries) { + // hardcoded string uses characters which make it an invalid resource name + static const char* obfuscated_resource_name = "0_resource_name_obfuscated"; + for (const auto& entry : type.entries) { pb::Entry* pb_entry = pb_type->add_entry(); - if (entry->id) { - pb_entry->mutable_entry_id()->set_id(entry->id.value()); + if (entry.id) { + pb_entry->mutable_entry_id()->set_id(entry.id.value()); + } + ResourceName resource_name({}, type.type, entry.name); + if (options.collapse_key_stringpool && + options.name_collapse_exemptions.find(resource_name) == + options.name_collapse_exemptions.end()) { + pb_entry->set_name(obfuscated_resource_name); + } else { + pb_entry->set_name(entry.name); } - pb_entry->set_name(entry->name); // Write the Visibility struct. pb::Visibility* pb_visibility = pb_entry->mutable_visibility(); - pb_visibility->set_level(SerializeVisibilityToPb(entry->visibility.level)); + pb_visibility->set_staged_api(entry.visibility.staged_api); + pb_visibility->set_level(SerializeVisibilityToPb(entry.visibility.level)); if (source_pool != nullptr) { - SerializeSourceToPb(entry->visibility.source, source_pool.get(), + SerializeSourceToPb(entry.visibility.source, source_pool.get(), pb_visibility->mutable_source()); } - pb_visibility->set_comment(entry->visibility.comment); + pb_visibility->set_comment(entry.visibility.comment); - if (entry->allow_new) { + if (entry.allow_new) { pb::AllowNew* pb_allow_new = pb_entry->mutable_allow_new(); if (source_pool != nullptr) { - SerializeSourceToPb(entry->allow_new.value().source, source_pool.get(), + SerializeSourceToPb(entry.allow_new.value().source, source_pool.get(), pb_allow_new->mutable_source()); } - pb_allow_new->set_comment(entry->allow_new.value().comment); + pb_allow_new->set_comment(entry.allow_new.value().comment); } - if (entry->overlayable_item) { - SerializeOverlayableItemToPb(entry->overlayable_item.value(), overlayables, + if (entry.overlayable_item) { + SerializeOverlayableItemToPb(entry.overlayable_item.value(), overlayables, source_pool.get(), pb_entry, out_table); } - for (const std::unique_ptr<ResourceConfigValue>& config_value : entry->values) { + if (entry.staged_id) { + pb::StagedId* pb_staged_id = pb_entry->mutable_staged_id(); + if (source_pool != nullptr) { + SerializeSourceToPb(entry.staged_id.value().source, source_pool.get(), + pb_staged_id->mutable_source()); + } + pb_staged_id->set_staged_id(entry.staged_id.value().id.id); + } + + for (const ResourceConfigValue* config_value : entry.values) { pb::ConfigValue* pb_config_value = pb_entry->add_config_value(); SerializeConfig(config_value->config, pb_config_value->mutable_config()); pb_config_value->mutable_config()->set_product(config_value->product); @@ -429,6 +449,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> @@ -632,6 +682,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; } diff --git a/tools/aapt2/format/proto/ProtoSerialize.h b/tools/aapt2/format/proto/ProtoSerialize.h index 7a3ea9903732..b0d56307fbe4 100644 --- a/tools/aapt2/format/proto/ProtoSerialize.h +++ b/tools/aapt2/format/proto/ProtoSerialize.h @@ -38,6 +38,15 @@ struct SerializeXmlOptions { struct SerializeTableOptions { /** Prevent serializing the source pool and source protos. */ bool exclude_sources = false; + + // When true, all the entry names in pb:ResourceTable are collapsed to a + // single entry name. When the proto table is converted to binary + // resources.arsc, the key string pool is collapsed to a single entry. All + // resource entries have name indices that point to this single value. + bool collapse_key_stringpool = false; + + // Set of resources to avoid collapsing to a single entry in key stringpool. + std::set<ResourceName> name_collapse_exemptions; }; // Serializes a Value to its protobuf representation. An optional StringPool will hold the diff --git a/tools/aapt2/format/proto/ProtoSerialize_test.cpp b/tools/aapt2/format/proto/ProtoSerialize_test.cpp index 1a7de6dc1c48..38c811fe3619 100644 --- a/tools/aapt2/format/proto/ProtoSerialize_test.cpp +++ b/tools/aapt2/format/proto/ProtoSerialize_test.cpp @@ -24,6 +24,7 @@ using ::android::ConfigDescription; using ::android::StringPiece; using ::testing::Eq; using ::testing::IsEmpty; +using ::testing::IsNull; using ::testing::NotNull; using ::testing::SizeIs; using ::testing::StrEq; @@ -39,11 +40,67 @@ class MockFileCollection : public io::IFileCollection { MOCK_METHOD0(GetDirSeparator, char()); }; +ResourceEntry* GetEntry(ResourceTable* table, const ResourceNameRef& res_name) { + auto result = table->FindResource(res_name); + return (result) ? result.value().entry : nullptr; +} + +ResourceEntry* GetEntry(ResourceTable* table, const ResourceNameRef& res_name, ResourceId id) { + auto result = table->FindResource(res_name, id); + return (result) ? result.value().entry : nullptr; +} + +TEST(ProtoSerializeTest, SerializeVisibility) { + std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .Add(NewResourceBuilder("com.app.a:bool/foo") + .SetVisibility({Visibility::Level::kUndefined}) + .Build()) + .Add(NewResourceBuilder("com.app.a:bool/bar") + .SetVisibility({Visibility::Level::kPrivate}) + .Build()) + .Add(NewResourceBuilder("com.app.a:bool/baz") + .SetVisibility({Visibility::Level::kPublic}) + .Build()) + .Add(NewResourceBuilder("com.app.a:bool/fiz") + .SetVisibility({.level = Visibility::Level::kPublic, .staged_api = true}) + .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()); + + auto search_result = new_table.FindResource(test::ParseNameOrDie("com.app.a:bool/foo")); + ASSERT_TRUE(search_result); + EXPECT_THAT(search_result.value().entry->visibility.level, Eq(Visibility::Level::kUndefined)); + EXPECT_FALSE(search_result.value().entry->visibility.staged_api); + + search_result = new_table.FindResource(test::ParseNameOrDie("com.app.a:bool/bar")); + ASSERT_TRUE(search_result); + EXPECT_THAT(search_result.value().entry->visibility.level, Eq(Visibility::Level::kPrivate)); + EXPECT_FALSE(search_result.value().entry->visibility.staged_api); + + search_result = new_table.FindResource(test::ParseNameOrDie("com.app.a:bool/baz")); + ASSERT_TRUE(search_result); + EXPECT_THAT(search_result.value().entry->visibility.level, Eq(Visibility::Level::kPublic)); + EXPECT_FALSE(search_result.value().entry->visibility.staged_api); + + search_result = new_table.FindResource(test::ParseNameOrDie("com.app.a:bool/fiz")); + ASSERT_TRUE(search_result); + EXPECT_THAT(search_result.value().entry->visibility.level, Eq(Visibility::Level::kPublic)); + EXPECT_TRUE(search_result.value().entry->visibility.staged_api); +} + TEST(ProtoSerializeTest, SerializeSinglePackage) { std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .SetPackageId("com.app.a", 0x7f) .AddFileReference("com.app.a:layout/main", ResourceId(0x7f020000), "res/layout/main.xml") .AddReference("com.app.a:layout/other", ResourceId(0x7f020001), "com.app.a:layout/main") .AddString("com.app.a:string/text", {}, "hi") @@ -52,11 +109,11 @@ TEST(ProtoSerializeTest, SerializeSinglePackage) { true /*allow_new*/) .Build(); - Visibility public_symbol; - public_symbol.level = Visibility::Level::kPublic; - ASSERT_TRUE(table->SetVisibilityWithId(test::ParseNameOrDie("com.app.a:layout/main"), - public_symbol, ResourceId(0x7f020000), - context->GetDiagnostics())); + ASSERT_TRUE(table->AddResource(NewResourceBuilder(test::ParseNameOrDie("com.app.a:layout/main")) + .SetId(0x7f020000) + .SetVisibility({Visibility::Level::kPublic}) + .Build(), + context->GetDiagnostics())); Id* id = test::GetValue<Id>(table.get(), "com.app.a:id/foo"); ASSERT_THAT(id, NotNull()); @@ -64,25 +121,35 @@ TEST(ProtoSerializeTest, SerializeSinglePackage) { // Make a plural. std::unique_ptr<Plural> plural = util::make_unique<Plural>(); plural->values[Plural::One] = util::make_unique<String>(table->string_pool.MakeRef("one")); - ASSERT_TRUE(table->AddResource(test::ParseNameOrDie("com.app.a:plurals/hey"), ConfigDescription{}, - {}, std::move(plural), context->GetDiagnostics())); + ASSERT_TRUE(table->AddResource(NewResourceBuilder(test::ParseNameOrDie("com.app.a:plurals/hey")) + .SetValue(std::move(plural)) + .Build(), + 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( + NewResourceBuilder(test::ParseNameOrDie("com.app.a:string/styled")) + .SetValue(util::make_unique<StyledString>(table->string_pool.MakeRef(style_string))) + .Build(), + context->GetDiagnostics())); + + // Make a resource with different products. ASSERT_TRUE( - table->AddResource(test::ParseNameOrDie("com.app.a:string/styled"), ConfigDescription{}, {}, - util::make_unique<StyledString>(table->string_pool.MakeRef(style_string)), + table->AddResource(NewResourceBuilder(test::ParseNameOrDie("com.app.a:integer/one")) + .SetValue(test::BuildPrimitive(android::Res_value::TYPE_INT_DEC, 123u), + test::ParseConfigOrDie("land")) + .Build(), context->GetDiagnostics())); - // Make a resource with different products. - ASSERT_TRUE(table->AddResource( - test::ParseNameOrDie("com.app.a:integer/one"), test::ParseConfigOrDie("land"), {}, - test::BuildPrimitive(android::Res_value::TYPE_INT_DEC, 123u), context->GetDiagnostics())); - ASSERT_TRUE(table->AddResource( - test::ParseNameOrDie("com.app.a:integer/one"), test::ParseConfigOrDie("land"), "tablet", - test::BuildPrimitive(android::Res_value::TYPE_INT_HEX, 321u), context->GetDiagnostics())); + ASSERT_TRUE( + table->AddResource(NewResourceBuilder(test::ParseNameOrDie("com.app.a:integer/one")) + .SetValue(test::BuildPrimitive(android::Res_value::TYPE_INT_HEX, 321u), + test::ParseConfigOrDie("land"), "tablet") + .Build(), + 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 @@ -90,16 +157,20 @@ TEST(ProtoSerializeTest, SerializeSinglePackage) { Reference expected_ref; expected_ref.name = test::ParseNameOrDie("android:layout/main"); expected_ref.id = ResourceId(0x01020000); - ASSERT_TRUE(table->AddResource( - test::ParseNameOrDie("com.app.a:layout/abc"), ConfigDescription::DefaultConfig(), {}, - util::make_unique<Reference>(expected_ref), context->GetDiagnostics())); + ASSERT_TRUE(table->AddResource(NewResourceBuilder(test::ParseNameOrDie("com.app.a:layout/abc")) + .SetValue(util::make_unique<Reference>(expected_ref)) + .Build(), + context->GetDiagnostics())); // Make an overlayable resource. OverlayableItem overlayable_item(std::make_shared<Overlayable>( "OverlayableName", "overlay://theme", Source("res/values/overlayable.xml", 40))); overlayable_item.source = Source("res/values/overlayable.xml", 42); - ASSERT_TRUE(table->SetOverlayable(test::ParseNameOrDie("com.app.a:integer/overlayable"), - overlayable_item, test::GetDiagnostics())); + ASSERT_TRUE( + table->AddResource(NewResourceBuilder(test::ParseNameOrDie("com.app.a:integer/overlayable")) + .SetOverlayable(overlayable_item) + .Build(), + context->GetDiagnostics())); pb::ResourceTable pb_table; SerializeTableToPb(*table, &pb_table, context->GetDiagnostics()); @@ -662,4 +733,222 @@ TEST(ProtoSerializeTest, SerializeAndDeserializeNonDynamicReference) { EXPECT_FALSE(actual_ref->is_dynamic); } +TEST(ProtoSerializeTest, CollapsingResourceNamesNoNameCollapseExemptionsSucceeds) { + const uint32_t id_one_id = 0x7f020000; + const uint32_t id_two_id = 0x7f020001; + const uint32_t id_three_id = 0x7f020002; + const uint32_t integer_three_id = 0x7f030000; + const uint32_t string_test_id = 0x7f040000; + const uint32_t layout_bar_id = 0x7f050000; + std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .AddSimple("com.app.test:id/one", ResourceId(id_one_id)) + .AddSimple("com.app.test:id/two", ResourceId(id_two_id)) + .AddValue("com.app.test:id/three", ResourceId(id_three_id), + test::BuildReference("com.app.test:id/one", ResourceId(id_one_id))) + .AddValue("com.app.test:integer/one", ResourceId(integer_three_id), + util::make_unique<BinaryPrimitive>( + uint8_t(android::Res_value::TYPE_INT_DEC), 1u)) + .AddValue("com.app.test:integer/one", test::ParseConfigOrDie("v1"), + ResourceId(integer_three_id), + util::make_unique<BinaryPrimitive>( + uint8_t(android::Res_value::TYPE_INT_DEC), 2u)) + .AddString("com.app.test:string/test", ResourceId(string_test_id), "foo") + .AddFileReference("com.app.test:layout/bar", ResourceId(layout_bar_id), + "res/layout/bar.xml") + .Build(); + + SerializeTableOptions options; + options.collapse_key_stringpool = true; + + pb::ResourceTable pb_table; + + SerializeTableToPb(*table, &pb_table, context->GetDiagnostics(), options); + test::TestFile file_a("res/layout/bar.xml"); + MockFileCollection files; + EXPECT_CALL(files, FindFile(Eq("res/layout/bar.xml"))) + .WillRepeatedly(::testing::Return(&file_a)); + ResourceTable new_table; + std::string error; + ASSERT_TRUE(DeserializeTableFromPb(pb_table, &files, &new_table, &error)) << error; + EXPECT_THAT(error, IsEmpty()); + + ResourceName real_id_resource( + "com.app.test", ResourceType::kId, "one"); + EXPECT_THAT(GetEntry(&new_table, real_id_resource), IsNull()); + + ResourceName obfuscated_id_resource( + "com.app.test", ResourceType::kId, "0_resource_name_obfuscated"); + + EXPECT_THAT(GetEntry(&new_table, obfuscated_id_resource, + id_one_id), NotNull()); + EXPECT_THAT(GetEntry(&new_table, obfuscated_id_resource, + id_two_id), NotNull()); + ResourceEntry* entry = GetEntry(&new_table, obfuscated_id_resource, id_three_id); + EXPECT_THAT(entry, NotNull()); + ResourceConfigValue* config_value = entry->FindValue({}); + Reference* ref = ValueCast<Reference>(config_value->value.get()); + EXPECT_THAT(ref->id.value(), Eq(id_one_id)); + + ResourceName obfuscated_integer_resource( + "com.app.test", ResourceType::kInteger, "0_resource_name_obfuscated"); + entry = GetEntry(&new_table, obfuscated_integer_resource, integer_three_id); + EXPECT_THAT(entry, NotNull()); + config_value = entry->FindValue({}); + BinaryPrimitive* bp = ValueCast<BinaryPrimitive>(config_value->value.get()); + EXPECT_THAT(bp->value.data, Eq(1u)); + + config_value = entry->FindValue(test::ParseConfigOrDie("v1")); + bp = ValueCast<BinaryPrimitive>(config_value->value.get()); + EXPECT_THAT(bp->value.data, Eq(2u)); + + ResourceName obfuscated_string_resource( + "com.app.test", ResourceType::kString, "0_resource_name_obfuscated"); + entry = GetEntry(&new_table, obfuscated_string_resource, string_test_id); + EXPECT_THAT(entry, NotNull()); + config_value = entry->FindValue({}); + String* s = ValueCast<String>(config_value->value.get()); + EXPECT_THAT(*(s->value), Eq("foo")); + + ResourceName obfuscated_layout_resource( + "com.app.test", ResourceType::kLayout, "0_resource_name_obfuscated"); + entry = GetEntry(&new_table, obfuscated_layout_resource, layout_bar_id); + EXPECT_THAT(entry, NotNull()); + config_value = entry->FindValue({}); + FileReference* f = ValueCast<FileReference>(config_value->value.get()); + EXPECT_THAT(*(f->path), Eq("res/layout/bar.xml")); +} + +TEST(ProtoSerializeTest, ObfuscatingResourceNamesWithNameCollapseExemptionsSucceeds) { + const uint32_t id_one_id = 0x7f020000; + const uint32_t id_two_id = 0x7f020001; + const uint32_t id_three_id = 0x7f020002; + const uint32_t integer_three_id = 0x7f030000; + const uint32_t string_test_id = 0x7f040000; + std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .AddSimple("com.app.test:id/one", ResourceId(id_one_id)) + .AddSimple("com.app.test:id/two", ResourceId(id_two_id)) + .AddValue("com.app.test:id/three", ResourceId(id_three_id), + test::BuildReference("com.app.test:id/one", ResourceId(id_one_id))) + .AddValue("com.app.test:integer/one", ResourceId(integer_three_id), + util::make_unique<BinaryPrimitive>( + uint8_t(android::Res_value::TYPE_INT_DEC), 1u)) + .AddValue("com.app.test:integer/one", test::ParseConfigOrDie("v1"), + ResourceId(integer_three_id), + util::make_unique<BinaryPrimitive>( + uint8_t(android::Res_value::TYPE_INT_DEC), 2u)) + .AddString("com.app.test:string/test", ResourceId(string_test_id), "foo") + .Build(); + + SerializeTableOptions options; + options.collapse_key_stringpool = true; + options.name_collapse_exemptions.insert(ResourceName({}, ResourceType::kId, "one")); + options.name_collapse_exemptions.insert(ResourceName({}, ResourceType::kString, "test")); + pb::ResourceTable pb_table; + + SerializeTableToPb(*table, &pb_table, context->GetDiagnostics(), options); + MockFileCollection files; + ResourceTable new_table; + std::string error; + ASSERT_TRUE(DeserializeTableFromPb(pb_table, &files, &new_table, &error)) << error; + EXPECT_THAT(error, IsEmpty()); + + EXPECT_THAT(GetEntry(&new_table, ResourceName("com.app.test", ResourceType::kId, "one"), + id_one_id), NotNull()); + ResourceName obfuscated_id_resource( + "com.app.test", ResourceType::kId, "0_resource_name_obfuscated"); + EXPECT_THAT(GetEntry(&new_table, obfuscated_id_resource, id_one_id), IsNull()); + + ResourceName real_id_resource( + "com.app.test", ResourceType::kId, "two"); + EXPECT_THAT(GetEntry(&new_table, real_id_resource, id_two_id), IsNull()); + EXPECT_THAT(GetEntry(&new_table, obfuscated_id_resource, id_two_id), NotNull()); + + ResourceEntry* entry = GetEntry(&new_table, obfuscated_id_resource, id_three_id); + EXPECT_THAT(entry, NotNull()); + ResourceConfigValue* config_value = entry->FindValue({}); + Reference* ref = ValueCast<Reference>(config_value->value.get()); + EXPECT_THAT(ref->id.value(), Eq(id_one_id)); + + // Note that this resource is also named "one", but it's a different type, so gets obfuscated. + ResourceName obfuscated_integer_resource( + "com.app.test", ResourceType::kInteger, "0_resource_name_obfuscated"); + entry = GetEntry(&new_table, obfuscated_integer_resource, integer_three_id); + EXPECT_THAT(entry, NotNull()); + config_value = entry->FindValue({}); + BinaryPrimitive* bp = ValueCast<BinaryPrimitive>(config_value->value.get()); + EXPECT_THAT(bp->value.data, Eq(1u)); + + config_value = entry->FindValue(test::ParseConfigOrDie("v1")); + bp = ValueCast<BinaryPrimitive>(config_value->value.get()); + EXPECT_THAT(bp->value.data, Eq(2u)); + + entry = GetEntry(&new_table, ResourceName("com.app.test", ResourceType::kString, "test"), + string_test_id); + EXPECT_THAT(entry, NotNull()); + config_value = entry->FindValue({}); + String* s = ValueCast<String>(config_value->value.get()); + 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)); +} + +TEST(ProtoSerializeTest, StagedId) { + CloningValueTransformer cloner(nullptr); + std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); + std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() + .Add(NewResourceBuilder("com.app.a:string/foo") + .SetStagedId(StagedId{.id = 0x01ff0001}) + .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()); + + auto result = new_table.FindResource(test::ParseNameOrDie("com.app.a:string/foo")); + ASSERT_TRUE(result); + ASSERT_TRUE(result.value().entry->staged_id); + EXPECT_THAT(result.value().entry->staged_id.value().id, Eq(ResourceId(0x01ff0001))); +} + } // namespace aapt diff --git a/tools/aapt2/java/ClassDefinition.h b/tools/aapt2/java/ClassDefinition.h index 1e4b6816075a..2acdadb3c034 100644 --- a/tools/aapt2/java/ClassDefinition.h +++ b/tools/aapt2/java/ClassDefinition.h @@ -59,8 +59,9 @@ class ClassMember { template <typename T> class PrimitiveMember : public ClassMember { public: - PrimitiveMember(const android::StringPiece& name, const T& val) - : name_(name.to_string()), val_(val) {} + PrimitiveMember(const android::StringPiece& name, const T& val, bool staged_api = false) + : name_(name.to_string()), val_(val), staged_api_(staged_api) { + } bool empty() const override { return false; @@ -70,8 +71,8 @@ class PrimitiveMember : public ClassMember { return name_; } - void Print(bool final, text::Printer* printer, bool strip_api_annotations = false) - const override { + void Print(bool final, text::Printer* printer, + bool strip_api_annotations = false) const override { using std::to_string; ClassMember::Print(final, printer, strip_api_annotations); @@ -80,7 +81,15 @@ class PrimitiveMember : public ClassMember { if (final) { printer->Print("final "); } - printer->Print("int ").Print(name_).Print("=").Print(to_string(val_)).Print(";"); + printer->Print("int ").Print(name_); + if (staged_api_) { + // Prevent references to staged apis from being inline by setting their value out-of-line. + printer->Print("; static { ").Print(name_); + } + printer->Print("=").Print(to_string(val_)).Print(";"); + if (staged_api_) { + printer->Print(" }"); + } } private: @@ -88,14 +97,16 @@ class PrimitiveMember : public ClassMember { std::string name_; T val_; + bool staged_api_; }; // Specialization for strings so they get the right type and are quoted with "". template <> class PrimitiveMember<std::string> : public ClassMember { public: - PrimitiveMember(const android::StringPiece& name, const std::string& val) - : name_(name.to_string()), val_(val) {} + PrimitiveMember(const android::StringPiece& name, const std::string& val, bool staged_api = false) + : name_(name.to_string()), val_(val) { + } bool empty() const override { return false; @@ -127,13 +138,13 @@ using IntMember = PrimitiveMember<uint32_t>; using ResourceMember = PrimitiveMember<ResourceId>; using StringMember = PrimitiveMember<std::string>; -template <typename T> +template <typename T, typename StringConverter> class PrimitiveArrayMember : public ClassMember { public: explicit PrimitiveArrayMember(const android::StringPiece& name) : name_(name.to_string()) {} void AddElement(const T& val) { - elements_.push_back(val); + elements_.emplace_back(val); } bool empty() const override { @@ -158,7 +169,7 @@ class PrimitiveArrayMember : public ClassMember { printer->Println(); } - printer->Print(to_string(*current)); + printer->Print(StringConverter::ToString(*current)); if (std::distance(current, end) > 1) { printer->Print(", "); } @@ -175,7 +186,24 @@ class PrimitiveArrayMember : public ClassMember { std::vector<T> elements_; }; -using ResourceArrayMember = PrimitiveArrayMember<ResourceId>; +struct FieldReference { + explicit FieldReference(std::string reference) : ref(std::move(reference)) { + } + std::string ref; +}; + +struct ResourceArrayMemberStringConverter { + static std::string ToString(const std::variant<ResourceId, FieldReference>& ref) { + if (auto id = std::get_if<ResourceId>(&ref)) { + return to_string(*id); + } else { + return std::get<FieldReference>(ref).ref; + } + } +}; + +using ResourceArrayMember = PrimitiveArrayMember<std::variant<ResourceId, FieldReference>, + ResourceArrayMemberStringConverter>; // Represents a method in a class. class MethodDefinition : public ClassMember { diff --git a/tools/aapt2/java/JavaClassGenerator.cpp b/tools/aapt2/java/JavaClassGenerator.cpp index dffad3b99c06..de6524dc7027 100644 --- a/tools/aapt2/java/JavaClassGenerator.cpp +++ b/tools/aapt2/java/JavaClassGenerator.cpp @@ -218,16 +218,22 @@ struct StyleableAttr { static bool operator<(const StyleableAttr& lhs, const StyleableAttr& rhs) { const ResourceId lhs_id = lhs.attr_ref->id.value_or_default(ResourceId(0)); const ResourceId rhs_id = rhs.attr_ref->id.value_or_default(ResourceId(0)); - if (lhs_id < rhs_id) { - return true; - } else if (lhs_id > rhs_id) { - return false; - } else { + if (lhs_id == rhs_id) { return lhs.attr_ref->name.value() < rhs.attr_ref->name.value(); } + return cmp_ids_dynamic_after_framework(lhs_id, rhs_id); } -void JavaClassGenerator::ProcessStyleable(const ResourceNameRef& name, const ResourceId& id, +static FieldReference GetRFieldReference(const ResourceName& name, + StringPiece fallback_package_name) { + const std::string package_name = + name.package.empty() ? fallback_package_name.to_string() : name.package; + const std::string entry = JavaClassGenerator::TransformToFieldName(name.entry); + return FieldReference( + StringPrintf("%s.R.%s.%s", package_name.c_str(), to_string(name.type).data(), entry.c_str())); +} + +bool JavaClassGenerator::ProcessStyleable(const ResourceNameRef& name, const ResourceId& id, const Styleable& styleable, const StringPiece& package_name_to_generate, ClassDefinition* out_class_def, @@ -343,14 +349,29 @@ void JavaClassGenerator::ProcessStyleable(const ResourceNameRef& name, const Res // Add the ResourceIds to the array member. for (size_t i = 0; i < attr_count; i++) { - const ResourceId id = sorted_attributes[i].attr_ref->id.value_or_default(ResourceId(0)); - array_def->AddElement(id); + const StyleableAttr& attr = sorted_attributes[i]; + std::string r_txt_contents; + if (attr.symbol && attr.symbol.value().is_dynamic) { + if (!attr.attr_ref->name) { + error_ = "unable to determine R.java field name of dynamic resource"; + return false; + } + + const FieldReference field_name = + GetRFieldReference(attr.attr_ref->name.value(), package_name_to_generate); + array_def->AddElement(field_name); + r_txt_contents = field_name.ref; + } else { + const ResourceId attr_id = attr.attr_ref->id.value_or_default(ResourceId(0)); + array_def->AddElement(attr_id); + r_txt_contents = to_string(attr_id); + } if (r_txt_printer != nullptr) { if (i != 0) { r_txt_printer->Print(","); } - r_txt_printer->Print(" ").Print(id.to_string()); + r_txt_printer->Print(" ").Print(r_txt_contents); } } @@ -422,19 +443,7 @@ void JavaClassGenerator::ProcessStyleable(const ResourceNameRef& name, const Res } } - // If there is a rewrite method to generate, add the statements that rewrite package IDs - // for this styleable. - if (out_rewrite_method != nullptr) { - out_rewrite_method->AppendStatement( - StringPrintf("for (int i = 0; i < styleable.%s.length; i++) {", array_field_name.data())); - out_rewrite_method->AppendStatement( - StringPrintf(" if ((styleable.%s[i] & 0xff000000) == 0) {", array_field_name.data())); - out_rewrite_method->AppendStatement( - StringPrintf(" styleable.%s[i] = (styleable.%s[i] & 0x00ffffff) | packageIdBits;", - array_field_name.data(), array_field_name.data())); - out_rewrite_method->AppendStatement(" }"); - out_rewrite_method->AppendStatement("}"); - } + return true; } void JavaClassGenerator::ProcessResource(const ResourceNameRef& name, const ResourceId& id, @@ -451,8 +460,8 @@ void JavaClassGenerator::ProcessResource(const ResourceNameRef& name, const Reso const std::string field_name = TransformToFieldName(name.entry); if (out_class_def != nullptr) { - std::unique_ptr<ResourceMember> resource_member = - util::make_unique<ResourceMember>(field_name, real_id); + auto resource_member = + util::make_unique<ResourceMember>(field_name, real_id, entry.visibility.staged_api); // Build the comments and annotations for this entry. AnnotationProcessor* processor = resource_member->GetCommentBuilder(); @@ -534,8 +543,8 @@ bool JavaClassGenerator::ProcessType(const StringPiece& package_name_to_generate // Create an ID if there is one (static libraries don't need one). ResourceId id; - if (package.id && type.id && entry->id) { - id = ResourceId(package.id.value(), type.id.value(), entry->id.value()); + if (entry->id) { + id = entry->id.value(); } // We need to make sure we hide the fact that we are generating kAttrPrivate attributes. @@ -554,12 +563,11 @@ bool JavaClassGenerator::ProcessType(const StringPiece& package_name_to_generate if (resource_name.type == ResourceType::kStyleable) { CHECK(!entry->values.empty()); - - const Styleable* styleable = - static_cast<const Styleable*>(entry->values.front()->value.get()); - - ProcessStyleable(resource_name, id, *styleable, package_name_to_generate, out_type_class_def, - out_rewrite_method_def, r_txt_printer); + const auto styleable = reinterpret_cast<const Styleable*>(entry->values.front()->value.get()); + if (!ProcessStyleable(resource_name, id, *styleable, package_name_to_generate, + out_type_class_def, out_rewrite_method_def, r_txt_printer)) { + return false; + } } else { ProcessResource(resource_name, id, *entry, out_type_class_def, out_rewrite_method_def, r_txt_printer); @@ -608,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; } @@ -629,8 +638,7 @@ bool JavaClassGenerator::Generate(const StringPiece& package_name_to_generate, if (type->type == ResourceType::kAttr) { // Also include private attributes in this same class. - const ResourceTableType* priv_type = package->FindType(ResourceType::kAttrPrivate); - if (priv_type) { + if (const ResourceTableType* priv_type = package->FindType(ResourceType::kAttrPrivate)) { if (!ProcessType(package_name_to_generate, *package, *priv_type, class_def.get(), rewrite_method.get(), r_txt_printer.get())) { return false; diff --git a/tools/aapt2/java/JavaClassGenerator.h b/tools/aapt2/java/JavaClassGenerator.h index 853120b3cb98..d9d1b39805f9 100644 --- a/tools/aapt2/java/JavaClassGenerator.h +++ b/tools/aapt2/java/JavaClassGenerator.h @@ -105,7 +105,7 @@ class JavaClassGenerator { // Writes a styleable resource to the R.java file, optionally writing out a rewrite rule for // its package ID if `out_rewrite_method` is not nullptr. // `package_name_to_generate` is the package - void ProcessStyleable(const ResourceNameRef& name, const ResourceId& id, + bool ProcessStyleable(const ResourceNameRef& name, const ResourceId& id, const Styleable& styleable, const android::StringPiece& package_name_to_generate, ClassDefinition* out_class_def, MethodDefinition* out_rewrite_method, diff --git a/tools/aapt2/java/JavaClassGenerator_test.cpp b/tools/aapt2/java/JavaClassGenerator_test.cpp index 1e1fe4740c6b..40395ed64fe3 100644 --- a/tools/aapt2/java/JavaClassGenerator_test.cpp +++ b/tools/aapt2/java/JavaClassGenerator_test.cpp @@ -34,7 +34,6 @@ namespace aapt { TEST(JavaClassGeneratorTest, FailWhenEntryIsJavaKeyword) { std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .SetPackageId("android", 0x01) .AddSimple("android:id/class", ResourceId(0x01020000)) .Build(); @@ -54,7 +53,6 @@ TEST(JavaClassGeneratorTest, FailWhenEntryIsJavaKeyword) { TEST(JavaClassGeneratorTest, TransformInvalidJavaIdentifierCharacter) { std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .SetPackageId("android", 0x01) .AddSimple("android:id/hey-man", ResourceId(0x01020000)) .AddValue("android:attr/cool.attr", ResourceId(0x01010000), test::AttributeBuilder().Build()) @@ -84,7 +82,6 @@ TEST(JavaClassGeneratorTest, TransformInvalidJavaIdentifierCharacter) { TEST(JavaClassGeneratorTest, CorrectPackageNameIsUsed) { std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .SetPackageId("android", 0x01) .AddSimple("android:id/one", ResourceId(0x01020000)) .AddSimple("android:id/com.foo$two", ResourceId(0x01020001)) .Build(); @@ -110,8 +107,6 @@ TEST(JavaClassGeneratorTest, CorrectPackageNameIsUsed) { TEST(JavaClassGeneratorTest, StyleableAttributesWithDifferentPackageName) { std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .SetPackageId("android", 0x01) - .SetPackageId("app", 0x7f) .AddValue("app:attr/foo", ResourceId(0x7f010000), test::AttributeBuilder().Build()) .AddValue("app:attr/bar", ResourceId(0x7f010001), @@ -159,7 +154,6 @@ TEST(JavaClassGeneratorTest, StyleableAttributesWithDifferentPackageName) { TEST(JavaClassGeneratorTest, AttrPrivateIsWrittenAsAttr) { std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .SetPackageId("android", 0x01) .AddSimple("android:attr/two", ResourceId(0x01010001)) .AddSimple("android:^attr-private/one", ResourceId(0x01010000)) .Build(); @@ -184,7 +178,6 @@ TEST(JavaClassGeneratorTest, OnlyWritePublicResources) { StdErrDiagnostics diag; std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .SetPackageId("android", 0x01) .AddSimple("android:id/one", ResourceId(0x01020000)) .AddSimple("android:id/two", ResourceId(0x01020001)) .AddSimple("android:id/three", ResourceId(0x01020002)) @@ -276,8 +269,6 @@ ResourceId>()), TEST(JavaClassGeneratorTest, EmitOtherPackagesAttributesInStyleable) { std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .SetPackageId("android", 0x01) - .SetPackageId("com.lib", 0x02) .AddValue("android:attr/bar", ResourceId(0x01010000), test::AttributeBuilder().Build()) .AddValue("com.lib:attr/bar", ResourceId(0x02010000), test::AttributeBuilder().Build()) .AddValue("android:styleable/foo", ResourceId(0x01030000), @@ -306,7 +297,6 @@ TEST(JavaClassGeneratorTest, EmitOtherPackagesAttributesInStyleable) { TEST(JavaClassGeneratorTest, CommentsForSimpleResourcesArePresent) { std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .SetPackageId("android", 0x01) .AddSimple("android:id/foo", ResourceId(0x01010000)) .Build(); test::GetValue<Id>(table.get(), "android:id/foo") @@ -344,12 +334,12 @@ TEST(JavaClassGeneratorTest, CommentsForStyleablesAndNestedAttributesArePresent) styleable.entries.push_back(Reference(test::ParseNameOrDie("android:attr/one"))); styleable.SetComment(StringPiece("This is a styleable")); + CloningValueTransformer cloner(nullptr); std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .SetPackageId("android", 0x01) .AddValue("android:attr/one", util::make_unique<Attribute>(attr)) .AddValue("android:styleable/Container", - std::unique_ptr<Styleable>(styleable.Clone(nullptr))) + std::unique_ptr<Styleable>(styleable.Transform(cloner))) .Build(); std::unique_ptr<IAaptContext> context = @@ -382,12 +372,12 @@ TEST(JavaClassGeneratorTest, CommentsForStyleableHiddenAttributesAreNotPresent) styleable.entries.push_back(Reference(test::ParseNameOrDie("android:attr/one"))); styleable.SetComment(StringPiece("This is a styleable")); + CloningValueTransformer cloner(nullptr); std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .SetPackageId("android", 0x01) .AddValue("android:attr/one", util::make_unique<Attribute>(attr)) .AddValue("android:styleable/Container", - std::unique_ptr<Styleable>(styleable.Clone(nullptr))) + std::unique_ptr<Styleable>(styleable.Transform(cloner))) .Build(); std::unique_ptr<IAaptContext> context = @@ -415,7 +405,6 @@ TEST(JavaClassGeneratorTest, CommentsForStyleableHiddenAttributesAreNotPresent) TEST(JavaClassGeneratorTest, StyleableAndIndicesAreColocated) { std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .SetPackageId("android", 0x01) .AddValue("android:attr/layout_gravity", util::make_unique<Attribute>()) .AddValue("android:attr/background", util::make_unique<Attribute>()) .AddValue("android:styleable/ActionBar", @@ -467,7 +456,6 @@ TEST(JavaClassGeneratorTest, CommentsForRemovedAttributesAreNotPresentInClass) { std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .SetPackageId("android", 0x01) .AddValue("android:attr/one", util::make_unique<Attribute>(attr)) .Build(); @@ -499,7 +487,6 @@ TEST(JavaClassGeneratorTest, CommentsForRemovedAttributesAreNotPresentInClass) { TEST(JavaClassGeneratorTest, GenerateOnResourcesLoadedCallbackForSharedLibrary) { std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .SetPackageId("android", 0x00) .AddValue("android:attr/foo", ResourceId(0x00010000), util::make_unique<Attribute>()) .AddValue("android:id/foo", ResourceId(0x00020000), util::make_unique<Id>()) .AddValue( @@ -536,7 +523,6 @@ TEST(JavaClassGeneratorTest, GenerateOnResourcesLoadedCallbackForSharedLibrary) TEST(JavaClassGeneratorTest, OnlyGenerateRText) { std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .SetPackageId("android", 0x01) .AddValue("android:attr/foo", ResourceId(0x01010000), util::make_unique<Attribute>()) .AddValue("android:styleable/hey.dude", ResourceId(0x01020000), test::StyleableBuilder() @@ -551,4 +537,58 @@ TEST(JavaClassGeneratorTest, OnlyGenerateRText) { ASSERT_TRUE(generator.Generate("android", nullptr)); } +TEST(JavaClassGeneratorTest, SortsDynamicAttributesAfterFrameworkAttributes) { + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .AddValue("android:attr/framework_attr", ResourceId(0x01010000), + test::AttributeBuilder().Build()) + .AddValue("lib:attr/dynamic_attr", ResourceId(0x00010000), + test::AttributeBuilder().Build()) + .AddValue("lib:styleable/MyStyleable", ResourceId(0x00030000), + test::StyleableBuilder() + .AddItem("android:attr/framework_attr", ResourceId(0x01010000)) + .AddItem("lib:attr/dynamic_attr", ResourceId(0x00010000)) + .Build()) + .Build(); + + std::unique_ptr<IAaptContext> context = + test::ContextBuilder() + .AddSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get())) + .SetNameManglerPolicy(NameManglerPolicy{"custom"}) + .SetCompilationPackage("custom") + .Build(); + JavaClassGenerator generator(context.get(), table.get(), {}); + + std::string output; + StringOutputStream out(&output); + EXPECT_TRUE(generator.Generate("lib", &out)); + out.Flush(); + + EXPECT_THAT(output, HasSubstr("public static final int[] MyStyleable={")); + EXPECT_THAT(output, HasSubstr("0x01010000, lib.R.attr.dynamic_attr")); + EXPECT_THAT(output, HasSubstr("public static final int MyStyleable_android_framework_attr=0;")); + 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 diff --git a/tools/aapt2/java/ProguardRules_test.cpp b/tools/aapt2/java/ProguardRules_test.cpp index c7ae0b6a75cc..e1040666e410 100644 --- a/tools/aapt2/java/ProguardRules_test.cpp +++ b/tools/aapt2/java/ProguardRules_test.cpp @@ -247,8 +247,10 @@ TEST(ProguardRulesTest, IncludedLayoutRulesAreConditional) { ResourceTable table; StdErrDiagnostics errDiagnostics; - table.AddResource(bar_layout->file.name, ConfigDescription::DefaultConfig(), "", - util::make_unique<FileReference>(), &errDiagnostics); + table.AddResource(NewResourceBuilder(bar_layout->file.name) + .SetValue(util::make_unique<FileReference>()) + .Build(), + &errDiagnostics); std::unique_ptr<IAaptContext> context = test::ContextBuilder() @@ -262,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())); diff --git a/tools/aapt2/jni/aapt2_jni.cpp b/tools/aapt2/jni/aapt2_jni.cpp index ba9646f9aeb4..ec3c5431c7a3 100644 --- a/tools/aapt2/jni/aapt2_jni.cpp +++ b/tools/aapt2/jni/aapt2_jni.cpp @@ -139,5 +139,5 @@ JNIEXPORT jint JNICALL Java_com_android_tools_aapt2_Aapt2Jni_nativeLink(JNIEnv* JNIEXPORT void JNICALL Java_com_android_tools_aapt2_Aapt2Jni_ping( JNIEnv *env, jclass aapt_obj) { - // This is just a dummy method to see if the library has been loaded. + // This is just a no-op method to see if the library has been loaded. } diff --git a/tools/aapt2/link/AutoVersioner.cpp b/tools/aapt2/link/AutoVersioner.cpp index 73b92542a755..876494e617a6 100644 --- a/tools/aapt2/link/AutoVersioner.cpp +++ b/tools/aapt2/link/AutoVersioner.cpp @@ -72,6 +72,7 @@ ApiVersion FindNextApiVersionForConfig(const ResourceEntry* entry, bool AutoVersioner::Consume(IAaptContext* context, ResourceTable* table) { TRACE_NAME("AutoVersioner::Consume"); + CloningValueTransformer cloner(&table->string_pool); for (auto& package : table->packages) { for (auto& type : package->types) { if (type->type != ResourceType::kStyle) { @@ -128,7 +129,7 @@ bool AutoVersioner::Consume(IAaptContext* context, ResourceTable* table) { ConfigDescription new_config(config_value->config); new_config.sdkVersion = static_cast<uint16_t>(min_sdk_stripped.value()); - std::unique_ptr<Style> new_style(style->Clone(&table->string_pool)); + std::unique_ptr<Style> new_style(style->Transform(cloner)); new_style->SetComment(style->GetComment()); new_style->SetSource(style->GetSource()); diff --git a/tools/aapt2/link/AutoVersioner_test.cpp b/tools/aapt2/link/AutoVersioner_test.cpp index 1117472f104b..02fd00bba439 100644 --- a/tools/aapt2/link/AutoVersioner_test.cpp +++ b/tools/aapt2/link/AutoVersioner_test.cpp @@ -54,7 +54,6 @@ TEST(AutoVersionerTest, GenerateVersionedResourceWhenHigherVersionExists) { TEST(AutoVersionerTest, VersionStylesForTable) { std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .SetPackageId("app", 0x7f) .AddValue( "app:style/Foo", test::ParseConfigOrDie("v4"), ResourceId(0x7f020000), diff --git a/tools/aapt2/link/Linkers.h b/tools/aapt2/link/Linkers.h index c9b8d3993959..be6c930b9284 100644 --- a/tools/aapt2/link/Linkers.h +++ b/tools/aapt2/link/Linkers.h @@ -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 diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp index c813a446b8db..8abd9dec56be 100644 --- a/tools/aapt2/link/ManifestFixer.cpp +++ b/tools/aapt2/link/ManifestFixer.cpp @@ -112,6 +112,27 @@ static xml::XmlNodeAction::ActionFuncWithDiag RequiredAndroidAttribute(const std }; } +static xml::XmlNodeAction::ActionFuncWithDiag RequiredOneAndroidAttribute( + const std::string& attrName1, const std::string& attrName2) { + return [=](xml::Element* el, SourcePathDiagnostics* diag) -> bool { + xml::Attribute* attr1 = el->FindAttribute(xml::kSchemaAndroid, attrName1); + xml::Attribute* attr2 = el->FindAttribute(xml::kSchemaAndroid, attrName2); + if (attr1 == nullptr && attr2 == nullptr) { + diag->Error(DiagMessage(el->line_number) + << "<" << el->name << "> is missing required attribute 'android:" << attrName1 + << "' or 'android:" << attrName2 << "'"); + return false; + } + if (attr1 != nullptr && attr2 != nullptr) { + diag->Error(DiagMessage(el->line_number) + << "<" << el->name << "> can only specify one of attribute 'android:" << attrName1 + << "' or 'android:" << attrName2 << "'"); + return false; + } + return true; + }; +} + static bool AutoGenerateIsFeatureSplit(xml::Element* el, SourcePathDiagnostics* diag) { constexpr const char* kFeatureSplit = "featureSplit"; constexpr const char* kIsFeatureSplit = "isFeatureSplit"; @@ -142,7 +163,8 @@ static bool AutoGenerateIsFeatureSplit(xml::Element* el, SourcePathDiagnostics* return true; } -static bool VerifyManifest(xml::Element* el, SourcePathDiagnostics* diag) { +static bool VerifyManifest(xml::Element* el, xml::XmlActionExecutorPolicy policy, + SourcePathDiagnostics* diag) { xml::Attribute* attr = el->FindAttribute({}, "package"); if (!attr) { diag->Error(DiagMessage(el->line_number) @@ -153,10 +175,16 @@ static bool VerifyManifest(xml::Element* el, SourcePathDiagnostics* diag) { << "attribute 'package' in <manifest> tag must not be a reference"); return false; } else if (!util::IsAndroidPackageName(attr->value)) { - diag->Error(DiagMessage(el->line_number) - << "attribute 'package' in <manifest> tag is not a valid Android package name: '" - << attr->value << "'"); - return false; + DiagMessage error_msg(el->line_number); + error_msg << "attribute 'package' in <manifest> tag is not a valid Android package name: '" + << attr->value << "'"; + if (policy == xml::XmlActionExecutorPolicy::kAllowListWarning) { + // Treat the error only as a warning. + diag->Warn(error_msg); + } else { + diag->Error(error_msg); + return false; + } } attr = el->FindAttribute({}, "split"); @@ -282,6 +310,10 @@ bool ManifestFixer::BuildRules(xml::XmlActionExecutor* executor, // Common <meta-data> actions. xml::XmlNodeAction meta_data_action; + // Common <property> actions. + xml::XmlNodeAction property_action; + property_action.Action(RequiredOneAndroidAttribute("resource", "value")); + // Common <uses-feature> actions. xml::XmlNodeAction uses_feature_action; uses_feature_action.Action(VerifyUsesFeature); @@ -292,6 +324,7 @@ bool ManifestFixer::BuildRules(xml::XmlActionExecutor* executor, component_action["intent-filter"] = intent_filter_action; component_action["preferred"] = intent_filter_action; component_action["meta-data"] = meta_data_action; + component_action["property"] = property_action; // Manifest actions. xml::XmlNodeAction& manifest_action = (*executor)["manifest"]; @@ -334,6 +367,16 @@ bool ManifestFixer::BuildRules(xml::XmlActionExecutor* executor, } } + if (options_.revision_code_default) { + if (options_.replace_version) { + el->RemoveAttribute(xml::kSchemaAndroid, "revisionCode"); + } + if (el->FindAttribute(xml::kSchemaAndroid, "revisionCode") == nullptr) { + el->attributes.push_back(xml::Attribute{xml::kSchemaAndroid, "revisionCode", + options_.revision_code_default.value()}); + } + } + return true; }); @@ -376,10 +419,6 @@ bool ManifestFixer::BuildRules(xml::XmlActionExecutor* executor, }); manifest_action["instrumentation"]["meta-data"] = meta_data_action; - // TODO moltmann: Remove - manifest_action["feature"]; - manifest_action["feature"]["inherit-from"]; - manifest_action["attribution"]; manifest_action["attribution"]["inherit-from"]; manifest_action["original-package"]; @@ -397,6 +436,8 @@ bool ManifestFixer::BuildRules(xml::XmlActionExecutor* executor, manifest_action["protected-broadcast"]; manifest_action["adopt-permissions"]; manifest_action["uses-permission"]; + manifest_action["uses-permission"]["required-feature"].Action(RequiredNameIsNotEmpty); + manifest_action["uses-permission"]["required-not-feature"].Action(RequiredNameIsNotEmpty); manifest_action["uses-permission-sdk-23"]; manifest_action["permission"]; manifest_action["permission"]["meta-data"] = meta_data_action; @@ -426,8 +467,10 @@ bool ManifestFixer::BuildRules(xml::XmlActionExecutor* executor, application_action.Action(OptionalNameIsJavaClassName); application_action["uses-library"].Action(RequiredNameIsNotEmpty); + application_action["uses-native-library"].Action(RequiredNameIsNotEmpty); application_action["library"].Action(RequiredNameIsNotEmpty); application_action["profileable"]; + application_action["property"] = property_action; xml::XmlNodeAction& static_library_action = application_action["static-library"]; static_library_action.Action(RequiredNameIsJavaPackage); @@ -573,8 +616,8 @@ bool ManifestFixer::Consume(IAaptContext* context, xml::XmlResource* doc) { } xml::XmlActionExecutorPolicy policy = options_.warn_validation - ? xml::XmlActionExecutorPolicy::kWhitelistWarning - : xml::XmlActionExecutorPolicy::kWhitelist; + ? xml::XmlActionExecutorPolicy::kAllowListWarning + : xml::XmlActionExecutorPolicy::kAllowList; if (!executor.Execute(policy, context->GetDiagnostics(), doc)) { return false; } diff --git a/tools/aapt2/link/ManifestFixer.h b/tools/aapt2/link/ManifestFixer.h index ec4367b450fb..34ad8d586df1 100644 --- a/tools/aapt2/link/ManifestFixer.h +++ b/tools/aapt2/link/ManifestFixer.h @@ -60,6 +60,10 @@ struct ManifestFixerOptions { // replace_version is set. Maybe<std::string> version_code_major_default; + // The revision code to set if 'android:revisionCode' is not defined in <manifest> or if + // replace_version is set. + Maybe<std::string> revision_code_default; + // The version of the framework being compiled against to set for 'android:compileSdkVersion' in // the <manifest> tag. Maybe<std::string> compile_sdk_version; diff --git a/tools/aapt2/link/ManifestFixer_test.cpp b/tools/aapt2/link/ManifestFixer_test.cpp index 0791805e2506..432f10bdab97 100644 --- a/tools/aapt2/link/ManifestFixer_test.cpp +++ b/tools/aapt2/link/ManifestFixer_test.cpp @@ -445,6 +445,66 @@ TEST_F(ManifestFixerTest, ReplaceVersionNameAndCode) { EXPECT_THAT(attr->value, StrEq("0x20000000")); } +TEST_F(ManifestFixerTest, UseDefaultRevisionCode) { + ManifestFixerOptions options; + options.revision_code_default = std::string("0x10000000"); + + std::unique_ptr<xml::XmlResource> doc = VerifyWithOptions(R"EOF( + <manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="android" + android:versionCode="0x00000001" />)EOF", + options); + ASSERT_THAT(doc, NotNull()); + + xml::Element* manifest_el = doc->root.get(); + ASSERT_THAT(manifest_el, NotNull()); + + xml::Attribute* attr = manifest_el->FindAttribute(xml::kSchemaAndroid, "revisionCode"); + ASSERT_THAT(attr, NotNull()); + EXPECT_THAT(attr->value, StrEq("0x10000000")); +} + +TEST_F(ManifestFixerTest, DontUseDefaultRevisionCode) { + ManifestFixerOptions options; + options.revision_code_default = std::string("0x10000000"); + + std::unique_ptr<xml::XmlResource> doc = VerifyWithOptions(R"EOF( + <manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="android" + android:versionCode="0x00000001" + android:revisionCode="0x00000002" />)EOF", + options); + ASSERT_THAT(doc, NotNull()); + + xml::Element* manifest_el = doc->root.get(); + ASSERT_THAT(manifest_el, NotNull()); + + xml::Attribute* attr = manifest_el->FindAttribute(xml::kSchemaAndroid, "revisionCode"); + ASSERT_THAT(attr, NotNull()); + EXPECT_THAT(attr->value, StrEq("0x00000002")); +} + +TEST_F(ManifestFixerTest, ReplaceRevisionCode) { + ManifestFixerOptions options; + options.replace_version = true; + options.revision_code_default = std::string("0x10000000"); + + std::unique_ptr<xml::XmlResource> doc = VerifyWithOptions(R"EOF( + <manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="android" + android:versionCode="0x00000001" + android:revisionCode="0x00000002" />)EOF", + options); + ASSERT_THAT(doc, NotNull()); + + xml::Element* manifest_el = doc->root.get(); + ASSERT_THAT(manifest_el, NotNull()); + + xml::Attribute* attr = manifest_el->FindAttribute(xml::kSchemaAndroid, "revisionCode"); + ASSERT_THAT(attr, NotNull()); + EXPECT_THAT(attr->value, StrEq("0x10000000")); +} + TEST_F(ManifestFixerTest, ReplaceVersionName) { ManifestFixerOptions options; options.replace_version = true; @@ -886,4 +946,78 @@ TEST_F(ManifestFixerTest, UsesLibraryMustHaveNonEmptyName) { EXPECT_THAT(Verify(input), NotNull()); } +TEST_F(ManifestFixerTest, ApplicationPropertyAttributeRequired) { + std::string input = R"( + <manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="android"> + <application> + <property android:name="" /> + </application> + </manifest>)"; + EXPECT_THAT(Verify(input), IsNull()); +} + +TEST_F(ManifestFixerTest, ApplicationPropertyOnlyOneAttributeDefined) { + std::string input = R"( + <manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="android"> + <application> + <property android:name="" android:value="" android:resource="" /> + </application> + </manifest>)"; + EXPECT_THAT(Verify(input), IsNull()); + + input = R"( + <manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="android"> + <application> + <property android:name="" android:resource="" /> + </application> + </manifest>)"; + EXPECT_THAT(Verify(input), NotNull()); + + input = R"( + <manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="android"> + <application> + <property android:name="" android:value="" /> + </application> + </manifest>)"; + EXPECT_THAT(Verify(input), NotNull()); +} + +TEST_F(ManifestFixerTest, ComponentPropertyOnlyOneAttributeDefined) { + std::string input = R"( + <manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="android"> + <application> + <activity android:name=".MyActivity"> + <property android:name="" android:value="" android:resource="" /> + </activity> + </application> + </manifest>)"; + EXPECT_THAT(Verify(input), IsNull()); + + input = R"( + <manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="android"> + <application> + <activity android:name=".MyActivity"> + <property android:name="" android:value="" /> + </activity> + </application> + </manifest>)"; + EXPECT_THAT(Verify(input), NotNull()); + + input = R"( + <manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="android"> + <application> + <activity android:name=".MyActivity"> + <property android:name="" android:resource="" /> + </activity> + </application> + </manifest>)"; + EXPECT_THAT(Verify(input), NotNull()); +} } // namespace aapt diff --git a/tools/aapt2/link/NoDefaultResourceRemover_test.cpp b/tools/aapt2/link/NoDefaultResourceRemover_test.cpp index d129c9ac8db7..3179fefc3fd0 100644 --- a/tools/aapt2/link/NoDefaultResourceRemover_test.cpp +++ b/tools/aapt2/link/NoDefaultResourceRemover_test.cpp @@ -21,10 +21,9 @@ namespace aapt { TEST(NoDefaultResourceRemoverTest, RemoveEntryWithNoDefaultAndOnlyLocales) { - std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); + std::unique_ptr<IAaptContext> context = test::ContextBuilder().SetPackageId(0x01).Build(); std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .SetPackageId("android", 0x01) .AddSimple("android:string/foo") .AddSimple("android:string/foo", test::ParseConfigOrDie("en-rGB")) .AddSimple("android:string/foo", test::ParseConfigOrDie("fr-rFR")) @@ -47,10 +46,10 @@ TEST(NoDefaultResourceRemoverTest, RemoveEntryWithNoDefaultAndOnlyLocales) { } TEST(NoDefaultResourceRemoverTest, KeepEntryWithLocalesAndDensities) { - std::unique_ptr<IAaptContext> context = test::ContextBuilder().SetMinSdkVersion(26).Build(); + std::unique_ptr<IAaptContext> context = + test::ContextBuilder().SetPackageId(0x01).SetMinSdkVersion(26).Build(); std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .SetPackageId("android", 0x01) .AddSimple("android:drawable/keep1", test::ParseConfigOrDie("mdpi")) // v4 .AddSimple("android:drawable/keep1", test::ParseConfigOrDie("en-rGB")) .AddSimple("android:drawable/keep1", test::ParseConfigOrDie("fr-rFR")) @@ -71,10 +70,10 @@ TEST(NoDefaultResourceRemoverTest, KeepEntryWithLocalesAndDensities) { } TEST(NoDefaultResourceRemoverTest, RemoveEntryWithLocalesAndDensitiesLowVersion) { - std::unique_ptr<IAaptContext> context = test::ContextBuilder().SetMinSdkVersion(3).Build(); + std::unique_ptr<IAaptContext> context = + test::ContextBuilder().SetPackageId(0x01).SetMinSdkVersion(3).Build(); std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .SetPackageId("android", 0x01) .AddSimple("android:drawable/remove1", test::ParseConfigOrDie("mdpi")) // v4 .AddSimple("android:drawable/remove1", test::ParseConfigOrDie("en-rGB")) .AddSimple("android:drawable/remove1", test::ParseConfigOrDie("fr-rFR")) @@ -87,10 +86,10 @@ TEST(NoDefaultResourceRemoverTest, RemoveEntryWithLocalesAndDensitiesLowVersion) } TEST(NoDefaultResourceRemoverTest, KeepEntryWithVersion) { - std::unique_ptr<IAaptContext> context = test::ContextBuilder().SetMinSdkVersion(8).Build(); + std::unique_ptr<IAaptContext> context = + test::ContextBuilder().SetPackageId(0x01).SetMinSdkVersion(8).Build(); std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .SetPackageId("android", 0x01) .AddSimple("android:drawable/keep1", test::ParseConfigOrDie("v8")) .AddSimple("android:drawable/keep1", test::ParseConfigOrDie("en-rGB")) .AddSimple("android:drawable/keep1", test::ParseConfigOrDie("fr-rFR")) diff --git a/tools/aapt2/link/ProductFilter_test.cpp b/tools/aapt2/link/ProductFilter_test.cpp index dd4767463711..4f78bbcece33 100644 --- a/tools/aapt2/link/ProductFilter_test.cpp +++ b/tools/aapt2/link/ProductFilter_test.cpp @@ -30,21 +30,29 @@ TEST(ProductFilterTest, SelectTwoProducts) { ResourceTable table; ASSERT_TRUE(table.AddResource( - test::ParseNameOrDie("android:string/one"), land, "", - test::ValueBuilder<Id>().SetSource(Source("land/default.xml")).Build(), + NewResourceBuilder(test::ParseNameOrDie("android:string/one")) + .SetValue(test::ValueBuilder<Id>().SetSource(Source("land/default.xml")).Build(), land) + .Build(), context->GetDiagnostics())); + ASSERT_TRUE(table.AddResource( - test::ParseNameOrDie("android:string/one"), land, "tablet", - test::ValueBuilder<Id>().SetSource(Source("land/tablet.xml")).Build(), + NewResourceBuilder(test::ParseNameOrDie("android:string/one")) + .SetValue(test::ValueBuilder<Id>().SetSource(Source("land/tablet.xml")).Build(), land, + "tablet") + .Build(), context->GetDiagnostics())); ASSERT_TRUE(table.AddResource( - test::ParseNameOrDie("android:string/one"), port, "", - test::ValueBuilder<Id>().SetSource(Source("port/default.xml")).Build(), + NewResourceBuilder(test::ParseNameOrDie("android:string/one")) + .SetValue(test::ValueBuilder<Id>().SetSource(Source("port/default.xml")).Build(), port) + .Build(), context->GetDiagnostics())); + ASSERT_TRUE(table.AddResource( - test::ParseNameOrDie("android:string/one"), port, "tablet", - test::ValueBuilder<Id>().SetSource(Source("port/tablet.xml")).Build(), + NewResourceBuilder(test::ParseNameOrDie("android:string/one")) + .SetValue(test::ValueBuilder<Id>().SetSource(Source("port/tablet.xml")).Build(), port, + "tablet") + .Build(), context->GetDiagnostics())); ProductFilter filter({"tablet"}); @@ -65,15 +73,17 @@ TEST(ProductFilterTest, SelectDefaultProduct) { ResourceTable table; ASSERT_TRUE(table.AddResource( - test::ParseNameOrDie("android:string/one"), - ConfigDescription::DefaultConfig(), "", - test::ValueBuilder<Id>().SetSource(Source("default.xml")).Build(), + NewResourceBuilder(test::ParseNameOrDie("android:string/one")) + .SetValue(test::ValueBuilder<Id>().SetSource(Source("default.xml")).Build()) + .Build(), context->GetDiagnostics())); + ASSERT_TRUE(table.AddResource( - test::ParseNameOrDie("android:string/one"), - ConfigDescription::DefaultConfig(), "tablet", - test::ValueBuilder<Id>().SetSource(Source("tablet.xml")).Build(), + NewResourceBuilder(test::ParseNameOrDie("android:string/one")) + .SetValue(test::ValueBuilder<Id>().SetSource(Source("tablet.xml")).Build(), {}, "tablet") + .Build(), context->GetDiagnostics())); + ; ProductFilter filter(std::unordered_set<std::string>{}); ASSERT_TRUE(filter.Consume(context.get(), &table)); @@ -91,19 +101,22 @@ TEST(ProductFilterTest, FailOnAmbiguousProduct) { ResourceTable table; ASSERT_TRUE(table.AddResource( - test::ParseNameOrDie("android:string/one"), - ConfigDescription::DefaultConfig(), "", - test::ValueBuilder<Id>().SetSource(Source("default.xml")).Build(), + NewResourceBuilder(test::ParseNameOrDie("android:string/one")) + .SetValue(test::ValueBuilder<Id>().SetSource(Source("default.xml")).Build()) + .Build(), context->GetDiagnostics())); + ASSERT_TRUE(table.AddResource( - test::ParseNameOrDie("android:string/one"), - ConfigDescription::DefaultConfig(), "tablet", - test::ValueBuilder<Id>().SetSource(Source("tablet.xml")).Build(), + NewResourceBuilder(test::ParseNameOrDie("android:string/one")) + .SetValue(test::ValueBuilder<Id>().SetSource(Source("tablet.xml")).Build(), {}, "tablet") + .Build(), context->GetDiagnostics())); + ASSERT_TRUE(table.AddResource( - test::ParseNameOrDie("android:string/one"), - ConfigDescription::DefaultConfig(), "no-sdcard", - test::ValueBuilder<Id>().SetSource(Source("no-sdcard.xml")).Build(), + NewResourceBuilder(test::ParseNameOrDie("android:string/one")) + .SetValue(test::ValueBuilder<Id>().SetSource(Source("no-sdcard.xml")).Build(), {}, + "no-sdcard") + .Build(), context->GetDiagnostics())); ProductFilter filter({"tablet", "no-sdcard"}); @@ -114,15 +127,17 @@ TEST(ProductFilterTest, FailOnMultipleDefaults) { std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); ResourceTable table; + ASSERT_TRUE( + table.AddResource(NewResourceBuilder(test::ParseNameOrDie("android:string/one")) + .SetValue(test::ValueBuilder<Id>().SetSource(Source(".xml")).Build()) + .Build(), + context->GetDiagnostics())); + ASSERT_TRUE(table.AddResource( - test::ParseNameOrDie("android:string/one"), - ConfigDescription::DefaultConfig(), "", - test::ValueBuilder<Id>().SetSource(Source(".xml")).Build(), - context->GetDiagnostics())); - ASSERT_TRUE(table.AddResource( - test::ParseNameOrDie("android:string/one"), - ConfigDescription::DefaultConfig(), "default", - test::ValueBuilder<Id>().SetSource(Source("default.xml")).Build(), + NewResourceBuilder(test::ParseNameOrDie("android:string/one")) + .SetValue(test::ValueBuilder<Id>().SetSource(Source("default.xml")).Build(), {}, + "default") + .Build(), context->GetDiagnostics())); ProductFilter filter(std::unordered_set<std::string>{}); diff --git a/tools/aapt2/link/ReferenceLinker.cpp b/tools/aapt2/link/ReferenceLinker.cpp index 8e49fabe6a5c..4ac25bd6a0e0 100644 --- a/tools/aapt2/link/ReferenceLinker.cpp +++ b/tools/aapt2/link/ReferenceLinker.cpp @@ -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 { +struct LoggingResourceName { + LoggingResourceName(const Reference& ref, const CallSite& callsite, + const xml::IPackageDeclStack* decls) + : ref_(ref), callsite_(callsite), decls_(decls) { + } -// 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; - } + 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; } - // 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()); - } + 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; + } - for (Style::Entry& entry : style->entries) { - std::string err_str; + if (full_name != name.ref_.name.value()) { + out << " (aka " << full_name << ")"; + } + return out; +} - // 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); +} // namespace - // 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; - - // 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()); - - // Link/resolve the final value (mostly if it's a reference). - entry.value->Accept(this); - - // 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()); - - // 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; - } +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; + } + + error_ = true; + return CloningValueTransformer::TransformDerived(value); +} + +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()); + } - } else { + for (Style::Entry& entry : new_style->entries) { + std::string err_str; + + // 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); + + // 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; + + // Link/resolve the final value if it's a reference. + entry.value = entry.value->Transform(*this); + + // 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()); + + // 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> 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())); - } +// 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); + + // 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())); } + } - if (transformed) { - return transformed; - } + if (transformed) { + return transformed; } - return value; } + return value; +} - const CallSite& callsite_; - IAaptContext* context_; - SymbolTable* symbols_; - xml::IPackageDeclStack* package_decls_; - StringPool* string_pool_; - bool error_ = false; -}; +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; } } diff --git a/tools/aapt2/link/ReferenceLinker.h b/tools/aapt2/link/ReferenceLinker.h index 1256709edbf4..770f1e500ac0 100644 --- a/tools/aapt2/link/ReferenceLinker.h +++ b/tools/aapt2/link/ReferenceLinker.h @@ -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; diff --git a/tools/aapt2/link/ReferenceLinker_test.cpp b/tools/aapt2/link/ReferenceLinker_test.cpp index a31ce9496d0c..2d8f0d39053f 100644 --- a/tools/aapt2/link/ReferenceLinker_test.cpp +++ b/tools/aapt2/link/ReferenceLinker_test.cpp @@ -28,7 +28,6 @@ namespace aapt { TEST(ReferenceLinkerTest, LinkSimpleReferences) { std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .SetPackageId("com.app.test", 0x7f) .AddReference("com.app.test:string/foo", ResourceId(0x7f020000), "com.app.test:string/bar") @@ -75,7 +74,6 @@ TEST(ReferenceLinkerTest, LinkSimpleReferences) { TEST(ReferenceLinkerTest, LinkStyleAttributes) { std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .SetPackageId("com.app.test", 0x7f) .AddValue("com.app.test:style/Theme", test::StyleBuilder() .SetParent("android:style/Theme.Material") @@ -155,7 +153,6 @@ TEST(ReferenceLinkerTest, LinkMangledReferencesAndAttributes) { std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .SetPackageId("com.app.test", 0x7f) .AddValue("com.app.test:style/Theme", ResourceId(0x7f020000), test::StyleBuilder() .AddItem("com.android.support:attr/foo", @@ -176,7 +173,6 @@ TEST(ReferenceLinkerTest, LinkMangledReferencesAndAttributes) { TEST(ReferenceLinkerTest, FailToLinkPrivateSymbols) { std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .SetPackageId("com.app.test", 0x7f) .AddReference("com.app.test:string/foo", ResourceId(0x7f020000), "android:string/hidden") .Build(); @@ -201,7 +197,6 @@ TEST(ReferenceLinkerTest, FailToLinkPrivateSymbols) { TEST(ReferenceLinkerTest, FailToLinkPrivateMangledSymbols) { std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .SetPackageId("com.app.test", 0x7f) .AddReference("com.app.test:string/foo", ResourceId(0x7f020000), "com.app.lib:string/hidden") .Build(); @@ -229,7 +224,6 @@ TEST(ReferenceLinkerTest, FailToLinkPrivateMangledSymbols) { TEST(ReferenceLinkerTest, FailToLinkPrivateStyleAttributes) { std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .SetPackageId("com.app.test", 0x7f) .AddValue("com.app.test:style/Theme", test::StyleBuilder() .AddItem("android:attr/hidden", @@ -371,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 diff --git a/tools/aapt2/link/TableMerger.cpp b/tools/aapt2/link/TableMerger.cpp index c25e4503a208..22f4d18dc3ca 100644 --- a/tools/aapt2/link/TableMerger.cpp +++ b/tools/aapt2/link/TableMerger.cpp @@ -31,11 +31,10 @@ namespace aapt { TableMerger::TableMerger(IAaptContext* context, ResourceTable* out_table, const TableMergerOptions& options) - : context_(context), master_table_(out_table), options_(options) { + : context_(context), main_table_(out_table), options_(options) { // Create the desired package that all tables will be merged into. - master_package_ = - master_table_->CreatePackage(context_->GetCompilationPackage(), context_->GetPackageId()); - CHECK(master_package_ != nullptr) << "package name or ID already taken"; + main_package_ = main_table_->FindOrCreatePackage(context_->GetCompilationPackage()); + CHECK(main_package_ != nullptr) << "package name or ID already taken"; } bool TableMerger::Merge(const Source& src, ResourceTable* table, bool overlay) { @@ -85,20 +84,9 @@ bool TableMerger::MergeAndMangle(const Source& src, const StringPiece& package_n static bool MergeType(IAaptContext* context, const Source& src, ResourceTableType* dst_type, ResourceTableType* src_type) { - if (src_type->visibility_level > dst_type->visibility_level) { + if (src_type->visibility_level >= dst_type->visibility_level) { // The incoming type's visibility is stronger, so we should override the visibility. - if (src_type->visibility_level == Visibility::Level::kPublic) { - // Only copy the ID if the source is public, or else the ID is meaningless. - dst_type->id = src_type->id; - } dst_type->visibility_level = src_type->visibility_level; - } else if (dst_type->visibility_level == Visibility::Level::kPublic && - src_type->visibility_level == Visibility::Level::kPublic && dst_type->id && - src_type->id && dst_type->id.value() != src_type->id.value()) { - // Both types are public and have different IDs. - context->GetDiagnostics()->Error(DiagMessage(src) << "cannot merge type '" << src_type->type - << "': conflicting public IDs"); - return false; } return true; } @@ -163,6 +151,18 @@ static bool MergeEntry(IAaptContext* context, const Source& src, dst_entry->overlayable_item = std::move(src_entry->overlayable_item); } + if (src_entry->staged_id) { + if (dst_entry->staged_id && + dst_entry->staged_id.value().id != src_entry->staged_id.value().id) { + context->GetDiagnostics()->Error(DiagMessage(src_entry->staged_id.value().source) + << "conflicting staged id declaration for resource '" + << src_entry->name << "'"); + context->GetDiagnostics()->Error(DiagMessage(dst_entry->staged_id.value().source) + << "previous declaration here"); + } + dst_entry->staged_id = std::move(src_entry->staged_id); + } + return true; } @@ -235,7 +235,7 @@ bool TableMerger::DoMerge(const Source& src, ResourceTablePackage* src_package, bool error = false; for (auto& src_type : src_package->types) { - ResourceTableType* dst_type = master_package_->FindOrCreateType(src_type->type); + ResourceTableType* dst_type = main_package_->FindOrCreateType(src_type->type); if (!MergeType(context_, src, dst_type, src_type.get())) { error = true; continue; @@ -279,7 +279,7 @@ bool TableMerger::DoMerge(const Source& src, ResourceTablePackage* src_package, if (dst_config_value) { CollisionResult collision_result = MergeConfigValue( context_, res_name, overlay, options_.override_styles_instead_of_overlaying, - dst_config_value, src_config_value.get(), &master_table_->string_pool); + dst_config_value, src_config_value.get(), &main_table_->string_pool); if (collision_result == CollisionResult::kConflict) { error = true; continue; @@ -292,13 +292,13 @@ bool TableMerger::DoMerge(const Source& src, ResourceTablePackage* src_package, } // Continue if we're taking the new resource. - + CloningValueTransformer cloner(&main_table_->string_pool); if (FileReference* f = ValueCast<FileReference>(src_config_value->value.get())) { std::unique_ptr<FileReference> new_file_ref; if (mangle_package) { new_file_ref = CloneAndMangleFile(src_package->name, *f); } else { - new_file_ref = std::unique_ptr<FileReference>(f->Clone(&master_table_->string_pool)); + new_file_ref = std::unique_ptr<FileReference>(f->Transform(cloner)); } dst_config_value->value = std::move(new_file_ref); @@ -306,8 +306,7 @@ bool TableMerger::DoMerge(const Source& src, ResourceTablePackage* src_package, Maybe<std::string> original_comment = (dst_config_value->value) ? dst_config_value->value->GetComment() : Maybe<std::string>(); - dst_config_value->value = std::unique_ptr<Value>( - src_config_value->value->Clone(&master_table_->string_pool)); + dst_config_value->value = src_config_value->value->Transform(cloner); // Keep the comment from the original resource and ignore all comments from overlaying // resources @@ -328,14 +327,16 @@ std::unique_ptr<FileReference> TableMerger::CloneAndMangleFile( std::string mangled_entry = NameMangler::MangleEntry(package, entry.to_string()); std::string newPath = prefix.to_string() + mangled_entry + suffix.to_string(); std::unique_ptr<FileReference> new_file_ref = - util::make_unique<FileReference>(master_table_->string_pool.MakeRef(newPath)); + util::make_unique<FileReference>(main_table_->string_pool.MakeRef(newPath)); new_file_ref->SetComment(file_ref.GetComment()); new_file_ref->SetSource(file_ref.GetSource()); new_file_ref->type = file_ref.type; new_file_ref->file = file_ref.file; return new_file_ref; } - return std::unique_ptr<FileReference>(file_ref.Clone(&master_table_->string_pool)); + + CloningValueTransformer cloner(&main_table_->string_pool); + return std::unique_ptr<FileReference>(file_ref.Transform(cloner)); } bool TableMerger::MergeFile(const ResourceFile& file_desc, bool overlay, io::IFile* file) { @@ -347,7 +348,7 @@ bool TableMerger::MergeFile(const ResourceFile& file_desc, bool overlay, io::IFi file_ref->type = file_desc.type; file_ref->file = file; - ResourceTablePackage* pkg = table.CreatePackage(file_desc.name.package, 0x0); + ResourceTablePackage* pkg = table.FindOrCreatePackage(file_desc.name.package); pkg->FindOrCreateType(file_desc.name.type) ->FindOrCreateEntry(file_desc.name.entry) ->FindOrCreateValue(file_desc.config, {}) diff --git a/tools/aapt2/link/TableMerger.h b/tools/aapt2/link/TableMerger.h index a35a134a887d..e01a0c186392 100644 --- a/tools/aapt2/link/TableMerger.h +++ b/tools/aapt2/link/TableMerger.h @@ -80,9 +80,9 @@ class TableMerger { DISALLOW_COPY_AND_ASSIGN(TableMerger); IAaptContext* context_; - ResourceTable* master_table_; + ResourceTable* main_table_; TableMergerOptions options_; - ResourceTablePackage* master_package_; + ResourceTablePackage* main_package_; std::set<std::string> merged_packages_; bool MergeImpl(const Source& src, ResourceTable* src_table, bool overlay, bool allow_new); diff --git a/tools/aapt2/link/TableMerger_test.cpp b/tools/aapt2/link/TableMerger_test.cpp index 69cf5ee7002b..4358fb565a7d 100644 --- a/tools/aapt2/link/TableMerger_test.cpp +++ b/tools/aapt2/link/TableMerger_test.cpp @@ -55,16 +55,13 @@ struct TableMergerTest : public ::testing::Test { TEST_F(TableMergerTest, SimpleMerge) { std::unique_ptr<ResourceTable> table_a = test::ResourceTableBuilder() - .SetPackageId("com.app.a", 0x7f) .AddReference("com.app.a:id/foo", "com.app.a:id/bar") .AddReference("com.app.a:id/bar", "com.app.b:id/foo") - .AddValue( - "com.app.a:styleable/view", - test::StyleableBuilder().AddItem("com.app.b:id/foo").Build()) + .AddValue("com.app.a:styleable/view", + test::StyleableBuilder().AddItem("com.app.b:id/foo").Build()) .Build(); std::unique_ptr<ResourceTable> table_b = test::ResourceTableBuilder() - .SetPackageId("com.app.b", 0x7f) .AddSimple("com.app.b:id/foo") .Build(); @@ -129,12 +126,10 @@ TEST_F(TableMergerTest, MergeFileReferences) { std::unique_ptr<ResourceTable> table_a = test::ResourceTableBuilder() - .SetPackageId("com.app.a", 0x7f) .AddFileReference("com.app.a:xml/file", "res/xml/file.xml", &file_a) .Build(); std::unique_ptr<ResourceTable> table_b = test::ResourceTableBuilder() - .SetPackageId("com.app.b", 0x7f) .AddFileReference("com.app.b:xml/file", "res/xml/file.xml", &file_b) .Build(); @@ -158,12 +153,10 @@ TEST_F(TableMergerTest, MergeFileReferences) { TEST_F(TableMergerTest, OverrideResourceWithOverlay) { std::unique_ptr<ResourceTable> base = test::ResourceTableBuilder() - .SetPackageId("", 0x00) .AddValue("bool/foo", ResourceUtils::TryParseBool("true")) .Build(); std::unique_ptr<ResourceTable> overlay = test::ResourceTableBuilder() - .SetPackageId("", 0x00) .AddValue("bool/foo", ResourceUtils::TryParseBool("false")) .Build(); @@ -194,14 +187,12 @@ TEST_F(TableMergerTest, DoNotOverrideResourceComment) { std::unique_ptr<ResourceTable> base = test::ResourceTableBuilder() - .SetPackageId("", 0x00) .AddValue("bool/foo", std::move(foo_original)) .AddValue("bool/bar", std::move(bar_original)) .Build(); std::unique_ptr<ResourceTable> overlay = test::ResourceTableBuilder() - .SetPackageId("", 0x00) .AddValue("bool/foo", std::move(foo_overlay)) .AddValue("bool/bar", std::move(bar_overlay)) .AddValue("bool/baz", std::move(baz_overlay)) @@ -226,12 +217,10 @@ TEST_F(TableMergerTest, DoNotOverrideResourceComment) { TEST_F(TableMergerTest, OverrideSameResourceIdsWithOverlay) { std::unique_ptr<ResourceTable> base = test::ResourceTableBuilder() - .SetPackageId("", 0x7f) .SetSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0001), Visibility::Level::kPublic) .Build(); std::unique_ptr<ResourceTable> overlay = test::ResourceTableBuilder() - .SetPackageId("", 0x7f) .SetSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0001), Visibility::Level::kPublic) .Build(); @@ -247,12 +236,10 @@ TEST_F(TableMergerTest, OverrideSameResourceIdsWithOverlay) { TEST_F(TableMergerTest, FailToOverrideConflictingTypeIdsWithOverlay) { std::unique_ptr<ResourceTable> base = test::ResourceTableBuilder() - .SetPackageId("", 0x7f) .SetSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0001), Visibility::Level::kPublic) .Build(); std::unique_ptr<ResourceTable> overlay = test::ResourceTableBuilder() - .SetPackageId("", 0x7f) .SetSymbolState("bool/foo", ResourceId(0x7f, 0x02, 0x0001), Visibility::Level::kPublic) .Build(); @@ -268,12 +255,10 @@ TEST_F(TableMergerTest, FailToOverrideConflictingTypeIdsWithOverlay) { TEST_F(TableMergerTest, FailToOverrideConflictingEntryIdsWithOverlay) { std::unique_ptr<ResourceTable> base = test::ResourceTableBuilder() - .SetPackageId("", 0x7f) .SetSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0001), Visibility::Level::kPublic) .Build(); std::unique_ptr<ResourceTable> overlay = test::ResourceTableBuilder() - .SetPackageId("", 0x7f) .SetSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0002), Visibility::Level::kPublic) .Build(); @@ -289,12 +274,10 @@ TEST_F(TableMergerTest, FailToOverrideConflictingEntryIdsWithOverlay) { TEST_F(TableMergerTest, FailConflictingVisibility) { std::unique_ptr<ResourceTable> base = test::ResourceTableBuilder() - .SetPackageId("", 0x7f) .SetSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0001), Visibility::Level::kPublic) .Build(); std::unique_ptr<ResourceTable> overlay = test::ResourceTableBuilder() - .SetPackageId("", 0x7f) .SetSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0001), Visibility::Level::kPrivate) .Build(); @@ -318,11 +301,9 @@ TEST_F(TableMergerTest, FailConflictingVisibility) { } TEST_F(TableMergerTest, MergeAddResourceFromOverlay) { - std::unique_ptr<ResourceTable> table_a = - test::ResourceTableBuilder().SetPackageId("", 0x7f).Build(); + std::unique_ptr<ResourceTable> table_a = test::ResourceTableBuilder().Build(); std::unique_ptr<ResourceTable> table_b = test::ResourceTableBuilder() - .SetPackageId("", 0x7f) .SetSymbolState("bool/foo", {}, Visibility::Level::kUndefined, true /*allow new overlay*/) .AddValue("bool/foo", ResourceUtils::TryParseBool("true")) .Build(); @@ -337,11 +318,9 @@ TEST_F(TableMergerTest, MergeAddResourceFromOverlay) { } TEST_F(TableMergerTest, MergeAddResourceFromOverlayWithAutoAddOverlay) { - std::unique_ptr<ResourceTable> table_a = - test::ResourceTableBuilder().SetPackageId("", 0x7f).Build(); + std::unique_ptr<ResourceTable> table_a = test::ResourceTableBuilder().Build(); std::unique_ptr<ResourceTable> table_b = test::ResourceTableBuilder() - .SetPackageId("", 0x7f) .AddValue("bool/foo", ResourceUtils::TryParseBool("true")) .Build(); @@ -355,11 +334,9 @@ TEST_F(TableMergerTest, MergeAddResourceFromOverlayWithAutoAddOverlay) { } TEST_F(TableMergerTest, FailToMergeNewResourceWithoutAutoAddOverlay) { - std::unique_ptr<ResourceTable> table_a = - test::ResourceTableBuilder().SetPackageId("", 0x7f).Build(); + std::unique_ptr<ResourceTable> table_a = test::ResourceTableBuilder().Build(); std::unique_ptr<ResourceTable> table_b = test::ResourceTableBuilder() - .SetPackageId("", 0x7f) .AddValue("bool/foo", ResourceUtils::TryParseBool("true")) .Build(); @@ -375,7 +352,6 @@ TEST_F(TableMergerTest, FailToMergeNewResourceWithoutAutoAddOverlay) { TEST_F(TableMergerTest, OverlaidStyleablesAndStylesShouldBeMerged) { std::unique_ptr<ResourceTable> table_a = test::ResourceTableBuilder() - .SetPackageId("com.app.a", 0x7f) .AddValue("com.app.a:styleable/Foo", test::StyleableBuilder() .AddItem("com.app.a:attr/bar") @@ -391,7 +367,6 @@ TEST_F(TableMergerTest, OverlaidStyleablesAndStylesShouldBeMerged) { std::unique_ptr<ResourceTable> table_b = test::ResourceTableBuilder() - .SetPackageId("com.app.a", 0x7f) .AddValue("com.app.a:styleable/Foo", test::StyleableBuilder() .AddItem("com.app.a:attr/bat") .AddItem("com.app.a:attr/foo") @@ -441,7 +416,6 @@ TEST_F(TableMergerTest, OverlaidStyleablesAndStylesShouldBeMerged) { TEST_F(TableMergerTest, OverrideStyleInsteadOfOverlaying) { std::unique_ptr<ResourceTable> table_a = test::ResourceTableBuilder() - .SetPackageId("com.app.a", 0x7f) .AddValue( "com.app.a:styleable/MyWidget", test::StyleableBuilder().AddItem("com.app.a:attr/foo", ResourceId(0x1234)).Build()) @@ -452,7 +426,6 @@ TEST_F(TableMergerTest, OverrideStyleInsteadOfOverlaying) { .Build(); std::unique_ptr<ResourceTable> table_b = test::ResourceTableBuilder() - .SetPackageId("com.app.a", 0x7f) .AddValue( "com.app.a:styleable/MyWidget", test::StyleableBuilder().AddItem("com.app.a:attr/bar", ResourceId(0x5678)).Build()) @@ -494,13 +467,11 @@ TEST_F(TableMergerTest, SetOverlayable) { std::unique_ptr<ResourceTable> table_a = test::ResourceTableBuilder() - .SetPackageId("com.app.a", 0x7f) .SetOverlayable("bool/foo", overlayable_item) .Build(); std::unique_ptr<ResourceTable> table_b = test::ResourceTableBuilder() - .SetPackageId("com.app.a", 0x7f) .AddSimple("bool/foo") .Build(); @@ -527,7 +498,6 @@ TEST_F(TableMergerTest, SetOverlayableLater) { "overlay://customization"); std::unique_ptr<ResourceTable> table_a = test::ResourceTableBuilder() - .SetPackageId("com.app.a", 0x7f) .AddSimple("bool/foo") .Build(); @@ -536,7 +506,6 @@ TEST_F(TableMergerTest, SetOverlayableLater) { overlayable_item.policies |= PolicyFlags::SYSTEM_PARTITION; std::unique_ptr<ResourceTable> table_b = test::ResourceTableBuilder() - .SetPackageId("com.app.a", 0x7f) .SetOverlayable("bool/foo", overlayable_item) .Build(); @@ -565,7 +534,6 @@ TEST_F(TableMergerTest, SameResourceDifferentNameFail) { overlayable_item_first.policies |= PolicyFlags::PRODUCT_PARTITION; std::unique_ptr<ResourceTable> table_a = test::ResourceTableBuilder() - .SetPackageId("com.app.a", 0x7f) .SetOverlayable("bool/foo", overlayable_item_first) .Build(); @@ -575,7 +543,6 @@ TEST_F(TableMergerTest, SameResourceDifferentNameFail) { overlayable_item_second.policies |= PolicyFlags::PRODUCT_PARTITION; std::unique_ptr<ResourceTable> table_b = test::ResourceTableBuilder() - .SetPackageId("com.app.a", 0x7f) .SetOverlayable("bool/foo", overlayable_item_second) .Build(); @@ -594,7 +561,6 @@ TEST_F(TableMergerTest, SameResourceDifferentActorFail) { overlayable_item_first.policies |= PolicyFlags::PRODUCT_PARTITION; std::unique_ptr<ResourceTable> table_a = test::ResourceTableBuilder() - .SetPackageId("com.app.a", 0x7f) .SetOverlayable("bool/foo", overlayable_item_first) .Build(); @@ -604,7 +570,6 @@ TEST_F(TableMergerTest, SameResourceDifferentActorFail) { overlayable_item_second.policies |= PolicyFlags::PRODUCT_PARTITION; std::unique_ptr<ResourceTable> table_b = test::ResourceTableBuilder() - .SetPackageId("com.app.a", 0x7f) .SetOverlayable("bool/foo", overlayable_item_second) .Build(); @@ -623,7 +588,6 @@ TEST_F(TableMergerTest, SameResourceDifferentPoliciesFail) { overlayable_item_first.policies |= PolicyFlags::PRODUCT_PARTITION; std::unique_ptr<ResourceTable> table_a = test::ResourceTableBuilder() - .SetPackageId("com.app.a", 0x7f) .SetOverlayable("bool/foo", overlayable_item_first) .Build(); @@ -633,7 +597,6 @@ TEST_F(TableMergerTest, SameResourceDifferentPoliciesFail) { overlayable_item_second.policies |= PolicyFlags::SIGNATURE; std::unique_ptr<ResourceTable> table_b = test::ResourceTableBuilder() - .SetPackageId("com.app.a", 0x7f) .SetOverlayable("bool/foo", overlayable_item_second) .Build(); @@ -653,7 +616,6 @@ TEST_F(TableMergerTest, SameResourceSameOverlayable) { overlayable_item_first.policies |= PolicyFlags::PRODUCT_PARTITION; std::unique_ptr<ResourceTable> table_a = test::ResourceTableBuilder() - .SetPackageId("com.app.a", 0x7f) .SetOverlayable("bool/foo", overlayable_item_first) .Build(); @@ -661,7 +623,6 @@ TEST_F(TableMergerTest, SameResourceSameOverlayable) { overlayable_item_second.policies |= PolicyFlags::PRODUCT_PARTITION; std::unique_ptr<ResourceTable> table_b = test::ResourceTableBuilder() - .SetPackageId("com.app.a", 0x7f) .SetOverlayable("bool/foo", overlayable_item_second) .Build(); diff --git a/tools/aapt2/link/XmlCompatVersioner.cpp b/tools/aapt2/link/XmlCompatVersioner.cpp index 20ebdc696814..957b64cd8dbb 100644 --- a/tools/aapt2/link/XmlCompatVersioner.cpp +++ b/tools/aapt2/link/XmlCompatVersioner.cpp @@ -23,9 +23,10 @@ namespace aapt { static xml::Attribute CopyAttr(const xml::Attribute& src, StringPool* out_string_pool) { + CloningValueTransformer cloner(out_string_pool); xml::Attribute dst{src.namespace_uri, src.name, src.value, src.compiled_attribute}; if (src.compiled_value != nullptr) { - dst.compiled_value.reset(src.compiled_value->Clone(out_string_pool)); + dst.compiled_value = src.compiled_value->Transform(cloner); } return dst; } @@ -34,6 +35,7 @@ static xml::Attribute CopyAttr(const xml::Attribute& src, StringPool* out_string // (came from a rule). static bool CopyAttribute(const xml::Attribute& src_attr, bool generated, xml::Element* dst_el, StringPool* out_string_pool) { + CloningValueTransformer cloner(out_string_pool); xml::Attribute* dst_attr = dst_el->FindAttribute(src_attr.namespace_uri, src_attr.name); if (dst_attr != nullptr) { if (generated) { @@ -41,7 +43,7 @@ static bool CopyAttribute(const xml::Attribute& src_attr, bool generated, xml::E dst_attr->value = src_attr.value; dst_attr->compiled_attribute = src_attr.compiled_attribute; if (src_attr.compiled_value != nullptr) { - dst_attr->compiled_value.reset(src_attr.compiled_value->Clone(out_string_pool)); + dst_attr->compiled_value = src_attr.compiled_value->Transform(cloner); } return true; } @@ -143,8 +145,8 @@ std::vector<std::unique_ptr<xml::XmlResource>> XmlCompatVersioner::Process( // Iterate from smallest to largest API version. for (ApiVersion api : apis_referenced) { - std::set<ApiVersion> dummy; - versioned_docs.push_back(ProcessDoc(api, api_range.end, doc, &dummy)); + std::set<ApiVersion> tmp; + versioned_docs.push_back(ProcessDoc(api, api_range.end, doc, &tmp)); } return versioned_docs; } @@ -158,7 +160,8 @@ static inline std::unique_ptr<Item> CloneIfNotNull(const std::unique_ptr<Item>& if (src == nullptr) { return {}; } - return std::unique_ptr<Item>(src->Clone(out_string_pool)); + CloningValueTransformer cloner(out_string_pool); + return src->Transform(cloner); } std::vector<DegradeResult> DegradeToManyRule::Degrade(const xml::Element& src_el, diff --git a/tools/aapt2/link/XmlCompatVersioner_test.cpp b/tools/aapt2/link/XmlCompatVersioner_test.cpp index a98ab0f76de4..d63809615a5b 100644 --- a/tools/aapt2/link/XmlCompatVersioner_test.cpp +++ b/tools/aapt2/link/XmlCompatVersioner_test.cpp @@ -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 = diff --git a/tools/aapt2/link/XmlReferenceLinker.cpp b/tools/aapt2/link/XmlReferenceLinker.cpp index c3c16b92f712..aaa085e2eb15 100644 --- a/tools/aapt2/link/XmlReferenceLinker.cpp +++ b/tools/aapt2/link/XmlReferenceLinker.cpp @@ -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(); diff --git a/tools/aapt2/link/XmlReferenceLinker_test.cpp b/tools/aapt2/link/XmlReferenceLinker_test.cpp index 0ce2e50d6e44..ddf5b9a22c2f 100644 --- a/tools/aapt2/link/XmlReferenceLinker_test.cpp +++ b/tools/aapt2/link/XmlReferenceLinker_test.cpp @@ -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(); diff --git a/tools/aapt2/optimize/ResourceFilter.cpp b/tools/aapt2/optimize/ResourceFilter.cpp index 250b65197a7d..08c045bf68f7 100644 --- a/tools/aapt2/optimize/ResourceFilter.cpp +++ b/tools/aapt2/optimize/ResourceFilter.cpp @@ -20,8 +20,8 @@ namespace aapt { -ResourceFilter::ResourceFilter(const std::unordered_set<ResourceName>& blacklist) - : blacklist_(blacklist) { +ResourceFilter::ResourceFilter(const std::unordered_set<ResourceName>& exclude_list) + : exclude_list_(exclude_list) { } bool ResourceFilter::Consume(IAaptContext* context, ResourceTable* table) { @@ -29,7 +29,7 @@ bool ResourceFilter::Consume(IAaptContext* context, ResourceTable* table) { for (auto& type : package->types) { for (auto it = type->entries.begin(); it != type->entries.end(); ) { ResourceName resource = ResourceName({}, type->type, (*it)->name); - if (blacklist_.find(resource) != blacklist_.end()) { + if (exclude_list_.find(resource) != exclude_list_.end()) { it = type->entries.erase(it); } else { ++it; diff --git a/tools/aapt2/optimize/ResourceFilter.h b/tools/aapt2/optimize/ResourceFilter.h index d4baf654b0ff..a2645333e497 100644 --- a/tools/aapt2/optimize/ResourceFilter.h +++ b/tools/aapt2/optimize/ResourceFilter.h @@ -25,16 +25,16 @@ namespace aapt { -// Removes non-whitelisted entries from resource table. +// Removes exclude-listed entries from resource table. class ResourceFilter : public IResourceTableConsumer { public: - explicit ResourceFilter(const std::unordered_set<ResourceName>& blacklist); + explicit ResourceFilter(const std::unordered_set<ResourceName>& exclude_list); bool Consume(IAaptContext* context, ResourceTable* table) override; private: DISALLOW_COPY_AND_ASSIGN(ResourceFilter); - std::unordered_set<ResourceName> blacklist_; + std::unordered_set<ResourceName> exclude_list_; }; } // namespace aapt diff --git a/tools/aapt2/optimize/ResourceFilter_test.cpp b/tools/aapt2/optimize/ResourceFilter_test.cpp index ef57f9c56dab..34d8fd280fa9 100644 --- a/tools/aapt2/optimize/ResourceFilter_test.cpp +++ b/tools/aapt2/optimize/ResourceFilter_test.cpp @@ -31,22 +31,22 @@ TEST(ResourceFilterTest, SomeValuesAreFilteredOut) { std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .AddString("android:string/notblacklisted", ResourceId{}, default_config, "value") - .AddString("android:string/blacklisted", ResourceId{}, default_config, "value") - .AddString("android:string/notblacklisted2", ResourceId{}, default_config, "value") - .AddString("android:string/blacklisted2", ResourceId{}, default_config, "value") + .AddString("android:string/notexclude_listed", ResourceId{}, default_config, "value") + .AddString("android:string/exclude_listed", ResourceId{}, default_config, "value") + .AddString("android:string/notexclude_listed2", ResourceId{}, default_config, "value") + .AddString("android:string/exclude_listed2", ResourceId{}, default_config, "value") .Build(); - std::unordered_set<ResourceName> blacklist = { - ResourceName({}, ResourceType::kString, "blacklisted"), - ResourceName({}, ResourceType::kString, "blacklisted2"), + std::unordered_set<ResourceName> exclude_list = { + ResourceName({}, ResourceType::kString, "exclude_listed"), + ResourceName({}, ResourceType::kString, "exclude_listed2"), }; - ASSERT_TRUE(ResourceFilter(blacklist).Consume(context.get(), table.get())); - EXPECT_THAT(table, HasValue("android:string/notblacklisted", default_config)); - EXPECT_THAT(table, HasValue("android:string/notblacklisted2", default_config)); - EXPECT_THAT(table, Not(HasValue("android:string/blacklisted", default_config))); - EXPECT_THAT(table, Not(HasValue("android:string/blacklisted2", default_config))); + ASSERT_TRUE(ResourceFilter(exclude_list).Consume(context.get(), table.get())); + EXPECT_THAT(table, HasValue("android:string/notexclude_listed", default_config)); + EXPECT_THAT(table, HasValue("android:string/notexclude_listed2", default_config)); + EXPECT_THAT(table, Not(HasValue("android:string/exclude_listed", default_config))); + EXPECT_THAT(table, Not(HasValue("android:string/exclude_listed2", default_config))); } TEST(ResourceFilterTest, TypeIsCheckedBeforeFiltering) { @@ -55,21 +55,21 @@ TEST(ResourceFilterTest, TypeIsCheckedBeforeFiltering) { std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .AddString("android:string/notblacklisted", ResourceId{}, default_config, "value") - .AddString("android:string/blacklisted", ResourceId{}, default_config, "value") - .AddString("android:drawable/notblacklisted", ResourceId{}, default_config, "value") - .AddString("android:drawable/blacklisted", ResourceId{}, default_config, "value") + .AddString("android:string/notexclude_listed", ResourceId{}, default_config, "value") + .AddString("android:string/exclude_listed", ResourceId{}, default_config, "value") + .AddString("android:drawable/notexclude_listed", ResourceId{}, default_config, "value") + .AddString("android:drawable/exclude_listed", ResourceId{}, default_config, "value") .Build(); - std::unordered_set<ResourceName> blacklist = { - ResourceName({}, ResourceType::kString, "blacklisted"), + std::unordered_set<ResourceName> exclude_list = { + ResourceName({}, ResourceType::kString, "exclude_listed"), }; - ASSERT_TRUE(ResourceFilter(blacklist).Consume(context.get(), table.get())); - EXPECT_THAT(table, HasValue("android:string/notblacklisted", default_config)); - EXPECT_THAT(table, HasValue("android:drawable/blacklisted", default_config)); - EXPECT_THAT(table, HasValue("android:drawable/notblacklisted", default_config)); - EXPECT_THAT(table, Not(HasValue("android:string/blacklisted", default_config))); + ASSERT_TRUE(ResourceFilter(exclude_list).Consume(context.get(), table.get())); + EXPECT_THAT(table, HasValue("android:string/notexclude_listed", default_config)); + EXPECT_THAT(table, HasValue("android:drawable/exclude_listed", default_config)); + EXPECT_THAT(table, HasValue("android:drawable/notexclude_listed", default_config)); + EXPECT_THAT(table, Not(HasValue("android:string/exclude_listed", default_config))); } } // namespace aapt diff --git a/tools/aapt2/process/SymbolTable.cpp b/tools/aapt2/process/SymbolTable.cpp index ad716c78d65d..d385267fe5ed 100644 --- a/tools/aapt2/process/SymbolTable.cpp +++ b/tools/aapt2/process/SymbolTable.cpp @@ -197,9 +197,10 @@ std::unique_ptr<SymbolTable::Symbol> ResourceTableSymbolSource::FindByName( std::unique_ptr<SymbolTable::Symbol> symbol = util::make_unique<SymbolTable::Symbol>(); symbol->is_public = (sr.entry->visibility.level == Visibility::Level::kPublic); - if (sr.package->id && sr.type->id && sr.entry->id) { - symbol->id = ResourceId(sr.package->id.value(), sr.type->id.value(), sr.entry->id.value()); - symbol->is_dynamic = (sr.package->id.value() == 0); + if (sr.entry->id) { + symbol->id = sr.entry->id.value(); + symbol->is_dynamic = + (sr.entry->id.value().package_id() == 0) || sr.entry->visibility.staged_api; } if (name.type == ResourceType::kAttr || name.type == ResourceType::kAttrPrivate) { @@ -227,8 +228,7 @@ bool AssetManagerSymbolSource::AddAssetPath(const StringPiece& path) { apk_assets.push_back(apk_asset.get()); } - asset_manager_.SetApkAssets(apk_assets, true /* invalidate_caches */, - false /* filter_incompatible_configs */); + asset_manager_.SetApkAssets(apk_assets); return true; } return false; @@ -351,9 +351,9 @@ std::unique_ptr<SymbolTable::Symbol> AssetManagerSymbolSource::FindByName( return true; } - auto value = asset_manager_.GetResource(res_id.id, true /* may_be_bag */); - if (value.has_value()) { - type_spec_flags = value->flags; + auto flags = asset_manager_.GetResourceTypeSpecFlags(res_id.id); + if (flags.has_value()) { + type_spec_flags = *flags; found = true; return false; } @@ -371,11 +371,12 @@ std::unique_ptr<SymbolTable::Symbol> AssetManagerSymbolSource::FindByName( } else { s = util::make_unique<SymbolTable::Symbol>(); s->id = res_id; - s->is_dynamic = IsPackageDynamic(ResourceId(res_id).package_id(), real_name.package); } if (s) { s->is_public = (type_spec_flags & android::ResTable_typeSpec::SPEC_PUBLIC) != 0; + s->is_dynamic = IsPackageDynamic(ResourceId(res_id).package_id(), real_name.package) || + (type_spec_flags & android::ResTable_typeSpec::SPEC_STAGED_API) != 0; return s; } return {}; @@ -406,8 +407,8 @@ std::unique_ptr<SymbolTable::Symbol> AssetManagerSymbolSource::FindById( return {}; } - auto value = asset_manager_.GetResource(id.id, true /* may_be_bag */); - if (!value.has_value()) { + auto flags = asset_manager_.GetResourceTypeSpecFlags(id.id); + if (!flags.has_value()) { return {}; } @@ -418,11 +419,12 @@ std::unique_ptr<SymbolTable::Symbol> AssetManagerSymbolSource::FindById( } else { s = util::make_unique<SymbolTable::Symbol>(); s->id = id; - s->is_dynamic = IsPackageDynamic(ResourceId(id).package_id(), name.package); } if (s) { - s->is_public = (value->flags & android::ResTable_typeSpec::SPEC_PUBLIC) != 0; + s->is_public = (*flags & android::ResTable_typeSpec::SPEC_PUBLIC) != 0; + s->is_dynamic = IsPackageDynamic(ResourceId(id).package_id(), name.package) || + (*flags & android::ResTable_typeSpec::SPEC_STAGED_API) != 0; return s; } return {}; diff --git a/tools/aapt2/split/TableSplitter.cpp b/tools/aapt2/split/TableSplitter.cpp index 6a672717f38e..116b2ab9aa98 100644 --- a/tools/aapt2/split/TableSplitter.cpp +++ b/tools/aapt2/split/TableSplitter.cpp @@ -185,7 +185,7 @@ void TableSplitter::SplitTable(ResourceTable* original_table) { // Initialize all packages for splits. for (size_t idx = 0; idx < split_count; idx++) { ResourceTable* split_table = splits_[idx].get(); - split_table->CreatePackage(pkg->name, pkg->id); + split_table->FindOrCreatePackage(pkg->name); } for (auto& type : pkg->types) { @@ -229,6 +229,7 @@ void TableSplitter::SplitTable(ResourceTable* original_table) { for (size_t idx = 0; idx < split_count; idx++) { const SplitConstraints& split_constraint = split_constraints_[idx]; ResourceTable* split_table = splits_[idx].get(); + CloningValueTransformer cloner(&split_table->string_pool); // Select the values we want from this entry for this split. SplitValueSelector selector(split_constraint); @@ -241,10 +242,7 @@ void TableSplitter::SplitTable(ResourceTable* original_table) { // not have actual values for each type/entry. ResourceTablePackage* split_pkg = split_table->FindPackage(pkg->name); ResourceTableType* split_type = split_pkg->FindOrCreateType(type->type); - if (!split_type->id) { - split_type->id = type->id; - split_type->visibility_level = type->visibility_level; - } + split_type->visibility_level = type->visibility_level; ResourceEntry* split_entry = split_type->FindOrCreateEntry(entry->name); if (!split_entry->id) { @@ -257,8 +255,7 @@ void TableSplitter::SplitTable(ResourceTable* original_table) { for (ResourceConfigValue* config_value : selected_values) { ResourceConfigValue* new_config_value = split_entry->FindOrCreateValue(config_value->config, config_value->product); - new_config_value->value = std::unique_ptr<Value>( - config_value->value->Clone(&split_table->string_pool)); + new_config_value->value = config_value->value->Transform(cloner); } } } diff --git a/tools/aapt2/split/TableSplitter_test.cpp b/tools/aapt2/split/TableSplitter_test.cpp index cdf07386c70f..c6a2ff33979b 100644 --- a/tools/aapt2/split/TableSplitter_test.cpp +++ b/tools/aapt2/split/TableSplitter_test.cpp @@ -193,15 +193,23 @@ TEST(TableSplitterTest, SplitTableByConfigAndDensity) { ResourceTable table; const ResourceName foo = test::ParseNameOrDie("android:string/foo"); - ASSERT_TRUE(table.AddResource(foo, test::ParseConfigOrDie("land-hdpi"), {}, - util::make_unique<Id>(), - test::GetDiagnostics())); - ASSERT_TRUE(table.AddResource(foo, test::ParseConfigOrDie("land-xhdpi"), {}, - util::make_unique<Id>(), - test::GetDiagnostics())); - ASSERT_TRUE(table.AddResource(foo, test::ParseConfigOrDie("land-xxhdpi"), {}, - util::make_unique<Id>(), - test::GetDiagnostics())); + ASSERT_TRUE( + table.AddResource(NewResourceBuilder(foo) + .SetValue(util::make_unique<Id>(), test::ParseConfigOrDie("land-hdpi")) + .Build(), + test::GetDiagnostics())); + + ASSERT_TRUE( + table.AddResource(NewResourceBuilder(foo) + .SetValue(util::make_unique<Id>(), test::ParseConfigOrDie("land-xhdpi")) + .Build(), + test::GetDiagnostics())); + + ASSERT_TRUE(table.AddResource( + NewResourceBuilder(foo) + .SetValue(util::make_unique<Id>(), test::ParseConfigOrDie("land-xxhdpi")) + .Build(), + test::GetDiagnostics())); std::vector<SplitConstraints> constraints; constraints.push_back( diff --git a/tools/aapt2/test/Builders.cpp b/tools/aapt2/test/Builders.cpp index 9a93f2a7476c..4816596da487 100644 --- a/tools/aapt2/test/Builders.cpp +++ b/tools/aapt2/test/Builders.cpp @@ -34,13 +34,6 @@ using ::android::StringPiece; namespace aapt { namespace test { -ResourceTableBuilder& ResourceTableBuilder::SetPackageId(const StringPiece& package_name, - uint8_t id) { - ResourceTablePackage* package = table_->CreatePackage(package_name, id); - CHECK(package != nullptr); - return *this; -} - ResourceTableBuilder& ResourceTableBuilder::AddSimple(const StringPiece& name, const ResourceId& id) { return AddValue(name, id, util::make_unique<Id>()); @@ -118,8 +111,13 @@ ResourceTableBuilder& ResourceTableBuilder::AddValue(const StringPiece& name, const ResourceId& id, std::unique_ptr<Value> value) { ResourceName res_name = ParseNameOrDie(name); - CHECK(table_->AddResourceWithIdMangled(res_name, id, config, {}, std::move(value), - GetDiagnostics())); + NewResourceBuilder builder(res_name); + builder.SetValue(std::move(value), config).SetAllowMangled(true); + if (id.id != 0U) { + builder.SetId(id); + } + + CHECK(table_->AddResource(builder.Build(), GetDiagnostics())); return *this; } @@ -128,10 +126,13 @@ ResourceTableBuilder& ResourceTableBuilder::SetSymbolState(const StringPiece& na Visibility::Level level, bool allow_new) { ResourceName res_name = ParseNameOrDie(name); - Visibility visibility; - visibility.level = level; - CHECK(table_->SetVisibilityWithIdMangled(res_name, visibility, id, GetDiagnostics())); - CHECK(table_->SetAllowNewMangled(res_name, AllowNew{}, GetDiagnostics())); + NewResourceBuilder builder(res_name); + builder.SetVisibility({level}).SetAllowNew({}).SetAllowMangled(true); + if (id.id != 0U) { + builder.SetId(id); + } + + CHECK(table_->AddResource(builder.Build(), GetDiagnostics())); return *this; } @@ -139,7 +140,14 @@ ResourceTableBuilder& ResourceTableBuilder::SetOverlayable(const StringPiece& na const OverlayableItem& overlayable) { ResourceName res_name = ParseNameOrDie(name); - CHECK(table_->SetOverlayable(res_name, overlayable, GetDiagnostics())); + CHECK(table_->AddResource( + NewResourceBuilder(res_name).SetOverlayable(overlayable).SetAllowMangled(true).Build(), + GetDiagnostics())); + return *this; +} + +ResourceTableBuilder& ResourceTableBuilder::Add(NewResource&& res) { + CHECK(table_->AddResource(std::move(res), GetDiagnostics())); return *this; } diff --git a/tools/aapt2/test/Builders.h b/tools/aapt2/test/Builders.h index c971a1b47fd5..3ff955d65f24 100644 --- a/tools/aapt2/test/Builders.h +++ b/tools/aapt2/test/Builders.h @@ -39,7 +39,6 @@ class ResourceTableBuilder { public: ResourceTableBuilder() = default; - ResourceTableBuilder& SetPackageId(const android::StringPiece& package_name, uint8_t id); ResourceTableBuilder& AddSimple(const android::StringPiece& name, const ResourceId& id = {}); ResourceTableBuilder& AddSimple(const android::StringPiece& name, const android::ConfigDescription& config, @@ -75,6 +74,7 @@ class ResourceTableBuilder { Visibility::Level level, bool allow_new = false); ResourceTableBuilder& SetOverlayable(const android::StringPiece& name, const OverlayableItem& overlayable); + ResourceTableBuilder& Add(NewResource&& res); StringPool* string_pool(); std::unique_ptr<ResourceTable> Build(); diff --git a/tools/aapt2/test/Common.cpp b/tools/aapt2/test/Common.cpp index b54c155ddc2f..23c22185a53f 100644 --- a/tools/aapt2/test/Common.cpp +++ b/tools/aapt2/test/Common.cpp @@ -21,7 +21,7 @@ using android::ConfigDescription; namespace aapt { namespace test { -struct DummyDiagnosticsImpl : public IDiagnostics { +struct TestDiagnosticsImpl : public IDiagnostics { void Log(Level level, DiagMessageActual& actual_msg) override { switch (level) { case Level::Note: @@ -39,7 +39,7 @@ struct DummyDiagnosticsImpl : public IDiagnostics { }; IDiagnostics* GetDiagnostics() { - static DummyDiagnosticsImpl diag; + static TestDiagnosticsImpl diag; return &diag; } diff --git a/tools/aapt2/test/Context.h b/tools/aapt2/test/Context.h index 553c43e6c469..5d8ded39e654 100644 --- a/tools/aapt2/test/Context.h +++ b/tools/aapt2/test/Context.h @@ -200,10 +200,11 @@ class StaticSymbolSourceBuilder { private: std::unique_ptr<SymbolTable::Symbol> CloneSymbol(SymbolTable::Symbol* sym) { + CloningValueTransformer cloner(nullptr); std::unique_ptr<SymbolTable::Symbol> clone = util::make_unique<SymbolTable::Symbol>(); clone->id = sym->id; if (sym->attribute) { - clone->attribute = std::unique_ptr<Attribute>(sym->attribute->Clone(nullptr)); + clone->attribute = std::unique_ptr<Attribute>(sym->attribute->Transform(cloner)); } clone->is_public = sym->is_public; return clone; diff --git a/tools/aapt2/test/Fixture.cpp b/tools/aapt2/test/Fixture.cpp index 5386802dbc8e..285e5a11b4c0 100644 --- a/tools/aapt2/test/Fixture.cpp +++ b/tools/aapt2/test/Fixture.cpp @@ -18,18 +18,17 @@ #include <dirent.h> -#include "android-base/errors.h" -#include "android-base/file.h" -#include "android-base/stringprintf.h" -#include "android-base/utf8.h" -#include "androidfw/StringPiece.h" -#include "gmock/gmock.h" -#include "gtest/gtest.h" +#include <android-base/errors.h> +#include <android-base/file.h> +#include <android-base/stringprintf.h> +#include <android-base/utf8.h> +#include <androidfw/StringPiece.h> +#include <gmock/gmock.h> +#include <gtest/gtest.h> #include "cmd/Compile.h" #include "cmd/Link.h" #include "io/FileStream.h" -#include "io/Util.h" #include "util/Files.h" using testing::Eq; @@ -81,9 +80,6 @@ void TestDirectoryFixture::TearDown() { } void TestDirectoryFixture::WriteFile(const std::string& path, const std::string& contents) { - CHECK(util::StartsWith(path, temp_dir_)) - << "Attempting to create a file outside of test temporary directory."; - // Create any intermediate directories specified in the path auto pos = std::find(path.rbegin(), path.rend(), file::sDirSep); if (pos != path.rend()) { @@ -170,4 +166,74 @@ void CommandTestFixture::AssertLoadXml(LoadedApk* apk, const io::IData* data, } } +ManifestBuilder::ManifestBuilder(CommandTestFixture* fixture) : fixture_(fixture) { +} + +ManifestBuilder& ManifestBuilder::SetPackageName(const std::string& package_name) { + package_name_ = package_name; + return *this; +} + +ManifestBuilder& ManifestBuilder::AddContents(const std::string& contents) { + contents_ += contents + "\n"; + return *this; +} + +std::string ManifestBuilder::Build(const std::string& file_path) { + const char* manifest_template = R"( + <manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="%s"> + %s + </manifest>)"; + + fixture_->WriteFile(file_path, android::base::StringPrintf( + manifest_template, package_name_.c_str(), contents_.c_str())); + return file_path; +} + +std::string ManifestBuilder::Build() { + return Build(fixture_->GetTestPath("AndroidManifest.xml")); +} + +LinkCommandBuilder::LinkCommandBuilder(CommandTestFixture* fixture) : fixture_(fixture) { +} + +LinkCommandBuilder& LinkCommandBuilder::SetManifestFile(const std::string& file) { + manifest_supplied_ = true; + args_.emplace_back("--manifest"); + args_.emplace_back(file); + return *this; +} + +LinkCommandBuilder& LinkCommandBuilder::AddFlag(const std::string& flag) { + args_.emplace_back(flag); + return *this; +} + +LinkCommandBuilder& LinkCommandBuilder::AddCompiledResDir(const std::string& dir, + IDiagnostics* diag) { + if (auto files = file::FindFiles(dir, diag)) { + for (std::string& compile_file : files.value()) { + args_.emplace_back(file::BuildPath({dir, compile_file})); + } + } + return *this; +} + +LinkCommandBuilder& LinkCommandBuilder::AddParameter(const std::string& param, + const std::string& value) { + args_.emplace_back(param); + args_.emplace_back(value); + return *this; +} + +std::vector<std::string> LinkCommandBuilder::Build(const std::string& out_apk) { + if (!manifest_supplied_) { + SetManifestFile(ManifestBuilder(fixture_).Build()); + } + args_.emplace_back("-o"); + args_.emplace_back(out_apk); + return args_; +} + } // namespace aapt
\ No newline at end of file diff --git a/tools/aapt2/test/Fixture.h b/tools/aapt2/test/Fixture.h index 457d65e30b65..f8c4889aee3b 100644 --- a/tools/aapt2/test/Fixture.h +++ b/tools/aapt2/test/Fixture.h @@ -32,7 +32,7 @@ namespace aapt { class TestDirectoryFixture : public ::testing::Test { public: TestDirectoryFixture() = default; - virtual ~TestDirectoryFixture() = default; + ~TestDirectoryFixture() override = default; // Creates the test directory or clears its contents if it contains previously created files. void SetUp() override; @@ -41,14 +41,14 @@ class TestDirectoryFixture : public ::testing::Test { void TearDown() override; // Retrieve the test directory of the fixture. - const android::StringPiece GetTestDirectory() { + android::StringPiece GetTestDirectory() { return temp_dir_; } // Retrieves the absolute path of the specified relative path in the test directory. Directories // should be separated using forward slashes ('/'), and these slashes will be translated to // backslashes when running Windows tests. - const std::string GetTestPath(const android::StringPiece& path) { + std::string GetTestPath(const android::StringPiece& path) { std::string base = temp_dir_; for (android::StringPiece part : util::Split(path, '/')) { file::AppendPath(&base, part); @@ -68,7 +68,7 @@ class TestDirectoryFixture : public ::testing::Test { class CommandTestFixture : public TestDirectoryFixture { public: CommandTestFixture() = default; - virtual ~CommandTestFixture() = default; + ~CommandTestFixture() override = default; // Wries the contents of the file to the specified path. The file is compiled and the flattened // file is written to the out directory. @@ -99,6 +99,33 @@ class CommandTestFixture : public TestDirectoryFixture { DISALLOW_COPY_AND_ASSIGN(CommandTestFixture); }; +struct ManifestBuilder { + explicit ManifestBuilder(CommandTestFixture* fixture); + ManifestBuilder& AddContents(const std::string& contents); + ManifestBuilder& SetPackageName(const std::string& package_name); + std::string Build(const std::string& file_path); + std::string Build(); + + private: + CommandTestFixture* fixture_; + std::string package_name_ = CommandTestFixture::kDefaultPackageName; + std::string contents_; +}; + +struct LinkCommandBuilder { + explicit LinkCommandBuilder(CommandTestFixture* fixture); + LinkCommandBuilder& AddCompiledResDir(const std::string& dir, IDiagnostics* diag); + LinkCommandBuilder& AddFlag(const std::string& flag); + LinkCommandBuilder& AddParameter(const std::string& param, const std::string& value); + LinkCommandBuilder& SetManifestFile(const std::string& manifest_path); + std::vector<std::string> Build(const std::string& out_apk_path); + + private: + CommandTestFixture* fixture_; + std::vector<std::string> args_; + bool manifest_supplied_ = false; +}; + } // namespace aapt #endif // AAPT_TEST_FIXTURE_H
\ No newline at end of file diff --git a/tools/aapt2/trace/TraceBuffer.h b/tools/aapt2/trace/TraceBuffer.h index 8618e0eeb731..ba751dd72f41 100644 --- a/tools/aapt2/trace/TraceBuffer.h +++ b/tools/aapt2/trace/TraceBuffer.h @@ -40,7 +40,7 @@ public: void BeginTrace(const std::string& tag); void EndTrace(); -// A master trace is required to flush events to disk. Events are formatted in systrace +// A main trace is required to flush events to disk. Events are formatted in systrace // json format. class FlushTrace { public: diff --git a/tools/aapt2/util/Maybe_test.cpp b/tools/aapt2/util/Maybe_test.cpp index 2057ddcc9e45..4c921f13a3ca 100644 --- a/tools/aapt2/util/Maybe_test.cpp +++ b/tools/aapt2/util/Maybe_test.cpp @@ -22,32 +22,32 @@ namespace aapt { -struct Dummy { - Dummy() { +struct Fake { + Fake() { data = new int; *data = 1; - std::cerr << "Construct Dummy{0x" << (void*)this << "} with data=0x" + std::cerr << "Construct Fake{0x" << (void*)this << "} with data=0x" << (void*)data << std::endl; } - Dummy(const Dummy& rhs) { + Fake(const Fake& rhs) { data = nullptr; if (rhs.data) { data = new int; *data = *rhs.data; } - std::cerr << "CopyConstruct Dummy{0x" << (void*)this << "} from Dummy{0x" + std::cerr << "CopyConstruct Fake{0x" << (void*)this << "} from Fake{0x" << (const void*)&rhs << "}" << std::endl; } - Dummy(Dummy&& rhs) { + Fake(Fake&& rhs) { data = rhs.data; rhs.data = nullptr; - std::cerr << "MoveConstruct Dummy{0x" << (void*)this << "} from Dummy{0x" + std::cerr << "MoveConstruct Fake{0x" << (void*)this << "} from Fake{0x" << (const void*)&rhs << "}" << std::endl; } - Dummy& operator=(const Dummy& rhs) { + Fake& operator=(const Fake& rhs) { delete data; data = nullptr; @@ -55,22 +55,22 @@ struct Dummy { data = new int; *data = *rhs.data; } - std::cerr << "CopyAssign Dummy{0x" << (void*)this << "} from Dummy{0x" + std::cerr << "CopyAssign Fake{0x" << (void*)this << "} from Fake{0x" << (const void*)&rhs << "}" << std::endl; return *this; } - Dummy& operator=(Dummy&& rhs) { + Fake& operator=(Fake&& rhs) { delete data; data = rhs.data; rhs.data = nullptr; - std::cerr << "MoveAssign Dummy{0x" << (void*)this << "} from Dummy{0x" + std::cerr << "MoveAssign Fake{0x" << (void*)this << "} from Fake{0x" << (const void*)&rhs << "}" << std::endl; return *this; } - ~Dummy() { - std::cerr << "Destruct Dummy{0x" << (void*)this << "} with data=0x" + ~Fake() { + std::cerr << "Destruct Fake{0x" << (void*)this << "} with data=0x" << (void*)data << std::endl; delete data; } @@ -100,15 +100,15 @@ TEST(MaybeTest, MakeSomething) { } TEST(MaybeTest, Lifecycle) { - Maybe<Dummy> val = make_nothing<Dummy>(); + Maybe<Fake> val = make_nothing<Fake>(); - Maybe<Dummy> val2 = make_value(Dummy()); + Maybe<Fake> val2 = make_value(Fake()); } TEST(MaybeTest, MoveAssign) { - Maybe<Dummy> val; + Maybe<Fake> val; { - Maybe<Dummy> val2 = Dummy(); + Maybe<Fake> val2 = Fake(); val = std::move(val2); } } diff --git a/tools/aapt2/util/Util.cpp b/tools/aapt2/util/Util.cpp index 793a2368526b..d7a8e6fe6ada 100644 --- a/tools/aapt2/util/Util.cpp +++ b/tools/aapt2/util/Util.cpp @@ -38,6 +38,11 @@ using ::android::StringPiece16; namespace aapt { namespace util { +// Package name and shared user id would be used as a part of the file name. +// Limits size to 223 and reserves 32 for the OS. +// See frameworks/base/core/java/android/content/pm/parsing/ParsingPackageUtils.java +constexpr static const size_t kMaxPackageNameSize = 223; + static std::vector<std::string> SplitAndTransform( const StringPiece& str, char sep, const std::function<char(char)>& f) { std::vector<std::string> parts; @@ -169,9 +174,21 @@ static int IsAndroidNameImpl(const StringPiece& str) { } bool IsAndroidPackageName(const StringPiece& str) { + if (str.size() > kMaxPackageNameSize) { + return false; + } return IsAndroidNameImpl(str) > 1 || str == "android"; } +bool IsAndroidSharedUserId(const android::StringPiece& package_name, + const android::StringPiece& shared_user_id) { + if (shared_user_id.size() > kMaxPackageNameSize) { + return false; + } + return shared_user_id.empty() || IsAndroidNameImpl(shared_user_id) > 1 || + package_name == "android"; +} + bool IsAndroidSplitName(const StringPiece& str) { return IsAndroidNameImpl(str) > 0; } diff --git a/tools/aapt2/util/Util.h b/tools/aapt2/util/Util.h index a956957eace8..c77aca31a810 100644 --- a/tools/aapt2/util/Util.h +++ b/tools/aapt2/util/Util.h @@ -81,6 +81,7 @@ bool IsJavaPackageName(const android::StringPiece& str); // - First character of each component (separated by '.') must be an ASCII letter. // - Subsequent characters of a component can be ASCII alphanumeric or an underscore. // - Package must contain at least two components, unless it is 'android'. +// - The maximum package name length is 223. bool IsAndroidPackageName(const android::StringPiece& str); // Tests that the string is a valid Android split name. @@ -88,6 +89,15 @@ bool IsAndroidPackageName(const android::StringPiece& str); // - Subsequent characters of a component can be ASCII alphanumeric or an underscore. bool IsAndroidSplitName(const android::StringPiece& str); +// Tests that the string is a valid Android shared user id. +// - First character of each component (separated by '.') must be an ASCII letter. +// - Subsequent characters of a component can be ASCII alphanumeric or an underscore. +// - Must contain at least two components, unless package name is 'android'. +// - The maximum shared user id length is 223. +// - Treat empty string as valid, it's the case of no shared user id. +bool IsAndroidSharedUserId(const android::StringPiece& package_name, + const android::StringPiece& shared_user_id); + // Converts the class name to a fully qualified class name from the given // `package`. Ex: // diff --git a/tools/aapt2/util/Util_test.cpp b/tools/aapt2/util/Util_test.cpp index d4e3bec24bd1..4ebcb115306f 100644 --- a/tools/aapt2/util/Util_test.cpp +++ b/tools/aapt2/util/Util_test.cpp @@ -27,6 +27,17 @@ using ::testing::SizeIs; namespace aapt { +// Test that a max package name size 223 is valid. +static const std::string kMaxPackageName = + "com.foo.nameRw8ajIGbYmqPuO0K7TYJFsI2pjlDAS0pYOYQlJvtQux" + "SoBKV1hMyNh4XfmcMj8OgPHfFaTXeKEHFMdGQHpw9Dz9Uqr8h1krgJLRv2aXyPCsGdVwBJzfZ4COVRiX3sc9O" + "CUrTTvZe6wXlgKb5Qz5qdkTBZ5euzGeoyZwestDTBIgT5exAl5efnznwzceS7VsIntgY10UUQvaoTsLBO6l"; +// Test that a long package name size 224 is invalid. +static const std::string kLongPackageName = + "com.foo.nameRw8ajIGbYmqPuO0K7TYJFsI2pjlDAS0pYOYQlJvtQu" + "xSoBKV1hMyNh4XfmcMj8OgPHfFaTXeKEHFMdGQHpw9Dz9Uqr8h1krgJLRv2aXyPCsGdVwBJzfZ4COVRiX3sc9O" + "CUrTTvZe6wXlgKb5Qz5qdkTBZ5euzGeoyZwestDTBIgT5exAl5efnznwzceS7VsIntgY10UUQvaoTsLBO6le"; + TEST(UtilTest, TrimOnlyWhitespace) { const StringPiece trimmed = util::TrimWhitespace("\n "); EXPECT_TRUE(trimmed.empty()); @@ -108,6 +119,7 @@ TEST(UtilTest, IsAndroidPackageName) { EXPECT_TRUE(util::IsAndroidPackageName("com.foo.test_thing")); EXPECT_TRUE(util::IsAndroidPackageName("com.foo.testing_thing_")); EXPECT_TRUE(util::IsAndroidPackageName("com.foo.test_99_")); + EXPECT_TRUE(util::IsAndroidPackageName(kMaxPackageName)); EXPECT_FALSE(util::IsAndroidPackageName("android._test")); EXPECT_FALSE(util::IsAndroidPackageName("com")); @@ -116,6 +128,27 @@ TEST(UtilTest, IsAndroidPackageName) { EXPECT_FALSE(util::IsAndroidPackageName(".android")); EXPECT_FALSE(util::IsAndroidPackageName("..")); EXPECT_FALSE(util::IsAndroidPackageName("cøm.foo")); + EXPECT_FALSE(util::IsAndroidPackageName(kLongPackageName)); +} + +TEST(UtilTest, IsAndroidSharedUserId) { + EXPECT_TRUE(util::IsAndroidSharedUserId("android", "foo")); + EXPECT_TRUE(util::IsAndroidSharedUserId("com.foo", "android.test")); + EXPECT_TRUE(util::IsAndroidSharedUserId("com.foo", "com.foo")); + EXPECT_TRUE(util::IsAndroidSharedUserId("com.foo", "com.foo.test_thing")); + EXPECT_TRUE(util::IsAndroidSharedUserId("com.foo", "com.foo.testing_thing_")); + EXPECT_TRUE(util::IsAndroidSharedUserId("com.foo", "com.foo.test_99_")); + EXPECT_TRUE(util::IsAndroidSharedUserId("com.foo", "")); + EXPECT_TRUE(util::IsAndroidSharedUserId("com.foo", kMaxPackageName)); + + EXPECT_FALSE(util::IsAndroidSharedUserId("com.foo", "android._test")); + EXPECT_FALSE(util::IsAndroidSharedUserId("com.foo", "com")); + EXPECT_FALSE(util::IsAndroidSharedUserId("com.foo", "_android")); + EXPECT_FALSE(util::IsAndroidSharedUserId("com.foo", "android.")); + EXPECT_FALSE(util::IsAndroidSharedUserId("com.foo", ".android")); + EXPECT_FALSE(util::IsAndroidSharedUserId("com.foo", "..")); + EXPECT_FALSE(util::IsAndroidSharedUserId("com.foo", "cøm.foo")); + EXPECT_FALSE(util::IsAndroidSharedUserId("com.foo", kLongPackageName)); } TEST(UtilTest, FullyQualifiedClassName) { diff --git a/tools/aapt2/xml/XmlActionExecutor.cpp b/tools/aapt2/xml/XmlActionExecutor.cpp index cb844f085ecc..ea42d26358a8 100644 --- a/tools/aapt2/xml/XmlActionExecutor.cpp +++ b/tools/aapt2/xml/XmlActionExecutor.cpp @@ -21,23 +21,34 @@ using ::android::StringPiece; namespace aapt { namespace xml { -static bool wrapper_one(XmlNodeAction::ActionFunc& f, Element* el, SourcePathDiagnostics*) { +static bool wrapper_one(const XmlNodeAction::ActionFunc& f, Element* el, + const XmlActionExecutorPolicy& policy, SourcePathDiagnostics*) { return f(el); } -static bool wrapper_two(XmlNodeAction::ActionFuncWithDiag& f, Element* el, - SourcePathDiagnostics* diag) { +static bool wrapper_two(const XmlNodeAction::ActionFuncWithDiag& f, Element* el, + const XmlActionExecutorPolicy& policy, SourcePathDiagnostics* diag) { return f(el, diag); } +static bool wrapper_three(const XmlNodeAction::ActionFuncWithPolicyAndDiag& f, Element* el, + const XmlActionExecutorPolicy& policy, SourcePathDiagnostics* diag) { + return f(el, policy, diag); +} + void XmlNodeAction::Action(XmlNodeAction::ActionFunc f) { - actions_.emplace_back(std::bind( - wrapper_one, std::move(f), std::placeholders::_1, std::placeholders::_2)); + actions_.emplace_back(std::bind(wrapper_one, std::move(f), std::placeholders::_1, + std::placeholders::_2, std::placeholders::_3)); } void XmlNodeAction::Action(XmlNodeAction::ActionFuncWithDiag f) { - actions_.emplace_back(std::bind( - wrapper_two, std::move(f), std::placeholders::_1, std::placeholders::_2)); + actions_.emplace_back(std::bind(wrapper_two, std::move(f), std::placeholders::_1, + std::placeholders::_2, std::placeholders::_3)); +} + +void XmlNodeAction::Action(XmlNodeAction::ActionFuncWithPolicyAndDiag f) { + actions_.emplace_back(std::bind(wrapper_three, std::move(f), std::placeholders::_1, + std::placeholders::_2, std::placeholders::_3)); } static void PrintElementToDiagMessage(const Element* el, DiagMessage* msg) { @@ -51,8 +62,8 @@ static void PrintElementToDiagMessage(const Element* el, DiagMessage* msg) { bool XmlNodeAction::Execute(XmlActionExecutorPolicy policy, std::vector<StringPiece>* bread_crumb, SourcePathDiagnostics* diag, Element* el) const { bool error = false; - for (const ActionFuncWithDiag& action : actions_) { - error |= !action(el, diag); + for (const ActionFuncWithPolicyAndDiag& action : actions_) { + error |= !action(el, policy, diag); } for (Element* child_el : el->GetChildElements()) { @@ -74,11 +85,11 @@ bool XmlNodeAction::Execute(XmlActionExecutorPolicy policy, std::vector<StringPi for (const StringPiece& element : *bread_crumb) { error_msg << "<" << element << ">"; } - if (policy == XmlActionExecutorPolicy::kWhitelistWarning) { + if (policy == XmlActionExecutorPolicy::kAllowListWarning) { // Treat the error only as a warning. diag->Warn(error_msg); } else { - // Policy is XmlActionExecutorPolicy::kWhitelist, we should fail. + // Policy is XmlActionExecutorPolicy::kAllowList, we should fail. diag->Error(error_msg); error = true; } @@ -94,7 +105,7 @@ bool XmlActionExecutor::Execute(XmlActionExecutorPolicy policy, IDiagnostics* di Element* el = doc->root.get(); if (!el) { - if (policy == XmlActionExecutorPolicy::kWhitelist) { + if (policy == XmlActionExecutorPolicy::kAllowList) { source_diag.Error(DiagMessage() << "no root XML tag found"); return false; } @@ -109,7 +120,7 @@ bool XmlActionExecutor::Execute(XmlActionExecutorPolicy policy, IDiagnostics* di return iter->second.Execute(policy, &bread_crumb, &source_diag, el); } - if (policy == XmlActionExecutorPolicy::kWhitelist) { + if (policy == XmlActionExecutorPolicy::kAllowList) { DiagMessage error_msg(el->line_number); error_msg << "unexpected root element "; PrintElementToDiagMessage(el, &error_msg); diff --git a/tools/aapt2/xml/XmlActionExecutor.h b/tools/aapt2/xml/XmlActionExecutor.h index f689b2a3eaa8..78c43345deb7 100644 --- a/tools/aapt2/xml/XmlActionExecutor.h +++ b/tools/aapt2/xml/XmlActionExecutor.h @@ -37,18 +37,20 @@ enum class XmlActionExecutorPolicy { // The actions defined must match and run. If an element is found that does not match an action, // an error occurs. // Note: namespaced elements are always ignored. - kWhitelist, + kAllowList, // The actions defined should match and run. if an element is found that does not match an // action, a warning is printed. // Note: namespaced elements are always ignored. - kWhitelistWarning, + kAllowListWarning, }; // Contains the actions to perform at this XML node. This is a recursive data structure that // holds XmlNodeActions for child XML nodes. class XmlNodeAction { public: + using ActionFuncWithPolicyAndDiag = + std::function<bool(Element*, XmlActionExecutorPolicy, SourcePathDiagnostics*)>; using ActionFuncWithDiag = std::function<bool(Element*, SourcePathDiagnostics*)>; using ActionFunc = std::function<bool(Element*)>; @@ -61,6 +63,7 @@ class XmlNodeAction { // Add an action to be performed at this XmlNodeAction. void Action(ActionFunc f); void Action(ActionFuncWithDiag); + void Action(ActionFuncWithPolicyAndDiag); private: friend class XmlActionExecutor; @@ -69,7 +72,7 @@ class XmlNodeAction { SourcePathDiagnostics* diag, Element* el) const; std::map<std::string, XmlNodeAction> map_; - std::vector<ActionFuncWithDiag> actions_; + std::vector<ActionFuncWithPolicyAndDiag> actions_; }; // Allows the definition of actions to execute at specific XML elements defined by their hierarchy. diff --git a/tools/aapt2/xml/XmlActionExecutor_test.cpp b/tools/aapt2/xml/XmlActionExecutor_test.cpp index d39854e5fe4e..d47b49590f5c 100644 --- a/tools/aapt2/xml/XmlActionExecutor_test.cpp +++ b/tools/aapt2/xml/XmlActionExecutor_test.cpp @@ -60,10 +60,10 @@ TEST(XmlActionExecutorTest, FailsWhenUndefinedHierarchyExists) { StdErrDiagnostics diag; doc = test::BuildXmlDom("<manifest><application /><activity /></manifest>"); - ASSERT_FALSE(executor.Execute(XmlActionExecutorPolicy::kWhitelist, &diag, doc.get())); + ASSERT_FALSE(executor.Execute(XmlActionExecutorPolicy::kAllowList, &diag, doc.get())); doc = test::BuildXmlDom("<manifest><application><activity /></application></manifest>"); - ASSERT_FALSE(executor.Execute(XmlActionExecutorPolicy::kWhitelist, &diag, doc.get())); + ASSERT_FALSE(executor.Execute(XmlActionExecutorPolicy::kAllowList, &diag, doc.get())); } } // namespace xml diff --git a/tools/aapt2/xml/XmlDom.cpp b/tools/aapt2/xml/XmlDom.cpp index 005eeb936612..2cdcfe45b50e 100644 --- a/tools/aapt2/xml/XmlDom.cpp +++ b/tools/aapt2/xml/XmlDom.cpp @@ -374,6 +374,7 @@ std::unique_ptr<XmlResource> Inflate(const void* data, size_t len, std::string* std::unique_ptr<XmlResource> XmlResource::Clone() const { std::unique_ptr<XmlResource> cloned = util::make_unique<XmlResource>(file); + CloningValueTransformer cloner(&cloned->string_pool); if (root != nullptr) { cloned->root = root->CloneElement([&](const xml::Element& src, xml::Element* dst) { dst->attributes.reserve(src.attributes.size()); @@ -384,7 +385,7 @@ std::unique_ptr<XmlResource> XmlResource::Clone() const { cloned_attr.value = attr.value; cloned_attr.compiled_attribute = attr.compiled_attribute; if (attr.compiled_value != nullptr) { - cloned_attr.compiled_value.reset(attr.compiled_value->Clone(&cloned->string_pool)); + cloned_attr.compiled_value = attr.compiled_value->Transform(cloner); } dst->attributes.push_back(std::move(cloned_attr)); } diff --git a/tools/aapt2/xml/XmlPullParser.cpp b/tools/aapt2/xml/XmlPullParser.cpp index a023494ad8f7..182203d397c3 100644 --- a/tools/aapt2/xml/XmlPullParser.cpp +++ b/tools/aapt2/xml/XmlPullParser.cpp @@ -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(); } diff --git a/tools/aapt2/xml/XmlPullParser.h b/tools/aapt2/xml/XmlPullParser.h index 6ebaa285745b..5da2d4b10a4b 100644 --- a/tools/aapt2/xml/XmlPullParser.h +++ b/tools/aapt2/xml/XmlPullParser.h @@ -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_; }; diff --git a/tools/aosp/aosp_sha.sh b/tools/aosp/aosp_sha.sh index f25fcdcb7479..99aaa3c4d6e5 100755 --- a/tools/aosp/aosp_sha.sh +++ b/tools/aosp/aosp_sha.sh @@ -11,7 +11,7 @@ else if (( count == 0 )); then echo fi - echo -e "\033[0;31mThe source of truth for '$file' is in AOSP.\033[0m" + echo -e "\033[0;31;47mThe source of truth for '$file' is in AOSP.\033[0m" (( count++ )) done < <(git show --name-only --pretty=format: $1 | grep -- "$2") if (( count != 0 )); then diff --git a/tools/codegen/src/com/android/codegen/ClassInfo.kt b/tools/codegen/src/com/android/codegen/ClassInfo.kt index bf95a2eb2193..056898c9eca1 100644 --- a/tools/codegen/src/com/android/codegen/ClassInfo.kt +++ b/tools/codegen/src/com/android/codegen/ClassInfo.kt @@ -1,12 +1,14 @@ package com.android.codegen import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration +import com.github.javaparser.ast.body.TypeDeclaration open class ClassInfo(val classAst: ClassOrInterfaceDeclaration, val fileInfo: FileInfo) { val fileAst = fileInfo.fileAst val nestedClasses = classAst.members.filterIsInstance<ClassOrInterfaceDeclaration>() + val nestedTypes = classAst.members.filterIsInstance<TypeDeclaration<*>>() val superInterfaces = classAst.implementedTypes.map { it.asString() } val superClass = classAst.extendedTypes.getOrNull(0) diff --git a/tools/codegen/src/com/android/codegen/ClassPrinter.kt b/tools/codegen/src/com/android/codegen/ClassPrinter.kt index b90e1bb3e7e7..8cae14a2d040 100644 --- a/tools/codegen/src/com/android/codegen/ClassPrinter.kt +++ b/tools/codegen/src/com/android/codegen/ClassPrinter.kt @@ -145,7 +145,6 @@ class ClassPrinter( } return when { cliArgs.contains("--hidden-$kebabCase") -> true - this == FeatureFlag.BUILD_UPON -> FeatureFlag.BUILDER.hidden else -> false } } diff --git a/tools/codegen/src/com/android/codegen/Debug.kt b/tools/codegen/src/com/android/codegen/Debug.kt new file mode 100644 index 000000000000..de3184468540 --- /dev/null +++ b/tools/codegen/src/com/android/codegen/Debug.kt @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.codegen + +import com.github.javaparser.ast.Node + +fun Node.dump(indent: String = ""): String { + return buildString { + append(indent) + appendln(dumpOneLineNoChildren()) + childNodes.forEach { child -> + append(child.dump(indent + " ")) + } + } +} + +private fun Node.dumpOneLineNoChildren(): String { + val node = this + return buildString { + append(node::class.java.simpleName) + if (childNodes.isEmpty()) { + append(": ").append(node.toString()) + } + } +} diff --git a/tools/codegen/src/com/android/codegen/FieldInfo.kt b/tools/codegen/src/com/android/codegen/FieldInfo.kt index 02ebaef90f0b..74392ddc30e6 100644 --- a/tools/codegen/src/com/android/codegen/FieldInfo.kt +++ b/tools/codegen/src/com/android/codegen/FieldInfo.kt @@ -220,11 +220,12 @@ data class FieldInfo( isBinder(FieldInnerType!!) -> "BinderList" else -> "ParcelableList" } + isStrongBinder(Type) -> "StrongBinder" isIInterface(Type) -> "StrongInterface" - isBinder(Type) -> "StrongBinder" else -> "TypedObject" }.capitalize() + private fun isStrongBinder(type: String) = type == "Binder" || type == "IBinder" private fun isBinder(type: String) = type == "Binder" || type == "IBinder" || isIInterface(type) private fun isIInterface(type: String) = type.length >= 2 && type[0] == 'I' && type[1].isUpperCase() }
\ No newline at end of file diff --git a/tools/codegen/src/com/android/codegen/FileInfo.kt b/tools/codegen/src/com/android/codegen/FileInfo.kt index 909472640f29..a1d0389b0041 100644 --- a/tools/codegen/src/com/android/codegen/FileInfo.kt +++ b/tools/codegen/src/com/android/codegen/FileInfo.kt @@ -272,7 +272,7 @@ class FileInfo( /** Debug info */ fun summary(): String = when(this) { is Code -> "${javaClass.simpleName}(${lines.size} lines): ${lines.getOrNull(0)?.take(70) ?: ""}..." - is DataClass -> "DataClass ${ast.nameAsString}:\n" + + is DataClass -> "DataClass ${ast.nameAsString} nested:${ast.nestedTypes.map { it.nameAsString }}:\n" + chunks.joinToString("\n") { it.summary() } + "\n//end ${ast.nameAsString}" } diff --git a/tools/codegen/src/com/android/codegen/Generators.kt b/tools/codegen/src/com/android/codegen/Generators.kt index 5a96cf1d9bdb..5fc800b09ee9 100644 --- a/tools/codegen/src/com/android/codegen/Generators.kt +++ b/tools/codegen/src/com/android/codegen/Generators.kt @@ -3,7 +3,8 @@ package com.android.codegen import com.github.javaparser.ast.body.FieldDeclaration import com.github.javaparser.ast.body.MethodDeclaration import com.github.javaparser.ast.body.VariableDeclarator -import com.github.javaparser.ast.expr.* +import com.github.javaparser.ast.expr.AnnotationExpr +import com.github.javaparser.ast.expr.ArrayInitializerExpr import java.io.File @@ -13,10 +14,7 @@ import java.io.File fun ClassPrinter.generateConstDefs() { val consts = classAst.fields.filter { it.isStatic && it.isFinal && it.variables.all { variable -> - val initializer = variable.initializer.orElse(null) - val isLiteral = initializer is LiteralExpr - || (initializer is UnaryExpr && initializer.expression is LiteralExpr) - isLiteral && variable.type.asString() in listOf("int", "String") + variable.type.asString() in listOf("int", "String") } && it.annotations.none { it.nameAsString == DataClassSuppressConstDefs } }.flatMap { field -> field.variables.map { it to field } } val intConsts = consts.filter { it.first.type.asString() == "int" } @@ -163,7 +161,12 @@ fun ClassPrinter.generateCopyConstructor() { return } - +"/** Copy constructor */" + +"/**" + +" * Copy constructor" + if (FeatureFlag.COPY_CONSTRUCTOR.hidden) { + +" * @hide" + } + +" */" +GENERATED_MEMBER_HEADER "public $ClassName(@$NonNull $ClassName orig)" { fields.forEachApply { @@ -703,7 +706,7 @@ fun ClassPrinter.generateSetters() { generateFieldJavadoc(forceHide = FeatureFlag.SETTERS.hidden) +GENERATED_MEMBER_HEADER - "public $ClassType set$NameUpperCamel($annotatedTypeForSetterParam value)" { + "public @$NonNull $ClassType set$NameUpperCamel($annotatedTypeForSetterParam value)" { generateSetFrom("value") +"return this;" } diff --git a/tools/codegen/src/com/android/codegen/InputSignaturesComputation.kt b/tools/codegen/src/com/android/codegen/InputSignaturesComputation.kt index d6953c00fc0b..83108e5ae109 100644 --- a/tools/codegen/src/com/android/codegen/InputSignaturesComputation.kt +++ b/tools/codegen/src/com/android/codegen/InputSignaturesComputation.kt @@ -41,7 +41,10 @@ private fun ClassPrinter.generateInputSignaturesForClass(classAst: ClassOrInterf } } + ("class ${classAst.nameAsString}" + " extends ${classAst.extendedTypes.map { getFullClassName(it) }.ifEmpty { listOf("java.lang.Object") }.joinToString(", ")}" + - " implements [${classAst.implementedTypes.joinToString(", ") { getFullClassName(it) }}]") + " implements [${classAst.implementedTypes.joinToString(", ") { getFullClassName(it) }}]") + + classAst.nestedNonDataClasses.flatMap { nestedClass -> + generateInputSignaturesForClass(nestedClass) + } } private fun ClassPrinter.annotationsToString(annotatedAst: NodeWithAnnotations<*>): String { @@ -60,6 +63,7 @@ private fun ClassPrinter.annotationToString(ann: AnnotationExpr?): String { append("@") append(getFullClassName(ann.nameAsString)) if (ann is MarkerAnnotationExpr) return@buildString + if (!ann.nameAsString.startsWith("DataClass")) return@buildString append("(") @@ -125,7 +129,7 @@ private fun ClassPrinter.getFullClassName(className: String): String { if (classAst.nameAsString == className) return thisPackagePrefix + classAst.nameAsString - nestedClasses.find { + nestedTypes.find { it.nameAsString == className }?.let { return thisClassPrefix + it.nameAsString } @@ -141,6 +145,8 @@ private fun ClassPrinter.getFullClassName(className: String): String { if (className[0].isLowerCase()) return className //primitive + if (className[0] == '?') return className //wildcard + return thisPackagePrefix + className } diff --git a/tools/codegen/src/com/android/codegen/SharedConstants.kt b/tools/codegen/src/com/android/codegen/SharedConstants.kt index 6f740cd663e3..4da401951470 100644 --- a/tools/codegen/src/com/android/codegen/SharedConstants.kt +++ b/tools/codegen/src/com/android/codegen/SharedConstants.kt @@ -1,7 +1,7 @@ package com.android.codegen const val CODEGEN_NAME = "codegen" -const val CODEGEN_VERSION = "1.0.15" +const val CODEGEN_VERSION = "1.0.23" const val CANONICAL_BUILDER_CLASS = "Builder" const val BASE_BUILDER_CLASS = "BaseBuilder" diff --git a/tools/codegen/src/com/android/codegen/Utils.kt b/tools/codegen/src/com/android/codegen/Utils.kt index a117aa09ab62..9ceb2042d74e 100644 --- a/tools/codegen/src/com/android/codegen/Utils.kt +++ b/tools/codegen/src/com/android/codegen/Utils.kt @@ -103,6 +103,10 @@ val TypeDeclaration<*>.nestedTypes get() = childNodes.filterIsInstance<TypeDecla val TypeDeclaration<*>.nestedDataClasses get() = nestedTypes.filterIsInstance<ClassOrInterfaceDeclaration>() .filter { it.annotations.any { it.nameAsString.endsWith("DataClass") } } +val TypeDeclaration<*>.nestedNonDataClasses get() + = nestedTypes.filterIsInstance<ClassOrInterfaceDeclaration>() + .filter { it.annotations.none { it.nameAsString.endsWith("DataClass") } } + .filterNot { it.isInterface } val TypeDeclaration<*>.startLine get() = range.get()!!.begin.line inline fun <T> List<T>.forEachSequentialPair(action: (T, T?) -> Unit) { diff --git a/tools/fonts/Android.bp b/tools/fonts/Android.bp index 14c4b2c3fb03..eeb9e3ceda1e 100644 --- a/tools/fonts/Android.bp +++ b/tools/fonts/Android.bp @@ -46,3 +46,15 @@ python_binary_host { "fontTools", ], } + +python_binary_host { + name: "update_font_metadata", + defaults: ["fonts_python_defaults"], + main: "update_font_metadata.py", + srcs: [ + "update_font_metadata.py", + ], + libs: [ + "fontTools", + ], +} diff --git a/tools/fonts/fontchain_linter.py b/tools/fonts/fontchain_linter.py index a4a315b7e371..1266ccee4df5 100755 --- a/tools/fonts/fontchain_linter.py +++ b/tools/fonts/fontchain_linter.py @@ -4,6 +4,7 @@ import collections import copy import glob from os import path +import re import sys from xml.etree import ElementTree @@ -113,7 +114,7 @@ def get_variation_sequences_cmap(font): def get_emoji_map(font): # Add normal characters emoji_map = copy.copy(get_best_cmap(font)) - reverse_cmap = {glyph: code for code, glyph in emoji_map.items()} + reverse_cmap = {glyph: code for code, glyph in emoji_map.items() if not contains_pua(code) } # Add variation sequences vs_dict = get_variation_sequences_cmap(font).uvsDict @@ -199,8 +200,9 @@ def check_hyphens(hyphens_dir): class FontRecord(object): - def __init__(self, name, scripts, variant, weight, style, fallback_for, font): + def __init__(self, name, psName, scripts, variant, weight, style, fallback_for, font): self.name = name + self.psName = psName self.scripts = scripts self.variant = variant self.weight = weight @@ -236,6 +238,7 @@ def parse_fonts_xml(fonts_xml_path): assert variant in {None, 'elegant', 'compact'}, ( 'Unexpected value for variant: %s' % variant) + trim_re = re.compile(r"^[ \n\r\t]*(.+)[ \n\r\t]*$") for family in families: name = family.get('name') variant = family.get('variant') @@ -251,6 +254,10 @@ def parse_fonts_xml(fonts_xml_path): assert child.tag == 'font', ( 'Unknown tag <%s>' % child.tag) font_file = child.text.rstrip() + + m = trim_re.match(font_file) + font_file = m.group(1) + weight = int(child.get('weight')) assert weight % 100 == 0, ( 'Font weight "%d" is not a multiple of 100.' % weight) @@ -270,11 +277,12 @@ def parse_fonts_xml(fonts_xml_path): if index: index = int(index) - if not path.exists(path.join(_fonts_dir, font_file)): + if not path.exists(path.join(_fonts_dir, m.group(1))): continue # Missing font is a valid case. Just ignore the missing font files. record = FontRecord( name, + child.get('postScriptName'), frozenset(scripts), variant, weight, @@ -314,27 +322,54 @@ def get_emoji_font(): return emoji_fonts[0] +def is_pua(x): + return 0xE000 <= x <= 0xF8FF or 0xF0000 <= x <= 0xFFFFD or 0x100000 <= x <= 0x10FFFD + +def contains_pua(sequence): + if type(sequence) is tuple: + return any([is_pua(x) for x in sequence]) + else: + return is_pua(sequence) + + +def check_emoji_compat(): + ttf = open_font(get_emoji_font()) + meta = ttf['meta'] + assert meta, 'Compat font must have meta table' + assert 'Emji' in meta.data, 'meta table should have \'Emji\' data.' + def check_emoji_font_coverage(emoji_font, all_emoji, equivalent_emoji): coverage = get_emoji_map(emoji_font) + + errors = [] + for sequence in all_emoji: - assert sequence in coverage, ( - '%s is not supported in the emoji font.' % printable(sequence)) + if not sequence in coverage: + errors.append('%s is not supported in the emoji font.' % printable(sequence)) for sequence in coverage: if sequence in {0x0000, 0x000D, 0x0020}: # The font needs to support a few extra characters, which is OK continue - assert sequence in all_emoji, ( - 'Emoji font should not support %s.' % printable(sequence)) + + if contains_pua(sequence): + # The font needs to have some PUA for EmojiCompat library. + continue + + if sequence not in all_emoji: + errors.append('%s support unexpected in the emoji font.' % printable(sequence)) for first, second in equivalent_emoji.items(): - assert coverage[first] == coverage[second], ( - '%s and %s should map to the same glyph.' % ( + if first not in coverage or second not in coverage: + continue # sequence will be reported missing + if coverage[first] != coverage[second]: + errors.append('%s and %s should map to the same glyph.' % ( printable(first), printable(second))) for glyph in set(coverage.values()): - maps_to_glyph = [seq for seq in coverage if coverage[seq] == glyph] + maps_to_glyph = [ + seq for seq in coverage if coverage[seq] == glyph and not contains_pua(seq) ] if len(maps_to_glyph) > 1: # There are more than one sequences mapping to the same glyph. We # need to make sure they were expected to be equivalent. @@ -344,11 +379,13 @@ def check_emoji_font_coverage(emoji_font, all_emoji, equivalent_emoji): while equivalent_seq in equivalent_emoji: equivalent_seq = equivalent_emoji[equivalent_seq] equivalent_seqs.add(equivalent_seq) - assert len(equivalent_seqs) == 1, ( - 'The sequences %s should not result in the same glyph %s' % ( + if len(equivalent_seqs) != 1: + errors.append('The sequences %s should not result in the same glyph %s' % ( printable(equivalent_seqs), glyph)) + assert not errors, '%d emoji font errors:\n%s\n%d emoji font coverage errors' % (len(errors), '\n'.join(errors), len(errors)) + def check_emoji_defaults(default_emoji): missing_text_chars = _emoji_properties['Emoji'] - default_emoji @@ -657,6 +694,37 @@ def check_cjk_punctuation(): break assert_font_supports_none_of_chars(record.font, cjk_punctuation, name) +def getPostScriptName(font): + font_file, index = font + font_path = path.join(_fonts_dir, font_file) + if index is not None: + # Use the first font file in the collection for resolving post script name. + ttf = ttLib.TTFont(font_path, fontNumber=0) + else: + ttf = ttLib.TTFont(font_path) + + nameTable = ttf['name'] + for name in nameTable.names: + if (name.nameID == 6 and name.platformID == 3 and name.platEncID == 1 + and name.langID == 0x0409): + return str(name) + +def check_canonical_name(): + for record in _all_fonts: + file_name, index = record.font + + psName = getPostScriptName(record.font) + if record.psName: + # If fonts element has postScriptName attribute, it should match with the PostScript + # name in the name table. + assert psName == record.psName, ('postScriptName attribute %s should match with %s' % ( + record.psName, psName)) + else: + # If fonts element doesn't have postScriptName attribute, the file name should match + # with the PostScript name in the name table. + assert psName == file_name[:-4], ('file name %s should match with %s' % ( + file_name, psName)) + def main(): global _fonts_dir @@ -675,11 +743,14 @@ def main(): check_cjk_punctuation() + check_canonical_name() + check_emoji = sys.argv[2] if check_emoji == 'true': ucd_path = sys.argv[3] parse_ucd(ucd_path) all_emoji, default_emoji, equivalent_emoji = compute_expected_emoji() + check_emoji_compat() check_emoji_coverage(all_emoji, equivalent_emoji) check_emoji_defaults(default_emoji) diff --git a/tools/fonts/update_font_metadata.py b/tools/fonts/update_font_metadata.py new file mode 100755 index 000000000000..c07a98a1e3d2 --- /dev/null +++ b/tools/fonts/update_font_metadata.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python + +import argparse + +from fontTools import ttLib + + +def update_font_revision(font, revisionSpec): + if revisionSpec.startswith('+'): + font['head'].fontRevision += float(revisionSpec[1:]) + else: + font['head'].fontRevision = float(revisionSpec) + + +def main(): + args_parser = argparse.ArgumentParser(description='Update font file metadata') + args_parser.add_argument('--input', help='Input otf/ttf font file.') + args_parser.add_argument('--output', help='Output file for updated font file.') + args_parser.add_argument('--revision', help='Updated font revision. Use + to update revision based on the current revision') + args = args_parser.parse_args() + + font = ttLib.TTFont(args.input) + update_font_revision(font, args.revision) + font.save(args.output) + +if __name__ == "__main__": + main() diff --git a/tools/powerstats/Android.bp b/tools/powerstats/Android.bp new file mode 100644 index 000000000000..9c58daf0f922 --- /dev/null +++ b/tools/powerstats/Android.bp @@ -0,0 +1,19 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +java_binary_host { + name: "PowerStatsServiceProtoParser", + manifest: "PowerStatsServiceProtoParser_manifest.txt", + srcs: [ + "*.java", + ], + static_libs: [ + "platformprotos", + ], +} diff --git a/tools/powerstats/OWNERS b/tools/powerstats/OWNERS index d68066bb8c40..12f13ea63db0 100644 --- a/tools/powerstats/OWNERS +++ b/tools/powerstats/OWNERS @@ -1 +1 @@ -include /services/core/java/com/android/server/power/OWNERS +include /services/core/java/com/android/server/powerstats/OWNERS diff --git a/tools/powerstats/PowerStatsServiceProtoParser.java b/tools/powerstats/PowerStatsServiceProtoParser.java new file mode 100644 index 000000000000..04f9bf26b43c --- /dev/null +++ b/tools/powerstats/PowerStatsServiceProtoParser.java @@ -0,0 +1,196 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.powerstats; + +import java.io.FileInputStream; +import java.io.IOException; + +/** + * This class implements a utility to parse ODPM data out + * of incident reports contained in bugreports. The data + * is output to STDOUT in csv format. + */ +public class PowerStatsServiceProtoParser { + private static void printEnergyMeterInfo(PowerStatsServiceMeterProto proto) { + String csvHeader = new String(); + for (int i = 0; i < proto.getChannelCount(); i++) { + ChannelProto energyMeterInfo = proto.getChannel(i); + csvHeader += "Index,Timestamp,Duration," + energyMeterInfo.getId() + + "/" + energyMeterInfo.getName() + "/" + energyMeterInfo.getSubsystem() + ","; + } + System.out.println(csvHeader); + } + + private static void printEnergyMeasurements(PowerStatsServiceMeterProto proto) { + int energyMeterInfoCount = proto.getChannelCount(); + + if (energyMeterInfoCount > 0) { + int energyMeasurementCount = proto.getEnergyMeasurementCount(); + int energyMeasurementSetCount = energyMeasurementCount / energyMeterInfoCount; + + for (int i = 0; i < energyMeasurementSetCount; i++) { + String csvRow = new String(); + for (int j = 0; j < energyMeterInfoCount; j++) { + EnergyMeasurementProto energyMeasurement = + proto.getEnergyMeasurement(i * energyMeterInfoCount + j); + csvRow += energyMeasurement.getId() + "," + + energyMeasurement.getTimestampMs() + "," + + energyMeasurement.getDurationMs() + "," + + energyMeasurement.getEnergyUws() + ","; + } + System.out.println(csvRow); + } + } else { + System.out.println("Error: energyMeterInfoCount is zero"); + } + } + + private static void printEnergyConsumer(PowerStatsServiceModelProto proto) { + String csvHeader = new String(); + for (int i = 0; i < proto.getEnergyConsumerCount(); i++) { + EnergyConsumerProto energyConsumer = proto.getEnergyConsumer(i); + csvHeader += "Index,Timestamp," + energyConsumer.getId() + "/" + + energyConsumer.getOrdinal() + "/" + + energyConsumer.getType() + "/" + + energyConsumer.getName() + ","; + } + System.out.println(csvHeader); + } + + private static void printEnergyConsumerResults(PowerStatsServiceModelProto proto) { + int energyConsumerCount = proto.getEnergyConsumerCount(); + + if (energyConsumerCount > 0) { + int energyConsumerResultCount = proto.getEnergyConsumerResultCount(); + int energyConsumerResultSetCount = energyConsumerResultCount / energyConsumerCount; + + for (int i = 0; i < energyConsumerResultSetCount; i++) { + String csvRow = new String(); + for (int j = 0; j < energyConsumerCount; j++) { + EnergyConsumerResultProto energyConsumerResult = + proto.getEnergyConsumerResult(i * energyConsumerCount + j); + csvRow += energyConsumerResult.getId() + "," + + energyConsumerResult.getTimestampMs() + "," + + energyConsumerResult.getEnergyUws() + ","; + for (int k = 0; k < energyConsumerResult.getAttributionCount(); k++) { + final EnergyConsumerAttributionProto energyConsumerAttribution = + energyConsumerResult.getAttribution(k); + csvRow += energyConsumerAttribution.getUid() + "," + + energyConsumerAttribution.getEnergyUws() + ","; + } + } + System.out.println(csvRow); + } + } else { + System.out.println("Error: energyConsumerCount is zero"); + } + } + + private static void printPowerEntityInfo(PowerStatsServiceResidencyProto proto) { + String csvHeader = new String(); + for (int i = 0; i < proto.getPowerEntityCount(); i++) { + PowerEntityProto powerEntity = proto.getPowerEntity(i); + csvHeader += powerEntity.getId() + "," + powerEntity.getName() + ","; + for (int j = 0; j < powerEntity.getStatesCount(); j++) { + StateProto state = powerEntity.getStates(j); + csvHeader += state.getId() + "," + state.getName() + ","; + } + } + System.out.println(csvHeader); + } + + private static void printStateResidencyResult(PowerStatsServiceResidencyProto proto) { + for (int i = 0; i < proto.getStateResidencyResultCount(); i++) { + String csvRow = new String(); + + StateResidencyResultProto stateResidencyResult = proto.getStateResidencyResult(i); + csvRow += stateResidencyResult.getId() + ","; + + for (int j = 0; j < stateResidencyResult.getStateResidencyDataCount(); j++) { + StateResidencyProto stateResidency = stateResidencyResult.getStateResidencyData(j); + csvRow += stateResidency.getId() + "," + + stateResidency.getTotalTimeInStateMs() + "," + + stateResidency.getTotalStateEntryCount() + "," + + stateResidency.getLastEntryTimestampMs() + ","; + } + System.out.println(csvRow); + } + } + + private static void generateCsvFile(String pathToIncidentReport) { + try { + // Print power meter data. + IncidentReportMeterProto irMeterProto = + IncidentReportMeterProto.parseFrom(new FileInputStream(pathToIncidentReport)); + + if (irMeterProto.hasIncidentReport()) { + PowerStatsServiceMeterProto pssMeterProto = irMeterProto.getIncidentReport(); + printEnergyMeterInfo(pssMeterProto); + printEnergyMeasurements(pssMeterProto); + } else { + System.out.println("Meter incident report not found. Exiting."); + } + + // Print power model data. + IncidentReportModelProto irModelProto = + IncidentReportModelProto.parseFrom(new FileInputStream(pathToIncidentReport)); + + if (irModelProto.hasIncidentReport()) { + PowerStatsServiceModelProto pssModelProto = irModelProto.getIncidentReport(); + printEnergyConsumer(pssModelProto); + printEnergyConsumerResults(pssModelProto); + } else { + System.out.println("Model incident report not found. Exiting."); + } + + // Print state residency data. + IncidentReportResidencyProto irResidencyProto = + IncidentReportResidencyProto.parseFrom( + new FileInputStream(pathToIncidentReport)); + + if (irResidencyProto.hasIncidentReport()) { + PowerStatsServiceResidencyProto pssResidencyProto = + irResidencyProto.getIncidentReport(); + printPowerEntityInfo(pssResidencyProto); + printStateResidencyResult(pssResidencyProto); + } else { + System.out.println("Residency incident report not found. Exiting."); + } + + } catch (IOException e) { + System.out.println("Unable to open incident report file: " + pathToIncidentReport); + System.out.println(e); + } + } + + /** + * This is the entry point to parse the ODPM data out of incident reports. + * It requires one argument which is the path to the incident_report.proto + * file captured in a bugreport. + * + * @param args Path to incident_report.proto passed in from command line. + */ + public static void main(String[] args) { + if (args.length > 0) { + generateCsvFile(args[0]); + } else { + System.err.println("Usage: PowerStatsServiceProtoParser <incident_report.proto>"); + System.err.println("Missing path to incident_report.proto. Exiting."); + System.exit(1); + } + } +} diff --git a/tools/powerstats/PowerStatsServiceProtoParser_manifest.txt b/tools/powerstats/PowerStatsServiceProtoParser_manifest.txt new file mode 100644 index 000000000000..5df12118ce80 --- /dev/null +++ b/tools/powerstats/PowerStatsServiceProtoParser_manifest.txt @@ -0,0 +1 @@ +Main-class: com.android.server.powerstats.PowerStatsServiceProtoParser diff --git a/tools/processors/intdef_mappings/Android.bp b/tools/processors/intdef_mappings/Android.bp new file mode 100644 index 000000000000..82a5dac21160 --- /dev/null +++ b/tools/processors/intdef_mappings/Android.bp @@ -0,0 +1,42 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +java_plugin { + name: "intdef-annotation-processor", + + processor_class: "android.processor.IntDefProcessor", + + srcs: [ + ":framework-annotations", + "src/**/*.java", + "src/**/*.kt" + ], + + use_tools_jar: true, +} + +java_test_host { + name: "intdef-annotation-processor-test", + + srcs: [ + "test/**/*.java", + "test/**/*.kt" + ], + java_resource_dirs: ["test/resources"], + + static_libs: [ + "compile-testing-prebuilt", + "truth-prebuilt", + "junit", + "guava", + "intdef-annotation-processor" + ], + + test_suites: ["general-tests"], +} diff --git a/tools/processors/intdef_mappings/src/android/processor/IntDefProcessor.kt b/tools/processors/intdef_mappings/src/android/processor/IntDefProcessor.kt new file mode 100644 index 000000000000..4c1fa6ec40b3 --- /dev/null +++ b/tools/processors/intdef_mappings/src/android/processor/IntDefProcessor.kt @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.processor + +import android.annotation.IntDef +import com.sun.source.tree.IdentifierTree +import com.sun.source.tree.MemberSelectTree +import com.sun.source.tree.NewArrayTree +import com.sun.source.util.SimpleTreeVisitor +import com.sun.source.util.Trees +import java.io.IOException +import java.io.Writer +import javax.annotation.processing.AbstractProcessor +import javax.annotation.processing.RoundEnvironment +import javax.lang.model.SourceVersion +import javax.lang.model.element.AnnotationValue +import javax.lang.model.element.TypeElement +import javax.tools.Diagnostic.Kind +import javax.tools.StandardLocation.CLASS_OUTPUT +import kotlin.collections.set + +/** + * The IntDefProcessor is intended to generate a mapping from ints to their respective string + * identifier for each IntDef for use by Winscope or any other tool which requires such a mapping. + * + * The processor will run when building :framework-minus-apex-intdefs and dump all the IntDef + * mappings found in the files that make up the build target as json to outputPath. + */ +class IntDefProcessor : AbstractProcessor() { + private val outputName = "intDefMapping.json" + + override fun getSupportedSourceVersion(): SourceVersion = SourceVersion.latest() + + // Define what the annotation we care about are for compiler optimization + override fun getSupportedAnnotationTypes() = LinkedHashSet<String>().apply { + add(IntDef::class.java.name) + } + + override fun process(annotations: Set<TypeElement>, roundEnv: RoundEnvironment): Boolean { + // There should only be one matching annotation definition for intDef + val annotationType = annotations.firstOrNull() ?: return false + val annotatedElements = roundEnv.getElementsAnnotatedWith(annotationType) + + val annotationTypeToIntDefMapping = annotatedElements.associate { annotatedElement -> + val type = (annotatedElement as TypeElement).qualifiedName.toString() + val mapping = generateIntDefMapping(annotatedElement, annotationType) + val intDef = annotatedElement.getAnnotation(IntDef::class.java) + type to IntDefMapping(mapping, intDef.flag) + } + + try { + outputToFile(annotationTypeToIntDefMapping) + } catch (e: IOException) { + error("Failed to write IntDef mappings :: $e") + } + return false + } + + private fun generateIntDefMapping( + annotatedElement: TypeElement, + annotationType: TypeElement + ): Map<Int, String> { + // LinkedHashMap makes sure ordering is the same as in the code + val mapping = LinkedHashMap<Int, String>() + + val annotationMirror = annotatedElement.annotationMirrors + // Should only ever be one matching this condition + .first { it.annotationType.asElement() == annotationType } + + val value = annotationMirror.elementValues.entries + .first { entry -> entry.key.simpleName.contentEquals("value") } + .value + + val trees = Trees.instance(processingEnv) + val tree = trees.getTree(annotatedElement, annotationMirror, value) + + val identifiers = ArrayList<String>() + tree.accept(IdentifierVisitor(), identifiers) + + val values = value.value as List<AnnotationValue> + + for (i in identifiers.indices) { + mapping[values[i].value as Int] = identifiers[i] + } + + return mapping + } + + private class IdentifierVisitor : SimpleTreeVisitor<Void, ArrayList<String>>() { + override fun visitNewArray(node: NewArrayTree, indentifiers: ArrayList<String>): Void? { + for (initializer in node.initializers) { + initializer.accept(this, indentifiers) + } + + return null + } + + override fun visitMemberSelect(node: MemberSelectTree, indentifiers: ArrayList<String>): + Void? { + indentifiers.add(node.identifier.toString()) + + return null + } + + override fun visitIdentifier(node: IdentifierTree, indentifiers: ArrayList<String>): Void? { + indentifiers.add(node.name.toString()) + + return null + } + } + + @Throws(IOException::class) + private fun outputToFile(annotationTypeToIntDefMapping: Map<String, IntDefMapping>) { + val resource = processingEnv.filer.createResource( + CLASS_OUTPUT, "com.android.winscope", outputName) + val writer = resource.openWriter() + serializeTo(annotationTypeToIntDefMapping, writer) + writer.close() + } + + private fun error(message: String) { + processingEnv.messager.printMessage(Kind.ERROR, message) + } + + private fun note(message: String) { + processingEnv.messager.printMessage(Kind.NOTE, message) + } + + class IntDefMapping(val mapping: Map<Int, String>, val flag: Boolean) { + val size + get() = this.mapping.size + + val entries + get() = this.mapping.entries + } + + companion object { + fun serializeTo( + annotationTypeToIntDefMapping: Map<String, IntDefMapping>, + writer: Writer + ) { + val indent = " " + + writer.appendln("{") + + val intDefTypesCount = annotationTypeToIntDefMapping.size + var currentIntDefTypesCount = 0 + for ((field, intDefMapping) in annotationTypeToIntDefMapping) { + writer.appendln("""$indent"$field": {""") + + // Start IntDef + + writer.appendln("""$indent$indent"flag": ${intDefMapping.flag},""") + + writer.appendln("""$indent$indent"values": {""") + intDefMapping.entries.joinTo(writer, separator = ",\n") { (value, identifier) -> + """$indent$indent$indent"$value": "$identifier"""" + } + writer.appendln() + writer.appendln("$indent$indent}") + + // End IntDef + + writer.append("$indent}") + if (++currentIntDefTypesCount < intDefTypesCount) { + writer.appendln(",") + } else { + writer.appendln("") + } + } + + writer.appendln("}") + } + } +} diff --git a/tools/processors/intdef_mappings/test/android/processor/IntDefProcessorTest.kt b/tools/processors/intdef_mappings/test/android/processor/IntDefProcessorTest.kt new file mode 100644 index 000000000000..c0c159c98aac --- /dev/null +++ b/tools/processors/intdef_mappings/test/android/processor/IntDefProcessorTest.kt @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.processor + +import android.processor.IntDefProcessor.IntDefMapping +import com.google.common.collect.ObjectArrays.concat +import com.google.testing.compile.CompilationSubject.assertThat +import com.google.testing.compile.Compiler.javac +import com.google.testing.compile.JavaFileObjects +import junit.framework.Assert.assertEquals +import org.junit.Test +import java.io.StringWriter +import javax.tools.JavaFileObject +import javax.tools.StandardLocation.CLASS_OUTPUT + +/** + * Tests for [IntDefProcessor] + */ +class IntDefProcessorTest { + private val mAnnotations = arrayOf<JavaFileObject>( + JavaFileObjects.forSourceLines("android.annotation.IntDef", + "package android.annotation;", + "import java.lang.annotation.Retention;", + "import java.lang.annotation.Target;", + "import static java.lang.annotation.ElementType.ANNOTATION_TYPE;", + "import static java.lang.annotation.RetentionPolicy.SOURCE;", + "@Retention(SOURCE)", + "@Target({ANNOTATION_TYPE})", + "public @interface IntDef {", + " String[] prefix() default {};", + " String[] suffix() default {};", + " int[] value() default {};", + " boolean flag() default false;", + "}") + ) + + @Test + public fun annotationProcessorGeneratesMapping() { + val sources: Array<JavaFileObject> = arrayOf( + JavaFileObjects.forSourceLines( + "com.android.server.accessibility.magnification.MagnificationGestureMatcher", + "package com.android.server.accessibility.magnification;", + "import android.annotation.IntDef;", + "import java.lang.annotation.Retention;", + "import java.lang.annotation.RetentionPolicy;", + "class MagnificationGestureMatcher {", + " private static final int GESTURE_BASE = 100;", + " public static final int GESTURE_TWO_FINGER_DOWN = GESTURE_BASE + 1;", + " public static final int GESTURE_SWIPE = GESTURE_BASE + 2;", + " @IntDef(prefix = {\"GESTURE_MAGNIFICATION_\"}, value = {", + " GESTURE_TWO_FINGER_DOWN,", + " GESTURE_SWIPE", + " })", + " @Retention(RetentionPolicy.SOURCE)", + " @interface GestureId {}", + "}" + ), + JavaFileObjects.forSourceLines( + "android.service.storage.ExternalStorageService", + "package android.service.storage;", + "import android.annotation.IntDef;", + "import java.lang.annotation.Retention;", + "import java.lang.annotation.RetentionPolicy;", + "class MagnificationGestureMatcher {", + " public static final int FLAG_SESSION_TYPE_FUSE = 1 << 0;", + " public static final int FLAG_SESSION_ATTRIBUTE_INDEXABLE = 1 << 1;", + " @IntDef(flag = true, prefix = {\"FLAG_SESSION_\"},", + " value = {FLAG_SESSION_TYPE_FUSE, FLAG_SESSION_ATTRIBUTE_INDEXABLE})", + " @Retention(RetentionPolicy.SOURCE)", + " public @interface SessionFlag {}", + "}" + ) + ) + + val expectedFile = """ + { + "com.android.server.accessibility.magnification.MagnificationGestureMatcher.GestureId": { + "flag": false, + "values": { + "101": "GESTURE_TWO_FINGER_DOWN", + "102": "GESTURE_SWIPE" + } + }, + "android.service.storage.MagnificationGestureMatcher.SessionFlag": { + "flag": true, + "values": { + "1": "FLAG_SESSION_TYPE_FUSE", + "2": "FLAG_SESSION_ATTRIBUTE_INDEXABLE" + } + } + } + + """.trimIndent() + + val filesToCompile = concat(mAnnotations, sources, JavaFileObject::class.java) + + val compilation = javac() + .withProcessors(IntDefProcessor()) + .compile(filesToCompile.toMutableList()) + + assertThat(compilation).succeeded() + assertThat(compilation).generatedFile(CLASS_OUTPUT, "com.android.winscope", + "intDefMapping.json").contentsAsUtf8String().isEqualTo(expectedFile) + } + + @Test + public fun serializesMappingCorrectly() { + val map = linkedMapOf( + "SimpleIntDef" to IntDefMapping(linkedMapOf( + 0x0001 to "VAL_1", + 0x0002 to "VAL_2", + 0x0003 to "VAL_3" + ), flag = false), + "Flags" to IntDefMapping(linkedMapOf( + 0b0001 to "PRIVATE_FLAG_1", + 0b0010 to "PRIVATE_FLAG_2", + 0b0100 to "PRIVATE_FLAG_3" + ), flag = true) + ) + + val writer = StringWriter() + IntDefProcessor.serializeTo(map, writer) + + val actualOutput = writer.toString() + val expectedOutput = """ + { + "SimpleIntDef": { + "flag": false, + "values": { + "1": "VAL_1", + "2": "VAL_2", + "3": "VAL_3" + } + }, + "Flags": { + "flag": true, + "values": { + "1": "PRIVATE_FLAG_1", + "2": "PRIVATE_FLAG_2", + "4": "PRIVATE_FLAG_3" + } + } + } + + """.trimIndent() + + assertEquals(actualOutput, expectedOutput) + } +}
\ No newline at end of file diff --git a/tools/processors/staledataclass/src/android/processor/staledataclass/StaleDataclassProcessor.kt b/tools/processors/staledataclass/src/android/processor/staledataclass/StaleDataclassProcessor.kt index 51faa49a86cc..1aec9b812e61 100644 --- a/tools/processors/staledataclass/src/android/processor/staledataclass/StaleDataclassProcessor.kt +++ b/tools/processors/staledataclass/src/android/processor/staledataclass/StaleDataclassProcessor.kt @@ -98,8 +98,10 @@ class StaleDataclassProcessor: AbstractProcessor() { private fun elemToString(elem: Element): String { return buildString { - append(elem.modifiers.joinToString(" ") { it.name.toLowerCase() }).append(" ") - append(elem.annotationMirrors.joinToString(" ")).append(" ") + append(elem.modifiers.joinToString(" ") { it.name.toLowerCase() }) + append(" ") + append(elem.annotationMirrors.joinToString(" ", transform = { annotationToString(it) })) + append(" ") if (elem is Symbol) { if (elem.type is Type.MethodType) { append((elem.type as Type.MethodType).returnType) @@ -112,6 +114,14 @@ class StaleDataclassProcessor: AbstractProcessor() { } } + private fun annotationToString(ann: AnnotationMirror): String { + return if (ann.annotationType.toString().startsWith("com.android.internal.util.DataClass")) { + ann.toString() + } else { + ann.toString().substringBefore("(") + } + } + private fun processSingleFile(elementAnnotatedWithGenerated: Element) { val classElement = elementAnnotatedWithGenerated.enclosingElement diff --git a/tools/protologtool/Android.bp b/tools/protologtool/Android.bp index ea3cd427a130..039bb4e2788c 100644 --- a/tools/protologtool/Android.bp +++ b/tools/protologtool/Android.bp @@ -11,9 +11,9 @@ java_library_host { name: "protologtool-lib", srcs: [ "src/com/android/protolog/tool/**/*.kt", + ":protolog-common-src", ], static_libs: [ - "protolog-common", "javaparser", "platformprotos", "jsonlib", diff --git a/tools/protologtool/src/com/android/protolog/tool/LogParser.kt b/tools/protologtool/src/com/android/protolog/tool/LogParser.kt index a59038fc99a0..645c5672da64 100644 --- a/tools/protologtool/src/com/android/protolog/tool/LogParser.kt +++ b/tools/protologtool/src/com/android/protolog/tool/LogParser.kt @@ -16,16 +16,15 @@ package com.android.protolog.tool +import com.android.internal.protolog.ProtoLogFileProto +import com.android.internal.protolog.ProtoLogMessage +import com.android.internal.protolog.common.InvalidFormatStringException +import com.android.internal.protolog.common.LogDataType import com.android.json.stream.JsonReader -import com.android.server.protolog.common.InvalidFormatStringException -import com.android.server.protolog.common.LogDataType -import com.android.server.protolog.ProtoLogMessage -import com.android.server.protolog.ProtoLogFileProto import java.io.BufferedReader import java.io.InputStream import java.io.InputStreamReader import java.io.PrintStream -import java.lang.Exception import java.text.SimpleDateFormat import java.util.Date import java.util.Locale diff --git a/tools/protologtool/src/com/android/protolog/tool/ProtoLogGroupReader.kt b/tools/protologtool/src/com/android/protolog/tool/ProtoLogGroupReader.kt index 75493b6427cb..42b628b0e262 100644 --- a/tools/protologtool/src/com/android/protolog/tool/ProtoLogGroupReader.kt +++ b/tools/protologtool/src/com/android/protolog/tool/ProtoLogGroupReader.kt @@ -17,7 +17,7 @@ package com.android.protolog.tool import com.android.protolog.tool.Constants.ENUM_VALUES_METHOD -import com.android.server.protolog.common.IProtoLogGroup +import com.android.internal.protolog.common.IProtoLogGroup import java.io.File import java.net.URLClassLoader diff --git a/tools/protologtool/src/com/android/protolog/tool/SourceTransformer.kt b/tools/protologtool/src/com/android/protolog/tool/SourceTransformer.kt index 36ea41129450..27e61a139451 100644 --- a/tools/protologtool/src/com/android/protolog/tool/SourceTransformer.kt +++ b/tools/protologtool/src/com/android/protolog/tool/SourceTransformer.kt @@ -16,7 +16,7 @@ package com.android.protolog.tool -import com.android.server.protolog.common.LogDataType +import com.android.internal.protolog.common.LogDataType import com.github.javaparser.StaticJavaParser import com.github.javaparser.ast.CompilationUnit import com.github.javaparser.ast.NodeList @@ -89,7 +89,7 @@ class SourceTransformer( // Out: ProtoLog.e(GROUP, 1234, 0, null, arg) newCall.arguments.add(2, IntegerLiteralExpr(typeMask)) // Replace call to a stub method with an actual implementation. - // Out: com.android.server.protolog.ProtoLogImpl.e(GROUP, 1234, null, arg) + // Out: ProtoLogImpl.e(GROUP, 1234, null, arg) newCall.setScope(protoLogImplClassNode) // Create a call to ProtoLog$Cache.GROUP_enabled // Out: com.android.server.protolog.ProtoLog$Cache.GROUP_enabled @@ -119,9 +119,9 @@ class SourceTransformer( } blockStmt.addStatement(ExpressionStmt(newCall)) // Create an IF-statement with the previously created condition. - // Out: if (com.android.server.protolog.ProtoLogImpl.isEnabled(GROUP)) { + // Out: if (ProtoLogImpl.isEnabled(GROUP)) { // long protoLogParam0 = arg; - // com.android.server.protolog.ProtoLogImpl.e(GROUP, 1234, 0, null, protoLogParam0); + // ProtoLogImpl.e(GROUP, 1234, 0, null, protoLogParam0); // } ifStmt = IfStmt(isLogEnabled, blockStmt, null) } else { diff --git a/tools/protologtool/tests/com/android/protolog/tool/CommandOptionsTest.kt b/tools/protologtool/tests/com/android/protolog/tool/CommandOptionsTest.kt index cf36651c3e39..3cfbb435a764 100644 --- a/tools/protologtool/tests/com/android/protolog/tool/CommandOptionsTest.kt +++ b/tools/protologtool/tests/com/android/protolog/tool/CommandOptionsTest.kt @@ -31,7 +31,7 @@ class CommandOptionsTest { private const val TEST_PROTOLOG_CLASS = "com.android.server.wm.ProtoLog" private const val TEST_PROTOLOGIMPL_CLASS = "com.android.server.wm.ProtoLogImpl" private const val TEST_PROTOLOGCACHE_CLASS = "com.android.server.wm.ProtoLog\$Cache" - private const val TEST_PROTOLOGGROUP_CLASS = "com.android.server.wm.ProtoLogGroup" + private const val TEST_PROTOLOGGROUP_CLASS = "com.android.internal.protolog.ProtoLogGroup" private const val TEST_PROTOLOGGROUP_JAR = "out/soong/.intermediates/frameworks/base/" + "services/core/services.core.wm.protologgroups/android_common/javac/" + "services.core.wm.protologgroups.jar" diff --git a/tools/protologtool/tests/com/android/protolog/tool/EndToEndTest.kt b/tools/protologtool/tests/com/android/protolog/tool/EndToEndTest.kt index dd8a0b1c50b4..0d2b91d6cfb8 100644 --- a/tools/protologtool/tests/com/android/protolog/tool/EndToEndTest.kt +++ b/tools/protologtool/tests/com/android/protolog/tool/EndToEndTest.kt @@ -33,8 +33,8 @@ class EndToEndTest { val output = run( src = "frameworks/base/org/example/Example.java" to """ package org.example; - import com.android.server.protolog.common.ProtoLog; - import static com.android.server.wm.ProtoLogGroup.GROUP; + import com.android.internal.protolog.common.ProtoLog; + import static com.android.internal.protolog.ProtoLogGroup.GROUP; class Example { void method() { @@ -46,11 +46,11 @@ class EndToEndTest { """.trimIndent(), logGroup = LogGroup("GROUP", true, false, "TAG_GROUP"), commandOptions = CommandOptions(arrayOf("transform-protolog-calls", - "--protolog-class", "com.android.server.protolog.common.ProtoLog", - "--protolog-impl-class", "com.android.server.protolog.ProtoLogImpl", + "--protolog-class", "com.android.internal.protolog.common.ProtoLog", + "--protolog-impl-class", "com.android.internal.protolog.ProtoLogImpl", "--protolog-cache-class", - "com.android.server.protolog.ProtoLog${"\$\$"}Cache", - "--loggroups-class", "com.android.server.wm.ProtoLogGroup", + "com.android.server.wm.ProtoLogCache", + "--loggroups-class", "com.android.internal.protolog.ProtoLogGroup", "--loggroups-jar", "not_required.jar", "--output-srcjar", "out.srcjar", "frameworks/base/org/example/Example.java")) @@ -64,8 +64,8 @@ class EndToEndTest { val output = run( src = "frameworks/base/org/example/Example.java" to """ package org.example; - import com.android.server.protolog.common.ProtoLog; - import static com.android.server.wm.ProtoLogGroup.GROUP; + import com.android.internal.protolog.common.ProtoLog; + import static com.android.internal.protolog.ProtoLogGroup.GROUP; class Example { void method() { @@ -77,8 +77,8 @@ class EndToEndTest { """.trimIndent(), logGroup = LogGroup("GROUP", true, false, "TAG_GROUP"), commandOptions = CommandOptions(arrayOf("generate-viewer-config", - "--protolog-class", "com.android.server.protolog.common.ProtoLog", - "--loggroups-class", "com.android.server.wm.ProtoLogGroup", + "--protolog-class", "com.android.internal.protolog.common.ProtoLog", + "--loggroups-class", "com.android.internal.protolog.ProtoLogGroup", "--loggroups-jar", "not_required.jar", "--viewer-conf", "out.json", "frameworks/base/org/example/Example.java")) diff --git a/tools/protologtool/tests/com/android/protolog/tool/LogParserTest.kt b/tools/protologtool/tests/com/android/protolog/tool/LogParserTest.kt index 04a3bfa499d8..67a31da87081 100644 --- a/tools/protologtool/tests/com/android/protolog/tool/LogParserTest.kt +++ b/tools/protologtool/tests/com/android/protolog/tool/LogParserTest.kt @@ -17,8 +17,8 @@ package com.android.protolog.tool import com.android.json.stream.JsonReader -import com.android.server.protolog.ProtoLogMessage -import com.android.server.protolog.ProtoLogFileProto +import com.android.internal.protolog.ProtoLogMessage +import com.android.internal.protolog.ProtoLogFileProto import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Test diff --git a/tools/validatekeymaps/Android.bp b/tools/validatekeymaps/Android.bp index 9fcf034536fc..0423b7abd685 100644 --- a/tools/validatekeymaps/Android.bp +++ b/tools/validatekeymaps/Android.bp @@ -29,12 +29,21 @@ cc_binary_host { "libutils", "libcutils", "liblog", + "libui-types", ], + target: { + linux_glibc: { + static_libs: [ + // libbinder is only available for linux + "libbinder", + ], + }, + }, // This tool is prebuilt if we're doing an app-only build. product_variables: { unbundled_build: { - enabled: false, + enabled: false, }, }, } diff --git a/tools/validatekeymaps/Main.cpp b/tools/validatekeymaps/Main.cpp index 877715a66f6d..991b28071515 100644 --- a/tools/validatekeymaps/Main.cpp +++ b/tools/validatekeymaps/Main.cpp @@ -26,15 +26,15 @@ using namespace android; -static const char* kProgName = "validatekeymaps"; +static const char* PROG_NAME = "validatekeymaps"; static bool gQuiet = false; -enum FileType { - FILETYPE_UNKNOWN, - FILETYPE_KEYLAYOUT, - FILETYPE_KEYCHARACTERMAP, - FILETYPE_VIRTUALKEYDEFINITION, - FILETYPE_INPUTDEVICECONFIGURATION, +enum class FileType { + UNKNOWN, + KEY_LAYOUT, + KEY_CHARACTER_MAP, + VIRTUAL_KEY_DEFINITION, + INPUT_DEVICE_CONFIGURATION, }; static void log(const char* fmt, ...) { @@ -57,33 +57,32 @@ static void error(const char* fmt, ...) { static void usage() { error("Keymap Validation Tool\n\n"); error("Usage:\n"); - error( - " %s [-q] [*.kl] [*.kcm] [*.idc] [virtualkeys.*] [...]\n" - " Validates the specified key layouts, key character maps, \n" - " input device configurations, or virtual key definitions.\n\n" - " -q Quiet; do not write anything to standard out.\n", - kProgName); + error(" %s [-q] [*.kl] [*.kcm] [*.idc] [virtualkeys.*] [...]\n" + " Validates the specified key layouts, key character maps, \n" + " input device configurations, or virtual key definitions.\n\n" + " -q Quiet; do not write anything to standard out.\n", + PROG_NAME); } static FileType getFileType(const char* filename) { const char *extension = strrchr(filename, '.'); if (extension) { if (strcmp(extension, ".kl") == 0) { - return FILETYPE_KEYLAYOUT; + return FileType::KEY_LAYOUT; } if (strcmp(extension, ".kcm") == 0) { - return FILETYPE_KEYCHARACTERMAP; + return FileType::KEY_CHARACTER_MAP; } if (strcmp(extension, ".idc") == 0) { - return FILETYPE_INPUTDEVICECONFIGURATION; + return FileType::INPUT_DEVICE_CONFIGURATION; } } if (strstr(filename, "virtualkeys.")) { - return FILETYPE_VIRTUALKEYDEFINITION; + return FileType::VIRTUAL_KEY_DEFINITION; } - return FILETYPE_UNKNOWN; + return FileType::UNKNOWN; } static bool validateFile(const char* filename) { @@ -91,50 +90,49 @@ static bool validateFile(const char* filename) { FileType fileType = getFileType(filename); switch (fileType) { - case FILETYPE_UNKNOWN: - error("Supported file types: *.kl, *.kcm, virtualkeys.*\n\n"); - return false; - - case FILETYPE_KEYLAYOUT: { - sp<KeyLayoutMap> map; - status_t status = KeyLayoutMap::load(filename, &map); - if (status) { - error("Error %d parsing key layout file.\n\n", status); + case FileType::UNKNOWN: + error("Supported file types: *.kl, *.kcm, virtualkeys.*\n\n"); return false; + + case FileType::KEY_LAYOUT: { + base::Result<std::shared_ptr<KeyLayoutMap>> ret = KeyLayoutMap::load(filename); + if (!ret.ok()) { + error("Error %s parsing key layout file.\n\n", ret.error().message().c_str()); + return false; + } + break; } - break; - } - case FILETYPE_KEYCHARACTERMAP: { - sp<KeyCharacterMap> map; - status_t status = KeyCharacterMap::load(filename, - KeyCharacterMap::FORMAT_ANY, &map); - if (status) { - error("Error %d parsing key character map file.\n\n", status); - return false; + case FileType::KEY_CHARACTER_MAP: { + base::Result<std::shared_ptr<KeyCharacterMap>> ret = + KeyCharacterMap::load(filename, KeyCharacterMap::Format::ANY); + if (!ret.ok()) { + error("Error %s parsing key character map file.\n\n", + ret.error().message().c_str()); + return false; + } + break; } - break; - } - case FILETYPE_INPUTDEVICECONFIGURATION: { - PropertyMap* map; - status_t status = PropertyMap::load(String8(filename), &map); - if (status) { - error("Error %d parsing input device configuration file.\n\n", status); - return false; + case FileType::INPUT_DEVICE_CONFIGURATION: { + android::base::Result<std::unique_ptr<PropertyMap>> propertyMap = + PropertyMap::load(String8(filename)); + if (!propertyMap.ok()) { + error("Error %d parsing input device configuration file.\n\n", + propertyMap.error().code()); + return false; + } + break; } - delete map; - break; - } - case FILETYPE_VIRTUALKEYDEFINITION: { - std::unique_ptr<VirtualKeyMap> map = VirtualKeyMap::load(filename); - if (!map) { - error("Error while parsing virtual key definition file.\n\n"); - return false; + case FileType::VIRTUAL_KEY_DEFINITION: { + std::unique_ptr<VirtualKeyMap> map = VirtualKeyMap::load(filename); + if (!map) { + error("Error while parsing virtual key definition file.\n\n"); + return false; + } + break; } - break; - } } return true; diff --git a/tools/xmlpersistence/Android.bp b/tools/xmlpersistence/Android.bp new file mode 100644 index 000000000000..0b6dba626794 --- /dev/null +++ b/tools/xmlpersistence/Android.bp @@ -0,0 +1,20 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +java_binary_host { + name: "xmlpersistence_cli", + manifest: "manifest.txt", + srcs: [ + "src/**/*.kt", + ], + static_libs: [ + "javaparser-symbol-solver", + "javapoet", + ], +} diff --git a/tools/xmlpersistence/OWNERS b/tools/xmlpersistence/OWNERS new file mode 100644 index 000000000000..4f4d06a32676 --- /dev/null +++ b/tools/xmlpersistence/OWNERS @@ -0,0 +1 @@ +zhanghai@google.com diff --git a/tools/xmlpersistence/manifest.txt b/tools/xmlpersistence/manifest.txt new file mode 100644 index 000000000000..6d9771998efc --- /dev/null +++ b/tools/xmlpersistence/manifest.txt @@ -0,0 +1 @@ +Main-class: MainKt diff --git a/tools/xmlpersistence/src/main/kotlin/Generator.kt b/tools/xmlpersistence/src/main/kotlin/Generator.kt new file mode 100644 index 000000000000..b2c5f4ac767b --- /dev/null +++ b/tools/xmlpersistence/src/main/kotlin/Generator.kt @@ -0,0 +1,576 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import com.squareup.javapoet.ClassName +import com.squareup.javapoet.FieldSpec +import com.squareup.javapoet.JavaFile +import com.squareup.javapoet.MethodSpec +import com.squareup.javapoet.NameAllocator +import com.squareup.javapoet.ParameterSpec +import com.squareup.javapoet.TypeSpec +import java.io.File +import java.io.FileInputStream +import java.io.FileNotFoundException +import java.io.FileOutputStream +import java.io.IOException +import java.nio.charset.StandardCharsets +import java.time.Year +import java.util.Objects +import javax.lang.model.element.Modifier + +// JavaPoet only supports line comments, and can't add a newline after file level comments. +val FILE_HEADER = """ + /* + * Copyright (C) ${Year.now().value} The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + // Generated by xmlpersistence. DO NOT MODIFY! + // CHECKSTYLE:OFF Generated code + // @formatter:off +""".trimIndent() + "\n\n" + +private val atomicFileType = ClassName.get("android.util", "AtomicFile") + +fun generate(persistence: PersistenceInfo): JavaFile { + val distinctClassFields = persistence.root.allClassFields.distinctBy { it.type } + val type = TypeSpec.classBuilder(persistence.name) + .addJavadoc( + """ + Generated class implementing XML persistence for${'$'}W{@link $1T}. + <p> + This class provides atomicity for persistence via {@link $2T}, however it does not provide + thread safety, so please bring your own synchronization mechanism. + """.trimIndent(), persistence.root.type, atomicFileType + ) + .addModifiers(Modifier.PUBLIC, Modifier.FINAL) + .addField(generateFileField()) + .addMethod(generateConstructor()) + .addMethod(generateReadMethod(persistence.root)) + .addMethod(generateParseMethod(persistence.root)) + .addMethods(distinctClassFields.map { generateParseClassMethod(it) }) + .addMethod(generateWriteMethod(persistence.root)) + .addMethod(generateSerializeMethod(persistence.root)) + .addMethods(distinctClassFields.map { generateSerializeClassMethod(it) }) + .addMethod(generateDeleteMethod()) + .build() + return JavaFile.builder(persistence.root.type.packageName(), type) + .skipJavaLangImports(true) + .indent(" ") + .build() +} + +private val nonNullType = ClassName.get("android.annotation", "NonNull") + +private fun generateFileField(): FieldSpec = + FieldSpec.builder(atomicFileType, "mFile", Modifier.PRIVATE, Modifier.FINAL) + .addAnnotation(nonNullType) + .build() + +private fun generateConstructor(): MethodSpec = + MethodSpec.constructorBuilder() + .addJavadoc( + """ + Create an instance of this class. + + @param file the XML file for persistence + """.trimIndent() + ) + .addModifiers(Modifier.PUBLIC) + .addParameter( + ParameterSpec.builder(File::class.java, "file").addAnnotation(nonNullType).build() + ) + .addStatement("mFile = new \$1T(file)", atomicFileType) + .build() + +private val nullableType = ClassName.get("android.annotation", "Nullable") + +private val xmlPullParserType = ClassName.get("org.xmlpull.v1", "XmlPullParser") + +private val xmlType = ClassName.get("android.util", "Xml") + +private val xmlPullParserExceptionType = ClassName.get("org.xmlpull.v1", "XmlPullParserException") + +private fun generateReadMethod(rootField: ClassFieldInfo): MethodSpec = + MethodSpec.methodBuilder("read") + .addJavadoc( + """ + Read${'$'}W{@link $1T}${'$'}Wfrom${'$'}Wthe${'$'}WXML${'$'}Wfile. + + @return the persisted${'$'}W{@link $1T},${'$'}Wor${'$'}W{@code null}${'$'}Wif${'$'}Wthe${'$'}WXML${'$'}Wfile${'$'}Wdoesn't${'$'}Wexist + @throws IllegalArgumentException if an error occurred while reading + """.trimIndent(), rootField.type + ) + .addAnnotation(nullableType) + .addModifiers(Modifier.PUBLIC) + .returns(rootField.type) + .addControlFlow("try (\$1T inputStream = mFile.openRead())", FileInputStream::class.java) { + addStatement("final \$1T parser = \$2T.newPullParser()", xmlPullParserType, xmlType) + addStatement("parser.setInput(inputStream, null)") + addStatement("return parse(parser)") + nextControlFlow("catch (\$1T e)", FileNotFoundException::class.java) + addStatement("return null") + nextControlFlow( + "catch (\$1T | \$2T e)", IOException::class.java, xmlPullParserExceptionType + ) + addStatement("throw new IllegalArgumentException(e)") + } + .build() + +private val ClassFieldInfo.allClassFields: List<ClassFieldInfo> + get() = + mutableListOf<ClassFieldInfo>().apply { + this += this@allClassFields + for (field in fields) { + when (field) { + is ClassFieldInfo -> this += field.allClassFields + is ListFieldInfo -> this += field.element.allClassFields + } + } + } + +private fun generateParseMethod(rootField: ClassFieldInfo): MethodSpec = + MethodSpec.methodBuilder("parse") + .addAnnotation(nonNullType) + .addModifiers(Modifier.PRIVATE, Modifier.STATIC) + .returns(rootField.type) + .addParameter( + ParameterSpec.builder(xmlPullParserType, "parser").addAnnotation(nonNullType).build() + ) + .addExceptions(listOf(ClassName.get(IOException::class.java), xmlPullParserExceptionType)) + .apply { + addStatement("int type") + addStatement("int depth") + addStatement("int innerDepth = parser.getDepth() + 1") + addControlFlow( + "while ((type = parser.next()) != \$1T.END_DOCUMENT\$W" + + "&& ((depth = parser.getDepth()) >= innerDepth || type != \$1T.END_TAG))", + xmlPullParserType + ) { + addControlFlow( + "if (depth > innerDepth || type != \$1T.START_TAG)", xmlPullParserType + ) { + addStatement("continue") + } + addControlFlow( + "if (\$1T.equals(parser.getName(),\$W\$2S))", Objects::class.java, + rootField.tagName + ) { + addStatement("return \$1L(parser)", rootField.parseMethodName) + } + } + addStatement( + "throw new IllegalArgumentException(\$1S)", + "Missing root tag <${rootField.tagName}>" + ) + } + .build() + +private fun generateParseClassMethod(classField: ClassFieldInfo): MethodSpec = + MethodSpec.methodBuilder(classField.parseMethodName) + .addAnnotation(nonNullType) + .addModifiers(Modifier.PRIVATE, Modifier.STATIC) + .returns(classField.type) + .addParameter( + ParameterSpec.builder(xmlPullParserType, "parser").addAnnotation(nonNullType).build() + ) + .apply { + val (attributeFields, tagFields) = classField.fields + .partition { it is PrimitiveFieldInfo || it is StringFieldInfo } + if (tagFields.isNotEmpty()) { + addExceptions( + listOf(ClassName.get(IOException::class.java), xmlPullParserExceptionType) + ) + } + val nameAllocator = NameAllocator().apply { + newName("parser") + newName("type") + newName("depth") + newName("innerDepth") + } + for (field in attributeFields) { + val variableName = nameAllocator.newName(field.variableName, field) + when (field) { + is PrimitiveFieldInfo -> { + val stringVariableName = + nameAllocator.newName("${field.variableName}String") + addStatement( + "final String \$1L =\$Wparser.getAttributeValue(null,\$W\$2S)", + stringVariableName, field.attributeName + ) + if (field.isRequired) { + addControlFlow("if (\$1L == null)", stringVariableName) { + addStatement( + "throw new IllegalArgumentException(\$1S)", + "Missing attribute \"${field.attributeName}\"" + ) + } + } + val boxedType = field.type.box() + val parseTypeMethodName = if (field.type.isPrimitive) { + "parse${field.type.toString().capitalize()}" + } else { + "valueOf" + } + if (field.isRequired) { + addStatement( + "final \$1T \$2L =\$W\$3T.\$4L($5L)", field.type, variableName, + boxedType, parseTypeMethodName, stringVariableName + ) + } else { + addStatement( + "final \$1T \$2L =\$W$3L != null ?\$W\$4T.\$5L($3L)\$W: null", + field.type, variableName, stringVariableName, boxedType, + parseTypeMethodName + ) + } + } + is StringFieldInfo -> + addStatement( + "final String \$1L =\$Wparser.getAttributeValue(null,\$W\$2S)", + variableName, field.attributeName + ) + else -> error(field) + } + } + if (tagFields.isNotEmpty()) { + for (field in tagFields) { + val variableName = nameAllocator.newName(field.variableName, field) + when (field) { + is ClassFieldInfo -> + addStatement("\$1T \$2L =\$Wnull", field.type, variableName) + is ListFieldInfo -> + addStatement( + "final \$1T \$2L =\$Wnew \$3T<>()", field.type, variableName, + ArrayList::class.java + ) + else -> error(field) + } + } + addStatement("int type") + addStatement("int depth") + addStatement("int innerDepth = parser.getDepth() + 1") + addControlFlow( + "while ((type = parser.next()) != \$1T.END_DOCUMENT\$W" + + "&& ((depth = parser.getDepth()) >= innerDepth || type != \$1T.END_TAG))", + xmlPullParserType + ) { + addControlFlow( + "if (depth > innerDepth || type != \$1T.START_TAG)", xmlPullParserType + ) { + addStatement("continue") + } + addControlFlow("switch (parser.getName())") { + for (field in tagFields) { + addControlFlow("case \$1S:", field.tagName) { + val variableName = nameAllocator.get(field) + when (field) { + is ClassFieldInfo -> { + addControlFlow("if (\$1L != null)", variableName) { + addStatement( + "throw new IllegalArgumentException(\$1S)", + "Duplicate tag \"${field.tagName}\"" + ) + } + addStatement( + "\$1L =\$W\$2L(parser)", variableName, + field.parseMethodName + ) + addStatement("break") + } + is ListFieldInfo -> { + val elementNameAllocator = nameAllocator.clone() + val elementVariableName = elementNameAllocator.newName( + field.element.xmlName!!.toLowerCamelCase() + ) + addStatement( + "final \$1T \$2L =\$W\$3L(parser)", field.element.type, + elementVariableName, field.element.parseMethodName + ) + addStatement( + "\$1L.add(\$2L)", variableName, elementVariableName + ) + addStatement("break") + } + else -> error(field) + } + } + } + } + } + } + for (field in tagFields.filter { it is ClassFieldInfo && it.isRequired }) { + addControlFlow("if ($1L == null)", nameAllocator.get(field)) { + addStatement( + "throw new IllegalArgumentException(\$1S)", "Missing tag <${field.tagName}>" + ) + } + } + addStatement( + classField.fields.joinToString(",\$W", "return new \$1T(", ")") { + nameAllocator.get(it) + }, classField.type + ) + } + .build() + +private val ClassFieldInfo.parseMethodName: String + get() = "parse${type.simpleName().toUpperCamelCase()}" + +private val xmlSerializerType = ClassName.get("org.xmlpull.v1", "XmlSerializer") + +private fun generateWriteMethod(rootField: ClassFieldInfo): MethodSpec = + MethodSpec.methodBuilder("write") + .apply { + val nameAllocator = NameAllocator().apply { + newName("outputStream") + newName("serializer") + } + val parameterName = nameAllocator.newName(rootField.variableName) + addJavadoc( + """ + Write${'$'}W{@link $1T}${'$'}Wto${'$'}Wthe${'$'}WXML${'$'}Wfile. + + @param $2L the${'$'}W{@link ${'$'}1T}${'$'}Wto${'$'}Wpersist + """.trimIndent(), rootField.type, parameterName + ) + addAnnotation(nullableType) + addModifiers(Modifier.PUBLIC) + addParameter( + ParameterSpec.builder(rootField.type, parameterName) + .addAnnotation(nonNullType) + .build() + ) + addStatement("\$1T outputStream = null", FileOutputStream::class.java) + addControlFlow("try") { + addStatement("outputStream = mFile.startWrite()") + addStatement( + "final \$1T serializer =\$W\$2T.newSerializer()", xmlSerializerType, xmlType + ) + addStatement( + "serializer.setOutput(outputStream, \$1T.UTF_8.name())", + StandardCharsets::class.java + ) + addStatement( + "serializer.setFeature(\$1S, true)", + "http://xmlpull.org/v1/doc/features.html#indent-output" + ) + addStatement("serializer.startDocument(null, true)") + addStatement("serialize(serializer,\$W\$1L)", parameterName) + addStatement("serializer.endDocument()") + addStatement("mFile.finishWrite(outputStream)") + nextControlFlow("catch (Exception e)") + addStatement("e.printStackTrace()") + addStatement("mFile.failWrite(outputStream)") + } + } + .build() + +private fun generateSerializeMethod(rootField: ClassFieldInfo): MethodSpec = + MethodSpec.methodBuilder("serialize") + .addModifiers(Modifier.PRIVATE, Modifier.STATIC) + .addParameter( + ParameterSpec.builder(xmlSerializerType, "serializer") + .addAnnotation(nonNullType) + .build() + ) + .apply { + val nameAllocator = NameAllocator().apply { newName("serializer") } + val parameterName = nameAllocator.newName(rootField.variableName) + addParameter( + ParameterSpec.builder(rootField.type, parameterName) + .addAnnotation(nonNullType) + .build() + ) + addException(IOException::class.java) + addStatement("serializer.startTag(null, \$1S)", rootField.tagName) + addStatement("\$1L(serializer, \$2L)", rootField.serializeMethodName, parameterName) + addStatement("serializer.endTag(null, \$1S)", rootField.tagName) + } + .build() + +private fun generateSerializeClassMethod(classField: ClassFieldInfo): MethodSpec = + MethodSpec.methodBuilder(classField.serializeMethodName) + .addModifiers(Modifier.PRIVATE, Modifier.STATIC) + .addParameter( + ParameterSpec.builder(xmlSerializerType, "serializer") + .addAnnotation(nonNullType) + .build() + ) + .apply { + val nameAllocator = NameAllocator().apply { + newName("serializer") + newName("i") + } + val parameterName = nameAllocator.newName(classField.serializeParameterName) + addParameter( + ParameterSpec.builder(classField.type, parameterName) + .addAnnotation(nonNullType) + .build() + ) + addException(IOException::class.java) + val (attributeFields, tagFields) = classField.fields + .partition { it is PrimitiveFieldInfo || it is StringFieldInfo } + for (field in attributeFields) { + val variableName = "$parameterName.${field.name}" + if (!field.isRequired) { + beginControlFlow("if (\$1L != null)", variableName) + } + when (field) { + is PrimitiveFieldInfo -> { + if (field.isRequired && !field.type.isPrimitive) { + addControlFlow("if (\$1L == null)", variableName) { + addStatement( + "throw new IllegalArgumentException(\$1S)", + "Field \"${field.name}\" is null" + ) + } + } + val stringVariableName = + nameAllocator.newName("${field.variableName}String") + addStatement( + "final String \$1L =\$WString.valueOf(\$2L)", stringVariableName, + variableName + ) + addStatement( + "serializer.attribute(null, \$1S, \$2L)", field.attributeName, + stringVariableName + ) + } + is StringFieldInfo -> { + if (field.isRequired) { + addControlFlow("if (\$1L == null)", variableName) { + addStatement( + "throw new IllegalArgumentException(\$1S)", + "Field \"${field.name}\" is null" + ) + } + } + addStatement( + "serializer.attribute(null, \$1S, \$2L)", field.attributeName, + variableName + ) + } + else -> error(field) + } + if (!field.isRequired) { + endControlFlow() + } + } + for (field in tagFields) { + val variableName = "$parameterName.${field.name}" + if (field.isRequired) { + addControlFlow("if (\$1L == null)", variableName) { + addStatement( + "throw new IllegalArgumentException(\$1S)", + "Field \"${field.name}\" is null" + ) + } + } + when (field) { + is ClassFieldInfo -> { + addStatement("serializer.startTag(null, \$1S)", field.tagName) + addStatement( + "\$1L(serializer, \$2L)", field.serializeMethodName, variableName + ) + addStatement("serializer.endTag(null, \$1S)", field.tagName) + } + is ListFieldInfo -> { + val sizeVariableName = nameAllocator.newName("${field.variableName}Size") + addStatement( + "final int \$1L =\$W\$2L.size()", sizeVariableName, variableName + ) + addControlFlow("for (int i = 0;\$Wi < \$1L;\$Wi++)", sizeVariableName) { + val elementNameAllocator = nameAllocator.clone() + val elementVariableName = elementNameAllocator.newName( + field.element.xmlName!!.toLowerCamelCase() + ) + addStatement( + "final \$1T \$2L =\$W\$3L.get(i)", field.element.type, + elementVariableName, variableName + ) + addControlFlow("if (\$1L == null)", elementVariableName) { + addStatement( + "throw new IllegalArgumentException(\$1S\$W+ i\$W+ \$2S)", + "Field element \"${field.name}[", "]\" is null" + ) + } + addStatement("serializer.startTag(null, \$1S)", field.element.tagName) + addStatement( + "\$1L(serializer,\$W\$2L)", field.element.serializeMethodName, + elementVariableName + ) + addStatement("serializer.endTag(null, \$1S)", field.element.tagName) + } + } + else -> error(field) + } + } + } + .build() + +private val ClassFieldInfo.serializeMethodName: String + get() = "serialize${type.simpleName().toUpperCamelCase()}" + +private val ClassFieldInfo.serializeParameterName: String + get() = type.simpleName().toLowerCamelCase() + +private val FieldInfo.variableName: String + get() = name.toLowerCamelCase() + +private val FieldInfo.attributeName: String + get() { + check(this is PrimitiveFieldInfo || this is StringFieldInfo) + return xmlNameOrName.toLowerCamelCase() + } + +private val FieldInfo.tagName: String + get() { + check(this is ClassFieldInfo || this is ListFieldInfo) + return xmlNameOrName.toLowerKebabCase() + } + +private val FieldInfo.xmlNameOrName: String + get() = xmlName ?: name + +private fun generateDeleteMethod(): MethodSpec = + MethodSpec.methodBuilder("delete") + .addJavadoc("Delete the XML file, if any.") + .addModifiers(Modifier.PUBLIC) + .addStatement("mFile.delete()") + .build() + +private inline fun MethodSpec.Builder.addControlFlow( + controlFlow: String, + vararg args: Any, + block: MethodSpec.Builder.() -> Unit +): MethodSpec.Builder { + beginControlFlow(controlFlow, *args) + block() + endControlFlow() + return this +} diff --git a/tools/xmlpersistence/src/main/kotlin/Main.kt b/tools/xmlpersistence/src/main/kotlin/Main.kt new file mode 100644 index 000000000000..e271f8cb9361 --- /dev/null +++ b/tools/xmlpersistence/src/main/kotlin/Main.kt @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.io.File +import java.nio.file.Files + +fun main(args: Array<String>) { + val showUsage = args.isEmpty() || when (args.singleOrNull()) { + "-h", "--help" -> true + else -> false + } + if (showUsage) { + usage() + return + } + + val files = args.flatMap { + File(it).walk().filter { it.isFile && it.extension == "java" }.map { it.toPath() } + } + val persistences = parse(files) + for (persistence in persistences) { + val file = generate(persistence) + Files.newBufferedWriter(persistence.path).use { + it.write(FILE_HEADER) + file.writeTo(it) + } + } +} + +private fun usage() { + println("Usage: xmlpersistence <FILES>") +} diff --git a/tools/xmlpersistence/src/main/kotlin/Parser.kt b/tools/xmlpersistence/src/main/kotlin/Parser.kt new file mode 100644 index 000000000000..3ea12a9aa389 --- /dev/null +++ b/tools/xmlpersistence/src/main/kotlin/Parser.kt @@ -0,0 +1,248 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import com.github.javaparser.JavaParser +import com.github.javaparser.ParseProblemException +import com.github.javaparser.ParseResult +import com.github.javaparser.ParserConfiguration +import com.github.javaparser.ast.Node +import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration +import com.github.javaparser.ast.body.FieldDeclaration +import com.github.javaparser.ast.body.TypeDeclaration +import com.github.javaparser.ast.expr.AnnotationExpr +import com.github.javaparser.ast.expr.Expression +import com.github.javaparser.ast.expr.NormalAnnotationExpr +import com.github.javaparser.ast.expr.SingleMemberAnnotationExpr +import com.github.javaparser.ast.expr.StringLiteralExpr +import com.github.javaparser.resolution.declarations.ResolvedReferenceTypeDeclaration +import com.github.javaparser.resolution.types.ResolvedPrimitiveType +import com.github.javaparser.resolution.types.ResolvedReferenceType +import com.github.javaparser.symbolsolver.JavaSymbolSolver +import com.github.javaparser.symbolsolver.javaparsermodel.declarations.JavaParserClassDeclaration +import com.github.javaparser.symbolsolver.resolution.typesolvers.CombinedTypeSolver +import com.github.javaparser.symbolsolver.resolution.typesolvers.MemoryTypeSolver +import com.github.javaparser.symbolsolver.resolution.typesolvers.ReflectionTypeSolver +import com.squareup.javapoet.ClassName +import com.squareup.javapoet.ParameterizedTypeName +import com.squareup.javapoet.TypeName +import java.nio.file.Path +import java.util.Optional + +class PersistenceInfo( + val name: String, + val root: ClassFieldInfo, + val path: Path +) + +sealed class FieldInfo { + abstract val name: String + abstract val xmlName: String? + abstract val type: TypeName + abstract val isRequired: Boolean +} + +class PrimitiveFieldInfo( + override val name: String, + override val xmlName: String?, + override val type: TypeName, + override val isRequired: Boolean +) : FieldInfo() + +class StringFieldInfo( + override val name: String, + override val xmlName: String?, + override val isRequired: Boolean +) : FieldInfo() { + override val type: TypeName = ClassName.get(String::class.java) +} + +class ClassFieldInfo( + override val name: String, + override val xmlName: String?, + override val type: ClassName, + override val isRequired: Boolean, + val fields: List<FieldInfo> +) : FieldInfo() + +class ListFieldInfo( + override val name: String, + override val xmlName: String?, + override val type: ParameterizedTypeName, + val element: ClassFieldInfo +) : FieldInfo() { + override val isRequired: Boolean = true +} + +fun parse(files: List<Path>): List<PersistenceInfo> { + val typeSolver = CombinedTypeSolver().apply { add(ReflectionTypeSolver()) } + val javaParser = JavaParser(ParserConfiguration() + .setSymbolResolver(JavaSymbolSolver(typeSolver))) + val compilationUnits = files.map { javaParser.parse(it).getOrThrow() } + val memoryTypeSolver = MemoryTypeSolver().apply { + for (compilationUnit in compilationUnits) { + for (typeDeclaration in compilationUnit.getNodesByClass<TypeDeclaration<*>>()) { + val name = typeDeclaration.fullyQualifiedName.getOrNull() ?: continue + addDeclaration(name, typeDeclaration.resolve()) + } + } + } + typeSolver.add(memoryTypeSolver) + return mutableListOf<PersistenceInfo>().apply { + for (compilationUnit in compilationUnits) { + val classDeclarations = compilationUnit + .getNodesByClass<ClassOrInterfaceDeclaration>() + .filter { !it.isInterface && (!it.isNestedType || it.isStatic) } + this += classDeclarations.mapNotNull { parsePersistenceInfo(it) } + } + } +} + +private fun parsePersistenceInfo(classDeclaration: ClassOrInterfaceDeclaration): PersistenceInfo? { + val annotation = classDeclaration.getAnnotationByName("XmlPersistence").getOrNull() + ?: return null + val rootClassName = classDeclaration.nameAsString + val name = annotation.getMemberValue("value")?.stringLiteralValue + ?: "${rootClassName}Persistence" + val rootXmlName = classDeclaration.getAnnotationByName("XmlName").getOrNull() + ?.getMemberValue("value")?.stringLiteralValue + val root = parseClassFieldInfo( + rootXmlName ?: rootClassName, rootXmlName, true, classDeclaration + ) + val path = classDeclaration.findCompilationUnit().get().storage.get().path + .resolveSibling("$name.java") + return PersistenceInfo(name, root, path) +} + +private fun parseClassFieldInfo( + name: String, + xmlName: String?, + isRequired: Boolean, + classDeclaration: ClassOrInterfaceDeclaration +): ClassFieldInfo { + val fields = classDeclaration.fields.filterNot { it.isStatic }.map { parseFieldInfo(it) } + val type = classDeclaration.resolve().typeName + return ClassFieldInfo(name, xmlName, type, isRequired, fields) +} + +private fun parseFieldInfo(field: FieldDeclaration): FieldInfo { + require(field.isPublic && field.isFinal) + val variable = field.variables.single() + val name = variable.nameAsString + val annotations = field.annotations + variable.type.annotations + val annotation = annotations.getByName("XmlName") + val xmlName = annotation?.getMemberValue("value")?.stringLiteralValue + val isRequired = annotations.getByName("NonNull") != null + return when (val type = variable.type.resolve()) { + is ResolvedPrimitiveType -> { + val primitiveType = type.typeName + PrimitiveFieldInfo(name, xmlName, primitiveType, true) + } + is ResolvedReferenceType -> { + when (type.qualifiedName) { + Boolean::class.javaObjectType.name, Byte::class.javaObjectType.name, + Short::class.javaObjectType.name, Char::class.javaObjectType.name, + Integer::class.javaObjectType.name, Long::class.javaObjectType.name, + Float::class.javaObjectType.name, Double::class.javaObjectType.name -> + PrimitiveFieldInfo(name, xmlName, type.typeName, isRequired) + String::class.java.name -> StringFieldInfo(name, xmlName, isRequired) + List::class.java.name -> { + requireNotNull(xmlName) + val elementType = type.typeParametersValues().single() + require(elementType is ResolvedReferenceType) + val listType = ParameterizedTypeName.get( + ClassName.get(List::class.java), elementType.typeName + ) + val element = parseClassFieldInfo( + "(element)", xmlName, true, elementType.classDeclaration + ) + ListFieldInfo(name, xmlName, listType, element) + } + else -> parseClassFieldInfo(name, xmlName, isRequired, type.classDeclaration) + } + } + else -> error(type) + } +} + +private fun <T> ParseResult<T>.getOrThrow(): T = + if (isSuccessful) { + result.get() + } else { + throw ParseProblemException(problems) + } + +private inline fun <reified T : Node> Node.getNodesByClass(): List<T> = + getNodesByClass(T::class.java) + +private fun <T : Node> Node.getNodesByClass(klass: Class<T>): List<T> = mutableListOf<T>().apply { + if (klass.isInstance(this@getNodesByClass)) { + this += klass.cast(this@getNodesByClass) + } + for (childNode in childNodes) { + this += childNode.getNodesByClass(klass) + } +} + +private fun <T> Optional<T>.getOrNull(): T? = orElse(null) + +private fun List<AnnotationExpr>.getByName(name: String): AnnotationExpr? = + find { it.name.identifier == name } + +private fun AnnotationExpr.getMemberValue(name: String): Expression? = + when (this) { + is NormalAnnotationExpr -> pairs.find { it.nameAsString == name }?.value + is SingleMemberAnnotationExpr -> if (name == "value") memberValue else null + else -> null + } + +private val Expression.stringLiteralValue: String + get() { + require(this is StringLiteralExpr) + return value + } + +private val ResolvedReferenceType.classDeclaration: ClassOrInterfaceDeclaration + get() { + val resolvedClassDeclaration = typeDeclaration + require(resolvedClassDeclaration is JavaParserClassDeclaration) + return resolvedClassDeclaration.wrappedNode + } + +private val ResolvedPrimitiveType.typeName: TypeName + get() = + when (this) { + ResolvedPrimitiveType.BOOLEAN -> TypeName.BOOLEAN + ResolvedPrimitiveType.BYTE -> TypeName.BYTE + ResolvedPrimitiveType.SHORT -> TypeName.SHORT + ResolvedPrimitiveType.CHAR -> TypeName.CHAR + ResolvedPrimitiveType.INT -> TypeName.INT + ResolvedPrimitiveType.LONG -> TypeName.LONG + ResolvedPrimitiveType.FLOAT -> TypeName.FLOAT + ResolvedPrimitiveType.DOUBLE -> TypeName.DOUBLE + } + +// This doesn't support type parameters. +private val ResolvedReferenceType.typeName: TypeName + get() = typeDeclaration.typeName + +private val ResolvedReferenceTypeDeclaration.typeName: ClassName + get() { + val packageName = packageName + val classNames = className.split(".") + val topLevelClassName = classNames.first() + val nestedClassNames = classNames.drop(1) + return ClassName.get(packageName, topLevelClassName, *nestedClassNames.toTypedArray()) + } diff --git a/tools/xmlpersistence/src/main/kotlin/StringCaseExtensions.kt b/tools/xmlpersistence/src/main/kotlin/StringCaseExtensions.kt new file mode 100644 index 000000000000..b4bdbba7170b --- /dev/null +++ b/tools/xmlpersistence/src/main/kotlin/StringCaseExtensions.kt @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.util.Locale + +private val camelHumpBoundary = Regex( + "-" + + "|_" + + "|(?<=[0-9])(?=[^0-9])" + + "|(?<=[A-Z])(?=[^A-Za-z]|[A-Z][a-z])" + + "|(?<=[a-z])(?=[^a-z])" +) + +private fun String.toCamelHumps(): List<String> = split(camelHumpBoundary) + +fun String.toUpperCamelCase(): String = + toCamelHumps().joinToString("") { it.toLowerCase(Locale.ROOT).capitalize(Locale.ROOT) } + +fun String.toLowerCamelCase(): String = toUpperCamelCase().decapitalize(Locale.ROOT) + +fun String.toUpperKebabCase(): String = + toCamelHumps().joinToString("-") { it.toUpperCase(Locale.ROOT) } + +fun String.toLowerKebabCase(): String = + toCamelHumps().joinToString("-") { it.toLowerCase(Locale.ROOT) } + +fun String.toUpperSnakeCase(): String = + toCamelHumps().joinToString("_") { it.toUpperCase(Locale.ROOT) } + +fun String.toLowerSnakeCase(): String = + toCamelHumps().joinToString("_") { it.toLowerCase(Locale.ROOT) } |