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

Commit 5766943f authored by Shane Farmer's avatar Shane Farmer
Browse files

AAPT2: Split APK by ABI.

Added a FilterChain that can apply multiple filter steps to an APK file
as it is being written to disk. The first filter applied is by ABI. If
a library in the APK does not match the filter it is skipped.

Added an AbiFilter that keeps files that are either not native libs or
are for the set of wanted ABIs

Test: ran unit tests locally
Test: ran against an APK with ARM and x68 libs and diffed the results

Change-Id: I3fb901d3de3513e85f2a2763a8e4487a28ed4881
parent c10f9d81
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -85,6 +85,7 @@ cc_library_host_static {
        "compile/Pseudolocalizer.cpp",
        "compile/XmlIdCollector.cpp",
        "configuration/ConfigurationParser.cpp",
        "filter/AbiFilter.cpp",
        "filter/ConfigFilter.cpp",
        "flatten/Archive.cpp",
        "flatten/TableFlattener.cpp",
+13 −0
Original line number Diff line number Diff line
@@ -57,6 +57,12 @@ std::unique_ptr<LoadedApk> LoadedApk::LoadApkFromPath(IAaptContext* context,

bool LoadedApk::WriteToArchive(IAaptContext* context, const TableFlattenerOptions& options,
                               IArchiveWriter* writer) {
  FilterChain empty;
  return WriteToArchive(context, options, &empty, writer);
}

bool LoadedApk::WriteToArchive(IAaptContext* context, const TableFlattenerOptions& options,
                               FilterChain* filters, IArchiveWriter* writer) {
  std::set<std::string> referenced_resources;
  // List the files being referenced in the resource table.
  for (auto& pkg : table_->packages) {
@@ -89,6 +95,13 @@ bool LoadedApk::WriteToArchive(IAaptContext* context, const TableFlattenerOption
      continue;
    }

    if (!filters->Keep(path)) {
      if (context->IsVerbose()) {
        context->GetDiagnostics()->Note(DiagMessage() << "Filtered '" << path << "' from APK.");
      }
      continue;
    }

    // The resource table needs to be re-serialized since it might have changed.
    if (path == "resources.arsc") {
      BigBuffer buffer(4096);
+9 −0
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@
#include "androidfw/StringPiece.h"

#include "ResourceTable.h"
#include "filter/Filter.h"
#include "flatten/Archive.h"
#include "flatten/TableFlattener.h"
#include "io/ZipArchive.h"
@@ -49,6 +50,14 @@ class LoadedApk {
  bool WriteToArchive(IAaptContext* context, const TableFlattenerOptions& options,
                      IArchiveWriter* writer);

  /**
   * Writes the APK on disk at the given path, while also removing the resource
   * files that are not referenced in the resource table. The provided filter
   * chain is applied to each entry in the APK file.
   */
  bool WriteToArchive(IAaptContext* context, const TableFlattenerOptions& options,
                      FilterChain* filters, IArchiveWriter* writer);

  static std::unique_ptr<LoadedApk> LoadApkFromPath(IAaptContext* context,
                                                    const android::StringPiece& path);

+83 −9
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
#include <memory>
#include <vector>

#include "android-base/stringprintf.h"
#include "androidfw/StringPiece.h"

#include "Diagnostics.h"
@@ -26,6 +27,8 @@
#include "SdkConstants.h"
#include "ValueVisitor.h"
#include "cmd/Util.h"
#include "configuration/ConfigurationParser.h"
#include "filter/AbiFilter.h"
#include "flatten/TableFlattener.h"
#include "flatten/XmlFlattener.h"
#include "io/BigBufferInputStream.h"
@@ -33,14 +36,21 @@
#include "optimize/ResourceDeduper.h"
#include "optimize/VersionCollapser.h"
#include "split/TableSplitter.h"
#include "util/Files.h"

using android::StringPiece;
using ::aapt::configuration::Abi;
using ::aapt::configuration::Artifact;
using ::aapt::configuration::Configuration;
using ::android::StringPiece;
using ::android::base::StringPrintf;

namespace aapt {

struct OptimizeOptions {
  // Path to the output APK.
  std::string output_path;
  Maybe<std::string> output_path;
  // Path to the output APK directory for splits.
  Maybe<std::string> output_dir;

  // Details of the app extracted from the AndroidManifest.xml
  AppInfo app_info;
@@ -55,6 +65,9 @@ struct OptimizeOptions {
  std::vector<SplitConstraints> split_constraints;

  TableFlattenerOptions table_flattener_options;

  // TODO: Come up with a better name for the Configuration struct.
  Maybe<Configuration> configuration;
};

class OptimizeContext : public IAaptContext {
@@ -175,11 +188,53 @@ class OptimizeCommand {
      ++split_constraints_iter;
    }

    if (options_.configuration && options_.output_dir) {
      Configuration& config = options_.configuration.value();

      // For now, just write out the stripped APK since ABI splitting doesn't modify anything else.
      for (const Artifact& artifact : config.artifacts) {
        if (artifact.abi_group) {
          const std::string& group = artifact.abi_group.value();

          auto abi_group = config.abi_groups.find(group);
          // TODO: Remove validation when configuration parser ensures referential integrity.
          if (abi_group == config.abi_groups.end()) {
            context_->GetDiagnostics()->Note(
                DiagMessage() << "could not find referenced ABI group '" << group << "'");
            return 1;
          }
          FilterChain filters;
          filters.AddFilter(AbiFilter::FromAbiList(abi_group->second));

          const std::string& path = apk->GetSource().path;
          const StringPiece ext = file::GetExtension(path);
          const std::string name = path.substr(0, path.rfind(ext.to_string()));

          // Name is hard coded for now since only one split dimension is supported.
          // TODO: Incorporate name generation into the configuration objects.
          const std::string file_name =
              StringPrintf("%s.%s%s", name.c_str(), group.c_str(), ext.data());
          std::string out = options_.output_dir.value();
          file::AppendPath(&out, file_name);

          std::unique_ptr<IArchiveWriter> writer =
              CreateZipFileArchiveWriter(context_->GetDiagnostics(), out);

          if (!apk->WriteToArchive(context_, options_.table_flattener_options, &filters,
                                   writer.get())) {
            return 1;
          }
        }
      }
    }

    if (options_.output_path) {
      std::unique_ptr<IArchiveWriter> writer =
        CreateZipFileArchiveWriter(context_->GetDiagnostics(), options_.output_path);
          CreateZipFileArchiveWriter(context_->GetDiagnostics(), options_.output_path.value());
      if (!apk->WriteToArchive(context_, options_.table_flattener_options, writer.get())) {
        return 1;
      }
    }

    return 0;
  }
@@ -293,13 +348,16 @@ bool ExtractAppDataFromManifest(OptimizeContext* context, LoadedApk* apk,
int Optimize(const std::vector<StringPiece>& args) {
  OptimizeContext context;
  OptimizeOptions options;
  Maybe<std::string> config_path;
  Maybe<std::string> target_densities;
  std::vector<std::string> configs;
  std::vector<std::string> split_args;
  bool verbose = false;
  Flags flags =
      Flags()
          .RequiredFlag("-o", "Path to the output APK.", &options.output_path)
          .OptionalFlag("-o", "Path to the output APK.", &options.output_path)
          .OptionalFlag("-d", "Path to the output directory (for splits).", &options.output_dir)
          .OptionalFlag("-x", "Path to XML configuration file.", &config_path)
          .OptionalFlag(
              "--target-densities",
              "Comma separated list of the screen densities that the APK will be optimized for.\n"
@@ -369,6 +427,22 @@ int Optimize(const std::vector<StringPiece>& args) {
    }
  }

  if (config_path) {
    if (!options.output_dir) {
      context.GetDiagnostics()->Error(
          DiagMessage() << "Output directory is required when using a configuration file");
      return 1;
    }
    std::string& path = config_path.value();
    Maybe<ConfigurationParser> for_path = ConfigurationParser::ForPath(path);
    if (for_path) {
      options.configuration = for_path.value().WithDiagnostics(context.GetDiagnostics()).Parse();
    } else {
      context.GetDiagnostics()->Error(DiagMessage() << "Could not parse config file " << path);
      return 1;
    }
  }

  if (!ExtractAppDataFromManifest(&context, apk.get(), &options)) {
    return 1;
  }
+21 −11
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@

#include <algorithm>
#include <functional>
#include <map>
#include <memory>
#include <utility>

@@ -56,15 +57,15 @@ using ::aapt::xml::XmlActionExecutorPolicy;
using ::aapt::xml::XmlNodeAction;
using ::android::base::ReadFileToString;

const std::unordered_map<std::string, Abi> kAbiMap = {
    {"armeabi", Abi::kArmeV6},
    {"armeabi-v7a", Abi::kArmV7a},
    {"arm64-v8a", Abi::kArm64V8a},
    {"x86", Abi::kX86},
    {"x86_64", Abi::kX86_64},
    {"mips", Abi::kMips},
    {"mips64", Abi::kMips64},
    {"universal", Abi::kUniversal},
const std::unordered_map<std::string, Abi> kStringToAbiMap = {
    {"armeabi", Abi::kArmeV6}, {"armeabi-v7a", Abi::kArmV7a},  {"arm64-v8a", Abi::kArm64V8a},
    {"x86", Abi::kX86},        {"x86_64", Abi::kX86_64},       {"mips", Abi::kMips},
    {"mips64", Abi::kMips64},  {"universal", Abi::kUniversal},
};
const std::map<Abi, std::string> kAbiToStringMap = {
    {Abi::kArmeV6, "armeabi"}, {Abi::kArmV7a, "armeabi-v7a"},  {Abi::kArm64V8a, "arm64-v8a"},
    {Abi::kX86, "x86"},        {Abi::kX86_64, "x86_64"},       {Abi::kMips, "mips"},
    {Abi::kMips64, "mips64"},  {Abi::kUniversal, "universal"},
};

constexpr const char* kAaptXmlNs = "http://schemas.android.com/tools/aapt";
@@ -102,7 +103,13 @@ class NamespaceVisitor : public xml::Visitor {

}  // namespace

namespace configuration {

const std::string& AbiToString(Abi abi) {
  return kAbiToStringMap.find(abi)->second;
}

}  // namespace configuration

/** Returns a ConfigurationParser for the file located at the provided path. */
Maybe<ConfigurationParser> ConfigurationParser::ForPath(const std::string& path) {
@@ -175,6 +182,9 @@ Maybe<Configuration> ConfigurationParser::Parse() {
    return {};
  }

  // TODO: Validate all references in the configuration are valid. It should be safe to assume from
  // this point on that any references from one section to another will be present.

  return {config};
}

@@ -201,7 +211,7 @@ ConfigurationParser::ActionHandler ConfigurationParser::artifact_handler_ =
              DiagMessage() << "Unknown artifact attribute: " << attr.name << " = " << attr.value);
        }
      }
      config->artifacts[artifact.name] = artifact;
      config->artifacts.push_back(artifact);
      return true;
    };

@@ -236,7 +246,7 @@ ConfigurationParser::ActionHandler ConfigurationParser::abi_group_handler_ =
          for (auto& node : child->children) {
            xml::Text* t;
            if ((t = NodeCast<xml::Text>(node.get())) != nullptr) {
              group.push_back(kAbiMap.at(TrimWhitespace(t->text).to_string()));
              group.push_back(kStringToAbiMap.at(TrimWhitespace(t->text).to_string()));
              break;
            }
          }
Loading