Home | History | Annotate | Download | only in cmd
      1 /*
      2  * Copyright (C) 2017 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 #include <memory>
     18 #include <vector>
     19 
     20 #include "android-base/file.h"
     21 #include "android-base/stringprintf.h"
     22 
     23 #include "androidfw/ResourceTypes.h"
     24 #include "androidfw/StringPiece.h"
     25 
     26 #include "Diagnostics.h"
     27 #include "Flags.h"
     28 #include "LoadedApk.h"
     29 #include "ResourceUtils.h"
     30 #include "SdkConstants.h"
     31 #include "ValueVisitor.h"
     32 #include "cmd/Util.h"
     33 #include "configuration/ConfigurationParser.h"
     34 #include "filter/AbiFilter.h"
     35 #include "format/binary/TableFlattener.h"
     36 #include "format/binary/XmlFlattener.h"
     37 #include "io/BigBufferStream.h"
     38 #include "io/Util.h"
     39 #include "optimize/MultiApkGenerator.h"
     40 #include "optimize/ResourceDeduper.h"
     41 #include "optimize/VersionCollapser.h"
     42 #include "split/TableSplitter.h"
     43 #include "util/Files.h"
     44 #include "util/Util.h"
     45 
     46 using ::aapt::configuration::Abi;
     47 using ::aapt::configuration::OutputArtifact;
     48 using ::android::ResTable_config;
     49 using ::android::StringPiece;
     50 using ::android::base::ReadFileToString;
     51 using ::android::base::StringAppendF;
     52 using ::android::base::StringPrintf;
     53 
     54 namespace aapt {
     55 
     56 struct OptimizeOptions {
     57   // Path to the output APK.
     58   Maybe<std::string> output_path;
     59   // Path to the output APK directory for splits.
     60   Maybe<std::string> output_dir;
     61 
     62   // Details of the app extracted from the AndroidManifest.xml
     63   AppInfo app_info;
     64 
     65   // Split APK options.
     66   TableSplitterOptions table_splitter_options;
     67 
     68   // List of output split paths. These are in the same order as `split_constraints`.
     69   std::vector<std::string> split_paths;
     70 
     71   // List of SplitConstraints governing what resources go into each split. Ordered by `split_paths`.
     72   std::vector<SplitConstraints> split_constraints;
     73 
     74   TableFlattenerOptions table_flattener_options;
     75 
     76   Maybe<std::vector<OutputArtifact>> apk_artifacts;
     77 
     78   // Set of artifacts to keep when generating multi-APK splits. If the list is empty, all artifacts
     79   // are kept and will be written as output.
     80   std::unordered_set<std::string> kept_artifacts;
     81 };
     82 
     83 class OptimizeContext : public IAaptContext {
     84  public:
     85   OptimizeContext() = default;
     86 
     87   PackageType GetPackageType() override {
     88     // Not important here. Using anything other than kApp adds EXTRA validation, which we want to
     89     // avoid.
     90     return PackageType::kApp;
     91   }
     92 
     93   IDiagnostics* GetDiagnostics() override {
     94     return &diagnostics_;
     95   }
     96 
     97   NameMangler* GetNameMangler() override {
     98     UNIMPLEMENTED(FATAL);
     99     return nullptr;
    100   }
    101 
    102   const std::string& GetCompilationPackage() override {
    103     static std::string empty;
    104     return empty;
    105   }
    106 
    107   uint8_t GetPackageId() override {
    108     return 0;
    109   }
    110 
    111   SymbolTable* GetExternalSymbols() override {
    112     UNIMPLEMENTED(FATAL);
    113     return nullptr;
    114   }
    115 
    116   bool IsVerbose() override {
    117     return verbose_;
    118   }
    119 
    120   void SetVerbose(bool val) {
    121     verbose_ = val;
    122   }
    123 
    124   void SetMinSdkVersion(int sdk_version) {
    125     sdk_version_ = sdk_version;
    126   }
    127 
    128   int GetMinSdkVersion() override {
    129     return sdk_version_;
    130   }
    131 
    132  private:
    133   DISALLOW_COPY_AND_ASSIGN(OptimizeContext);
    134 
    135   StdErrDiagnostics diagnostics_;
    136   bool verbose_ = false;
    137   int sdk_version_ = 0;
    138 };
    139 
    140 class OptimizeCommand {
    141  public:
    142   OptimizeCommand(OptimizeContext* context, const OptimizeOptions& options)
    143       : options_(options), context_(context) {
    144   }
    145 
    146   int Run(std::unique_ptr<LoadedApk> apk) {
    147     if (context_->IsVerbose()) {
    148       context_->GetDiagnostics()->Note(DiagMessage() << "Optimizing APK...");
    149     }
    150 
    151     VersionCollapser collapser;
    152     if (!collapser.Consume(context_, apk->GetResourceTable())) {
    153       return 1;
    154     }
    155 
    156     ResourceDeduper deduper;
    157     if (!deduper.Consume(context_, apk->GetResourceTable())) {
    158       context_->GetDiagnostics()->Error(DiagMessage() << "failed deduping resources");
    159       return 1;
    160     }
    161 
    162     // Adjust the SplitConstraints so that their SDK version is stripped if it is less than or
    163     // equal to the minSdk.
    164     options_.split_constraints =
    165         AdjustSplitConstraintsForMinSdk(context_->GetMinSdkVersion(), options_.split_constraints);
    166 
    167     // Stripping the APK using the TableSplitter. The resource table is modified in place in the
    168     // LoadedApk.
    169     TableSplitter splitter(options_.split_constraints, options_.table_splitter_options);
    170     if (!splitter.VerifySplitConstraints(context_)) {
    171       return 1;
    172     }
    173     splitter.SplitTable(apk->GetResourceTable());
    174 
    175     auto path_iter = options_.split_paths.begin();
    176     auto split_constraints_iter = options_.split_constraints.begin();
    177     for (std::unique_ptr<ResourceTable>& split_table : splitter.splits()) {
    178       if (context_->IsVerbose()) {
    179         context_->GetDiagnostics()->Note(
    180             DiagMessage(*path_iter) << "generating split with configurations '"
    181                                     << util::Joiner(split_constraints_iter->configs, ", ") << "'");
    182       }
    183 
    184       // Generate an AndroidManifest.xml for each split.
    185       std::unique_ptr<xml::XmlResource> split_manifest =
    186           GenerateSplitManifest(options_.app_info, *split_constraints_iter);
    187       std::unique_ptr<IArchiveWriter> split_writer =
    188           CreateZipFileArchiveWriter(context_->GetDiagnostics(), *path_iter);
    189       if (!split_writer) {
    190         return 1;
    191       }
    192 
    193       if (!WriteSplitApk(split_table.get(), split_manifest.get(), split_writer.get())) {
    194         return 1;
    195       }
    196 
    197       ++path_iter;
    198       ++split_constraints_iter;
    199     }
    200 
    201     if (options_.apk_artifacts && options_.output_dir) {
    202       MultiApkGenerator generator{apk.get(), context_};
    203       MultiApkGeneratorOptions generator_options = {
    204           options_.output_dir.value(), options_.apk_artifacts.value(),
    205           options_.table_flattener_options, options_.kept_artifacts};
    206       if (!generator.FromBaseApk(generator_options)) {
    207         return 1;
    208       }
    209     }
    210 
    211     if (options_.output_path) {
    212       std::unique_ptr<IArchiveWriter> writer =
    213           CreateZipFileArchiveWriter(context_->GetDiagnostics(), options_.output_path.value());
    214       if (!apk->WriteToArchive(context_, options_.table_flattener_options, writer.get())) {
    215         return 1;
    216       }
    217     }
    218 
    219     return 0;
    220   }
    221 
    222  private:
    223   bool WriteSplitApk(ResourceTable* table, xml::XmlResource* manifest, IArchiveWriter* writer) {
    224     BigBuffer manifest_buffer(4096);
    225     XmlFlattener xml_flattener(&manifest_buffer, {});
    226     if (!xml_flattener.Consume(context_, manifest)) {
    227       return false;
    228     }
    229 
    230     io::BigBufferInputStream manifest_buffer_in(&manifest_buffer);
    231     if (!io::CopyInputStreamToArchive(context_, &manifest_buffer_in, "AndroidManifest.xml",
    232                                       ArchiveEntry::kCompress, writer)) {
    233       return false;
    234     }
    235 
    236     std::map<std::pair<ConfigDescription, StringPiece>, FileReference*> config_sorted_files;
    237     for (auto& pkg : table->packages) {
    238       for (auto& type : pkg->types) {
    239         // Sort by config and name, so that we get better locality in the zip file.
    240         config_sorted_files.clear();
    241 
    242         for (auto& entry : type->entries) {
    243           for (auto& config_value : entry->values) {
    244             auto* file_ref = ValueCast<FileReference>(config_value->value.get());
    245             if (file_ref == nullptr) {
    246               continue;
    247             }
    248 
    249             if (file_ref->file == nullptr) {
    250               ResourceNameRef name(pkg->name, type->type, entry->name);
    251               context_->GetDiagnostics()->Warn(DiagMessage(file_ref->GetSource())
    252                                                << "file for resource " << name << " with config '"
    253                                                << config_value->config << "' not found");
    254               continue;
    255             }
    256 
    257             const StringPiece entry_name = entry->name;
    258             config_sorted_files[std::make_pair(config_value->config, entry_name)] = file_ref;
    259           }
    260         }
    261 
    262         for (auto& entry : config_sorted_files) {
    263           FileReference* file_ref = entry.second;
    264           if (!io::CopyFileToArchivePreserveCompression(context_, file_ref->file, *file_ref->path,
    265                                                         writer)) {
    266             return false;
    267           }
    268         }
    269       }
    270     }
    271 
    272     BigBuffer table_buffer(4096);
    273     TableFlattener table_flattener(options_.table_flattener_options, &table_buffer);
    274     if (!table_flattener.Consume(context_, table)) {
    275       return false;
    276     }
    277 
    278     io::BigBufferInputStream table_buffer_in(&table_buffer);
    279     return io::CopyInputStreamToArchive(context_, &table_buffer_in, "resources.arsc",
    280                                         ArchiveEntry::kAlign, writer);
    281   }
    282 
    283   OptimizeOptions options_;
    284   OptimizeContext* context_;
    285 };
    286 
    287 bool ExtractWhitelistFromConfig(const std::string& path, OptimizeContext* context,
    288                                 OptimizeOptions* options) {
    289   std::string contents;
    290   if (!ReadFileToString(path, &contents, true)) {
    291     context->GetDiagnostics()->Error(DiagMessage()
    292                                      << "failed to parse whitelist from config file: " << path);
    293     return false;
    294   }
    295   for (const StringPiece& resource_name : util::Tokenize(contents, ',')) {
    296     options->table_flattener_options.whitelisted_resources.insert(resource_name.to_string());
    297   }
    298   return true;
    299 }
    300 
    301 bool ExtractAppDataFromManifest(OptimizeContext* context, const LoadedApk* apk,
    302                                 OptimizeOptions* out_options) {
    303   const xml::XmlResource* manifest = apk->GetManifest();
    304   if (manifest == nullptr) {
    305     return false;
    306   }
    307 
    308   Maybe<AppInfo> app_info = ExtractAppInfoFromBinaryManifest(*manifest, context->GetDiagnostics());
    309   if (!app_info) {
    310     context->GetDiagnostics()->Error(DiagMessage()
    311                                      << "failed to extract data from AndroidManifest.xml");
    312     return false;
    313   }
    314 
    315   out_options->app_info = std::move(app_info.value());
    316   context->SetMinSdkVersion(out_options->app_info.min_sdk_version.value_or_default(0));
    317   return true;
    318 }
    319 
    320 int Optimize(const std::vector<StringPiece>& args) {
    321   OptimizeContext context;
    322   OptimizeOptions options;
    323   Maybe<std::string> config_path;
    324   Maybe<std::string> whitelist_path;
    325   Maybe<std::string> target_densities;
    326   std::vector<std::string> configs;
    327   std::vector<std::string> split_args;
    328   std::unordered_set<std::string> kept_artifacts;
    329   bool verbose = false;
    330   bool print_only = false;
    331   Flags flags =
    332       Flags()
    333           .OptionalFlag("-o", "Path to the output APK.", &options.output_path)
    334           .OptionalFlag("-d", "Path to the output directory (for splits).", &options.output_dir)
    335           .OptionalFlag("-x", "Path to XML configuration file.", &config_path)
    336           .OptionalSwitch("-p", "Print the multi APK artifacts and exit.", &print_only)
    337           .OptionalFlag(
    338               "--target-densities",
    339               "Comma separated list of the screen densities that the APK will be optimized for.\n"
    340               "All the resources that would be unused on devices of the given densities will be \n"
    341               "removed from the APK.",
    342               &target_densities)
    343           .OptionalFlag("--whitelist-config-path",
    344                         "Path to the whitelist.cfg file containing whitelisted resources \n"
    345                         "whose names should not be altered in final resource tables.",
    346                         &whitelist_path)
    347           .OptionalFlagList("-c",
    348                             "Comma separated list of configurations to include. The default\n"
    349                             "is all configurations.",
    350                             &configs)
    351           .OptionalFlagList("--split",
    352                             "Split resources matching a set of configs out to a "
    353                             "Split APK.\nSyntax: path/to/output.apk;<config>[,<config>[...]].\n"
    354                             "On Windows, use a semicolon ';' separator instead.",
    355                             &split_args)
    356           .OptionalFlagList("--keep-artifacts",
    357                             "Comma separated list of artifacts to keep. If none are specified,\n"
    358                             "all artifacts will be kept.",
    359                             &kept_artifacts)
    360           .OptionalSwitch("--enable-sparse-encoding",
    361                           "Enables encoding sparse entries using a binary search tree.\n"
    362                           "This decreases APK size at the cost of resource retrieval performance.",
    363                           &options.table_flattener_options.use_sparse_entries)
    364           .OptionalSwitch("--enable-resource-obfuscation",
    365                           "Enables obfuscation of key string pool to single value",
    366                           &options.table_flattener_options.collapse_key_stringpool)
    367           .OptionalSwitch("-v", "Enables verbose logging", &verbose);
    368 
    369   if (!flags.Parse("aapt2 optimize", args, &std::cerr)) {
    370     return 1;
    371   }
    372 
    373   if (flags.GetArgs().size() != 1u) {
    374     std::cerr << "must have one APK as argument.\n\n";
    375     flags.Usage("aapt2 optimize", &std::cerr);
    376     return 1;
    377   }
    378 
    379   const std::string& apk_path = flags.GetArgs()[0];
    380 
    381   context.SetVerbose(verbose);
    382   IDiagnostics* diag = context.GetDiagnostics();
    383 
    384   if (config_path) {
    385     std::string& path = config_path.value();
    386     Maybe<ConfigurationParser> for_path = ConfigurationParser::ForPath(path);
    387     if (for_path) {
    388       options.apk_artifacts = for_path.value().WithDiagnostics(diag).Parse(apk_path);
    389       if (!options.apk_artifacts) {
    390         diag->Error(DiagMessage() << "Failed to parse the output artifact list");
    391         return 1;
    392       }
    393 
    394     } else {
    395       diag->Error(DiagMessage() << "Could not parse config file " << path);
    396       return 1;
    397     }
    398 
    399     if (print_only) {
    400       for (const OutputArtifact& artifact : options.apk_artifacts.value()) {
    401         std::cout << artifact.name << std::endl;
    402       }
    403       return 0;
    404     }
    405 
    406     if (!kept_artifacts.empty()) {
    407       for (const std::string& artifact_str : kept_artifacts) {
    408         for (const StringPiece& artifact : util::Tokenize(artifact_str, ',')) {
    409           options.kept_artifacts.insert(artifact.to_string());
    410         }
    411       }
    412     }
    413 
    414     // Since we know that we are going to process the APK (not just print targets), make sure we
    415     // have somewhere to write them to.
    416     if (!options.output_dir) {
    417       diag->Error(DiagMessage() << "Output directory is required when using a configuration file");
    418       return 1;
    419     }
    420   } else if (print_only) {
    421     diag->Error(DiagMessage() << "Asked to print artifacts without providing a configurations");
    422     return 1;
    423   }
    424 
    425   std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(apk_path, context.GetDiagnostics());
    426   if (!apk) {
    427     return 1;
    428   }
    429 
    430   if (target_densities) {
    431     // Parse the target screen densities.
    432     for (const StringPiece& config_str : util::Tokenize(target_densities.value(), ',')) {
    433       Maybe<uint16_t> target_density = ParseTargetDensityParameter(config_str, diag);
    434       if (!target_density) {
    435         return 1;
    436       }
    437       options.table_splitter_options.preferred_densities.push_back(target_density.value());
    438     }
    439   }
    440 
    441   std::unique_ptr<IConfigFilter> filter;
    442   if (!configs.empty()) {
    443     filter = ParseConfigFilterParameters(configs, diag);
    444     if (filter == nullptr) {
    445       return 1;
    446     }
    447     options.table_splitter_options.config_filter = filter.get();
    448   }
    449 
    450   // Parse the split parameters.
    451   for (const std::string& split_arg : split_args) {
    452     options.split_paths.emplace_back();
    453     options.split_constraints.emplace_back();
    454     if (!ParseSplitParameter(split_arg, diag, &options.split_paths.back(),
    455                              &options.split_constraints.back())) {
    456       return 1;
    457     }
    458   }
    459 
    460   if (options.table_flattener_options.collapse_key_stringpool) {
    461     if (whitelist_path) {
    462       std::string& path = whitelist_path.value();
    463       if (!ExtractWhitelistFromConfig(path, &context, &options)) {
    464         return 1;
    465       }
    466     }
    467   }
    468 
    469   if (!ExtractAppDataFromManifest(&context, apk.get(), &options)) {
    470     return 1;
    471   }
    472 
    473   OptimizeCommand cmd(&context, options);
    474   return cmd.Run(std::move(apk));
    475 }
    476 
    477 }  // namespace aapt
    478