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