You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
361 lines
15 KiB
361 lines
15 KiB
/*
|
|
* 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
|