/* * 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 "ValueVisitor.h" #include "util/Util.h" using android::StringPiece; namespace aapt { TableMerger::TableMerger(IAaptContext* context, ResourceTable* out_table, const TableMergerOptions& options) : context_(context), master_table_(out_table), options_(options) { // Create the desired package that all tables will be merged into. master_package_ = master_table_->CreatePackage( context_->GetCompilationPackage(), context_->GetPackageId()); CHECK(master_package_ != nullptr) << "package name or ID already taken"; } bool TableMerger::Merge(const Source& src, ResourceTable* table, io::IFileCollection* collection) { return MergeImpl(src, table, collection, false /* overlay */, true /* allow new */); } bool TableMerger::MergeOverlay(const Source& src, ResourceTable* table, io::IFileCollection* collection) { return MergeImpl(src, table, collection, true /* overlay */, options_.auto_add_overlay); } /** * This will merge packages with the same package name (or no package name). */ bool TableMerger::MergeImpl(const Source& src, ResourceTable* table, io::IFileCollection* collection, 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) { FileMergeCallback callback; if (collection) { callback = [&](const ResourceNameRef& name, const ConfigDescription& config, FileReference* new_file, FileReference* old_file) -> bool { // The old file's path points inside the APK, so we can use it as is. io::IFile* f = collection->FindFile(*old_file->path); if (!f) { context_->GetDiagnostics()->Error(DiagMessage(src) << "file '" << *old_file->path << "' not found"); return false; } new_file->file = f; return true; }; } // 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, table, package.get(), false /* mangle */, overlay, allow_new, callback); } } return !error; } /** * This will merge and mangle resources from a static library. */ bool TableMerger::MergeAndMangle(const Source& src, const StringPiece& package_name, ResourceTable* table, io::IFileCollection* collection) { 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); auto callback = [&]( const ResourceNameRef& name, const ConfigDescription& config, FileReference* new_file, FileReference* old_file) -> bool { // The old file's path points inside the APK, so we can use it as is. io::IFile* f = collection->FindFile(*old_file->path); if (!f) { context_->GetDiagnostics()->Error( DiagMessage(src) << "file '" << *old_file->path << "' not found"); return false; } new_file->file = f; return true; }; error |= !DoMerge(src, table, package.get(), mangle, false /* overlay */, true /* allow new */, callback); } return !error; } static bool MergeType(IAaptContext* context, const Source& src, ResourceTableType* dst_type, ResourceTableType* src_type) { if (dst_type->symbol_status.state < src_type->symbol_status.state) { // The incoming type's visibility is stronger, so we should override // the visibility. if (src_type->symbol_status.state == SymbolState::kPublic) { // Only copy the ID if the source is public, or else the ID is // meaningless. dst_type->id = src_type->id; } dst_type->symbol_status = std::move(src_type->symbol_status); } else if (dst_type->symbol_status.state == SymbolState::kPublic && src_type->symbol_status.state == SymbolState::kPublic && dst_type->id && src_type->id && dst_type->id.value() != src_type->id.value()) { // Both types are public and have different IDs. context->GetDiagnostics()->Error(DiagMessage(src) << "cannot merge type '" << src_type->type << "': conflicting public IDs"); return false; } return true; } static bool MergeEntry(IAaptContext* context, const Source& src, ResourceEntry* dst_entry, ResourceEntry* src_entry) { if (dst_entry->symbol_status.state < src_entry->symbol_status.state) { // The incoming type's visibility is stronger, so we should override // the visibility. if (src_entry->symbol_status.state == SymbolState::kPublic) { // Only copy the ID if the source is public, or else the ID is // meaningless. dst_entry->id = src_entry->id; } dst_entry->symbol_status = std::move(src_entry->symbol_status); } else if (src_entry->symbol_status.state == SymbolState::kPublic && dst_entry->symbol_status.state == SymbolState::kPublic && dst_entry->id && src_entry->id && dst_entry->id.value() != src_entry->id.value()) { // Both entries are public and have different IDs. context->GetDiagnostics()->Error( DiagMessage(src) << "cannot merge entry '" << src_entry->name << "': conflicting public IDs"); return false; } return true; } /** * Modified CollisionResolver which will merge Styleables. Used with overlays. * * Styleables are not actual resources, but they are treated as such during the * compilation phase. Styleables don't simply overlay each other, their * definitions merge * and accumulate. If both values are Styleables, we just merge them into the * existing value. */ static ResourceTable::CollisionResult ResolveMergeCollision(Value* existing, Value* incoming) { if (Styleable* existing_styleable = ValueCast(existing)) { if (Styleable* incoming_styleable = ValueCast(incoming)) { // Styleables get merged. existing_styleable->MergeWith(incoming_styleable); return ResourceTable::CollisionResult::kKeepOriginal; } } // Delegate to the default handler. return ResourceTable::ResolveValueCollision(existing, incoming); } static ResourceTable::CollisionResult MergeConfigValue( IAaptContext* context, const ResourceNameRef& res_name, const bool overlay, ResourceConfigValue* dst_config_value, ResourceConfigValue* src_config_value) { 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(dst_value, src_value); } 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, ResourceTable* src_table, ResourceTablePackage* src_package, const bool mangle_package, const bool overlay, const bool allow_new_resources, const FileMergeCallback& callback) { bool error = false; for (auto& src_type : src_package->types) { ResourceTableType* dst_type = master_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) { 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 tag or use " << "--auto-add-overlay"); error = true; continue; } if (!MergeEntry(context_, src, dst_entry, src_entry.get())) { 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, dst_config_value, src_config_value.get()); 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. if (FileReference* f = ValueCast(src_config_value->value.get())) { std::unique_ptr new_file_ref; if (mangle_package) { new_file_ref = CloneAndMangleFile(src_package->name, *f); } else { new_file_ref = std::unique_ptr( f->Clone(&master_table_->string_pool)); } if (callback) { if (!callback(res_name, src_config_value->config, new_file_ref.get(), f)) { error = true; continue; } } dst_config_value->value = std::move(new_file_ref); } else { dst_config_value->value = std::unique_ptr( src_config_value->value->Clone(&master_table_->string_pool)); } } } } return !error; } std::unique_ptr 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 new_file_ref = util::make_unique( master_table_->string_pool.MakeRef(newPath)); new_file_ref->SetComment(file_ref.GetComment()); new_file_ref->SetSource(file_ref.GetSource()); return new_file_ref; } return std::unique_ptr( file_ref.Clone(&master_table_->string_pool)); } bool TableMerger::MergeFileImpl(const ResourceFile& file_desc, io::IFile* file, bool overlay) { ResourceTable table; std::string path = ResourceUtils::BuildResourceFileName(file_desc); std::unique_ptr file_ref = util::make_unique(table.string_pool.MakeRef(path)); file_ref->SetSource(file_desc.source); file_ref->file = file; ResourceTablePackage* pkg = table.CreatePackage(file_desc.name.package, 0x0); pkg->FindOrCreateType(file_desc.name.type) ->FindOrCreateEntry(file_desc.name.entry) ->FindOrCreateValue(file_desc.config, {}) ->value = std::move(file_ref); return DoMerge(file->GetSource(), &table, pkg, false /* mangle */, overlay /* overlay */, true /* allow_new */, {}); } bool TableMerger::MergeFile(const ResourceFile& file_desc, io::IFile* file) { return MergeFileImpl(file_desc, file, false /* overlay */); } bool TableMerger::MergeFileOverlay(const ResourceFile& file_desc, io::IFile* file) { return MergeFileImpl(file_desc, file, true /* overlay */); } } // namespace aapt