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 "Optimize.h" 18 19 #include <memory> 20 #include <vector> 21 22 #include "android-base/file.h" 23 #include "android-base/stringprintf.h" 24 25 #include "androidfw/ConfigDescription.h" 26 #include "androidfw/ResourceTypes.h" 27 #include "androidfw/StringPiece.h" 28 29 #include "Diagnostics.h" 30 #include "LoadedApk.h" 31 #include "ResourceUtils.h" 32 #include "SdkConstants.h" 33 #include "ValueVisitor.h" 34 #include "cmd/Util.h" 35 #include "configuration/ConfigurationParser.h" 36 #include "filter/AbiFilter.h" 37 #include "format/binary/TableFlattener.h" 38 #include "format/binary/XmlFlattener.h" 39 #include "io/BigBufferStream.h" 40 #include "io/Util.h" 41 #include "optimize/MultiApkGenerator.h" 42 #include "optimize/ResourceDeduper.h" 43 #include "optimize/ResourceFilter.h" 44 #include "optimize/ResourcePathShortener.h" 45 #include "optimize/VersionCollapser.h" 46 #include "split/TableSplitter.h" 47 #include "util/Files.h" 48 #include "util/Util.h" 49 50 using ::aapt::configuration::Abi; 51 using ::aapt::configuration::OutputArtifact; 52 using ::android::ConfigDescription; 53 using ::android::ResTable_config; 54 using ::android::StringPiece; 55 using ::android::base::ReadFileToString; 56 using ::android::base::WriteStringToFile; 57 using ::android::base::StringAppendF; 58 using ::android::base::StringPrintf; 59 60 namespace aapt { 61 62 class OptimizeContext : public IAaptContext { 63 public: 64 OptimizeContext() = default; 65 66 PackageType GetPackageType() override { 67 // Not important here. Using anything other than kApp adds EXTRA validation, which we want to 68 // avoid. 69 return PackageType::kApp; 70 } 71 72 IDiagnostics* GetDiagnostics() override { 73 return &diagnostics_; 74 } 75 76 NameMangler* GetNameMangler() override { 77 UNIMPLEMENTED(FATAL); 78 return nullptr; 79 } 80 81 const std::string& GetCompilationPackage() override { 82 static std::string empty; 83 return empty; 84 } 85 86 uint8_t GetPackageId() override { 87 return 0; 88 } 89 90 SymbolTable* GetExternalSymbols() override { 91 UNIMPLEMENTED(FATAL); 92 return nullptr; 93 } 94 95 bool IsVerbose() override { 96 return verbose_; 97 } 98 99 void SetVerbose(bool val) { 100 verbose_ = val; 101 } 102 103 void SetMinSdkVersion(int sdk_version) { 104 sdk_version_ = sdk_version; 105 } 106 107 int GetMinSdkVersion() override { 108 return sdk_version_; 109 } 110 111 private: 112 DISALLOW_COPY_AND_ASSIGN(OptimizeContext); 113 114 StdErrDiagnostics diagnostics_; 115 bool verbose_ = false; 116 int sdk_version_ = 0; 117 }; 118 119 class Optimizer { 120 public: 121 Optimizer(OptimizeContext* context, const OptimizeOptions& options) 122 : options_(options), context_(context) { 123 } 124 125 int Run(std::unique_ptr<LoadedApk> apk) { 126 if (context_->IsVerbose()) { 127 context_->GetDiagnostics()->Note(DiagMessage() << "Optimizing APK..."); 128 } 129 if (!options_.resources_blacklist.empty()) { 130 ResourceFilter filter(options_.resources_blacklist); 131 if (!filter.Consume(context_, apk->GetResourceTable())) { 132 context_->GetDiagnostics()->Error(DiagMessage() << "failed filtering resources"); 133 return 1; 134 } 135 } 136 137 VersionCollapser collapser; 138 if (!collapser.Consume(context_, apk->GetResourceTable())) { 139 return 1; 140 } 141 142 ResourceDeduper deduper; 143 if (!deduper.Consume(context_, apk->GetResourceTable())) { 144 context_->GetDiagnostics()->Error(DiagMessage() << "failed deduping resources"); 145 return 1; 146 } 147 148 if (options_.shorten_resource_paths) { 149 ResourcePathShortener shortener(options_.table_flattener_options.shortened_path_map); 150 if (!shortener.Consume(context_, apk->GetResourceTable())) { 151 context_->GetDiagnostics()->Error(DiagMessage() << "failed shortening resource paths"); 152 return 1; 153 } 154 if (options_.shortened_paths_map_path 155 && !WriteShortenedPathsMap(options_.table_flattener_options.shortened_path_map, 156 options_.shortened_paths_map_path.value())) { 157 context_->GetDiagnostics()->Error(DiagMessage() 158 << "failed to write shortened resource paths to file"); 159 return 1; 160 } 161 } 162 163 // Adjust the SplitConstraints so that their SDK version is stripped if it is less than or 164 // equal to the minSdk. 165 options_.split_constraints = 166 AdjustSplitConstraintsForMinSdk(context_->GetMinSdkVersion(), options_.split_constraints); 167 168 // Stripping the APK using the TableSplitter. The resource table is modified in place in the 169 // LoadedApk. 170 TableSplitter splitter(options_.split_constraints, options_.table_splitter_options); 171 if (!splitter.VerifySplitConstraints(context_)) { 172 return 1; 173 } 174 splitter.SplitTable(apk->GetResourceTable()); 175 176 auto path_iter = options_.split_paths.begin(); 177 auto split_constraints_iter = options_.split_constraints.begin(); 178 for (std::unique_ptr<ResourceTable>& split_table : splitter.splits()) { 179 if (context_->IsVerbose()) { 180 context_->GetDiagnostics()->Note( 181 DiagMessage(*path_iter) << "generating split with configurations '" 182 << util::Joiner(split_constraints_iter->configs, ", ") << "'"); 183 } 184 185 // Generate an AndroidManifest.xml for each split. 186 std::unique_ptr<xml::XmlResource> split_manifest = 187 GenerateSplitManifest(options_.app_info, *split_constraints_iter); 188 std::unique_ptr<IArchiveWriter> split_writer = 189 CreateZipFileArchiveWriter(context_->GetDiagnostics(), *path_iter); 190 if (!split_writer) { 191 return 1; 192 } 193 194 if (!WriteSplitApk(split_table.get(), split_manifest.get(), split_writer.get())) { 195 return 1; 196 } 197 198 ++path_iter; 199 ++split_constraints_iter; 200 } 201 202 if (options_.apk_artifacts && options_.output_dir) { 203 MultiApkGenerator generator{apk.get(), context_}; 204 MultiApkGeneratorOptions generator_options = { 205 options_.output_dir.value(), options_.apk_artifacts.value(), 206 options_.table_flattener_options, options_.kept_artifacts}; 207 if (!generator.FromBaseApk(generator_options)) { 208 return 1; 209 } 210 } 211 212 if (options_.output_path) { 213 std::unique_ptr<IArchiveWriter> writer = 214 CreateZipFileArchiveWriter(context_->GetDiagnostics(), options_.output_path.value()); 215 if (!apk->WriteToArchive(context_, options_.table_flattener_options, writer.get())) { 216 return 1; 217 } 218 } 219 220 return 0; 221 } 222 223 private: 224 bool WriteSplitApk(ResourceTable* table, xml::XmlResource* manifest, IArchiveWriter* writer) { 225 BigBuffer manifest_buffer(4096); 226 XmlFlattener xml_flattener(&manifest_buffer, {}); 227 if (!xml_flattener.Consume(context_, manifest)) { 228 return false; 229 } 230 231 io::BigBufferInputStream manifest_buffer_in(&manifest_buffer); 232 if (!io::CopyInputStreamToArchive(context_, &manifest_buffer_in, "AndroidManifest.xml", 233 ArchiveEntry::kCompress, writer)) { 234 return false; 235 } 236 237 std::map<std::pair<ConfigDescription, StringPiece>, FileReference*> config_sorted_files; 238 for (auto& pkg : table->packages) { 239 for (auto& type : pkg->types) { 240 // Sort by config and name, so that we get better locality in the zip file. 241 config_sorted_files.clear(); 242 243 for (auto& entry : type->entries) { 244 for (auto& config_value : entry->values) { 245 auto* file_ref = ValueCast<FileReference>(config_value->value.get()); 246 if (file_ref == nullptr) { 247 continue; 248 } 249 250 if (file_ref->file == nullptr) { 251 ResourceNameRef name(pkg->name, type->type, entry->name); 252 context_->GetDiagnostics()->Warn(DiagMessage(file_ref->GetSource()) 253 << "file for resource " << name << " with config '" 254 << config_value->config << "' not found"); 255 continue; 256 } 257 258 const StringPiece entry_name = entry->name; 259 config_sorted_files[std::make_pair(config_value->config, entry_name)] = file_ref; 260 } 261 } 262 263 for (auto& entry : config_sorted_files) { 264 FileReference* file_ref = entry.second; 265 if (!io::CopyFileToArchivePreserveCompression(context_, file_ref->file, *file_ref->path, 266 writer)) { 267 return false; 268 } 269 } 270 } 271 } 272 273 BigBuffer table_buffer(4096); 274 TableFlattener table_flattener(options_.table_flattener_options, &table_buffer); 275 if (!table_flattener.Consume(context_, table)) { 276 return false; 277 } 278 279 io::BigBufferInputStream table_buffer_in(&table_buffer); 280 return io::CopyInputStreamToArchive(context_, &table_buffer_in, "resources.arsc", 281 ArchiveEntry::kAlign, writer); 282 } 283 284 bool WriteShortenedPathsMap(const std::map<std::string, std::string> &path_map, 285 const std::string &file_path) { 286 std::stringstream ss; 287 for (auto it = path_map.cbegin(); it != path_map.cend(); ++it) { 288 ss << it->first << " -> " << it->second << "\n"; 289 } 290 return WriteStringToFile(ss.str(), file_path); 291 } 292 293 OptimizeOptions options_; 294 OptimizeContext* context_; 295 }; 296 297 bool ExtractObfuscationWhitelistFromConfig(const std::string& path, OptimizeContext* context, 298 OptimizeOptions* options) { 299 std::string contents; 300 if (!ReadFileToString(path, &contents, true)) { 301 context->GetDiagnostics()->Error(DiagMessage() 302 << "failed to parse whitelist from config file: " << path); 303 return false; 304 } 305 for (StringPiece resource_name : util::Tokenize(contents, ',')) { 306 options->table_flattener_options.whitelisted_resources.insert( 307 resource_name.to_string()); 308 } 309 return true; 310 } 311 312 bool ExtractConfig(const std::string& path, OptimizeContext* context, 313 OptimizeOptions* options) { 314 std::string content; 315 if (!android::base::ReadFileToString(path, &content, true /*follow_symlinks*/)) { 316 context->GetDiagnostics()->Error(DiagMessage(path) << "failed reading whitelist"); 317 return false; 318 } 319 320 size_t line_no = 0; 321 for (StringPiece line : util::Tokenize(content, '\n')) { 322 line_no++; 323 line = util::TrimWhitespace(line); 324 if (line.empty()) { 325 continue; 326 } 327 328 auto split_line = util::Split(line, '#'); 329 if (split_line.size() < 2) { 330 context->GetDiagnostics()->Error(DiagMessage(line) << "No # found in line"); 331 return false; 332 } 333 StringPiece resource_string = split_line[0]; 334 StringPiece directives = split_line[1]; 335 ResourceNameRef resource_name; 336 if (!ResourceUtils::ParseResourceName(resource_string, &resource_name)) { 337 context->GetDiagnostics()->Error(DiagMessage(line) << "Malformed resource name"); 338 return false; 339 } 340 if (!resource_name.package.empty()) { 341 context->GetDiagnostics()->Error(DiagMessage(line) 342 << "Package set for resource. Only use type/name"); 343 return false; 344 } 345 for (StringPiece directive : util::Tokenize(directives, ',')) { 346 if (directive == "remove") { 347 options->resources_blacklist.insert(resource_name.ToResourceName()); 348 } else if (directive == "no_obfuscate") { 349 options->table_flattener_options.whitelisted_resources.insert( 350 resource_name.entry.to_string()); 351 } 352 } 353 } 354 return true; 355 } 356 357 bool ExtractAppDataFromManifest(OptimizeContext* context, const LoadedApk* apk, 358 OptimizeOptions* out_options) { 359 const xml::XmlResource* manifest = apk->GetManifest(); 360 if (manifest == nullptr) { 361 return false; 362 } 363 364 Maybe<AppInfo> app_info = ExtractAppInfoFromBinaryManifest(*manifest, context->GetDiagnostics()); 365 if (!app_info) { 366 context->GetDiagnostics()->Error(DiagMessage() 367 << "failed to extract data from AndroidManifest.xml"); 368 return false; 369 } 370 371 out_options->app_info = std::move(app_info.value()); 372 context->SetMinSdkVersion(out_options->app_info.min_sdk_version.value_or_default(0)); 373 return true; 374 } 375 376 int OptimizeCommand::Action(const std::vector<std::string>& args) { 377 if (args.size() != 1u) { 378 std::cerr << "must have one APK as argument.\n\n"; 379 Usage(&std::cerr); 380 return 1; 381 } 382 383 const std::string& apk_path = args[0]; 384 OptimizeContext context; 385 context.SetVerbose(verbose_); 386 IDiagnostics* diag = context.GetDiagnostics(); 387 388 if (config_path_) { 389 std::string& path = config_path_.value(); 390 Maybe<ConfigurationParser> for_path = ConfigurationParser::ForPath(path); 391 if (for_path) { 392 options_.apk_artifacts = for_path.value().WithDiagnostics(diag).Parse(apk_path); 393 if (!options_.apk_artifacts) { 394 diag->Error(DiagMessage() << "Failed to parse the output artifact list"); 395 return 1; 396 } 397 398 } else { 399 diag->Error(DiagMessage() << "Could not parse config file " << path); 400 return 1; 401 } 402 403 if (print_only_) { 404 for (const OutputArtifact& artifact : options_.apk_artifacts.value()) { 405 std::cout << artifact.name << std::endl; 406 } 407 return 0; 408 } 409 410 if (!kept_artifacts_.empty()) { 411 for (const std::string& artifact_str : kept_artifacts_) { 412 for (const StringPiece& artifact : util::Tokenize(artifact_str, ',')) { 413 options_.kept_artifacts.insert(artifact.to_string()); 414 } 415 } 416 } 417 418 // Since we know that we are going to process the APK (not just print targets), make sure we 419 // have somewhere to write them to. 420 if (!options_.output_dir) { 421 diag->Error(DiagMessage() << "Output directory is required when using a configuration file"); 422 return 1; 423 } 424 } else if (print_only_) { 425 diag->Error(DiagMessage() << "Asked to print artifacts without providing a configurations"); 426 return 1; 427 } 428 429 std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(apk_path, context.GetDiagnostics()); 430 if (!apk) { 431 return 1; 432 } 433 434 if (target_densities_) { 435 // Parse the target screen densities. 436 for (const StringPiece& config_str : util::Tokenize(target_densities_.value(), ',')) { 437 Maybe<uint16_t> target_density = ParseTargetDensityParameter(config_str, diag); 438 if (!target_density) { 439 return 1; 440 } 441 options_.table_splitter_options.preferred_densities.push_back(target_density.value()); 442 } 443 } 444 445 std::unique_ptr<IConfigFilter> filter; 446 if (!configs_.empty()) { 447 filter = ParseConfigFilterParameters(configs_, diag); 448 if (filter == nullptr) { 449 return 1; 450 } 451 options_.table_splitter_options.config_filter = filter.get(); 452 } 453 454 // Parse the split parameters. 455 for (const std::string& split_arg : split_args_) { 456 options_.split_paths.emplace_back(); 457 options_.split_constraints.emplace_back(); 458 if (!ParseSplitParameter(split_arg, diag, &options_.split_paths.back(), 459 &options_.split_constraints.back())) { 460 return 1; 461 } 462 } 463 464 if (options_.table_flattener_options.collapse_key_stringpool) { 465 if (whitelist_path_) { 466 std::string& path = whitelist_path_.value(); 467 if (!ExtractObfuscationWhitelistFromConfig(path, &context, &options_)) { 468 return 1; 469 } 470 } 471 } 472 473 if (resources_config_path_) { 474 std::string& path = resources_config_path_.value(); 475 if (!ExtractConfig(path, &context, &options_)) { 476 return 1; 477 } 478 } 479 480 if (!ExtractAppDataFromManifest(&context, apk.get(), &options_)) { 481 return 1; 482 } 483 484 Optimizer cmd(&context, options_); 485 return cmd.Run(std::move(apk)); 486 } 487 488 } // namespace aapt 489