Link.cpp revision 9ba47d813075fcb05c5e1532c137c93b394631cb
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 "JavaClassGenerator.h" 21#include "NameMangler.h" 22#include "ProguardRules.h" 23#include "XmlDom.h" 24 25#include "compile/IdAssigner.h" 26#include "flatten/Archive.h" 27#include "flatten/TableFlattener.h" 28#include "flatten/XmlFlattener.h" 29#include "link/Linkers.h" 30#include "link/TableMerger.h" 31#include "process/IResourceTableConsumer.h" 32#include "process/SymbolTable.h" 33#include "unflatten/BinaryResourceParser.h" 34#include "unflatten/FileExportHeaderReader.h" 35#include "util/Files.h" 36#include "util/StringPiece.h" 37 38#include <fstream> 39#include <sys/stat.h> 40#include <utils/FileMap.h> 41#include <vector> 42 43namespace aapt { 44 45struct LinkOptions { 46 std::string outputPath; 47 std::string manifestPath; 48 std::vector<std::string> includePaths; 49 Maybe<std::string> generateJavaClassPath; 50 Maybe<std::string> generateProguardRulesPath; 51 bool noAutoVersion = false; 52 bool staticLib = false; 53 bool verbose = false; 54 bool outputToDirectory = false; 55 Maybe<std::string> privateSymbols; 56}; 57 58struct LinkContext : public IAaptContext { 59 StdErrDiagnostics mDiagnostics; 60 std::unique_ptr<NameMangler> mNameMangler; 61 std::u16string mCompilationPackage; 62 uint8_t mPackageId; 63 std::unique_ptr<ISymbolTable> mSymbols; 64 65 IDiagnostics* getDiagnostics() override { 66 return &mDiagnostics; 67 } 68 69 NameMangler* getNameMangler() override { 70 return mNameMangler.get(); 71 } 72 73 StringPiece16 getCompilationPackage() override { 74 return mCompilationPackage; 75 } 76 77 uint8_t getPackageId() override { 78 return mPackageId; 79 } 80 81 ISymbolTable* getExternalSymbols() override { 82 return mSymbols.get(); 83 } 84}; 85 86struct LinkCommand { 87 LinkOptions mOptions; 88 LinkContext mContext; 89 90 std::string buildResourceFileName(const ResourceFile& resFile) { 91 std::stringstream out; 92 out << "res/" << resFile.name.type; 93 if (resFile.config != ConfigDescription{}) { 94 out << "-" << resFile.config; 95 } 96 out << "/"; 97 98 if (mContext.getNameMangler()->shouldMangle(resFile.name.package)) { 99 out << NameMangler::mangleEntry(resFile.name.package, resFile.name.entry); 100 } else { 101 out << resFile.name.entry; 102 } 103 out << file::getExtension(resFile.source.path); 104 return out.str(); 105 } 106 107 /** 108 * Creates a SymbolTable that loads symbols from the various APKs and caches the 109 * results for faster lookup. 110 */ 111 std::unique_ptr<ISymbolTable> createSymbolTableFromIncludePaths() { 112 AssetManagerSymbolTableBuilder builder; 113 for (const std::string& path : mOptions.includePaths) { 114 if (mOptions.verbose) { 115 mContext.getDiagnostics()->note( 116 DiagMessage(Source{ path }) << "loading include path"); 117 } 118 119 std::unique_ptr<android::AssetManager> assetManager = 120 util::make_unique<android::AssetManager>(); 121 int32_t cookie = 0; 122 if (!assetManager->addAssetPath(android::String8(path.data(), path.size()), &cookie)) { 123 mContext.getDiagnostics()->error( 124 DiagMessage(Source{ path }) << "failed to load include path"); 125 return {}; 126 } 127 builder.add(std::move(assetManager)); 128 } 129 return builder.build(); 130 } 131 132 /** 133 * Loads the resource table (not inside an apk) at the given path. 134 */ 135 std::unique_ptr<ResourceTable> loadTable(const std::string& input) { 136 std::string errorStr; 137 Maybe<android::FileMap> map = file::mmapPath(input, &errorStr); 138 if (!map) { 139 mContext.getDiagnostics()->error(DiagMessage(Source{ input }) << errorStr); 140 return {}; 141 } 142 143 std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>(); 144 BinaryResourceParser parser(&mContext, table.get(), Source{ input }, 145 map.value().getDataPtr(), map.value().getDataLength()); 146 if (!parser.parse()) { 147 return {}; 148 } 149 return table; 150 } 151 152 /** 153 * Inflates an XML file from the source path. 154 */ 155 std::unique_ptr<XmlResource> loadXml(const std::string& path) { 156 std::ifstream fin(path, std::ifstream::binary); 157 if (!fin) { 158 mContext.getDiagnostics()->error(DiagMessage(Source{ path }) << strerror(errno)); 159 return {}; 160 } 161 162 return xml::inflate(&fin, mContext.getDiagnostics(), Source{ path }); 163 } 164 165 /** 166 * Inflates a binary XML file from the source path. 167 */ 168 std::unique_ptr<XmlResource> loadBinaryXmlSkipFileExport(const std::string& path) { 169 // Read header for symbol info and export info. 170 std::string errorStr; 171 Maybe<android::FileMap> maybeF = file::mmapPath(path, &errorStr); 172 if (!maybeF) { 173 mContext.getDiagnostics()->error(DiagMessage(path) << errorStr); 174 return {}; 175 } 176 177 ssize_t offset = getWrappedDataOffset(maybeF.value().getDataPtr(), 178 maybeF.value().getDataLength(), &errorStr); 179 if (offset < 0) { 180 mContext.getDiagnostics()->error(DiagMessage(path) << errorStr); 181 return {}; 182 } 183 184 std::unique_ptr<XmlResource> xmlRes = xml::inflate( 185 (const uint8_t*) maybeF.value().getDataPtr() + (size_t) offset, 186 maybeF.value().getDataLength() - offset, 187 mContext.getDiagnostics(), Source(path)); 188 if (!xmlRes) { 189 return {}; 190 } 191 return xmlRes; 192 } 193 194 Maybe<ResourceFile> loadFileExportHeader(const std::string& path) { 195 // Read header for symbol info and export info. 196 std::string errorStr; 197 Maybe<android::FileMap> maybeF = file::mmapPath(path, &errorStr); 198 if (!maybeF) { 199 mContext.getDiagnostics()->error(DiagMessage(path) << errorStr); 200 return {}; 201 } 202 203 ResourceFile resFile; 204 ssize_t offset = unwrapFileExportHeader(maybeF.value().getDataPtr(), 205 maybeF.value().getDataLength(), 206 &resFile, &errorStr); 207 if (offset < 0) { 208 mContext.getDiagnostics()->error(DiagMessage(path) << errorStr); 209 return {}; 210 } 211 return std::move(resFile); 212 } 213 214 bool copyFileToArchive(const std::string& path, const std::string& outPath, uint32_t flags, 215 IArchiveWriter* writer) { 216 std::string errorStr; 217 Maybe<android::FileMap> maybeF = file::mmapPath(path, &errorStr); 218 if (!maybeF) { 219 mContext.getDiagnostics()->error(DiagMessage(path) << errorStr); 220 return false; 221 } 222 223 ssize_t offset = getWrappedDataOffset(maybeF.value().getDataPtr(), 224 maybeF.value().getDataLength(), 225 &errorStr); 226 if (offset < 0) { 227 mContext.getDiagnostics()->error(DiagMessage(path) << errorStr); 228 return false; 229 } 230 231 ArchiveEntry* entry = writer->writeEntry(outPath, flags, &maybeF.value(), 232 offset, maybeF.value().getDataLength() - offset); 233 if (!entry) { 234 mContext.getDiagnostics()->error( 235 DiagMessage(mOptions.outputPath) << "failed to write file " << outPath); 236 return false; 237 } 238 return true; 239 } 240 241 Maybe<AppInfo> extractAppInfoFromManifest(XmlResource* xmlRes) { 242 xml::Node* node = xmlRes->root.get(); 243 244 // Find the first xml::Element. 245 while (node && !xml::nodeCast<xml::Element>(node)) { 246 node = !node->children.empty() ? node->children.front().get() : nullptr; 247 } 248 249 // Make sure the first element is <manifest> with package attribute. 250 if (xml::Element* manifestEl = xml::nodeCast<xml::Element>(node)) { 251 if (manifestEl->namespaceUri.empty() && manifestEl->name == u"manifest") { 252 if (xml::Attribute* packageAttr = manifestEl->findAttribute({}, u"package")) { 253 return AppInfo{ packageAttr->value }; 254 } 255 } 256 } 257 return {}; 258 } 259 260 bool verifyNoExternalPackages(ResourceTable* table) { 261 bool error = false; 262 for (const auto& package : table->packages) { 263 if (mContext.getCompilationPackage() != package->name || 264 !package->id || package->id.value() != mContext.getPackageId()) { 265 // We have a package that is not related to the one we're building! 266 for (const auto& type : package->types) { 267 for (const auto& entry : type->entries) { 268 for (const auto& configValue : entry->values) { 269 mContext.getDiagnostics()->error(DiagMessage(configValue.source) 270 << "defined resource '" 271 << ResourceNameRef(package->name, 272 type->type, 273 entry->name) 274 << "' for external package '" 275 << package->name << "'"); 276 error = true; 277 } 278 } 279 } 280 } 281 } 282 return !error; 283 } 284 285 std::unique_ptr<IArchiveWriter> makeArchiveWriter() { 286 if (mOptions.outputToDirectory) { 287 return createDirectoryArchiveWriter(mOptions.outputPath); 288 } else { 289 return createZipFileArchiveWriter(mOptions.outputPath); 290 } 291 } 292 293 bool flattenTable(ResourceTable* table, IArchiveWriter* writer) { 294 BigBuffer buffer(1024); 295 TableFlattenerOptions options = {}; 296 options.useExtendedChunks = mOptions.staticLib; 297 TableFlattener flattener(&buffer, options); 298 if (!flattener.consume(&mContext, table)) { 299 return false; 300 } 301 302 ArchiveEntry* entry = writer->writeEntry("resources.arsc", ArchiveEntry::kAlign, buffer); 303 if (!entry) { 304 mContext.getDiagnostics()->error( 305 DiagMessage() << "failed to write resources.arsc to archive"); 306 return false; 307 } 308 return true; 309 } 310 311 bool flattenXml(XmlResource* xmlRes, const StringPiece& path, Maybe<size_t> maxSdkLevel, 312 IArchiveWriter* writer) { 313 BigBuffer buffer(1024); 314 XmlFlattenerOptions options = {}; 315 options.keepRawValues = mOptions.staticLib; 316 options.maxSdkLevel = maxSdkLevel; 317 XmlFlattener flattener(&buffer, options); 318 if (!flattener.consume(&mContext, xmlRes)) { 319 return false; 320 } 321 322 ArchiveEntry* entry = writer->writeEntry(path, ArchiveEntry::kCompress, buffer); 323 if (!entry) { 324 mContext.getDiagnostics()->error( 325 DiagMessage() << "failed to write " << path << " to archive"); 326 return false; 327 } 328 return true; 329 } 330 331 bool writeJavaFile(ResourceTable* table, const StringPiece16& package) { 332 if (!mOptions.generateJavaClassPath) { 333 return true; 334 } 335 336 std::string outPath = mOptions.generateJavaClassPath.value(); 337 file::appendPath(&outPath, file::packageToPath(util::utf16ToUtf8(package))); 338 file::mkdirs(outPath); 339 file::appendPath(&outPath, "R.java"); 340 341 std::ofstream fout(outPath, std::ofstream::binary); 342 if (!fout) { 343 mContext.getDiagnostics()->error(DiagMessage() << strerror(errno)); 344 return false; 345 } 346 347 JavaClassGeneratorOptions javaOptions; 348 if (mOptions.staticLib) { 349 javaOptions.useFinal = false; 350 } 351 352 JavaClassGenerator generator(table, javaOptions); 353 if (!generator.generate(mContext.getCompilationPackage(), &fout)) { 354 mContext.getDiagnostics()->error(DiagMessage(outPath) << generator.getError()); 355 return false; 356 } 357 return true; 358 } 359 360 bool writeProguardFile(const proguard::KeepSet& keepSet) { 361 if (!mOptions.generateProguardRulesPath) { 362 return true; 363 } 364 365 std::ofstream fout(mOptions.generateProguardRulesPath.value(), std::ofstream::binary); 366 if (!fout) { 367 mContext.getDiagnostics()->error(DiagMessage() << strerror(errno)); 368 return false; 369 } 370 371 proguard::writeKeepSet(&fout, keepSet); 372 if (!fout) { 373 mContext.getDiagnostics()->error(DiagMessage() << strerror(errno)); 374 return false; 375 } 376 return true; 377 } 378 379 int run(const std::vector<std::string>& inputFiles) { 380 // Load the AndroidManifest.xml 381 std::unique_ptr<XmlResource> manifestXml = loadXml(mOptions.manifestPath); 382 if (!manifestXml) { 383 return 1; 384 } 385 386 if (Maybe<AppInfo> maybeAppInfo = extractAppInfoFromManifest(manifestXml.get())) { 387 mContext.mCompilationPackage = maybeAppInfo.value().package; 388 } else { 389 mContext.getDiagnostics()->error(DiagMessage(mOptions.manifestPath) 390 << "no package specified in <manifest> tag"); 391 return 1; 392 } 393 394 if (!util::isJavaPackageName(mContext.mCompilationPackage)) { 395 mContext.getDiagnostics()->error(DiagMessage(mOptions.manifestPath) 396 << "invalid package name '" 397 << mContext.mCompilationPackage 398 << "'"); 399 return 1; 400 } 401 402 mContext.mNameMangler = util::make_unique<NameMangler>( 403 NameManglerPolicy{ mContext.mCompilationPackage }); 404 405 if (mContext.mCompilationPackage == u"android") { 406 mContext.mPackageId = 0x01; 407 } else { 408 mContext.mPackageId = 0x7f; 409 } 410 411 mContext.mSymbols = createSymbolTableFromIncludePaths(); 412 if (!mContext.mSymbols) { 413 return 1; 414 } 415 416 if (mOptions.verbose) { 417 mContext.getDiagnostics()->note( 418 DiagMessage() << "linking package '" << mContext.mCompilationPackage << "' " 419 << "with package ID " << std::hex << (int) mContext.mPackageId); 420 } 421 422 ResourceTable mergedTable; 423 TableMerger tableMerger(&mContext, &mergedTable); 424 425 struct FilesToProcess { 426 Source source; 427 ResourceFile file; 428 }; 429 430 bool error = false; 431 std::queue<FilesToProcess> filesToProcess; 432 for (const std::string& input : inputFiles) { 433 if (util::stringEndsWith<char>(input, ".apk")) { 434 // TODO(adamlesinski): Load resources from a static library APK 435 // Merge the table into TableMerger. 436 437 } else if (util::stringEndsWith<char>(input, ".arsc.flat")) { 438 if (mOptions.verbose) { 439 mContext.getDiagnostics()->note(DiagMessage() << "linking " << input); 440 } 441 442 std::unique_ptr<ResourceTable> table = loadTable(input); 443 if (!table) { 444 return 1; 445 } 446 447 if (!tableMerger.merge(Source(input), table.get())) { 448 return 1; 449 } 450 451 } else { 452 // Extract the exported IDs here so we can build the resource table. 453 if (Maybe<ResourceFile> maybeF = loadFileExportHeader(input)) { 454 ResourceFile& f = maybeF.value(); 455 456 if (f.name.package.empty()) { 457 f.name.package = mContext.getCompilationPackage().toString(); 458 } 459 460 Maybe<ResourceName> mangledName = mContext.getNameMangler()->mangleName(f.name); 461 462 // Add this file to the table. 463 if (!mergedTable.addFileReference(mangledName ? mangledName.value() : f.name, 464 f.config, f.source, 465 util::utf8ToUtf16(buildResourceFileName(f)), 466 mContext.getDiagnostics())) { 467 error = true; 468 } 469 470 // Add the exports of this file to the table. 471 for (SourcedResourceName& exportedSymbol : f.exportedSymbols) { 472 if (exportedSymbol.name.package.empty()) { 473 exportedSymbol.name.package = mContext.getCompilationPackage() 474 .toString(); 475 } 476 477 Maybe<ResourceName> mangledName = mContext.getNameMangler()->mangleName( 478 exportedSymbol.name); 479 if (!mergedTable.addResource( 480 mangledName ? mangledName.value() : exportedSymbol.name, 481 {}, {}, f.source.withLine(exportedSymbol.line), 482 util::make_unique<Id>(), mContext.getDiagnostics())) { 483 error = true; 484 } 485 } 486 487 filesToProcess.push(FilesToProcess{ Source(input), std::move(f) }); 488 } else { 489 return 1; 490 } 491 } 492 } 493 494 if (error) { 495 mContext.getDiagnostics()->error(DiagMessage() << "failed parsing input"); 496 return 1; 497 } 498 499 if (!verifyNoExternalPackages(&mergedTable)) { 500 return 1; 501 } 502 503 if (!mOptions.staticLib) { 504 PrivateAttributeMover mover; 505 if (!mover.consume(&mContext, &mergedTable)) { 506 mContext.getDiagnostics()->error( 507 DiagMessage() << "failed moving private attributes"); 508 return 1; 509 } 510 } 511 512 { 513 IdAssigner idAssigner; 514 if (!idAssigner.consume(&mContext, &mergedTable)) { 515 mContext.getDiagnostics()->error(DiagMessage() << "failed assigning IDs"); 516 return 1; 517 } 518 } 519 520 mContext.mNameMangler = util::make_unique<NameMangler>( 521 NameManglerPolicy{ mContext.mCompilationPackage, tableMerger.getMergedPackages() }); 522 mContext.mSymbols = JoinedSymbolTableBuilder() 523 .addSymbolTable(util::make_unique<SymbolTableWrapper>(&mergedTable)) 524 .addSymbolTable(std::move(mContext.mSymbols)) 525 .build(); 526 527 { 528 ReferenceLinker linker; 529 if (!linker.consume(&mContext, &mergedTable)) { 530 mContext.getDiagnostics()->error(DiagMessage() << "failed linking references"); 531 return 1; 532 } 533 } 534 535 proguard::KeepSet proguardKeepSet; 536 537 std::unique_ptr<IArchiveWriter> archiveWriter = makeArchiveWriter(); 538 if (!archiveWriter) { 539 mContext.getDiagnostics()->error(DiagMessage() << "failed to create archive"); 540 return 1; 541 } 542 543 { 544 XmlReferenceLinker manifestLinker; 545 if (manifestLinker.consume(&mContext, manifestXml.get())) { 546 547 if (!proguard::collectProguardRulesForManifest(Source(mOptions.manifestPath), 548 manifestXml.get(), 549 &proguardKeepSet)) { 550 error = true; 551 } 552 553 if (!flattenXml(manifestXml.get(), "AndroidManifest.xml", {}, 554 archiveWriter.get())) { 555 error = true; 556 } 557 } else { 558 error = true; 559 } 560 } 561 562 for (; !filesToProcess.empty(); filesToProcess.pop()) { 563 FilesToProcess& f = filesToProcess.front(); 564 if (f.file.name.type != ResourceType::kRaw && 565 util::stringEndsWith<char>(f.source.path, ".xml.flat")) { 566 if (mOptions.verbose) { 567 mContext.getDiagnostics()->note(DiagMessage() << "linking " << f.source.path); 568 } 569 570 std::unique_ptr<XmlResource> xmlRes = loadBinaryXmlSkipFileExport(f.source.path); 571 if (!xmlRes) { 572 return 1; 573 } 574 575 xmlRes->file = std::move(f.file); 576 577 XmlReferenceLinker xmlLinker; 578 if (xmlLinker.consume(&mContext, xmlRes.get())) { 579 if (!proguard::collectProguardRules(xmlRes->file.source, xmlRes.get(), 580 &proguardKeepSet)) { 581 error = true; 582 } 583 584 Maybe<size_t> maxSdkLevel; 585 if (!mOptions.noAutoVersion) { 586 maxSdkLevel = std::max<size_t>(xmlRes->file.config.sdkVersion, 1u); 587 } 588 589 if (!flattenXml(xmlRes.get(), buildResourceFileName(xmlRes->file), maxSdkLevel, 590 archiveWriter.get())) { 591 error = true; 592 } 593 594 if (!mOptions.noAutoVersion) { 595 Maybe<ResourceTable::SearchResult> result = mergedTable.findResource( 596 xmlRes->file.name); 597 for (int sdkLevel : xmlLinker.getSdkLevels()) { 598 if (sdkLevel > xmlRes->file.config.sdkVersion && 599 shouldGenerateVersionedResource(result.value().entry, 600 xmlRes->file.config, 601 sdkLevel)) { 602 xmlRes->file.config.sdkVersion = sdkLevel; 603 if (!mergedTable.addFileReference(xmlRes->file.name, 604 xmlRes->file.config, 605 xmlRes->file.source, 606 util::utf8ToUtf16( 607 buildResourceFileName(xmlRes->file)), 608 mContext.getDiagnostics())) { 609 error = true; 610 continue; 611 } 612 613 if (!flattenXml(xmlRes.get(), buildResourceFileName(xmlRes->file), 614 sdkLevel, archiveWriter.get())) { 615 error = true; 616 } 617 } 618 } 619 } 620 621 } else { 622 error = true; 623 } 624 } else { 625 if (mOptions.verbose) { 626 mContext.getDiagnostics()->note(DiagMessage() << "copying " << f.source.path); 627 } 628 629 if (!copyFileToArchive(f.source.path, buildResourceFileName(f.file), 0, 630 archiveWriter.get())) { 631 error = true; 632 } 633 } 634 } 635 636 if (error) { 637 mContext.getDiagnostics()->error(DiagMessage() << "failed linking file resources"); 638 return 1; 639 } 640 641 if (!mOptions.noAutoVersion) { 642 AutoVersioner versioner; 643 if (!versioner.consume(&mContext, &mergedTable)) { 644 mContext.getDiagnostics()->error(DiagMessage() << "failed versioning styles"); 645 return 1; 646 } 647 } 648 649 if (!flattenTable(&mergedTable, archiveWriter.get())) { 650 mContext.getDiagnostics()->error(DiagMessage() << "failed to write resources.arsc"); 651 return 1; 652 } 653 654 if (mOptions.generateJavaClassPath) { 655 if (!writeJavaFile(&mergedTable, mContext.getCompilationPackage())) { 656 return 1; 657 } 658 } 659 660 if (mOptions.generateProguardRulesPath) { 661 if (!writeProguardFile(proguardKeepSet)) { 662 return 1; 663 } 664 } 665 666 if (mOptions.verbose) { 667 Debug::printTable(&mergedTable); 668 for (; !tableMerger.getFileMergeQueue()->empty(); 669 tableMerger.getFileMergeQueue()->pop()) { 670 const FileToMerge& f = tableMerger.getFileMergeQueue()->front(); 671 mContext.getDiagnostics()->note( 672 DiagMessage() << f.srcPath << " -> " << f.dstPath << " from (0x" 673 << std::hex << (uintptr_t) f.srcTable << std::dec); 674 } 675 } 676 677 return 0; 678 } 679}; 680 681int link(const std::vector<StringPiece>& args) { 682 LinkOptions options; 683 Flags flags = Flags() 684 .requiredFlag("-o", "Output path", &options.outputPath) 685 .requiredFlag("--manifest", "Path to the Android manifest to build", 686 &options.manifestPath) 687 .optionalFlagList("-I", "Adds an Android APK to link against", &options.includePaths) 688 .optionalFlag("--java", "Directory in which to generate R.java", 689 &options.generateJavaClassPath) 690 .optionalFlag("--proguard", "Output file for generated Proguard rules", 691 &options.generateProguardRulesPath) 692 .optionalSwitch("--no-auto-version", 693 "Disables automatic style and layout SDK versioning", 694 &options.noAutoVersion) 695 .optionalSwitch("--output-to-dir", "Outputs the APK contents to a directory specified " 696 "by -o", 697 &options.outputToDirectory) 698 .optionalSwitch("--static-lib", "Generate a static Android library", &options.staticLib) 699 .optionalFlag("--private-symbols", "Package name to use when generating R.java for " 700 "private symbols. If not specified, public and private symbols will " 701 "use the application's package name", &options.privateSymbols) 702 .optionalSwitch("-v", "Enables verbose logging", &options.verbose); 703 704 if (!flags.parse("aapt2 link", args, &std::cerr)) { 705 return 1; 706 } 707 708 LinkCommand cmd = { options }; 709 return cmd.run(flags.getArgs()); 710} 711 712} // namespace aapt 713