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/stringprintf.h"
     21 #include "androidfw/StringPiece.h"
     22 
     23 #include "Diagnostics.h"
     24 #include "Flags.h"
     25 #include "LoadedApk.h"
     26 #include "ResourceUtils.h"
     27 #include "SdkConstants.h"
     28 #include "ValueVisitor.h"
     29 #include "cmd/Util.h"
     30 #include "configuration/ConfigurationParser.h"
     31 #include "filter/AbiFilter.h"
     32 #include "flatten/TableFlattener.h"
     33 #include "flatten/XmlFlattener.h"
     34 #include "io/BigBufferInputStream.h"
     35 #include "io/Util.h"
     36 #include "optimize/ResourceDeduper.h"
     37 #include "optimize/VersionCollapser.h"
     38 #include "split/TableSplitter.h"
     39 #include "util/Files.h"
     40 
     41 using ::aapt::configuration::Abi;
     42 using ::aapt::configuration::Artifact;
     43 using ::aapt::configuration::PostProcessingConfiguration;
     44 using ::android::StringPiece;
     45 using ::android::base::StringPrintf;
     46 
     47 namespace aapt {
     48 
     49 struct OptimizeOptions {
     50   // Path to the output APK.
     51   Maybe<std::string> output_path;
     52   // Path to the output APK directory for splits.
     53   Maybe<std::string> output_dir;
     54 
     55   // Details of the app extracted from the AndroidManifest.xml
     56   AppInfo app_info;
     57 
     58   // Split APK options.
     59   TableSplitterOptions table_splitter_options;
     60 
     61   // List of output split paths. These are in the same order as `split_constraints`.
     62   std::vector<std::string> split_paths;
     63 
     64   // List of SplitConstraints governing what resources go into each split. Ordered by `split_paths`.
     65   std::vector<SplitConstraints> split_constraints;
     66 
     67   TableFlattenerOptions table_flattener_options;
     68 
     69   Maybe<PostProcessingConfiguration> configuration;
     70 };
     71 
     72 class OptimizeContext : public IAaptContext {
     73  public:
     74   OptimizeContext() = default;
     75 
     76   PackageType GetPackageType() override {
     77     // Not important here. Using anything other than kApp adds EXTRA validation, which we want to
     78     // avoid.
     79     return PackageType::kApp;
     80   }
     81 
     82   IDiagnostics* GetDiagnostics() override {
     83     return &diagnostics_;
     84   }
     85 
     86   NameMangler* GetNameMangler() override {
     87     UNIMPLEMENTED(FATAL);
     88     return nullptr;
     89   }
     90 
     91   const std::string& GetCompilationPackage() override {
     92     static std::string empty;
     93     return empty;
     94   }
     95 
     96   uint8_t GetPackageId() override {
     97     return 0;
     98   }
     99 
    100   SymbolTable* GetExternalSymbols() override {
    101     UNIMPLEMENTED(FATAL);
    102     return nullptr;
    103   }
    104 
    105   bool IsVerbose() override {
    106     return verbose_;
    107   }
    108 
    109   void SetVerbose(bool val) {
    110     verbose_ = val;
    111   }
    112 
    113   void SetMinSdkVersion(int sdk_version) {
    114     sdk_version_ = sdk_version;
    115   }
    116 
    117   int GetMinSdkVersion() override {
    118     return sdk_version_;
    119   }
    120 
    121  private:
    122   DISALLOW_COPY_AND_ASSIGN(OptimizeContext);
    123 
    124   StdErrDiagnostics diagnostics_;
    125   bool verbose_ = false;
    126   int sdk_version_ = 0;
    127 };
    128 
    129 class OptimizeCommand {
    130  public:
    131   OptimizeCommand(OptimizeContext* context, const OptimizeOptions& options)
    132       : options_(options), context_(context) {
    133   }
    134 
    135   int Run(std::unique_ptr<LoadedApk> apk) {
    136     if (context_->IsVerbose()) {
    137       context_->GetDiagnostics()->Note(DiagMessage() << "Optimizing APK...");
    138     }
    139 
    140     VersionCollapser collapser;
    141     if (!collapser.Consume(context_, apk->GetResourceTable())) {
    142       return 1;
    143     }
    144 
    145     ResourceDeduper deduper;
    146     if (!deduper.Consume(context_, apk->GetResourceTable())) {
    147       context_->GetDiagnostics()->Error(DiagMessage() << "failed deduping resources");
    148       return 1;
    149     }
    150 
    151     // Adjust the SplitConstraints so that their SDK version is stripped if it is less than or
    152     // equal to the minSdk.
    153     options_.split_constraints =
    154         AdjustSplitConstraintsForMinSdk(context_->GetMinSdkVersion(), options_.split_constraints);
    155 
    156     // Stripping the APK using the TableSplitter. The resource table is modified in place in the
    157     // LoadedApk.
    158     TableSplitter splitter(options_.split_constraints, options_.table_splitter_options);
    159     if (!splitter.VerifySplitConstraints(context_)) {
    160       return 1;
    161     }
    162     splitter.SplitTable(apk->GetResourceTable());
    163 
    164     auto path_iter = options_.split_paths.begin();
    165     auto split_constraints_iter = options_.split_constraints.begin();
    166     for (std::unique_ptr<ResourceTable>& split_table : splitter.splits()) {
    167       if (context_->IsVerbose()) {
    168         context_->GetDiagnostics()->Note(
    169             DiagMessage(*path_iter) << "generating split with configurations '"
    170                                     << util::Joiner(split_constraints_iter->configs, ", ") << "'");
    171       }
    172 
    173       // Generate an AndroidManifest.xml for each split.
    174       std::unique_ptr<xml::XmlResource> split_manifest =
    175           GenerateSplitManifest(options_.app_info, *split_constraints_iter);
    176       std::unique_ptr<IArchiveWriter> split_writer =
    177           CreateZipFileArchiveWriter(context_->GetDiagnostics(), *path_iter);
    178       if (!split_writer) {
    179         return 1;
    180       }
    181 
    182       if (!WriteSplitApk(split_table.get(), split_manifest.get(), split_writer.get())) {
    183         return 1;
    184       }
    185 
    186       ++path_iter;
    187       ++split_constraints_iter;
    188     }
    189 
    190     if (options_.configuration && options_.output_dir) {
    191       PostProcessingConfiguration& config = options_.configuration.value();
    192 
    193       // For now, just write out the stripped APK since ABI splitting doesn't modify anything else.
    194       for (const Artifact& artifact : config.artifacts) {
    195         if (artifact.abi_group) {
    196           const std::string& group = artifact.abi_group.value();
    197 
    198           auto abi_group = config.abi_groups.find(group);
    199           // TODO: Remove validation when configuration parser ensures referential integrity.
    200           if (abi_group == config.abi_groups.end()) {
    201             context_->GetDiagnostics()->Note(
    202                 DiagMessage() << "could not find referenced ABI group '" << group << "'");
    203             return 1;
    204           }
    205           FilterChain filters;
    206           filters.AddFilter(AbiFilter::FromAbiList(abi_group->second));
    207 
    208           const std::string& path = apk->GetSource().path;
    209           const StringPiece ext = file::GetExtension(path);
    210           const std::string name = path.substr(0, path.rfind(ext.to_string()));
    211 
    212           // Name is hard coded for now since only one split dimension is supported.
    213           // TODO: Incorporate name generation into the configuration objects.
    214           const std::string file_name =
    215               StringPrintf("%s.%s%s", name.c_str(), group.c_str(), ext.data());
    216           std::string out = options_.output_dir.value();
    217           file::AppendPath(&out, file_name);
    218 
    219           std::unique_ptr<IArchiveWriter> writer =
    220               CreateZipFileArchiveWriter(context_->GetDiagnostics(), out);
    221 
    222           if (!apk->WriteToArchive(context_, options_.table_flattener_options, &filters,
    223                                    writer.get())) {
    224             return 1;
    225           }
    226         }
    227       }
    228     }
    229 
    230     if (options_.output_path) {
    231       std::unique_ptr<IArchiveWriter> writer =
    232           CreateZipFileArchiveWriter(context_->GetDiagnostics(), options_.output_path.value());
    233       if (!apk->WriteToArchive(context_, options_.table_flattener_options, writer.get())) {
    234         return 1;
    235       }
    236     }
    237 
    238     return 0;
    239   }
    240 
    241  private:
    242   bool WriteSplitApk(ResourceTable* table, xml::XmlResource* manifest, IArchiveWriter* writer) {
    243     BigBuffer manifest_buffer(4096);
    244     XmlFlattener xml_flattener(&manifest_buffer, {});
    245     if (!xml_flattener.Consume(context_, manifest)) {
    246       return false;
    247     }
    248 
    249     io::BigBufferInputStream manifest_buffer_in(&manifest_buffer);
    250     if (!io::CopyInputStreamToArchive(context_, &manifest_buffer_in, "AndroidManifest.xml",
    251                                       ArchiveEntry::kCompress, writer)) {
    252       return false;
    253     }
    254 
    255     std::map<std::pair<ConfigDescription, StringPiece>, FileReference*> config_sorted_files;
    256     for (auto& pkg : table->packages) {
    257       for (auto& type : pkg->types) {
    258         // Sort by config and name, so that we get better locality in the zip file.
    259         config_sorted_files.clear();
    260 
    261         for (auto& entry : type->entries) {
    262           for (auto& config_value : entry->values) {
    263             FileReference* file_ref = ValueCast<FileReference>(config_value->value.get());
    264             if (file_ref == nullptr) {
    265               continue;
    266             }
    267 
    268             if (file_ref->file == nullptr) {
    269               ResourceNameRef name(pkg->name, type->type, entry->name);
    270               context_->GetDiagnostics()->Warn(DiagMessage(file_ref->GetSource())
    271                                                << "file for resource " << name << " with config '"
    272                                                << config_value->config << "' not found");
    273               continue;
    274             }
    275 
    276             const StringPiece entry_name = entry->name;
    277             config_sorted_files[std::make_pair(config_value->config, entry_name)] = file_ref;
    278           }
    279         }
    280 
    281         for (auto& entry : config_sorted_files) {
    282           FileReference* file_ref = entry.second;
    283           uint32_t compression_flags =
    284               file_ref->file->WasCompressed() ? ArchiveEntry::kCompress : 0u;
    285           if (!io::CopyFileToArchive(context_, file_ref->file, *file_ref->path, compression_flags,
    286                                      writer)) {
    287             return false;
    288           }
    289         }
    290       }
    291     }
    292 
    293     BigBuffer table_buffer(4096);
    294     TableFlattener table_flattener(options_.table_flattener_options, &table_buffer);
    295     if (!table_flattener.Consume(context_, table)) {
    296       return false;
    297     }
    298 
    299     io::BigBufferInputStream table_buffer_in(&table_buffer);
    300     if (!io::CopyInputStreamToArchive(context_, &table_buffer_in, "resources.arsc",
    301                                       ArchiveEntry::kAlign, writer)) {
    302       return false;
    303     }
    304     return true;
    305   }
    306 
    307   OptimizeOptions options_;
    308   OptimizeContext* context_;
    309 };
    310 
    311 bool ExtractAppDataFromManifest(OptimizeContext* context, LoadedApk* apk,
    312                                 OptimizeOptions* out_options) {
    313   io::IFile* manifest_file = apk->GetFileCollection()->FindFile("AndroidManifest.xml");
    314   if (manifest_file == nullptr) {
    315     context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
    316                                      << "missing AndroidManifest.xml");
    317     return false;
    318   }
    319 
    320   std::unique_ptr<io::IData> data = manifest_file->OpenAsData();
    321   if (data == nullptr) {
    322     context->GetDiagnostics()->Error(DiagMessage(manifest_file->GetSource())
    323                                      << "failed to open file");
    324     return false;
    325   }
    326 
    327   std::unique_ptr<xml::XmlResource> manifest = xml::Inflate(
    328       data->data(), data->size(), context->GetDiagnostics(), manifest_file->GetSource());
    329   if (manifest == nullptr) {
    330     context->GetDiagnostics()->Error(DiagMessage() << "failed to read binary AndroidManifest.xml");
    331     return false;
    332   }
    333 
    334   Maybe<AppInfo> app_info =
    335       ExtractAppInfoFromBinaryManifest(manifest.get(), context->GetDiagnostics());
    336   if (!app_info) {
    337     context->GetDiagnostics()->Error(DiagMessage()
    338                                      << "failed to extract data from AndroidManifest.xml");
    339     return false;
    340   }
    341 
    342   out_options->app_info = std::move(app_info.value());
    343   context->SetMinSdkVersion(out_options->app_info.min_sdk_version.value_or_default(0));
    344   return true;
    345 }
    346 
    347 int Optimize(const std::vector<StringPiece>& args) {
    348   OptimizeContext context;
    349   OptimizeOptions options;
    350   Maybe<std::string> config_path;
    351   Maybe<std::string> target_densities;
    352   std::vector<std::string> configs;
    353   std::vector<std::string> split_args;
    354   bool verbose = false;
    355   Flags flags =
    356       Flags()
    357           .OptionalFlag("-o", "Path to the output APK.", &options.output_path)
    358           .OptionalFlag("-d", "Path to the output directory (for splits).", &options.output_dir)
    359           .OptionalFlag("-x", "Path to XML configuration file.", &config_path)
    360           .OptionalFlag(
    361               "--target-densities",
    362               "Comma separated list of the screen densities that the APK will be optimized for.\n"
    363               "All the resources that would be unused on devices of the given densities will be \n"
    364               "removed from the APK.",
    365               &target_densities)
    366           .OptionalFlagList("-c",
    367                             "Comma separated list of configurations to include. The default\n"
    368                             "is all configurations.",
    369                             &configs)
    370           .OptionalFlagList("--split",
    371                             "Split resources matching a set of configs out to a "
    372                             "Split APK.\nSyntax: path/to/output.apk;<config>[,<config>[...]].\n"
    373                             "On Windows, use a semicolon ';' separator instead.",
    374                             &split_args)
    375           .OptionalSwitch("--enable-sparse-encoding",
    376                           "Enables encoding sparse entries using a binary search tree.\n"
    377                           "This decreases APK size at the cost of resource retrieval performance.",
    378                           &options.table_flattener_options.use_sparse_entries)
    379           .OptionalSwitch("-v", "Enables verbose logging", &verbose);
    380 
    381   if (!flags.Parse("aapt2 optimize", args, &std::cerr)) {
    382     return 1;
    383   }
    384 
    385   if (flags.GetArgs().size() != 1u) {
    386     std::cerr << "must have one APK as argument.\n\n";
    387     flags.Usage("aapt2 optimize", &std::cerr);
    388     return 1;
    389   }
    390 
    391   std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(&context, flags.GetArgs()[0]);
    392   if (!apk) {
    393     return 1;
    394   }
    395 
    396   context.SetVerbose(verbose);
    397 
    398   if (target_densities) {
    399     // Parse the target screen densities.
    400     for (const StringPiece& config_str : util::Tokenize(target_densities.value(), ',')) {
    401       Maybe<uint16_t> target_density =
    402           ParseTargetDensityParameter(config_str, context.GetDiagnostics());
    403       if (!target_density) {
    404         return 1;
    405       }
    406       options.table_splitter_options.preferred_densities.push_back(target_density.value());
    407     }
    408   }
    409 
    410   std::unique_ptr<IConfigFilter> filter;
    411   if (!configs.empty()) {
    412     filter = ParseConfigFilterParameters(configs, context.GetDiagnostics());
    413     if (filter == nullptr) {
    414       return 1;
    415     }
    416     options.table_splitter_options.config_filter = filter.get();
    417   }
    418 
    419   // Parse the split parameters.
    420   for (const std::string& split_arg : split_args) {
    421     options.split_paths.push_back({});
    422     options.split_constraints.push_back({});
    423     if (!ParseSplitParameter(split_arg, context.GetDiagnostics(), &options.split_paths.back(),
    424                              &options.split_constraints.back())) {
    425       return 1;
    426     }
    427   }
    428 
    429   if (config_path) {
    430     if (!options.output_dir) {
    431       context.GetDiagnostics()->Error(
    432           DiagMessage() << "Output directory is required when using a configuration file");
    433       return 1;
    434     }
    435     std::string& path = config_path.value();
    436     Maybe<ConfigurationParser> for_path = ConfigurationParser::ForPath(path);
    437     if (for_path) {
    438       options.configuration = for_path.value().WithDiagnostics(context.GetDiagnostics()).Parse();
    439     } else {
    440       context.GetDiagnostics()->Error(DiagMessage() << "Could not parse config file " << path);
    441       return 1;
    442     }
    443   }
    444 
    445   if (!ExtractAppDataFromManifest(&context, apk.get(), &options)) {
    446     return 1;
    447   }
    448 
    449   OptimizeCommand cmd(&context, options);
    450   return cmd.Run(std::move(apk));
    451 }
    452 
    453 }  // namespace aapt
    454