Link.cpp revision 8900aa8b44f2dae3ee7b0f53422a4dbcb4fd2903
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 "NameMangler.h" 21#include "compile/IdAssigner.h" 22#include "flatten/Archive.h" 23#include "flatten/TableFlattener.h" 24#include "flatten/XmlFlattener.h" 25#include "io/FileSystem.h" 26#include "io/ZipArchive.h" 27#include "java/JavaClassGenerator.h" 28#include "java/ManifestClassGenerator.h" 29#include "java/ProguardRules.h" 30#include "link/Linkers.h" 31#include "link/ReferenceLinker.h" 32#include "link/ManifestFixer.h" 33#include "link/TableMerger.h" 34#include "process/IResourceTableConsumer.h" 35#include "process/SymbolTable.h" 36#include "unflatten/BinaryResourceParser.h" 37#include "unflatten/FileExportHeaderReader.h" 38#include "util/Files.h" 39#include "util/StringPiece.h" 40#include "xml/XmlDom.h" 41 42#include <fstream> 43#include <sys/stat.h> 44#include <vector> 45 46namespace aapt { 47 48struct LinkOptions { 49 std::string outputPath; 50 std::string manifestPath; 51 std::vector<std::string> includePaths; 52 std::vector<std::string> overlayFiles; 53 Maybe<std::string> generateJavaClassPath; 54 Maybe<std::u16string> customJavaPackage; 55 std::set<std::u16string> extraJavaPackages; 56 Maybe<std::string> generateProguardRulesPath; 57 bool noAutoVersion = false; 58 bool staticLib = false; 59 bool generateNonFinalIds = false; 60 bool verbose = false; 61 bool outputToDirectory = false; 62 bool autoAddOverlay = false; 63 bool doNotCompressAnything = false; 64 std::vector<std::string> extensionsToNotCompress; 65 Maybe<std::u16string> privateSymbols; 66 ManifestFixerOptions manifestFixerOptions; 67 68}; 69 70struct LinkContext : public IAaptContext { 71 StdErrDiagnostics mDiagnostics; 72 std::unique_ptr<NameMangler> mNameMangler; 73 std::u16string mCompilationPackage; 74 uint8_t mPackageId; 75 std::unique_ptr<ISymbolTable> mSymbols; 76 77 IDiagnostics* getDiagnostics() override { 78 return &mDiagnostics; 79 } 80 81 NameMangler* getNameMangler() override { 82 return mNameMangler.get(); 83 } 84 85 StringPiece16 getCompilationPackage() override { 86 return mCompilationPackage; 87 } 88 89 uint8_t getPackageId() override { 90 return mPackageId; 91 } 92 93 ISymbolTable* getExternalSymbols() override { 94 return mSymbols.get(); 95 } 96}; 97 98class LinkCommand { 99public: 100 LinkCommand(const LinkOptions& options) : 101 mOptions(options), mContext(), mFinalTable(), mFileCollection(nullptr) { 102 std::unique_ptr<io::FileCollection> fileCollection = 103 util::make_unique<io::FileCollection>(); 104 105 // Get a pointer to the FileCollection for convenience, but it will be owned by the vector. 106 mFileCollection = fileCollection.get(); 107 108 // Move it to the collection. 109 mCollections.push_back(std::move(fileCollection)); 110 } 111 112 /** 113 * Creates a SymbolTable that loads symbols from the various APKs and caches the 114 * results for faster lookup. 115 */ 116 std::unique_ptr<ISymbolTable> createSymbolTableFromIncludePaths() { 117 AssetManagerSymbolTableBuilder builder; 118 for (const std::string& path : mOptions.includePaths) { 119 if (mOptions.verbose) { 120 mContext.getDiagnostics()->note(DiagMessage(path) << "loading include path"); 121 } 122 123 std::unique_ptr<android::AssetManager> assetManager = 124 util::make_unique<android::AssetManager>(); 125 int32_t cookie = 0; 126 if (!assetManager->addAssetPath(android::String8(path.data(), path.size()), &cookie)) { 127 mContext.getDiagnostics()->error( 128 DiagMessage(path) << "failed to load include path"); 129 return {}; 130 } 131 builder.add(std::move(assetManager)); 132 } 133 return builder.build(); 134 } 135 136 std::unique_ptr<ResourceTable> loadTable(const Source& source, const void* data, size_t len) { 137 std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>(); 138 BinaryResourceParser parser(&mContext, table.get(), source, data, len); 139 if (!parser.parse()) { 140 return {}; 141 } 142 return table; 143 } 144 145 /** 146 * Inflates an XML file from the source path. 147 */ 148 static std::unique_ptr<xml::XmlResource> loadXml(const std::string& path, IDiagnostics* diag) { 149 std::ifstream fin(path, std::ifstream::binary); 150 if (!fin) { 151 diag->error(DiagMessage(path) << strerror(errno)); 152 return {}; 153 } 154 155 return xml::inflate(&fin, diag, Source(path)); 156 } 157 158 static std::unique_ptr<xml::XmlResource> loadBinaryXmlSkipFileExport( 159 const Source& source, 160 const void* data, size_t len, 161 IDiagnostics* diag) { 162 std::string errorStr; 163 ssize_t offset = getWrappedDataOffset(data, len, &errorStr); 164 if (offset < 0) { 165 diag->error(DiagMessage(source) << errorStr); 166 return {}; 167 } 168 169 std::unique_ptr<xml::XmlResource> xmlRes = xml::inflate( 170 reinterpret_cast<const uint8_t*>(data) + static_cast<size_t>(offset), 171 len - static_cast<size_t>(offset), 172 diag, 173 source); 174 if (!xmlRes) { 175 return {}; 176 } 177 return xmlRes; 178 } 179 180 static std::unique_ptr<ResourceFile> loadFileExportHeader(const Source& source, 181 const void* data, size_t len, 182 IDiagnostics* diag) { 183 std::unique_ptr<ResourceFile> resFile = util::make_unique<ResourceFile>(); 184 std::string errorStr; 185 ssize_t offset = unwrapFileExportHeader(data, len, resFile.get(), &errorStr); 186 if (offset < 0) { 187 diag->error(DiagMessage(source) << errorStr); 188 return {}; 189 } 190 return resFile; 191 } 192 193 uint32_t getCompressionFlags(const StringPiece& str) { 194 if (mOptions.doNotCompressAnything) { 195 return 0; 196 } 197 198 for (const std::string& extension : mOptions.extensionsToNotCompress) { 199 if (util::stringEndsWith<char>(str, extension)) { 200 return 0; 201 } 202 } 203 return ArchiveEntry::kCompress; 204 } 205 206 bool copyFileToArchive(io::IFile* file, const std::string& outPath, 207 IArchiveWriter* writer) { 208 std::unique_ptr<io::IData> data = file->openAsData(); 209 if (!data) { 210 mContext.getDiagnostics()->error(DiagMessage(file->getSource()) 211 << "failed to open file"); 212 return false; 213 } 214 215 std::string errorStr; 216 ssize_t offset = getWrappedDataOffset(data->data(), data->size(), &errorStr); 217 if (offset < 0) { 218 mContext.getDiagnostics()->error(DiagMessage(file->getSource()) << errorStr); 219 return false; 220 } 221 222 if (writer->startEntry(outPath, getCompressionFlags(outPath))) { 223 if (writer->writeEntry(reinterpret_cast<const uint8_t*>(data->data()) + offset, 224 data->size() - static_cast<size_t>(offset))) { 225 if (writer->finishEntry()) { 226 return true; 227 } 228 } 229 } 230 231 mContext.getDiagnostics()->error( 232 DiagMessage(mOptions.outputPath) << "failed to write file " << outPath); 233 return false; 234 } 235 236 Maybe<AppInfo> extractAppInfoFromManifest(xml::XmlResource* xmlRes) { 237 // Make sure the first element is <manifest> with package attribute. 238 if (xml::Element* manifestEl = xml::findRootElement(xmlRes->root.get())) { 239 if (manifestEl->namespaceUri.empty() && manifestEl->name == u"manifest") { 240 if (xml::Attribute* packageAttr = manifestEl->findAttribute({}, u"package")) { 241 return AppInfo{ packageAttr->value }; 242 } 243 } 244 } 245 return {}; 246 } 247 248 /** 249 * Precondition: ResourceTable doesn't have any IDs assigned yet, nor is it linked. 250 * Postcondition: ResourceTable has only one package left. All others are stripped, or there 251 * is an error and false is returned. 252 */ 253 bool verifyNoExternalPackages() { 254 auto isExtPackageFunc = [&](const std::unique_ptr<ResourceTablePackage>& pkg) -> bool { 255 return mContext.getCompilationPackage() != pkg->name || 256 !pkg->id || 257 pkg->id.value() != mContext.getPackageId(); 258 }; 259 260 bool error = false; 261 for (const auto& package : mFinalTable.packages) { 262 if (isExtPackageFunc(package)) { 263 // We have a package that is not related to the one we're building! 264 for (const auto& type : package->types) { 265 for (const auto& entry : type->entries) { 266 ResourceNameRef resName(package->name, type->type, entry->name); 267 268 for (const auto& configValue : entry->values) { 269 // Special case the occurrence of an ID that is being generated for the 270 // 'android' package. This is due to legacy reasons. 271 if (valueCast<Id>(configValue.value.get()) && 272 package->name == u"android") { 273 mContext.getDiagnostics()->warn( 274 DiagMessage(configValue.value->getSource()) 275 << "generated id '" << resName 276 << "' for external package '" << package->name 277 << "'"); 278 } else { 279 mContext.getDiagnostics()->error( 280 DiagMessage(configValue.value->getSource()) 281 << "defined resource '" << resName 282 << "' for external package '" << package->name 283 << "'"); 284 error = true; 285 } 286 } 287 } 288 } 289 } 290 } 291 292 auto newEndIter = std::remove_if(mFinalTable.packages.begin(), mFinalTable.packages.end(), 293 isExtPackageFunc); 294 mFinalTable.packages.erase(newEndIter, mFinalTable.packages.end()); 295 return !error; 296 } 297 298 std::unique_ptr<IArchiveWriter> makeArchiveWriter() { 299 if (mOptions.outputToDirectory) { 300 return createDirectoryArchiveWriter(mContext.getDiagnostics(), mOptions.outputPath); 301 } else { 302 return createZipFileArchiveWriter(mContext.getDiagnostics(), mOptions.outputPath); 303 } 304 } 305 306 bool flattenTable(ResourceTable* table, IArchiveWriter* writer) { 307 BigBuffer buffer(1024); 308 TableFlattenerOptions options = {}; 309 options.useExtendedChunks = mOptions.staticLib; 310 TableFlattener flattener(&buffer, options); 311 if (!flattener.consume(&mContext, table)) { 312 return false; 313 } 314 315 if (writer->startEntry("resources.arsc", ArchiveEntry::kAlign)) { 316 if (writer->writeEntry(buffer)) { 317 if (writer->finishEntry()) { 318 return true; 319 } 320 } 321 } 322 323 mContext.getDiagnostics()->error( 324 DiagMessage() << "failed to write resources.arsc to archive"); 325 return false; 326 } 327 328 bool flattenXml(xml::XmlResource* xmlRes, const StringPiece& path, Maybe<size_t> maxSdkLevel, 329 IArchiveWriter* writer) { 330 BigBuffer buffer(1024); 331 XmlFlattenerOptions options = {}; 332 options.keepRawValues = mOptions.staticLib; 333 options.maxSdkLevel = maxSdkLevel; 334 XmlFlattener flattener(&buffer, options); 335 if (!flattener.consume(&mContext, xmlRes)) { 336 return false; 337 } 338 339 if (writer->startEntry(path, ArchiveEntry::kCompress)) { 340 if (writer->writeEntry(buffer)) { 341 if (writer->finishEntry()) { 342 return true; 343 } 344 } 345 } 346 mContext.getDiagnostics()->error( 347 DiagMessage() << "failed to write " << path << " to archive"); 348 return false; 349 } 350 351 bool writeJavaFile(ResourceTable* table, const StringPiece16& packageNameToGenerate, 352 const StringPiece16& outPackage, JavaClassGeneratorOptions javaOptions) { 353 if (!mOptions.generateJavaClassPath) { 354 return true; 355 } 356 357 std::string outPath = mOptions.generateJavaClassPath.value(); 358 file::appendPath(&outPath, file::packageToPath(util::utf16ToUtf8(outPackage))); 359 file::mkdirs(outPath); 360 file::appendPath(&outPath, "R.java"); 361 362 std::ofstream fout(outPath, std::ofstream::binary); 363 if (!fout) { 364 mContext.getDiagnostics()->error(DiagMessage() << strerror(errno)); 365 return false; 366 } 367 368 JavaClassGenerator generator(table, javaOptions); 369 if (!generator.generate(packageNameToGenerate, outPackage, &fout)) { 370 mContext.getDiagnostics()->error(DiagMessage(outPath) << generator.getError()); 371 return false; 372 } 373 return true; 374 } 375 376 bool writeManifestJavaFile(xml::XmlResource* manifestXml) { 377 if (!mOptions.generateJavaClassPath) { 378 return true; 379 } 380 381 std::string outPath = mOptions.generateJavaClassPath.value(); 382 file::appendPath(&outPath, 383 file::packageToPath(util::utf16ToUtf8(mContext.getCompilationPackage()))); 384 file::mkdirs(outPath); 385 file::appendPath(&outPath, "Manifest.java"); 386 387 std::ofstream fout(outPath, std::ofstream::binary); 388 if (!fout) { 389 mContext.getDiagnostics()->error(DiagMessage() << strerror(errno)); 390 return false; 391 } 392 393 ManifestClassGenerator generator; 394 if (!generator.generate(mContext.getDiagnostics(), mContext.getCompilationPackage(), 395 manifestXml, &fout)) { 396 return false; 397 } 398 399 if (!fout) { 400 mContext.getDiagnostics()->error(DiagMessage() << strerror(errno)); 401 return false; 402 } 403 return true; 404 } 405 406 bool writeProguardFile(const proguard::KeepSet& keepSet) { 407 if (!mOptions.generateProguardRulesPath) { 408 return true; 409 } 410 411 std::ofstream fout(mOptions.generateProguardRulesPath.value(), std::ofstream::binary); 412 if (!fout) { 413 mContext.getDiagnostics()->error(DiagMessage() << strerror(errno)); 414 return false; 415 } 416 417 proguard::writeKeepSet(&fout, keepSet); 418 if (!fout) { 419 mContext.getDiagnostics()->error(DiagMessage() << strerror(errno)); 420 return false; 421 } 422 return true; 423 } 424 425 bool mergeStaticLibrary(const std::string& input) { 426 // TODO(adamlesinski): Load resources from a static library APK and merge the table into 427 // TableMerger. 428 mContext.getDiagnostics()->warn(DiagMessage() 429 << "linking static libraries not supported yet: " 430 << input); 431 return true; 432 } 433 434 bool mergeResourceTable(io::IFile* file, bool override) { 435 if (mOptions.verbose) { 436 mContext.getDiagnostics()->note(DiagMessage() << "linking " << file->getSource()); 437 } 438 439 std::unique_ptr<io::IData> data = file->openAsData(); 440 if (!data) { 441 mContext.getDiagnostics()->error(DiagMessage(file->getSource()) 442 << "failed to open file"); 443 return false; 444 } 445 446 std::unique_ptr<ResourceTable> table = loadTable(file->getSource(), data->data(), 447 data->size()); 448 if (!table) { 449 return false; 450 } 451 452 bool result = false; 453 if (override) { 454 result = mTableMerger->mergeOverlay(file->getSource(), table.get()); 455 } else { 456 result = mTableMerger->merge(file->getSource(), table.get()); 457 } 458 return result; 459 } 460 461 bool mergeCompiledFile(io::IFile* file, std::unique_ptr<ResourceFile> fileDesc, bool overlay) { 462 if (mOptions.verbose) { 463 mContext.getDiagnostics()->note(DiagMessage() << "adding " << file->getSource()); 464 } 465 466 bool result = false; 467 if (overlay) { 468 result = mTableMerger->mergeFileOverlay(*fileDesc, file); 469 } else { 470 result = mTableMerger->mergeFile(*fileDesc, file); 471 } 472 473 if (!result) { 474 return false; 475 } 476 477 // Add the exports of this file to the table. 478 for (SourcedResourceName& exportedSymbol : fileDesc->exportedSymbols) { 479 if (exportedSymbol.name.package.empty()) { 480 exportedSymbol.name.package = mContext.getCompilationPackage().toString(); 481 } 482 483 ResourceNameRef resName = exportedSymbol.name; 484 485 Maybe<ResourceName> mangledName = mContext.getNameMangler()->mangleName( 486 exportedSymbol.name); 487 if (mangledName) { 488 resName = mangledName.value(); 489 } 490 491 std::unique_ptr<Id> id = util::make_unique<Id>(); 492 id->setSource(fileDesc->source.withLine(exportedSymbol.line)); 493 bool result = mFinalTable.addResourceAllowMangled(resName, {}, std::move(id), 494 mContext.getDiagnostics()); 495 if (!result) { 496 return false; 497 } 498 } 499 return true; 500 } 501 502 /** 503 * Creates an io::IFileCollection from the ZIP archive and processes the files within. 504 */ 505 bool mergeArchive(const std::string& input, bool override) { 506 std::string errorStr; 507 std::unique_ptr<io::ZipFileCollection> collection = io::ZipFileCollection::create( 508 input, &errorStr); 509 if (!collection) { 510 mContext.getDiagnostics()->error(DiagMessage(input) << errorStr); 511 return false; 512 } 513 514 bool error = false; 515 for (auto iter = collection->iterator(); iter->hasNext(); ) { 516 if (!processFile(iter->next(), override)) { 517 error = true; 518 } 519 } 520 521 // Make sure to move the collection into the set of IFileCollections. 522 mCollections.push_back(std::move(collection)); 523 return !error; 524 } 525 526 bool processFile(const std::string& path, bool override) { 527 if (util::stringEndsWith<char>(path, ".flata") || 528 util::stringEndsWith<char>(path, ".jar") || 529 util::stringEndsWith<char>(path, ".jack") || 530 util::stringEndsWith<char>(path, ".zip")) { 531 return mergeArchive(path, override); 532 } 533 534 io::IFile* file = mFileCollection->insertFile(path); 535 return processFile(file, override); 536 } 537 538 bool processFile(io::IFile* file, bool override) { 539 const Source& src = file->getSource(); 540 if (util::stringEndsWith<char>(src.path, ".arsc.flat")) { 541 return mergeResourceTable(file, override); 542 } else if (util::stringEndsWith<char>(src.path, ".flat")){ 543 // Try opening the file and looking for an Export header. 544 std::unique_ptr<io::IData> data = file->openAsData(); 545 if (!data) { 546 mContext.getDiagnostics()->error(DiagMessage(src) << "failed to open"); 547 return false; 548 } 549 550 std::unique_ptr<ResourceFile> resourceFile = loadFileExportHeader( 551 src, data->data(), data->size(), mContext.getDiagnostics()); 552 if (resourceFile) { 553 return mergeCompiledFile(file, std::move(resourceFile), override); 554 } 555 556 return false; 557 } 558 559 // Ignore non .flat files. This could be classes.dex or something else that happens 560 // to be in an archive. 561 return true; 562 } 563 564 int run(const std::vector<std::string>& inputFiles) { 565 // Load the AndroidManifest.xml 566 std::unique_ptr<xml::XmlResource> manifestXml = loadXml(mOptions.manifestPath, 567 mContext.getDiagnostics()); 568 if (!manifestXml) { 569 return 1; 570 } 571 572 if (Maybe<AppInfo> maybeAppInfo = extractAppInfoFromManifest(manifestXml.get())) { 573 mContext.mCompilationPackage = maybeAppInfo.value().package; 574 } else { 575 mContext.getDiagnostics()->error(DiagMessage(mOptions.manifestPath) 576 << "no package specified in <manifest> tag"); 577 return 1; 578 } 579 580 if (!util::isJavaPackageName(mContext.mCompilationPackage)) { 581 mContext.getDiagnostics()->error(DiagMessage(mOptions.manifestPath) 582 << "invalid package name '" 583 << mContext.mCompilationPackage 584 << "'"); 585 return 1; 586 } 587 588 mContext.mNameMangler = util::make_unique<NameMangler>( 589 NameManglerPolicy{ mContext.mCompilationPackage }); 590 591 if (mContext.mCompilationPackage == u"android") { 592 mContext.mPackageId = 0x01; 593 } else { 594 mContext.mPackageId = 0x7f; 595 } 596 597 mContext.mSymbols = createSymbolTableFromIncludePaths(); 598 if (!mContext.mSymbols) { 599 return 1; 600 } 601 602 TableMergerOptions tableMergerOptions; 603 tableMergerOptions.autoAddOverlay = mOptions.autoAddOverlay; 604 mTableMerger = util::make_unique<TableMerger>(&mContext, &mFinalTable, tableMergerOptions); 605 606 if (mOptions.verbose) { 607 mContext.getDiagnostics()->note( 608 DiagMessage() << "linking package '" << mContext.mCompilationPackage << "' " 609 << "with package ID " << std::hex << (int) mContext.mPackageId); 610 } 611 612 613 for (const std::string& input : inputFiles) { 614 if (!processFile(input, false)) { 615 mContext.getDiagnostics()->error(DiagMessage() << "failed parsing input"); 616 return 1; 617 } 618 } 619 620 for (const std::string& input : mOptions.overlayFiles) { 621 if (!processFile(input, true)) { 622 mContext.getDiagnostics()->error(DiagMessage() << "failed parsing overlays"); 623 return 1; 624 } 625 } 626 627 if (!verifyNoExternalPackages()) { 628 return 1; 629 } 630 631 if (!mOptions.staticLib) { 632 PrivateAttributeMover mover; 633 if (!mover.consume(&mContext, &mFinalTable)) { 634 mContext.getDiagnostics()->error( 635 DiagMessage() << "failed moving private attributes"); 636 return 1; 637 } 638 } 639 640 { 641 IdAssigner idAssigner; 642 if (!idAssigner.consume(&mContext, &mFinalTable)) { 643 mContext.getDiagnostics()->error(DiagMessage() << "failed assigning IDs"); 644 return 1; 645 } 646 } 647 648 mContext.mNameMangler = util::make_unique<NameMangler>(NameManglerPolicy{ 649 mContext.mCompilationPackage, mTableMerger->getMergedPackages() }); 650 mContext.mSymbols = JoinedSymbolTableBuilder() 651 .addSymbolTable(util::make_unique<SymbolTableWrapper>(&mFinalTable)) 652 .addSymbolTable(std::move(mContext.mSymbols)) 653 .build(); 654 655 { 656 ReferenceLinker linker; 657 if (!linker.consume(&mContext, &mFinalTable)) { 658 mContext.getDiagnostics()->error(DiagMessage() << "failed linking references"); 659 return 1; 660 } 661 } 662 663 proguard::KeepSet proguardKeepSet; 664 665 std::unique_ptr<IArchiveWriter> archiveWriter = makeArchiveWriter(); 666 if (!archiveWriter) { 667 mContext.getDiagnostics()->error(DiagMessage() << "failed to create archive"); 668 return 1; 669 } 670 671 bool error = false; 672 { 673 ManifestFixer manifestFixer(mOptions.manifestFixerOptions); 674 if (!manifestFixer.consume(&mContext, manifestXml.get())) { 675 error = true; 676 } 677 678 // AndroidManifest.xml has no resource name, but the CallSite is built from the name 679 // (aka, which package the AndroidManifest.xml is coming from). 680 // So we give it a package name so it can see local resources. 681 manifestXml->file.name.package = mContext.getCompilationPackage().toString(); 682 683 XmlReferenceLinker manifestLinker; 684 if (manifestLinker.consume(&mContext, manifestXml.get())) { 685 if (!proguard::collectProguardRulesForManifest(Source(mOptions.manifestPath), 686 manifestXml.get(), 687 &proguardKeepSet)) { 688 error = true; 689 } 690 691 if (mOptions.generateJavaClassPath) { 692 if (!writeManifestJavaFile(manifestXml.get())) { 693 error = true; 694 } 695 } 696 697 if (!flattenXml(manifestXml.get(), "AndroidManifest.xml", {}, 698 archiveWriter.get())) { 699 error = true; 700 } 701 } else { 702 error = true; 703 } 704 } 705 706 if (error) { 707 mContext.getDiagnostics()->error(DiagMessage() << "failed processing manifest"); 708 return 1; 709 } 710 711 for (auto& mergeEntry : mTableMerger->getFilesToMerge()) { 712 const ResourceKeyRef& key = mergeEntry.first; 713 const FileToMerge& fileToMerge = mergeEntry.second; 714 715 const StringPiece path = fileToMerge.file->getSource().path; 716 717 if (key.name.type != ResourceType::kRaw && 718 (util::stringEndsWith<char>(path, ".xml.flat") || 719 util::stringEndsWith<char>(path, ".xml"))) { 720 if (mOptions.verbose) { 721 mContext.getDiagnostics()->note(DiagMessage() << "linking " << path); 722 } 723 724 io::IFile* file = fileToMerge.file; 725 std::unique_ptr<io::IData> data = file->openAsData(); 726 if (!data) { 727 mContext.getDiagnostics()->error(DiagMessage(file->getSource()) 728 << "failed to open file"); 729 return 1; 730 } 731 732 std::unique_ptr<xml::XmlResource> xmlRes; 733 if (util::stringEndsWith<char>(path, ".flat")) { 734 xmlRes = loadBinaryXmlSkipFileExport(file->getSource(), 735 data->data(), data->size(), 736 mContext.getDiagnostics()); 737 } else { 738 xmlRes = xml::inflate(data->data(), data->size(), mContext.getDiagnostics(), 739 file->getSource()); 740 } 741 742 if (!xmlRes) { 743 return 1; 744 } 745 746 // Create the file description header. 747 xmlRes->file = ResourceFile{ 748 key.name.toResourceName(), 749 key.config, 750 fileToMerge.originalSource, 751 }; 752 753 XmlReferenceLinker xmlLinker; 754 if (xmlLinker.consume(&mContext, xmlRes.get())) { 755 if (!proguard::collectProguardRules(xmlRes->file.source, xmlRes.get(), 756 &proguardKeepSet)) { 757 error = true; 758 } 759 760 Maybe<size_t> maxSdkLevel; 761 if (!mOptions.noAutoVersion) { 762 maxSdkLevel = std::max<size_t>(xmlRes->file.config.sdkVersion, 1u); 763 } 764 765 if (!flattenXml(xmlRes.get(), fileToMerge.dstPath, maxSdkLevel, 766 archiveWriter.get())) { 767 error = true; 768 } 769 770 if (!mOptions.noAutoVersion) { 771 Maybe<ResourceTable::SearchResult> result = mFinalTable.findResource( 772 xmlRes->file.name); 773 for (int sdkLevel : xmlLinker.getSdkLevels()) { 774 if (sdkLevel > xmlRes->file.config.sdkVersion && 775 shouldGenerateVersionedResource(result.value().entry, 776 xmlRes->file.config, 777 sdkLevel)) { 778 xmlRes->file.config.sdkVersion = sdkLevel; 779 780 std::string genResourcePath = ResourceUtils::buildResourceFileName( 781 xmlRes->file, mContext.getNameMangler()); 782 783 bool added = mFinalTable.addFileReference( 784 xmlRes->file.name, 785 xmlRes->file.config, 786 xmlRes->file.source, 787 util::utf8ToUtf16(genResourcePath), 788 mContext.getDiagnostics()); 789 if (!added) { 790 error = true; 791 continue; 792 } 793 794 if (!flattenXml(xmlRes.get(), genResourcePath, sdkLevel, 795 archiveWriter.get())) { 796 error = true; 797 } 798 } 799 } 800 } 801 802 } else { 803 error = true; 804 } 805 } else { 806 if (mOptions.verbose) { 807 mContext.getDiagnostics()->note(DiagMessage() << "copying " << path); 808 } 809 810 if (!copyFileToArchive(fileToMerge.file, fileToMerge.dstPath, 811 archiveWriter.get())) { 812 error = true; 813 } 814 } 815 } 816 817 if (error) { 818 mContext.getDiagnostics()->error(DiagMessage() << "failed linking file resources"); 819 return 1; 820 } 821 822 if (!mOptions.noAutoVersion) { 823 AutoVersioner versioner; 824 if (!versioner.consume(&mContext, &mFinalTable)) { 825 mContext.getDiagnostics()->error(DiagMessage() << "failed versioning styles"); 826 return 1; 827 } 828 } 829 830 if (!flattenTable(&mFinalTable, archiveWriter.get())) { 831 mContext.getDiagnostics()->error(DiagMessage() << "failed to write resources.arsc"); 832 return 1; 833 } 834 835 if (mOptions.generateJavaClassPath) { 836 JavaClassGeneratorOptions options; 837 options.types = JavaClassGeneratorOptions::SymbolTypes::kAll; 838 839 if (mOptions.staticLib || mOptions.generateNonFinalIds) { 840 options.useFinal = false; 841 } 842 843 const StringPiece16 actualPackage = mContext.getCompilationPackage(); 844 StringPiece16 outputPackage = mContext.getCompilationPackage(); 845 if (mOptions.customJavaPackage) { 846 // Override the output java package to the custom one. 847 outputPackage = mOptions.customJavaPackage.value(); 848 } 849 850 if (mOptions.privateSymbols) { 851 // If we defined a private symbols package, we only emit Public symbols 852 // to the original package, and private and public symbols to the private package. 853 854 options.types = JavaClassGeneratorOptions::SymbolTypes::kPublic; 855 if (!writeJavaFile(&mFinalTable, mContext.getCompilationPackage(), 856 outputPackage, options)) { 857 return 1; 858 } 859 860 options.types = JavaClassGeneratorOptions::SymbolTypes::kPublicPrivate; 861 outputPackage = mOptions.privateSymbols.value(); 862 } 863 864 if (!writeJavaFile(&mFinalTable, actualPackage, outputPackage, options)) { 865 return 1; 866 } 867 868 for (const std::u16string& extraPackage : mOptions.extraJavaPackages) { 869 if (!writeJavaFile(&mFinalTable, actualPackage, extraPackage, options)) { 870 return 1; 871 } 872 } 873 } 874 875 if (mOptions.generateProguardRulesPath) { 876 if (!writeProguardFile(proguardKeepSet)) { 877 return 1; 878 } 879 } 880 881 if (mOptions.verbose) { 882 Debug::printTable(&mFinalTable); 883 } 884 return 0; 885 } 886 887private: 888 LinkOptions mOptions; 889 LinkContext mContext; 890 ResourceTable mFinalTable; 891 892 ResourceTable mLocalFileTable; 893 std::unique_ptr<TableMerger> mTableMerger; 894 895 // A pointer to the FileCollection representing the filesystem (not archives). 896 io::FileCollection* mFileCollection; 897 898 // A vector of IFileCollections. This is mainly here to keep ownership of the collections. 899 std::vector<std::unique_ptr<io::IFileCollection>> mCollections; 900}; 901 902int link(const std::vector<StringPiece>& args) { 903 LinkOptions options; 904 Maybe<std::string> privateSymbolsPackage; 905 Maybe<std::string> minSdkVersion, targetSdkVersion; 906 Maybe<std::string> renameManifestPackage, renameInstrumentationTargetPackage; 907 Maybe<std::string> versionCode, versionName; 908 Maybe<std::string> customJavaPackage; 909 std::vector<std::string> extraJavaPackages; 910 bool legacyXFlag = false; 911 bool requireLocalization = false; 912 Flags flags = Flags() 913 .requiredFlag("-o", "Output path", &options.outputPath) 914 .requiredFlag("--manifest", "Path to the Android manifest to build", 915 &options.manifestPath) 916 .optionalFlagList("-I", "Adds an Android APK to link against", &options.includePaths) 917 .optionalFlagList("-R", "Compilation unit to link, using `overlay` semantics.\n" 918 "The last conflicting resource given takes precedence.", 919 &options.overlayFiles) 920 .optionalFlag("--java", "Directory in which to generate R.java", 921 &options.generateJavaClassPath) 922 .optionalFlag("--proguard", "Output file for generated Proguard rules", 923 &options.generateProguardRulesPath) 924 .optionalSwitch("--no-auto-version", 925 "Disables automatic style and layout SDK versioning", 926 &options.noAutoVersion) 927 .optionalSwitch("-x", "Legacy flag that specifies to use the package identifier 0x01", 928 &legacyXFlag) 929 .optionalSwitch("-z", "Require localization of strings marked 'suggested'", 930 &requireLocalization) 931 .optionalSwitch("--output-to-dir", "Outputs the APK contents to a directory specified " 932 "by -o", 933 &options.outputToDirectory) 934 .optionalFlag("--min-sdk-version", "Default minimum SDK version to use for " 935 "AndroidManifest.xml", &minSdkVersion) 936 .optionalFlag("--target-sdk-version", "Default target SDK version to use for " 937 "AndroidManifest.xml", &targetSdkVersion) 938 .optionalFlag("--version-code", "Version code (integer) to inject into the " 939 "AndroidManifest.xml if none is present", &versionCode) 940 .optionalFlag("--version-name", "Version name to inject into the AndroidManifest.xml " 941 "if none is present", &versionName) 942 .optionalSwitch("--static-lib", "Generate a static Android library", &options.staticLib) 943 .optionalSwitch("--non-final-ids", "Generates R.java without the final modifier.\n" 944 "This is implied when --static-lib is specified.", 945 &options.generateNonFinalIds) 946 .optionalFlag("--private-symbols", "Package name to use when generating R.java for " 947 "private symbols.\n" 948 "If not specified, public and private symbols will use the application's " 949 "package name", &privateSymbolsPackage) 950 .optionalFlag("--custom-package", "Custom Java package under which to generate R.java", 951 &customJavaPackage) 952 .optionalFlagList("--extra-packages", "Generate the same R.java but with different " 953 "package names", &extraJavaPackages) 954 .optionalSwitch("--auto-add-overlay", "Allows the addition of new resources in " 955 "overlays without <add-resource> tags", &options.autoAddOverlay) 956 .optionalFlag("--rename-manifest-package", "Renames the package in AndroidManifest.xml", 957 &renameManifestPackage) 958 .optionalFlag("--rename-instrumentation-target-package", 959 "Changes the name of the target package for instrumentation. Most useful " 960 "when used\nin conjunction with --rename-manifest-package", 961 &renameInstrumentationTargetPackage) 962 .optionalFlagList("-0", "File extensions not to compress", 963 &options.extensionsToNotCompress) 964 .optionalSwitch("-v", "Enables verbose logging", &options.verbose); 965 966 if (!flags.parse("aapt2 link", args, &std::cerr)) { 967 return 1; 968 } 969 970 if (privateSymbolsPackage) { 971 options.privateSymbols = util::utf8ToUtf16(privateSymbolsPackage.value()); 972 } 973 974 if (minSdkVersion) { 975 options.manifestFixerOptions.minSdkVersionDefault = 976 util::utf8ToUtf16(minSdkVersion.value()); 977 } 978 979 if (targetSdkVersion) { 980 options.manifestFixerOptions.targetSdkVersionDefault = 981 util::utf8ToUtf16(targetSdkVersion.value()); 982 } 983 984 if (renameManifestPackage) { 985 options.manifestFixerOptions.renameManifestPackage = 986 util::utf8ToUtf16(renameManifestPackage.value()); 987 } 988 989 if (renameInstrumentationTargetPackage) { 990 options.manifestFixerOptions.renameInstrumentationTargetPackage = 991 util::utf8ToUtf16(renameInstrumentationTargetPackage.value()); 992 } 993 994 if (versionCode) { 995 options.manifestFixerOptions.versionCodeDefault = util::utf8ToUtf16(versionCode.value()); 996 } 997 998 if (versionName) { 999 options.manifestFixerOptions.versionNameDefault = util::utf8ToUtf16(versionName.value()); 1000 } 1001 1002 if (customJavaPackage) { 1003 options.customJavaPackage = util::utf8ToUtf16(customJavaPackage.value()); 1004 } 1005 1006 // Populate the set of extra packages for which to generate R.java. 1007 for (std::string& extraPackage : extraJavaPackages) { 1008 // A given package can actually be a colon separated list of packages. 1009 for (StringPiece package : util::split(extraPackage, ':')) { 1010 options.extraJavaPackages.insert(util::utf8ToUtf16(package)); 1011 } 1012 } 1013 1014 LinkCommand cmd(options); 1015 return cmd.run(flags.getArgs()); 1016} 1017 1018} // namespace aapt 1019