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
This commit is contained in:
@@ -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 @@ int Convert(IAaptContext* context, LoadedApk* apk, IArchiveWriter* output_writer
|
||||
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 @@ int ConvertCommand::Action(const std::vector<std::string>& args) {
|
||||
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_);
|
||||
|
||||
@@ -50,6 +50,25 @@ class ConvertCommand : public Command {
|
||||
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 @@ class ConvertCommand : public Command {
|
||||
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,
|
||||
|
||||
@@ -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 @@ TEST_F(ConvertTest, DuplicateEntriesWrittenOnce) {
|
||||
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
|
||||
|
||||
@@ -305,51 +305,14 @@ class Optimizer {
|
||||
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,
|
||||
|
||||
@@ -123,6 +123,14 @@ class OptimizeCommand : public Command {
|
||||
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_);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -447,4 +447,41 @@ std::regex GetRegularExpression(const std::string &input) {
|
||||
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
|
||||
|
||||
@@ -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 @@ void SetLongVersionCode(xml::Element* manifest, uint64_t version_code);
|
||||
// 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 */
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include "util/Files.h"
|
||||
|
||||
using ::android::ConfigDescription;
|
||||
using testing::UnorderedElementsAre;
|
||||
|
||||
namespace aapt {
|
||||
|
||||
@@ -411,4 +412,61 @@ TEST (UtilTest, RegularExpressionNonEnglish) {
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user