Link.cpp revision 330c57dbd19a064d1f5a4adb3c8ed014711b473f
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::string outPath = mOptions.generateJavaClassPath.value();
766        file::appendPath(&outPath,
767                         file::packageToPath(util::utf16ToUtf8(mContext->getCompilationPackage())));
768        if (!file::mkdirs(outPath)) {
769            mContext->getDiagnostics()->error(
770                    DiagMessage() << "failed to create directory '" << outPath << "'");
771            return false;
772        }
773
774        file::appendPath(&outPath, "Manifest.java");
775
776        std::ofstream fout(outPath, std::ofstream::binary);
777        if (!fout) {
778            mContext->getDiagnostics()->error(
779                    DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno));
780            return false;
781        }
782
783        ManifestClassGenerator generator;
784        if (!generator.generate(mContext->getDiagnostics(), mContext->getCompilationPackage(),
785                                manifestXml, &fout)) {
786            return false;
787        }
788
789        if (!fout) {
790            mContext->getDiagnostics()->error(
791                    DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno));
792            return false;
793        }
794        return true;
795    }
796
797    bool writeProguardFile(const proguard::KeepSet& keepSet) {
798        if (!mOptions.generateProguardRulesPath) {
799            return true;
800        }
801
802        const std::string& outPath = mOptions.generateProguardRulesPath.value();
803        std::ofstream fout(outPath, std::ofstream::binary);
804        if (!fout) {
805            mContext->getDiagnostics()->error(
806                    DiagMessage() << "failed to open '" << outPath << "': " << strerror(errno));
807            return false;
808        }
809
810        proguard::writeKeepSet(&fout, keepSet);
811        if (!fout) {
812            mContext->getDiagnostics()->error(
813                    DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno));
814            return false;
815        }
816        return true;
817    }
818
819    std::unique_ptr<ResourceTable> loadStaticLibrary(const std::string& input,
820                                                     std::string* outError) {
821        std::unique_ptr<io::ZipFileCollection> collection = io::ZipFileCollection::create(
822                input, outError);
823        if (!collection) {
824            return {};
825        }
826        return loadTablePbFromCollection(collection.get());
827    }
828
829    std::unique_ptr<ResourceTable> loadTablePbFromCollection(io::IFileCollection* collection) {
830        io::IFile* file = collection->findFile("resources.arsc.flat");
831        if (!file) {
832            return {};
833        }
834
835        std::unique_ptr<io::IData> data = file->openAsData();
836        return loadTableFromPb(file->getSource(), data->data(), data->size(),
837                               mContext->getDiagnostics());
838    }
839
840    bool mergeStaticLibrary(const std::string& input, bool override) {
841        if (mContext->verbose()) {
842            mContext->getDiagnostics()->note(DiagMessage() << "merging static library " << input);
843        }
844
845        std::string errorStr;
846        std::unique_ptr<io::ZipFileCollection> collection =
847                io::ZipFileCollection::create(input, &errorStr);
848        if (!collection) {
849            mContext->getDiagnostics()->error(DiagMessage(input) << errorStr);
850            return false;
851        }
852
853        std::unique_ptr<ResourceTable> table = loadTablePbFromCollection(collection.get());
854        if (!table) {
855            mContext->getDiagnostics()->error(DiagMessage(input) << "invalid static library");
856            return false;
857        }
858
859        ResourceTablePackage* pkg = table->findPackageById(0x7f);
860        if (!pkg) {
861            mContext->getDiagnostics()->error(DiagMessage(input)
862                                              << "static library has no package");
863            return false;
864        }
865
866        bool result;
867        if (mOptions.noStaticLibPackages) {
868            // Merge all resources as if they were in the compilation package. This is the old
869            // behaviour of aapt.
870
871            // Add the package to the set of --extra-packages so we emit an R.java for each
872            // library package.
873            if (!pkg->name.empty()) {
874                mOptions.extraJavaPackages.insert(pkg->name);
875            }
876
877            pkg->name = u"";
878            if (override) {
879                result = mTableMerger->mergeOverlay(Source(input), table.get(), collection.get());
880            } else {
881                result = mTableMerger->merge(Source(input), table.get(), collection.get());
882            }
883
884        } else {
885            // This is the proper way to merge libraries, where the package name is preserved
886            // and resource names are mangled.
887            result = mTableMerger->mergeAndMangle(Source(input), pkg->name, table.get(),
888                                                  collection.get());
889        }
890
891        if (!result) {
892            return false;
893        }
894
895        // Make sure to move the collection into the set of IFileCollections.
896        mCollections.push_back(std::move(collection));
897        return true;
898    }
899
900    bool mergeResourceTable(io::IFile* file, bool override) {
901        if (mContext->verbose()) {
902            mContext->getDiagnostics()->note(DiagMessage() << "merging resource table "
903                                             << file->getSource());
904        }
905
906        std::unique_ptr<io::IData> data = file->openAsData();
907        if (!data) {
908            mContext->getDiagnostics()->error(DiagMessage(file->getSource())
909                                             << "failed to open file");
910            return false;
911        }
912
913        std::unique_ptr<ResourceTable> table = loadTableFromPb(file->getSource(),
914                                                               data->data(), data->size(),
915                                                               mContext->getDiagnostics());
916        if (!table) {
917            return false;
918        }
919
920        bool result = false;
921        if (override) {
922            result = mTableMerger->mergeOverlay(file->getSource(), table.get());
923        } else {
924            result = mTableMerger->merge(file->getSource(), table.get());
925        }
926        return result;
927    }
928
929    bool mergeCompiledFile(io::IFile* file, ResourceFile* fileDesc, bool override) {
930        if (mContext->verbose()) {
931            mContext->getDiagnostics()->note(DiagMessage() << "merging compiled file "
932                                             << file->getSource());
933        }
934
935        bool result = false;
936        if (override) {
937            result = mTableMerger->mergeFileOverlay(*fileDesc, file);
938        } else {
939            result = mTableMerger->mergeFile(*fileDesc, file);
940        }
941
942        if (!result) {
943            return false;
944        }
945
946        // Add the exports of this file to the table.
947        for (SourcedResourceName& exportedSymbol : fileDesc->exportedSymbols) {
948            if (exportedSymbol.name.package.empty()) {
949                exportedSymbol.name.package = mContext->getCompilationPackage();
950            }
951
952            ResourceNameRef resName = exportedSymbol.name;
953
954            Maybe<ResourceName> mangledName = mContext->getNameMangler()->mangleName(
955                    exportedSymbol.name);
956            if (mangledName) {
957                resName = mangledName.value();
958            }
959
960            std::unique_ptr<Id> id = util::make_unique<Id>();
961            id->setSource(fileDesc->source.withLine(exportedSymbol.line));
962            bool result = mFinalTable.addResourceAllowMangled(
963                    resName, ConfigDescription::defaultConfig(), std::string(), std::move(id),
964                    mContext->getDiagnostics());
965            if (!result) {
966                return false;
967            }
968        }
969        return true;
970    }
971
972    /**
973     * Takes a path to load as a ZIP file and merges the files within into the master ResourceTable.
974     * If override is true, conflicting resources are allowed to override each other, in order of
975     * last seen.
976     *
977     * An io::IFileCollection is created from the ZIP file and added to the set of
978     * io::IFileCollections that are open.
979     */
980    bool mergeArchive(const std::string& input, bool override) {
981        if (mContext->verbose()) {
982            mContext->getDiagnostics()->note(DiagMessage() << "merging archive " << input);
983        }
984
985        std::string errorStr;
986        std::unique_ptr<io::ZipFileCollection> collection =
987                io::ZipFileCollection::create(input, &errorStr);
988        if (!collection) {
989            mContext->getDiagnostics()->error(DiagMessage(input) << errorStr);
990            return false;
991        }
992
993        bool error = false;
994        for (auto iter = collection->iterator(); iter->hasNext(); ) {
995            if (!mergeFile(iter->next(), override)) {
996                error = true;
997            }
998        }
999
1000        // Make sure to move the collection into the set of IFileCollections.
1001        mCollections.push_back(std::move(collection));
1002        return !error;
1003    }
1004
1005    /**
1006     * Takes a path to load and merge into the master ResourceTable. If override is true,
1007     * conflicting resources are allowed to override each other, in order of last seen.
1008     *
1009     * If the file path ends with .flata, .jar, .jack, or .zip the file is treated as ZIP archive
1010     * and the files within are merged individually.
1011     *
1012     * Otherwise the files is processed on its own.
1013     */
1014    bool mergePath(const std::string& path, bool override) {
1015        if (util::stringEndsWith<char>(path, ".flata") ||
1016                util::stringEndsWith<char>(path, ".jar") ||
1017                util::stringEndsWith<char>(path, ".jack") ||
1018                util::stringEndsWith<char>(path, ".zip")) {
1019            return mergeArchive(path, override);
1020        } else if (util::stringEndsWith<char>(path, ".apk")) {
1021            return mergeStaticLibrary(path, override);
1022        }
1023
1024        io::IFile* file = mFileCollection->insertFile(path);
1025        return mergeFile(file, override);
1026    }
1027
1028    /**
1029     * Takes a file to load and merge into the master ResourceTable. If override is true,
1030     * conflicting resources are allowed to override each other, in order of last seen.
1031     *
1032     * If the file ends with .arsc.flat, then it is loaded as a ResourceTable and merged into the
1033     * master ResourceTable. If the file ends with .flat, then it is treated like a compiled file
1034     * and the header data is read and merged into the final ResourceTable.
1035     *
1036     * All other file types are ignored. This is because these files could be coming from a zip,
1037     * where we could have other files like classes.dex.
1038     */
1039    bool mergeFile(io::IFile* file, bool override) {
1040        const Source& src = file->getSource();
1041        if (util::stringEndsWith<char>(src.path, ".arsc.flat")) {
1042            return mergeResourceTable(file, override);
1043
1044        } else if (util::stringEndsWith<char>(src.path, ".flat")){
1045            // Try opening the file and looking for an Export header.
1046            std::unique_ptr<io::IData> data = file->openAsData();
1047            if (!data) {
1048                mContext->getDiagnostics()->error(DiagMessage(src) << "failed to open");
1049                return false;
1050            }
1051
1052            std::unique_ptr<ResourceFile> resourceFile = loadFileExportHeader(
1053                    src, data->data(), data->size(), mContext->getDiagnostics());
1054            if (resourceFile) {
1055                return mergeCompiledFile(file, resourceFile.get(), override);
1056            }
1057            return false;
1058        }
1059
1060        // Ignore non .flat files. This could be classes.dex or something else that happens
1061        // to be in an archive.
1062        return true;
1063    }
1064
1065    int run(const std::vector<std::string>& inputFiles) {
1066        // Load the AndroidManifest.xml
1067        std::unique_ptr<xml::XmlResource> manifestXml = loadXml(mOptions.manifestPath,
1068                                                                mContext->getDiagnostics());
1069        if (!manifestXml) {
1070            return 1;
1071        }
1072
1073        if (Maybe<AppInfo> maybeAppInfo = extractAppInfoFromManifest(manifestXml.get())) {
1074            mContext->setCompilationPackage(maybeAppInfo.value().package);
1075        } else {
1076            mContext->getDiagnostics()->error(DiagMessage(mOptions.manifestPath)
1077                                             << "no package specified in <manifest> tag");
1078            return 1;
1079        }
1080
1081        if (!util::isJavaPackageName(mContext->getCompilationPackage())) {
1082            mContext->getDiagnostics()->error(DiagMessage(mOptions.manifestPath)
1083                                             << "invalid package name '"
1084                                             << mContext->getCompilationPackage()
1085                                             << "'");
1086            return 1;
1087        }
1088
1089        mContext->setNameManglerPolicy(NameManglerPolicy{ mContext->getCompilationPackage() });
1090
1091        if (mContext->getCompilationPackage() == u"android") {
1092            mContext->setPackageId(0x01);
1093        } else {
1094            mContext->setPackageId(0x7f);
1095        }
1096
1097        if (!loadSymbolsFromIncludePaths()) {
1098            return 1;
1099        }
1100
1101        TableMergerOptions tableMergerOptions;
1102        tableMergerOptions.autoAddOverlay = mOptions.autoAddOverlay;
1103        mTableMerger = util::make_unique<TableMerger>(mContext, &mFinalTable, tableMergerOptions);
1104
1105        if (mContext->verbose()) {
1106            mContext->getDiagnostics()->note(
1107                    DiagMessage() << "linking package '" << mContext->getCompilationPackage()
1108                                  << "' with package ID " << std::hex
1109                                  << (int) mContext->getPackageId());
1110        }
1111
1112
1113        for (const std::string& input : inputFiles) {
1114            if (!mergePath(input, false)) {
1115                mContext->getDiagnostics()->error(DiagMessage() << "failed parsing input");
1116                return 1;
1117            }
1118        }
1119
1120        for (const std::string& input : mOptions.overlayFiles) {
1121            if (!mergePath(input, true)) {
1122                mContext->getDiagnostics()->error(DiagMessage() << "failed parsing overlays");
1123                return 1;
1124            }
1125        }
1126
1127        if (!verifyNoExternalPackages()) {
1128            return 1;
1129        }
1130
1131        if (!mOptions.staticLib) {
1132            PrivateAttributeMover mover;
1133            if (!mover.consume(mContext, &mFinalTable)) {
1134                mContext->getDiagnostics()->error(
1135                        DiagMessage() << "failed moving private attributes");
1136                return 1;
1137            }
1138        }
1139
1140        if (!mOptions.staticLib) {
1141            // Assign IDs if we are building a regular app.
1142            IdAssigner idAssigner;
1143            if (!idAssigner.consume(mContext, &mFinalTable)) {
1144                mContext->getDiagnostics()->error(DiagMessage() << "failed assigning IDs");
1145                return 1;
1146            }
1147        } else {
1148            // Static libs are merged with other apps, and ID collisions are bad, so verify that
1149            // no IDs have been set.
1150            if (!verifyNoIdsSet()) {
1151                return 1;
1152            }
1153        }
1154
1155        // Add the names to mangle based on our source merge earlier.
1156        mContext->setNameManglerPolicy(NameManglerPolicy{
1157                mContext->getCompilationPackage(), mTableMerger->getMergedPackages() });
1158
1159        // Add our table to the symbol table.
1160        mContext->getExternalSymbols()->prependSource(
1161                        util::make_unique<ResourceTableSymbolSource>(&mFinalTable));
1162
1163        {
1164            ReferenceLinker linker;
1165            if (!linker.consume(mContext, &mFinalTable)) {
1166                mContext->getDiagnostics()->error(DiagMessage() << "failed linking references");
1167                return 1;
1168            }
1169
1170            if (mOptions.staticLib) {
1171                if (!mOptions.products.empty()) {
1172                    mContext->getDiagnostics()->warn(
1173                            DiagMessage() << "can't select products when building static library");
1174                }
1175
1176                if (mOptions.tableSplitterOptions.configFilter != nullptr ||
1177                        mOptions.tableSplitterOptions.preferredDensity) {
1178                    mContext->getDiagnostics()->warn(
1179                            DiagMessage() << "can't strip resources when building static library");
1180                }
1181            } else {
1182                ProductFilter productFilter(mOptions.products);
1183                if (!productFilter.consume(mContext, &mFinalTable)) {
1184                    mContext->getDiagnostics()->error(DiagMessage() << "failed stripping products");
1185                    return 1;
1186                }
1187
1188                // TODO(adamlesinski): Actually pass in split constraints and handle splits at the file
1189                // level.
1190                TableSplitter tableSplitter({}, mOptions.tableSplitterOptions);
1191                if (!tableSplitter.verifySplitConstraints(mContext)) {
1192                    return 1;
1193                }
1194                tableSplitter.splitTable(&mFinalTable);
1195            }
1196        }
1197
1198        proguard::KeepSet proguardKeepSet;
1199
1200        std::unique_ptr<IArchiveWriter> archiveWriter = makeArchiveWriter();
1201        if (!archiveWriter) {
1202            mContext->getDiagnostics()->error(DiagMessage() << "failed to create archive");
1203            return 1;
1204        }
1205
1206        bool error = false;
1207        {
1208            ManifestFixer manifestFixer(mOptions.manifestFixerOptions);
1209            if (!manifestFixer.consume(mContext, manifestXml.get())) {
1210                error = true;
1211            }
1212
1213            // AndroidManifest.xml has no resource name, but the CallSite is built from the name
1214            // (aka, which package the AndroidManifest.xml is coming from).
1215            // So we give it a package name so it can see local resources.
1216            manifestXml->file.name.package = mContext->getCompilationPackage();
1217
1218            XmlReferenceLinker manifestLinker;
1219            if (manifestLinker.consume(mContext, manifestXml.get())) {
1220                if (!proguard::collectProguardRulesForManifest(Source(mOptions.manifestPath),
1221                                                               manifestXml.get(),
1222                                                               &proguardKeepSet)) {
1223                    error = true;
1224                }
1225
1226                if (mOptions.generateJavaClassPath) {
1227                    if (!writeManifestJavaFile(manifestXml.get())) {
1228                        error = true;
1229                    }
1230                }
1231
1232                const bool keepRawValues = mOptions.staticLib;
1233                bool result = flattenXml(manifestXml.get(), "AndroidManifest.xml", {},
1234                                         keepRawValues, archiveWriter.get(), mContext);
1235                if (!result) {
1236                    error = true;
1237                }
1238            } else {
1239                error = true;
1240            }
1241        }
1242
1243        if (error) {
1244            mContext->getDiagnostics()->error(DiagMessage() << "failed processing manifest");
1245            return 1;
1246        }
1247
1248        ResourceFileFlattenerOptions fileFlattenerOptions;
1249        fileFlattenerOptions.keepRawValues = mOptions.staticLib;
1250        fileFlattenerOptions.doNotCompressAnything = mOptions.doNotCompressAnything;
1251        fileFlattenerOptions.extensionsToNotCompress = mOptions.extensionsToNotCompress;
1252        fileFlattenerOptions.noAutoVersion = mOptions.noAutoVersion;
1253        fileFlattenerOptions.noVersionVectors = mOptions.noVersionVectors;
1254        ResourceFileFlattener fileFlattener(fileFlattenerOptions, mContext, &proguardKeepSet);
1255
1256        if (!fileFlattener.flatten(&mFinalTable, archiveWriter.get())) {
1257            mContext->getDiagnostics()->error(DiagMessage() << "failed linking file resources");
1258            return 1;
1259        }
1260
1261        if (!mOptions.noAutoVersion) {
1262            AutoVersioner versioner;
1263            if (!versioner.consume(mContext, &mFinalTable)) {
1264                mContext->getDiagnostics()->error(DiagMessage() << "failed versioning styles");
1265                return 1;
1266            }
1267        }
1268
1269        if (mOptions.staticLib) {
1270            if (!flattenTableToPb(&mFinalTable, archiveWriter.get())) {
1271                mContext->getDiagnostics()->error(DiagMessage()
1272                                                  << "failed to write resources.arsc.flat");
1273                return 1;
1274            }
1275        } else {
1276            if (!flattenTable(&mFinalTable, archiveWriter.get())) {
1277                mContext->getDiagnostics()->error(DiagMessage()
1278                                                  << "failed to write resources.arsc");
1279                return 1;
1280            }
1281        }
1282
1283        if (mOptions.generateJavaClassPath) {
1284            JavaClassGeneratorOptions options;
1285            options.types = JavaClassGeneratorOptions::SymbolTypes::kAll;
1286
1287            if (mOptions.staticLib || mOptions.generateNonFinalIds) {
1288                options.useFinal = false;
1289            }
1290
1291            const StringPiece16 actualPackage = mContext->getCompilationPackage();
1292            StringPiece16 outputPackage = mContext->getCompilationPackage();
1293            if (mOptions.customJavaPackage) {
1294                // Override the output java package to the custom one.
1295                outputPackage = mOptions.customJavaPackage.value();
1296            }
1297
1298            if (mOptions.privateSymbols) {
1299                // If we defined a private symbols package, we only emit Public symbols
1300                // to the original package, and private and public symbols to the private package.
1301
1302                options.types = JavaClassGeneratorOptions::SymbolTypes::kPublic;
1303                if (!writeJavaFile(&mFinalTable, mContext->getCompilationPackage(),
1304                                   outputPackage, options)) {
1305                    return 1;
1306                }
1307
1308                options.types = JavaClassGeneratorOptions::SymbolTypes::kPublicPrivate;
1309                outputPackage = mOptions.privateSymbols.value();
1310            }
1311
1312            if (!writeJavaFile(&mFinalTable, actualPackage, outputPackage, options)) {
1313                return 1;
1314            }
1315
1316            for (const std::u16string& extraPackage : mOptions.extraJavaPackages) {
1317                if (!writeJavaFile(&mFinalTable, actualPackage, extraPackage, options)) {
1318                    return 1;
1319                }
1320            }
1321        }
1322
1323        if (mOptions.generateProguardRulesPath) {
1324            if (!writeProguardFile(proguardKeepSet)) {
1325                return 1;
1326            }
1327        }
1328
1329        if (mContext->verbose()) {
1330            DebugPrintTableOptions debugPrintTableOptions;
1331            debugPrintTableOptions.showSources = true;
1332            Debug::printTable(&mFinalTable, debugPrintTableOptions);
1333        }
1334        return 0;
1335    }
1336
1337private:
1338    LinkOptions mOptions;
1339    LinkContext* mContext;
1340    ResourceTable mFinalTable;
1341
1342    std::unique_ptr<TableMerger> mTableMerger;
1343
1344    // A pointer to the FileCollection representing the filesystem (not archives).
1345    std::unique_ptr<io::FileCollection> mFileCollection;
1346
1347    // A vector of IFileCollections. This is mainly here to keep ownership of the collections.
1348    std::vector<std::unique_ptr<io::IFileCollection>> mCollections;
1349
1350    // A vector of ResourceTables. This is here to retain ownership, so that the SymbolTable
1351    // can use these.
1352    std::vector<std::unique_ptr<ResourceTable>> mStaticTableIncludes;
1353};
1354
1355int link(const std::vector<StringPiece>& args) {
1356    LinkContext context;
1357    LinkOptions options;
1358    Maybe<std::string> privateSymbolsPackage;
1359    Maybe<std::string> minSdkVersion, targetSdkVersion;
1360    Maybe<std::string> renameManifestPackage, renameInstrumentationTargetPackage;
1361    Maybe<std::string> versionCode, versionName;
1362    Maybe<std::string> customJavaPackage;
1363    std::vector<std::string> extraJavaPackages;
1364    Maybe<std::string> configs;
1365    Maybe<std::string> preferredDensity;
1366    Maybe<std::string> productList;
1367    bool legacyXFlag = false;
1368    bool requireLocalization = false;
1369    bool verbose = false;
1370    Flags flags = Flags()
1371            .requiredFlag("-o", "Output path", &options.outputPath)
1372            .requiredFlag("--manifest", "Path to the Android manifest to build",
1373                          &options.manifestPath)
1374            .optionalFlagList("-I", "Adds an Android APK to link against", &options.includePaths)
1375            .optionalFlagList("-R", "Compilation unit to link, using `overlay` semantics.\n"
1376                              "The last conflicting resource given takes precedence.",
1377                              &options.overlayFiles)
1378            .optionalFlag("--java", "Directory in which to generate R.java",
1379                          &options.generateJavaClassPath)
1380            .optionalFlag("--proguard", "Output file for generated Proguard rules",
1381                          &options.generateProguardRulesPath)
1382            .optionalSwitch("--no-auto-version",
1383                            "Disables automatic style and layout SDK versioning",
1384                            &options.noAutoVersion)
1385            .optionalSwitch("--no-version-vectors",
1386                            "Disables automatic versioning of vector drawables. Use this only\n"
1387                            "when building with vector drawable support library",
1388                            &options.noVersionVectors)
1389            .optionalSwitch("-x", "Legacy flag that specifies to use the package identifier 0x01",
1390                            &legacyXFlag)
1391            .optionalSwitch("-z", "Require localization of strings marked 'suggested'",
1392                            &requireLocalization)
1393            .optionalFlag("-c", "Comma separated list of configurations to include. The default\n"
1394                                "is all configurations", &configs)
1395            .optionalFlag("--preferred-density",
1396                          "Selects the closest matching density and strips out all others.",
1397                          &preferredDensity)
1398            .optionalFlag("--product", "Comma separated list of product names to keep",
1399                          &productList)
1400            .optionalSwitch("--output-to-dir", "Outputs the APK contents to a directory specified "
1401                            "by -o",
1402                            &options.outputToDirectory)
1403            .optionalFlag("--min-sdk-version", "Default minimum SDK version to use for "
1404                          "AndroidManifest.xml", &minSdkVersion)
1405            .optionalFlag("--target-sdk-version", "Default target SDK version to use for "
1406                          "AndroidManifest.xml", &targetSdkVersion)
1407            .optionalFlag("--version-code", "Version code (integer) to inject into the "
1408                          "AndroidManifest.xml if none is present", &versionCode)
1409            .optionalFlag("--version-name", "Version name to inject into the AndroidManifest.xml "
1410                          "if none is present", &versionName)
1411            .optionalSwitch("--static-lib", "Generate a static Android library", &options.staticLib)
1412            .optionalSwitch("--no-static-lib-packages",
1413                            "Merge all library resources under the app's package",
1414                            &options.noStaticLibPackages)
1415            .optionalSwitch("--non-final-ids", "Generates R.java without the final modifier.\n"
1416                            "This is implied when --static-lib is specified.",
1417                            &options.generateNonFinalIds)
1418            .optionalFlag("--private-symbols", "Package name to use when generating R.java for "
1419                          "private symbols.\n"
1420                          "If not specified, public and private symbols will use the application's "
1421                          "package name", &privateSymbolsPackage)
1422            .optionalFlag("--custom-package", "Custom Java package under which to generate R.java",
1423                          &customJavaPackage)
1424            .optionalFlagList("--extra-packages", "Generate the same R.java but with different "
1425                              "package names", &extraJavaPackages)
1426            .optionalSwitch("--auto-add-overlay", "Allows the addition of new resources in "
1427                            "overlays without <add-resource> tags", &options.autoAddOverlay)
1428            .optionalFlag("--rename-manifest-package", "Renames the package in AndroidManifest.xml",
1429                          &renameManifestPackage)
1430            .optionalFlag("--rename-instrumentation-target-package",
1431                          "Changes the name of the target package for instrumentation. Most useful "
1432                          "when used\nin conjunction with --rename-manifest-package",
1433                          &renameInstrumentationTargetPackage)
1434            .optionalFlagList("-0", "File extensions not to compress",
1435                              &options.extensionsToNotCompress)
1436            .optionalSwitch("-v", "Enables verbose logging", &verbose);
1437
1438    if (!flags.parse("aapt2 link", args, &std::cerr)) {
1439        return 1;
1440    }
1441
1442    if (verbose) {
1443        context.setVerbose(verbose);
1444    }
1445
1446    if (privateSymbolsPackage) {
1447        options.privateSymbols = util::utf8ToUtf16(privateSymbolsPackage.value());
1448    }
1449
1450    if (minSdkVersion) {
1451        options.manifestFixerOptions.minSdkVersionDefault =
1452                util::utf8ToUtf16(minSdkVersion.value());
1453    }
1454
1455    if (targetSdkVersion) {
1456        options.manifestFixerOptions.targetSdkVersionDefault =
1457                util::utf8ToUtf16(targetSdkVersion.value());
1458    }
1459
1460    if (renameManifestPackage) {
1461        options.manifestFixerOptions.renameManifestPackage =
1462                util::utf8ToUtf16(renameManifestPackage.value());
1463    }
1464
1465    if (renameInstrumentationTargetPackage) {
1466        options.manifestFixerOptions.renameInstrumentationTargetPackage =
1467                util::utf8ToUtf16(renameInstrumentationTargetPackage.value());
1468    }
1469
1470    if (versionCode) {
1471        options.manifestFixerOptions.versionCodeDefault = util::utf8ToUtf16(versionCode.value());
1472    }
1473
1474    if (versionName) {
1475        options.manifestFixerOptions.versionNameDefault = util::utf8ToUtf16(versionName.value());
1476    }
1477
1478    if (customJavaPackage) {
1479        options.customJavaPackage = util::utf8ToUtf16(customJavaPackage.value());
1480    }
1481
1482    // Populate the set of extra packages for which to generate R.java.
1483    for (std::string& extraPackage : extraJavaPackages) {
1484        // A given package can actually be a colon separated list of packages.
1485        for (StringPiece package : util::split(extraPackage, ':')) {
1486            options.extraJavaPackages.insert(util::utf8ToUtf16(package));
1487        }
1488    }
1489
1490    if (productList) {
1491        for (StringPiece product : util::tokenize<char>(productList.value(), ',')) {
1492            if (product != "" && product != "default") {
1493                options.products.insert(product.toString());
1494            }
1495        }
1496    }
1497
1498    AxisConfigFilter filter;
1499    if (configs) {
1500        for (const StringPiece& configStr : util::tokenize<char>(configs.value(), ',')) {
1501            ConfigDescription config;
1502            LocaleValue lv;
1503            if (lv.initFromFilterString(configStr)) {
1504                lv.writeTo(&config);
1505            } else if (!ConfigDescription::parse(configStr, &config)) {
1506                context.getDiagnostics()->error(
1507                        DiagMessage() << "invalid config '" << configStr << "' for -c option");
1508                return 1;
1509            }
1510
1511            if (config.density != 0) {
1512                context.getDiagnostics()->warn(
1513                        DiagMessage() << "ignoring density '" << config << "' for -c option");
1514            } else {
1515                filter.addConfig(config);
1516            }
1517        }
1518
1519        options.tableSplitterOptions.configFilter = &filter;
1520    }
1521
1522    if (preferredDensity) {
1523        ConfigDescription preferredDensityConfig;
1524        if (!ConfigDescription::parse(preferredDensity.value(), &preferredDensityConfig)) {
1525            context.getDiagnostics()->error(DiagMessage() << "invalid density '"
1526                                            << preferredDensity.value()
1527                                            << "' for --preferred-density option");
1528            return 1;
1529        }
1530
1531        // Clear the version that can be automatically added.
1532        preferredDensityConfig.sdkVersion = 0;
1533
1534        if (preferredDensityConfig.diff(ConfigDescription::defaultConfig())
1535                != ConfigDescription::CONFIG_DENSITY) {
1536            context.getDiagnostics()->error(DiagMessage() << "invalid preferred density '"
1537                                            << preferredDensity.value() << "'. "
1538                                            << "Preferred density must only be a density value");
1539            return 1;
1540        }
1541        options.tableSplitterOptions.preferredDensity = preferredDensityConfig.density;
1542    }
1543
1544    // Turn off auto versioning for static-libs.
1545    if (options.staticLib) {
1546        options.noAutoVersion = true;
1547        options.noVersionVectors = true;
1548    }
1549
1550    LinkCommand cmd(&context, options);
1551    return cmd.run(flags.getArgs());
1552}
1553
1554} // namespace aapt
1555