| /* | |
| * Copyright (C) 2015 The Android Open Source Project | |
| * | |
| * Licensed under the Apache License, Version 2.0 (the "License"); | |
| * you may not use this file except in compliance with the License. | |
| * You may obtain a copy of the License at | |
| * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | |
| * Unless required by applicable law or agreed to in writing, software | |
| * distributed under the License is distributed on an "AS IS" BASIS, | |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| * See the License for the specific language governing permissions and | |
| * limitations under the License. | |
| */ | |
| #include "link/TableMerger.h" | |
| #include "android-base/logging.h" | |
| #include "ResourceTable.h" | |
| #include "ResourceUtils.h" | |
| #include "ResourceValues.h" | |
| #include "trace/TraceBuffer.h" | |
| #include "ValueVisitor.h" | |
| #include "util/Util.h" | |
| using ::android::StringPiece; | |
| namespace aapt { | |
| TableMerger::TableMerger(IAaptContext* context, ResourceTable* out_table, | |
| const TableMergerOptions& options) | |
| : context_(context), main_table_(out_table), options_(options) { | |
| // Create the desired package that all tables will be merged into. | |
| main_package_ = main_table_->FindOrCreatePackage(context_->GetCompilationPackage()); | |
| CHECK(main_package_ != nullptr) << "package name or ID already taken"; | |
| } | |
| bool TableMerger::Merge(const Source& src, ResourceTable* table, bool overlay) { | |
| TRACE_CALL(); | |
| // We allow adding new resources if this is not an overlay, or if the options allow overlays | |
| // to add new resources. | |
| return MergeImpl(src, table, overlay, options_.auto_add_overlay || !overlay /*allow_new*/); | |
| } | |
| // This will merge packages with the same package name (or no package name). | |
| bool TableMerger::MergeImpl(const Source& src, ResourceTable* table, bool overlay, bool allow_new) { | |
| bool error = false; | |
| for (auto& package : table->packages) { | |
| // Only merge an empty package or the package we're building. | |
| // Other packages may exist, which likely contain attribute definitions. | |
| // This is because at compile time it is unknown if the attributes are | |
| // simply uses of the attribute or definitions. | |
| if (package->name.empty() || context_->GetCompilationPackage() == package->name) { | |
| // Merge here. Once the entries are merged and mangled, any references to them are still | |
| // valid. This is because un-mangled references are mangled, then looked up at resolution | |
| // time. Also, when linking, we convert references with no package name to use the compilation | |
| // package name. | |
| error |= !DoMerge(src, package.get(), false /*mangle*/, overlay, allow_new); | |
| } | |
| } | |
| return !error; | |
| } | |
| // This will merge and mangle resources from a static library. It is assumed that all FileReferences | |
| // have correctly set their io::IFile*. | |
| bool TableMerger::MergeAndMangle(const Source& src, const StringPiece& package_name, | |
| ResourceTable* table) { | |
| bool error = false; | |
| for (auto& package : table->packages) { | |
| // Warn of packages with an unrelated ID. | |
| if (package_name != package->name) { | |
| context_->GetDiagnostics()->Warn(DiagMessage(src) << "ignoring package " << package->name); | |
| continue; | |
| } | |
| bool mangle = package_name != context_->GetCompilationPackage(); | |
| merged_packages_.insert(package->name); | |
| error |= !DoMerge(src, package.get(), mangle, false /*overlay*/, true /*allow_new*/); | |
| } | |
| return !error; | |
| } | |
| static bool MergeType(IAaptContext* context, const Source& src, ResourceTableType* dst_type, | |
| ResourceTableType* src_type) { | |
| if (src_type->visibility_level >= dst_type->visibility_level) { | |
| // The incoming type's visibility is stronger, so we should override the visibility. | |
| dst_type->visibility_level = src_type->visibility_level; | |
| } | |
| return true; | |
| } | |
| static bool MergeEntry(IAaptContext* context, const Source& src, | |
| ResourceEntry* dst_entry, ResourceEntry* src_entry, | |
| bool strict_visibility) { | |
| if (strict_visibility | |
| && dst_entry->visibility.level != Visibility::Level::kUndefined | |
| && src_entry->visibility.level != dst_entry->visibility.level) { | |
| context->GetDiagnostics()->Error( | |
| DiagMessage(src) << "cannot merge resource '" << dst_entry->name << "' with conflicting visibilities: " | |
| << "public and private"); | |
| return false; | |
| } | |
| // Copy over the strongest visibility. | |
| if (src_entry->visibility.level > dst_entry->visibility.level) { | |
| // Only copy the ID if the source is public, or else the ID is meaningless. | |
| if (src_entry->visibility.level == Visibility::Level::kPublic) { | |
| dst_entry->id = src_entry->id; | |
| } | |
| dst_entry->visibility = std::move(src_entry->visibility); | |
| } else if (src_entry->visibility.level == Visibility::Level::kPublic && | |
| dst_entry->visibility.level == Visibility::Level::kPublic && dst_entry->id && | |
| src_entry->id && src_entry->id != dst_entry->id) { | |
| // Both entries are public and have different IDs. | |
| context->GetDiagnostics()->Error(DiagMessage(src) << "cannot merge entry '" << src_entry->name | |
| << "': conflicting public IDs"); | |
| return false; | |
| } | |
| // Copy over the rest of the properties, if needed. | |
| if (src_entry->allow_new) { | |
| dst_entry->allow_new = std::move(src_entry->allow_new); | |
| } | |
| if (src_entry->overlayable_item) { | |
| if (dst_entry->overlayable_item) { | |
| CHECK(src_entry->overlayable_item.value().overlayable != nullptr); | |
| Overlayable* src_overlayable = src_entry->overlayable_item.value().overlayable.get(); | |
| CHECK(dst_entry->overlayable_item.value().overlayable != nullptr); | |
| Overlayable* dst_overlayable = dst_entry->overlayable_item.value().overlayable.get(); | |
| if (src_overlayable->name != dst_overlayable->name | |
| || src_overlayable->actor != dst_overlayable->actor | |
| || src_entry->overlayable_item.value().policies != | |
| dst_entry->overlayable_item.value().policies) { | |
| // Do not allow a resource with an overlayable declaration to have that overlayable | |
| // declaration redefined. | |
| context->GetDiagnostics()->Error(DiagMessage(src_entry->overlayable_item.value().source) | |
| << "duplicate overlayable declaration for resource '" | |
| << src_entry->name << "'"); | |
| context->GetDiagnostics()->Error(DiagMessage(dst_entry->overlayable_item.value().source) | |
| << "previous declaration here"); | |
| return false; | |
| } | |
| } | |
| dst_entry->overlayable_item = std::move(src_entry->overlayable_item); | |
| } | |
| if (src_entry->staged_id) { | |
| if (dst_entry->staged_id && | |
| dst_entry->staged_id.value().id != src_entry->staged_id.value().id) { | |
| context->GetDiagnostics()->Error(DiagMessage(src_entry->staged_id.value().source) | |
| << "conflicting staged id declaration for resource '" | |
| << src_entry->name << "'"); | |
| context->GetDiagnostics()->Error(DiagMessage(dst_entry->staged_id.value().source) | |
| << "previous declaration here"); | |
| } | |
| dst_entry->staged_id = std::move(src_entry->staged_id); | |
| } | |
| return true; | |
| } | |
| // Modified CollisionResolver which will merge Styleables and Styles. Used with overlays. | |
| // | |
| // Styleables are not actual resources, but they are treated as such during the compilation phase. | |
| // | |
| // Styleables and Styles don't simply overlay each other, their definitions merge and accumulate. | |
| // If both values are Styleables/Styles, we just merge them into the existing value. | |
| static ResourceTable::CollisionResult ResolveMergeCollision( | |
| bool override_styles_instead_of_overlaying, Value* existing, Value* incoming, | |
| StringPool* pool) { | |
| if (Styleable* existing_styleable = ValueCast<Styleable>(existing)) { | |
| if (Styleable* incoming_styleable = ValueCast<Styleable>(incoming)) { | |
| // Styleables get merged. | |
| existing_styleable->MergeWith(incoming_styleable); | |
| return ResourceTable::CollisionResult::kKeepOriginal; | |
| } | |
| } else if (!override_styles_instead_of_overlaying) { | |
| if (Style* existing_style = ValueCast<Style>(existing)) { | |
| if (Style* incoming_style = ValueCast<Style>(incoming)) { | |
| // Styles get merged. | |
| existing_style->MergeWith(incoming_style, pool); | |
| return ResourceTable::CollisionResult::kKeepOriginal; | |
| } | |
| } | |
| } | |
| // Delegate to the default handler. | |
| return ResourceTable::ResolveValueCollision(existing, incoming); | |
| } | |
| static ResourceTable::CollisionResult MergeConfigValue(IAaptContext* context, | |
| const ResourceNameRef& res_name, | |
| bool overlay, | |
| bool override_styles_instead_of_overlaying, | |
| ResourceConfigValue* dst_config_value, | |
| ResourceConfigValue* src_config_value, | |
| StringPool* pool) { | |
| using CollisionResult = ResourceTable::CollisionResult; | |
| Value* dst_value = dst_config_value->value.get(); | |
| Value* src_value = src_config_value->value.get(); | |
| CollisionResult collision_result; | |
| if (overlay) { | |
| collision_result = | |
| ResolveMergeCollision(override_styles_instead_of_overlaying, dst_value, src_value, pool); | |
| } else { | |
| collision_result = ResourceTable::ResolveValueCollision(dst_value, src_value); | |
| } | |
| if (collision_result == CollisionResult::kConflict) { | |
| if (overlay) { | |
| return CollisionResult::kTakeNew; | |
| } | |
| // Error! | |
| context->GetDiagnostics()->Error(DiagMessage(src_value->GetSource()) | |
| << "resource '" << res_name << "' has a conflicting value for " | |
| << "configuration (" << src_config_value->config << ")"); | |
| context->GetDiagnostics()->Note(DiagMessage(dst_value->GetSource()) | |
| << "originally defined here"); | |
| return CollisionResult::kConflict; | |
| } | |
| return collision_result; | |
| } | |
| bool TableMerger::DoMerge(const Source& src, ResourceTablePackage* src_package, bool mangle_package, | |
| bool overlay, bool allow_new_resources) { | |
| bool error = false; | |
| for (auto& src_type : src_package->types) { | |
| ResourceTableType* dst_type = main_package_->FindOrCreateType(src_type->type); | |
| if (!MergeType(context_, src, dst_type, src_type.get())) { | |
| error = true; | |
| continue; | |
| } | |
| for (auto& src_entry : src_type->entries) { | |
| std::string entry_name = src_entry->name; | |
| if (mangle_package) { | |
| entry_name = NameMangler::MangleEntry(src_package->name, src_entry->name); | |
| } | |
| ResourceEntry* dst_entry; | |
| if (allow_new_resources || src_entry->allow_new) { | |
| dst_entry = dst_type->FindOrCreateEntry(entry_name); | |
| } else { | |
| dst_entry = dst_type->FindEntry(entry_name); | |
| } | |
| const ResourceNameRef res_name(src_package->name, src_type->type, src_entry->name); | |
| if (!dst_entry) { | |
| context_->GetDiagnostics()->Error(DiagMessage(src) | |
| << "resource " << res_name | |
| << " does not override an existing resource"); | |
| context_->GetDiagnostics()->Note(DiagMessage(src) << "define an <add-resource> tag or use " | |
| << "--auto-add-overlay"); | |
| error = true; | |
| continue; | |
| } | |
| if (!MergeEntry(context_, src, dst_entry, src_entry.get(), options_.strict_visibility)) { | |
| error = true; | |
| continue; | |
| } | |
| for (auto& src_config_value : src_entry->values) { | |
| using CollisionResult = ResourceTable::CollisionResult; | |
| ResourceConfigValue* dst_config_value = dst_entry->FindValue( | |
| src_config_value->config, src_config_value->product); | |
| if (dst_config_value) { | |
| CollisionResult collision_result = MergeConfigValue( | |
| context_, res_name, overlay, options_.override_styles_instead_of_overlaying, | |
| dst_config_value, src_config_value.get(), &main_table_->string_pool); | |
| if (collision_result == CollisionResult::kConflict) { | |
| error = true; | |
| continue; | |
| } else if (collision_result == CollisionResult::kKeepOriginal) { | |
| continue; | |
| } | |
| } else { | |
| dst_config_value = | |
| dst_entry->FindOrCreateValue(src_config_value->config, src_config_value->product); | |
| } | |
| // Continue if we're taking the new resource. | |
| CloningValueTransformer cloner(&main_table_->string_pool); | |
| if (FileReference* f = ValueCast<FileReference>(src_config_value->value.get())) { | |
| std::unique_ptr<FileReference> new_file_ref; | |
| if (mangle_package) { | |
| new_file_ref = CloneAndMangleFile(src_package->name, *f); | |
| } else { | |
| new_file_ref = std::unique_ptr<FileReference>(f->Transform(cloner)); | |
| } | |
| dst_config_value->value = std::move(new_file_ref); | |
| } else { | |
| Maybe<std::string> original_comment = (dst_config_value->value) | |
| ? dst_config_value->value->GetComment() : Maybe<std::string>(); | |
| dst_config_value->value = src_config_value->value->Transform(cloner); | |
| // Keep the comment from the original resource and ignore all comments from overlaying | |
| // resources | |
| if (overlay && original_comment) { | |
| dst_config_value->value->SetComment(original_comment.value()); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| return !error; | |
| } | |
| std::unique_ptr<FileReference> TableMerger::CloneAndMangleFile( | |
| const std::string& package, const FileReference& file_ref) { | |
| StringPiece prefix, entry, suffix; | |
| if (util::ExtractResFilePathParts(*file_ref.path, &prefix, &entry, &suffix)) { | |
| std::string mangled_entry = NameMangler::MangleEntry(package, entry.to_string()); | |
| std::string newPath = prefix.to_string() + mangled_entry + suffix.to_string(); | |
| std::unique_ptr<FileReference> new_file_ref = | |
| util::make_unique<FileReference>(main_table_->string_pool.MakeRef(newPath)); | |
| new_file_ref->SetComment(file_ref.GetComment()); | |
| new_file_ref->SetSource(file_ref.GetSource()); | |
| new_file_ref->type = file_ref.type; | |
| new_file_ref->file = file_ref.file; | |
| return new_file_ref; | |
| } | |
| CloningValueTransformer cloner(&main_table_->string_pool); | |
| return std::unique_ptr<FileReference>(file_ref.Transform(cloner)); | |
| } | |
| bool TableMerger::MergeFile(const ResourceFile& file_desc, bool overlay, io::IFile* file) { | |
| ResourceTable table; | |
| std::string path = ResourceUtils::BuildResourceFileName(file_desc); | |
| std::unique_ptr<FileReference> file_ref = | |
| util::make_unique<FileReference>(table.string_pool.MakeRef(path)); | |
| file_ref->SetSource(file_desc.source); | |
| file_ref->type = file_desc.type; | |
| file_ref->file = file; | |
| ResourceTablePackage* pkg = table.FindOrCreatePackage(file_desc.name.package); | |
| pkg->FindOrCreateType(file_desc.name.type) | |
| ->FindOrCreateEntry(file_desc.name.entry) | |
| ->FindOrCreateValue(file_desc.config, {}) | |
| ->value = std::move(file_ref); | |
| return DoMerge(file->GetSource(), pkg, false /*mangle*/, overlay /*overlay*/, true /*allow_new*/); | |
| } | |
| } // namespace aapt |