Expose flags for collapse resource name to 'convert' command.
To achieve this ParseResourceConfig is extracted to Utils.cpp and tests
are moved from Optimize_test.cpp to Util_test.cpp.
Bug: b/249793372
Test: Util_test, Convert_test
Change-Id: I5a0458e3834d5ea62c96013abc14527285e895e0
diff --git a/tools/aapt2/cmd/Convert.cpp b/tools/aapt2/cmd/Convert.cpp
index aeedf8b..52e113e 100644
--- a/tools/aapt2/cmd/Convert.cpp
+++ b/tools/aapt2/cmd/Convert.cpp
@@ -21,7 +21,9 @@
#include "Diagnostics.h"
#include "LoadedApk.h"
#include "ValueVisitor.h"
+#include "android-base/file.h"
#include "android-base/macros.h"
+#include "android-base/stringprintf.h"
#include "androidfw/StringPiece.h"
#include "cmd/Util.h"
#include "format/binary/TableFlattener.h"
@@ -353,6 +355,27 @@
return 0;
}
+bool ExtractResourceConfig(const std::string& path, IAaptContext* context,
+ TableFlattenerOptions& out_options) {
+ std::string content;
+ if (!android::base::ReadFileToString(path, &content, true /*follow_symlinks*/)) {
+ context->GetDiagnostics()->Error(android::DiagMessage(path) << "failed reading config file");
+ return false;
+ }
+ std::unordered_set<ResourceName> resources_exclude_list;
+ bool result = ParseResourceConfig(content, context, resources_exclude_list,
+ out_options.name_collapse_exemptions);
+ if (!result) {
+ return false;
+ }
+ if (!resources_exclude_list.empty()) {
+ context->GetDiagnostics()->Error(android::DiagMessage(path)
+ << "Unsupported '#remove' directive in resource config.");
+ return false;
+ }
+ return true;
+}
+
const char* ConvertCommand::kOutputFormatProto = "proto";
const char* ConvertCommand::kOutputFormatBinary = "binary";
@@ -401,6 +424,11 @@
if (force_sparse_encoding_) {
table_flattener_options_.sparse_entries = SparseEntriesMode::Forced;
}
+ if (resources_config_path_) {
+ if (!ExtractResourceConfig(*resources_config_path_, &context, table_flattener_options_)) {
+ return 1;
+ }
+ }
return Convert(&context, apk.get(), writer.get(), format, table_flattener_options_,
xml_flattener_options_);
diff --git a/tools/aapt2/cmd/Convert.h b/tools/aapt2/cmd/Convert.h
index 6c09649..15fe11f 100644
--- a/tools/aapt2/cmd/Convert.h
+++ b/tools/aapt2/cmd/Convert.h
@@ -50,6 +50,25 @@
android::base::StringPrintf("Preserve raw attribute values in xml files when using the"
" '%s' output format", kOutputFormatBinary),
&xml_flattener_options_.keep_raw_values);
+ AddOptionalFlag("--resources-config-path",
+ "Path to the resources.cfg file containing the list of resources and \n"
+ "directives to each resource. \n"
+ "Format: type/resource_name#[directive][,directive]",
+ &resources_config_path_);
+ AddOptionalSwitch(
+ "--collapse-resource-names",
+ "Collapses resource names to a single value in the key string pool. Resources can \n"
+ "be exempted using the \"no_collapse\" directive in a file specified by "
+ "--resources-config-path.",
+ &table_flattener_options_.collapse_key_stringpool);
+ AddOptionalSwitch(
+ "--deduplicate-entry-values",
+ "Whether to deduplicate pairs of resource entry and value for simple resources.\n"
+ "This is recommended to be used together with '--collapse-resource-names' flag or for\n"
+ "APKs where resource names are manually collapsed. For such APKs this flag allows to\n"
+ "store the same resource value only once in resource table which decreases APK size.\n"
+ "Has no effect on APKs where resource names are kept.",
+ &table_flattener_options_.deduplicate_entry_values);
AddOptionalSwitch("-v", "Enables verbose logging", &verbose_);
}
@@ -66,6 +85,7 @@
bool verbose_ = false;
bool enable_sparse_encoding_ = false;
bool force_sparse_encoding_ = false;
+ std::optional<std::string> resources_config_path_;
};
int Convert(IAaptContext* context, LoadedApk* input, IArchiveWriter* output_writer,
diff --git a/tools/aapt2/cmd/Convert_test.cpp b/tools/aapt2/cmd/Convert_test.cpp
index 27df8c1..2c9388b 100644
--- a/tools/aapt2/cmd/Convert_test.cpp
+++ b/tools/aapt2/cmd/Convert_test.cpp
@@ -17,13 +17,18 @@
#include "Convert.h"
#include "LoadedApk.h"
+#include "test/Common.h"
#include "test/Test.h"
#include "ziparchive/zip_archive.h"
+using testing::AnyOfArray;
using testing::Eq;
using testing::Ne;
+using testing::Not;
+using testing::SizeIs;
namespace aapt {
+using namespace aapt::test;
using ConvertTest = CommandTestFixture;
@@ -145,4 +150,76 @@
EXPECT_THAT(count, Eq(1));
}
+TEST_F(ConvertTest, ConvertWithResourceNameCollapsing) {
+ StdErrDiagnostics diag;
+ const std::string compiled_files_dir = GetTestPath("compiled");
+ ASSERT_TRUE(CompileFile(GetTestPath("res/values/values.xml"),
+ R"(<resources>
+ <string name="first">string</string>
+ <string name="second">string</string>
+ <string name="third">another string</string>
+
+ <bool name="bool1">true</bool>
+ <bool name="bool2">true</bool>
+ <bool name="bool3">true</bool>
+
+ <integer name="int1">10</integer>
+ <integer name="int2">10</integer>
+ </resources>)",
+ compiled_files_dir, &diag));
+ std::string resource_config_path = GetTestPath("resource-config");
+ WriteFile(resource_config_path, "integer/int1#no_collapse\ninteger/int2#no_collapse");
+
+ const std::string proto_apk = GetTestPath("proto.apk");
+ std::vector<std::string> link_args = {
+ "--proto-format", "--manifest", GetDefaultManifest(kDefaultPackageName), "-o", proto_apk,
+ };
+ ASSERT_TRUE(Link(link_args, compiled_files_dir, &diag));
+
+ const std::string binary_apk = GetTestPath("binary.apk");
+ std::vector<android::StringPiece> convert_args = {"-o",
+ binary_apk,
+ "--output-format",
+ "binary",
+ "--collapse-resource-names",
+ "--deduplicate-entry-values",
+ "--resources-config-path",
+ resource_config_path,
+ proto_apk};
+ ASSERT_THAT(ConvertCommand().Execute(convert_args, &std::cerr), Eq(0));
+
+ std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(binary_apk, &diag);
+ for (const auto& package : apk->GetResourceTable()->packages) {
+ for (const auto& type : package->types) {
+ switch (type->named_type.type) {
+ case ResourceType::kBool:
+ EXPECT_THAT(type->entries, SizeIs(3));
+ for (const auto& entry : type->entries) {
+ auto value = ValueCast<BinaryPrimitive>(entry->FindValue({})->value.get())->value;
+ EXPECT_THAT(value.data, Eq(0xffffffffu));
+ }
+ break;
+ case ResourceType::kString:
+ EXPECT_THAT(type->entries, SizeIs(3));
+ for (const auto& entry : type->entries) {
+ auto value = ValueCast<String>(entry->FindValue({})->value.get())->value;
+ EXPECT_THAT(entry->name, Not(AnyOfArray({"first", "second", "third"})));
+ EXPECT_THAT(*value, AnyOfArray({"string", "another string"}));
+ }
+ break;
+ case ResourceType::kInteger:
+ EXPECT_THAT(type->entries, SizeIs(2));
+ for (const auto& entry : type->entries) {
+ auto value = ValueCast<BinaryPrimitive>(entry->FindValue({})->value.get())->value;
+ EXPECT_THAT(entry->name, AnyOfArray({"int1", "int2"}));
+ EXPECT_THAT(value.data, Eq(10));
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ }
+}
+
} // namespace aapt
diff --git a/tools/aapt2/cmd/Optimize.cpp b/tools/aapt2/cmd/Optimize.cpp
index 9feaf52..042926c 100644
--- a/tools/aapt2/cmd/Optimize.cpp
+++ b/tools/aapt2/cmd/Optimize.cpp
@@ -305,51 +305,14 @@
OptimizeContext* context_;
};
-bool ParseConfig(const std::string& content, IAaptContext* context, OptimizeOptions* options) {
- size_t line_no = 0;
- for (StringPiece line : util::Tokenize(content, '\n')) {
- line_no++;
- line = util::TrimWhitespace(line);
- if (line.empty()) {
- continue;
- }
-
- auto split_line = util::Split(line, '#');
- if (split_line.size() < 2) {
- context->GetDiagnostics()->Error(android::DiagMessage(line) << "No # found in line");
- return false;
- }
- StringPiece resource_string = split_line[0];
- StringPiece directives = split_line[1];
- ResourceNameRef resource_name;
- if (!ResourceUtils::ParseResourceName(resource_string, &resource_name)) {
- context->GetDiagnostics()->Error(android::DiagMessage(line) << "Malformed resource name");
- return false;
- }
- if (!resource_name.package.empty()) {
- context->GetDiagnostics()->Error(android::DiagMessage(line)
- << "Package set for resource. Only use type/name");
- return false;
- }
- for (StringPiece directive : util::Tokenize(directives, ',')) {
- if (directive == "remove") {
- options->resources_exclude_list.insert(resource_name.ToResourceName());
- } else if (directive == "no_collapse" || directive == "no_obfuscate") {
- options->table_flattener_options.name_collapse_exemptions.insert(
- resource_name.ToResourceName());
- }
- }
- }
- return true;
-}
-
bool ExtractConfig(const std::string& path, IAaptContext* context, OptimizeOptions* options) {
std::string content;
if (!android::base::ReadFileToString(path, &content, true /*follow_symlinks*/)) {
context->GetDiagnostics()->Error(android::DiagMessage(path) << "failed reading config file");
return false;
}
- return ParseConfig(content, context, options);
+ return ParseResourceConfig(content, context, options->resources_exclude_list,
+ options->table_flattener_options.name_collapse_exemptions);
}
bool ExtractAppDataFromManifest(OptimizeContext* context, const LoadedApk* apk,
diff --git a/tools/aapt2/cmd/Optimize.h b/tools/aapt2/cmd/Optimize.h
index 790bb74..794a87b 100644
--- a/tools/aapt2/cmd/Optimize.h
+++ b/tools/aapt2/cmd/Optimize.h
@@ -123,6 +123,14 @@
AddOptionalFlag("--resource-path-shortening-map",
"Path to output the map of old resource paths to shortened paths.",
&options_.shortened_paths_map_path);
+ AddOptionalSwitch(
+ "--deduplicate-entry-values",
+ "Whether to deduplicate pairs of resource entry and value for simple resources.\n"
+ "This is recommended to be used together with '--collapse-resource-names' flag or for\n"
+ "APKs where resource names are manually collapsed. For such APKs this flag allows to\n"
+ "store the same resource value only once in resource table which decreases APK size.\n"
+ "Has no effect on APKs where resource names are kept.",
+ &options_.table_flattener_options.deduplicate_entry_values);
AddOptionalSwitch("-v", "Enables verbose logging", &verbose_);
}
diff --git a/tools/aapt2/cmd/Optimize_test.cpp b/tools/aapt2/cmd/Optimize_test.cpp
deleted file mode 100644
index d180c87..0000000
--- a/tools/aapt2/cmd/Optimize_test.cpp
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "Optimize.h"
-
-#include "AppInfo.h"
-#include "LoadedApk.h"
-#include "Resource.h"
-#include "androidfw/IDiagnostics.h"
-#include "test/Test.h"
-
-using testing::Contains;
-using testing::Eq;
-
-namespace aapt {
-
-bool ParseConfig(const std::string&, IAaptContext*, OptimizeOptions*);
-
-using OptimizeTest = CommandTestFixture;
-
-TEST_F(OptimizeTest, ParseConfigWithNoCollapseExemptions) {
- const std::string& content = R"(
-string/foo#no_collapse
-dimen/bar#no_collapse
-)";
- aapt::test::Context context;
- OptimizeOptions options;
- ParseConfig(content, &context, &options);
-
- const std::set<ResourceName>& name_collapse_exemptions =
- options.table_flattener_options.name_collapse_exemptions;
-
- ASSERT_THAT(name_collapse_exemptions.size(), Eq(2));
- EXPECT_THAT(name_collapse_exemptions, Contains(ResourceName({}, ResourceType::kString, "foo")));
- EXPECT_THAT(name_collapse_exemptions, Contains(ResourceName({}, ResourceType::kDimen, "bar")));
-}
-
-TEST_F(OptimizeTest, ParseConfigWithNoObfuscateExemptions) {
- const std::string& content = R"(
-string/foo#no_obfuscate
-dimen/bar#no_obfuscate
-)";
- aapt::test::Context context;
- OptimizeOptions options;
- ParseConfig(content, &context, &options);
-
- const std::set<ResourceName>& name_collapse_exemptions =
- options.table_flattener_options.name_collapse_exemptions;
-
- ASSERT_THAT(name_collapse_exemptions.size(), Eq(2));
- EXPECT_THAT(name_collapse_exemptions, Contains(ResourceName({}, ResourceType::kString, "foo")));
- EXPECT_THAT(name_collapse_exemptions, Contains(ResourceName({}, ResourceType::kDimen, "bar")));
-}
-
-} // namespace aapt
diff --git a/tools/aapt2/cmd/Util.cpp b/tools/aapt2/cmd/Util.cpp
index c3a6ed1..56e2f52 100644
--- a/tools/aapt2/cmd/Util.cpp
+++ b/tools/aapt2/cmd/Util.cpp
@@ -447,4 +447,41 @@
return case_insensitive;
}
+bool ParseResourceConfig(const std::string& content, IAaptContext* context,
+ std::unordered_set<ResourceName>& out_resource_exclude_list,
+ std::set<ResourceName>& out_name_collapse_exemptions) {
+ for (StringPiece line : util::Tokenize(content, '\n')) {
+ line = util::TrimWhitespace(line);
+ if (line.empty()) {
+ continue;
+ }
+
+ auto split_line = util::Split(line, '#');
+ if (split_line.size() < 2) {
+ context->GetDiagnostics()->Error(android::DiagMessage(line) << "No # found in line");
+ return false;
+ }
+ StringPiece resource_string = split_line[0];
+ StringPiece directives = split_line[1];
+ ResourceNameRef resource_name;
+ if (!ResourceUtils::ParseResourceName(resource_string, &resource_name)) {
+ context->GetDiagnostics()->Error(android::DiagMessage(line) << "Malformed resource name");
+ return false;
+ }
+ if (!resource_name.package.empty()) {
+ context->GetDiagnostics()->Error(android::DiagMessage(line)
+ << "Package set for resource. Only use type/name");
+ return false;
+ }
+ for (StringPiece directive : util::Tokenize(directives, ',')) {
+ if (directive == "remove") {
+ out_resource_exclude_list.insert(resource_name.ToResourceName());
+ } else if (directive == "no_collapse" || directive == "no_obfuscate") {
+ out_name_collapse_exemptions.insert(resource_name.ToResourceName());
+ }
+ }
+ }
+ return true;
+}
+
} // namespace aapt
diff --git a/tools/aapt2/cmd/Util.h b/tools/aapt2/cmd/Util.h
index 7af27f5..3d4ca24 100644
--- a/tools/aapt2/cmd/Util.h
+++ b/tools/aapt2/cmd/Util.h
@@ -18,12 +18,15 @@
#define AAPT_SPLIT_UTIL_H
#include <regex>
+#include <set>
+#include <unordered_set>
#include "AppInfo.h"
#include "SdkConstants.h"
#include "androidfw/IDiagnostics.h"
#include "androidfw/StringPiece.h"
#include "filter/ConfigFilter.h"
+#include "process/IResourceTableConsumer.h"
#include "split/TableSplitter.h"
#include "xml/XmlDom.h"
@@ -76,6 +79,10 @@
// Returns a case insensitive regular expression based on the input.
std::regex GetRegularExpression(const std::string &input);
+bool ParseResourceConfig(const std::string& content, IAaptContext* context,
+ std::unordered_set<ResourceName>& out_resource_exclude_list,
+ std::set<ResourceName>& out_name_collapse_exemptions);
+
} // namespace aapt
#endif /* AAPT_SPLIT_UTIL_H */
diff --git a/tools/aapt2/cmd/Util_test.cpp b/tools/aapt2/cmd/Util_test.cpp
index 91accfe..28a6de8 100644
--- a/tools/aapt2/cmd/Util_test.cpp
+++ b/tools/aapt2/cmd/Util_test.cpp
@@ -25,6 +25,7 @@
#include "util/Files.h"
using ::android::ConfigDescription;
+using testing::UnorderedElementsAre;
namespace aapt {
@@ -411,4 +412,61 @@
EXPECT_FALSE(std::regex_search("file.koncowka", expression));
}
+TEST(UtilTest, ParseConfigWithDirectives) {
+ const std::string& content = R"(
+bool/remove_me#remove
+bool/keep_name#no_collapse
+string/foo#no_obfuscate
+dimen/bar#no_obfuscate
+)";
+ aapt::test::Context context;
+ std::unordered_set<ResourceName> resource_exclusion;
+ std::set<ResourceName> name_collapse_exemptions;
+
+ EXPECT_TRUE(ParseResourceConfig(content, &context, resource_exclusion, name_collapse_exemptions));
+
+ EXPECT_THAT(name_collapse_exemptions,
+ UnorderedElementsAre(ResourceName({}, ResourceType::kString, "foo"),
+ ResourceName({}, ResourceType::kDimen, "bar"),
+ ResourceName({}, ResourceType::kBool, "keep_name")));
+ EXPECT_THAT(resource_exclusion,
+ UnorderedElementsAre(ResourceName({}, ResourceType::kBool, "remove_me")));
+}
+
+TEST(UtilTest, ParseConfigResourceWithPackage) {
+ const std::string& content = R"(
+package:bool/remove_me#remove
+)";
+ aapt::test::Context context;
+ std::unordered_set<ResourceName> resource_exclusion;
+ std::set<ResourceName> name_collapse_exemptions;
+
+ EXPECT_FALSE(
+ ParseResourceConfig(content, &context, resource_exclusion, name_collapse_exemptions));
+}
+
+TEST(UtilTest, ParseConfigInvalidName) {
+ const std::string& content = R"(
+package:bool/1231#remove
+)";
+ aapt::test::Context context;
+ std::unordered_set<ResourceName> resource_exclusion;
+ std::set<ResourceName> name_collapse_exemptions;
+
+ EXPECT_FALSE(
+ ParseResourceConfig(content, &context, resource_exclusion, name_collapse_exemptions));
+}
+
+TEST(UtilTest, ParseConfigNoHash) {
+ const std::string& content = R"(
+package:bool/my_bool
+)";
+ aapt::test::Context context;
+ std::unordered_set<ResourceName> resource_exclusion;
+ std::set<ResourceName> name_collapse_exemptions;
+
+ EXPECT_FALSE(
+ ParseResourceConfig(content, &context, resource_exclusion, name_collapse_exemptions));
+}
+
} // namespace aapt