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 "Debug.h" 19#include "Flags.h" 20#include "Locale.h" 21#include "NameMangler.h" 22#include "ResourceUtils.h" 23#include "compile/IdAssigner.h" 24#include "filter/ConfigFilter.h" 25#include "flatten/Archive.h" 26#include "flatten/TableFlattener.h" 27#include "flatten/XmlFlattener.h" 28#include "io/FileSystem.h" 29#include "io/ZipArchive.h" 30#include "java/JavaClassGenerator.h" 31#include "java/ManifestClassGenerator.h" 32#include "java/ProguardRules.h" 33#include "link/Linkers.h" 34#include "link/ProductFilter.h" 35#include "link/ReferenceLinker.h" 36#include "link/ManifestFixer.h" 37#include "link/TableMerger.h" 38#include "process/IResourceTableConsumer.h" 39#include "process/SymbolTable.h" 40#include "proto/ProtoSerialize.h" 41#include "split/TableSplitter.h" 42#include "unflatten/BinaryResourceParser.h" 43#include "util/Files.h" 44#include "util/StringPiece.h" 45#include "xml/XmlDom.h" 46 47#include <google/protobuf/io/coded_stream.h> 48 49#include <fstream> 50#include <sys/stat.h> 51#include <vector> 52 53namespace aapt { 54 55struct LinkOptions { 56 std::string outputPath; 57 std::string manifestPath; 58 std::vector<std::string> includePaths; 59 std::vector<std::string> overlayFiles; 60 Maybe<std::string> generateJavaClassPath; 61 Maybe<std::u16string> customJavaPackage; 62 std::set<std::u16string> extraJavaPackages; 63 Maybe<std::string> generateProguardRulesPath; 64 bool noAutoVersion = false; 65 bool noVersionVectors = false; 66 bool staticLib = false; 67 bool noStaticLibPackages = false; 68 bool generateNonFinalIds = false; 69 std::vector<std::string> javadocAnnotations; 70 bool outputToDirectory = false; 71 bool autoAddOverlay = false; 72 bool doNotCompressAnything = false; 73 std::vector<std::string> extensionsToNotCompress; 74 Maybe<std::u16string> privateSymbols; 75 ManifestFixerOptions manifestFixerOptions; 76 std::unordered_set<std::string> products; 77 TableSplitterOptions tableSplitterOptions; 78}; 79 80class LinkContext : public IAaptContext { 81public: 82 LinkContext() : mNameMangler({}) { 83 } 84 85 IDiagnostics* getDiagnostics() override { 86 return &mDiagnostics; 87 } 88 89 NameMangler* getNameMangler() override { 90 return &mNameMangler; 91 } 92 93 void setNameManglerPolicy(const NameManglerPolicy& policy) { 94 mNameMangler = NameMangler(policy); 95 } 96 97 const std::u16string& getCompilationPackage() override { 98 return mCompilationPackage; 99 } 100 101 void setCompilationPackage(const StringPiece16& packageName) { 102 mCompilationPackage = packageName.toString(); 103 } 104 105 uint8_t getPackageId() override { 106 return mPackageId; 107 } 108 109 void setPackageId(uint8_t id) { 110 mPackageId = id; 111 } 112 113 SymbolTable* getExternalSymbols() override { 114 return &mSymbols; 115 } 116 117 bool verbose() override { 118 return mVerbose; 119 } 120 121 void setVerbose(bool val) { 122 mVerbose = val; 123 } 124 125private: 126 StdErrDiagnostics mDiagnostics; 127 NameMangler mNameMangler; 128 std::u16string mCompilationPackage; 129 uint8_t mPackageId = 0x0; 130 SymbolTable mSymbols; 131 bool mVerbose = false; 132}; 133 134static bool copyFileToArchive(io::IFile* file, const std::string& outPath, 135 uint32_t compressionFlags, 136 IArchiveWriter* writer, IAaptContext* context) { 137 std::unique_ptr<io::IData> data = file->openAsData(); 138 if (!data) { 139 context->getDiagnostics()->error(DiagMessage(file->getSource()) 140 << "failed to open file"); 141 return false; 142 } 143 144 const uint8_t* buffer = reinterpret_cast<const uint8_t*>(data->data()); 145 size_t bufferSize = data->size(); 146 147 // If the file ends with .flat, we must strip off the CompiledFileHeader from it. 148 if (util::stringEndsWith<char>(file->getSource().path, ".flat")) { 149 CompiledFileInputStream inputStream(data->data(), data->size()); 150 if (!inputStream.CompiledFile()) { 151 context->getDiagnostics()->error(DiagMessage(file->getSource()) 152 << "invalid compiled file header"); 153 return false; 154 } 155 buffer = reinterpret_cast<const uint8_t*>(inputStream.data()); 156 bufferSize = inputStream.size(); 157 } 158 159 if (context->verbose()) { 160 context->getDiagnostics()->note(DiagMessage() << "writing " << outPath << " to archive"); 161 } 162 163 if (writer->startEntry(outPath, compressionFlags)) { 164 if (writer->writeEntry(buffer, bufferSize)) { 165 if (writer->finishEntry()) { 166 return true; 167 } 168 } 169 } 170 171 context->getDiagnostics()->error(DiagMessage() << "failed to write file " << outPath); 172 return false; 173} 174 175static bool flattenXml(xml::XmlResource* xmlRes, const StringPiece& path, Maybe<size_t> maxSdkLevel, 176 bool keepRawValues, IArchiveWriter* writer, IAaptContext* context) { 177 BigBuffer buffer(1024); 178 XmlFlattenerOptions options = {}; 179 options.keepRawValues = keepRawValues; 180 options.maxSdkLevel = maxSdkLevel; 181 XmlFlattener flattener(&buffer, options); 182 if (!flattener.consume(context, xmlRes)) { 183 return false; 184 } 185 186 if (context->verbose()) { 187 DiagMessage msg; 188 msg << "writing " << path << " to archive"; 189 if (maxSdkLevel) { 190 msg << " maxSdkLevel=" << maxSdkLevel.value() << " keepRawValues=" << keepRawValues; 191 } 192 context->getDiagnostics()->note(msg); 193 } 194 195 if (writer->startEntry(path, ArchiveEntry::kCompress)) { 196 if (writer->writeEntry(buffer)) { 197 if (writer->finishEntry()) { 198 return true; 199 } 200 } 201 } 202 context->getDiagnostics()->error(DiagMessage() << "failed to write " << path << " to archive"); 203 return false; 204} 205 206/*static std::unique_ptr<ResourceTable> loadTable(const Source& source, const void* data, size_t len, 207 IDiagnostics* diag) { 208 std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>(); 209 BinaryResourceParser parser(diag, table.get(), source, data, len); 210 if (!parser.parse()) { 211 return {}; 212 } 213 return table; 214}*/ 215 216static std::unique_ptr<ResourceTable> loadTableFromPb(const Source& source, 217 const void* data, size_t len, 218 IDiagnostics* diag) { 219 pb::ResourceTable pbTable; 220 if (!pbTable.ParseFromArray(data, len)) { 221 diag->error(DiagMessage(source) << "invalid compiled table"); 222 return {}; 223 } 224 225 std::unique_ptr<ResourceTable> table = deserializeTableFromPb(pbTable, source, diag); 226 if (!table) { 227 return {}; 228 } 229 return table; 230} 231 232/** 233 * Inflates an XML file from the source path. 234 */ 235static std::unique_ptr<xml::XmlResource> loadXml(const std::string& path, IDiagnostics* diag) { 236 std::ifstream fin(path, std::ifstream::binary); 237 if (!fin) { 238 diag->error(DiagMessage(path) << strerror(errno)); 239 return {}; 240 } 241 return xml::inflate(&fin, diag, Source(path)); 242} 243 244static std::unique_ptr<xml::XmlResource> loadBinaryXmlSkipFileExport(const Source& source, 245 const void* data, size_t len, 246 IDiagnostics* diag) { 247 CompiledFileInputStream inputStream(data, len); 248 if (!inputStream.CompiledFile()) { 249 diag->error(DiagMessage(source) << "invalid compiled file header"); 250 return {}; 251 } 252 253 const uint8_t* xmlData = reinterpret_cast<const uint8_t*>(inputStream.data()); 254 const size_t xmlDataLen = inputStream.size(); 255 256 std::unique_ptr<xml::XmlResource> xmlRes = xml::inflate(xmlData, xmlDataLen, diag, source); 257 if (!xmlRes) { 258 return {}; 259 } 260 return xmlRes; 261} 262 263static std::unique_ptr<ResourceFile> loadFileExportHeader(const Source& source, 264 const void* data, size_t len, 265 IDiagnostics* diag) { 266 CompiledFileInputStream inputStream(data, len); 267 const pb::CompiledFile* pbFile = inputStream.CompiledFile(); 268 if (!pbFile) { 269 diag->error(DiagMessage(source) << "invalid compiled file header"); 270 return {}; 271 } 272 273 std::unique_ptr<ResourceFile> resFile = deserializeCompiledFileFromPb(*pbFile, source, diag); 274 if (!resFile) { 275 return {}; 276 } 277 return resFile; 278} 279 280struct ResourceFileFlattenerOptions { 281 bool noAutoVersion = false; 282 bool noVersionVectors = false; 283 bool keepRawValues = false; 284 bool doNotCompressAnything = false; 285 std::vector<std::string> extensionsToNotCompress; 286}; 287 288class ResourceFileFlattener { 289public: 290 ResourceFileFlattener(const ResourceFileFlattenerOptions& options, 291 IAaptContext* context, proguard::KeepSet* keepSet) : 292 mOptions(options), mContext(context), mKeepSet(keepSet) { 293 } 294 295 bool flatten(ResourceTable* table, IArchiveWriter* archiveWriter); 296 297private: 298 struct FileOperation { 299 io::IFile* fileToCopy; 300 std::unique_ptr<xml::XmlResource> xmlToFlatten; 301 std::string dstPath; 302 bool skipVersion = false; 303 }; 304 305 uint32_t getCompressionFlags(const StringPiece& str); 306 307 bool linkAndVersionXmlFile(const ResourceEntry* entry, const ResourceFile& fileDesc, 308 io::IFile* file, ResourceTable* table, FileOperation* outFileOp); 309 310 ResourceFileFlattenerOptions mOptions; 311 IAaptContext* mContext; 312 proguard::KeepSet* mKeepSet; 313}; 314 315uint32_t ResourceFileFlattener::getCompressionFlags(const StringPiece& str) { 316 if (mOptions.doNotCompressAnything) { 317 return 0; 318 } 319 320 for (const std::string& extension : mOptions.extensionsToNotCompress) { 321 if (util::stringEndsWith<char>(str, extension)) { 322 return 0; 323 } 324 } 325 return ArchiveEntry::kCompress; 326} 327 328bool ResourceFileFlattener::linkAndVersionXmlFile(const ResourceEntry* entry, 329 const ResourceFile& fileDesc, 330 io::IFile* file, 331 ResourceTable* table, 332 FileOperation* outFileOp) { 333 const StringPiece srcPath = file->getSource().path; 334 if (mContext->verbose()) { 335 mContext->getDiagnostics()->note(DiagMessage() << "linking " << srcPath); 336 } 337 338 std::unique_ptr<io::IData> data = file->openAsData(); 339 if (!data) { 340 mContext->getDiagnostics()->error(DiagMessage(file->getSource()) << "failed to open file"); 341 return false; 342 } 343 344 if (util::stringEndsWith<char>(srcPath, ".flat")) { 345 outFileOp->xmlToFlatten = loadBinaryXmlSkipFileExport(file->getSource(), 346 data->data(), data->size(), 347 mContext->getDiagnostics()); 348 } else { 349 outFileOp->xmlToFlatten = xml::inflate(data->data(), data->size(), 350 mContext->getDiagnostics(), 351 file->getSource()); 352 } 353 354 if (!outFileOp->xmlToFlatten) { 355 return false; 356 } 357 358 // Copy the the file description header. 359 outFileOp->xmlToFlatten->file = fileDesc; 360 361 XmlReferenceLinker xmlLinker; 362 if (!xmlLinker.consume(mContext, outFileOp->xmlToFlatten.get())) { 363 return false; 364 } 365 366 if (!proguard::collectProguardRules(outFileOp->xmlToFlatten->file.source, 367 outFileOp->xmlToFlatten.get(), mKeepSet)) { 368 return false; 369 } 370 371 if (!mOptions.noAutoVersion) { 372 if (mOptions.noVersionVectors) { 373 // Skip this if it is a vector or animated-vector. 374 xml::Element* el = xml::findRootElement(outFileOp->xmlToFlatten.get()); 375 if (el && el->namespaceUri.empty()) { 376 if (el->name == u"vector" || el->name == u"animated-vector") { 377 // We are NOT going to version this file. 378 outFileOp->skipVersion = true; 379 return true; 380 } 381 } 382 } 383 384 // Find the first SDK level used that is higher than this defined config and 385 // not superseded by a lower or equal SDK level resource. 386 for (int sdkLevel : xmlLinker.getSdkLevels()) { 387 if (sdkLevel > outFileOp->xmlToFlatten->file.config.sdkVersion) { 388 if (!shouldGenerateVersionedResource(entry, outFileOp->xmlToFlatten->file.config, 389 sdkLevel)) { 390 // If we shouldn't generate a versioned resource, stop checking. 391 break; 392 } 393 394 ResourceFile versionedFileDesc = outFileOp->xmlToFlatten->file; 395 versionedFileDesc.config.sdkVersion = (uint16_t) sdkLevel; 396 397 if (mContext->verbose()) { 398 mContext->getDiagnostics()->note(DiagMessage(versionedFileDesc.source) 399 << "auto-versioning resource from config '" 400 << outFileOp->xmlToFlatten->file.config 401 << "' -> '" 402 << versionedFileDesc.config << "'"); 403 } 404 405 std::u16string genPath = util::utf8ToUtf16(ResourceUtils::buildResourceFileName( 406 versionedFileDesc, mContext->getNameMangler())); 407 408 bool added = table->addFileReferenceAllowMangled(versionedFileDesc.name, 409 versionedFileDesc.config, 410 versionedFileDesc.source, 411 genPath, 412 file, 413 mContext->getDiagnostics()); 414 if (!added) { 415 return false; 416 } 417 break; 418 } 419 } 420 } 421 return true; 422} 423 424/** 425 * Do not insert or remove any resources while executing in this function. It will 426 * corrupt the iteration order. 427 */ 428bool ResourceFileFlattener::flatten(ResourceTable* table, IArchiveWriter* archiveWriter) { 429 bool error = false; 430 std::map<std::pair<ConfigDescription, StringPiece16>, FileOperation> configSortedFiles; 431 432 for (auto& pkg : table->packages) { 433 for (auto& type : pkg->types) { 434 // Sort by config and name, so that we get better locality in the zip file. 435 configSortedFiles.clear(); 436 for (auto& entry : type->entries) { 437 // Iterate via indices because auto generated values can be inserted ahead of 438 // the value being processed. 439 for (size_t i = 0; i < entry->values.size(); i++) { 440 ResourceConfigValue* configValue = entry->values[i].get(); 441 442 FileReference* fileRef = valueCast<FileReference>(configValue->value.get()); 443 if (!fileRef) { 444 continue; 445 } 446 447 io::IFile* file = fileRef->file; 448 if (!file) { 449 mContext->getDiagnostics()->error(DiagMessage(fileRef->getSource()) 450 << "file not found"); 451 return false; 452 } 453 454 FileOperation fileOp; 455 fileOp.dstPath = util::utf16ToUtf8(*fileRef->path); 456 457 const StringPiece srcPath = file->getSource().path; 458 if (type->type != ResourceType::kRaw && 459 (util::stringEndsWith<char>(srcPath, ".xml.flat") || 460 util::stringEndsWith<char>(srcPath, ".xml"))) { 461 ResourceFile fileDesc; 462 fileDesc.config = configValue->config; 463 fileDesc.name = ResourceName(pkg->name, type->type, entry->name); 464 fileDesc.source = fileRef->getSource(); 465 if (!linkAndVersionXmlFile(entry.get(), fileDesc, file, table, &fileOp)) { 466 error = true; 467 continue; 468 } 469 470 } else { 471 fileOp.fileToCopy = file; 472 } 473 474 // NOTE(adamlesinski): Explicitly construct a StringPiece16 here, or else 475 // we end up copying the string in the std::make_pair() method, then creating 476 // a StringPiece16 from the copy, which would cause us to end up referencing 477 // garbage in the map. 478 const StringPiece16 entryName(entry->name); 479 configSortedFiles[std::make_pair(configValue->config, entryName)] = 480 std::move(fileOp); 481 } 482 } 483 484 if (error) { 485 return false; 486 } 487 488 // Now flatten the sorted values. 489 for (auto& mapEntry : configSortedFiles) { 490 const ConfigDescription& config = mapEntry.first.first; 491 const FileOperation& fileOp = mapEntry.second; 492 493 if (fileOp.xmlToFlatten) { 494 Maybe<size_t> maxSdkLevel; 495 if (!mOptions.noAutoVersion && !fileOp.skipVersion) { 496 maxSdkLevel = std::max<size_t>(config.sdkVersion, 1u); 497 } 498 499 bool result = flattenXml(fileOp.xmlToFlatten.get(), fileOp.dstPath, maxSdkLevel, 500 mOptions.keepRawValues, 501 archiveWriter, mContext); 502 if (!result) { 503 error = true; 504 } 505 } else { 506 bool result = copyFileToArchive(fileOp.fileToCopy, fileOp.dstPath, 507 getCompressionFlags(fileOp.dstPath), 508 archiveWriter, mContext); 509 if (!result) { 510 error = true; 511 } 512 } 513 } 514 } 515 } 516 return !error; 517} 518 519class LinkCommand { 520public: 521 LinkCommand(LinkContext* context, const LinkOptions& options) : 522 mOptions(options), mContext(context), mFinalTable(), 523 mFileCollection(util::make_unique<io::FileCollection>()) { 524 } 525 526 /** 527 * Creates a SymbolTable that loads symbols from the various APKs and caches the 528 * results for faster lookup. 529 */ 530 bool loadSymbolsFromIncludePaths() { 531 std::unique_ptr<AssetManagerSymbolSource> assetSource = 532 util::make_unique<AssetManagerSymbolSource>(); 533 for (const std::string& path : mOptions.includePaths) { 534 if (mContext->verbose()) { 535 mContext->getDiagnostics()->note(DiagMessage(path) << "loading include path"); 536 } 537 538 // First try to load the file as a static lib. 539 std::string errorStr; 540 std::unique_ptr<ResourceTable> staticInclude = loadStaticLibrary(path, &errorStr); 541 if (staticInclude) { 542 if (!mOptions.staticLib) { 543 // Can't include static libraries when not building a static library. 544 mContext->getDiagnostics()->error( 545 DiagMessage(path) << "can't include static library when building app"); 546 return false; 547 } 548 549 // If we are using --no-static-lib-packages, we need to rename the package of this 550 // table to our compilation package. 551 if (mOptions.noStaticLibPackages) { 552 if (ResourceTablePackage* pkg = staticInclude->findPackageById(0x7f)) { 553 pkg->name = mContext->getCompilationPackage(); 554 } 555 } 556 557 mContext->getExternalSymbols()->appendSource( 558 util::make_unique<ResourceTableSymbolSource>(staticInclude.get())); 559 560 mStaticTableIncludes.push_back(std::move(staticInclude)); 561 562 } else if (!errorStr.empty()) { 563 // We had an error with reading, so fail. 564 mContext->getDiagnostics()->error(DiagMessage(path) << errorStr); 565 return false; 566 } 567 568 if (!assetSource->addAssetPath(path)) { 569 mContext->getDiagnostics()->error( 570 DiagMessage(path) << "failed to load include path"); 571 return false; 572 } 573 } 574 575 mContext->getExternalSymbols()->appendSource(std::move(assetSource)); 576 return true; 577 } 578 579 Maybe<AppInfo> extractAppInfoFromManifest(xml::XmlResource* xmlRes) { 580 // Make sure the first element is <manifest> with package attribute. 581 if (xml::Element* manifestEl = xml::findRootElement(xmlRes->root.get())) { 582 if (manifestEl->namespaceUri.empty() && manifestEl->name == u"manifest") { 583 if (xml::Attribute* packageAttr = manifestEl->findAttribute({}, u"package")) { 584 return AppInfo{ packageAttr->value }; 585 } 586 } 587 } 588 return {}; 589 } 590 591 /** 592 * Precondition: ResourceTable doesn't have any IDs assigned yet, nor is it linked. 593 * Postcondition: ResourceTable has only one package left. All others are stripped, or there 594 * is an error and false is returned. 595 */ 596 bool verifyNoExternalPackages() { 597 auto isExtPackageFunc = [&](const std::unique_ptr<ResourceTablePackage>& pkg) -> bool { 598 return mContext->getCompilationPackage() != pkg->name || 599 !pkg->id || 600 pkg->id.value() != mContext->getPackageId(); 601 }; 602 603 bool error = false; 604 for (const auto& package : mFinalTable.packages) { 605 if (isExtPackageFunc(package)) { 606 // We have a package that is not related to the one we're building! 607 for (const auto& type : package->types) { 608 for (const auto& entry : type->entries) { 609 ResourceNameRef resName(package->name, type->type, entry->name); 610 611 for (const auto& configValue : entry->values) { 612 // Special case the occurrence of an ID that is being generated for the 613 // 'android' package. This is due to legacy reasons. 614 if (valueCast<Id>(configValue->value.get()) && 615 package->name == u"android") { 616 mContext->getDiagnostics()->warn( 617 DiagMessage(configValue->value->getSource()) 618 << "generated id '" << resName 619 << "' for external package '" << package->name 620 << "'"); 621 } else { 622 mContext->getDiagnostics()->error( 623 DiagMessage(configValue->value->getSource()) 624 << "defined resource '" << resName 625 << "' for external package '" << package->name 626 << "'"); 627 error = true; 628 } 629 } 630 } 631 } 632 } 633 } 634 635 auto newEndIter = std::remove_if(mFinalTable.packages.begin(), mFinalTable.packages.end(), 636 isExtPackageFunc); 637 mFinalTable.packages.erase(newEndIter, mFinalTable.packages.end()); 638 return !error; 639 } 640 641 /** 642 * Returns true if no IDs have been set, false otherwise. 643 */ 644 bool verifyNoIdsSet() { 645 for (const auto& package : mFinalTable.packages) { 646 for (const auto& type : package->types) { 647 if (type->id) { 648 mContext->getDiagnostics()->error(DiagMessage() << "type " << type->type 649 << " has ID " << std::hex 650 << (int) type->id.value() 651 << std::dec << " assigned"); 652 return false; 653 } 654 655 for (const auto& entry : type->entries) { 656 if (entry->id) { 657 ResourceNameRef resName(package->name, type->type, entry->name); 658 mContext->getDiagnostics()->error(DiagMessage() << "entry " << resName 659 << " has ID " << std::hex 660 << (int) entry->id.value() 661 << std::dec << " assigned"); 662 return false; 663 } 664 } 665 } 666 } 667 return true; 668 } 669 670 std::unique_ptr<IArchiveWriter> makeArchiveWriter() { 671 if (mOptions.outputToDirectory) { 672 return createDirectoryArchiveWriter(mContext->getDiagnostics(), mOptions.outputPath); 673 } else { 674 return createZipFileArchiveWriter(mContext->getDiagnostics(), mOptions.outputPath); 675 } 676 } 677 678 bool flattenTable(ResourceTable* table, IArchiveWriter* writer) { 679 BigBuffer buffer(1024); 680 TableFlattener flattener(&buffer); 681 if (!flattener.consume(mContext, table)) { 682 return false; 683 } 684 685 if (writer->startEntry("resources.arsc", ArchiveEntry::kAlign)) { 686 if (writer->writeEntry(buffer)) { 687 if (writer->finishEntry()) { 688 return true; 689 } 690 } 691 } 692 693 mContext->getDiagnostics()->error( 694 DiagMessage() << "failed to write resources.arsc to archive"); 695 return false; 696 } 697 698 bool flattenTableToPb(ResourceTable* table, IArchiveWriter* writer) { 699 // Create the file/zip entry. 700 if (!writer->startEntry("resources.arsc.flat", 0)) { 701 mContext->getDiagnostics()->error(DiagMessage() << "failed to open"); 702 return false; 703 } 704 705 std::unique_ptr<pb::ResourceTable> pbTable = serializeTableToPb(table); 706 707 // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream 708 // interface. 709 { 710 google::protobuf::io::CopyingOutputStreamAdaptor adaptor(writer); 711 712 if (!pbTable->SerializeToZeroCopyStream(&adaptor)) { 713 mContext->getDiagnostics()->error(DiagMessage() << "failed to write"); 714 return false; 715 } 716 } 717 718 if (!writer->finishEntry()) { 719 mContext->getDiagnostics()->error(DiagMessage() << "failed to finish entry"); 720 return false; 721 } 722 return true; 723 } 724 725 bool writeJavaFile(ResourceTable* table, const StringPiece16& packageNameToGenerate, 726 const StringPiece16& outPackage, JavaClassGeneratorOptions javaOptions) { 727 if (!mOptions.generateJavaClassPath) { 728 return true; 729 } 730 731 std::string outPath = mOptions.generateJavaClassPath.value(); 732 file::appendPath(&outPath, file::packageToPath(util::utf16ToUtf8(outPackage))); 733 if (!file::mkdirs(outPath)) { 734 mContext->getDiagnostics()->error( 735 DiagMessage() << "failed to create directory '" << outPath << "'"); 736 return false; 737 } 738 739 file::appendPath(&outPath, "R.java"); 740 741 std::ofstream fout(outPath, std::ofstream::binary); 742 if (!fout) { 743 mContext->getDiagnostics()->error( 744 DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno)); 745 return false; 746 } 747 748 JavaClassGenerator generator(mContext, table, javaOptions); 749 if (!generator.generate(packageNameToGenerate, outPackage, &fout)) { 750 mContext->getDiagnostics()->error(DiagMessage(outPath) << generator.getError()); 751 return false; 752 } 753 754 if (!fout) { 755 mContext->getDiagnostics()->error( 756 DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno)); 757 } 758 return true; 759 } 760 761 bool writeManifestJavaFile(xml::XmlResource* manifestXml) { 762 if (!mOptions.generateJavaClassPath) { 763 return true; 764 } 765 766 std::unique_ptr<ClassDefinition> manifestClass = generateManifestClass( 767 mContext->getDiagnostics(), manifestXml); 768 769 if (!manifestClass) { 770 // Something bad happened, but we already logged it, so exit. 771 return false; 772 } 773 774 if (manifestClass->empty()) { 775 // Empty Manifest class, no need to generate it. 776 return true; 777 } 778 779 // Add any JavaDoc annotations to the generated class. 780 for (const std::string& annotation : mOptions.javadocAnnotations) { 781 std::string properAnnotation = "@"; 782 properAnnotation += annotation; 783 manifestClass->getCommentBuilder()->appendComment(properAnnotation); 784 } 785 786 const std::string packageUtf8 = util::utf16ToUtf8(mContext->getCompilationPackage()); 787 788 std::string outPath = mOptions.generateJavaClassPath.value(); 789 file::appendPath(&outPath, file::packageToPath(packageUtf8)); 790 791 if (!file::mkdirs(outPath)) { 792 mContext->getDiagnostics()->error( 793 DiagMessage() << "failed to create directory '" << outPath << "'"); 794 return false; 795 } 796 797 file::appendPath(&outPath, "Manifest.java"); 798 799 std::ofstream fout(outPath, std::ofstream::binary); 800 if (!fout) { 801 mContext->getDiagnostics()->error( 802 DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno)); 803 return false; 804 } 805 806 if (!ClassDefinition::writeJavaFile(manifestClass.get(), packageUtf8, true, &fout)) { 807 mContext->getDiagnostics()->error( 808 DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno)); 809 return false; 810 } 811 return true; 812 } 813 814 bool writeProguardFile(const proguard::KeepSet& keepSet) { 815 if (!mOptions.generateProguardRulesPath) { 816 return true; 817 } 818 819 const std::string& outPath = mOptions.generateProguardRulesPath.value(); 820 std::ofstream fout(outPath, std::ofstream::binary); 821 if (!fout) { 822 mContext->getDiagnostics()->error( 823 DiagMessage() << "failed to open '" << outPath << "': " << strerror(errno)); 824 return false; 825 } 826 827 proguard::writeKeepSet(&fout, keepSet); 828 if (!fout) { 829 mContext->getDiagnostics()->error( 830 DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno)); 831 return false; 832 } 833 return true; 834 } 835 836 std::unique_ptr<ResourceTable> loadStaticLibrary(const std::string& input, 837 std::string* outError) { 838 std::unique_ptr<io::ZipFileCollection> collection = io::ZipFileCollection::create( 839 input, outError); 840 if (!collection) { 841 return {}; 842 } 843 return loadTablePbFromCollection(collection.get()); 844 } 845 846 std::unique_ptr<ResourceTable> loadTablePbFromCollection(io::IFileCollection* collection) { 847 io::IFile* file = collection->findFile("resources.arsc.flat"); 848 if (!file) { 849 return {}; 850 } 851 852 std::unique_ptr<io::IData> data = file->openAsData(); 853 return loadTableFromPb(file->getSource(), data->data(), data->size(), 854 mContext->getDiagnostics()); 855 } 856 857 bool mergeStaticLibrary(const std::string& input, bool override) { 858 if (mContext->verbose()) { 859 mContext->getDiagnostics()->note(DiagMessage() << "merging static library " << input); 860 } 861 862 std::string errorStr; 863 std::unique_ptr<io::ZipFileCollection> collection = 864 io::ZipFileCollection::create(input, &errorStr); 865 if (!collection) { 866 mContext->getDiagnostics()->error(DiagMessage(input) << errorStr); 867 return false; 868 } 869 870 std::unique_ptr<ResourceTable> table = loadTablePbFromCollection(collection.get()); 871 if (!table) { 872 mContext->getDiagnostics()->error(DiagMessage(input) << "invalid static library"); 873 return false; 874 } 875 876 ResourceTablePackage* pkg = table->findPackageById(0x7f); 877 if (!pkg) { 878 mContext->getDiagnostics()->error(DiagMessage(input) 879 << "static library has no package"); 880 return false; 881 } 882 883 bool result; 884 if (mOptions.noStaticLibPackages) { 885 // Merge all resources as if they were in the compilation package. This is the old 886 // behaviour of aapt. 887 888 // Add the package to the set of --extra-packages so we emit an R.java for each 889 // library package. 890 if (!pkg->name.empty()) { 891 mOptions.extraJavaPackages.insert(pkg->name); 892 } 893 894 pkg->name = u""; 895 if (override) { 896 result = mTableMerger->mergeOverlay(Source(input), table.get(), collection.get()); 897 } else { 898 result = mTableMerger->merge(Source(input), table.get(), collection.get()); 899 } 900 901 } else { 902 // This is the proper way to merge libraries, where the package name is preserved 903 // and resource names are mangled. 904 result = mTableMerger->mergeAndMangle(Source(input), pkg->name, table.get(), 905 collection.get()); 906 } 907 908 if (!result) { 909 return false; 910 } 911 912 // Make sure to move the collection into the set of IFileCollections. 913 mCollections.push_back(std::move(collection)); 914 return true; 915 } 916 917 bool mergeResourceTable(io::IFile* file, bool override) { 918 if (mContext->verbose()) { 919 mContext->getDiagnostics()->note(DiagMessage() << "merging resource table " 920 << file->getSource()); 921 } 922 923 std::unique_ptr<io::IData> data = file->openAsData(); 924 if (!data) { 925 mContext->getDiagnostics()->error(DiagMessage(file->getSource()) 926 << "failed to open file"); 927 return false; 928 } 929 930 std::unique_ptr<ResourceTable> table = loadTableFromPb(file->getSource(), 931 data->data(), data->size(), 932 mContext->getDiagnostics()); 933 if (!table) { 934 return false; 935 } 936 937 bool result = false; 938 if (override) { 939 result = mTableMerger->mergeOverlay(file->getSource(), table.get()); 940 } else { 941 result = mTableMerger->merge(file->getSource(), table.get()); 942 } 943 return result; 944 } 945 946 bool mergeCompiledFile(io::IFile* file, ResourceFile* fileDesc, bool override) { 947 if (mContext->verbose()) { 948 mContext->getDiagnostics()->note(DiagMessage() << "merging compiled file " 949 << file->getSource()); 950 } 951 952 bool result = false; 953 if (override) { 954 result = mTableMerger->mergeFileOverlay(*fileDesc, file); 955 } else { 956 result = mTableMerger->mergeFile(*fileDesc, file); 957 } 958 959 if (!result) { 960 return false; 961 } 962 963 // Add the exports of this file to the table. 964 for (SourcedResourceName& exportedSymbol : fileDesc->exportedSymbols) { 965 if (exportedSymbol.name.package.empty()) { 966 exportedSymbol.name.package = mContext->getCompilationPackage(); 967 } 968 969 ResourceNameRef resName = exportedSymbol.name; 970 971 Maybe<ResourceName> mangledName = mContext->getNameMangler()->mangleName( 972 exportedSymbol.name); 973 if (mangledName) { 974 resName = mangledName.value(); 975 } 976 977 std::unique_ptr<Id> id = util::make_unique<Id>(); 978 id->setSource(fileDesc->source.withLine(exportedSymbol.line)); 979 bool result = mFinalTable.addResourceAllowMangled( 980 resName, ConfigDescription::defaultConfig(), std::string(), std::move(id), 981 mContext->getDiagnostics()); 982 if (!result) { 983 return false; 984 } 985 } 986 return true; 987 } 988 989 /** 990 * Takes a path to load as a ZIP file and merges the files within into the master ResourceTable. 991 * If override is true, conflicting resources are allowed to override each other, in order of 992 * last seen. 993 * 994 * An io::IFileCollection is created from the ZIP file and added to the set of 995 * io::IFileCollections that are open. 996 */ 997 bool mergeArchive(const std::string& input, bool override) { 998 if (mContext->verbose()) { 999 mContext->getDiagnostics()->note(DiagMessage() << "merging archive " << input); 1000 } 1001 1002 std::string errorStr; 1003 std::unique_ptr<io::ZipFileCollection> collection = 1004 io::ZipFileCollection::create(input, &errorStr); 1005 if (!collection) { 1006 mContext->getDiagnostics()->error(DiagMessage(input) << errorStr); 1007 return false; 1008 } 1009 1010 bool error = false; 1011 for (auto iter = collection->iterator(); iter->hasNext(); ) { 1012 if (!mergeFile(iter->next(), override)) { 1013 error = true; 1014 } 1015 } 1016 1017 // Make sure to move the collection into the set of IFileCollections. 1018 mCollections.push_back(std::move(collection)); 1019 return !error; 1020 } 1021 1022 /** 1023 * Takes a path to load and merge into the master ResourceTable. If override is true, 1024 * conflicting resources are allowed to override each other, in order of last seen. 1025 * 1026 * If the file path ends with .flata, .jar, .jack, or .zip the file is treated as ZIP archive 1027 * and the files within are merged individually. 1028 * 1029 * Otherwise the files is processed on its own. 1030 */ 1031 bool mergePath(const std::string& path, bool override) { 1032 if (util::stringEndsWith<char>(path, ".flata") || 1033 util::stringEndsWith<char>(path, ".jar") || 1034 util::stringEndsWith<char>(path, ".jack") || 1035 util::stringEndsWith<char>(path, ".zip")) { 1036 return mergeArchive(path, override); 1037 } else if (util::stringEndsWith<char>(path, ".apk")) { 1038 return mergeStaticLibrary(path, override); 1039 } 1040 1041 io::IFile* file = mFileCollection->insertFile(path); 1042 return mergeFile(file, override); 1043 } 1044 1045 /** 1046 * Takes a file to load and merge into the master ResourceTable. If override is true, 1047 * conflicting resources are allowed to override each other, in order of last seen. 1048 * 1049 * If the file ends with .arsc.flat, then it is loaded as a ResourceTable and merged into the 1050 * master ResourceTable. If the file ends with .flat, then it is treated like a compiled file 1051 * and the header data is read and merged into the final ResourceTable. 1052 * 1053 * All other file types are ignored. This is because these files could be coming from a zip, 1054 * where we could have other files like classes.dex. 1055 */ 1056 bool mergeFile(io::IFile* file, bool override) { 1057 const Source& src = file->getSource(); 1058 if (util::stringEndsWith<char>(src.path, ".arsc.flat")) { 1059 return mergeResourceTable(file, override); 1060 1061 } else if (util::stringEndsWith<char>(src.path, ".flat")){ 1062 // Try opening the file and looking for an Export header. 1063 std::unique_ptr<io::IData> data = file->openAsData(); 1064 if (!data) { 1065 mContext->getDiagnostics()->error(DiagMessage(src) << "failed to open"); 1066 return false; 1067 } 1068 1069 std::unique_ptr<ResourceFile> resourceFile = loadFileExportHeader( 1070 src, data->data(), data->size(), mContext->getDiagnostics()); 1071 if (resourceFile) { 1072 return mergeCompiledFile(file, resourceFile.get(), override); 1073 } 1074 return false; 1075 } 1076 1077 // Ignore non .flat files. This could be classes.dex or something else that happens 1078 // to be in an archive. 1079 return true; 1080 } 1081 1082 int run(const std::vector<std::string>& inputFiles) { 1083 // Load the AndroidManifest.xml 1084 std::unique_ptr<xml::XmlResource> manifestXml = loadXml(mOptions.manifestPath, 1085 mContext->getDiagnostics()); 1086 if (!manifestXml) { 1087 return 1; 1088 } 1089 1090 if (Maybe<AppInfo> maybeAppInfo = extractAppInfoFromManifest(manifestXml.get())) { 1091 mContext->setCompilationPackage(maybeAppInfo.value().package); 1092 } else { 1093 mContext->getDiagnostics()->error(DiagMessage(mOptions.manifestPath) 1094 << "no package specified in <manifest> tag"); 1095 return 1; 1096 } 1097 1098 if (!util::isJavaPackageName(mContext->getCompilationPackage())) { 1099 mContext->getDiagnostics()->error(DiagMessage(mOptions.manifestPath) 1100 << "invalid package name '" 1101 << mContext->getCompilationPackage() 1102 << "'"); 1103 return 1; 1104 } 1105 1106 mContext->setNameManglerPolicy(NameManglerPolicy{ mContext->getCompilationPackage() }); 1107 1108 if (mContext->getCompilationPackage() == u"android") { 1109 mContext->setPackageId(0x01); 1110 } else { 1111 mContext->setPackageId(0x7f); 1112 } 1113 1114 if (!loadSymbolsFromIncludePaths()) { 1115 return 1; 1116 } 1117 1118 TableMergerOptions tableMergerOptions; 1119 tableMergerOptions.autoAddOverlay = mOptions.autoAddOverlay; 1120 mTableMerger = util::make_unique<TableMerger>(mContext, &mFinalTable, tableMergerOptions); 1121 1122 if (mContext->verbose()) { 1123 mContext->getDiagnostics()->note( 1124 DiagMessage() << "linking package '" << mContext->getCompilationPackage() 1125 << "' with package ID " << std::hex 1126 << (int) mContext->getPackageId()); 1127 } 1128 1129 1130 for (const std::string& input : inputFiles) { 1131 if (!mergePath(input, false)) { 1132 mContext->getDiagnostics()->error(DiagMessage() << "failed parsing input"); 1133 return 1; 1134 } 1135 } 1136 1137 for (const std::string& input : mOptions.overlayFiles) { 1138 if (!mergePath(input, true)) { 1139 mContext->getDiagnostics()->error(DiagMessage() << "failed parsing overlays"); 1140 return 1; 1141 } 1142 } 1143 1144 if (!verifyNoExternalPackages()) { 1145 return 1; 1146 } 1147 1148 if (!mOptions.staticLib) { 1149 PrivateAttributeMover mover; 1150 if (!mover.consume(mContext, &mFinalTable)) { 1151 mContext->getDiagnostics()->error( 1152 DiagMessage() << "failed moving private attributes"); 1153 return 1; 1154 } 1155 } 1156 1157 if (!mOptions.staticLib) { 1158 // Assign IDs if we are building a regular app. 1159 IdAssigner idAssigner; 1160 if (!idAssigner.consume(mContext, &mFinalTable)) { 1161 mContext->getDiagnostics()->error(DiagMessage() << "failed assigning IDs"); 1162 return 1; 1163 } 1164 } else { 1165 // Static libs are merged with other apps, and ID collisions are bad, so verify that 1166 // no IDs have been set. 1167 if (!verifyNoIdsSet()) { 1168 return 1; 1169 } 1170 } 1171 1172 // Add the names to mangle based on our source merge earlier. 1173 mContext->setNameManglerPolicy(NameManglerPolicy{ 1174 mContext->getCompilationPackage(), mTableMerger->getMergedPackages() }); 1175 1176 // Add our table to the symbol table. 1177 mContext->getExternalSymbols()->prependSource( 1178 util::make_unique<ResourceTableSymbolSource>(&mFinalTable)); 1179 1180 { 1181 ReferenceLinker linker; 1182 if (!linker.consume(mContext, &mFinalTable)) { 1183 mContext->getDiagnostics()->error(DiagMessage() << "failed linking references"); 1184 return 1; 1185 } 1186 1187 if (mOptions.staticLib) { 1188 if (!mOptions.products.empty()) { 1189 mContext->getDiagnostics()->warn( 1190 DiagMessage() << "can't select products when building static library"); 1191 } 1192 1193 if (mOptions.tableSplitterOptions.configFilter != nullptr || 1194 mOptions.tableSplitterOptions.preferredDensity) { 1195 mContext->getDiagnostics()->warn( 1196 DiagMessage() << "can't strip resources when building static library"); 1197 } 1198 } else { 1199 ProductFilter productFilter(mOptions.products); 1200 if (!productFilter.consume(mContext, &mFinalTable)) { 1201 mContext->getDiagnostics()->error(DiagMessage() << "failed stripping products"); 1202 return 1; 1203 } 1204 1205 // TODO(adamlesinski): Actually pass in split constraints and handle splits at the file 1206 // level. 1207 TableSplitter tableSplitter({}, mOptions.tableSplitterOptions); 1208 if (!tableSplitter.verifySplitConstraints(mContext)) { 1209 return 1; 1210 } 1211 tableSplitter.splitTable(&mFinalTable); 1212 } 1213 } 1214 1215 proguard::KeepSet proguardKeepSet; 1216 1217 std::unique_ptr<IArchiveWriter> archiveWriter = makeArchiveWriter(); 1218 if (!archiveWriter) { 1219 mContext->getDiagnostics()->error(DiagMessage() << "failed to create archive"); 1220 return 1; 1221 } 1222 1223 bool error = false; 1224 { 1225 ManifestFixer manifestFixer(mOptions.manifestFixerOptions); 1226 if (!manifestFixer.consume(mContext, manifestXml.get())) { 1227 error = true; 1228 } 1229 1230 // AndroidManifest.xml has no resource name, but the CallSite is built from the name 1231 // (aka, which package the AndroidManifest.xml is coming from). 1232 // So we give it a package name so it can see local resources. 1233 manifestXml->file.name.package = mContext->getCompilationPackage(); 1234 1235 XmlReferenceLinker manifestLinker; 1236 if (manifestLinker.consume(mContext, manifestXml.get())) { 1237 if (!proguard::collectProguardRulesForManifest(Source(mOptions.manifestPath), 1238 manifestXml.get(), 1239 &proguardKeepSet)) { 1240 error = true; 1241 } 1242 1243 if (mOptions.generateJavaClassPath) { 1244 if (!writeManifestJavaFile(manifestXml.get())) { 1245 error = true; 1246 } 1247 } 1248 1249 const bool keepRawValues = mOptions.staticLib; 1250 bool result = flattenXml(manifestXml.get(), "AndroidManifest.xml", {}, 1251 keepRawValues, archiveWriter.get(), mContext); 1252 if (!result) { 1253 error = true; 1254 } 1255 } else { 1256 error = true; 1257 } 1258 } 1259 1260 if (error) { 1261 mContext->getDiagnostics()->error(DiagMessage() << "failed processing manifest"); 1262 return 1; 1263 } 1264 1265 ResourceFileFlattenerOptions fileFlattenerOptions; 1266 fileFlattenerOptions.keepRawValues = mOptions.staticLib; 1267 fileFlattenerOptions.doNotCompressAnything = mOptions.doNotCompressAnything; 1268 fileFlattenerOptions.extensionsToNotCompress = mOptions.extensionsToNotCompress; 1269 fileFlattenerOptions.noAutoVersion = mOptions.noAutoVersion; 1270 fileFlattenerOptions.noVersionVectors = mOptions.noVersionVectors; 1271 ResourceFileFlattener fileFlattener(fileFlattenerOptions, mContext, &proguardKeepSet); 1272 1273 if (!fileFlattener.flatten(&mFinalTable, archiveWriter.get())) { 1274 mContext->getDiagnostics()->error(DiagMessage() << "failed linking file resources"); 1275 return 1; 1276 } 1277 1278 if (!mOptions.noAutoVersion) { 1279 AutoVersioner versioner; 1280 if (!versioner.consume(mContext, &mFinalTable)) { 1281 mContext->getDiagnostics()->error(DiagMessage() << "failed versioning styles"); 1282 return 1; 1283 } 1284 } 1285 1286 if (mOptions.staticLib) { 1287 if (!flattenTableToPb(&mFinalTable, archiveWriter.get())) { 1288 mContext->getDiagnostics()->error(DiagMessage() 1289 << "failed to write resources.arsc.flat"); 1290 return 1; 1291 } 1292 } else { 1293 if (!flattenTable(&mFinalTable, archiveWriter.get())) { 1294 mContext->getDiagnostics()->error(DiagMessage() 1295 << "failed to write resources.arsc"); 1296 return 1; 1297 } 1298 } 1299 1300 if (mOptions.generateJavaClassPath) { 1301 JavaClassGeneratorOptions options; 1302 options.types = JavaClassGeneratorOptions::SymbolTypes::kAll; 1303 options.javadocAnnotations = mOptions.javadocAnnotations; 1304 1305 if (mOptions.staticLib || mOptions.generateNonFinalIds) { 1306 options.useFinal = false; 1307 } 1308 1309 const StringPiece16 actualPackage = mContext->getCompilationPackage(); 1310 StringPiece16 outputPackage = mContext->getCompilationPackage(); 1311 if (mOptions.customJavaPackage) { 1312 // Override the output java package to the custom one. 1313 outputPackage = mOptions.customJavaPackage.value(); 1314 } 1315 1316 if (mOptions.privateSymbols) { 1317 // If we defined a private symbols package, we only emit Public symbols 1318 // to the original package, and private and public symbols to the private package. 1319 1320 options.types = JavaClassGeneratorOptions::SymbolTypes::kPublic; 1321 if (!writeJavaFile(&mFinalTable, mContext->getCompilationPackage(), 1322 outputPackage, options)) { 1323 return 1; 1324 } 1325 1326 options.types = JavaClassGeneratorOptions::SymbolTypes::kPublicPrivate; 1327 outputPackage = mOptions.privateSymbols.value(); 1328 } 1329 1330 if (!writeJavaFile(&mFinalTable, actualPackage, outputPackage, options)) { 1331 return 1; 1332 } 1333 1334 for (const std::u16string& extraPackage : mOptions.extraJavaPackages) { 1335 if (!writeJavaFile(&mFinalTable, actualPackage, extraPackage, options)) { 1336 return 1; 1337 } 1338 } 1339 } 1340 1341 if (mOptions.generateProguardRulesPath) { 1342 if (!writeProguardFile(proguardKeepSet)) { 1343 return 1; 1344 } 1345 } 1346 1347 if (mContext->verbose()) { 1348 DebugPrintTableOptions debugPrintTableOptions; 1349 debugPrintTableOptions.showSources = true; 1350 Debug::printTable(&mFinalTable, debugPrintTableOptions); 1351 } 1352 return 0; 1353 } 1354 1355private: 1356 LinkOptions mOptions; 1357 LinkContext* mContext; 1358 ResourceTable mFinalTable; 1359 1360 std::unique_ptr<TableMerger> mTableMerger; 1361 1362 // A pointer to the FileCollection representing the filesystem (not archives). 1363 std::unique_ptr<io::FileCollection> mFileCollection; 1364 1365 // A vector of IFileCollections. This is mainly here to keep ownership of the collections. 1366 std::vector<std::unique_ptr<io::IFileCollection>> mCollections; 1367 1368 // A vector of ResourceTables. This is here to retain ownership, so that the SymbolTable 1369 // can use these. 1370 std::vector<std::unique_ptr<ResourceTable>> mStaticTableIncludes; 1371}; 1372 1373int link(const std::vector<StringPiece>& args) { 1374 LinkContext context; 1375 LinkOptions options; 1376 Maybe<std::string> privateSymbolsPackage; 1377 Maybe<std::string> minSdkVersion, targetSdkVersion; 1378 Maybe<std::string> renameManifestPackage, renameInstrumentationTargetPackage; 1379 Maybe<std::string> versionCode, versionName; 1380 Maybe<std::string> customJavaPackage; 1381 std::vector<std::string> extraJavaPackages; 1382 Maybe<std::string> configs; 1383 Maybe<std::string> preferredDensity; 1384 Maybe<std::string> productList; 1385 bool legacyXFlag = false; 1386 bool requireLocalization = false; 1387 bool verbose = false; 1388 Flags flags = Flags() 1389 .requiredFlag("-o", "Output path", &options.outputPath) 1390 .requiredFlag("--manifest", "Path to the Android manifest to build", 1391 &options.manifestPath) 1392 .optionalFlagList("-I", "Adds an Android APK to link against", &options.includePaths) 1393 .optionalFlagList("-R", "Compilation unit to link, using `overlay` semantics.\n" 1394 "The last conflicting resource given takes precedence.", 1395 &options.overlayFiles) 1396 .optionalFlag("--java", "Directory in which to generate R.java", 1397 &options.generateJavaClassPath) 1398 .optionalFlag("--proguard", "Output file for generated Proguard rules", 1399 &options.generateProguardRulesPath) 1400 .optionalSwitch("--no-auto-version", 1401 "Disables automatic style and layout SDK versioning", 1402 &options.noAutoVersion) 1403 .optionalSwitch("--no-version-vectors", 1404 "Disables automatic versioning of vector drawables. Use this only\n" 1405 "when building with vector drawable support library", 1406 &options.noVersionVectors) 1407 .optionalSwitch("-x", "Legacy flag that specifies to use the package identifier 0x01", 1408 &legacyXFlag) 1409 .optionalSwitch("-z", "Require localization of strings marked 'suggested'", 1410 &requireLocalization) 1411 .optionalFlag("-c", "Comma separated list of configurations to include. The default\n" 1412 "is all configurations", &configs) 1413 .optionalFlag("--preferred-density", 1414 "Selects the closest matching density and strips out all others.", 1415 &preferredDensity) 1416 .optionalFlag("--product", "Comma separated list of product names to keep", 1417 &productList) 1418 .optionalSwitch("--output-to-dir", "Outputs the APK contents to a directory specified " 1419 "by -o", 1420 &options.outputToDirectory) 1421 .optionalFlag("--min-sdk-version", "Default minimum SDK version to use for " 1422 "AndroidManifest.xml", &minSdkVersion) 1423 .optionalFlag("--target-sdk-version", "Default target SDK version to use for " 1424 "AndroidManifest.xml", &targetSdkVersion) 1425 .optionalFlag("--version-code", "Version code (integer) to inject into the " 1426 "AndroidManifest.xml if none is present", &versionCode) 1427 .optionalFlag("--version-name", "Version name to inject into the AndroidManifest.xml " 1428 "if none is present", &versionName) 1429 .optionalSwitch("--static-lib", "Generate a static Android library", &options.staticLib) 1430 .optionalSwitch("--no-static-lib-packages", 1431 "Merge all library resources under the app's package", 1432 &options.noStaticLibPackages) 1433 .optionalSwitch("--non-final-ids", "Generates R.java without the final modifier.\n" 1434 "This is implied when --static-lib is specified.", 1435 &options.generateNonFinalIds) 1436 .optionalFlag("--private-symbols", "Package name to use when generating R.java for " 1437 "private symbols.\n" 1438 "If not specified, public and private symbols will use the application's " 1439 "package name", &privateSymbolsPackage) 1440 .optionalFlag("--custom-package", "Custom Java package under which to generate R.java", 1441 &customJavaPackage) 1442 .optionalFlagList("--extra-packages", "Generate the same R.java but with different " 1443 "package names", &extraJavaPackages) 1444 .optionalFlagList("--add-javadoc-annotation", "Adds a JavaDoc annotation to all " 1445 "generated Java classes", &options.javadocAnnotations) 1446 .optionalSwitch("--auto-add-overlay", "Allows the addition of new resources in " 1447 "overlays without <add-resource> tags", &options.autoAddOverlay) 1448 .optionalFlag("--rename-manifest-package", "Renames the package in AndroidManifest.xml", 1449 &renameManifestPackage) 1450 .optionalFlag("--rename-instrumentation-target-package", 1451 "Changes the name of the target package for instrumentation. Most useful " 1452 "when used\nin conjunction with --rename-manifest-package", 1453 &renameInstrumentationTargetPackage) 1454 .optionalFlagList("-0", "File extensions not to compress", 1455 &options.extensionsToNotCompress) 1456 .optionalSwitch("-v", "Enables verbose logging", &verbose); 1457 1458 if (!flags.parse("aapt2 link", args, &std::cerr)) { 1459 return 1; 1460 } 1461 1462 // Expand all argument-files passed into the command line. These start with '@'. 1463 std::vector<std::string> argList; 1464 for (const std::string& arg : flags.getArgs()) { 1465 if (util::stringStartsWith<char>(arg, "@")) { 1466 const std::string path = arg.substr(1, arg.size() - 1); 1467 std::string error; 1468 if (!file::appendArgsFromFile(path, &argList, &error)) { 1469 context.getDiagnostics()->error(DiagMessage(path) << error); 1470 return 1; 1471 } 1472 } else { 1473 argList.push_back(arg); 1474 } 1475 } 1476 1477 if (verbose) { 1478 context.setVerbose(verbose); 1479 } 1480 1481 if (privateSymbolsPackage) { 1482 options.privateSymbols = util::utf8ToUtf16(privateSymbolsPackage.value()); 1483 } 1484 1485 if (minSdkVersion) { 1486 options.manifestFixerOptions.minSdkVersionDefault = 1487 util::utf8ToUtf16(minSdkVersion.value()); 1488 } 1489 1490 if (targetSdkVersion) { 1491 options.manifestFixerOptions.targetSdkVersionDefault = 1492 util::utf8ToUtf16(targetSdkVersion.value()); 1493 } 1494 1495 if (renameManifestPackage) { 1496 options.manifestFixerOptions.renameManifestPackage = 1497 util::utf8ToUtf16(renameManifestPackage.value()); 1498 } 1499 1500 if (renameInstrumentationTargetPackage) { 1501 options.manifestFixerOptions.renameInstrumentationTargetPackage = 1502 util::utf8ToUtf16(renameInstrumentationTargetPackage.value()); 1503 } 1504 1505 if (versionCode) { 1506 options.manifestFixerOptions.versionCodeDefault = util::utf8ToUtf16(versionCode.value()); 1507 } 1508 1509 if (versionName) { 1510 options.manifestFixerOptions.versionNameDefault = util::utf8ToUtf16(versionName.value()); 1511 } 1512 1513 if (customJavaPackage) { 1514 options.customJavaPackage = util::utf8ToUtf16(customJavaPackage.value()); 1515 } 1516 1517 // Populate the set of extra packages for which to generate R.java. 1518 for (std::string& extraPackage : extraJavaPackages) { 1519 // A given package can actually be a colon separated list of packages. 1520 for (StringPiece package : util::split(extraPackage, ':')) { 1521 options.extraJavaPackages.insert(util::utf8ToUtf16(package)); 1522 } 1523 } 1524 1525 if (productList) { 1526 for (StringPiece product : util::tokenize<char>(productList.value(), ',')) { 1527 if (product != "" && product != "default") { 1528 options.products.insert(product.toString()); 1529 } 1530 } 1531 } 1532 1533 AxisConfigFilter filter; 1534 if (configs) { 1535 for (const StringPiece& configStr : util::tokenize<char>(configs.value(), ',')) { 1536 ConfigDescription config; 1537 LocaleValue lv; 1538 if (lv.initFromFilterString(configStr)) { 1539 lv.writeTo(&config); 1540 } else if (!ConfigDescription::parse(configStr, &config)) { 1541 context.getDiagnostics()->error( 1542 DiagMessage() << "invalid config '" << configStr << "' for -c option"); 1543 return 1; 1544 } 1545 1546 if (config.density != 0) { 1547 context.getDiagnostics()->warn( 1548 DiagMessage() << "ignoring density '" << config << "' for -c option"); 1549 } else { 1550 filter.addConfig(config); 1551 } 1552 } 1553 1554 options.tableSplitterOptions.configFilter = &filter; 1555 } 1556 1557 if (preferredDensity) { 1558 ConfigDescription preferredDensityConfig; 1559 if (!ConfigDescription::parse(preferredDensity.value(), &preferredDensityConfig)) { 1560 context.getDiagnostics()->error(DiagMessage() << "invalid density '" 1561 << preferredDensity.value() 1562 << "' for --preferred-density option"); 1563 return 1; 1564 } 1565 1566 // Clear the version that can be automatically added. 1567 preferredDensityConfig.sdkVersion = 0; 1568 1569 if (preferredDensityConfig.diff(ConfigDescription::defaultConfig()) 1570 != ConfigDescription::CONFIG_DENSITY) { 1571 context.getDiagnostics()->error(DiagMessage() << "invalid preferred density '" 1572 << preferredDensity.value() << "'. " 1573 << "Preferred density must only be a density value"); 1574 return 1; 1575 } 1576 options.tableSplitterOptions.preferredDensity = preferredDensityConfig.density; 1577 } 1578 1579 // Turn off auto versioning for static-libs. 1580 if (options.staticLib) { 1581 options.noAutoVersion = true; 1582 options.noVersionVectors = true; 1583 } 1584 1585 LinkCommand cmd(&context, options); 1586 return cmd.run(argList); 1587} 1588 1589} // namespace aapt 1590