diff options
author | Adam Lesinski <adamlesinski@google.com> | 2017-02-16 12:05:42 -0800 |
---|---|---|
committer | Adam Lesinski <adamlesinski@google.com> | 2017-02-22 11:16:13 -0800 |
commit | ceb9b2f80f853059233cdd29057f39a5960a74ae (patch) | |
tree | 9093e8537319a97d8a2cbd8d4f0042c81de5ff8e | |
parent | f9bd2944694539f1dce74d420156cc50bbb4af14 (diff) |
AAPT2: Shared library support
Test: make aapt2_tests
Change-Id: I98dddf1367e6c0ac425bb20be46e6ff05f4f2f45
35 files changed, 770 insertions, 470 deletions
diff --git a/tests/SharedLibrary/client/Android.mk b/tests/SharedLibrary/client/Android.mk index 1d66bb977c37..a04fb0550998 100644 --- a/tests/SharedLibrary/client/Android.mk +++ b/tests/SharedLibrary/client/Android.mk @@ -1,6 +1,7 @@ LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) +LOCAL_USE_AAPT2 := true LOCAL_SRC_FILES := $(call all-subdir-java-files) LOCAL_RES_LIBRARIES := SharedLibrary diff --git a/tests/SharedLibrary/lib/Android.mk b/tests/SharedLibrary/lib/Android.mk index b2fc369527bf..78fcb8b7c0a6 100644 --- a/tests/SharedLibrary/lib/Android.mk +++ b/tests/SharedLibrary/lib/Android.mk @@ -1,5 +1,6 @@ LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) +LOCAL_USE_AAPT2 := true LOCAL_SRC_FILES := $(call all-subdir-java-files) diff --git a/tests/SharedLibrary/lib/res/values/public.xml b/tests/SharedLibrary/lib/res/values/public.xml index 23d307e03a2d..8d7a7adec14f 100644 --- a/tests/SharedLibrary/lib/res/values/public.xml +++ b/tests/SharedLibrary/lib/res/values/public.xml @@ -15,5 +15,5 @@ <public type="drawable" name="size_48x48" id="0x00030000" /> - <public type="array" name="animals" id="0x02050000" /> + <public type="array" name="animals" id="0x00050000" /> </resources> diff --git a/tools/aapt2/Main.cpp b/tools/aapt2/Main.cpp index ba378d716793..7d68d364426c 100644 --- a/tools/aapt2/Main.cpp +++ b/tools/aapt2/Main.cpp @@ -25,7 +25,7 @@ namespace aapt { static const char* sMajorVersion = "2"; // Update minor version whenever a feature or flag is added. -static const char* sMinorVersion = "7"; +static const char* sMinorVersion = "8"; int PrintVersion() { std::cerr << "Android Asset Packaging Tool (aapt) " << sMajorVersion << "." diff --git a/tools/aapt2/NameMangler.h b/tools/aapt2/NameMangler.h index dba2d096dd9d..1305a4cf0710 100644 --- a/tools/aapt2/NameMangler.h +++ b/tools/aapt2/NameMangler.h @@ -62,6 +62,8 @@ class NameMangler { return policy_.packages_to_mangle.count(package) != 0; } + const std::string& GetTargetPackageName() const { return policy_.target_package_name; } + /** * Returns a mangled name that is a combination of `name` and `package`. * The mangled name should contain symbols that are illegal to define in XML, diff --git a/tools/aapt2/Resource.h b/tools/aapt2/Resource.h index 4d915d9f23c0..cffe8d5d803f 100644 --- a/tools/aapt2/Resource.h +++ b/tools/aapt2/Resource.h @@ -138,6 +138,10 @@ struct ResourceId { ResourceId(uint8_t p, uint8_t t, uint16_t e); bool is_valid() const; + + // Returns true if the ID is a valid ID or dynamic ID (package ID can be 0). + bool is_valid_dynamic() const; + uint8_t package_id() const; uint8_t type_id() const; uint16_t entry_id() const; @@ -211,6 +215,8 @@ inline bool ResourceId::is_valid() const { return (id & 0xff000000u) != 0 && (id & 0x00ff0000u) != 0; } +inline bool ResourceId::is_valid_dynamic() const { return (id & 0x00ff0000u) != 0; } + inline uint8_t ResourceId::package_id() const { return static_cast<uint8_t>(id >> 24); } diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp index 47ca2660f15a..8461905d8034 100644 --- a/tools/aapt2/ResourceParser.cpp +++ b/tools/aapt2/ResourceParser.cpp @@ -701,14 +701,11 @@ bool ResourceParser::ParsePublic(xml::XmlPullParser* parser, out_resource->name.type = *parsed_type; - if (Maybe<StringPiece> maybe_id_str = - xml::FindNonEmptyAttribute(parser, "id")) { - Maybe<ResourceId> maybe_id = - ResourceUtils::ParseResourceId(maybe_id_str.value()); + if (Maybe<StringPiece> maybe_id_str = xml::FindNonEmptyAttribute(parser, "id")) { + Maybe<ResourceId> maybe_id = ResourceUtils::ParseResourceId(maybe_id_str.value()); if (!maybe_id) { - diag_->Error(DiagMessage(out_resource->source) << "invalid resource ID '" - << maybe_id.value() - << "' in <public>"); + diag_->Error(DiagMessage(out_resource->source) + << "invalid resource ID '" << maybe_id_str.value() << "' in <public>"); return false; } out_resource->id = maybe_id.value(); diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp index dd78750db290..6e4b450b65e5 100644 --- a/tools/aapt2/ResourceTable.cpp +++ b/tools/aapt2/ResourceTable.cpp @@ -387,8 +387,7 @@ bool ResourceTable::AddResourceImpl( } ResourceTablePackage* package = FindOrCreatePackage(name.package); - if (res_id.is_valid() && package->id && - package->id.value() != res_id.package_id()) { + if (res_id.is_valid_dynamic() && package->id && package->id.value() != res_id.package_id()) { diag->Error(DiagMessage(value->GetSource()) << "trying to add resource '" << name << "' with ID " << res_id << " but package '" << package->name << "' already has ID " @@ -397,7 +396,7 @@ bool ResourceTable::AddResourceImpl( } ResourceTableType* type = package->FindOrCreateType(name.type); - if (res_id.is_valid() && type->id && type->id.value() != res_id.type_id()) { + if (res_id.is_valid_dynamic() && type->id && type->id.value() != res_id.type_id()) { diag->Error(DiagMessage(value->GetSource()) << "trying to add resource '" << name << "' with ID " << res_id << " but type '" << type->type << "' already has ID " @@ -406,8 +405,7 @@ bool ResourceTable::AddResourceImpl( } ResourceEntry* entry = type->FindOrCreateEntry(name.entry); - if (res_id.is_valid() && entry->id && - entry->id.value() != res_id.entry_id()) { + if (res_id.is_valid_dynamic() && entry->id && entry->id.value() != res_id.entry_id()) { diag->Error(DiagMessage(value->GetSource()) << "trying to add resource '" << name << "' with ID " << res_id << " but resource already has ID " @@ -441,7 +439,7 @@ bool ResourceTable::AddResourceImpl( } } - if (res_id.is_valid()) { + if (res_id.is_valid_dynamic()) { package->id = res_id.package_id(); type->id = res_id.type_id(); entry->id = res_id.entry_id(); @@ -480,8 +478,7 @@ bool ResourceTable::SetSymbolStateImpl(const ResourceNameRef& name, } ResourceTablePackage* package = FindOrCreatePackage(name.package); - if (res_id.is_valid() && package->id && - package->id.value() != res_id.package_id()) { + if (res_id.is_valid_dynamic() && package->id && package->id.value() != res_id.package_id()) { diag->Error(DiagMessage(symbol.source) << "trying to add resource '" << name << "' with ID " << res_id << " but package '" << package->name << "' already has ID " @@ -490,7 +487,7 @@ bool ResourceTable::SetSymbolStateImpl(const ResourceNameRef& name, } ResourceTableType* type = package->FindOrCreateType(name.type); - if (res_id.is_valid() && type->id && type->id.value() != res_id.type_id()) { + if (res_id.is_valid_dynamic() && type->id && type->id.value() != res_id.type_id()) { diag->Error(DiagMessage(symbol.source) << "trying to add resource '" << name << "' with ID " << res_id << " but type '" << type->type << "' already has ID " @@ -499,8 +496,7 @@ bool ResourceTable::SetSymbolStateImpl(const ResourceNameRef& name, } ResourceEntry* entry = type->FindOrCreateEntry(name.entry); - if (res_id.is_valid() && entry->id && - entry->id.value() != res_id.entry_id()) { + if (res_id.is_valid_dynamic() && entry->id && entry->id.value() != res_id.entry_id()) { diag->Error(DiagMessage(symbol.source) << "trying to add resource '" << name << "' with ID " << res_id << " but resource already has ID " @@ -509,7 +505,7 @@ bool ResourceTable::SetSymbolStateImpl(const ResourceNameRef& name, return false; } - if (res_id.is_valid()) { + if (res_id.is_valid_dynamic()) { package->id = res_id.package_id(); type->id = res_id.type_id(); entry->id = res_id.entry_id(); diff --git a/tools/aapt2/ResourceTable.h b/tools/aapt2/ResourceTable.h index 0fe966c5089b..6b69aaf02cbe 100644 --- a/tools/aapt2/ResourceTable.h +++ b/tools/aapt2/ResourceTable.h @@ -158,11 +158,8 @@ class ResourceTableType { DISALLOW_COPY_AND_ASSIGN(ResourceTableType); }; -enum class PackageType { System, Vendor, App, Dynamic }; - class ResourceTablePackage { public: - PackageType type = PackageType::App; Maybe<uint8_t> id; std::string name; @@ -241,6 +238,19 @@ class ResourceTable { Maybe<SearchResult> FindResource(const ResourceNameRef& name); /** + * 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); + + ResourceTablePackage* FindPackageById(uint8_t id); + + ResourceTablePackage* CreatePackage(const android::StringPiece& name, Maybe<uint8_t> id = {}); + + /** * The string pool used by this resource table. Values that reference strings * must use * this pool to create their strings. @@ -259,18 +269,9 @@ class ResourceTable { */ std::vector<std::unique_ptr<ResourceTablePackage>> packages; - /** - * 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); - - ResourceTablePackage* FindPackageById(uint8_t id); - - ResourceTablePackage* CreatePackage(const android::StringPiece& name, Maybe<uint8_t> id = {}); + // Set of dynamic packages that this table may reference. Their package names get encoded + // into the resources.arsc along with their compile-time assigned IDs. + std::map<size_t, std::string> included_packages_; private: ResourceTablePackage* FindOrCreatePackage(const android::StringPiece& name); diff --git a/tools/aapt2/ResourceUtils.cpp b/tools/aapt2/ResourceUtils.cpp index 150dc58290d4..2d8e5a219026 100644 --- a/tools/aapt2/ResourceUtils.cpp +++ b/tools/aapt2/ResourceUtils.cpp @@ -469,7 +469,7 @@ Maybe<ResourceId> ParseResourceId(const StringPiece& str) { if (android::ResTable::stringToInt(str16.data(), str16.size(), &value)) { if (value.dataType == android::Res_value::TYPE_INT_HEX) { ResourceId id(value.data); - if (id.is_valid()) { + if (id.is_valid_dynamic()) { return id; } } diff --git a/tools/aapt2/ValueVisitor.h b/tools/aapt2/ValueVisitor.h index 1cb6aa13f336..2763d49f15c4 100644 --- a/tools/aapt2/ValueVisitor.h +++ b/tools/aapt2/ValueVisitor.h @@ -29,7 +29,8 @@ namespace aapt { struct RawValueVisitor { virtual ~RawValueVisitor() = default; - virtual void VisitItem(Item* value) {} + virtual void VisitAny(Value* value) {} + virtual void VisitItem(Item* value) { VisitAny(value); } virtual void Visit(Reference* value) { VisitItem(value); } virtual void Visit(RawString* value) { VisitItem(value); } virtual void Visit(String* value) { VisitItem(value); } @@ -38,11 +39,11 @@ struct RawValueVisitor { virtual void Visit(Id* value) { VisitItem(value); } virtual void Visit(BinaryPrimitive* value) { VisitItem(value); } - virtual void Visit(Attribute* value) {} - virtual void Visit(Style* value) {} - virtual void Visit(Array* value) {} - virtual void Visit(Plural* value) {} - virtual void Visit(Styleable* value) {} + virtual void Visit(Attribute* value) { VisitAny(value); } + virtual void Visit(Style* value) { VisitAny(value); } + virtual void Visit(Array* value) { VisitAny(value); } + virtual void Visit(Plural* value) { VisitAny(value); } + virtual void Visit(Styleable* value) { VisitAny(value); } }; // NOLINT, do not add parentheses around T. diff --git a/tools/aapt2/compile/IdAssigner.h b/tools/aapt2/compile/IdAssigner.h index 9640eb8c50ff..3dcb1eb939b3 100644 --- a/tools/aapt2/compile/IdAssigner.h +++ b/tools/aapt2/compile/IdAssigner.h @@ -26,11 +26,8 @@ namespace aapt { -/** - * Assigns IDs to each resource in the table, respecting existing IDs and - * filling in gaps - * in between fixed ID assignments. - */ +// Assigns IDs to each resource in the table, respecting existing IDs and +// filling in gaps in between fixed ID assignments. class IdAssigner : public IResourceTableConsumer { public: IdAssigner() = default; diff --git a/tools/aapt2/diff/Diff.cpp b/tools/aapt2/diff/Diff.cpp index acebedafe43d..c8774687944c 100644 --- a/tools/aapt2/diff/Diff.cpp +++ b/tools/aapt2/diff/Diff.cpp @@ -28,6 +28,8 @@ namespace aapt { class DiffContext : public IAaptContext { public: + DiffContext() : name_mangler_({}), symbol_table_(&name_mangler_) {} + const std::string& GetCompilationPackage() override { return empty_; } uint8_t GetPackageId() override { return 0x0; } @@ -45,7 +47,7 @@ class DiffContext : public IAaptContext { private: std::string empty_; StdErrDiagnostics diagnostics_; - NameMangler name_mangler_ = NameMangler(NameManglerPolicy{}); + NameMangler name_mangler_; SymbolTable symbol_table_; }; diff --git a/tools/aapt2/flatten/TableFlattener.cpp b/tools/aapt2/flatten/TableFlattener.cpp index 697b07fa414d..3098458e4e4f 100644 --- a/tools/aapt2/flatten/TableFlattener.cpp +++ b/tools/aapt2/flatten/TableFlattener.cpp @@ -217,16 +217,17 @@ class MapFlattenVisitor : public RawValueVisitor { class PackageFlattener { public: - PackageFlattener(IAaptContext* context, ResourceTablePackage* package, bool use_sparse_entries) + PackageFlattener(IAaptContext* context, ResourceTablePackage* package, + const std::map<size_t, std::string>* shared_libs, bool use_sparse_entries) : context_(context), diag_(context->GetDiagnostics()), package_(package), + shared_libs_(shared_libs), use_sparse_entries_(use_sparse_entries) {} bool FlattenPackage(BigBuffer* buffer) { ChunkWriter pkg_writer(buffer); - ResTable_package* pkg_header = - pkg_writer.StartChunk<ResTable_package>(RES_TABLE_PACKAGE_TYPE); + ResTable_package* pkg_header = pkg_writer.StartChunk<ResTable_package>(RES_TABLE_PACKAGE_TYPE); pkg_header->id = util::HostToDevice32(package_->id.value()); if (package_->name.size() >= arraysize(pkg_header->name)) { @@ -253,6 +254,11 @@ class PackageFlattener { // Append the types. 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()) { + FlattenLibrarySpec(buffer); + } + pkg_writer.Finish(); return true; } @@ -470,6 +476,10 @@ class PackageFlattener { type_pool_.MakeRef(ToString(type->type)); std::vector<ResourceEntry*> sorted_entries = CollectAndSortEntries(type); + if (sorted_entries.empty()) { + continue; + } + if (!FlattenTypeSpec(type, &sorted_entries, buffer)) { return false; } @@ -504,9 +514,38 @@ class PackageFlattener { return true; } + void FlattenLibrarySpec(BigBuffer* buffer) { + ChunkWriter lib_writer(buffer); + 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(); + 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) { + // Add this package + lib_entry->packageId = util::HostToDevice32(0x00); + strcpy16_htod(lib_entry->packageName, arraysize(lib_entry->packageName), + util::Utf8ToUtf16(package_->name)); + ++lib_entry; + } + + for (auto& map_entry : *shared_libs_) { + lib_entry->packageId = util::HostToDevice32(map_entry.first); + strcpy16_htod(lib_entry->packageName, arraysize(lib_entry->packageName), + util::Utf8ToUtf16(map_entry.second)); + ++lib_entry; + } + lib_writer.Finish(); + } + IAaptContext* context_; IDiagnostics* diag_; ResourceTablePackage* package_; + const std::map<size_t, std::string>* shared_libs_; bool use_sparse_entries_; StringPool type_pool_; StringPool key_pool_; @@ -542,7 +581,8 @@ bool TableFlattener::Consume(IAaptContext* context, ResourceTable* table) { // Flatten each package. for (auto& package : table->packages) { - PackageFlattener flattener(context, package.get(), options_.use_sparse_entries); + PackageFlattener flattener(context, package.get(), &table->included_packages_, + options_.use_sparse_entries); if (!flattener.FlattenPackage(&package_buffer)) { return false; } diff --git a/tools/aapt2/flatten/TableFlattener_test.cpp b/tools/aapt2/flatten/TableFlattener_test.cpp index ff717423fc92..419618749a95 100644 --- a/tools/aapt2/flatten/TableFlattener_test.cpp +++ b/tools/aapt2/flatten/TableFlattener_test.cpp @@ -56,13 +56,13 @@ class TableFlattenerTest : public ::testing::Test { return result; } - if (out_table->add(content.data(), content.size(), -1, true) != NO_ERROR) { + if (out_table->add(content.data(), content.size(), 1, true) != NO_ERROR) { return ::testing::AssertionFailure() << "flattened ResTable is corrupt"; } return ::testing::AssertionSuccess(); } - ::testing::AssertionResult Flatten(IAaptContext* context, const TableFlattenerOptions options, + ::testing::AssertionResult Flatten(IAaptContext* context, const TableFlattenerOptions& options, ResourceTable* table, ResourceTable* out_table) { std::string content; auto result = Flatten(context, options, table, &content); @@ -359,4 +359,56 @@ TEST_F(TableFlattenerTest, DoNotUseSparseEntryForDenseConfig) { EXPECT_EQ(no_sparse_contents.size(), sparse_contents.size()); } +TEST_F(TableFlattenerTest, FlattenSharedLibrary) { + std::unique_ptr<IAaptContext> context = + 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; + ASSERT_TRUE(Flatten(context.get(), {}, table.get(), &result)); + + Maybe<ResourceTable::SearchResult> search_result = + result.FindResource(test::ParseNameOrDie("lib:id/foo")); + AAPT_ASSERT_TRUE(search_result); + EXPECT_EQ(0x00u, search_result.value().package->id.value()); + + auto iter = result.included_packages_.find(0x00); + ASSERT_NE(result.included_packages_.end(), iter); + EXPECT_EQ("lib", iter->second); +} + +TEST_F(TableFlattenerTest, FlattenTableReferencingSharedLibraries) { + std::unique_ptr<IAaptContext> context = + 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), + test::BuildReference("lib_two:id/bar", ResourceId(0x03010000))) + .Build(); + table->included_packages_[0x02] = "lib_one"; + table->included_packages_[0x03] = "lib_two"; + + ResTable result; + ASSERT_TRUE(Flatten(context.get(), {}, table.get(), &result)); + + const DynamicRefTable* dynamic_ref_table = result.getDynamicRefTableForCookie(1); + ASSERT_NE(nullptr, dynamic_ref_table); + + const KeyedVector<String16, uint8_t> entries = dynamic_ref_table->entries(); + + ssize_t idx = entries.indexOfKey(android::String16("lib_one")); + ASSERT_GE(idx, 0); + EXPECT_EQ(0x02u, entries.valueAt(idx)); + + idx = entries.indexOfKey(android::String16("lib_two")); + ASSERT_GE(idx, 0); + EXPECT_EQ(0x03u, entries.valueAt(idx)); +} + } // namespace aapt diff --git a/tools/aapt2/java/ClassDefinition.cpp b/tools/aapt2/java/ClassDefinition.cpp index 53d6ea1a79c5..0cec9ae407f5 100644 --- a/tools/aapt2/java/ClassDefinition.cpp +++ b/tools/aapt2/java/ClassDefinition.cpp @@ -22,6 +22,23 @@ using android::StringPiece; namespace aapt { +void ClassMember::WriteToStream(const StringPiece& prefix, bool final, std::ostream* out) const { + processor_.WriteToStream(out, prefix); +} + +void MethodDefinition::AppendStatement(const StringPiece& statement) { + statements_.push_back(statement.to_string()); +} + +void MethodDefinition::WriteToStream(const StringPiece& prefix, bool final, + std::ostream* out) const { + *out << prefix << signature_ << " {\n"; + for (const auto& statement : statements_) { + *out << prefix << " " << statement << "\n"; + } + *out << prefix << "}"; +} + bool ClassDefinition::empty() const { for (const std::unique_ptr<ClassMember>& member : members_) { if (!member->empty()) { @@ -40,7 +57,7 @@ void ClassDefinition::WriteToStream(const StringPiece& prefix, bool final, ClassMember::WriteToStream(prefix, final, out); *out << prefix << "public "; - if (qualifier_ == ClassQualifier::Static) { + if (qualifier_ == ClassQualifier::kStatic) { *out << "static "; } *out << "final class " << name_ << " {\n"; diff --git a/tools/aapt2/java/ClassDefinition.h b/tools/aapt2/java/ClassDefinition.h index 64e4b2987be3..ca76421390d6 100644 --- a/tools/aapt2/java/ClassDefinition.h +++ b/tools/aapt2/java/ClassDefinition.h @@ -41,10 +41,11 @@ class ClassMember { virtual bool empty() const = 0; + // Writes the class member to the out stream. Subclasses should derive this method + // to write their own data. Call this base method from the subclass to write out + // this member's comments/annotations. virtual void WriteToStream(const android::StringPiece& prefix, bool final, - std::ostream* out) const { - processor_.WriteToStream(out, prefix); - } + std::ostream* out) const; private: AnnotationProcessor processor_; @@ -142,7 +143,29 @@ class PrimitiveArrayMember : public ClassMember { using ResourceArrayMember = PrimitiveArrayMember<ResourceId>; -enum class ClassQualifier { None, Static }; +// Represents a method in a class. +class MethodDefinition : public ClassMember { + public: + // Expected method signature example: 'public static void onResourcesLoaded(int p)'. + explicit MethodDefinition(const android::StringPiece& signature) + : signature_(signature.to_string()) {} + + // Appends a single statement to the method. It should include no newlines or else + // formatting may be broken. + void AppendStatement(const android::StringPiece& statement); + + // Even if the method is empty, we always want to write the method signature. + bool empty() const override { return false; } + + void WriteToStream(const android::StringPiece& prefix, bool final, + std::ostream* out) const override; + + private: + std::string signature_; + std::vector<std::string> statements_; +}; + +enum class ClassQualifier { kNone, kStatic }; class ClassDefinition : public ClassMember { public: diff --git a/tools/aapt2/java/JavaClassGenerator.cpp b/tools/aapt2/java/JavaClassGenerator.cpp index b71dc485633b..9c0f13c0241d 100644 --- a/tools/aapt2/java/JavaClassGenerator.cpp +++ b/tools/aapt2/java/JavaClassGenerator.cpp @@ -23,6 +23,7 @@ #include <tuple> #include "android-base/logging.h" +#include "android-base/stringprintf.h" #include "androidfw/StringPiece.h" #include "NameMangler.h" @@ -35,6 +36,7 @@ #include "process/SymbolTable.h" using android::StringPiece; +using android::base::StringPrintf; namespace aapt { @@ -55,11 +57,9 @@ static bool IsValidSymbol(const StringPiece& symbol) { return sJavaIdentifiers.find(symbol) == sJavaIdentifiers.end(); } -/* - * Java symbols can not contain . or -, but those are valid in a resource name. - * Replace those with '_'. - */ -static std::string Transform(const StringPiece& symbol) { +// Java symbols can not contain . or -, but those are valid in a resource name. +// Replace those with '_'. +static std::string TransformToFieldName(const StringPiece& symbol) { std::string output = symbol.to_string(); for (char& c : output) { if (c == '.' || c == '-') { @@ -69,34 +69,31 @@ static std::string Transform(const StringPiece& symbol) { return output; } -/** - * Transforms an attribute in a styleable to the Java field name: - * - * <declare-styleable name="Foo"> - * <attr name="android:bar" /> - * <attr name="bar" /> - * </declare-styleable> - * - * Foo_android_bar - * Foo_bar - */ -static std::string TransformNestedAttr( - const ResourceNameRef& attr_name, const std::string& styleable_class_name, - const StringPiece& package_name_to_generate) { +// Transforms an attribute in a styleable to the Java field name: +// +// <declare-styleable name="Foo"> +// <attr name="android:bar" /> +// <attr name="bar" /> +// </declare-styleable> +// +// Foo_android_bar +// Foo_bar +static std::string TransformNestedAttr(const ResourceNameRef& attr_name, + const std::string& styleable_class_name, + const StringPiece& package_name_to_generate) { std::string output = styleable_class_name; // We may reference IDs from other packages, so prefix the entry name with // the package. if (!attr_name.package.empty() && package_name_to_generate != attr_name.package) { - output += "_" + Transform(attr_name.package); + output += "_" + TransformToFieldName(attr_name.package); } - output += "_" + Transform(attr_name.entry); + output += "_" + TransformToFieldName(attr_name.entry); return output; } -static void AddAttributeFormatDoc(AnnotationProcessor* processor, - Attribute* attr) { +static void AddAttributeFormatDoc(AnnotationProcessor* processor, Attribute* attr) { const uint32_t type_mask = attr->type_mask; if (type_mask & android::ResTable_map::TYPE_REFERENCE) { processor->AppendComment( @@ -128,7 +125,7 @@ static void AddAttributeFormatDoc(AnnotationProcessor* processor, processor->AppendComment( "<p>May be a color value, in the form of " "\"<code>#<i>rgb</i></code>\",\n" - "\"<code>#<i>argb</i></code>\", \"<code>#<i>rrggbb</i></code\", or \n" + "\"<code>#<i>argb</i></code>\", \"<code>#<i>rrggbb</i></code>\", or \n" "\"<code>#<i>aarrggbb</i></code>\"."); } @@ -202,18 +199,21 @@ bool JavaClassGenerator::SkipSymbol(SymbolState state) { return true; } +// Whether or not to skip writing this symbol. +bool JavaClassGenerator::SkipSymbol(const Maybe<SymbolTable::Symbol>& symbol) { + return !symbol || (options_.types == JavaClassGeneratorOptions::SymbolTypes::kPublic && + !symbol.value().is_public); +} + struct StyleableAttr { - const Reference* attr_ref; + const Reference* attr_ref = nullptr; std::string field_name; - std::unique_ptr<SymbolTable::Symbol> symbol; + Maybe<SymbolTable::Symbol> symbol; }; -static bool less_styleable_attr(const StyleableAttr& lhs, - const StyleableAttr& rhs) { - const ResourceId lhs_id = - lhs.attr_ref->id ? lhs.attr_ref->id.value() : ResourceId(0); - const ResourceId rhs_id = - rhs.attr_ref->id ? rhs.attr_ref->id.value() : ResourceId(0); +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) { @@ -223,72 +223,57 @@ static bool less_styleable_attr(const StyleableAttr& lhs, } } -void JavaClassGenerator::AddMembersToStyleableClass( - const StringPiece& package_name_to_generate, const std::string& entry_name, - const Styleable* styleable, ClassDefinition* out_styleable_class_def) { - const std::string class_name = Transform(entry_name); - - std::unique_ptr<ResourceArrayMember> styleable_array_def = - util::make_unique<ResourceArrayMember>(class_name); +void JavaClassGenerator::ProcessStyleable(const ResourceNameRef& name, const ResourceId& id, + const Styleable& styleable, + const StringPiece& package_name_to_generate, + ClassDefinition* out_class_def, + MethodDefinition* out_rewrite_method) { + const std::string array_field_name = TransformToFieldName(name.entry); + std::unique_ptr<ResourceArrayMember> array_def = + util::make_unique<ResourceArrayMember>(array_field_name); - // This must be sorted by resource ID. + // The array must be sorted by resource ID. std::vector<StyleableAttr> sorted_attributes; - sorted_attributes.reserve(styleable->entries.size()); - for (const auto& attr : styleable->entries) { + sorted_attributes.reserve(styleable.entries.size()); + for (const auto& attr : styleable.entries) { // If we are not encoding final attributes, the styleable entry may have no // ID if we are building a static library. CHECK(!options_.use_final || attr.id) << "no ID set for Styleable entry"; CHECK(bool(attr.name)) << "no name set for Styleable entry"; - // We will need the unmangled, transformed name in the comments and the - // field, + // We will need the unmangled, transformed name in the comments and the field, // so create it once and cache it in this StyleableAttr data structure. - StyleableAttr styleable_attr = {}; + StyleableAttr styleable_attr; styleable_attr.attr_ref = &attr; - styleable_attr.field_name = TransformNestedAttr( - attr.name.value(), class_name, package_name_to_generate); - - Reference mangled_reference; - mangled_reference.id = attr.id; - mangled_reference.name = attr.name; - if (mangled_reference.name.value().package.empty()) { - mangled_reference.name.value().package = - context_->GetCompilationPackage(); - } - if (Maybe<ResourceName> mangled_name = - context_->GetNameMangler()->MangleName( - mangled_reference.name.value())) { - mangled_reference.name = mangled_name; - } + // The field name for this attribute is prefixed by the name of this styleable and + // the package it comes from. + styleable_attr.field_name = + TransformNestedAttr(attr.name.value(), array_field_name, package_name_to_generate); - // Look up the symbol so that we can write out in the comments what are - // possible - // legal values for this attribute. - const SymbolTable::Symbol* symbol = - context_->GetExternalSymbols()->FindByReference(mangled_reference); + // Look up the symbol so that we can write out in the comments what are possible legal values + // for this attribute. + const SymbolTable::Symbol* symbol = context_->GetExternalSymbols()->FindByReference(attr); if (symbol && symbol->attribute) { - // Copy the symbol data structure because the returned instance can be - // destroyed. - styleable_attr.symbol = util::make_unique<SymbolTable::Symbol>(*symbol); + // Copy the symbol data structure because the returned instance can be destroyed. + styleable_attr.symbol = *symbol; } sorted_attributes.push_back(std::move(styleable_attr)); } // Sort the attributes by ID. - std::sort(sorted_attributes.begin(), sorted_attributes.end(), - less_styleable_attr); + std::sort(sorted_attributes.begin(), sorted_attributes.end()); + // Build the JavaDoc comment for the Styleable array. This has references to child attributes + // and what possible values can be used for them. const size_t attr_count = sorted_attributes.size(); if (attr_count > 0) { - // Build the comment string for the Styleable. It includes details about the - // child attributes. std::stringstream styleable_comment; - if (!styleable->GetComment().empty()) { - styleable_comment << styleable->GetComment() << "\n"; + if (!styleable.GetComment().empty()) { + styleable_comment << styleable.GetComment() << "\n"; } else { - styleable_comment << "Attributes that can be used with a " << class_name - << ".\n"; + // Apply a default intro comment if the styleable has no comments of its own. + styleable_comment << "Attributes that can be used with a " << array_field_name << ".\n"; } styleable_comment << "<p>Includes the following attributes:</p>\n" @@ -297,22 +282,16 @@ void JavaClassGenerator::AddMembersToStyleableClass( "<colgroup align=\"left\" />\n" "<tr><th>Attribute</th><th>Description</th></tr>\n"; + // Build the table of attributes with their links and names. for (const StyleableAttr& entry : sorted_attributes) { - if (!entry.symbol) { + if (SkipSymbol(entry.symbol)) { continue; } - if (options_.types == JavaClassGeneratorOptions::SymbolTypes::kPublic && - !entry.symbol->is_public) { - // Don't write entries for non-public attributes. - continue; - } - - StringPiece attr_comment_line = entry.symbol->attribute->GetComment(); + StringPiece attr_comment_line = entry.symbol.value().attribute->GetComment(); if (attr_comment_line.contains("@removed")) { // Removed attributes are public but hidden from the documentation, so - // don't emit - // them as part of the class documentation. + // don't emit them as part of the class documentation. continue; } @@ -327,71 +306,52 @@ void JavaClassGenerator::AddMembersToStyleableClass( styleable_comment << "<td>"; - // Only use the comment up until the first '.'. This is to stay compatible - // with - // the way old AAPT did it (presumably to keep it short and to avoid - // including + // Only use the comment up until the first '.'. This is to stay compatible with + // the way old AAPT did it (presumably to keep it short and to avoid including // annotations like @hide which would affect this Styleable). - auto iter = - std::find(attr_comment_line.begin(), attr_comment_line.end(), u'.'); + auto iter = std::find(attr_comment_line.begin(), attr_comment_line.end(), '.'); if (iter != attr_comment_line.end()) { - attr_comment_line = - attr_comment_line.substr(0, (iter - attr_comment_line.begin()) + 1); + attr_comment_line = attr_comment_line.substr(0, (iter - attr_comment_line.begin()) + 1); } styleable_comment << attr_comment_line << "</td></tr>\n"; } styleable_comment << "</table>\n"; + // Generate the @see lines for each attribute. for (const StyleableAttr& entry : sorted_attributes) { - if (!entry.symbol) { - continue; - } - - if (options_.types == JavaClassGeneratorOptions::SymbolTypes::kPublic && - !entry.symbol->is_public) { - // Don't write entries for non-public attributes. + if (SkipSymbol(entry.symbol)) { continue; } styleable_comment << "@see #" << entry.field_name << "\n"; } - styleable_array_def->GetCommentBuilder()->AppendComment( - styleable_comment.str()); + array_def->GetCommentBuilder()->AppendComment(styleable_comment.str()); } // Add the ResourceIds to the array member. for (const StyleableAttr& styleable_attr : sorted_attributes) { - styleable_array_def->AddElement(styleable_attr.attr_ref->id - ? styleable_attr.attr_ref->id.value() - : ResourceId(0)); + const ResourceId id = styleable_attr.attr_ref->id.value_or_default(ResourceId(0)); + array_def->AddElement(id); } // Add the Styleable array to the Styleable class. - out_styleable_class_def->AddMember(std::move(styleable_array_def)); + out_class_def->AddMember(std::move(array_def)); // Now we emit the indices into the array. for (size_t i = 0; i < attr_count; i++) { const StyleableAttr& styleable_attr = sorted_attributes[i]; - - if (!styleable_attr.symbol) { - continue; - } - - if (options_.types == JavaClassGeneratorOptions::SymbolTypes::kPublic && - !styleable_attr.symbol->is_public) { - // Don't write entries for non-public attributes. + if (SkipSymbol(styleable_attr.symbol)) { continue; } StringPiece comment = styleable_attr.attr_ref->GetComment(); - if (styleable_attr.symbol->attribute && comment.empty()) { - comment = styleable_attr.symbol->attribute->GetComment(); + if (styleable_attr.symbol.value().attribute && comment.empty()) { + comment = styleable_attr.symbol.value().attribute->GetComment(); } if (comment.contains("@removed")) { // Removed attributes are public but hidden from the documentation, so - // don't emit them - // as part of the class documentation. + // don't emit them as part of the class documentation. continue; } @@ -414,114 +374,151 @@ void JavaClassGenerator::AddMembersToStyleableClass( std::stringstream default_comment; default_comment << "<p>This symbol is the offset where the " << "{@link " << package_name << ".R.attr#" - << Transform(attr_name.entry) << "}\n" + << TransformToFieldName(attr_name.entry) << "}\n" << "attribute's value can be found in the " - << "{@link #" << class_name << "} array."; + << "{@link #" << array_field_name << "} array."; attr_processor->AppendComment(default_comment.str()); } attr_processor->AppendNewLine(); - - AddAttributeFormatDoc(attr_processor, - styleable_attr.symbol->attribute.get()); + AddAttributeFormatDoc(attr_processor, styleable_attr.symbol.value().attribute.get()); attr_processor->AppendNewLine(); + attr_processor->AppendComment( + StringPrintf("@attr name %s:%s", package_name.data(), attr_name.entry.data())); + + out_class_def->AddMember(std::move(index_member)); + } + + // 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) | (p << 24);", + array_field_name.data(), array_field_name.data())); + out_rewrite_method->AppendStatement(" }"); + out_rewrite_method->AppendStatement("}"); + } +} + +void JavaClassGenerator::ProcessResource(const ResourceNameRef& name, const ResourceId& id, + const ResourceEntry& entry, ClassDefinition* out_class_def, + MethodDefinition* out_rewrite_method) { + const std::string field_name = TransformToFieldName(name.entry); + std::unique_ptr<ResourceMember> resource_member = + util::make_unique<ResourceMember>(field_name, id); + + // Build the comments and annotations for this entry. + AnnotationProcessor* processor = resource_member->GetCommentBuilder(); + + // Add the comments from any <public> tags. + if (entry.symbol_status.state != SymbolState::kUndefined) { + processor->AppendComment(entry.symbol_status.comment); + } - std::stringstream doclava_name; - doclava_name << "@attr name " << package_name << ":" << attr_name.entry; + // Add the comments from all configurations of this entry. + for (const auto& config_value : entry.values) { + processor->AppendComment(config_value->value->GetComment()); + } + + // If this is an Attribute, append the format Javadoc. + if (!entry.values.empty()) { + if (Attribute* attr = ValueCast<Attribute>(entry.values.front()->value.get())) { + // We list out the available values for the given attribute. + AddAttributeFormatDoc(processor, attr); + } + } - attr_processor->AppendComment(doclava_name.str()); + out_class_def->AddMember(std::move(resource_member)); - out_styleable_class_def->AddMember(std::move(index_member)); + if (out_rewrite_method != nullptr) { + const StringPiece& type_str = ToString(name.type); + out_rewrite_method->AppendStatement(StringPrintf("%s.%s = (%s.%s & 0x00ffffff) | (p << 24);", + type_str.data(), field_name.data(), + type_str.data(), field_name.data())); } } -bool JavaClassGenerator::AddMembersToTypeClass( - const StringPiece& package_name_to_generate, - const ResourceTablePackage* package, const ResourceTableType* type, - ClassDefinition* out_type_class_def) { - for (const auto& entry : type->entries) { - if (SkipSymbol(entry->symbol_status.state)) { +Maybe<std::string> JavaClassGenerator::UnmangleResource(const StringPiece& package_name, + const StringPiece& package_name_to_generate, + const ResourceEntry& entry) { + if (SkipSymbol(entry.symbol_status.state)) { + return {}; + } + + std::string unmangled_package; + std::string unmangled_name = entry.name; + if (NameMangler::Unmangle(&unmangled_name, &unmangled_package)) { + // The entry name was mangled, and we successfully unmangled it. + // Check that we want to emit this symbol. + if (package_name != unmangled_package) { + // Skip the entry if it doesn't belong to the package we're writing. + return {}; + } + } else if (package_name_to_generate != package_name) { + // We are processing a mangled package name, + // but this is a non-mangled resource. + return {}; + } + return {std::move(unmangled_name)}; +} + +bool JavaClassGenerator::ProcessType(const StringPiece& package_name_to_generate, + const ResourceTablePackage& package, + const ResourceTableType& type, + ClassDefinition* out_type_class_def, + MethodDefinition* out_rewrite_method_def) { + for (const auto& entry : type.entries) { + const Maybe<std::string> unmangled_name = + UnmangleResource(package.name, package_name_to_generate, *entry); + if (!unmangled_name) { continue; } + // 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 (package.id && type.id && entry->id) { + id = ResourceId(package.id.value(), type.id.value(), entry->id.value()); } - std::string unmangled_package; - std::string unmangled_name = entry->name; - if (NameMangler::Unmangle(&unmangled_name, &unmangled_package)) { - // The entry name was mangled, and we successfully unmangled it. - // Check that we want to emit this symbol. - if (package->name != unmangled_package) { - // Skip the entry if it doesn't belong to the package we're writing. - continue; - } - } else if (package_name_to_generate != package->name) { - // We are processing a mangled package name, - // but this is a non-mangled resource. - continue; - } + // We need to make sure we hide the fact that we are generating kAttrPrivate attributes. + const ResourceNameRef resource_name( + package_name_to_generate, + type.type == ResourceType::kAttrPrivate ? ResourceType::kAttr : type.type, + unmangled_name.value()); - if (!IsValidSymbol(unmangled_name)) { - ResourceNameRef resource_name(package_name_to_generate, type->type, - unmangled_name); + // Check to see if the unmangled name is a valid Java name (not a keyword). + if (!IsValidSymbol(unmangled_name.value())) { std::stringstream err; err << "invalid symbol name '" << resource_name << "'"; error_ = err.str(); return false; } - if (type->type == ResourceType::kStyleable) { + if (resource_name.type == ResourceType::kStyleable) { CHECK(!entry->values.empty()); const Styleable* styleable = static_cast<const Styleable*>(entry->values.front()->value.get()); - // Comments are handled within this method. - AddMembersToStyleableClass(package_name_to_generate, unmangled_name, - styleable, out_type_class_def); + ProcessStyleable(resource_name, id, *styleable, package_name_to_generate, out_type_class_def, + out_rewrite_method_def); } else { - std::unique_ptr<ResourceMember> resource_member = - util::make_unique<ResourceMember>(Transform(unmangled_name), id); - - // Build the comments and annotations for this entry. - AnnotationProcessor* processor = resource_member->GetCommentBuilder(); - - // Add the comments from any <public> tags. - if (entry->symbol_status.state != SymbolState::kUndefined) { - processor->AppendComment(entry->symbol_status.comment); - } - - // Add the comments from all configurations of this entry. - for (const auto& config_value : entry->values) { - processor->AppendComment(config_value->value->GetComment()); - } - - // If this is an Attribute, append the format Javadoc. - if (!entry->values.empty()) { - if (Attribute* attr = - ValueCast<Attribute>(entry->values.front()->value.get())) { - // We list out the available values for the given attribute. - AddAttributeFormatDoc(processor, attr); - } - } - - out_type_class_def->AddMember(std::move(resource_member)); + ProcessResource(resource_name, id, *entry, out_type_class_def, out_rewrite_method_def); } } return true; } -bool JavaClassGenerator::Generate(const StringPiece& package_name_to_generate, - std::ostream* out) { +bool JavaClassGenerator::Generate(const StringPiece& package_name_to_generate, std::ostream* out) { return Generate(package_name_to_generate, package_name_to_generate, out); } -static void AppendJavaDocAnnotations( - const std::vector<std::string>& annotations, - AnnotationProcessor* processor) { +static void AppendJavaDocAnnotations(const std::vector<std::string>& annotations, + AnnotationProcessor* processor) { for (const std::string& annotation : annotations) { std::string proper_annotation = "@"; proper_annotation += annotation; @@ -532,37 +529,40 @@ static void AppendJavaDocAnnotations( bool JavaClassGenerator::Generate(const StringPiece& package_name_to_generate, const StringPiece& out_package_name, std::ostream* out) { - ClassDefinition r_class("R", ClassQualifier::None, true); + ClassDefinition r_class("R", ClassQualifier::kNone, true); + std::unique_ptr<MethodDefinition> rewrite_method; + + // Generate an onResourcesLoaded() callback if requested. + if (options_.generate_rewrite_callback) { + rewrite_method = + util::make_unique<MethodDefinition>("public static void onResourcesLoaded(int p)"); + } 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. continue; } + // Stay consistent with AAPT and generate an empty type class if the R class + // is public. const bool force_creation_if_empty = (options_.types == JavaClassGeneratorOptions::SymbolTypes::kPublic); - std::unique_ptr<ClassDefinition> class_def = - util::make_unique<ClassDefinition>(ToString(type->type), - ClassQualifier::Static, - force_creation_if_empty); - - bool result = AddMembersToTypeClass( - package_name_to_generate, package.get(), type.get(), class_def.get()); - if (!result) { + std::unique_ptr<ClassDefinition> class_def = util::make_unique<ClassDefinition>( + ToString(type->type), ClassQualifier::kStatic, force_creation_if_empty); + if (!ProcessType(package_name_to_generate, *package, *type, class_def.get(), + rewrite_method.get())) { return false; } if (type->type == ResourceType::kAttr) { // Also include private attributes in this same class. - ResourceTableType* priv_type = - package->FindType(ResourceType::kAttrPrivate); + const ResourceTableType* priv_type = package->FindType(ResourceType::kAttrPrivate); if (priv_type) { - result = - AddMembersToTypeClass(package_name_to_generate, package.get(), - priv_type, class_def.get()); - if (!result) { + if (!ProcessType(package_name_to_generate, *package, *priv_type, class_def.get(), + rewrite_method.get())) { return false; } } @@ -571,23 +571,23 @@ bool JavaClassGenerator::Generate(const StringPiece& package_name_to_generate, if (type->type == ResourceType::kStyleable && options_.types == JavaClassGeneratorOptions::SymbolTypes::kPublic) { // When generating a public R class, we don't want Styleable to be part - // of the API. - // It is only emitted for documentation purposes. + // of the API. It is only emitted for documentation purposes. class_def->GetCommentBuilder()->AppendComment("@doconly"); } - AppendJavaDocAnnotations(options_.javadoc_annotations, - class_def->GetCommentBuilder()); + AppendJavaDocAnnotations(options_.javadoc_annotations, class_def->GetCommentBuilder()); r_class.AddMember(std::move(class_def)); } } - AppendJavaDocAnnotations(options_.javadoc_annotations, - r_class.GetCommentBuilder()); + if (rewrite_method != nullptr) { + r_class.AddMember(std::move(rewrite_method)); + } + + AppendJavaDocAnnotations(options_.javadoc_annotations, r_class.GetCommentBuilder()); - if (!ClassDefinition::WriteJavaFile(&r_class, out_package_name, - options_.use_final, out)) { + if (!ClassDefinition::WriteJavaFile(&r_class, out_package_name, options_.use_final, out)) { return false; } diff --git a/tools/aapt2/java/JavaClassGenerator.h b/tools/aapt2/java/JavaClassGenerator.h index 5cf556ea5707..178f558ac2ec 100644 --- a/tools/aapt2/java/JavaClassGenerator.h +++ b/tools/aapt2/java/JavaClassGenerator.h @@ -25,19 +25,22 @@ #include "ResourceTable.h" #include "ResourceValues.h" #include "process/IResourceTableConsumer.h" +#include "process/SymbolTable.h" namespace aapt { class AnnotationProcessor; class ClassDefinition; +class MethodDefinition; struct JavaClassGeneratorOptions { - /* - * Specifies whether to use the 'final' modifier - * on resource entries. Default is true. - */ + // Specifies whether to use the 'final' modifier on resource entries. Default is true. bool use_final = true; + // Whether to generate code to rewrite the package ID of resources. + // Implies use_final == true. Default is false. + bool generate_rewrite_callback = false; + enum class SymbolTypes { kAll, kPublicPrivate, @@ -46,47 +49,54 @@ struct JavaClassGeneratorOptions { SymbolTypes types = SymbolTypes::kAll; - /** - * A list of JavaDoc annotations to add to the comments of all generated - * classes. - */ + // A list of JavaDoc annotations to add to the comments of all generated classes. std::vector<std::string> javadoc_annotations; }; -/* - * Generates the R.java file for a resource table. - */ +// Generates the R.java file for a resource table. class JavaClassGenerator { public: JavaClassGenerator(IAaptContext* context, ResourceTable* table, const JavaClassGeneratorOptions& options); - /* - * Writes the R.java file to `out`. Only symbols belonging to `package` are - * written. - * All symbols technically belong to a single package, but linked libraries - * will - * have their names mangled, denoting that they came from a different package. - * We need to generate these symbols in a separate file. - * Returns true on success. - */ - bool Generate(const android::StringPiece& packageNameToGenerate, std::ostream* out); + // Writes the R.java file to `out`. Only symbols belonging to `package` are written. + // All symbols technically belong to a single package, but linked libraries will + // have their names mangled, denoting that they came from a different package. + // We need to generate these symbols in a separate file. Returns true on success. + bool Generate(const android::StringPiece& package_name_to_generate, std::ostream* out); - bool Generate(const android::StringPiece& packageNameToGenerate, - const android::StringPiece& outputPackageName, std::ostream* out); + bool Generate(const android::StringPiece& package_name_to_generate, + const android::StringPiece& output_package_name, std::ostream* out); const std::string& getError() const; private: - bool AddMembersToTypeClass(const android::StringPiece& packageNameToGenerate, - const ResourceTablePackage* package, const ResourceTableType* type, - ClassDefinition* outTypeClassDef); - - void AddMembersToStyleableClass(const android::StringPiece& packageNameToGenerate, - const std::string& entryName, const Styleable* styleable, - ClassDefinition* outStyleableClassDef); - bool SkipSymbol(SymbolState state); + bool SkipSymbol(const Maybe<SymbolTable::Symbol>& symbol); + + // Returns the unmangled resource entry name if the unmangled package is the same as + // package_name_to_generate. Returns nothing if the resource should be skipped. + Maybe<std::string> UnmangleResource(const android::StringPiece& package_name, + const android::StringPiece& package_name_to_generate, + const ResourceEntry& entry); + + bool ProcessType(const android::StringPiece& package_name_to_generate, + const ResourceTablePackage& package, const ResourceTableType& type, + ClassDefinition* out_type_class_def, MethodDefinition* out_rewrite_method_def); + + // Writes a resource to the R.java file, optionally writing out a rewrite rule for its package + // ID if `out_rewrite_method` is not nullptr. + void ProcessResource(const ResourceNameRef& name, const ResourceId& id, + const ResourceEntry& entry, ClassDefinition* out_class_def, + MethodDefinition* out_rewrite_method); + + // 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, + const Styleable& styleable, + const android::StringPiece& package_name_to_generate, + ClassDefinition* out_class_def, MethodDefinition* out_rewrite_method); IAaptContext* context_; ResourceTable* table_; diff --git a/tools/aapt2/java/JavaClassGenerator_test.cpp b/tools/aapt2/java/JavaClassGenerator_test.cpp index 55c5cb26892f..bcb2d4f42eda 100644 --- a/tools/aapt2/java/JavaClassGenerator_test.cpp +++ b/tools/aapt2/java/JavaClassGenerator_test.cpp @@ -293,8 +293,7 @@ TEST(JavaClassGeneratorTest, CommentsForSimpleResourcesArePresent) { TEST(JavaClassGeneratorTest, CommentsForEnumAndFlagAttributesArePresent) {} -TEST(JavaClassGeneratorTest, - CommentsForStyleablesAndNestedAttributesArePresent) { +TEST(JavaClassGeneratorTest, CommentsForStyleablesAndNestedAttributesArePresent) { Attribute attr(false); attr.SetComment(StringPiece("This is an attribute")); @@ -364,4 +363,33 @@ TEST(JavaClassGeneratorTest, CommentsForRemovedAttributesAreNotPresentInClass) { EXPECT_EQ(std::string::npos, actual.find("removed", pos + 1)); } +TEST(JavaClassGeneratorTest, GenerateOnResourcesLoadedCallbackForSharedLibrary) { + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .SetPackageId("android", 0x00) + .AddValue("android:attr/foo", ResourceId(0x00010000), util::make_unique<Attribute>(false)) + .AddValue("android:id/foo", ResourceId(0x00020000), util::make_unique<Id>()) + .AddValue( + "android:style/foo", ResourceId(0x00030000), + test::StyleBuilder() + .AddItem("android:attr/foo", ResourceId(0x00010000), util::make_unique<Id>()) + .Build()) + .Build(); + + std::unique_ptr<IAaptContext> context = + test::ContextBuilder().SetPackageId(0x00).SetCompilationPackage("android").Build(); + + JavaClassGeneratorOptions options; + options.use_final = false; + options.generate_rewrite_callback = true; + JavaClassGenerator generator(context.get(), table.get(), options); + + std::stringstream out; + ASSERT_TRUE(generator.Generate("android", &out)); + + std::string actual = out.str(); + + EXPECT_NE(std::string::npos, actual.find("onResourcesLoaded")); +} + } // namespace aapt diff --git a/tools/aapt2/java/ManifestClassGenerator.cpp b/tools/aapt2/java/ManifestClassGenerator.cpp index de8e59aed6da..f49e4985fcf1 100644 --- a/tools/aapt2/java/ManifestClassGenerator.cpp +++ b/tools/aapt2/java/ManifestClassGenerator.cpp @@ -100,11 +100,9 @@ std::unique_ptr<ClassDefinition> GenerateManifestClass(IDiagnostics* diag, } std::unique_ptr<ClassDefinition> permission_class = - util::make_unique<ClassDefinition>("permission", ClassQualifier::Static, - false); + util::make_unique<ClassDefinition>("permission", ClassQualifier::kStatic, false); std::unique_ptr<ClassDefinition> permission_group_class = - util::make_unique<ClassDefinition>("permission_group", - ClassQualifier::Static, false); + util::make_unique<ClassDefinition>("permission_group", ClassQualifier::kStatic, false); bool error = false; std::vector<xml::Element*> children = el->GetChildElements(); @@ -125,8 +123,7 @@ std::unique_ptr<ClassDefinition> GenerateManifestClass(IDiagnostics* diag, } std::unique_ptr<ClassDefinition> manifest_class = - util::make_unique<ClassDefinition>("Manifest", ClassQualifier::None, - false); + util::make_unique<ClassDefinition>("Manifest", ClassQualifier::kNone, false); manifest_class->AddMember(std::move(permission_class)); manifest_class->AddMember(std::move(permission_group_class)); return manifest_class; diff --git a/tools/aapt2/link/Link.cpp b/tools/aapt2/link/Link.cpp index f07e20bbc78a..016246131f5f 100644 --- a/tools/aapt2/link/Link.cpp +++ b/tools/aapt2/link/Link.cpp @@ -59,7 +59,16 @@ using ::google::protobuf::io::CopyingOutputStreamAdaptor; namespace aapt { +// The type of package to build. +enum class PackageType { + kApp, + kSharedLib, + kStaticLib, +}; + struct LinkOptions { + PackageType package_type = PackageType::kApp; + std::string output_path; std::string manifest_path; std::vector<std::string> include_paths; @@ -87,7 +96,6 @@ struct LinkOptions { std::unordered_set<std::string> extensions_to_not_compress; // Static lib options. - bool static_lib = false; bool no_static_lib_packages = false; // AndroidManifest.xml massaging options. @@ -111,7 +119,7 @@ struct LinkOptions { class LinkContext : public IAaptContext { public: - LinkContext() : name_mangler_({}) {} + LinkContext() : name_mangler_({}), symbols_(&name_mangler_) {} IDiagnostics* GetDiagnostics() override { return &diagnostics_; } @@ -684,14 +692,13 @@ class LinkCommand { // First try to load the file as a static lib. std::string error_str; - std::unique_ptr<ResourceTable> static_include = - LoadStaticLibrary(path, &error_str); - if (static_include) { - if (!options_.static_lib) { - // Can't include static libraries when not building a static library. + std::unique_ptr<ResourceTable> include_static = LoadStaticLibrary(path, &error_str); + if (include_static) { + if (options_.package_type != PackageType::kStaticLib) { + // Can't include static libraries when not building a static library (they have no IDs + // assigned). context_->GetDiagnostics()->Error( - DiagMessage(path) - << "can't include static library when building app"); + DiagMessage(path) << "can't include static library when not building a static lib"); return false; } @@ -699,16 +706,15 @@ class LinkCommand { // package of this // table to our compilation package. if (options_.no_static_lib_packages) { - if (ResourceTablePackage* pkg = - static_include->FindPackageById(0x7f)) { + if (ResourceTablePackage* pkg = include_static->FindPackageById(0x7f)) { pkg->name = context_->GetCompilationPackage(); } } context_->GetExternalSymbols()->AppendSource( - util::make_unique<ResourceTableSymbolSource>(static_include.get())); + util::make_unique<ResourceTableSymbolSource>(include_static.get())); - static_table_includes_.push_back(std::move(static_include)); + static_table_includes_.push_back(std::move(include_static)); } else if (!error_str.empty()) { // We had an error with reading, so fail. @@ -717,12 +723,19 @@ class LinkCommand { } if (!asset_source->AddAssetPath(path)) { - context_->GetDiagnostics()->Error(DiagMessage(path) - << "failed to load include path"); + context_->GetDiagnostics()->Error(DiagMessage(path) << "failed to load include path"); return false; } } + // Capture the shared libraries so that the final resource table can be properly flattened + // with support for shared libraries. + for (auto& entry : asset_source->GetAssignedPackageIds()) { + if (entry.first > 0x01 && entry.first < 0x7f) { + final_table_.included_packages_[entry.first] = entry.second; + } + } + context_->GetExternalSymbols()->AppendSource(std::move(asset_source)); return true; } @@ -1402,7 +1415,7 @@ class LinkCommand { */ bool WriteApk(IArchiveWriter* writer, proguard::KeepSet* keep_set, xml::XmlResource* manifest, ResourceTable* table) { - const bool keep_raw_values = options_.static_lib; + const bool keep_raw_values = options_.package_type == PackageType::kStaticLib; bool result = FlattenXml(manifest, "AndroidManifest.xml", {}, keep_raw_values, writer, context_); if (!result) { @@ -1422,25 +1435,21 @@ class LinkCommand { file_flattener_options.update_proguard_spec = static_cast<bool>(options_.generate_proguard_rules_path); - ResourceFileFlattener file_flattener(file_flattener_options, context_, - keep_set); + ResourceFileFlattener file_flattener(file_flattener_options, context_, keep_set); if (!file_flattener.Flatten(table, writer)) { - context_->GetDiagnostics()->Error(DiagMessage() - << "failed linking file resources"); + context_->GetDiagnostics()->Error(DiagMessage() << "failed linking file resources"); return false; } - if (options_.static_lib) { + if (options_.package_type == PackageType::kStaticLib) { if (!FlattenTableToPb(table, writer)) { - context_->GetDiagnostics()->Error( - DiagMessage() << "failed to write resources.arsc.flat"); + context_->GetDiagnostics()->Error(DiagMessage() << "failed to write resources.arsc.flat"); return false; } } else { if (!FlattenTable(table, writer)) { - context_->GetDiagnostics()->Error(DiagMessage() - << "failed to write resources.arsc"); + context_->GetDiagnostics()->Error(DiagMessage() << "failed to write resources.arsc"); return false; } } @@ -1484,7 +1493,9 @@ class LinkCommand { context_->SetNameManglerPolicy( NameManglerPolicy{context_->GetCompilationPackage()}); - if (context_->GetCompilationPackage() == "android") { + if (options_.package_type == PackageType::kSharedLib) { + context_->SetPackageId(0x00); + } else if (context_->GetCompilationPackage() == "android") { context_->SetPackageId(0x01); } else { context_->SetPackageId(0x7f); @@ -1527,7 +1538,7 @@ class LinkCommand { return 1; } - if (!options_.static_lib) { + if (options_.package_type != PackageType::kStaticLib) { PrivateAttributeMover mover; if (!mover.Consume(context_, &final_table_)) { context_->GetDiagnostics()->Error( @@ -1538,8 +1549,7 @@ class LinkCommand { // Assign IDs if we are building a regular app. IdAssigner id_assigner(&options_.stable_id_map); if (!id_assigner.Consume(context_, &final_table_)) { - context_->GetDiagnostics()->Error(DiagMessage() - << "failed assigning IDs"); + context_->GetDiagnostics()->Error(DiagMessage() << "failed assigning IDs"); return 1; } @@ -1586,17 +1596,15 @@ class LinkCommand { return 1; } - if (options_.static_lib) { + if (options_.package_type == PackageType::kStaticLib) { if (!options_.products.empty()) { - context_->GetDiagnostics() - ->Warn(DiagMessage() - << "can't select products when building static library"); + context_->GetDiagnostics()->Warn(DiagMessage() + << "can't select products when building static library"); } } else { ProductFilter product_filter(options_.products); if (!product_filter.Consume(context_, &final_table_)) { - context_->GetDiagnostics()->Error(DiagMessage() - << "failed stripping products"); + context_->GetDiagnostics()->Error(DiagMessage() << "failed stripping products"); return 1; } } @@ -1610,7 +1618,7 @@ class LinkCommand { } } - if (!options_.static_lib && context_->GetMinSdkVersion() > 0) { + if (options_.package_type != PackageType::kStaticLib && context_->GetMinSdkVersion() > 0) { if (context_->IsVerbose()) { context_->GetDiagnostics()->Note( DiagMessage() << "collapsing resource versions for minimum SDK " @@ -1626,8 +1634,7 @@ class LinkCommand { if (!options_.no_resource_deduping) { ResourceDeduper deduper; if (!deduper.Consume(context_, &final_table_)) { - context_->GetDiagnostics()->Error(DiagMessage() - << "failed deduping resources"); + context_->GetDiagnostics()->Error(DiagMessage() << "failed deduping resources"); return 1; } } @@ -1635,12 +1642,11 @@ class LinkCommand { proguard::KeepSet proguard_keep_set; proguard::KeepSet proguard_main_dex_keep_set; - if (options_.static_lib) { + if (options_.package_type == PackageType::kStaticLib) { if (options_.table_splitter_options.config_filter != nullptr || !options_.table_splitter_options.preferred_densities.empty()) { - context_->GetDiagnostics() - ->Warn(DiagMessage() - << "can't strip resources when building static library"); + context_->GetDiagnostics()->Warn(DiagMessage() + << "can't strip resources when building static library"); } } else { // Adjust the SplitConstraints so that their SDK version is stripped if it @@ -1778,8 +1784,13 @@ class LinkCommand { options.types = JavaClassGeneratorOptions::SymbolTypes::kAll; options.javadoc_annotations = options_.javadoc_annotations; - if (options_.static_lib || options_.generate_non_final_ids) { + if (options_.package_type == PackageType::kStaticLib || options_.generate_non_final_ids) { + options.use_final = false; + } + + if (options_.package_type == PackageType::kSharedLib) { options.use_final = false; + options.generate_rewrite_callback = true; } const StringPiece actual_package = context_->GetCompilationPackage(); @@ -1850,9 +1861,11 @@ class LinkCommand { std::vector<std::unique_ptr<io::IFileCollection>> collections_; // A vector of ResourceTables. This is here to retain ownership, so that the - // SymbolTable - // can use these. + // SymbolTable can use these. std::vector<std::unique_ptr<ResourceTable>> static_table_includes_; + + // The set of shared libraries being used, mapping their assigned package ID to package name. + std::map<size_t, std::string> shared_libs_; }; int Link(const std::vector<StringPiece>& args) { @@ -1866,6 +1879,8 @@ int Link(const std::vector<StringPiece>& args) { bool legacy_x_flag = false; bool require_localization = false; bool verbose = false; + bool shared_lib = false; + bool static_lib = false; Maybe<std::string> stable_id_file_path; std::vector<std::string> split_args; Flags flags = @@ -1942,7 +1957,8 @@ int Link(const std::vector<StringPiece>& args) { "Version name to inject into the AndroidManifest.xml " "if none is present", &options.manifest_fixer_options.version_name_default) - .OptionalSwitch("--static-lib", "Generate a static Android library", &options.static_lib) + .OptionalSwitch("--shared-lib", "Generates a shared Android runtime library", &shared_lib) + .OptionalSwitch("--static-lib", "Generate a static Android library", &static_lib) .OptionalSwitch("--no-static-lib-packages", "Merge all library resources under the app's package", &options.no_static_lib_packages) @@ -2096,7 +2112,19 @@ int Link(const std::vector<StringPiece>& args) { options.table_splitter_options.preferred_densities.push_back(preferred_density_config.density); } - if (!options.static_lib && stable_id_file_path) { + if (shared_lib && static_lib) { + context.GetDiagnostics()->Error(DiagMessage() + << "only one of --shared-lib and --static-lib can be defined"); + return 1; + } + + if (shared_lib) { + options.package_type = PackageType::kSharedLib; + } else if (static_lib) { + options.package_type = PackageType::kStaticLib; + } + + if (options.package_type != PackageType::kStaticLib && stable_id_file_path) { if (!LoadStableIdMap(context.GetDiagnostics(), stable_id_file_path.value(), &options.stable_id_map)) { return 1; @@ -2122,7 +2150,7 @@ int Link(const std::vector<StringPiece>& args) { } // Turn off auto versioning for static-libs. - if (options.static_lib) { + if (options.package_type == PackageType::kStaticLib) { options.no_auto_version = true; options.no_version_vectors = true; options.no_version_transitions = true; diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp index b4cf4f8d7f54..d5c0dc4087ba 100644 --- a/tools/aapt2/link/ManifestFixer.cpp +++ b/tools/aapt2/link/ManifestFixer.cpp @@ -54,16 +54,14 @@ static bool NameIsJavaClassName(xml::Element* el, xml::Attribute* attr, return true; } -static bool OptionalNameIsJavaClassName(xml::Element* el, - SourcePathDiagnostics* diag) { +static bool OptionalNameIsJavaClassName(xml::Element* el, SourcePathDiagnostics* diag) { if (xml::Attribute* attr = el->FindAttribute(xml::kSchemaAndroid, "name")) { return NameIsJavaClassName(el, attr, diag); } return true; } -static bool RequiredNameIsJavaClassName(xml::Element* el, - SourcePathDiagnostics* diag) { +static bool RequiredNameIsJavaClassName(xml::Element* el, SourcePathDiagnostics* diag) { if (xml::Attribute* attr = el->FindAttribute(xml::kSchemaAndroid, "name")) { return NameIsJavaClassName(el, attr, diag); } @@ -72,6 +70,15 @@ static bool RequiredNameIsJavaClassName(xml::Element* el, return false; } +static bool RequiredNameIsJavaPackage(xml::Element* el, SourcePathDiagnostics* diag) { + if (xml::Attribute* attr = el->FindAttribute(xml::kSchemaAndroid, "name")) { + return util::IsJavaPackageName(attr->value); + } + diag->Error(DiagMessage(el->line_number) + << "<" << el->name << "> is missing attribute 'android:name'"); + return false; +} + static bool VerifyManifest(xml::Element* el, SourcePathDiagnostics* diag) { xml::Attribute* attr = el->FindAttribute({}, "package"); if (!attr) { @@ -263,7 +270,8 @@ bool ManifestFixer::BuildRules(xml::XmlActionExecutor* executor, xml::XmlNodeAction& application_action = manifest_action["application"]; application_action.Action(OptionalNameIsJavaClassName); - application_action["uses-library"]; + application_action["uses-library"].Action(RequiredNameIsJavaPackage); + application_action["library"].Action(RequiredNameIsJavaPackage); application_action["meta-data"] = meta_data_action; application_action["activity"] = component_action; application_action["activity-alias"] = component_action; diff --git a/tools/aapt2/link/PrivateAttributeMover.cpp b/tools/aapt2/link/PrivateAttributeMover.cpp index cc07a6e1925b..eee4b60c1769 100644 --- a/tools/aapt2/link/PrivateAttributeMover.cpp +++ b/tools/aapt2/link/PrivateAttributeMover.cpp @@ -26,11 +26,9 @@ namespace aapt { template <typename InputContainer, typename OutputIterator, typename Predicate> -OutputIterator move_if(InputContainer& input_container, OutputIterator result, - Predicate pred) { +OutputIterator move_if(InputContainer& input_container, OutputIterator result, Predicate pred) { const auto last = input_container.end(); - auto new_end = - std::find_if(input_container.begin(), input_container.end(), pred); + auto new_end = std::find_if(input_container.begin(), input_container.end(), pred); if (new_end == last) { return result; } @@ -57,8 +55,7 @@ OutputIterator move_if(InputContainer& input_container, OutputIterator result, return result; } -bool PrivateAttributeMover::Consume(IAaptContext* context, - ResourceTable* table) { +bool PrivateAttributeMover::Consume(IAaptContext* context, ResourceTable* table) { for (auto& package : table->packages) { ResourceTableType* type = package->FindType(ResourceType::kAttr); if (!type) { @@ -68,18 +65,24 @@ bool PrivateAttributeMover::Consume(IAaptContext* context, if (type->symbol_status.state != SymbolState::kPublic) { // No public attributes, so we can safely leave these private attributes // where they are. - return true; + continue; } - ResourceTableType* priv_attr_type = - package->FindOrCreateType(ResourceType::kAttrPrivate); - CHECK(priv_attr_type->entries.empty()); + std::vector<std::unique_ptr<ResourceEntry>> private_attr_entries; - move_if(type->entries, std::back_inserter(priv_attr_type->entries), + move_if(type->entries, std::back_inserter(private_attr_entries), [](const std::unique_ptr<ResourceEntry>& entry) -> bool { return entry->symbol_status.state != SymbolState::kPublic; }); - break; + + if (private_attr_entries.empty()) { + // No private attributes. + continue; + } + + ResourceTableType* priv_attr_type = package->FindOrCreateType(ResourceType::kAttrPrivate); + CHECK(priv_attr_type->entries.empty()); + priv_attr_type->entries = std::move(private_attr_entries); } return true; } diff --git a/tools/aapt2/link/PrivateAttributeMover_test.cpp b/tools/aapt2/link/PrivateAttributeMover_test.cpp index 90c4922625be..7fcf6e7ab5cd 100644 --- a/tools/aapt2/link/PrivateAttributeMover_test.cpp +++ b/tools/aapt2/link/PrivateAttributeMover_test.cpp @@ -54,8 +54,7 @@ TEST(PrivateAttributeMoverTest, MovePrivateAttributes) { EXPECT_NE(type->FindEntry("privateB"), nullptr); } -TEST(PrivateAttributeMoverTest, - LeavePrivateAttributesWhenNoPublicAttributesDefined) { +TEST(PrivateAttributeMoverTest, LeavePrivateAttributesWhenNoPublicAttributesDefined) { std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() @@ -77,4 +76,23 @@ TEST(PrivateAttributeMoverTest, ASSERT_EQ(type, nullptr); } +TEST(PrivateAttributeMoverTest, DoNotCreatePrivateAttrsIfNoneExist) { + std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .AddSimple("android:attr/pub") + .SetSymbolState("android:attr/pub", ResourceId(0x01010000), SymbolState::kPublic) + .Build(); + + ResourceTablePackage* package = table->FindPackage("android"); + ASSERT_NE(nullptr, package); + + ASSERT_EQ(nullptr, package->FindType(ResourceType::kAttrPrivate)); + + PrivateAttributeMover mover; + ASSERT_TRUE(mover.Consume(context.get(), table.get())); + + ASSERT_EQ(nullptr, package->FindType(ResourceType::kAttrPrivate)); +} + } // namespace aapt diff --git a/tools/aapt2/link/ReferenceLinker.cpp b/tools/aapt2/link/ReferenceLinker.cpp index ea68b61f89c4..4a4282637b3b 100644 --- a/tools/aapt2/link/ReferenceLinker.cpp +++ b/tools/aapt2/link/ReferenceLinker.cpp @@ -96,10 +96,8 @@ class ReferenceLinkerVisitor : public ValueVisitor { // Find the attribute in the symbol table and check if it is visible from // this callsite. - const SymbolTable::Symbol* symbol = - ReferenceLinker::ResolveAttributeCheckVisibility( - transformed_reference, context_->GetNameMangler(), symbols_, - callsite_, &err_str); + const SymbolTable::Symbol* symbol = ReferenceLinker::ResolveAttributeCheckVisibility( + transformed_reference, symbols_, callsite_, &err_str); if (symbol) { // Assign our style key the correct ID. // The ID may not exist. @@ -225,12 +223,10 @@ bool ReferenceLinker::IsSymbolVisible(const SymbolTable::Symbol& symbol, return true; } -const SymbolTable::Symbol* ReferenceLinker::ResolveSymbol( - const Reference& reference, NameMangler* mangler, SymbolTable* symbols) { +const SymbolTable::Symbol* ReferenceLinker::ResolveSymbol(const Reference& reference, + SymbolTable* symbols) { if (reference.name) { - Maybe<ResourceName> mangled = mangler->MangleName(reference.name.value()); - return symbols->FindByName(mangled ? mangled.value() - : reference.name.value()); + return symbols->FindByName(reference.name.value()); } else if (reference.id) { return symbols->FindById(reference.id.value()); } else { @@ -238,11 +234,11 @@ const SymbolTable::Symbol* ReferenceLinker::ResolveSymbol( } } -const SymbolTable::Symbol* ReferenceLinker::ResolveSymbolCheckVisibility( - const Reference& reference, NameMangler* name_mangler, SymbolTable* symbols, - CallSite* callsite, std::string* out_error) { - const SymbolTable::Symbol* symbol = - ResolveSymbol(reference, name_mangler, symbols); +const SymbolTable::Symbol* ReferenceLinker::ResolveSymbolCheckVisibility(const Reference& reference, + SymbolTable* symbols, + CallSite* callsite, + std::string* out_error) { + const SymbolTable::Symbol* symbol = ResolveSymbol(reference, symbols); if (!symbol) { if (out_error) *out_error = "not found"; return nullptr; @@ -256,10 +252,9 @@ const SymbolTable::Symbol* ReferenceLinker::ResolveSymbolCheckVisibility( } const SymbolTable::Symbol* ReferenceLinker::ResolveAttributeCheckVisibility( - const Reference& reference, NameMangler* name_mangler, SymbolTable* symbols, - CallSite* callsite, std::string* out_error) { - const SymbolTable::Symbol* symbol = ResolveSymbolCheckVisibility( - reference, name_mangler, symbols, callsite, out_error); + const Reference& reference, SymbolTable* symbols, CallSite* callsite, std::string* out_error) { + const SymbolTable::Symbol* symbol = + ResolveSymbolCheckVisibility(reference, symbols, callsite, out_error); if (!symbol) { return nullptr; } @@ -271,11 +266,11 @@ const SymbolTable::Symbol* ReferenceLinker::ResolveAttributeCheckVisibility( return symbol; } -Maybe<xml::AaptAttribute> ReferenceLinker::CompileXmlAttribute( - const Reference& reference, NameMangler* name_mangler, SymbolTable* symbols, - CallSite* callsite, std::string* out_error) { - const SymbolTable::Symbol* symbol = - ResolveSymbol(reference, name_mangler, symbols); +Maybe<xml::AaptAttribute> ReferenceLinker::CompileXmlAttribute(const Reference& reference, + SymbolTable* symbols, + CallSite* callsite, + std::string* out_error) { + const SymbolTable::Symbol* symbol = ResolveSymbol(reference, symbols); if (!symbol) { if (out_error) *out_error = "not found"; return {}; @@ -311,13 +306,11 @@ bool ReferenceLinker::LinkReference(Reference* reference, IAaptContext* context, CHECK(reference->name || reference->id); Reference transformed_reference = *reference; - TransformReferenceFromNamespace(decls, context->GetCompilationPackage(), - &transformed_reference); + TransformReferenceFromNamespace(decls, context->GetCompilationPackage(), &transformed_reference); std::string err_str; - const SymbolTable::Symbol* s = ResolveSymbolCheckVisibility( - transformed_reference, context->GetNameMangler(), symbols, callsite, - &err_str); + const SymbolTable::Symbol* s = + ResolveSymbolCheckVisibility(transformed_reference, symbols, callsite, &err_str); if (s) { // The ID may not exist. This is fine because of the possibility of building // against libraries without assigned IDs. diff --git a/tools/aapt2/link/ReferenceLinker.h b/tools/aapt2/link/ReferenceLinker.h index bdabf249709d..2d18c49d3687 100644 --- a/tools/aapt2/link/ReferenceLinker.h +++ b/tools/aapt2/link/ReferenceLinker.h @@ -51,18 +51,17 @@ class ReferenceLinker : public IResourceTableConsumer { * Performs name mangling and looks up the resource in the symbol table. * Returns nullptr if the symbol was not found. */ - static const SymbolTable::Symbol* ResolveSymbol(const Reference& reference, - NameMangler* mangler, - SymbolTable* symbols); + static const SymbolTable::Symbol* ResolveSymbol(const Reference& reference, SymbolTable* symbols); /** * Performs name mangling and looks up the resource in the symbol table. If * the symbol is not visible by the reference at the callsite, nullptr is * returned. out_error holds the error message. */ - static const SymbolTable::Symbol* ResolveSymbolCheckVisibility( - const Reference& reference, NameMangler* name_mangler, - SymbolTable* symbols, CallSite* callsite, std::string* out_error); + static const SymbolTable::Symbol* ResolveSymbolCheckVisibility(const Reference& reference, + SymbolTable* symbols, + CallSite* callsite, + std::string* out_error); /** * Same as resolveSymbolCheckVisibility(), but also makes sure the symbol is @@ -70,18 +69,19 @@ class ReferenceLinker : public IResourceTableConsumer { * That is, the return value will have a non-null value for * ISymbolTable::Symbol::attribute. */ - static const SymbolTable::Symbol* ResolveAttributeCheckVisibility( - const Reference& reference, NameMangler* name_mangler, - SymbolTable* symbols, CallSite* callsite, std::string* out_error); + static const SymbolTable::Symbol* ResolveAttributeCheckVisibility(const Reference& reference, + SymbolTable* symbols, + CallSite* callsite, + std::string* out_error); /** * Resolves the attribute reference and returns an xml::AaptAttribute if * successful. * If resolution fails, outError holds the error message. */ - static Maybe<xml::AaptAttribute> CompileXmlAttribute( - const Reference& reference, NameMangler* name_mangler, - SymbolTable* symbols, CallSite* callsite, std::string* out_error); + static Maybe<xml::AaptAttribute> CompileXmlAttribute(const Reference& reference, + SymbolTable* symbols, CallSite* callsite, + std::string* out_error); /** * Writes the resource name to the DiagMessage, using the diff --git a/tools/aapt2/link/XmlReferenceLinker.cpp b/tools/aapt2/link/XmlReferenceLinker.cpp index 1dbe53c02ecf..b839862a0689 100644 --- a/tools/aapt2/link/XmlReferenceLinker.cpp +++ b/tools/aapt2/link/XmlReferenceLinker.cpp @@ -107,8 +107,8 @@ class XmlVisitor : public xml::PackageAwareVisitor { attr_ref.private_reference = maybe_package.value().private_namespace; std::string err_str; - attr.compiled_attribute = ReferenceLinker::CompileXmlAttribute( - attr_ref, context_->GetNameMangler(), symbols_, callsite_, &err_str); + attr.compiled_attribute = + ReferenceLinker::CompileXmlAttribute(attr_ref, symbols_, callsite_, &err_str); if (!attr.compiled_attribute) { context_->GetDiagnostics()->Error(DiagMessage(source) << "attribute '" diff --git a/tools/aapt2/process/SymbolTable.cpp b/tools/aapt2/process/SymbolTable.cpp index 1a3da73b8ce0..5d75e76af035 100644 --- a/tools/aapt2/process/SymbolTable.cpp +++ b/tools/aapt2/process/SymbolTable.cpp @@ -16,10 +16,15 @@ #include "process/SymbolTable.h" +#include <iostream> + +#include "android-base/logging.h" +#include "android-base/stringprintf.h" #include "androidfw/AssetManager.h" #include "androidfw/ResourceTypes.h" #include "ConfigDescription.h" +#include "NameMangler.h" #include "Resource.h" #include "ResourceUtils.h" #include "ValueVisitor.h" @@ -45,25 +50,49 @@ void SymbolTable::PrependSource(std::unique_ptr<ISymbolSource> source) { } const SymbolTable::Symbol* SymbolTable::FindByName(const ResourceName& name) { - if (const std::shared_ptr<Symbol>& s = cache_.get(name)) { + const ResourceName* name_with_package = &name; + + // Fill in the package name if necessary. + // If there is no package in `name`, we will need to copy the ResourceName + // and store it somewhere; we use the Maybe<> class to reserve storage. + Maybe<ResourceName> name_with_package_impl; + if (name.package.empty()) { + name_with_package_impl = ResourceName(mangler_->GetTargetPackageName(), name.type, name.entry); + name_with_package = &name_with_package_impl.value(); + } + + // We store the name unmangled in the cache, so look it up as-is. + if (const std::shared_ptr<Symbol>& s = cache_.get(*name_with_package)) { return s.get(); } - // We did not find it in the cache, so look through the sources. + // The name was not found in the cache. Mangle it (if necessary) and find it in our sources. + // Again, here we use a Maybe<> object to reserve storage if we need to mangle. + const ResourceName* mangled_name = name_with_package; + Maybe<ResourceName> mangled_name_impl; + if (mangler_->ShouldMangle(name_with_package->package)) { + mangled_name_impl = mangler_->MangleName(*name_with_package); + mangled_name = &mangled_name_impl.value(); + } + for (auto& symbolSource : sources_) { - std::unique_ptr<Symbol> symbol = symbolSource->FindByName(name); + std::unique_ptr<Symbol> symbol = symbolSource->FindByName(*mangled_name); if (symbol) { // Take ownership of the symbol into a shared_ptr. We do this because - // LruCache - // doesn't support unique_ptr. - std::shared_ptr<Symbol> shared_symbol = - std::shared_ptr<Symbol>(symbol.release()); - cache_.put(name, shared_symbol); + // LruCache doesn't support unique_ptr. + std::shared_ptr<Symbol> shared_symbol(std::move(symbol)); + + // Since we look in the cache with the unmangled, but package prefixed + // name, we must put the same name into the cache. + cache_.put(*name_with_package, shared_symbol); if (shared_symbol->id) { // The symbol has an ID, so we can also cache this! id_cache_.put(shared_symbol->id.value(), shared_symbol); } + + // Returns the raw pointer. Callers are not expected to hold on to this + // between calls to Find*. return shared_symbol.get(); } } @@ -79,12 +108,13 @@ const SymbolTable::Symbol* SymbolTable::FindById(const ResourceId& id) { for (auto& symbolSource : sources_) { std::unique_ptr<Symbol> symbol = symbolSource->FindById(id); if (symbol) { - // Take ownership of the symbol into a shared_ptr. We do this because - // LruCache + // Take ownership of the symbol into a shared_ptr. We do this because LruCache // doesn't support unique_ptr. - std::shared_ptr<Symbol> shared_symbol = - std::shared_ptr<Symbol>(symbol.release()); + std::shared_ptr<Symbol> shared_symbol(std::move(symbol)); id_cache_.put(id, shared_symbol); + + // Returns the raw pointer. Callers are not expected to hold on to this + // between calls to Find*. return shared_symbol.get(); } } @@ -92,16 +122,12 @@ const SymbolTable::Symbol* SymbolTable::FindById(const ResourceId& id) { } const SymbolTable::Symbol* SymbolTable::FindByReference(const Reference& ref) { - // First try the ID. This is because when we lookup by ID, we only fill in the - // ID cache. - // Looking up by name fills in the name and ID cache. So a cache miss will - // cause a failed - // ID lookup, then a successful name lookup. Subsequent look ups will hit - // immediately + // First try the ID. This is because when we lookup by ID, we only fill in the ID cache. + // Looking up by name fills in the name and ID cache. So a cache miss will cause a failed + // ID lookup, then a successful name lookup. Subsequent look ups will hit immediately // because the ID is cached too. // - // If we looked up by name first, a cache miss would mean we failed to lookup - // by name, then + // If we looked up by name first, a cache miss would mean we failed to lookup by name, then // succeeded to lookup by ID. Subsequent lookups will miss then hit. const SymbolTable::Symbol* symbol = nullptr; if (ref.id) { @@ -120,25 +146,21 @@ std::unique_ptr<SymbolTable::Symbol> ResourceTableSymbolSource::FindByName( if (!result) { if (name.type == ResourceType::kAttr) { // Recurse and try looking up a private attribute. - return FindByName( - ResourceName(name.package, ResourceType::kAttrPrivate, name.entry)); + return FindByName(ResourceName(name.package, ResourceType::kAttrPrivate, name.entry)); } return {}; } ResourceTable::SearchResult sr = result.value(); - std::unique_ptr<SymbolTable::Symbol> symbol = - util::make_unique<SymbolTable::Symbol>(); + std::unique_ptr<SymbolTable::Symbol> symbol = util::make_unique<SymbolTable::Symbol>(); symbol->is_public = (sr.entry->symbol_status.state == SymbolState::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->id = ResourceId(sr.package->id.value(), sr.type->id.value(), sr.entry->id.value()); } - if (name.type == ResourceType::kAttr || - name.type == ResourceType::kAttrPrivate) { + if (name.type == ResourceType::kAttr || name.type == ResourceType::kAttrPrivate) { const ConfigDescription kDefaultConfig; ResourceConfigValue* config_value = sr.entry->FindValue(kDefaultConfig); if (config_value) { @@ -155,8 +177,18 @@ std::unique_ptr<SymbolTable::Symbol> ResourceTableSymbolSource::FindByName( bool AssetManagerSymbolSource::AddAssetPath(const StringPiece& path) { int32_t cookie = 0; - return assets_.addAssetPath(android::String8(path.data(), path.size()), - &cookie); + return assets_.addAssetPath(android::String8(path.data(), path.size()), &cookie); +} + +std::map<size_t, std::string> AssetManagerSymbolSource::GetAssignedPackageIds() const { + std::map<size_t, std::string> package_map; + const android::ResTable& table = assets_.getResources(false); + const size_t package_count = table.getBasePackageCount(); + for (size_t i = 0; i < package_count; i++) { + package_map[table.getBasePackageId(i)] = + util::Utf16ToUtf8(android::StringPiece16(table.getBasePackageName(i).string())); + } + return package_map; } static std::unique_ptr<SymbolTable::Symbol> LookupAttributeInTable( @@ -170,8 +202,7 @@ static std::unique_ptr<SymbolTable::Symbol> LookupAttributeInTable( } // We found a resource. - std::unique_ptr<SymbolTable::Symbol> s = - util::make_unique<SymbolTable::Symbol>(); + std::unique_ptr<SymbolTable::Symbol> s = util::make_unique<SymbolTable::Symbol>(); s->id = id; // Check to see if it is an attribute. @@ -204,8 +235,7 @@ static std::unique_ptr<SymbolTable::Symbol> LookupAttributeInTable( return nullptr; } - Maybe<ResourceName> parsed_name = - ResourceUtils::ToResourceName(entry_name); + Maybe<ResourceName> parsed_name = ResourceUtils::ToResourceName(entry_name); if (!parsed_name) { return nullptr; } @@ -246,8 +276,7 @@ std::unique_ptr<SymbolTable::Symbol> AssetManagerSymbolSource::FindByName( } if (s) { - s->is_public = - (type_spec_flags & android::ResTable_typeSpec::SPEC_PUBLIC) != 0; + s->is_public = (type_spec_flags & android::ResTable_typeSpec::SPEC_PUBLIC) != 0; return s; } return {}; @@ -282,8 +311,7 @@ std::unique_ptr<SymbolTable::Symbol> AssetManagerSymbolSource::FindById( } if (s) { - s->is_public = - (type_spec_flags & android::ResTable_typeSpec::SPEC_PUBLIC) != 0; + s->is_public = (type_spec_flags & android::ResTable_typeSpec::SPEC_PUBLIC) != 0; return s; } return {}; diff --git a/tools/aapt2/process/SymbolTable.h b/tools/aapt2/process/SymbolTable.h index cf597bb4e679..298da4d42c77 100644 --- a/tools/aapt2/process/SymbolTable.h +++ b/tools/aapt2/process/SymbolTable.h @@ -47,6 +47,7 @@ inline android::hash_t hash_type(const ResourceId& id) { } class ISymbolSource; +class NameMangler; class SymbolTable { public: @@ -72,25 +73,32 @@ class SymbolTable { bool is_public = false; }; - SymbolTable() : cache_(200), id_cache_(200) {} + SymbolTable(NameMangler* mangler) : mangler_(mangler), cache_(200), id_cache_(200) {} + // Appends a symbol source. The cache is not cleared since entries that + // have already been found would take precedence due to ordering. void AppendSource(std::unique_ptr<ISymbolSource> source); + + // Prepends a symbol source so that its symbols take precedence. This will + // cause the existing cache to be cleared. void PrependSource(std::unique_ptr<ISymbolSource> source); - /** - * Never hold on to the result between calls to FindByName or FindById. The - * results stored in a cache which may evict entries. - */ + // NOTE: Never hold on to the result between calls to FindByXXX. The + // results are stored in a cache which may evict entries on subsequent calls. const Symbol* FindByName(const ResourceName& name); + + // NOTE: Never hold on to the result between calls to FindByXXX. The + // results are stored in a cache which may evict entries on subsequent calls. const Symbol* FindById(const ResourceId& id); - /** - * Let's the ISymbolSource decide whether looking up by name or ID is faster, - * if both are available. - */ + // Let's the ISymbolSource decide whether looking up by name or ID is faster, + // if both are available. + // NOTE: Never hold on to the result between calls to FindByXXX. The + // results are stored in a cache which may evict entries on subsequent calls. const Symbol* FindByReference(const Reference& ref); private: + NameMangler* mangler_; std::vector<std::unique_ptr<ISymbolSource>> sources_; // We use shared_ptr because unique_ptr is not supported and @@ -155,6 +163,7 @@ class AssetManagerSymbolSource : public ISymbolSource { AssetManagerSymbolSource() = default; bool AddAssetPath(const android::StringPiece& path); + std::map<size_t, std::string> GetAssignedPackageIds() const; std::unique_ptr<SymbolTable::Symbol> FindByName( const ResourceName& name) override; diff --git a/tools/aapt2/process/SymbolTable_test.cpp b/tools/aapt2/process/SymbolTable_test.cpp index 9ea0786cbc8e..bba316f4b484 100644 --- a/tools/aapt2/process/SymbolTable_test.cpp +++ b/tools/aapt2/process/SymbolTable_test.cpp @@ -55,4 +55,19 @@ TEST(ResourceTableSymbolSourceTest, FindPrivateAttrSymbol) { EXPECT_NE(nullptr, s->attribute); } +TEST(SymbolTableTest, FindByName) { + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .AddSimple("com.android.app:id/foo") + .AddSimple("com.android.app:id/" + NameMangler::MangleEntry("com.android.lib", "foo")) + .Build(); + + NameMangler mangler(NameManglerPolicy{"com.android.app", {"com.android.lib"}}); + SymbolTable symbol_table(&mangler); + symbol_table.AppendSource(util::make_unique<ResourceTableSymbolSource>(table.get())); + + EXPECT_NE(nullptr, symbol_table.FindByName(test::ParseNameOrDie("id/foo"))); + EXPECT_NE(nullptr, symbol_table.FindByName(test::ParseNameOrDie("com.android.lib:id/foo"))); +} + } // namespace aapt diff --git a/tools/aapt2/readme.md b/tools/aapt2/readme.md index 8bc4e8c65fac..8fa12d00079a 100644 --- a/tools/aapt2/readme.md +++ b/tools/aapt2/readme.md @@ -1,7 +1,12 @@ # Android Asset Packaging Tool 2.0 (AAPT2) release notes +## Version 2.8 +### `aapt2 link ...` +- Adds shared library support. Build a shared library with the `--shared-lib` flag. + Build a client of a shared library by simply including it via `-I`. + ## Version 2.7 -### `aapt2 compile` +### `aapt2 compile ...` - Fixes bug where psuedolocalization auto-translated strings marked 'translateable="false"'. ## Version 2.6 diff --git a/tools/aapt2/test/Context.h b/tools/aapt2/test/Context.h index 63e5f1628581..557cd1b646e3 100644 --- a/tools/aapt2/test/Context.h +++ b/tools/aapt2/test/Context.h @@ -33,7 +33,7 @@ namespace test { class Context : public IAaptContext { public: - Context() = default; + Context() : name_mangler_({}), symbols_(&name_mangler_), min_sdk_version_(0) {} SymbolTable* GetExternalSymbols() override { return &symbols_; } @@ -63,9 +63,9 @@ class Context : public IAaptContext { Maybe<std::string> compilation_package_; Maybe<uint8_t> package_id_; StdErrDiagnostics diagnostics_; + NameMangler name_mangler_; SymbolTable symbols_; - NameMangler name_mangler_ = NameMangler({}); - int min_sdk_version_ = 0; + int min_sdk_version_; }; class ContextBuilder { diff --git a/tools/aapt2/unflatten/BinaryResourceParser.cpp b/tools/aapt2/unflatten/BinaryResourceParser.cpp index 7098fe9f7cb6..29a921c497c9 100644 --- a/tools/aapt2/unflatten/BinaryResourceParser.cpp +++ b/tools/aapt2/unflatten/BinaryResourceParser.cpp @@ -248,6 +248,12 @@ bool BinaryResourceParser::ParsePackage(const ResChunk_header* chunk) { } break; + case android::RES_TABLE_LIBRARY_TYPE: + if (!ParseLibrary(parser.chunk())) { + return false; + } + break; + default: context_->GetDiagnostics()->Warn( DiagMessage(source_) @@ -395,6 +401,21 @@ bool BinaryResourceParser::ParseType(const ResourceTablePackage* package, return true; } +bool BinaryResourceParser::ParseLibrary(const ResChunk_header* chunk) { + DynamicRefTable dynamic_ref_table; + if (dynamic_ref_table.load(reinterpret_cast<const ResTable_lib_header*>(chunk)) != NO_ERROR) { + return false; + } + + const KeyedVector<String16, uint8_t>& entries = dynamic_ref_table.entries(); + const size_t count = entries.size(); + for (size_t i = 0; i < count; i++) { + table_->included_packages_[entries.valueAt(i)] = + util::Utf16ToUtf8(StringPiece16(entries.keyAt(i).string())); + } + return true; +} + std::unique_ptr<Item> BinaryResourceParser::ParseValue( const ResourceNameRef& name, const ConfigDescription& config, const Res_value* value, uint16_t flags) { diff --git a/tools/aapt2/unflatten/BinaryResourceParser.h b/tools/aapt2/unflatten/BinaryResourceParser.h index dc668fdab7b6..e3dd80212f12 100644 --- a/tools/aapt2/unflatten/BinaryResourceParser.h +++ b/tools/aapt2/unflatten/BinaryResourceParser.h @@ -61,6 +61,7 @@ class BinaryResourceParser { bool ParseTypeSpec(const android::ResChunk_header* chunk); bool ParseType(const ResourceTablePackage* package, const android::ResChunk_header* chunk); + bool ParseLibrary(const android::ResChunk_header* chunk); std::unique_ptr<Item> ParseValue(const ResourceNameRef& name, const ConfigDescription& config, |