Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 054e4331 authored by Iurii Makhno's avatar Iurii Makhno
Browse files

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
parent ed2e1a50
Loading
Loading
Loading
Loading
+28 −0
Original line number Diff line number Diff line
@@ -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_);
+20 −0
Original line number Diff line number Diff line
@@ -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,
+77 −0
Original line number Diff line number Diff line
@@ -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
+2 −39
Original line number Diff line number Diff line
@@ -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,
+8 −0
Original line number Diff line number Diff line
@@ -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_);
  }

Loading