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
diff --git a/tools/aapt2/Debug.cpp b/tools/aapt2/Debug.cpp
index 77c0872..ef3a62f 100644
--- a/tools/aapt2/Debug.cpp
+++ b/tools/aapt2/Debug.cpp
@@ -278,17 +278,19 @@
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 @@
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(") ");
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index 1efabbb..f1e2da9 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -45,6 +45,7 @@
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 @@
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 @@
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 @@
{"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 @@
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 @@
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::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)
diff --git a/tools/aapt2/ResourceParser.h b/tools/aapt2/ResourceParser.h
index 5c92def..2614997 100644
--- a/tools/aapt2/ResourceParser.h
+++ b/tools/aapt2/ResourceParser.h
@@ -115,6 +115,7 @@
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);
diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp
index 45ea654..8ab1493 100644
--- a/tools/aapt2/ResourceTable.cpp
+++ b/tools/aapt2/ResourceTable.cpp
@@ -83,6 +83,20 @@
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 @@
});
}
-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 @@
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 @@
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 @@
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 @@
if (found) {
return &*it;
}
- return &*el.insert(it, std::move(value));
+ return &*el.insert(it, std::forward<T>(value));
}
};
@@ -331,35 +333,77 @@
};
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 @@
// 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 @@
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 @@
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 @@
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;
diff --git a/tools/aapt2/ResourceTable.h b/tools/aapt2/ResourceTable.h
index 080ecc2..bae1d82 100644
--- a/tools/aapt2/ResourceTable.h
+++ b/tools/aapt2/ResourceTable.h
@@ -64,6 +64,12 @@
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 @@
// 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 @@
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 @@
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 @@
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 @@
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 @@
// 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 @@
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
diff --git a/tools/aapt2/Resources.proto b/tools/aapt2/Resources.proto
index b45c040..95b7949 100644
--- a/tools/aapt2/Resources.proto
+++ b/tools/aapt2/Resources.proto
@@ -190,6 +190,12 @@
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 @@
// 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.
diff --git a/tools/aapt2/cmd/Diff.cpp b/tools/aapt2/cmd/Diff.cpp
index df31087..3950f33 100644
--- a/tools/aapt2/cmd/Diff.cpp
+++ b/tools/aapt2/cmd/Diff.cpp
@@ -97,15 +97,15 @@
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 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 @@
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 @@
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";
}
diff --git a/tools/aapt2/cmd/Link_test.cpp b/tools/aapt2/cmd/Link_test.cpp
index 3118eb8..430c184 100644
--- a/tools/aapt2/cmd/Link_test.cpp
+++ b/tools/aapt2/cmd/Link_test.cpp
@@ -402,8 +402,39 @@
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 @@
<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 @@
<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;
@@ -473,33 +552,17 @@
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 @@
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 @@
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) {
diff --git a/tools/aapt2/compile/IdAssigner.cpp b/tools/aapt2/compile/IdAssigner.cpp
index 9a50b26..339b8af 100644
--- a/tools/aapt2/compile/IdAssigner.cpp
+++ b/tools/aapt2/compile/IdAssigner.cpp
@@ -129,11 +129,16 @@
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 @@
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());
}
diff --git a/tools/aapt2/format/binary/BinaryResourceParser.cpp b/tools/aapt2/format/binary/BinaryResourceParser.cpp
index f1b350f..2ec01cd 100644
--- a/tools/aapt2/format/binary/BinaryResourceParser.cpp
+++ b/tools/aapt2/format/binary/BinaryResourceParser.cpp
@@ -254,6 +254,12 @@
}
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 @@
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) {
diff --git a/tools/aapt2/format/binary/BinaryResourceParser.h b/tools/aapt2/format/binary/BinaryResourceParser.h
index 13dd982..cd71d16 100644
--- a/tools/aapt2/format/binary/BinaryResourceParser.h
+++ b/tools/aapt2/format/binary/BinaryResourceParser.h
@@ -57,6 +57,7 @@
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,
diff --git a/tools/aapt2/format/binary/TableFlattener.cpp b/tools/aapt2/format/binary/TableFlattener.cpp
index 74ecf47..a9192e8 100644
--- a/tools/aapt2/format/binary/TableFlattener.cpp
+++ b/tools/aapt2/format/binary/TableFlattener.cpp
@@ -72,7 +72,7 @@
}
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 @@
return false;
}
+ if (!FlattenAliases(buffer)) {
+ return false;
+ }
+
pkg_writer.Finish();
return true;
}
@@ -351,8 +355,8 @@
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 @@
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 @@
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 @@
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 @@
}
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 @@
// 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 @@
// 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 @@
}
// 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 @@
// 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 @@
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 @@
});
// 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());
diff --git a/tools/aapt2/format/proto/ProtoDeserialize.cpp b/tools/aapt2/format/proto/ProtoDeserialize.cpp
index ec331df..236c381 100644
--- a/tools/aapt2/format/proto/ProtoDeserialize.cpp
+++ b/tools/aapt2/format/proto/ProtoDeserialize.cpp
@@ -498,10 +498,20 @@
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()) {
diff --git a/tools/aapt2/format/proto/ProtoSerialize.cpp b/tools/aapt2/format/proto/ProtoSerialize.cpp
index d2f0336..6042ba8 100644
--- a/tools/aapt2/format/proto/ProtoSerialize.cpp
+++ b/tools/aapt2/format/proto/ProtoSerialize.cpp
@@ -364,43 +364,52 @@
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);
diff --git a/tools/aapt2/format/proto/ProtoSerialize_test.cpp b/tools/aapt2/format/proto/ProtoSerialize_test.cpp
index e563eda..38c811f 100644
--- a/tools/aapt2/format/proto/ProtoSerialize_test.cpp
+++ b/tools/aapt2/format/proto/ProtoSerialize_test.cpp
@@ -928,4 +928,27 @@
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
diff --git a/tools/aapt2/link/TableMerger.cpp b/tools/aapt2/link/TableMerger.cpp
index bc93ec6..22f4d18 100644
--- a/tools/aapt2/link/TableMerger.cpp
+++ b/tools/aapt2/link/TableMerger.cpp
@@ -151,6 +151,18 @@
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;
}
diff --git a/tools/aapt2/test/Fixture.cpp b/tools/aapt2/test/Fixture.cpp
index f94f0fe..285e5a1 100644
--- a/tools/aapt2/test/Fixture.cpp
+++ b/tools/aapt2/test/Fixture.cpp
@@ -80,9 +80,6 @@
}
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()) {