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