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