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