| /* |
| * 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 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(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 (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, |
| const bool overlay, |
| 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(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, 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 || src_entry->symbol_status.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())) { |
| 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(), &master_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. |
| |
| if (FileReference* f = ValueCast<FileReference>(src_config_value->value.get())) { |
| std::unique_ptr<FileReference> new_file_ref; |
| if (mangle_package) { |
| new_file_ref = CloneAndMangleFile(src_package->name, *f); |
| } else { |
| new_file_ref = std::unique_ptr<FileReference>(f->Clone(&master_table_->string_pool)); |
| } |
| |
| 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<Value>( |
| src_config_value->value->Clone(&master_table_->string_pool)); |
| } |
| } |
| } |
| } |
| 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>(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<FileReference>(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<FileReference> file_ref = |
| util::make_unique<FileReference>(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 |