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