1 /* 2 * Copyright (C) 2015 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 "AppInfo.h" 18 #include "BigBuffer.h" 19 #include "BinaryResourceParser.h" 20 #include "BindingXmlPullParser.h" 21 #include "Debug.h" 22 #include "Files.h" 23 #include "Flag.h" 24 #include "JavaClassGenerator.h" 25 #include "Linker.h" 26 #include "ManifestMerger.h" 27 #include "ManifestParser.h" 28 #include "ManifestValidator.h" 29 #include "NameMangler.h" 30 #include "Png.h" 31 #include "ProguardRules.h" 32 #include "ResourceParser.h" 33 #include "ResourceTable.h" 34 #include "ResourceTableResolver.h" 35 #include "ResourceValues.h" 36 #include "SdkConstants.h" 37 #include "SourceXmlPullParser.h" 38 #include "StringPiece.h" 39 #include "TableFlattener.h" 40 #include "Util.h" 41 #include "XmlFlattener.h" 42 #include "ZipFile.h" 43 44 #include <algorithm> 45 #include <androidfw/AssetManager.h> 46 #include <cstdlib> 47 #include <dirent.h> 48 #include <errno.h> 49 #include <fstream> 50 #include <iostream> 51 #include <sstream> 52 #include <sys/stat.h> 53 #include <unordered_set> 54 #include <utils/Errors.h> 55 56 constexpr const char* kAaptVersionStr = "2.0-alpha"; 57 58 using namespace aapt; 59 60 /** 61 * Used with smart pointers to free malloc'ed memory. 62 */ 63 struct DeleteMalloc { 64 void operator()(void* ptr) { 65 free(ptr); 66 } 67 }; 68 69 struct StaticLibraryData { 70 Source source; 71 std::unique_ptr<ZipFile> apk; 72 }; 73 74 /** 75 * Collect files from 'root', filtering out any files that do not 76 * match the FileFilter 'filter'. 77 */ 78 bool walkTree(const Source& root, const FileFilter& filter, 79 std::vector<Source>* outEntries) { 80 bool error = false; 81 82 for (const std::string& dirName : listFiles(root.path)) { 83 std::string dir = root.path; 84 appendPath(&dir, dirName); 85 86 FileType ft = getFileType(dir); 87 if (!filter(dirName, ft)) { 88 continue; 89 } 90 91 if (ft != FileType::kDirectory) { 92 continue; 93 } 94 95 for (const std::string& fileName : listFiles(dir)) { 96 std::string file(dir); 97 appendPath(&file, fileName); 98 99 FileType ft = getFileType(file); 100 if (!filter(fileName, ft)) { 101 continue; 102 } 103 104 if (ft != FileType::kRegular) { 105 Logger::error(Source{ file }) << "not a regular file." << std::endl; 106 error = true; 107 continue; 108 } 109 outEntries->push_back(Source{ file }); 110 } 111 } 112 return !error; 113 } 114 115 void versionStylesForCompat(const std::shared_ptr<ResourceTable>& table) { 116 for (auto& type : *table) { 117 if (type->type != ResourceType::kStyle) { 118 continue; 119 } 120 121 for (auto& entry : type->entries) { 122 // Add the versioned styles we want to create 123 // here. They are added to the table after 124 // iterating over the original set of styles. 125 // 126 // A stack is used since auto-generated styles 127 // from later versions should override 128 // auto-generated styles from earlier versions. 129 // Iterating over the styles is done in order, 130 // so we will always visit sdkVersions from smallest 131 // to largest. 132 std::stack<ResourceConfigValue> addStack; 133 134 for (ResourceConfigValue& configValue : entry->values) { 135 visitFunc<Style>(*configValue.value, [&](Style& style) { 136 // Collect which entries we've stripped and the smallest 137 // SDK level which was stripped. 138 size_t minSdkStripped = std::numeric_limits<size_t>::max(); 139 std::vector<Style::Entry> stripped; 140 141 // Iterate over the style's entries and erase/record the 142 // attributes whose SDK level exceeds the config's sdkVersion. 143 auto iter = style.entries.begin(); 144 while (iter != style.entries.end()) { 145 if (iter->key.name.package == u"android") { 146 size_t sdkLevel = findAttributeSdkLevel(iter->key.name); 147 if (sdkLevel > 1 && sdkLevel > configValue.config.sdkVersion) { 148 // Record that we are about to strip this. 149 stripped.emplace_back(std::move(*iter)); 150 minSdkStripped = std::min(minSdkStripped, sdkLevel); 151 152 // Erase this from this style. 153 iter = style.entries.erase(iter); 154 continue; 155 } 156 } 157 ++iter; 158 } 159 160 if (!stripped.empty()) { 161 // We have stripped attributes, so let's create a new style to hold them. 162 ConfigDescription versionConfig(configValue.config); 163 versionConfig.sdkVersion = minSdkStripped; 164 165 ResourceConfigValue value = { 166 versionConfig, 167 configValue.source, 168 {}, 169 170 // Create a copy of the original style. 171 std::unique_ptr<Value>(configValue.value->clone( 172 &table->getValueStringPool())) 173 }; 174 175 Style& newStyle = static_cast<Style&>(*value.value); 176 177 // Move the recorded stripped attributes into this new style. 178 std::move(stripped.begin(), stripped.end(), 179 std::back_inserter(newStyle.entries)); 180 181 // We will add this style to the table later. If we do it now, we will 182 // mess up iteration. 183 addStack.push(std::move(value)); 184 } 185 }); 186 } 187 188 auto comparator = 189 [](const ResourceConfigValue& lhs, const ConfigDescription& rhs) -> bool { 190 return lhs.config < rhs; 191 }; 192 193 while (!addStack.empty()) { 194 ResourceConfigValue& value = addStack.top(); 195 auto iter = std::lower_bound(entry->values.begin(), entry->values.end(), 196 value.config, comparator); 197 if (iter == entry->values.end() || iter->config != value.config) { 198 entry->values.insert(iter, std::move(value)); 199 } 200 addStack.pop(); 201 } 202 } 203 } 204 } 205 206 struct CompileItem { 207 ResourceName name; 208 ConfigDescription config; 209 Source source; 210 std::string extension; 211 }; 212 213 struct LinkItem { 214 ResourceName name; 215 ConfigDescription config; 216 Source source; 217 std::string originalPath; 218 ZipFile* apk; 219 std::u16string originalPackage; 220 }; 221 222 template <typename TChar> 223 static BasicStringPiece<TChar> getExtension(const BasicStringPiece<TChar>& str) { 224 auto iter = std::find(str.begin(), str.end(), static_cast<TChar>('.')); 225 if (iter == str.end()) { 226 return BasicStringPiece<TChar>(); 227 } 228 size_t offset = (iter - str.begin()) + 1; 229 return str.substr(offset, str.size() - offset); 230 } 231 232 std::string buildFileReference(const ResourceNameRef& name, const ConfigDescription& config, 233 const StringPiece& extension) { 234 std::stringstream path; 235 path << "res/" << name.type; 236 if (config != ConfigDescription{}) { 237 path << "-" << config; 238 } 239 path << "/" << util::utf16ToUtf8(name.entry); 240 if (!extension.empty()) { 241 path << "." << extension; 242 } 243 return path.str(); 244 } 245 246 std::string buildFileReference(const CompileItem& item) { 247 return buildFileReference(item.name, item.config, item.extension); 248 } 249 250 std::string buildFileReference(const LinkItem& item) { 251 return buildFileReference(item.name, item.config, getExtension<char>(item.originalPath)); 252 } 253 254 template <typename T> 255 bool addFileReference(const std::shared_ptr<ResourceTable>& table, const T& item) { 256 StringPool& pool = table->getValueStringPool(); 257 StringPool::Ref ref = pool.makeRef(util::utf8ToUtf16(buildFileReference(item)), 258 StringPool::Context{ 0, item.config }); 259 return table->addResource(item.name, item.config, item.source.line(0), 260 util::make_unique<FileReference>(ref)); 261 } 262 263 struct AaptOptions { 264 enum class Phase { 265 Link, 266 Compile, 267 Dump, 268 DumpStyleGraph, 269 }; 270 271 enum class PackageType { 272 StandardApp, 273 StaticLibrary, 274 }; 275 276 // The phase to process. 277 Phase phase; 278 279 // The type of package to produce. 280 PackageType packageType = PackageType::StandardApp; 281 282 // Details about the app. 283 AppInfo appInfo; 284 285 // The location of the manifest file. 286 Source manifest; 287 288 // The APK files to link. 289 std::vector<Source> input; 290 291 // The libraries these files may reference. 292 std::vector<Source> libraries; 293 294 // Output path. This can be a directory or file 295 // depending on the phase. 296 Source output; 297 298 // Directory in which to write binding xml files. 299 Source bindingOutput; 300 301 // Directory to in which to generate R.java. 302 Maybe<Source> generateJavaClass; 303 304 // File in which to produce proguard rules. 305 Maybe<Source> generateProguardRules; 306 307 // Whether to output verbose details about 308 // compilation. 309 bool verbose = false; 310 311 // Whether or not to auto-version styles or layouts 312 // referencing attributes defined in a newer SDK 313 // level than the style or layout is defined for. 314 bool versionStylesAndLayouts = true; 315 316 // The target style that will have it's style hierarchy dumped 317 // when the phase is DumpStyleGraph. 318 ResourceName dumpStyleTarget; 319 }; 320 321 struct IdCollector : public xml::Visitor { 322 IdCollector(const Source& source, const std::shared_ptr<ResourceTable>& table) : 323 mSource(source), mTable(table) { 324 } 325 326 virtual void visit(xml::Text* node) override {} 327 328 virtual void visit(xml::Namespace* node) override { 329 for (const auto& child : node->children) { 330 child->accept(this); 331 } 332 } 333 334 virtual void visit(xml::Element* node) override { 335 for (const xml::Attribute& attr : node->attributes) { 336 bool create = false; 337 bool priv = false; 338 ResourceNameRef nameRef; 339 if (ResourceParser::tryParseReference(attr.value, &nameRef, &create, &priv)) { 340 if (create) { 341 mTable->addResource(nameRef, {}, mSource.line(node->lineNumber), 342 util::make_unique<Id>()); 343 } 344 } 345 } 346 347 for (const auto& child : node->children) { 348 child->accept(this); 349 } 350 } 351 352 private: 353 Source mSource; 354 std::shared_ptr<ResourceTable> mTable; 355 }; 356 357 bool compileXml(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table, 358 const CompileItem& item, ZipFile* outApk) { 359 std::ifstream in(item.source.path, std::ifstream::binary); 360 if (!in) { 361 Logger::error(item.source) << strerror(errno) << std::endl; 362 return false; 363 } 364 365 SourceLogger logger(item.source); 366 std::unique_ptr<xml::Node> root = xml::inflate(&in, &logger); 367 if (!root) { 368 return false; 369 } 370 371 // Collect any resource ID's declared here. 372 IdCollector idCollector(item.source, table); 373 root->accept(&idCollector); 374 375 BigBuffer outBuffer(1024); 376 if (!xml::flatten(root.get(), options.appInfo.package, &outBuffer)) { 377 logger.error() << "failed to encode XML." << std::endl; 378 return false; 379 } 380 381 // Write the resulting compiled XML file to the output APK. 382 if (outApk->add(outBuffer, buildFileReference(item).data(), ZipEntry::kCompressStored, 383 nullptr) != android::NO_ERROR) { 384 Logger::error(options.output) << "failed to write compiled '" << item.source 385 << "' to apk." << std::endl; 386 return false; 387 } 388 return true; 389 } 390 391 /** 392 * Determines if a layout should be auto generated based on SDK level. We do not 393 * generate a layout if there is already a layout defined whose SDK version is greater than 394 * the one we want to generate. 395 */ 396 bool shouldGenerateVersionedResource(const std::shared_ptr<const ResourceTable>& table, 397 const ResourceName& name, const ConfigDescription& config, 398 int sdkVersionToGenerate) { 399 assert(sdkVersionToGenerate > config.sdkVersion); 400 const ResourceTableType* type; 401 const ResourceEntry* entry; 402 std::tie(type, entry) = table->findResource(name); 403 assert(type && entry); 404 405 auto iter = std::lower_bound(entry->values.begin(), entry->values.end(), config, 406 [](const ResourceConfigValue& lhs, const ConfigDescription& config) -> bool { 407 return lhs.config < config; 408 }); 409 410 assert(iter != entry->values.end()); 411 ++iter; 412 413 if (iter == entry->values.end()) { 414 return true; 415 } 416 417 ConfigDescription newConfig = config; 418 newConfig.sdkVersion = sdkVersionToGenerate; 419 return newConfig < iter->config; 420 } 421 422 bool linkXml(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table, 423 const std::shared_ptr<IResolver>& resolver, const LinkItem& item, 424 const void* data, size_t dataLen, ZipFile* outApk, std::queue<LinkItem>* outQueue, 425 proguard::KeepSet* keepSet) { 426 SourceLogger logger(item.source); 427 std::unique_ptr<xml::Node> root = xml::inflate(data, dataLen, &logger); 428 if (!root) { 429 return false; 430 } 431 432 xml::FlattenOptions xmlOptions; 433 if (options.packageType == AaptOptions::PackageType::StaticLibrary) { 434 xmlOptions.keepRawValues = true; 435 } 436 437 if (options.versionStylesAndLayouts) { 438 // We strip attributes that do not belong in this version of the resource. 439 // Non-version qualified resources have an implicit version 1 requirement. 440 xmlOptions.maxSdkAttribute = item.config.sdkVersion ? item.config.sdkVersion : 1; 441 } 442 443 if (options.generateProguardRules) { 444 proguard::collectProguardRules(item.name.type, item.source, root.get(), keepSet); 445 } 446 447 BigBuffer outBuffer(1024); 448 Maybe<size_t> minStrippedSdk = xml::flattenAndLink(item.source, root.get(), 449 item.originalPackage, resolver, 450 xmlOptions, &outBuffer); 451 if (!minStrippedSdk) { 452 logger.error() << "failed to encode XML." << std::endl; 453 return false; 454 } 455 456 if (minStrippedSdk.value() > 0) { 457 // Something was stripped, so let's generate a new file 458 // with the version of the smallest SDK version stripped. 459 // We can only generate a versioned layout if there doesn't exist a layout 460 // with sdk version greater than the current one but less than the one we 461 // want to generate. 462 if (shouldGenerateVersionedResource(table, item.name, item.config, 463 minStrippedSdk.value())) { 464 LinkItem newWork = item; 465 newWork.config.sdkVersion = minStrippedSdk.value(); 466 outQueue->push(newWork); 467 468 if (!addFileReference(table, newWork)) { 469 Logger::error(options.output) << "failed to add auto-versioned resource '" 470 << newWork.name << "'." << std::endl; 471 return false; 472 } 473 } 474 } 475 476 if (outApk->add(outBuffer, buildFileReference(item).data(), ZipEntry::kCompressDeflated, 477 nullptr) != android::NO_ERROR) { 478 Logger::error(options.output) << "failed to write linked file '" 479 << buildFileReference(item) << "' to apk." << std::endl; 480 return false; 481 } 482 return true; 483 } 484 485 bool compilePng(const AaptOptions& options, const CompileItem& item, ZipFile* outApk) { 486 std::ifstream in(item.source.path, std::ifstream::binary); 487 if (!in) { 488 Logger::error(item.source) << strerror(errno) << std::endl; 489 return false; 490 } 491 492 BigBuffer outBuffer(4096); 493 std::string err; 494 Png png; 495 if (!png.process(item.source, in, &outBuffer, {}, &err)) { 496 Logger::error(item.source) << err << std::endl; 497 return false; 498 } 499 500 if (outApk->add(outBuffer, buildFileReference(item).data(), ZipEntry::kCompressStored, 501 nullptr) != android::NO_ERROR) { 502 Logger::error(options.output) << "failed to write compiled '" << item.source 503 << "' to apk." << std::endl; 504 return false; 505 } 506 return true; 507 } 508 509 bool copyFile(const AaptOptions& options, const CompileItem& item, ZipFile* outApk) { 510 if (outApk->add(item.source.path.data(), buildFileReference(item).data(), 511 ZipEntry::kCompressStored, nullptr) != android::NO_ERROR) { 512 Logger::error(options.output) << "failed to copy file '" << item.source << "' to apk." 513 << std::endl; 514 return false; 515 } 516 return true; 517 } 518 519 bool compileManifest(const AaptOptions& options, const std::shared_ptr<IResolver>& resolver, 520 const std::map<std::shared_ptr<ResourceTable>, StaticLibraryData>& libApks, 521 const android::ResTable& table, ZipFile* outApk, proguard::KeepSet* keepSet) { 522 if (options.verbose) { 523 Logger::note(options.manifest) << "compiling AndroidManifest.xml." << std::endl; 524 } 525 526 std::ifstream in(options.manifest.path, std::ifstream::binary); 527 if (!in) { 528 Logger::error(options.manifest) << strerror(errno) << std::endl; 529 return false; 530 } 531 532 SourceLogger logger(options.manifest); 533 std::unique_ptr<xml::Node> root = xml::inflate(&in, &logger); 534 if (!root) { 535 return false; 536 } 537 538 ManifestMerger merger({}); 539 if (!merger.setAppManifest(options.manifest, options.appInfo.package, std::move(root))) { 540 return false; 541 } 542 543 for (const auto& entry : libApks) { 544 ZipFile* libApk = entry.second.apk.get(); 545 const std::u16string& libPackage = entry.first->getPackage(); 546 const Source& libSource = entry.second.source; 547 548 ZipEntry* zipEntry = libApk->getEntryByName("AndroidManifest.xml"); 549 if (!zipEntry) { 550 continue; 551 } 552 553 std::unique_ptr<void, DeleteMalloc> uncompressedData = std::unique_ptr<void, DeleteMalloc>( 554 libApk->uncompress(zipEntry)); 555 assert(uncompressedData); 556 557 SourceLogger logger(libSource); 558 std::unique_ptr<xml::Node> libRoot = xml::inflate(uncompressedData.get(), 559 zipEntry->getUncompressedLen(), &logger); 560 if (!libRoot) { 561 return false; 562 } 563 564 if (!merger.mergeLibraryManifest(libSource, libPackage, std::move(libRoot))) { 565 return false; 566 } 567 } 568 569 if (options.generateProguardRules) { 570 proguard::collectProguardRulesForManifest(options.manifest, merger.getMergedXml(), 571 keepSet); 572 } 573 574 BigBuffer outBuffer(1024); 575 if (!xml::flattenAndLink(options.manifest, merger.getMergedXml(), options.appInfo.package, 576 resolver, {}, &outBuffer)) { 577 return false; 578 } 579 580 std::unique_ptr<uint8_t[]> data = util::copy(outBuffer); 581 582 android::ResXMLTree tree; 583 if (tree.setTo(data.get(), outBuffer.size(), false) != android::NO_ERROR) { 584 return false; 585 } 586 587 ManifestValidator validator(table); 588 if (!validator.validate(options.manifest, &tree)) { 589 return false; 590 } 591 592 if (outApk->add(data.get(), outBuffer.size(), "AndroidManifest.xml", 593 ZipEntry::kCompressStored, nullptr) != android::NO_ERROR) { 594 Logger::error(options.output) << "failed to write 'AndroidManifest.xml' to apk." 595 << std::endl; 596 return false; 597 } 598 return true; 599 } 600 601 static bool compileValues(const std::shared_ptr<ResourceTable>& table, const Source& source, 602 const ConfigDescription& config) { 603 std::ifstream in(source.path, std::ifstream::binary); 604 if (!in) { 605 Logger::error(source) << strerror(errno) << std::endl; 606 return false; 607 } 608 609 std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<SourceXmlPullParser>(in); 610 ResourceParser parser(table, source, config, xmlParser); 611 return parser.parse(); 612 } 613 614 struct ResourcePathData { 615 std::u16string resourceDir; 616 std::u16string name; 617 std::string extension; 618 ConfigDescription config; 619 }; 620 621 /** 622 * Resource file paths are expected to look like: 623 * [--/res/]type[-config]/name 624 */ 625 static Maybe<ResourcePathData> extractResourcePathData(const Source& source) { 626 // TODO(adamlesinski): Use Windows path separator on windows. 627 std::vector<std::string> parts = util::splitAndLowercase(source.path, '/'); 628 if (parts.size() < 2) { 629 Logger::error(source) << "bad resource path." << std::endl; 630 return {}; 631 } 632 633 std::string& dir = parts[parts.size() - 2]; 634 StringPiece dirStr = dir; 635 636 ConfigDescription config; 637 size_t dashPos = dir.find('-'); 638 if (dashPos != std::string::npos) { 639 StringPiece configStr = dirStr.substr(dashPos + 1, dir.size() - (dashPos + 1)); 640 if (!ConfigDescription::parse(configStr, &config)) { 641 Logger::error(source) 642 << "invalid configuration '" 643 << configStr 644 << "'." 645 << std::endl; 646 return {}; 647 } 648 dirStr = dirStr.substr(0, dashPos); 649 } 650 651 std::string& filename = parts[parts.size() - 1]; 652 StringPiece name = filename; 653 StringPiece extension; 654 size_t dotPos = filename.find('.'); 655 if (dotPos != std::string::npos) { 656 extension = name.substr(dotPos + 1, filename.size() - (dotPos + 1)); 657 name = name.substr(0, dotPos); 658 } 659 660 return ResourcePathData{ 661 util::utf8ToUtf16(dirStr), 662 util::utf8ToUtf16(name), 663 extension.toString(), 664 config 665 }; 666 } 667 668 bool writeResourceTable(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table, 669 const TableFlattener::Options& flattenerOptions, ZipFile* outApk) { 670 if (table->begin() != table->end()) { 671 BigBuffer buffer(1024); 672 TableFlattener flattener(flattenerOptions); 673 if (!flattener.flatten(&buffer, *table)) { 674 Logger::error() << "failed to flatten resource table." << std::endl; 675 return false; 676 } 677 678 if (options.verbose) { 679 Logger::note() << "Final resource table size=" << util::formatSize(buffer.size()) 680 << std::endl; 681 } 682 683 if (outApk->add(buffer, "resources.arsc", ZipEntry::kCompressStored, nullptr) != 684 android::NO_ERROR) { 685 Logger::note(options.output) << "failed to store resource table." << std::endl; 686 return false; 687 } 688 } 689 return true; 690 } 691 692 /** 693 * For each FileReference in the table, adds a LinkItem to the link queue for processing. 694 */ 695 static void addApkFilesToLinkQueue(const std::u16string& package, const Source& source, 696 const std::shared_ptr<ResourceTable>& table, 697 const std::unique_ptr<ZipFile>& apk, 698 std::queue<LinkItem>* outLinkQueue) { 699 bool mangle = package != table->getPackage(); 700 for (auto& type : *table) { 701 for (auto& entry : type->entries) { 702 ResourceName name = { package, type->type, entry->name }; 703 if (mangle) { 704 NameMangler::mangle(table->getPackage(), &name.entry); 705 } 706 707 for (auto& value : entry->values) { 708 visitFunc<FileReference>(*value.value, [&](FileReference& ref) { 709 std::string pathUtf8 = util::utf16ToUtf8(*ref.path); 710 Source newSource = source; 711 newSource.path += "/"; 712 newSource.path += pathUtf8; 713 outLinkQueue->push(LinkItem{ 714 name, value.config, newSource, pathUtf8, apk.get(), 715 table->getPackage() }); 716 // Now rewrite the file path. 717 if (mangle) { 718 ref.path = table->getValueStringPool().makeRef(util::utf8ToUtf16( 719 buildFileReference(name, value.config, 720 getExtension<char>(pathUtf8)))); 721 } 722 }); 723 } 724 } 725 } 726 } 727 728 static constexpr int kOpenFlags = ZipFile::kOpenCreate | ZipFile::kOpenTruncate | 729 ZipFile::kOpenReadWrite; 730 731 bool link(const AaptOptions& options, const std::shared_ptr<ResourceTable>& outTable, 732 const std::shared_ptr<IResolver>& resolver) { 733 std::map<std::shared_ptr<ResourceTable>, StaticLibraryData> apkFiles; 734 std::unordered_set<std::u16string> linkedPackages; 735 736 // Populate the linkedPackages with our own. 737 linkedPackages.insert(options.appInfo.package); 738 739 // Load all APK files. 740 for (const Source& source : options.input) { 741 std::unique_ptr<ZipFile> zipFile = util::make_unique<ZipFile>(); 742 if (zipFile->open(source.path.data(), ZipFile::kOpenReadOnly) != android::NO_ERROR) { 743 Logger::error(source) << "failed to open: " << strerror(errno) << std::endl; 744 return false; 745 } 746 747 std::shared_ptr<ResourceTable> table = std::make_shared<ResourceTable>(); 748 749 ZipEntry* entry = zipFile->getEntryByName("resources.arsc"); 750 if (!entry) { 751 Logger::error(source) << "missing 'resources.arsc'." << std::endl; 752 return false; 753 } 754 755 std::unique_ptr<void, DeleteMalloc> uncompressedData = std::unique_ptr<void, DeleteMalloc>( 756 zipFile->uncompress(entry)); 757 assert(uncompressedData); 758 759 BinaryResourceParser parser(table, resolver, source, options.appInfo.package, 760 uncompressedData.get(), entry->getUncompressedLen()); 761 if (!parser.parse()) { 762 return false; 763 } 764 765 // Keep track of where this table came from. 766 apkFiles[table] = StaticLibraryData{ source, std::move(zipFile) }; 767 768 // Add the package to the set of linked packages. 769 linkedPackages.insert(table->getPackage()); 770 } 771 772 std::queue<LinkItem> linkQueue; 773 for (auto& p : apkFiles) { 774 const std::shared_ptr<ResourceTable>& inTable = p.first; 775 776 // Collect all FileReferences and add them to the queue for processing. 777 addApkFilesToLinkQueue(options.appInfo.package, p.second.source, inTable, p.second.apk, 778 &linkQueue); 779 780 // Merge the tables. 781 if (!outTable->merge(std::move(*inTable))) { 782 return false; 783 } 784 } 785 786 // Version all styles referencing attributes outside of their specified SDK version. 787 if (options.versionStylesAndLayouts) { 788 versionStylesForCompat(outTable); 789 } 790 791 { 792 // Now that everything is merged, let's link it. 793 Linker::Options linkerOptions; 794 if (options.packageType == AaptOptions::PackageType::StaticLibrary) { 795 linkerOptions.linkResourceIds = false; 796 } 797 Linker linker(outTable, resolver, linkerOptions); 798 if (!linker.linkAndValidate()) { 799 return false; 800 } 801 802 // Verify that all symbols exist. 803 const auto& unresolvedRefs = linker.getUnresolvedReferences(); 804 if (!unresolvedRefs.empty()) { 805 for (const auto& entry : unresolvedRefs) { 806 for (const auto& source : entry.second) { 807 Logger::error(source) << "unresolved symbol '" << entry.first << "'." 808 << std::endl; 809 } 810 } 811 return false; 812 } 813 } 814 815 // Open the output APK file for writing. 816 ZipFile outApk; 817 if (outApk.open(options.output.path.data(), kOpenFlags) != android::NO_ERROR) { 818 Logger::error(options.output) << "failed to open: " << strerror(errno) << std::endl; 819 return false; 820 } 821 822 proguard::KeepSet keepSet; 823 824 android::ResTable binTable; 825 if (!compileManifest(options, resolver, apkFiles, binTable, &outApk, &keepSet)) { 826 return false; 827 } 828 829 for (; !linkQueue.empty(); linkQueue.pop()) { 830 const LinkItem& item = linkQueue.front(); 831 832 assert(!item.originalPackage.empty()); 833 ZipEntry* entry = item.apk->getEntryByName(item.originalPath.data()); 834 if (!entry) { 835 Logger::error(item.source) << "failed to find '" << item.originalPath << "'." 836 << std::endl; 837 return false; 838 } 839 840 if (util::stringEndsWith<char>(item.originalPath, ".xml")) { 841 void* uncompressedData = item.apk->uncompress(entry); 842 assert(uncompressedData); 843 844 if (!linkXml(options, outTable, resolver, item, uncompressedData, 845 entry->getUncompressedLen(), &outApk, &linkQueue, &keepSet)) { 846 Logger::error(options.output) << "failed to link '" << item.originalPath << "'." 847 << std::endl; 848 return false; 849 } 850 } else { 851 if (outApk.add(item.apk, entry, buildFileReference(item).data(), 0, nullptr) != 852 android::NO_ERROR) { 853 Logger::error(options.output) << "failed to copy '" << item.originalPath << "'." 854 << std::endl; 855 return false; 856 } 857 } 858 } 859 860 // Generate the Java class file. 861 if (options.generateJavaClass) { 862 JavaClassGenerator::Options javaOptions; 863 if (options.packageType == AaptOptions::PackageType::StaticLibrary) { 864 javaOptions.useFinal = false; 865 } 866 JavaClassGenerator generator(outTable, javaOptions); 867 868 for (const std::u16string& package : linkedPackages) { 869 Source outPath = options.generateJavaClass.value(); 870 871 // Build the output directory from the package name. 872 // Eg. com.android.app -> com/android/app 873 const std::string packageUtf8 = util::utf16ToUtf8(package); 874 for (StringPiece part : util::tokenize<char>(packageUtf8, '.')) { 875 appendPath(&outPath.path, part); 876 } 877 878 if (!mkdirs(outPath.path)) { 879 Logger::error(outPath) << strerror(errno) << std::endl; 880 return false; 881 } 882 883 appendPath(&outPath.path, "R.java"); 884 885 if (options.verbose) { 886 Logger::note(outPath) << "writing Java symbols." << std::endl; 887 } 888 889 std::ofstream fout(outPath.path); 890 if (!fout) { 891 Logger::error(outPath) << strerror(errno) << std::endl; 892 return false; 893 } 894 895 if (!generator.generate(package, fout)) { 896 Logger::error(outPath) << generator.getError() << "." << std::endl; 897 return false; 898 } 899 } 900 } 901 902 // Generate the Proguard rules file. 903 if (options.generateProguardRules) { 904 const Source& outPath = options.generateProguardRules.value(); 905 906 if (options.verbose) { 907 Logger::note(outPath) << "writing proguard rules." << std::endl; 908 } 909 910 std::ofstream fout(outPath.path); 911 if (!fout) { 912 Logger::error(outPath) << strerror(errno) << std::endl; 913 return false; 914 } 915 916 if (!proguard::writeKeepSet(&fout, keepSet)) { 917 Logger::error(outPath) << "failed to write proguard rules." << std::endl; 918 return false; 919 } 920 } 921 922 outTable->getValueStringPool().prune(); 923 outTable->getValueStringPool().sort( 924 [](const StringPool::Entry& a, const StringPool::Entry& b) -> bool { 925 if (a.context.priority < b.context.priority) { 926 return true; 927 } 928 929 if (a.context.priority > b.context.priority) { 930 return false; 931 } 932 return a.value < b.value; 933 }); 934 935 936 // Flatten the resource table. 937 TableFlattener::Options flattenerOptions; 938 if (options.packageType != AaptOptions::PackageType::StaticLibrary) { 939 flattenerOptions.useExtendedChunks = false; 940 } 941 942 if (!writeResourceTable(options, outTable, flattenerOptions, &outApk)) { 943 return false; 944 } 945 946 outApk.flush(); 947 return true; 948 } 949 950 bool compile(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table, 951 const std::shared_ptr<IResolver>& resolver) { 952 std::queue<CompileItem> compileQueue; 953 bool error = false; 954 955 // Compile all the resource files passed in on the command line. 956 for (const Source& source : options.input) { 957 // Need to parse the resource type/config/filename. 958 Maybe<ResourcePathData> maybePathData = extractResourcePathData(source); 959 if (!maybePathData) { 960 return false; 961 } 962 963 const ResourcePathData& pathData = maybePathData.value(); 964 if (pathData.resourceDir == u"values") { 965 // The file is in the values directory, which means its contents will 966 // go into the resource table. 967 if (options.verbose) { 968 Logger::note(source) << "compiling values." << std::endl; 969 } 970 971 error |= !compileValues(table, source, pathData.config); 972 } else { 973 // The file is in a directory like 'layout' or 'drawable'. Find out 974 // the type. 975 const ResourceType* type = parseResourceType(pathData.resourceDir); 976 if (!type) { 977 Logger::error(source) << "invalid resource type '" << pathData.resourceDir << "'." 978 << std::endl; 979 return false; 980 } 981 982 compileQueue.push(CompileItem{ 983 ResourceName{ table->getPackage(), *type, pathData.name }, 984 pathData.config, 985 source, 986 pathData.extension 987 }); 988 } 989 } 990 991 if (error) { 992 return false; 993 } 994 // Open the output APK file for writing. 995 ZipFile outApk; 996 if (outApk.open(options.output.path.data(), kOpenFlags) != android::NO_ERROR) { 997 Logger::error(options.output) << "failed to open: " << strerror(errno) << std::endl; 998 return false; 999 } 1000 1001 // Compile each file. 1002 for (; !compileQueue.empty(); compileQueue.pop()) { 1003 const CompileItem& item = compileQueue.front(); 1004 1005 // Add the file name to the resource table. 1006 error |= !addFileReference(table, item); 1007 1008 if (item.extension == "xml") { 1009 error |= !compileXml(options, table, item, &outApk); 1010 } else if (item.extension == "png" || item.extension == "9.png") { 1011 error |= !compilePng(options, item, &outApk); 1012 } else { 1013 error |= !copyFile(options, item, &outApk); 1014 } 1015 } 1016 1017 if (error) { 1018 return false; 1019 } 1020 1021 // Link and assign resource IDs. 1022 Linker linker(table, resolver, {}); 1023 if (!linker.linkAndValidate()) { 1024 return false; 1025 } 1026 1027 // Flatten the resource table. 1028 if (!writeResourceTable(options, table, {}, &outApk)) { 1029 return false; 1030 } 1031 1032 outApk.flush(); 1033 return true; 1034 } 1035 1036 bool loadAppInfo(const Source& source, AppInfo* outInfo) { 1037 std::ifstream ifs(source.path, std::ifstream::in | std::ifstream::binary); 1038 if (!ifs) { 1039 Logger::error(source) << strerror(errno) << std::endl; 1040 return false; 1041 } 1042 1043 ManifestParser parser; 1044 std::shared_ptr<XmlPullParser> pullParser = std::make_shared<SourceXmlPullParser>(ifs); 1045 return parser.parse(source, pullParser, outInfo); 1046 } 1047 1048 static void printCommandsAndDie() { 1049 std::cerr << "The following commands are supported:" << std::endl << std::endl; 1050 std::cerr << "compile compiles a subset of resources" << std::endl; 1051 std::cerr << "link links together compiled resources and libraries" << std::endl; 1052 std::cerr << "dump dumps resource contents to to standard out" << std::endl; 1053 std::cerr << std::endl; 1054 std::cerr << "run aapt2 with one of the commands and the -h flag for extra details." 1055 << std::endl; 1056 exit(1); 1057 } 1058 1059 static AaptOptions prepareArgs(int argc, char** argv) { 1060 if (argc < 2) { 1061 std::cerr << "no command specified." << std::endl << std::endl; 1062 printCommandsAndDie(); 1063 } 1064 1065 const StringPiece command(argv[1]); 1066 argc -= 2; 1067 argv += 2; 1068 1069 AaptOptions options; 1070 1071 if (command == "--version" || command == "version") { 1072 std::cout << kAaptVersionStr << std::endl; 1073 exit(0); 1074 } else if (command == "link") { 1075 options.phase = AaptOptions::Phase::Link; 1076 } else if (command == "compile") { 1077 options.phase = AaptOptions::Phase::Compile; 1078 } else if (command == "dump") { 1079 options.phase = AaptOptions::Phase::Dump; 1080 } else if (command == "dump-style-graph") { 1081 options.phase = AaptOptions::Phase::DumpStyleGraph; 1082 } else { 1083 std::cerr << "invalid command '" << command << "'." << std::endl << std::endl; 1084 printCommandsAndDie(); 1085 } 1086 1087 bool isStaticLib = false; 1088 if (options.phase == AaptOptions::Phase::Link) { 1089 flag::requiredFlag("--manifest", "AndroidManifest.xml of your app", 1090 [&options](const StringPiece& arg) { 1091 options.manifest = Source{ arg.toString() }; 1092 }); 1093 1094 flag::optionalFlag("-I", "add an Android APK to link against", 1095 [&options](const StringPiece& arg) { 1096 options.libraries.push_back(Source{ arg.toString() }); 1097 }); 1098 1099 flag::optionalFlag("--java", "directory in which to generate R.java", 1100 [&options](const StringPiece& arg) { 1101 options.generateJavaClass = Source{ arg.toString() }; 1102 }); 1103 1104 flag::optionalFlag("--proguard", "file in which to output proguard rules", 1105 [&options](const StringPiece& arg) { 1106 options.generateProguardRules = Source{ arg.toString() }; 1107 }); 1108 1109 flag::optionalSwitch("--static-lib", "generate a static Android library", true, 1110 &isStaticLib); 1111 1112 flag::optionalFlag("--binding", "Output directory for binding XML files", 1113 [&options](const StringPiece& arg) { 1114 options.bindingOutput = Source{ arg.toString() }; 1115 }); 1116 flag::optionalSwitch("--no-version", "Disables automatic style and layout versioning", 1117 false, &options.versionStylesAndLayouts); 1118 } 1119 1120 if (options.phase == AaptOptions::Phase::Compile || 1121 options.phase == AaptOptions::Phase::Link) { 1122 // Common flags for all steps. 1123 flag::requiredFlag("-o", "Output path", [&options](const StringPiece& arg) { 1124 options.output = Source{ arg.toString() }; 1125 }); 1126 } 1127 1128 if (options.phase == AaptOptions::Phase::DumpStyleGraph) { 1129 flag::requiredFlag("--style", "Name of the style to dump", 1130 [&options](const StringPiece& arg, std::string* outError) -> bool { 1131 Reference styleReference; 1132 if (!ResourceParser::parseStyleParentReference(util::utf8ToUtf16(arg), 1133 &styleReference, outError)) { 1134 return false; 1135 } 1136 options.dumpStyleTarget = styleReference.name; 1137 return true; 1138 }); 1139 } 1140 1141 bool help = false; 1142 flag::optionalSwitch("-v", "enables verbose logging", true, &options.verbose); 1143 flag::optionalSwitch("-h", "displays this help menu", true, &help); 1144 1145 // Build the command string for output (eg. "aapt2 compile"). 1146 std::string fullCommand = "aapt2"; 1147 fullCommand += " "; 1148 fullCommand += command.toString(); 1149 1150 // Actually read the command line flags. 1151 flag::parse(argc, argv, fullCommand); 1152 1153 if (help) { 1154 flag::usageAndDie(fullCommand); 1155 } 1156 1157 if (isStaticLib) { 1158 options.packageType = AaptOptions::PackageType::StaticLibrary; 1159 } 1160 1161 // Copy all the remaining arguments. 1162 for (const std::string& arg : flag::getArgs()) { 1163 options.input.push_back(Source{ arg }); 1164 } 1165 return options; 1166 } 1167 1168 static bool doDump(const AaptOptions& options) { 1169 for (const Source& source : options.input) { 1170 std::unique_ptr<ZipFile> zipFile = util::make_unique<ZipFile>(); 1171 if (zipFile->open(source.path.data(), ZipFile::kOpenReadOnly) != android::NO_ERROR) { 1172 Logger::error(source) << "failed to open: " << strerror(errno) << std::endl; 1173 return false; 1174 } 1175 1176 std::shared_ptr<ResourceTable> table = std::make_shared<ResourceTable>(); 1177 std::shared_ptr<ResourceTableResolver> resolver = 1178 std::make_shared<ResourceTableResolver>( 1179 table, std::vector<std::shared_ptr<const android::AssetManager>>()); 1180 1181 ZipEntry* entry = zipFile->getEntryByName("resources.arsc"); 1182 if (!entry) { 1183 Logger::error(source) << "missing 'resources.arsc'." << std::endl; 1184 return false; 1185 } 1186 1187 std::unique_ptr<void, DeleteMalloc> uncompressedData = std::unique_ptr<void, DeleteMalloc>( 1188 zipFile->uncompress(entry)); 1189 assert(uncompressedData); 1190 1191 BinaryResourceParser parser(table, resolver, source, {}, uncompressedData.get(), 1192 entry->getUncompressedLen()); 1193 if (!parser.parse()) { 1194 return false; 1195 } 1196 1197 if (options.phase == AaptOptions::Phase::Dump) { 1198 Debug::printTable(table); 1199 } else if (options.phase == AaptOptions::Phase::DumpStyleGraph) { 1200 Debug::printStyleGraph(table, options.dumpStyleTarget); 1201 } 1202 } 1203 return true; 1204 } 1205 1206 int main(int argc, char** argv) { 1207 Logger::setLog(std::make_shared<Log>(std::cerr, std::cerr)); 1208 AaptOptions options = prepareArgs(argc, argv); 1209 1210 if (options.phase == AaptOptions::Phase::Dump || 1211 options.phase == AaptOptions::Phase::DumpStyleGraph) { 1212 if (!doDump(options)) { 1213 return 1; 1214 } 1215 return 0; 1216 } 1217 1218 // If we specified a manifest, go ahead and load the package name from the manifest. 1219 if (!options.manifest.path.empty()) { 1220 if (!loadAppInfo(options.manifest, &options.appInfo)) { 1221 return false; 1222 } 1223 1224 if (options.appInfo.package.empty()) { 1225 Logger::error() << "no package name specified." << std::endl; 1226 return false; 1227 } 1228 } 1229 1230 // Every phase needs a resource table. 1231 std::shared_ptr<ResourceTable> table = std::make_shared<ResourceTable>(); 1232 1233 // The package name is empty when in the compile phase. 1234 table->setPackage(options.appInfo.package); 1235 if (options.appInfo.package == u"android") { 1236 table->setPackageId(0x01); 1237 } else { 1238 table->setPackageId(0x7f); 1239 } 1240 1241 // Load the included libraries. 1242 std::vector<std::shared_ptr<const android::AssetManager>> sources; 1243 for (const Source& source : options.libraries) { 1244 std::shared_ptr<android::AssetManager> assetManager = 1245 std::make_shared<android::AssetManager>(); 1246 int32_t cookie; 1247 if (!assetManager->addAssetPath(android::String8(source.path.data()), &cookie)) { 1248 Logger::error(source) << "failed to load library." << std::endl; 1249 return false; 1250 } 1251 1252 if (cookie == 0) { 1253 Logger::error(source) << "failed to load library." << std::endl; 1254 return false; 1255 } 1256 sources.push_back(assetManager); 1257 } 1258 1259 // Make the resolver that will cache IDs for us. 1260 std::shared_ptr<ResourceTableResolver> resolver = std::make_shared<ResourceTableResolver>( 1261 table, sources); 1262 1263 if (options.phase == AaptOptions::Phase::Compile) { 1264 if (!compile(options, table, resolver)) { 1265 Logger::error() << "aapt exiting with failures." << std::endl; 1266 return 1; 1267 } 1268 } else if (options.phase == AaptOptions::Phase::Link) { 1269 if (!link(options, table, resolver)) { 1270 Logger::error() << "aapt exiting with failures." << std::endl; 1271 return 1; 1272 } 1273 } 1274 return 0; 1275 } 1276