Add <staging-public-group-final> to aapt2
To allow apps that compiled against a pre-release SDK to continue working for a period of time after API finalization, a new tag, <staging-public-group-final>, has been added to aapt2. When finalizing the framework resource API, converting <staging-public-group> tags to <staging-public-group-final> will cause aapt2 to generate the resource table so that there is a resource entry for the old non-finalized (staged) resource ID and another entry for the finalized resource ID of newly finalized resources. This allows an application that compiled against the pre-release SDK to continue resolving resources using pre-release resource IDs. All references to pre-release resource IDs will be rewritten to their finalized resource IDs through the information stored in the new staged alias chunk. This allows applications compiled against <staging-public-group> resources to use the newly finalized resource ID without re-compilation. When an application is re-compiled against the SDK with <staging-public-group-final> tags, the application will use the finalized resource IDs. This change limits the use of the alias chunk to the framework for S. Bug: 183411356 Test: aapt2_test Change-Id: Iba1c3033c3c2f32de8e4a19b58d3921c971092c4
This commit is contained in:
parent
969f4ec61b
commit
2fedba9a32
@ -221,6 +221,16 @@ void AssetManager2::BuildDynamicRefTable() {
|
||||
for (auto iter2 = package_groups_.begin(); iter2 != package_groups_end; ++iter2) {
|
||||
iter2->dynamic_ref_table->addMapping(String16(package_name.c_str(), package_name.size()),
|
||||
iter->dynamic_ref_table->mAssignedPackageId);
|
||||
|
||||
// Add the alias resources to the dynamic reference table of every package group. Since
|
||||
// staging aliases can only be defined by the framework package (which is not a shared
|
||||
// library), the compile-time package id of the framework is the same across all packages
|
||||
// that compile against the framework.
|
||||
for (const auto& package : iter2->packages_) {
|
||||
for (const auto& entry : package.loaded_package_->GetAliasResourceIdMap()) {
|
||||
iter->dynamic_ref_table->addAlias(entry.first, entry.second);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -41,6 +41,7 @@ using android::base::StringPrintf;
|
||||
|
||||
namespace android {
|
||||
|
||||
constexpr const static int kFrameworkPackageId = 0x01;
|
||||
constexpr const static int kAppPackageId = 0x7f;
|
||||
|
||||
namespace {
|
||||
@ -675,6 +676,42 @@ std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk,
|
||||
}
|
||||
} break;
|
||||
|
||||
case RES_TABLE_STAGED_ALIAS_TYPE: {
|
||||
if (loaded_package->package_id_ != kFrameworkPackageId) {
|
||||
LOG(WARNING) << "Alias chunk ignored for non-framework package '"
|
||||
<< loaded_package->package_name_ << "'";
|
||||
break;
|
||||
}
|
||||
|
||||
std::unordered_set<uint32_t> finalized_ids;
|
||||
const auto lib_alias = child_chunk.header<ResTable_staged_alias_header>();
|
||||
if (!lib_alias) {
|
||||
return {};
|
||||
}
|
||||
const auto entry_begin = child_chunk.data_ptr().convert<ResTable_staged_alias_entry>();
|
||||
const auto entry_end = entry_begin + dtohl(lib_alias->count);
|
||||
for (auto entry_iter = entry_begin; entry_iter != entry_end; ++entry_iter) {
|
||||
if (!entry_iter) {
|
||||
return {};
|
||||
}
|
||||
auto finalized_id = dtohl(entry_iter->finalizedResId);
|
||||
if (!finalized_ids.insert(finalized_id).second) {
|
||||
LOG(ERROR) << StringPrintf("Repeated finalized resource id '%08x' in staged aliases.",
|
||||
finalized_id);
|
||||
return {};
|
||||
}
|
||||
|
||||
auto staged_id = dtohl(entry_iter->stagedResId);
|
||||
auto [_, success] = loaded_package->alias_id_map_.insert(std::make_pair(staged_id,
|
||||
finalized_id));
|
||||
if (!success) {
|
||||
LOG(ERROR) << StringPrintf("Repeated staged resource id '%08x' in staged aliases.",
|
||||
staged_id);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
} break;
|
||||
|
||||
default:
|
||||
LOG(WARNING) << StringPrintf("Unknown chunk type '%02x'.", chunk.type());
|
||||
break;
|
||||
|
@ -7079,6 +7079,10 @@ void DynamicRefTable::addMapping(uint8_t buildPackageId, uint8_t runtimePackageI
|
||||
mLookupTable[buildPackageId] = runtimePackageId;
|
||||
}
|
||||
|
||||
void DynamicRefTable::addAlias(uint32_t stagedId, uint32_t finalizedId) {
|
||||
mAliasId[stagedId] = finalizedId;
|
||||
}
|
||||
|
||||
status_t DynamicRefTable::lookupResourceId(uint32_t* resId) const {
|
||||
uint32_t res = *resId;
|
||||
size_t packageId = Res_GETPACKAGE(res) + 1;
|
||||
@ -7088,8 +7092,16 @@ status_t DynamicRefTable::lookupResourceId(uint32_t* resId) const {
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
if (packageId == APP_PACKAGE_ID && !mAppAsLib) {
|
||||
// No lookup needs to be done, app package IDs are absolute.
|
||||
auto alias_id = mAliasId.find(res);
|
||||
if (alias_id != mAliasId.end()) {
|
||||
// Rewrite the resource id to its alias resource id. Since the alias resource id is a
|
||||
// compile-time id, it still needs to be resolved further.
|
||||
res = alias_id->second;
|
||||
}
|
||||
|
||||
if (packageId == SYS_PACKAGE_ID || (packageId == APP_PACKAGE_ID && !mAppAsLib)) {
|
||||
// No lookup needs to be done, app and framework package IDs are absolute.
|
||||
*resId = res;
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
|
@ -17,6 +17,7 @@
|
||||
#ifndef LOADEDARSC_H_
|
||||
#define LOADEDARSC_H_
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
@ -171,51 +172,51 @@ class LoadedPackage {
|
||||
incfs::verified_map_ptr<ResTable_type> type_chunk, uint32_t offset);
|
||||
|
||||
// Returns the string pool where type names are stored.
|
||||
inline const ResStringPool* GetTypeStringPool() const {
|
||||
const ResStringPool* GetTypeStringPool() const {
|
||||
return &type_string_pool_;
|
||||
}
|
||||
|
||||
// Returns the string pool where the names of resource entries are stored.
|
||||
inline const ResStringPool* GetKeyStringPool() const {
|
||||
const ResStringPool* GetKeyStringPool() const {
|
||||
return &key_string_pool_;
|
||||
}
|
||||
|
||||
inline const std::string& GetPackageName() const {
|
||||
const std::string& GetPackageName() const {
|
||||
return package_name_;
|
||||
}
|
||||
|
||||
inline int GetPackageId() const {
|
||||
int GetPackageId() const {
|
||||
return package_id_;
|
||||
}
|
||||
|
||||
// Returns true if this package is dynamic (shared library) and needs to have an ID assigned.
|
||||
inline bool IsDynamic() const {
|
||||
bool IsDynamic() const {
|
||||
return (property_flags_ & PROPERTY_DYNAMIC) != 0;
|
||||
}
|
||||
|
||||
// Returns true if this package is a Runtime Resource Overlay.
|
||||
inline bool IsOverlay() const {
|
||||
bool IsOverlay() const {
|
||||
return (property_flags_ & PROPERTY_OVERLAY) != 0;
|
||||
}
|
||||
|
||||
// Returns true if this package originates from a system provided resource.
|
||||
inline bool IsSystem() const {
|
||||
bool IsSystem() const {
|
||||
return (property_flags_ & PROPERTY_SYSTEM) != 0;
|
||||
}
|
||||
|
||||
// Returns true if this package is a custom loader and should behave like an overlay.
|
||||
inline bool IsCustomLoader() const {
|
||||
bool IsCustomLoader() const {
|
||||
return (property_flags_ & PROPERTY_LOADER) != 0;
|
||||
}
|
||||
|
||||
inline package_property_t GetPropertyFlags() const {
|
||||
package_property_t GetPropertyFlags() const {
|
||||
return property_flags_;
|
||||
}
|
||||
|
||||
// Returns the map of package name to package ID used in this LoadedPackage. At runtime, a
|
||||
// package could have been assigned a different package ID than what this LoadedPackage was
|
||||
// compiled with. AssetManager rewrites the package IDs so that they are compatible at runtime.
|
||||
inline const std::vector<DynamicPackageEntry>& GetDynamicPackageMap() const {
|
||||
const std::vector<DynamicPackageEntry>& GetDynamicPackageMap() const {
|
||||
return dynamic_package_map_;
|
||||
}
|
||||
|
||||
@ -270,6 +271,10 @@ class LoadedPackage {
|
||||
return overlayable_map_;
|
||||
}
|
||||
|
||||
const std::map<uint32_t, uint32_t>& GetAliasResourceIdMap() const {
|
||||
return alias_id_map_;
|
||||
}
|
||||
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(LoadedPackage);
|
||||
|
||||
@ -287,6 +292,7 @@ class LoadedPackage {
|
||||
ByteBucketArray<uint32_t> resource_ids_;
|
||||
std::vector<DynamicPackageEntry> dynamic_package_map_;
|
||||
std::vector<const std::pair<OverlayableInfo, std::unordered_set<uint32_t>>> overlayable_infos_;
|
||||
std::map<uint32_t, uint32_t> alias_id_map_;
|
||||
|
||||
// A map of overlayable name to actor
|
||||
std::unordered_map<std::string, std::string> overlayable_map_;
|
||||
|
@ -39,6 +39,7 @@
|
||||
#include <android/configuration.h>
|
||||
|
||||
#include <array>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
|
||||
namespace android {
|
||||
@ -229,30 +230,31 @@ struct ResChunk_header
|
||||
};
|
||||
|
||||
enum {
|
||||
RES_NULL_TYPE = 0x0000,
|
||||
RES_STRING_POOL_TYPE = 0x0001,
|
||||
RES_TABLE_TYPE = 0x0002,
|
||||
RES_XML_TYPE = 0x0003,
|
||||
RES_NULL_TYPE = 0x0000,
|
||||
RES_STRING_POOL_TYPE = 0x0001,
|
||||
RES_TABLE_TYPE = 0x0002,
|
||||
RES_XML_TYPE = 0x0003,
|
||||
|
||||
// Chunk types in RES_XML_TYPE
|
||||
RES_XML_FIRST_CHUNK_TYPE = 0x0100,
|
||||
RES_XML_START_NAMESPACE_TYPE= 0x0100,
|
||||
RES_XML_END_NAMESPACE_TYPE = 0x0101,
|
||||
RES_XML_START_ELEMENT_TYPE = 0x0102,
|
||||
RES_XML_END_ELEMENT_TYPE = 0x0103,
|
||||
RES_XML_CDATA_TYPE = 0x0104,
|
||||
RES_XML_LAST_CHUNK_TYPE = 0x017f,
|
||||
RES_XML_FIRST_CHUNK_TYPE = 0x0100,
|
||||
RES_XML_START_NAMESPACE_TYPE = 0x0100,
|
||||
RES_XML_END_NAMESPACE_TYPE = 0x0101,
|
||||
RES_XML_START_ELEMENT_TYPE = 0x0102,
|
||||
RES_XML_END_ELEMENT_TYPE = 0x0103,
|
||||
RES_XML_CDATA_TYPE = 0x0104,
|
||||
RES_XML_LAST_CHUNK_TYPE = 0x017f,
|
||||
// This contains a uint32_t array mapping strings in the string
|
||||
// pool back to resource identifiers. It is optional.
|
||||
RES_XML_RESOURCE_MAP_TYPE = 0x0180,
|
||||
RES_XML_RESOURCE_MAP_TYPE = 0x0180,
|
||||
|
||||
// Chunk types in RES_TABLE_TYPE
|
||||
RES_TABLE_PACKAGE_TYPE = 0x0200,
|
||||
RES_TABLE_TYPE_TYPE = 0x0201,
|
||||
RES_TABLE_TYPE_SPEC_TYPE = 0x0202,
|
||||
RES_TABLE_LIBRARY_TYPE = 0x0203,
|
||||
RES_TABLE_OVERLAYABLE_TYPE = 0x0204,
|
||||
RES_TABLE_PACKAGE_TYPE = 0x0200,
|
||||
RES_TABLE_TYPE_TYPE = 0x0201,
|
||||
RES_TABLE_TYPE_SPEC_TYPE = 0x0202,
|
||||
RES_TABLE_LIBRARY_TYPE = 0x0203,
|
||||
RES_TABLE_OVERLAYABLE_TYPE = 0x0204,
|
||||
RES_TABLE_OVERLAYABLE_POLICY_TYPE = 0x0205,
|
||||
RES_TABLE_STAGED_ALIAS_TYPE = 0x0206,
|
||||
};
|
||||
|
||||
/**
|
||||
@ -1638,6 +1640,29 @@ struct ResTable_lib_entry
|
||||
uint16_t packageName[128];
|
||||
};
|
||||
|
||||
/**
|
||||
* A map that allows rewriting staged (non-finalized) resource ids to their finalized counterparts.
|
||||
*/
|
||||
struct ResTable_staged_alias_header
|
||||
{
|
||||
struct ResChunk_header header;
|
||||
|
||||
// The number of ResTable_staged_alias_entry that follow this header.
|
||||
uint32_t count;
|
||||
};
|
||||
|
||||
/**
|
||||
* Maps the staged (non-finalized) resource id to its finalized resource id.
|
||||
*/
|
||||
struct ResTable_staged_alias_entry
|
||||
{
|
||||
// The compile-time staged resource id to rewrite.
|
||||
uint32_t stagedResId;
|
||||
|
||||
// The compile-time finalized resource id to which the staged resource id should be rewritten.
|
||||
uint32_t finalizedResId;
|
||||
};
|
||||
|
||||
/**
|
||||
* Specifies the set of resources that are explicitly allowed to be overlaid by RROs.
|
||||
*/
|
||||
@ -1751,6 +1776,8 @@ public:
|
||||
|
||||
void addMapping(uint8_t buildPackageId, uint8_t runtimePackageId);
|
||||
|
||||
void addAlias(uint32_t stagedId, uint32_t finalizedId);
|
||||
|
||||
// Returns whether or not the value must be looked up.
|
||||
bool requiresLookup(const Res_value* value) const;
|
||||
|
||||
@ -1768,6 +1795,7 @@ private:
|
||||
uint8_t mLookupTable[256];
|
||||
KeyedVector<String16, uint8_t> mEntries;
|
||||
bool mAppAsLib;
|
||||
std::map<uint32_t, uint32_t> mAliasId;
|
||||
};
|
||||
|
||||
bool U16StringToInt(const char16_t* s, size_t len, Res_value* outValue);
|
||||
|
@ -278,17 +278,19 @@ void Debug::PrintTable(const ResourceTable& table, const DebugPrintTableOptions&
|
||||
printer->Println(StringPrintf(" entryCount=%zd", type.entries.size()));
|
||||
|
||||
printer->Indent();
|
||||
for (const ResourceEntry* entry : type.entries) {
|
||||
for (const ResourceTableEntryView& entry : type.entries) {
|
||||
printer->Print("resource ");
|
||||
printer->Print(entry->id.value_or_default(0).to_string());
|
||||
printer->Print(ResourceId(package.id.value_or_default(0), type.id.value_or_default(0),
|
||||
entry.id.value_or_default(0))
|
||||
.to_string());
|
||||
printer->Print(" ");
|
||||
|
||||
// Write the name without the package (this is obvious and too verbose).
|
||||
printer->Print(to_string(type.type));
|
||||
printer->Print("/");
|
||||
printer->Print(entry->name);
|
||||
printer->Print(entry.name);
|
||||
|
||||
switch (entry->visibility.level) {
|
||||
switch (entry.visibility.level) {
|
||||
case Visibility::Level::kPublic:
|
||||
printer->Print(" PUBLIC");
|
||||
break;
|
||||
@ -300,19 +302,24 @@ void Debug::PrintTable(const ResourceTable& table, const DebugPrintTableOptions&
|
||||
break;
|
||||
}
|
||||
|
||||
if (entry->visibility.staged_api) {
|
||||
if (entry.visibility.staged_api) {
|
||||
printer->Print(" STAGED");
|
||||
}
|
||||
|
||||
if (entry->overlayable_item) {
|
||||
if (entry.overlayable_item) {
|
||||
printer->Print(" OVERLAYABLE");
|
||||
}
|
||||
|
||||
if (entry.staged_id) {
|
||||
printer->Print(" STAGED_ID=");
|
||||
printer->Print(entry.staged_id.value().id.to_string());
|
||||
}
|
||||
|
||||
printer->Println();
|
||||
|
||||
if (options.show_values) {
|
||||
printer->Indent();
|
||||
for (const auto& value : entry->values) {
|
||||
for (const auto& value : entry.values) {
|
||||
printer->Print("(");
|
||||
printer->Print(value->config.to_string());
|
||||
printer->Print(") ");
|
||||
|
@ -45,6 +45,7 @@ namespace aapt {
|
||||
namespace {
|
||||
constexpr const char* kPublicGroupTag = "public-group";
|
||||
constexpr const char* kStagingPublicGroupTag = "staging-public-group";
|
||||
constexpr const char* kStagingPublicGroupFinalTag = "staging-public-group-final";
|
||||
} // namespace
|
||||
|
||||
constexpr const char* sXliffNamespaceUri = "urn:oasis:names:tc:xliff:document:1.2";
|
||||
@ -109,6 +110,7 @@ struct ParsedResource {
|
||||
bool staged_api = false;
|
||||
bool allow_new = false;
|
||||
Maybe<OverlayableItem> overlayable_item;
|
||||
Maybe<StagedId> staged_alias;
|
||||
|
||||
std::string comment;
|
||||
std::unique_ptr<Value> value;
|
||||
@ -155,6 +157,10 @@ static bool AddResourcesToTable(ResourceTable* table, IDiagnostics* diag, Parsed
|
||||
res_builder.SetValue(std::move(res->value), res->config, res->product);
|
||||
}
|
||||
|
||||
if (res->staged_alias) {
|
||||
res_builder.SetStagedId(res->staged_alias.value());
|
||||
}
|
||||
|
||||
bool error = false;
|
||||
if (!res->name.entry.empty()) {
|
||||
if (!table->AddResource(res_builder.Build(), diag)) {
|
||||
@ -532,6 +538,7 @@ bool ResourceParser::ParseResource(xml::XmlPullParser* parser,
|
||||
{"public", std::mem_fn(&ResourceParser::ParsePublic)},
|
||||
{"public-group", std::mem_fn(&ResourceParser::ParsePublicGroup)},
|
||||
{"staging-public-group", std::mem_fn(&ResourceParser::ParseStagingPublicGroup)},
|
||||
{"staging-public-group-final", std::mem_fn(&ResourceParser::ParseStagingPublicGroupFinal)},
|
||||
{"string-array", std::mem_fn(&ResourceParser::ParseStringArray)},
|
||||
{"style", std::bind(&ResourceParser::ParseStyle, std::placeholders::_1, ResourceType::kStyle,
|
||||
std::placeholders::_2, std::placeholders::_3)},
|
||||
@ -671,7 +678,7 @@ bool ResourceParser::ParseResource(xml::XmlPullParser* parser,
|
||||
if (bag_iter != elToBagMap.end()) {
|
||||
// Ensure we have a name (unless this is a <public-group> or <overlayable>).
|
||||
if (resource_type != kPublicGroupTag && resource_type != kStagingPublicGroupTag &&
|
||||
resource_type != "overlayable") {
|
||||
resource_type != kStagingPublicGroupFinalTag && resource_type != "overlayable") {
|
||||
if (!maybe_name) {
|
||||
diag_->Error(DiagMessage(out_resource->source)
|
||||
<< "<" << parser->element_name() << "> missing 'name' attribute");
|
||||
@ -1034,7 +1041,6 @@ bool static ParseGroupImpl(xml::XmlPullParser* parser, ParsedResource* out_resou
|
||||
ParsedResource& entry_res = out_resource->child_resources.emplace_back(ParsedResource{
|
||||
.name = ResourceName{{}, *parsed_type, maybe_name.value().to_string()},
|
||||
.source = item_source,
|
||||
.id = next_id,
|
||||
.comment = std::move(comment),
|
||||
});
|
||||
|
||||
@ -1060,6 +1066,14 @@ bool ResourceParser::ParseStagingPublicGroup(xml::XmlPullParser* parser,
|
||||
});
|
||||
}
|
||||
|
||||
bool ResourceParser::ParseStagingPublicGroupFinal(xml::XmlPullParser* parser,
|
||||
ParsedResource* out_resource) {
|
||||
return ParseGroupImpl(parser, out_resource, kStagingPublicGroupFinalTag, diag_,
|
||||
[](ParsedResource& parsed_entry, ResourceId id) {
|
||||
parsed_entry.staged_alias = StagedId{id, parsed_entry.source};
|
||||
});
|
||||
}
|
||||
|
||||
bool ResourceParser::ParsePublicGroup(xml::XmlPullParser* parser, ParsedResource* out_resource) {
|
||||
if (options_.visibility) {
|
||||
diag_->Error(DiagMessage(out_resource->source)
|
||||
|
@ -115,6 +115,7 @@ class ResourceParser {
|
||||
bool ParsePublic(xml::XmlPullParser* parser, ParsedResource* out_resource);
|
||||
bool ParsePublicGroup(xml::XmlPullParser* parser, ParsedResource* out_resource);
|
||||
bool ParseStagingPublicGroup(xml::XmlPullParser* parser, ParsedResource* out_resource);
|
||||
bool ParseStagingPublicGroupFinal(xml::XmlPullParser* parser, ParsedResource* out_resource);
|
||||
bool ParseSymbolImpl(xml::XmlPullParser* parser, ParsedResource* out_resource);
|
||||
bool ParseSymbol(xml::XmlPullParser* parser, ParsedResource* out_resource);
|
||||
bool ParseOverlayable(xml::XmlPullParser* parser, ParsedResource* out_resource);
|
||||
|
@ -83,6 +83,20 @@ T* FindElementsRunAction(const android::StringPiece& name, Elements& entries, Fu
|
||||
return action(found, iter);
|
||||
}
|
||||
|
||||
struct ConfigKey {
|
||||
const ConfigDescription* config;
|
||||
const StringPiece& product;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
bool lt_config_key_ref(const T& lhs, const ConfigKey& rhs) {
|
||||
int cmp = lhs->config.compare(*rhs.config);
|
||||
if (cmp == 0) {
|
||||
cmp = StringPiece(lhs->product).compare(rhs.product);
|
||||
}
|
||||
return cmp < 0;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ResourceTable::ResourceTable(ResourceTable::Validation validation) : validation_(validation) {
|
||||
@ -134,23 +148,10 @@ ResourceEntry* ResourceTableType::FindOrCreateEntry(const android::StringPiece&
|
||||
});
|
||||
}
|
||||
|
||||
struct ConfigKey {
|
||||
const ConfigDescription* config;
|
||||
const StringPiece& product;
|
||||
};
|
||||
|
||||
bool lt_config_key_ref(const std::unique_ptr<ResourceConfigValue>& lhs, const ConfigKey& rhs) {
|
||||
int cmp = lhs->config.compare(*rhs.config);
|
||||
if (cmp == 0) {
|
||||
cmp = StringPiece(lhs->product).compare(rhs.product);
|
||||
}
|
||||
return cmp < 0;
|
||||
}
|
||||
|
||||
ResourceConfigValue* ResourceEntry::FindValue(const ConfigDescription& config,
|
||||
android::StringPiece product) {
|
||||
auto iter = std::lower_bound(values.begin(), values.end(), ConfigKey{&config, product},
|
||||
lt_config_key_ref);
|
||||
lt_config_key_ref<std::unique_ptr<ResourceConfigValue>>);
|
||||
if (iter != values.end()) {
|
||||
ResourceConfigValue* value = iter->get();
|
||||
if (value->config == config && StringPiece(value->product) == product) {
|
||||
@ -163,7 +164,7 @@ ResourceConfigValue* ResourceEntry::FindValue(const ConfigDescription& config,
|
||||
const ResourceConfigValue* ResourceEntry::FindValue(const android::ConfigDescription& config,
|
||||
android::StringPiece product) const {
|
||||
auto iter = std::lower_bound(values.begin(), values.end(), ConfigKey{&config, product},
|
||||
lt_config_key_ref);
|
||||
lt_config_key_ref<std::unique_ptr<ResourceConfigValue>>);
|
||||
if (iter != values.end()) {
|
||||
ResourceConfigValue* value = iter->get();
|
||||
if (value->config == config && StringPiece(value->product) == product) {
|
||||
@ -176,7 +177,7 @@ const ResourceConfigValue* ResourceEntry::FindValue(const android::ConfigDescrip
|
||||
ResourceConfigValue* ResourceEntry::FindOrCreateValue(const ConfigDescription& config,
|
||||
const StringPiece& product) {
|
||||
auto iter = std::lower_bound(values.begin(), values.end(), ConfigKey{&config, product},
|
||||
lt_config_key_ref);
|
||||
lt_config_key_ref<std::unique_ptr<ResourceConfigValue>>);
|
||||
if (iter != values.end()) {
|
||||
ResourceConfigValue* value = iter->get();
|
||||
if (value->config == config && StringPiece(value->product) == product) {
|
||||
@ -296,6 +297,7 @@ ResourceTable::CollisionResult ResourceTable::ResolveValueCollision(Value* exist
|
||||
return CollisionResult::kConflict;
|
||||
}
|
||||
|
||||
namespace {
|
||||
template <typename T, typename Comparer>
|
||||
struct SortedVectorInserter : public Comparer {
|
||||
std::pair<bool, typename std::vector<T>::iterator> LowerBound(std::vector<T>& el,
|
||||
@ -313,7 +315,7 @@ struct SortedVectorInserter : public Comparer {
|
||||
if (found) {
|
||||
return &*it;
|
||||
}
|
||||
return &*el.insert(it, std::move(value));
|
||||
return &*el.insert(it, std::forward<T>(value));
|
||||
}
|
||||
};
|
||||
|
||||
@ -331,35 +333,77 @@ struct TypeViewComparer {
|
||||
};
|
||||
|
||||
struct EntryViewComparer {
|
||||
bool operator()(const ResourceEntry* lhs, const ResourceEntry* rhs) {
|
||||
return less_than_struct_with_name_and_id<ResourceEntry, ResourceId>(
|
||||
*lhs, std::make_pair(rhs->name, rhs->id));
|
||||
bool operator()(const ResourceTableEntryView& lhs, const ResourceTableEntryView& rhs) {
|
||||
return less_than_struct_with_name_and_id<ResourceTableEntryView, uint16_t>(
|
||||
lhs, std::make_pair(rhs.name, rhs.id));
|
||||
}
|
||||
};
|
||||
|
||||
ResourceTableView ResourceTable::GetPartitionedView() const {
|
||||
ResourceTableView view;
|
||||
void InsertEntryIntoTableView(ResourceTableView& table, const ResourceTablePackage* package,
|
||||
const ResourceTableType* type, const std::string& entry_name,
|
||||
const Maybe<ResourceId>& id, const Visibility& visibility,
|
||||
const Maybe<AllowNew>& allow_new,
|
||||
const Maybe<OverlayableItem>& overlayable_item,
|
||||
const Maybe<StagedId>& staged_id,
|
||||
const std::vector<std::unique_ptr<ResourceConfigValue>>& values) {
|
||||
SortedVectorInserter<ResourceTablePackageView, PackageViewComparer> package_inserter;
|
||||
SortedVectorInserter<ResourceTableTypeView, TypeViewComparer> type_inserter;
|
||||
SortedVectorInserter<const ResourceEntry*, EntryViewComparer> entry_inserter;
|
||||
SortedVectorInserter<ResourceTableEntryView, EntryViewComparer> entry_inserter;
|
||||
|
||||
ResourceTablePackageView new_package{package->name,
|
||||
id ? id.value().package_id() : Maybe<uint8_t>{}};
|
||||
auto view_package = package_inserter.Insert(table.packages, std::move(new_package));
|
||||
|
||||
ResourceTableTypeView new_type{type->type, id ? id.value().type_id() : Maybe<uint8_t>{}};
|
||||
auto view_type = type_inserter.Insert(view_package->types, std::move(new_type));
|
||||
|
||||
if (visibility.level == Visibility::Level::kPublic) {
|
||||
// Only mark the type visibility level as public, it doesn't care about being private.
|
||||
view_type->visibility_level = Visibility::Level::kPublic;
|
||||
}
|
||||
|
||||
ResourceTableEntryView new_entry{.name = entry_name,
|
||||
.id = id ? id.value().entry_id() : Maybe<uint16_t>{},
|
||||
.visibility = visibility,
|
||||
.allow_new = allow_new,
|
||||
.overlayable_item = overlayable_item,
|
||||
.staged_id = staged_id};
|
||||
for (auto& value : values) {
|
||||
new_entry.values.emplace_back(value.get());
|
||||
}
|
||||
|
||||
entry_inserter.Insert(view_type->entries, std::move(new_entry));
|
||||
}
|
||||
} // namespace
|
||||
|
||||
const ResourceConfigValue* ResourceTableEntryView::FindValue(const ConfigDescription& config,
|
||||
android::StringPiece product) const {
|
||||
auto iter = std::lower_bound(values.begin(), values.end(), ConfigKey{&config, product},
|
||||
lt_config_key_ref<const ResourceConfigValue*>);
|
||||
if (iter != values.end()) {
|
||||
const ResourceConfigValue* value = *iter;
|
||||
if (value->config == config && StringPiece(value->product) == product) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ResourceTableView ResourceTable::GetPartitionedView(const ResourceTableViewOptions& options) const {
|
||||
ResourceTableView view;
|
||||
for (const auto& package : packages) {
|
||||
for (const auto& type : package->types) {
|
||||
for (const auto& entry : type->entries) {
|
||||
ResourceTablePackageView new_package{
|
||||
package->name, entry->id ? entry->id.value().package_id() : Maybe<uint8_t>{}};
|
||||
auto view_package = package_inserter.Insert(view.packages, std::move(new_package));
|
||||
InsertEntryIntoTableView(view, package.get(), type.get(), entry->name, entry->id,
|
||||
entry->visibility, entry->allow_new, entry->overlayable_item,
|
||||
entry->staged_id, entry->values);
|
||||
|
||||
ResourceTableTypeView new_type{type->type,
|
||||
entry->id ? entry->id.value().type_id() : Maybe<uint8_t>{}};
|
||||
auto view_type = type_inserter.Insert(view_package->types, std::move(new_type));
|
||||
|
||||
if (entry->visibility.level == Visibility::Level::kPublic) {
|
||||
// Only mark the type visibility level as public, it doesn't care about being private.
|
||||
view_type->visibility_level = Visibility::Level::kPublic;
|
||||
if (options.create_alias_entries && entry->staged_id) {
|
||||
auto alias_id = entry->staged_id.value().id;
|
||||
InsertEntryIntoTableView(view, package.get(), type.get(), entry->name, alias_id,
|
||||
entry->visibility, entry->allow_new, entry->overlayable_item, {},
|
||||
entry->values);
|
||||
}
|
||||
|
||||
entry_inserter.Insert(view_type->entries, entry.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -368,6 +412,8 @@ ResourceTableView ResourceTable::GetPartitionedView() const {
|
||||
// for the same resource type within the same package. For this reason, if there are types with
|
||||
// multiple type ids, each type needs to exist in its own package in order to be queried by name.
|
||||
std::vector<ResourceTablePackageView> new_packages;
|
||||
SortedVectorInserter<ResourceTablePackageView, PackageViewComparer> package_inserter;
|
||||
SortedVectorInserter<ResourceTableTypeView, TypeViewComparer> type_inserter;
|
||||
for (auto& package : view.packages) {
|
||||
// If a new package was already created for a different type within this package, then
|
||||
// we can reuse those packages for other types that need to be extracted from this package.
|
||||
@ -498,6 +544,10 @@ bool ResourceTable::AddResource(NewResource&& res, IDiagnostics* diag) {
|
||||
entry->allow_new = res.allow_new.value();
|
||||
}
|
||||
|
||||
if (res.staged_id.has_value()) {
|
||||
entry->staged_id = res.staged_id.value();
|
||||
}
|
||||
|
||||
if (res.value != nullptr) {
|
||||
auto config_value = entry->FindOrCreateValue(res.config, res.product);
|
||||
if (!config_value->value) {
|
||||
@ -575,6 +625,28 @@ Maybe<ResourceTable::SearchResult> ResourceTable::FindResource(const ResourceNam
|
||||
return {};
|
||||
}
|
||||
|
||||
bool ResourceTable::RemoveResource(const ResourceNameRef& name, ResourceId id) const {
|
||||
ResourceTablePackage* package = FindPackage(name.package);
|
||||
if (package == nullptr) {
|
||||
return {};
|
||||
}
|
||||
|
||||
ResourceTableType* type = package->FindType(name.type);
|
||||
if (type == nullptr) {
|
||||
return {};
|
||||
}
|
||||
|
||||
auto entry_it = std::equal_range(type->entries.begin(), type->entries.end(), name.entry,
|
||||
NameEqualRange<ResourceEntry>{});
|
||||
for (auto it = entry_it.first; it != entry_it.second; ++it) {
|
||||
if ((*it)->id == id) {
|
||||
type->entries.erase(it);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::unique_ptr<ResourceTable> ResourceTable::Clone() const {
|
||||
std::unique_ptr<ResourceTable> new_table = util::make_unique<ResourceTable>();
|
||||
CloningValueTransformer cloner(&new_table->string_pool);
|
||||
@ -640,6 +712,11 @@ NewResourceBuilder& NewResourceBuilder::SetAllowNew(AllowNew allow_new) {
|
||||
return *this;
|
||||
}
|
||||
|
||||
NewResourceBuilder& NewResourceBuilder::SetStagedId(StagedId staged_alias) {
|
||||
res_.staged_id = std::move(staged_alias);
|
||||
return *this;
|
||||
}
|
||||
|
||||
NewResourceBuilder& NewResourceBuilder::SetAllowMangled(bool allow_mangled) {
|
||||
res_.allow_mangled = allow_mangled;
|
||||
return *this;
|
||||
|
@ -64,6 +64,12 @@ struct AllowNew {
|
||||
std::string comment;
|
||||
};
|
||||
|
||||
// Represents the staged resource id of a finalized resource.
|
||||
struct StagedId {
|
||||
ResourceId id;
|
||||
Source source;
|
||||
};
|
||||
|
||||
struct Overlayable {
|
||||
Overlayable() = default;
|
||||
Overlayable(const android::StringPiece& name, const android::StringPiece& actor)
|
||||
@ -124,6 +130,9 @@ class ResourceEntry {
|
||||
// The declarations of this resource as overlayable for RROs
|
||||
Maybe<OverlayableItem> overlayable_item;
|
||||
|
||||
// The staged resource id for a finalized resource.
|
||||
Maybe<StagedId> staged_id;
|
||||
|
||||
// The resource's values for each configuration.
|
||||
std::vector<std::unique_ptr<ResourceConfigValue>> values;
|
||||
|
||||
@ -194,14 +203,27 @@ class ResourceTablePackage {
|
||||
DISALLOW_COPY_AND_ASSIGN(ResourceTablePackage);
|
||||
};
|
||||
|
||||
struct ResourceTableEntryView {
|
||||
std::string name;
|
||||
Maybe<uint16_t> id;
|
||||
Visibility visibility;
|
||||
Maybe<AllowNew> allow_new;
|
||||
Maybe<OverlayableItem> overlayable_item;
|
||||
Maybe<StagedId> staged_id;
|
||||
std::vector<const ResourceConfigValue*> values;
|
||||
|
||||
const ResourceConfigValue* FindValue(const android::ConfigDescription& config,
|
||||
android::StringPiece product = {}) const;
|
||||
};
|
||||
|
||||
struct ResourceTableTypeView {
|
||||
ResourceType type;
|
||||
Maybe<uint8_t> id;
|
||||
Visibility::Level visibility_level = Visibility::Level::kUndefined;
|
||||
|
||||
// Entries sorted in ascending entry id order. If ids have not been assigned, the entries are
|
||||
// // sorted lexicographically.
|
||||
std::vector<const ResourceEntry*> entries;
|
||||
// sorted lexicographically.
|
||||
std::vector<ResourceTableEntryView> entries;
|
||||
};
|
||||
|
||||
struct ResourceTablePackageView {
|
||||
@ -212,6 +234,10 @@ struct ResourceTablePackageView {
|
||||
std::vector<ResourceTableTypeView> types;
|
||||
};
|
||||
|
||||
struct ResourceTableViewOptions {
|
||||
bool create_alias_entries = false;
|
||||
};
|
||||
|
||||
struct ResourceTableView {
|
||||
// Packages sorted in ascending package id order. If ids have not been assigned, the packages are
|
||||
// sorted lexicographically.
|
||||
@ -237,6 +263,7 @@ struct NewResource {
|
||||
std::optional<Visibility> visibility;
|
||||
std::optional<OverlayableItem> overlayable;
|
||||
std::optional<AllowNew> allow_new;
|
||||
std::optional<StagedId> staged_id;
|
||||
bool allow_mangled = false;
|
||||
};
|
||||
|
||||
@ -249,6 +276,7 @@ struct NewResourceBuilder {
|
||||
NewResourceBuilder& SetVisibility(Visibility id);
|
||||
NewResourceBuilder& SetOverlayable(OverlayableItem overlayable);
|
||||
NewResourceBuilder& SetAllowNew(AllowNew allow_new);
|
||||
NewResourceBuilder& SetStagedId(StagedId id);
|
||||
NewResourceBuilder& SetAllowMangled(bool allow_mangled);
|
||||
NewResource Build();
|
||||
|
||||
@ -273,7 +301,7 @@ class ResourceTable {
|
||||
|
||||
// Retrieves a sorted a view of the packages, types, and entries sorted in ascending resource id
|
||||
// order.
|
||||
ResourceTableView GetPartitionedView() const;
|
||||
ResourceTableView GetPartitionedView(const ResourceTableViewOptions& options = {}) const;
|
||||
|
||||
struct SearchResult {
|
||||
ResourceTablePackage* package;
|
||||
@ -283,6 +311,7 @@ class ResourceTable {
|
||||
|
||||
Maybe<SearchResult> FindResource(const ResourceNameRef& name) const;
|
||||
Maybe<SearchResult> FindResource(const ResourceNameRef& name, ResourceId id) const;
|
||||
bool RemoveResource(const ResourceNameRef& name, ResourceId id) const;
|
||||
|
||||
// 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
|
||||
|
@ -190,6 +190,12 @@ message OverlayableItem {
|
||||
uint32 overlayable_idx = 4;
|
||||
}
|
||||
|
||||
// The staged resource ID definition of a finalized resource.
|
||||
message StagedId {
|
||||
Source source = 1;
|
||||
uint32 staged_id = 2;
|
||||
}
|
||||
|
||||
// An entry ID in the range [0x0000, 0xffff].
|
||||
message EntryId {
|
||||
uint32 id = 1;
|
||||
@ -222,6 +228,9 @@ message Entry {
|
||||
// The set of values defined for this entry, each corresponding to a different
|
||||
// configuration/variant.
|
||||
repeated ConfigValue config_value = 6;
|
||||
|
||||
// The staged resource ID of this finalized resource.
|
||||
StagedId staged_id = 7;
|
||||
}
|
||||
|
||||
// A Configuration/Value pair.
|
||||
|
@ -97,15 +97,15 @@ static bool IsIdDiff(const Visibility::Level& level_a, const Maybe<Id>& id_a,
|
||||
|
||||
static bool EmitResourceConfigValueDiff(
|
||||
IAaptContext* context, LoadedApk* apk_a, const ResourceTablePackageView& pkg_a,
|
||||
const ResourceTableTypeView& type_a, const ResourceEntry* entry_a,
|
||||
const ResourceTableTypeView& type_a, const ResourceTableEntryView& entry_a,
|
||||
const ResourceConfigValue* config_value_a, LoadedApk* apk_b,
|
||||
const ResourceTablePackageView& pkg_b, const ResourceTableTypeView& type_b,
|
||||
const ResourceEntry* entry_b, const ResourceConfigValue* config_value_b) {
|
||||
const ResourceTableEntryView& entry_b, const ResourceConfigValue* config_value_b) {
|
||||
Value* value_a = config_value_a->value.get();
|
||||
Value* value_b = config_value_b->value.get();
|
||||
if (!value_a->Equals(value_b)) {
|
||||
std::stringstream str_stream;
|
||||
str_stream << "value " << pkg_a.name << ":" << type_a.type << "/" << entry_a->name
|
||||
str_stream << "value " << pkg_a.name << ":" << type_a.type << "/" << entry_a.name
|
||||
<< " config=" << config_value_a->config << " does not match:\n";
|
||||
value_a->Print(&str_stream);
|
||||
str_stream << "\n vs \n";
|
||||
@ -118,32 +118,32 @@ static bool EmitResourceConfigValueDiff(
|
||||
|
||||
static bool EmitResourceEntryDiff(IAaptContext* context, LoadedApk* apk_a,
|
||||
const ResourceTablePackageView& pkg_a,
|
||||
const ResourceTableTypeView& type_a, const ResourceEntry* entry_a,
|
||||
LoadedApk* apk_b, const ResourceTablePackageView& pkg_b,
|
||||
const ResourceTableTypeView& type_a,
|
||||
const ResourceTableEntryView& entry_a, LoadedApk* apk_b,
|
||||
const ResourceTablePackageView& pkg_b,
|
||||
const ResourceTableTypeView& type_b,
|
||||
const ResourceEntry* entry_b) {
|
||||
const ResourceTableEntryView& entry_b) {
|
||||
bool diff = false;
|
||||
for (const std::unique_ptr<ResourceConfigValue>& config_value_a : entry_a->values) {
|
||||
auto config_value_b = entry_b->FindValue(config_value_a->config);
|
||||
for (const ResourceConfigValue* config_value_a : entry_a.values) {
|
||||
auto config_value_b = entry_b.FindValue(config_value_a->config);
|
||||
if (!config_value_b) {
|
||||
std::stringstream str_stream;
|
||||
str_stream << "missing " << pkg_a.name << ":" << type_a.type << "/" << entry_a->name
|
||||
str_stream << "missing " << pkg_a.name << ":" << type_a.type << "/" << entry_a.name
|
||||
<< " config=" << config_value_a->config;
|
||||
EmitDiffLine(apk_b->GetSource(), str_stream.str());
|
||||
diff = true;
|
||||
} else {
|
||||
diff |=
|
||||
EmitResourceConfigValueDiff(context, apk_a, pkg_a, type_a, entry_a, config_value_a.get(),
|
||||
apk_b, pkg_b, type_b, entry_b, config_value_b);
|
||||
diff |= EmitResourceConfigValueDiff(context, apk_a, pkg_a, type_a, entry_a, config_value_a,
|
||||
apk_b, pkg_b, type_b, entry_b, config_value_b);
|
||||
}
|
||||
}
|
||||
|
||||
// Check for any newly added config values.
|
||||
for (const std::unique_ptr<ResourceConfigValue>& config_value_b : entry_b->values) {
|
||||
auto config_value_a = entry_a->FindValue(config_value_b->config);
|
||||
for (const ResourceConfigValue* config_value_b : entry_b.values) {
|
||||
auto config_value_a = entry_a.FindValue(config_value_b->config);
|
||||
if (!config_value_a) {
|
||||
std::stringstream str_stream;
|
||||
str_stream << "new config " << pkg_b.name << ":" << type_b.type << "/" << entry_b->name
|
||||
str_stream << "new config " << pkg_b.name << ":" << type_b.type << "/" << entry_b.name
|
||||
<< " config=" << config_value_b->config;
|
||||
EmitDiffLine(apk_b->GetSource(), str_stream.str());
|
||||
diff = true;
|
||||
@ -164,36 +164,35 @@ static bool EmitResourceTypeDiff(IAaptContext* context, LoadedApk* apk_a,
|
||||
if (entry_b_iter == type_b.entries.end()) {
|
||||
// Type A contains a type that type B does not have.
|
||||
std::stringstream str_stream;
|
||||
str_stream << "missing " << pkg_a.name << ":" << type_a.type << "/" << (*entry_a_iter)->name;
|
||||
str_stream << "missing " << pkg_a.name << ":" << type_a.type << "/" << entry_a_iter->name;
|
||||
EmitDiffLine(apk_a->GetSource(), str_stream.str());
|
||||
diff = true;
|
||||
} else if (entry_a_iter == type_a.entries.end()) {
|
||||
// Type B contains a type that type A does not have.
|
||||
std::stringstream str_stream;
|
||||
str_stream << "new entry " << pkg_b.name << ":" << type_b.type << "/"
|
||||
<< (*entry_b_iter)->name;
|
||||
str_stream << "new entry " << pkg_b.name << ":" << type_b.type << "/" << entry_b_iter->name;
|
||||
EmitDiffLine(apk_b->GetSource(), str_stream.str());
|
||||
diff = true;
|
||||
} else {
|
||||
const auto& entry_a = *entry_a_iter;
|
||||
const auto& entry_b = *entry_b_iter;
|
||||
if (IsSymbolVisibilityDifferent(entry_a->visibility, entry_b->visibility)) {
|
||||
if (IsSymbolVisibilityDifferent(entry_a.visibility, entry_b.visibility)) {
|
||||
std::stringstream str_stream;
|
||||
str_stream << pkg_a.name << ":" << type_a.type << "/" << entry_a->name
|
||||
str_stream << pkg_a.name << ":" << type_a.type << "/" << entry_a.name
|
||||
<< " has different visibility (";
|
||||
if (entry_b->visibility.staged_api) {
|
||||
if (entry_b.visibility.staged_api) {
|
||||
str_stream << "STAGED ";
|
||||
}
|
||||
if (entry_b->visibility.level == Visibility::Level::kPublic) {
|
||||
if (entry_b.visibility.level == Visibility::Level::kPublic) {
|
||||
str_stream << "PUBLIC";
|
||||
} else {
|
||||
str_stream << "PRIVATE";
|
||||
}
|
||||
str_stream << " vs ";
|
||||
if (entry_a->visibility.staged_api) {
|
||||
if (entry_a.visibility.staged_api) {
|
||||
str_stream << "STAGED ";
|
||||
}
|
||||
if (entry_a->visibility.level == Visibility::Level::kPublic) {
|
||||
if (entry_a.visibility.level == Visibility::Level::kPublic) {
|
||||
str_stream << "PUBLIC";
|
||||
} else {
|
||||
str_stream << "PRIVATE";
|
||||
@ -201,19 +200,19 @@ static bool EmitResourceTypeDiff(IAaptContext* context, LoadedApk* apk_a,
|
||||
str_stream << ")";
|
||||
EmitDiffLine(apk_b->GetSource(), str_stream.str());
|
||||
diff = true;
|
||||
} else if (IsIdDiff(entry_a->visibility.level, entry_a->id, entry_b->visibility.level,
|
||||
entry_b->id)) {
|
||||
} else if (IsIdDiff(entry_a.visibility.level, entry_a.id, entry_b.visibility.level,
|
||||
entry_b.id)) {
|
||||
std::stringstream str_stream;
|
||||
str_stream << pkg_a.name << ":" << type_a.type << "/" << entry_a->name
|
||||
str_stream << pkg_a.name << ":" << type_a.type << "/" << entry_a.name
|
||||
<< " has different public ID (";
|
||||
if (entry_b->id) {
|
||||
str_stream << "0x" << std::hex << entry_b->id.value();
|
||||
if (entry_b.id) {
|
||||
str_stream << "0x" << std::hex << entry_b.id.value();
|
||||
} else {
|
||||
str_stream << "none";
|
||||
}
|
||||
str_stream << " vs ";
|
||||
if (entry_a->id) {
|
||||
str_stream << "0x " << std::hex << entry_a->id.value();
|
||||
if (entry_a.id) {
|
||||
str_stream << "0x " << std::hex << entry_a.id.value();
|
||||
} else {
|
||||
str_stream << "none";
|
||||
}
|
||||
|
@ -402,8 +402,39 @@ TEST_F(LinkTest, SharedLibraryAttributeRJava) {
|
||||
EXPECT_THAT(client_r_contents, HasSubstr(" com.example.lib.R.attr.foo, 0x7f010000"));
|
||||
}
|
||||
|
||||
TEST_F(LinkTest, StagedAndroidApi) {
|
||||
StdErrDiagnostics diag;
|
||||
struct SourceXML {
|
||||
std::string res_file_path;
|
||||
std::string file_contents;
|
||||
};
|
||||
|
||||
static void BuildApk(const std::vector<SourceXML>& source_files, const std::string& apk_path,
|
||||
LinkCommandBuilder&& link_args, CommandTestFixture* fixture,
|
||||
IDiagnostics* diag) {
|
||||
TemporaryDir res_dir;
|
||||
TemporaryDir compiled_res_dir;
|
||||
for (auto& source_file : source_files) {
|
||||
ASSERT_TRUE(fixture->CompileFile(res_dir.path + source_file.res_file_path,
|
||||
source_file.file_contents, compiled_res_dir.path, diag));
|
||||
}
|
||||
ASSERT_TRUE(fixture->Link(
|
||||
link_args.AddCompiledResDir(compiled_res_dir.path, diag).Build(apk_path), diag));
|
||||
}
|
||||
|
||||
static void BuildSDK(const std::vector<SourceXML>& source_files, const std::string& apk_path,
|
||||
const std::string& java_root_path, CommandTestFixture* fixture,
|
||||
IDiagnostics* diag) {
|
||||
auto android_manifest = ManifestBuilder(fixture).SetPackageName("android").Build();
|
||||
|
||||
auto android_link_args = LinkCommandBuilder(fixture)
|
||||
.SetManifestFile(android_manifest)
|
||||
.AddParameter("--private-symbols", "com.android.internal")
|
||||
.AddParameter("--java", java_root_path);
|
||||
|
||||
BuildApk(source_files, apk_path, std::move(android_link_args), fixture, diag);
|
||||
}
|
||||
|
||||
static void BuildNonFinalizedSDK(const std::string& apk_path, const std::string& java_path,
|
||||
CommandTestFixture* fixture, IDiagnostics* diag) {
|
||||
const std::string android_values =
|
||||
R"(<resources>
|
||||
<public type="attr" name="finalized_res" id="0x01010001"/>
|
||||
@ -413,6 +444,10 @@ TEST_F(LinkTest, StagedAndroidApi) {
|
||||
<public name="staged_s_res" />
|
||||
</staging-public-group>
|
||||
|
||||
<staging-public-group type="string" first-id="0x01fd0080">
|
||||
<public name="staged_s_string" />
|
||||
</staging-public-group>
|
||||
|
||||
<!-- SV2 staged attributes (support staged resources in a separate type id) -->
|
||||
<staging-public-group type="attr" first-id="0x01ff0049">
|
||||
<public name="staged_s2_res" />
|
||||
@ -423,46 +458,90 @@ TEST_F(LinkTest, StagedAndroidApi) {
|
||||
<public name="staged_t_res" />
|
||||
</staging-public-group>
|
||||
|
||||
<staging-public-group type="string" first-id="0x01fd0072">
|
||||
<public name="staged_t_string" />
|
||||
<attr name="finalized_res" />
|
||||
<attr name="staged_s_res" />
|
||||
<attr name="staged_s2_res" />
|
||||
<attr name="staged_t_res" />
|
||||
<string name="staged_s_string">Hello</string>
|
||||
</resources>)";
|
||||
|
||||
SourceXML source_xml{.res_file_path = "/res/values/values.xml", .file_contents = android_values};
|
||||
BuildSDK({source_xml}, apk_path, java_path, fixture, diag);
|
||||
}
|
||||
|
||||
static void BuildFinalizedSDK(const std::string& apk_path, const std::string& java_path,
|
||||
CommandTestFixture* fixture, IDiagnostics* diag) {
|
||||
const std::string android_values =
|
||||
R"(<resources>
|
||||
<public type="attr" name="finalized_res" id="0x01010001"/>
|
||||
<public type="attr" name="staged_s_res" id="0x01010002"/>
|
||||
<public type="attr" name="staged_s2_res" id="0x01010003"/>
|
||||
<public type="string" name="staged_s_string" id="0x01020000"/>
|
||||
|
||||
<!-- S staged attributes (support staged resources in the same type id) -->
|
||||
<staging-public-group-final type="attr" first-id="0x01010050">
|
||||
<public name="staged_s_res" />
|
||||
</staging-public-group-final>
|
||||
|
||||
<staging-public-group-final type="string" first-id="0x01fd0080">
|
||||
<public name="staged_s_string" />
|
||||
</staging-public-group-final>
|
||||
|
||||
<!-- SV2 staged attributes (support staged resources in a separate type id) -->
|
||||
<staging-public-group-final type="attr" first-id="0x01ff0049">
|
||||
<public name="staged_s2_res" />
|
||||
</staging-public-group-final>
|
||||
|
||||
<!-- T staged attributes (support staged resources in multiple separate type ids) -->
|
||||
<staging-public-group type="attr" first-id="0x01fe0063">
|
||||
<public name="staged_t_res" />
|
||||
</staging-public-group>
|
||||
|
||||
<attr name="finalized_res" />
|
||||
<attr name="staged_s_res" />
|
||||
<attr name="staged_s2_res" />
|
||||
<attr name="staged_t_res" />
|
||||
<string name="staged_t_string">Hello</string>
|
||||
<string name="staged_s_string">Hello</string>
|
||||
</resources>)";
|
||||
|
||||
SourceXML source_xml{.res_file_path = "/res/values/values.xml", .file_contents = android_values};
|
||||
BuildSDK({source_xml}, apk_path, java_path, fixture, diag);
|
||||
}
|
||||
|
||||
static void BuildAppAgainstSDK(const std::string& apk_path, const std::string& java_path,
|
||||
const std::string& sdk_path, CommandTestFixture* fixture,
|
||||
IDiagnostics* diag) {
|
||||
const std::string app_values =
|
||||
R"(<resources xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<attr name="bar" />
|
||||
<style name="MyStyle">
|
||||
<item name="android:staged_s_res">@android:string/staged_s_string</item>
|
||||
</style>
|
||||
<declare-styleable name="ClientStyleable">
|
||||
<attr name="android:finalized_res" />
|
||||
<attr name="android:staged_s_res" />
|
||||
<attr name="bar" />
|
||||
</declare-styleable>
|
||||
<public name="MyStyle" type="style" id="0x7f020000" />
|
||||
</resources>)";
|
||||
|
||||
const std::string android_res = GetTestPath("android-res");
|
||||
ASSERT_TRUE(
|
||||
CompileFile(GetTestPath("res/values/values.xml"), android_values, android_res, &diag));
|
||||
SourceXML source_xml{.res_file_path = "/res/values/values.xml", .file_contents = app_values};
|
||||
|
||||
auto app_manifest = ManifestBuilder(fixture).SetPackageName("com.example.app").Build();
|
||||
|
||||
auto app_link_args = LinkCommandBuilder(fixture)
|
||||
.SetManifestFile(app_manifest)
|
||||
.AddParameter("--java", java_path)
|
||||
.AddParameter("-I", sdk_path);
|
||||
|
||||
BuildApk({source_xml}, apk_path, std::move(app_link_args), fixture, diag);
|
||||
}
|
||||
|
||||
TEST_F(LinkTest, StagedAndroidApi) {
|
||||
StdErrDiagnostics diag;
|
||||
const std::string android_apk = GetTestPath("android.apk");
|
||||
const std::string android_java = GetTestPath("android_java");
|
||||
// clang-format off
|
||||
auto android_manifest = ManifestBuilder(this)
|
||||
.SetPackageName("android")
|
||||
.Build();
|
||||
|
||||
auto android_link_args = LinkCommandBuilder(this)
|
||||
.SetManifestFile(android_manifest)
|
||||
.AddParameter("--private-symbols", "com.android.internal")
|
||||
.AddParameter("--java", android_java)
|
||||
.AddCompiledResDir(android_res, &diag)
|
||||
.Build(android_apk);
|
||||
// clang-format on
|
||||
ASSERT_TRUE(Link(android_link_args, &diag));
|
||||
const std::string android_java = GetTestPath("android-java");
|
||||
BuildNonFinalizedSDK(android_apk, android_java, this, &diag);
|
||||
|
||||
const std::string android_r_java = android_java + "/android/R.java";
|
||||
std::string android_r_contents;
|
||||
@ -471,35 +550,19 @@ TEST_F(LinkTest, StagedAndroidApi) {
|
||||
EXPECT_THAT(
|
||||
android_r_contents,
|
||||
HasSubstr("public static final int staged_s_res; static { staged_s_res=0x01010050; }"));
|
||||
EXPECT_THAT(
|
||||
android_r_contents,
|
||||
HasSubstr("public static final int staged_s_string; static { staged_s_string=0x01fd0080; }"));
|
||||
EXPECT_THAT(
|
||||
android_r_contents,
|
||||
HasSubstr("public static final int staged_s2_res; static { staged_s2_res=0x01ff0049; }"));
|
||||
EXPECT_THAT(
|
||||
android_r_contents,
|
||||
HasSubstr("public static final int staged_t_res; static { staged_t_res=0x01fe0063; }"));
|
||||
EXPECT_THAT(
|
||||
android_r_contents,
|
||||
HasSubstr("public static final int staged_t_string; static { staged_t_string=0x01fd0072; }"));
|
||||
|
||||
// Build an app that uses the framework attribute in a declare-styleable
|
||||
const std::string client_res = GetTestPath("app-res");
|
||||
ASSERT_TRUE(CompileFile(GetTestPath("res/values/values.xml"), app_values, client_res, &diag));
|
||||
|
||||
const std::string app_apk = GetTestPath("app.apk");
|
||||
const std::string app_java = GetTestPath("app_java");
|
||||
// clang-format off
|
||||
auto app_manifest = ManifestBuilder(this)
|
||||
.SetPackageName("com.example.app")
|
||||
.Build();
|
||||
|
||||
auto app_link_args = LinkCommandBuilder(this)
|
||||
.SetManifestFile(app_manifest)
|
||||
.AddParameter("--java", app_java)
|
||||
.AddParameter("-I", android_apk)
|
||||
.AddCompiledResDir(client_res, &diag)
|
||||
.Build(app_apk);
|
||||
// clang-format on
|
||||
ASSERT_TRUE(Link(app_link_args, &diag));
|
||||
const std::string app_java = GetTestPath("app-java");
|
||||
BuildAppAgainstSDK(app_apk, app_java, android_apk, this, &diag);
|
||||
|
||||
const std::string client_r_java = app_java + "/com/example/app/R.java";
|
||||
std::string client_r_contents;
|
||||
@ -520,6 +583,10 @@ TEST_F(LinkTest, StagedAndroidApi) {
|
||||
ASSERT_TRUE(result.has_value());
|
||||
EXPECT_THAT(*result, Eq(0x01010050));
|
||||
|
||||
result = am.GetResourceId("android:string/staged_s_string");
|
||||
ASSERT_TRUE(result.has_value());
|
||||
EXPECT_THAT(*result, Eq(0x01fd0080));
|
||||
|
||||
result = am.GetResourceId("android:attr/staged_s2_res");
|
||||
ASSERT_TRUE(result.has_value());
|
||||
EXPECT_THAT(*result, Eq(0x01ff0049));
|
||||
@ -527,10 +594,88 @@ TEST_F(LinkTest, StagedAndroidApi) {
|
||||
result = am.GetResourceId("android:attr/staged_t_res");
|
||||
ASSERT_TRUE(result.has_value());
|
||||
EXPECT_THAT(*result, Eq(0x01fe0063));
|
||||
}
|
||||
|
||||
result = am.GetResourceId("android:string/staged_t_string");
|
||||
TEST_F(LinkTest, FinalizedAndroidApi) {
|
||||
StdErrDiagnostics diag;
|
||||
const std::string android_apk = GetTestPath("android.apk");
|
||||
const std::string android_java = GetTestPath("android-java");
|
||||
BuildFinalizedSDK(android_apk, android_java, this, &diag);
|
||||
|
||||
const std::string android_r_java = android_java + "/android/R.java";
|
||||
std::string android_r_contents;
|
||||
ASSERT_TRUE(android::base::ReadFileToString(android_r_java, &android_r_contents));
|
||||
EXPECT_THAT(android_r_contents, HasSubstr("public static final int finalized_res=0x01010001;"));
|
||||
EXPECT_THAT(android_r_contents, HasSubstr("public static final int staged_s_res=0x01010002;"));
|
||||
EXPECT_THAT(android_r_contents, HasSubstr("public static final int staged_s_string=0x01020000;"));
|
||||
EXPECT_THAT(android_r_contents, HasSubstr("public static final int staged_s2_res=0x01010003;"));
|
||||
EXPECT_THAT(
|
||||
android_r_contents,
|
||||
HasSubstr("public static final int staged_t_res; static { staged_t_res=0x01fe0063; }"));
|
||||
;
|
||||
|
||||
// Build an application against the non-finalized SDK and then load it into an AssetManager with
|
||||
// the finalized SDK.
|
||||
const std::string non_finalized_android_apk = GetTestPath("non-finalized-android.apk");
|
||||
const std::string non_finalized_android_java = GetTestPath("non-finalized-android-java");
|
||||
BuildNonFinalizedSDK(non_finalized_android_apk, non_finalized_android_java, this, &diag);
|
||||
|
||||
const std::string app_apk = GetTestPath("app.apk");
|
||||
const std::string app_java = GetTestPath("app-java");
|
||||
BuildAppAgainstSDK(app_apk, app_java, non_finalized_android_apk, this, &diag);
|
||||
|
||||
android::AssetManager2 am;
|
||||
auto android_asset = android::ApkAssets::Load(android_apk);
|
||||
auto app_against_non_final = android::ApkAssets::Load(app_apk);
|
||||
ASSERT_THAT(android_asset, NotNull());
|
||||
ASSERT_THAT(app_against_non_final, NotNull());
|
||||
ASSERT_TRUE(am.SetApkAssets({android_asset.get(), app_against_non_final.get()}));
|
||||
|
||||
auto result = am.GetResourceId("android:attr/finalized_res");
|
||||
ASSERT_TRUE(result.has_value());
|
||||
EXPECT_THAT(*result, Eq(0x01fd0072));
|
||||
EXPECT_THAT(*result, Eq(0x01010001));
|
||||
|
||||
result = am.GetResourceId("android:attr/staged_s_res");
|
||||
ASSERT_TRUE(result.has_value());
|
||||
EXPECT_THAT(*result, Eq(0x01010002));
|
||||
|
||||
result = am.GetResourceId("android:string/staged_s_string");
|
||||
ASSERT_TRUE(result.has_value());
|
||||
EXPECT_THAT(*result, Eq(0x01020000));
|
||||
|
||||
result = am.GetResourceId("android:attr/staged_s2_res");
|
||||
ASSERT_TRUE(result.has_value());
|
||||
EXPECT_THAT(*result, Eq(0x01010003));
|
||||
|
||||
{
|
||||
auto style = am.GetBag(0x7f020000);
|
||||
ASSERT_TRUE(style.has_value());
|
||||
|
||||
auto& entry = (*style)->entries[0];
|
||||
EXPECT_THAT(entry.key, Eq(0x01010002));
|
||||
EXPECT_THAT(entry.value.dataType, Eq(android::Res_value::TYPE_REFERENCE));
|
||||
EXPECT_THAT(entry.value.data, Eq(0x01020000));
|
||||
}
|
||||
|
||||
// Re-compile the application against the finalized SDK and then load it into an AssetManager with
|
||||
// the finalized SDK.
|
||||
const std::string app_apk_respin = GetTestPath("app-respin.apk");
|
||||
const std::string app_java_respin = GetTestPath("app-respin-java");
|
||||
BuildAppAgainstSDK(app_apk_respin, app_java_respin, android_apk, this, &diag);
|
||||
|
||||
auto app_against_final = android::ApkAssets::Load(app_apk_respin);
|
||||
ASSERT_THAT(app_against_final, NotNull());
|
||||
ASSERT_TRUE(am.SetApkAssets({android_asset.get(), app_against_final.get()}));
|
||||
|
||||
{
|
||||
auto style = am.GetBag(0x7f020000);
|
||||
ASSERT_TRUE(style.has_value());
|
||||
|
||||
auto& entry = (*style)->entries[0];
|
||||
EXPECT_THAT(entry.key, Eq(0x01010002));
|
||||
EXPECT_THAT(entry.value.dataType, Eq(android::Res_value::TYPE_REFERENCE));
|
||||
EXPECT_THAT(entry.value.data, Eq(0x01020000));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(LinkTest, MacroSubstitution) {
|
||||
|
@ -129,11 +129,16 @@ bool IdAssigner::Consume(IAaptContext* context, ResourceTable* table) {
|
||||
for (auto& type : package->types) {
|
||||
for (auto& entry : type->entries) {
|
||||
const ResourceName name(package->name, type->type, entry->name);
|
||||
if (entry->id) {
|
||||
if (!assigned_ids.ReserveId(name, entry->id.value(), entry->visibility,
|
||||
context->GetDiagnostics())) {
|
||||
return false;
|
||||
}
|
||||
if (entry->id && !assigned_ids.ReserveId(name, entry->id.value(), entry->visibility,
|
||||
context->GetDiagnostics())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto v = entry->visibility;
|
||||
v.staged_api = true;
|
||||
if (entry->staged_id && !assigned_ids.ReserveId(name, entry->staged_id.value().id, v,
|
||||
context->GetDiagnostics())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (assigned_id_map_) {
|
||||
@ -237,7 +242,7 @@ Result<std::monostate> TypeGroup::ReserveId(const ResourceName& name, ResourceId
|
||||
if (type_id_ != id.type_id()) {
|
||||
// Currently there cannot be multiple type ids for a single type.
|
||||
std::stringstream error;
|
||||
error << "type '" << name.type << "' already has ID " << std::hex << (int)id.type_id();
|
||||
error << "type '" << name.type << "' already has ID " << std::hex << (int)type_id_;
|
||||
return unexpected(error.str());
|
||||
}
|
||||
|
||||
|
@ -254,6 +254,12 @@ bool BinaryResourceParser::ParsePackage(const ResChunk_header* chunk) {
|
||||
}
|
||||
break;
|
||||
|
||||
case android::RES_TABLE_STAGED_ALIAS_TYPE:
|
||||
if (!ParseOverlayable(parser.chunk())) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
diag_->Warn(DiagMessage(source_)
|
||||
<< "unexpected chunk type "
|
||||
@ -489,6 +495,52 @@ bool BinaryResourceParser::ParseOverlayable(const ResChunk_header* chunk) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BinaryResourceParser::ParseStagedAliases(const ResChunk_header* chunk) {
|
||||
auto header = ConvertTo<ResTable_staged_alias_header>(chunk);
|
||||
if (!header) {
|
||||
diag_->Error(DiagMessage(source_) << "corrupt ResTable_staged_alias_header chunk");
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto ref_begin = reinterpret_cast<const ResTable_staged_alias_entry*>(
|
||||
((uint8_t*)header) + util::DeviceToHost32(header->header.headerSize));
|
||||
const auto ref_end = ref_begin + util::DeviceToHost32(header->count);
|
||||
for (auto ref_iter = ref_begin; ref_iter != ref_end; ++ref_iter) {
|
||||
const auto staged_id = ResourceId(util::DeviceToHost32(ref_iter->stagedResId));
|
||||
const auto finalized_id = ResourceId(util::DeviceToHost32(ref_iter->finalizedResId));
|
||||
|
||||
// If the staged alias chunk comes before the type chunks, the resource ids and resource name
|
||||
// pairing will not exist at this point.
|
||||
const auto iter = id_index_.find(finalized_id);
|
||||
if (iter == id_index_.cend()) {
|
||||
diag_->Error(DiagMessage(source_) << "failed to find resource name for finalized"
|
||||
<< " resource ID " << finalized_id);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set the staged if of the finalized resource.
|
||||
const auto& resource_name = iter->second;
|
||||
const StagedId staged_id_def{.id = staged_id};
|
||||
if (!table_->AddResource(NewResourceBuilder(resource_name)
|
||||
.SetId(finalized_id, OnIdConflict::CREATE_ENTRY)
|
||||
.SetStagedId(staged_id_def)
|
||||
.SetAllowMangled(true)
|
||||
.Build(),
|
||||
diag_)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Since a the finalized resource entry is cloned and added to the resource table under the
|
||||
// staged resource id, remove the cloned resource entry from the table.
|
||||
if (!table_->RemoveResource(resource_name, staged_id)) {
|
||||
diag_->Error(DiagMessage(source_) << "failed to find resource entry for staged "
|
||||
<< " resource ID " << staged_id);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::unique_ptr<Item> BinaryResourceParser::ParseValue(const ResourceNameRef& name,
|
||||
const ConfigDescription& config,
|
||||
const android::Res_value& value) {
|
||||
|
@ -57,6 +57,7 @@ class BinaryResourceParser {
|
||||
uint8_t package_id);
|
||||
bool ParseLibrary(const android::ResChunk_header* chunk);
|
||||
bool ParseOverlayable(const android::ResChunk_header* chunk);
|
||||
bool ParseStagedAliases(const android::ResChunk_header* chunk);
|
||||
|
||||
std::unique_ptr<Item> ParseValue(const ResourceNameRef& name,
|
||||
const android::ConfigDescription& config,
|
||||
|
@ -72,7 +72,7 @@ static bool cmp_style_entries(const Style::Entry* a, const Style::Entry* b) {
|
||||
}
|
||||
|
||||
struct FlatEntry {
|
||||
const ResourceEntry* entry;
|
||||
const ResourceTableEntryView* entry;
|
||||
const Value* value;
|
||||
|
||||
// The entry string pool index to the entry's name.
|
||||
@ -286,6 +286,10 @@ class PackageFlattener {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!FlattenAliases(buffer)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
pkg_writer.Finish();
|
||||
return true;
|
||||
}
|
||||
@ -351,8 +355,8 @@ class PackageFlattener {
|
||||
|
||||
BigBuffer values_buffer(512);
|
||||
for (FlatEntry& flat_entry : *entries) {
|
||||
CHECK(static_cast<size_t>(flat_entry.entry->id.value().entry_id()) < num_total_entries);
|
||||
offsets[flat_entry.entry->id.value().entry_id()] = values_buffer.size();
|
||||
CHECK(static_cast<size_t>(flat_entry.entry->id.value()) < num_total_entries);
|
||||
offsets[flat_entry.entry->id.value()] = values_buffer.size();
|
||||
if (!FlattenValue(&flat_entry, &values_buffer)) {
|
||||
diag_->Error(DiagMessage()
|
||||
<< "failed to flatten resource '"
|
||||
@ -404,6 +408,26 @@ class PackageFlattener {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FlattenAliases(BigBuffer* buffer) {
|
||||
if (aliases_.empty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
ChunkWriter alias_writer(buffer);
|
||||
auto header =
|
||||
alias_writer.StartChunk<ResTable_staged_alias_header>(RES_TABLE_STAGED_ALIAS_TYPE);
|
||||
header->count = util::HostToDevice32(aliases_.size());
|
||||
|
||||
auto mapping = alias_writer.NextBlock<ResTable_staged_alias_entry>(aliases_.size());
|
||||
for (auto& p : aliases_) {
|
||||
mapping->stagedResId = util::HostToDevice32(p.first);
|
||||
mapping->finalizedResId = util::HostToDevice32(p.second);
|
||||
++mapping;
|
||||
}
|
||||
alias_writer.Finish();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FlattenOverlayable(BigBuffer* buffer) {
|
||||
std::set<ResourceId> seen_ids;
|
||||
std::map<std::string, OverlayableChunk> overlayable_chunks;
|
||||
@ -413,18 +437,17 @@ class PackageFlattener {
|
||||
CHECK(bool(type.id)) << "type must have an ID set when flattening <overlayable>";
|
||||
for (auto& entry : type.entries) {
|
||||
CHECK(bool(type.id)) << "entry must have an ID set when flattening <overlayable>";
|
||||
if (!entry->overlayable_item) {
|
||||
if (!entry.overlayable_item) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const OverlayableItem& item = entry->overlayable_item.value();
|
||||
const OverlayableItem& item = entry.overlayable_item.value();
|
||||
|
||||
// Resource ids should only appear once in the resource table
|
||||
ResourceId id =
|
||||
android::make_resid(package_.id.value(), type.id.value(), entry->id.value().entry_id());
|
||||
ResourceId id = android::make_resid(package_.id.value(), type.id.value(), entry.id.value());
|
||||
CHECK(seen_ids.find(id) == seen_ids.end())
|
||||
<< "multiple overlayable definitions found for resource "
|
||||
<< ResourceName(package_.name, type.type, entry->name).to_string();
|
||||
<< ResourceName(package_.name, type.type, entry.name).to_string();
|
||||
seen_ids.insert(id);
|
||||
|
||||
// Find the overlayable chunk with the specified name
|
||||
@ -452,9 +475,8 @@ class PackageFlattener {
|
||||
|
||||
if (item.policies == 0) {
|
||||
context_->GetDiagnostics()->Error(DiagMessage(item.overlayable->source)
|
||||
<< "overlayable "
|
||||
<< entry->name
|
||||
<< " does not specify policy");
|
||||
<< "overlayable " << entry.name
|
||||
<< " does not specify policy");
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -520,7 +542,8 @@ class PackageFlattener {
|
||||
}
|
||||
|
||||
bool FlattenTypeSpec(const ResourceTableTypeView& type,
|
||||
const std::vector<const ResourceEntry*>& sorted_entries, BigBuffer* buffer) {
|
||||
const std::vector<ResourceTableEntryView>& sorted_entries,
|
||||
BigBuffer* buffer) {
|
||||
ChunkWriter type_spec_writer(buffer);
|
||||
ResTable_typeSpec* spec_header =
|
||||
type_spec_writer.StartChunk<ResTable_typeSpec>(RES_TABLE_TYPE_SPEC_TYPE);
|
||||
@ -534,7 +557,7 @@ class PackageFlattener {
|
||||
// We can't just take the size of the vector. There may be holes in the
|
||||
// entry ID space.
|
||||
// Since the entries are sorted by ID, the last one will be the biggest.
|
||||
const size_t num_entries = sorted_entries.back()->id.value().entry_id() + 1;
|
||||
const size_t num_entries = sorted_entries.back().id.value() + 1;
|
||||
|
||||
spec_header->entryCount = util::HostToDevice32(num_entries);
|
||||
|
||||
@ -542,23 +565,23 @@ class PackageFlattener {
|
||||
// show for which configuration axis the resource changes.
|
||||
uint32_t* config_masks = type_spec_writer.NextBlock<uint32_t>(num_entries);
|
||||
|
||||
for (const ResourceEntry* entry : sorted_entries) {
|
||||
const uint16_t entry_id = entry->id.value().entry_id();
|
||||
for (const ResourceTableEntryView& entry : sorted_entries) {
|
||||
const uint16_t entry_id = entry.id.value();
|
||||
|
||||
// Populate the config masks for this entry.
|
||||
uint32_t& entry_config_masks = config_masks[entry_id];
|
||||
if (entry->visibility.level == Visibility::Level::kPublic) {
|
||||
if (entry.visibility.level == Visibility::Level::kPublic) {
|
||||
entry_config_masks |= util::HostToDevice32(ResTable_typeSpec::SPEC_PUBLIC);
|
||||
}
|
||||
if (entry->visibility.staged_api) {
|
||||
if (entry.visibility.staged_api) {
|
||||
entry_config_masks |= util::HostToDevice32(ResTable_typeSpec::SPEC_STAGED_API);
|
||||
}
|
||||
|
||||
const size_t config_count = entry->values.size();
|
||||
const size_t config_count = entry.values.size();
|
||||
for (size_t i = 0; i < config_count; i++) {
|
||||
const ConfigDescription& config = entry->values[i]->config;
|
||||
const ConfigDescription& config = entry.values[i]->config;
|
||||
for (size_t j = i + 1; j < config_count; j++) {
|
||||
config_masks[entry_id] |= util::HostToDevice32(config.diff(entry->values[j]->config));
|
||||
config_masks[entry_id] |= util::HostToDevice32(config.diff(entry.values[j]->config));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -590,7 +613,7 @@ class PackageFlattener {
|
||||
}
|
||||
|
||||
// Since the entries are sorted by ID, the last ID will be the largest.
|
||||
const size_t num_entries = type.entries.back()->id.value().entry_id() + 1;
|
||||
const size_t num_entries = type.entries.back().id.value() + 1;
|
||||
|
||||
// The binary resource table lists resource entries for each
|
||||
// configuration.
|
||||
@ -603,20 +626,26 @@ class PackageFlattener {
|
||||
// hardcoded string uses characters which make it an invalid resource name
|
||||
const std::string obfuscated_resource_name = "0_resource_name_obfuscated";
|
||||
|
||||
for (const ResourceEntry* entry : type.entries) {
|
||||
for (const ResourceTableEntryView& entry : type.entries) {
|
||||
if (entry.staged_id) {
|
||||
aliases_.insert(std::make_pair(
|
||||
entry.staged_id.value().id.id,
|
||||
ResourceId(package_.id.value(), type.id.value(), entry.id.value()).id));
|
||||
}
|
||||
|
||||
uint32_t local_key_index;
|
||||
ResourceName resource_name({}, type.type, entry->name);
|
||||
ResourceName resource_name({}, type.type, entry.name);
|
||||
if (!collapse_key_stringpool_ ||
|
||||
name_collapse_exemptions_.find(resource_name) != name_collapse_exemptions_.end()) {
|
||||
local_key_index = (uint32_t)key_pool_.MakeRef(entry->name).index();
|
||||
local_key_index = (uint32_t)key_pool_.MakeRef(entry.name).index();
|
||||
} else {
|
||||
// resource isn't exempt from collapse, add it as obfuscated value
|
||||
local_key_index = (uint32_t)key_pool_.MakeRef(obfuscated_resource_name).index();
|
||||
}
|
||||
// Group values by configuration.
|
||||
for (auto& config_value : entry->values) {
|
||||
for (auto& config_value : entry.values) {
|
||||
config_to_entry_list_map[config_value->config].push_back(
|
||||
FlatEntry{entry, config_value->value.get(), local_key_index});
|
||||
FlatEntry{&entry, config_value->value.get(), local_key_index});
|
||||
}
|
||||
}
|
||||
|
||||
@ -667,6 +696,7 @@ class PackageFlattener {
|
||||
StringPool key_pool_;
|
||||
bool collapse_key_stringpool_;
|
||||
const std::set<ResourceName>& name_collapse_exemptions_;
|
||||
std::map<uint32_t, uint32_t> aliases_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
@ -684,7 +714,8 @@ bool TableFlattener::Consume(IAaptContext* context, ResourceTable* table) {
|
||||
});
|
||||
|
||||
// Write the ResTable header.
|
||||
const auto& table_view = table->GetPartitionedView();
|
||||
const auto& table_view =
|
||||
table->GetPartitionedView(ResourceTableViewOptions{.create_alias_entries = true});
|
||||
ChunkWriter table_writer(buffer_);
|
||||
ResTable_header* table_header = table_writer.StartChunk<ResTable_header>(RES_TABLE_TYPE);
|
||||
table_header->packageCount = util::HostToDevice32(table_view.packages.size());
|
||||
|
@ -498,10 +498,20 @@ static bool DeserializePackageFromPb(const pb::Package& pb_package, const ResStr
|
||||
out_error)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
entry->overlayable_item = std::move(overlayable_item);
|
||||
}
|
||||
|
||||
if (pb_entry.has_staged_id()) {
|
||||
const pb::StagedId& pb_staged_id = pb_entry.staged_id();
|
||||
|
||||
StagedId staged_id;
|
||||
if (pb_staged_id.has_source()) {
|
||||
DeserializeSourceFromPb(pb_staged_id.source(), src_pool, &staged_id.source);
|
||||
}
|
||||
staged_id.id = pb_staged_id.staged_id();
|
||||
entry->staged_id = std::move(staged_id);
|
||||
}
|
||||
|
||||
ResourceId resid(pb_package.package_id().id(), pb_type.type_id().id(),
|
||||
pb_entry.entry_id().id());
|
||||
if (resid.is_valid()) {
|
||||
|
@ -364,43 +364,52 @@ void SerializeTableToPb(const ResourceTable& table, pb::ResourceTable* out_table
|
||||
static const char* obfuscated_resource_name = "0_resource_name_obfuscated";
|
||||
for (const auto& entry : type.entries) {
|
||||
pb::Entry* pb_entry = pb_type->add_entry();
|
||||
if (entry->id) {
|
||||
pb_entry->mutable_entry_id()->set_id(entry->id.value().entry_id());
|
||||
if (entry.id) {
|
||||
pb_entry->mutable_entry_id()->set_id(entry.id.value());
|
||||
}
|
||||
ResourceName resource_name({}, type.type, entry->name);
|
||||
ResourceName resource_name({}, type.type, entry.name);
|
||||
if (options.collapse_key_stringpool &&
|
||||
options.name_collapse_exemptions.find(resource_name) ==
|
||||
options.name_collapse_exemptions.end()) {
|
||||
pb_entry->set_name(obfuscated_resource_name);
|
||||
} else {
|
||||
pb_entry->set_name(entry->name);
|
||||
pb_entry->set_name(entry.name);
|
||||
}
|
||||
|
||||
// Write the Visibility struct.
|
||||
pb::Visibility* pb_visibility = pb_entry->mutable_visibility();
|
||||
pb_visibility->set_staged_api(entry->visibility.staged_api);
|
||||
pb_visibility->set_level(SerializeVisibilityToPb(entry->visibility.level));
|
||||
pb_visibility->set_staged_api(entry.visibility.staged_api);
|
||||
pb_visibility->set_level(SerializeVisibilityToPb(entry.visibility.level));
|
||||
if (source_pool != nullptr) {
|
||||
SerializeSourceToPb(entry->visibility.source, source_pool.get(),
|
||||
SerializeSourceToPb(entry.visibility.source, source_pool.get(),
|
||||
pb_visibility->mutable_source());
|
||||
}
|
||||
pb_visibility->set_comment(entry->visibility.comment);
|
||||
pb_visibility->set_comment(entry.visibility.comment);
|
||||
|
||||
if (entry->allow_new) {
|
||||
if (entry.allow_new) {
|
||||
pb::AllowNew* pb_allow_new = pb_entry->mutable_allow_new();
|
||||
if (source_pool != nullptr) {
|
||||
SerializeSourceToPb(entry->allow_new.value().source, source_pool.get(),
|
||||
SerializeSourceToPb(entry.allow_new.value().source, source_pool.get(),
|
||||
pb_allow_new->mutable_source());
|
||||
}
|
||||
pb_allow_new->set_comment(entry->allow_new.value().comment);
|
||||
pb_allow_new->set_comment(entry.allow_new.value().comment);
|
||||
}
|
||||
|
||||
if (entry->overlayable_item) {
|
||||
SerializeOverlayableItemToPb(entry->overlayable_item.value(), overlayables,
|
||||
if (entry.overlayable_item) {
|
||||
SerializeOverlayableItemToPb(entry.overlayable_item.value(), overlayables,
|
||||
source_pool.get(), pb_entry, out_table);
|
||||
}
|
||||
|
||||
for (const std::unique_ptr<ResourceConfigValue>& config_value : entry->values) {
|
||||
if (entry.staged_id) {
|
||||
pb::StagedId* pb_staged_id = pb_entry->mutable_staged_id();
|
||||
if (source_pool != nullptr) {
|
||||
SerializeSourceToPb(entry.staged_id.value().source, source_pool.get(),
|
||||
pb_staged_id->mutable_source());
|
||||
}
|
||||
pb_staged_id->set_staged_id(entry.staged_id.value().id.id);
|
||||
}
|
||||
|
||||
for (const ResourceConfigValue* config_value : entry.values) {
|
||||
pb::ConfigValue* pb_config_value = pb_entry->add_config_value();
|
||||
SerializeConfig(config_value->config, pb_config_value->mutable_config());
|
||||
pb_config_value->mutable_config()->set_product(config_value->product);
|
||||
|
@ -928,4 +928,27 @@ TEST(ProtoSerializeTest, SerializeMacro) {
|
||||
EXPECT_THAT(deserialized->alias_namespaces, Eq(original->alias_namespaces));
|
||||
}
|
||||
|
||||
TEST(ProtoSerializeTest, StagedId) {
|
||||
CloningValueTransformer cloner(nullptr);
|
||||
std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
|
||||
std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
|
||||
.Add(NewResourceBuilder("com.app.a:string/foo")
|
||||
.SetStagedId(StagedId{.id = 0x01ff0001})
|
||||
.Build())
|
||||
.Build();
|
||||
|
||||
ResourceTable new_table;
|
||||
pb::ResourceTable pb_table;
|
||||
MockFileCollection files;
|
||||
std::string error;
|
||||
SerializeTableToPb(*table, &pb_table, context->GetDiagnostics());
|
||||
ASSERT_TRUE(DeserializeTableFromPb(pb_table, &files, &new_table, &error));
|
||||
EXPECT_THAT(error, IsEmpty());
|
||||
|
||||
auto result = new_table.FindResource(test::ParseNameOrDie("com.app.a:string/foo"));
|
||||
ASSERT_TRUE(result);
|
||||
ASSERT_TRUE(result.value().entry->staged_id);
|
||||
EXPECT_THAT(result.value().entry->staged_id.value().id, Eq(ResourceId(0x01ff0001)));
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
|
@ -151,6 +151,18 @@ static bool MergeEntry(IAaptContext* context, const Source& src,
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -80,9 +80,6 @@ void TestDirectoryFixture::TearDown() {
|
||||
}
|
||||
|
||||
void TestDirectoryFixture::WriteFile(const std::string& path, const std::string& contents) {
|
||||
CHECK(util::StartsWith(path, temp_dir_))
|
||||
<< "Attempting to create a file outside of test temporary directory.";
|
||||
|
||||
// Create any intermediate directories specified in the path
|
||||
auto pos = std::find(path.rbegin(), path.rend(), file::sDirSep);
|
||||
if (pos != path.rend()) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user