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 46using ::aapt::configuration::Abi; 47using ::aapt::configuration::OutputArtifact; 48using ::android::ResTable_config; 49using ::android::StringPiece; 50using ::android::base::ReadFileToString; 51using ::android::base::StringAppendF; 52using ::android::base::StringPrintf; 53 54namespace aapt { 55 56struct 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 83class 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 140class 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 287bool 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 301bool 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 320int 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