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