/*
 * Copyright (C) 2015 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "AppInfo.h"
#include "Debug.h"
#include "Flags.h"
#include "Locale.h"
#include "NameMangler.h"
#include "ResourceUtils.h"
#include "compile/IdAssigner.h"
#include "filter/ConfigFilter.h"
#include "flatten/Archive.h"
#include "flatten/TableFlattener.h"
#include "flatten/XmlFlattener.h"
#include "io/FileSystem.h"
#include "io/ZipArchive.h"
#include "java/JavaClassGenerator.h"
#include "java/ManifestClassGenerator.h"
#include "java/ProguardRules.h"
#include "link/Linkers.h"
#include "link/ProductFilter.h"
#include "link/ReferenceLinker.h"
#include "link/ManifestFixer.h"
#include "link/TableMerger.h"
#include "process/IResourceTableConsumer.h"
#include "process/SymbolTable.h"
#include "proto/ProtoSerialize.h"
#include "split/TableSplitter.h"
#include "unflatten/BinaryResourceParser.h"
#include "util/Files.h"
#include "util/StringPiece.h"
#include "xml/XmlDom.h"

#include <google/protobuf/io/coded_stream.h>

#include <fstream>
#include <sys/stat.h>
#include <vector>

namespace aapt {

struct LinkOptions {
    std::string outputPath;
    std::string manifestPath;
    std::vector<std::string> includePaths;
    std::vector<std::string> overlayFiles;
    Maybe<std::string> generateJavaClassPath;
    Maybe<std::u16string> customJavaPackage;
    std::set<std::u16string> extraJavaPackages;
    Maybe<std::string> generateProguardRulesPath;
    bool noAutoVersion = false;
    bool noVersionVectors = false;
    bool staticLib = false;
    bool noStaticLibPackages = false;
    bool generateNonFinalIds = false;
    std::vector<std::string> javadocAnnotations;
    bool outputToDirectory = false;
    bool autoAddOverlay = false;
    bool doNotCompressAnything = false;
    std::vector<std::string> extensionsToNotCompress;
    Maybe<std::u16string> privateSymbols;
    ManifestFixerOptions manifestFixerOptions;
    std::unordered_set<std::string> products;
    TableSplitterOptions tableSplitterOptions;
};

class LinkContext : public IAaptContext {
public:
    LinkContext() : mNameMangler({}) {
    }

    IDiagnostics* getDiagnostics() override {
        return &mDiagnostics;
    }

    NameMangler* getNameMangler() override {
        return &mNameMangler;
    }

    void setNameManglerPolicy(const NameManglerPolicy& policy) {
        mNameMangler = NameMangler(policy);
    }

    const std::u16string& getCompilationPackage() override {
        return mCompilationPackage;
    }

    void setCompilationPackage(const StringPiece16& packageName) {
        mCompilationPackage = packageName.toString();
    }

    uint8_t getPackageId() override {
        return mPackageId;
    }

    void setPackageId(uint8_t id) {
        mPackageId = id;
    }

    SymbolTable* getExternalSymbols() override {
        return &mSymbols;
    }

    bool verbose() override {
        return mVerbose;
    }

    void setVerbose(bool val) {
        mVerbose = val;
    }

private:
    StdErrDiagnostics mDiagnostics;
    NameMangler mNameMangler;
    std::u16string mCompilationPackage;
    uint8_t mPackageId = 0x0;
    SymbolTable mSymbols;
    bool mVerbose = false;
};

static bool copyFileToArchive(io::IFile* file, const std::string& outPath,
                              uint32_t compressionFlags,
                              IArchiveWriter* writer, IAaptContext* context) {
    std::unique_ptr<io::IData> data = file->openAsData();
    if (!data) {
        context->getDiagnostics()->error(DiagMessage(file->getSource())
                                         << "failed to open file");
        return false;
    }

    const uint8_t* buffer = reinterpret_cast<const uint8_t*>(data->data());
    size_t bufferSize = data->size();

    // If the file ends with .flat, we must strip off the CompiledFileHeader from it.
    if (util::stringEndsWith<char>(file->getSource().path, ".flat")) {
        CompiledFileInputStream inputStream(data->data(), data->size());
        if (!inputStream.CompiledFile()) {
            context->getDiagnostics()->error(DiagMessage(file->getSource())
                                             << "invalid compiled file header");
            return false;
        }
        buffer = reinterpret_cast<const uint8_t*>(inputStream.data());
        bufferSize = inputStream.size();
    }

    if (context->verbose()) {
        context->getDiagnostics()->note(DiagMessage() << "writing " << outPath << " to archive");
    }

    if (writer->startEntry(outPath, compressionFlags)) {
        if (writer->writeEntry(buffer, bufferSize)) {
            if (writer->finishEntry()) {
                return true;
            }
        }
    }

    context->getDiagnostics()->error(DiagMessage() << "failed to write file " << outPath);
    return false;
}

static bool flattenXml(xml::XmlResource* xmlRes, const StringPiece& path, Maybe<size_t> maxSdkLevel,
                       bool keepRawValues, IArchiveWriter* writer, IAaptContext* context) {
    BigBuffer buffer(1024);
    XmlFlattenerOptions options = {};
    options.keepRawValues = keepRawValues;
    options.maxSdkLevel = maxSdkLevel;
    XmlFlattener flattener(&buffer, options);
    if (!flattener.consume(context, xmlRes)) {
        return false;
    }

    if (context->verbose()) {
        DiagMessage msg;
        msg << "writing " << path << " to archive";
        if (maxSdkLevel) {
            msg << " maxSdkLevel=" << maxSdkLevel.value() << " keepRawValues=" << keepRawValues;
        }
        context->getDiagnostics()->note(msg);
    }

    if (writer->startEntry(path, ArchiveEntry::kCompress)) {
        if (writer->writeEntry(buffer)) {
            if (writer->finishEntry()) {
                return true;
            }
        }
    }
    context->getDiagnostics()->error(DiagMessage() << "failed to write " << path << " to archive");
    return false;
}

/*static std::unique_ptr<ResourceTable> loadTable(const Source& source, const void* data, size_t len,
                                                IDiagnostics* diag) {
    std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>();
    BinaryResourceParser parser(diag, table.get(), source, data, len);
    if (!parser.parse()) {
        return {};
    }
    return table;
}*/

static std::unique_ptr<ResourceTable> loadTableFromPb(const Source& source,
                                                      const void* data, size_t len,
                                                      IDiagnostics* diag) {
    pb::ResourceTable pbTable;
    if (!pbTable.ParseFromArray(data, len)) {
        diag->error(DiagMessage(source) << "invalid compiled table");
        return {};
    }

    std::unique_ptr<ResourceTable> table = deserializeTableFromPb(pbTable, source, diag);
    if (!table) {
        return {};
    }
    return table;
}

/**
 * Inflates an XML file from the source path.
 */
static std::unique_ptr<xml::XmlResource> loadXml(const std::string& path, IDiagnostics* diag) {
    std::ifstream fin(path, std::ifstream::binary);
    if (!fin) {
        diag->error(DiagMessage(path) << strerror(errno));
        return {};
    }
    return xml::inflate(&fin, diag, Source(path));
}

static std::unique_ptr<xml::XmlResource> loadBinaryXmlSkipFileExport(const Source& source,
                                                                     const void* data, size_t len,
                                                                     IDiagnostics* diag) {
    CompiledFileInputStream inputStream(data, len);
    if (!inputStream.CompiledFile()) {
        diag->error(DiagMessage(source) << "invalid compiled file header");
        return {};
    }

    const uint8_t* xmlData = reinterpret_cast<const uint8_t*>(inputStream.data());
    const size_t xmlDataLen = inputStream.size();

    std::unique_ptr<xml::XmlResource> xmlRes = xml::inflate(xmlData, xmlDataLen, diag, source);
    if (!xmlRes) {
        return {};
    }
    return xmlRes;
}

static std::unique_ptr<ResourceFile> loadFileExportHeader(const Source& source,
                                                          const void* data, size_t len,
                                                          IDiagnostics* diag) {
    CompiledFileInputStream inputStream(data, len);
    const pb::CompiledFile* pbFile = inputStream.CompiledFile();
    if (!pbFile) {
        diag->error(DiagMessage(source) << "invalid compiled file header");
        return {};
    }

    std::unique_ptr<ResourceFile> resFile = deserializeCompiledFileFromPb(*pbFile, source, diag);
    if (!resFile) {
        return {};
    }
    return resFile;
}

struct ResourceFileFlattenerOptions {
    bool noAutoVersion = false;
    bool noVersionVectors = false;
    bool keepRawValues = false;
    bool doNotCompressAnything = false;
    std::vector<std::string> extensionsToNotCompress;
};

class ResourceFileFlattener {
public:
    ResourceFileFlattener(const ResourceFileFlattenerOptions& options,
                          IAaptContext* context, proguard::KeepSet* keepSet) :
            mOptions(options), mContext(context), mKeepSet(keepSet) {
    }

    bool flatten(ResourceTable* table, IArchiveWriter* archiveWriter);

private:
    struct FileOperation {
        io::IFile* fileToCopy;
        std::unique_ptr<xml::XmlResource> xmlToFlatten;
        std::string dstPath;
        bool skipVersion = false;
    };

    uint32_t getCompressionFlags(const StringPiece& str);

    bool linkAndVersionXmlFile(const ResourceEntry* entry, const ResourceFile& fileDesc,
                               io::IFile* file, ResourceTable* table, FileOperation* outFileOp);

    ResourceFileFlattenerOptions mOptions;
    IAaptContext* mContext;
    proguard::KeepSet* mKeepSet;
};

uint32_t ResourceFileFlattener::getCompressionFlags(const StringPiece& str) {
    if (mOptions.doNotCompressAnything) {
        return 0;
    }

    for (const std::string& extension : mOptions.extensionsToNotCompress) {
        if (util::stringEndsWith<char>(str, extension)) {
            return 0;
        }
    }
    return ArchiveEntry::kCompress;
}

bool ResourceFileFlattener::linkAndVersionXmlFile(const ResourceEntry* entry,
                                                  const ResourceFile& fileDesc,
                                                  io::IFile* file,
                                                  ResourceTable* table,
                                                  FileOperation* outFileOp) {
    const StringPiece srcPath = file->getSource().path;
    if (mContext->verbose()) {
        mContext->getDiagnostics()->note(DiagMessage() << "linking " << srcPath);
    }

    std::unique_ptr<io::IData> data = file->openAsData();
    if (!data) {
        mContext->getDiagnostics()->error(DiagMessage(file->getSource()) << "failed to open file");
        return false;
    }

    if (util::stringEndsWith<char>(srcPath, ".flat")) {
        outFileOp->xmlToFlatten = loadBinaryXmlSkipFileExport(file->getSource(),
                                                              data->data(), data->size(),
                                                              mContext->getDiagnostics());
    } else {
        outFileOp->xmlToFlatten = xml::inflate(data->data(), data->size(),
                                               mContext->getDiagnostics(),
                                               file->getSource());
    }

    if (!outFileOp->xmlToFlatten) {
        return false;
    }

    // Copy the the file description header.
    outFileOp->xmlToFlatten->file = fileDesc;

    XmlReferenceLinker xmlLinker;
    if (!xmlLinker.consume(mContext, outFileOp->xmlToFlatten.get())) {
        return false;
    }

    if (!proguard::collectProguardRules(outFileOp->xmlToFlatten->file.source,
                                        outFileOp->xmlToFlatten.get(), mKeepSet)) {
        return false;
    }

    if (!mOptions.noAutoVersion) {
        if (mOptions.noVersionVectors) {
            // Skip this if it is a vector or animated-vector.
            xml::Element* el = xml::findRootElement(outFileOp->xmlToFlatten.get());
            if (el && el->namespaceUri.empty()) {
                if (el->name == u"vector" || el->name == u"animated-vector") {
                    // We are NOT going to version this file.
                    outFileOp->skipVersion = true;
                    return true;
                }
            }
        }

        // Find the first SDK level used that is higher than this defined config and
        // not superseded by a lower or equal SDK level resource.
        for (int sdkLevel : xmlLinker.getSdkLevels()) {
            if (sdkLevel > outFileOp->xmlToFlatten->file.config.sdkVersion) {
                if (!shouldGenerateVersionedResource(entry, outFileOp->xmlToFlatten->file.config,
                                                     sdkLevel)) {
                    // If we shouldn't generate a versioned resource, stop checking.
                    break;
                }

                ResourceFile versionedFileDesc = outFileOp->xmlToFlatten->file;
                versionedFileDesc.config.sdkVersion = (uint16_t) sdkLevel;

                if (mContext->verbose()) {
                    mContext->getDiagnostics()->note(DiagMessage(versionedFileDesc.source)
                                                     << "auto-versioning resource from config '"
                                                     << outFileOp->xmlToFlatten->file.config
                                                     << "' -> '"
                                                     << versionedFileDesc.config << "'");
                }

                std::u16string genPath = util::utf8ToUtf16(ResourceUtils::buildResourceFileName(
                        versionedFileDesc, mContext->getNameMangler()));

                bool added = table->addFileReferenceAllowMangled(versionedFileDesc.name,
                                                                 versionedFileDesc.config,
                                                                 versionedFileDesc.source,
                                                                 genPath,
                                                                 file,
                                                                 mContext->getDiagnostics());
                if (!added) {
                    return false;
                }
                break;
            }
        }
    }
    return true;
}

/**
 * Do not insert or remove any resources while executing in this function. It will
 * corrupt the iteration order.
 */
bool ResourceFileFlattener::flatten(ResourceTable* table, IArchiveWriter* archiveWriter) {
    bool error = false;
    std::map<std::pair<ConfigDescription, StringPiece16>, FileOperation> configSortedFiles;

    for (auto& pkg : table->packages) {
        for (auto& type : pkg->types) {
            // Sort by config and name, so that we get better locality in the zip file.
            configSortedFiles.clear();
            for (auto& entry : type->entries) {
                // Iterate via indices because auto generated values can be inserted ahead of
                // the value being processed.
                for (size_t i = 0; i < entry->values.size(); i++) {
                    ResourceConfigValue* configValue = entry->values[i].get();

                    FileReference* fileRef = valueCast<FileReference>(configValue->value.get());
                    if (!fileRef) {
                        continue;
                    }

                    io::IFile* file = fileRef->file;
                    if (!file) {
                        mContext->getDiagnostics()->error(DiagMessage(fileRef->getSource())
                                                          << "file not found");
                        return false;
                    }

                    FileOperation fileOp;
                    fileOp.dstPath = util::utf16ToUtf8(*fileRef->path);

                    const StringPiece srcPath = file->getSource().path;
                    if (type->type != ResourceType::kRaw &&
                            (util::stringEndsWith<char>(srcPath, ".xml.flat") ||
                            util::stringEndsWith<char>(srcPath, ".xml"))) {
                        ResourceFile fileDesc;
                        fileDesc.config = configValue->config;
                        fileDesc.name = ResourceName(pkg->name, type->type, entry->name);
                        fileDesc.source = fileRef->getSource();
                        if (!linkAndVersionXmlFile(entry.get(), fileDesc, file, table, &fileOp)) {
                            error = true;
                            continue;
                        }

                    } else {
                        fileOp.fileToCopy = file;
                    }

                    // NOTE(adamlesinski): Explicitly construct a StringPiece16 here, or else
                    // we end up copying the string in the std::make_pair() method, then creating
                    // a StringPiece16 from the copy, which would cause us to end up referencing
                    // garbage in the map.
                    const StringPiece16 entryName(entry->name);
                    configSortedFiles[std::make_pair(configValue->config, entryName)] =
                                      std::move(fileOp);
                }
            }

            if (error) {
                return false;
            }

            // Now flatten the sorted values.
            for (auto& mapEntry : configSortedFiles) {
                const ConfigDescription& config = mapEntry.first.first;
                const FileOperation& fileOp = mapEntry.second;

                if (fileOp.xmlToFlatten) {
                    Maybe<size_t> maxSdkLevel;
                    if (!mOptions.noAutoVersion && !fileOp.skipVersion) {
                        maxSdkLevel = std::max<size_t>(config.sdkVersion, 1u);
                    }

                    bool result = flattenXml(fileOp.xmlToFlatten.get(), fileOp.dstPath, maxSdkLevel,
                                             mOptions.keepRawValues,
                                             archiveWriter, mContext);
                    if (!result) {
                        error = true;
                    }
                } else {
                    bool result = copyFileToArchive(fileOp.fileToCopy, fileOp.dstPath,
                                                    getCompressionFlags(fileOp.dstPath),
                                                    archiveWriter, mContext);
                    if (!result) {
                        error = true;
                    }
                }
            }
        }
    }
    return !error;
}

class LinkCommand {
public:
    LinkCommand(LinkContext* context, const LinkOptions& options) :
            mOptions(options), mContext(context), mFinalTable(),
            mFileCollection(util::make_unique<io::FileCollection>()) {
    }

    /**
     * Creates a SymbolTable that loads symbols from the various APKs and caches the
     * results for faster lookup.
     */
    bool loadSymbolsFromIncludePaths() {
        std::unique_ptr<AssetManagerSymbolSource> assetSource =
                util::make_unique<AssetManagerSymbolSource>();
        for (const std::string& path : mOptions.includePaths) {
            if (mContext->verbose()) {
                mContext->getDiagnostics()->note(DiagMessage(path) << "loading include path");
            }

            // First try to load the file as a static lib.
            std::string errorStr;
            std::unique_ptr<ResourceTable> staticInclude = loadStaticLibrary(path, &errorStr);
            if (staticInclude) {
                if (!mOptions.staticLib) {
                    // Can't include static libraries when not building a static library.
                    mContext->getDiagnostics()->error(
                            DiagMessage(path) << "can't include static library when building app");
                    return false;
                }

                // If we are using --no-static-lib-packages, we need to rename the package of this
                // table to our compilation package.
                if (mOptions.noStaticLibPackages) {
                    if (ResourceTablePackage* pkg = staticInclude->findPackageById(0x7f)) {
                        pkg->name = mContext->getCompilationPackage();
                    }
                }

                mContext->getExternalSymbols()->appendSource(
                        util::make_unique<ResourceTableSymbolSource>(staticInclude.get()));

                mStaticTableIncludes.push_back(std::move(staticInclude));

            } else if (!errorStr.empty()) {
                // We had an error with reading, so fail.
                mContext->getDiagnostics()->error(DiagMessage(path) << errorStr);
                return false;
            }

            if (!assetSource->addAssetPath(path)) {
                mContext->getDiagnostics()->error(
                        DiagMessage(path) << "failed to load include path");
                return false;
            }
        }

        mContext->getExternalSymbols()->appendSource(std::move(assetSource));
        return true;
    }

    Maybe<AppInfo> extractAppInfoFromManifest(xml::XmlResource* xmlRes) {
        // Make sure the first element is <manifest> with package attribute.
        if (xml::Element* manifestEl = xml::findRootElement(xmlRes->root.get())) {
            if (manifestEl->namespaceUri.empty() && manifestEl->name == u"manifest") {
                if (xml::Attribute* packageAttr = manifestEl->findAttribute({}, u"package")) {
                    return AppInfo{ packageAttr->value };
                }
            }
        }
        return {};
    }

    /**
     * Precondition: ResourceTable doesn't have any IDs assigned yet, nor is it linked.
     * Postcondition: ResourceTable has only one package left. All others are stripped, or there
     *                is an error and false is returned.
     */
    bool verifyNoExternalPackages() {
        auto isExtPackageFunc = [&](const std::unique_ptr<ResourceTablePackage>& pkg) -> bool {
            return mContext->getCompilationPackage() != pkg->name ||
                    !pkg->id ||
                    pkg->id.value() != mContext->getPackageId();
        };

        bool error = false;
        for (const auto& package : mFinalTable.packages) {
            if (isExtPackageFunc(package)) {
                // We have a package that is not related to the one we're building!
                for (const auto& type : package->types) {
                    for (const auto& entry : type->entries) {
                        ResourceNameRef resName(package->name, type->type, entry->name);

                        for (const auto& configValue : entry->values) {
                            // Special case the occurrence of an ID that is being generated for the
                            // 'android' package. This is due to legacy reasons.
                            if (valueCast<Id>(configValue->value.get()) &&
                                    package->name == u"android") {
                                mContext->getDiagnostics()->warn(
                                        DiagMessage(configValue->value->getSource())
                                        << "generated id '" << resName
                                        << "' for external package '" << package->name
                                        << "'");
                            } else {
                                mContext->getDiagnostics()->error(
                                        DiagMessage(configValue->value->getSource())
                                        << "defined resource '" << resName
                                        << "' for external package '" << package->name
                                        << "'");
                                error = true;
                            }
                        }
                    }
                }
            }
        }

        auto newEndIter = std::remove_if(mFinalTable.packages.begin(), mFinalTable.packages.end(),
                                         isExtPackageFunc);
        mFinalTable.packages.erase(newEndIter, mFinalTable.packages.end());
        return !error;
    }

    /**
     * Returns true if no IDs have been set, false otherwise.
     */
    bool verifyNoIdsSet() {
        for (const auto& package : mFinalTable.packages) {
            for (const auto& type : package->types) {
                if (type->id) {
                    mContext->getDiagnostics()->error(DiagMessage() << "type " << type->type
                                                      << " has ID " << std::hex
                                                      << (int) type->id.value()
                                                      << std::dec << " assigned");
                    return false;
                }

                for (const auto& entry : type->entries) {
                    if (entry->id) {
                        ResourceNameRef resName(package->name, type->type, entry->name);
                        mContext->getDiagnostics()->error(DiagMessage() << "entry " << resName
                                                          << " has ID " << std::hex
                                                          << (int) entry->id.value()
                                                          << std::dec << " assigned");
                        return false;
                    }
                }
            }
        }
        return true;
    }

    std::unique_ptr<IArchiveWriter> makeArchiveWriter() {
        if (mOptions.outputToDirectory) {
            return createDirectoryArchiveWriter(mContext->getDiagnostics(), mOptions.outputPath);
        } else {
            return createZipFileArchiveWriter(mContext->getDiagnostics(), mOptions.outputPath);
        }
    }

    bool flattenTable(ResourceTable* table, IArchiveWriter* writer) {
        BigBuffer buffer(1024);
        TableFlattener flattener(&buffer);
        if (!flattener.consume(mContext, table)) {
            return false;
        }

        if (writer->startEntry("resources.arsc", ArchiveEntry::kAlign)) {
            if (writer->writeEntry(buffer)) {
                if (writer->finishEntry()) {
                    return true;
                }
            }
        }

        mContext->getDiagnostics()->error(
                DiagMessage() << "failed to write resources.arsc to archive");
        return false;
    }

    bool flattenTableToPb(ResourceTable* table, IArchiveWriter* writer) {
        // Create the file/zip entry.
        if (!writer->startEntry("resources.arsc.flat", 0)) {
            mContext->getDiagnostics()->error(DiagMessage() << "failed to open");
            return false;
        }

        std::unique_ptr<pb::ResourceTable> pbTable = serializeTableToPb(table);

        // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream
        // interface.
        {
            google::protobuf::io::CopyingOutputStreamAdaptor adaptor(writer);

            if (!pbTable->SerializeToZeroCopyStream(&adaptor)) {
                mContext->getDiagnostics()->error(DiagMessage() << "failed to write");
                return false;
            }
        }

        if (!writer->finishEntry()) {
            mContext->getDiagnostics()->error(DiagMessage() << "failed to finish entry");
            return false;
        }
        return true;
    }

    bool writeJavaFile(ResourceTable* table, const StringPiece16& packageNameToGenerate,
                       const StringPiece16& outPackage, JavaClassGeneratorOptions javaOptions) {
        if (!mOptions.generateJavaClassPath) {
            return true;
        }

        std::string outPath = mOptions.generateJavaClassPath.value();
        file::appendPath(&outPath, file::packageToPath(util::utf16ToUtf8(outPackage)));
        if (!file::mkdirs(outPath)) {
            mContext->getDiagnostics()->error(
                    DiagMessage() << "failed to create directory '" << outPath << "'");
            return false;
        }

        file::appendPath(&outPath, "R.java");

        std::ofstream fout(outPath, std::ofstream::binary);
        if (!fout) {
            mContext->getDiagnostics()->error(
                    DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno));
            return false;
        }

        JavaClassGenerator generator(mContext, table, javaOptions);
        if (!generator.generate(packageNameToGenerate, outPackage, &fout)) {
            mContext->getDiagnostics()->error(DiagMessage(outPath) << generator.getError());
            return false;
        }

        if (!fout) {
            mContext->getDiagnostics()->error(
                    DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno));
        }
        return true;
    }

    bool writeManifestJavaFile(xml::XmlResource* manifestXml) {
        if (!mOptions.generateJavaClassPath) {
            return true;
        }

        std::unique_ptr<ClassDefinition> manifestClass = generateManifestClass(
                mContext->getDiagnostics(), manifestXml);

        if (!manifestClass) {
            // Something bad happened, but we already logged it, so exit.
            return false;
        }

        if (manifestClass->empty()) {
            // Empty Manifest class, no need to generate it.
            return true;
        }

        // Add any JavaDoc annotations to the generated class.
        for (const std::string& annotation : mOptions.javadocAnnotations) {
            std::string properAnnotation = "@";
            properAnnotation += annotation;
            manifestClass->getCommentBuilder()->appendComment(properAnnotation);
        }

        const std::string packageUtf8 = util::utf16ToUtf8(mContext->getCompilationPackage());

        std::string outPath = mOptions.generateJavaClassPath.value();
        file::appendPath(&outPath, file::packageToPath(packageUtf8));

        if (!file::mkdirs(outPath)) {
            mContext->getDiagnostics()->error(
                    DiagMessage() << "failed to create directory '" << outPath << "'");
            return false;
        }

        file::appendPath(&outPath, "Manifest.java");

        std::ofstream fout(outPath, std::ofstream::binary);
        if (!fout) {
            mContext->getDiagnostics()->error(
                    DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno));
            return false;
        }

        if (!ClassDefinition::writeJavaFile(manifestClass.get(), packageUtf8, true, &fout)) {
            mContext->getDiagnostics()->error(
                    DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno));
            return false;
        }
        return true;
    }

    bool writeProguardFile(const proguard::KeepSet& keepSet) {
        if (!mOptions.generateProguardRulesPath) {
            return true;
        }

        const std::string& outPath = mOptions.generateProguardRulesPath.value();
        std::ofstream fout(outPath, std::ofstream::binary);
        if (!fout) {
            mContext->getDiagnostics()->error(
                    DiagMessage() << "failed to open '" << outPath << "': " << strerror(errno));
            return false;
        }

        proguard::writeKeepSet(&fout, keepSet);
        if (!fout) {
            mContext->getDiagnostics()->error(
                    DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno));
            return false;
        }
        return true;
    }

    std::unique_ptr<ResourceTable> loadStaticLibrary(const std::string& input,
                                                     std::string* outError) {
        std::unique_ptr<io::ZipFileCollection> collection = io::ZipFileCollection::create(
                input, outError);
        if (!collection) {
            return {};
        }
        return loadTablePbFromCollection(collection.get());
    }

    std::unique_ptr<ResourceTable> loadTablePbFromCollection(io::IFileCollection* collection) {
        io::IFile* file = collection->findFile("resources.arsc.flat");
        if (!file) {
            return {};
        }

        std::unique_ptr<io::IData> data = file->openAsData();
        return loadTableFromPb(file->getSource(), data->data(), data->size(),
                               mContext->getDiagnostics());
    }

    bool mergeStaticLibrary(const std::string& input, bool override) {
        if (mContext->verbose()) {
            mContext->getDiagnostics()->note(DiagMessage() << "merging static library " << input);
        }

        std::string errorStr;
        std::unique_ptr<io::ZipFileCollection> collection =
                io::ZipFileCollection::create(input, &errorStr);
        if (!collection) {
            mContext->getDiagnostics()->error(DiagMessage(input) << errorStr);
            return false;
        }

        std::unique_ptr<ResourceTable> table = loadTablePbFromCollection(collection.get());
        if (!table) {
            mContext->getDiagnostics()->error(DiagMessage(input) << "invalid static library");
            return false;
        }

        ResourceTablePackage* pkg = table->findPackageById(0x7f);
        if (!pkg) {
            mContext->getDiagnostics()->error(DiagMessage(input)
                                              << "static library has no package");
            return false;
        }

        bool result;
        if (mOptions.noStaticLibPackages) {
            // Merge all resources as if they were in the compilation package. This is the old
            // behaviour of aapt.

            // Add the package to the set of --extra-packages so we emit an R.java for each
            // library package.
            if (!pkg->name.empty()) {
                mOptions.extraJavaPackages.insert(pkg->name);
            }

            pkg->name = u"";
            if (override) {
                result = mTableMerger->mergeOverlay(Source(input), table.get(), collection.get());
            } else {
                result = mTableMerger->merge(Source(input), table.get(), collection.get());
            }

        } else {
            // This is the proper way to merge libraries, where the package name is preserved
            // and resource names are mangled.
            result = mTableMerger->mergeAndMangle(Source(input), pkg->name, table.get(),
                                                  collection.get());
        }

        if (!result) {
            return false;
        }

        // Make sure to move the collection into the set of IFileCollections.
        mCollections.push_back(std::move(collection));
        return true;
    }

    bool mergeResourceTable(io::IFile* file, bool override) {
        if (mContext->verbose()) {
            mContext->getDiagnostics()->note(DiagMessage() << "merging resource table "
                                             << file->getSource());
        }

        std::unique_ptr<io::IData> data = file->openAsData();
        if (!data) {
            mContext->getDiagnostics()->error(DiagMessage(file->getSource())
                                             << "failed to open file");
            return false;
        }

        std::unique_ptr<ResourceTable> table = loadTableFromPb(file->getSource(),
                                                               data->data(), data->size(),
                                                               mContext->getDiagnostics());
        if (!table) {
            return false;
        }

        bool result = false;
        if (override) {
            result = mTableMerger->mergeOverlay(file->getSource(), table.get());
        } else {
            result = mTableMerger->merge(file->getSource(), table.get());
        }
        return result;
    }

    bool mergeCompiledFile(io::IFile* file, ResourceFile* fileDesc, bool override) {
        if (mContext->verbose()) {
            mContext->getDiagnostics()->note(DiagMessage() << "merging compiled file "
                                             << file->getSource());
        }

        bool result = false;
        if (override) {
            result = mTableMerger->mergeFileOverlay(*fileDesc, file);
        } else {
            result = mTableMerger->mergeFile(*fileDesc, file);
        }

        if (!result) {
            return false;
        }

        // Add the exports of this file to the table.
        for (SourcedResourceName& exportedSymbol : fileDesc->exportedSymbols) {
            if (exportedSymbol.name.package.empty()) {
                exportedSymbol.name.package = mContext->getCompilationPackage();
            }

            ResourceNameRef resName = exportedSymbol.name;

            Maybe<ResourceName> mangledName = mContext->getNameMangler()->mangleName(
                    exportedSymbol.name);
            if (mangledName) {
                resName = mangledName.value();
            }

            std::unique_ptr<Id> id = util::make_unique<Id>();
            id->setSource(fileDesc->source.withLine(exportedSymbol.line));
            bool result = mFinalTable.addResourceAllowMangled(
                    resName, ConfigDescription::defaultConfig(), std::string(), std::move(id),
                    mContext->getDiagnostics());
            if (!result) {
                return false;
            }
        }
        return true;
    }

    /**
     * Takes a path to load as a ZIP file and merges the files within into the master ResourceTable.
     * If override is true, conflicting resources are allowed to override each other, in order of
     * last seen.
     *
     * An io::IFileCollection is created from the ZIP file and added to the set of
     * io::IFileCollections that are open.
     */
    bool mergeArchive(const std::string& input, bool override) {
        if (mContext->verbose()) {
            mContext->getDiagnostics()->note(DiagMessage() << "merging archive " << input);
        }

        std::string errorStr;
        std::unique_ptr<io::ZipFileCollection> collection =
                io::ZipFileCollection::create(input, &errorStr);
        if (!collection) {
            mContext->getDiagnostics()->error(DiagMessage(input) << errorStr);
            return false;
        }

        bool error = false;
        for (auto iter = collection->iterator(); iter->hasNext(); ) {
            if (!mergeFile(iter->next(), override)) {
                error = true;
            }
        }

        // Make sure to move the collection into the set of IFileCollections.
        mCollections.push_back(std::move(collection));
        return !error;
    }

    /**
     * Takes a path to load and merge into the master ResourceTable. If override is true,
     * conflicting resources are allowed to override each other, in order of last seen.
     *
     * If the file path ends with .flata, .jar, .jack, or .zip the file is treated as ZIP archive
     * and the files within are merged individually.
     *
     * Otherwise the files is processed on its own.
     */
    bool mergePath(const std::string& path, bool override) {
        if (util::stringEndsWith<char>(path, ".flata") ||
                util::stringEndsWith<char>(path, ".jar") ||
                util::stringEndsWith<char>(path, ".jack") ||
                util::stringEndsWith<char>(path, ".zip")) {
            return mergeArchive(path, override);
        } else if (util::stringEndsWith<char>(path, ".apk")) {
            return mergeStaticLibrary(path, override);
        }

        io::IFile* file = mFileCollection->insertFile(path);
        return mergeFile(file, override);
    }

    /**
     * Takes a file to load and merge into the master ResourceTable. If override is true,
     * conflicting resources are allowed to override each other, in order of last seen.
     *
     * If the file ends with .arsc.flat, then it is loaded as a ResourceTable and merged into the
     * master ResourceTable. If the file ends with .flat, then it is treated like a compiled file
     * and the header data is read and merged into the final ResourceTable.
     *
     * All other file types are ignored. This is because these files could be coming from a zip,
     * where we could have other files like classes.dex.
     */
    bool mergeFile(io::IFile* file, bool override) {
        const Source& src = file->getSource();
        if (util::stringEndsWith<char>(src.path, ".arsc.flat")) {
            return mergeResourceTable(file, override);

        } else if (util::stringEndsWith<char>(src.path, ".flat")){
            // Try opening the file and looking for an Export header.
            std::unique_ptr<io::IData> data = file->openAsData();
            if (!data) {
                mContext->getDiagnostics()->error(DiagMessage(src) << "failed to open");
                return false;
            }

            std::unique_ptr<ResourceFile> resourceFile = loadFileExportHeader(
                    src, data->data(), data->size(), mContext->getDiagnostics());
            if (resourceFile) {
                return mergeCompiledFile(file, resourceFile.get(), override);
            }
            return false;
        }

        // Ignore non .flat files. This could be classes.dex or something else that happens
        // to be in an archive.
        return true;
    }

    int run(const std::vector<std::string>& inputFiles) {
        // Load the AndroidManifest.xml
        std::unique_ptr<xml::XmlResource> manifestXml = loadXml(mOptions.manifestPath,
                                                                mContext->getDiagnostics());
        if (!manifestXml) {
            return 1;
        }

        if (Maybe<AppInfo> maybeAppInfo = extractAppInfoFromManifest(manifestXml.get())) {
            mContext->setCompilationPackage(maybeAppInfo.value().package);
        } else {
            mContext->getDiagnostics()->error(DiagMessage(mOptions.manifestPath)
                                             << "no package specified in <manifest> tag");
            return 1;
        }

        if (!util::isJavaPackageName(mContext->getCompilationPackage())) {
            mContext->getDiagnostics()->error(DiagMessage(mOptions.manifestPath)
                                             << "invalid package name '"
                                             << mContext->getCompilationPackage()
                                             << "'");
            return 1;
        }

        mContext->setNameManglerPolicy(NameManglerPolicy{ mContext->getCompilationPackage() });

        if (mContext->getCompilationPackage() == u"android") {
            mContext->setPackageId(0x01);
        } else {
            mContext->setPackageId(0x7f);
        }

        if (!loadSymbolsFromIncludePaths()) {
            return 1;
        }

        TableMergerOptions tableMergerOptions;
        tableMergerOptions.autoAddOverlay = mOptions.autoAddOverlay;
        mTableMerger = util::make_unique<TableMerger>(mContext, &mFinalTable, tableMergerOptions);

        if (mContext->verbose()) {
            mContext->getDiagnostics()->note(
                    DiagMessage() << "linking package '" << mContext->getCompilationPackage()
                                  << "' with package ID " << std::hex
                                  << (int) mContext->getPackageId());
        }


        for (const std::string& input : inputFiles) {
            if (!mergePath(input, false)) {
                mContext->getDiagnostics()->error(DiagMessage() << "failed parsing input");
                return 1;
            }
        }

        for (const std::string& input : mOptions.overlayFiles) {
            if (!mergePath(input, true)) {
                mContext->getDiagnostics()->error(DiagMessage() << "failed parsing overlays");
                return 1;
            }
        }

        if (!verifyNoExternalPackages()) {
            return 1;
        }

        if (!mOptions.staticLib) {
            PrivateAttributeMover mover;
            if (!mover.consume(mContext, &mFinalTable)) {
                mContext->getDiagnostics()->error(
                        DiagMessage() << "failed moving private attributes");
                return 1;
            }
        }

        if (!mOptions.staticLib) {
            // Assign IDs if we are building a regular app.
            IdAssigner idAssigner;
            if (!idAssigner.consume(mContext, &mFinalTable)) {
                mContext->getDiagnostics()->error(DiagMessage() << "failed assigning IDs");
                return 1;
            }
        } else {
            // Static libs are merged with other apps, and ID collisions are bad, so verify that
            // no IDs have been set.
            if (!verifyNoIdsSet()) {
                return 1;
            }
        }

        // Add the names to mangle based on our source merge earlier.
        mContext->setNameManglerPolicy(NameManglerPolicy{
                mContext->getCompilationPackage(), mTableMerger->getMergedPackages() });

        // Add our table to the symbol table.
        mContext->getExternalSymbols()->prependSource(
                        util::make_unique<ResourceTableSymbolSource>(&mFinalTable));

        {
            ReferenceLinker linker;
            if (!linker.consume(mContext, &mFinalTable)) {
                mContext->getDiagnostics()->error(DiagMessage() << "failed linking references");
                return 1;
            }

            if (mOptions.staticLib) {
                if (!mOptions.products.empty()) {
                    mContext->getDiagnostics()->warn(
                            DiagMessage() << "can't select products when building static library");
                }

                if (mOptions.tableSplitterOptions.configFilter != nullptr ||
                        mOptions.tableSplitterOptions.preferredDensity) {
                    mContext->getDiagnostics()->warn(
                            DiagMessage() << "can't strip resources when building static library");
                }
            } else {
                ProductFilter productFilter(mOptions.products);
                if (!productFilter.consume(mContext, &mFinalTable)) {
                    mContext->getDiagnostics()->error(DiagMessage() << "failed stripping products");
                    return 1;
                }

                // TODO(adamlesinski): Actually pass in split constraints and handle splits at the file
                // level.
                TableSplitter tableSplitter({}, mOptions.tableSplitterOptions);
                if (!tableSplitter.verifySplitConstraints(mContext)) {
                    return 1;
                }
                tableSplitter.splitTable(&mFinalTable);
            }
        }

        proguard::KeepSet proguardKeepSet;

        std::unique_ptr<IArchiveWriter> archiveWriter = makeArchiveWriter();
        if (!archiveWriter) {
            mContext->getDiagnostics()->error(DiagMessage() << "failed to create archive");
            return 1;
        }

        bool error = false;
        {
            ManifestFixer manifestFixer(mOptions.manifestFixerOptions);
            if (!manifestFixer.consume(mContext, manifestXml.get())) {
                error = true;
            }

            // AndroidManifest.xml has no resource name, but the CallSite is built from the name
            // (aka, which package the AndroidManifest.xml is coming from).
            // So we give it a package name so it can see local resources.
            manifestXml->file.name.package = mContext->getCompilationPackage();

            XmlReferenceLinker manifestLinker;
            if (manifestLinker.consume(mContext, manifestXml.get())) {
                if (!proguard::collectProguardRulesForManifest(Source(mOptions.manifestPath),
                                                               manifestXml.get(),
                                                               &proguardKeepSet)) {
                    error = true;
                }

                if (mOptions.generateJavaClassPath) {
                    if (!writeManifestJavaFile(manifestXml.get())) {
                        error = true;
                    }
                }

                const bool keepRawValues = mOptions.staticLib;
                bool result = flattenXml(manifestXml.get(), "AndroidManifest.xml", {},
                                         keepRawValues, archiveWriter.get(), mContext);
                if (!result) {
                    error = true;
                }
            } else {
                error = true;
            }
        }

        if (error) {
            mContext->getDiagnostics()->error(DiagMessage() << "failed processing manifest");
            return 1;
        }

        ResourceFileFlattenerOptions fileFlattenerOptions;
        fileFlattenerOptions.keepRawValues = mOptions.staticLib;
        fileFlattenerOptions.doNotCompressAnything = mOptions.doNotCompressAnything;
        fileFlattenerOptions.extensionsToNotCompress = mOptions.extensionsToNotCompress;
        fileFlattenerOptions.noAutoVersion = mOptions.noAutoVersion;
        fileFlattenerOptions.noVersionVectors = mOptions.noVersionVectors;
        ResourceFileFlattener fileFlattener(fileFlattenerOptions, mContext, &proguardKeepSet);

        if (!fileFlattener.flatten(&mFinalTable, archiveWriter.get())) {
            mContext->getDiagnostics()->error(DiagMessage() << "failed linking file resources");
            return 1;
        }

        if (!mOptions.noAutoVersion) {
            AutoVersioner versioner;
            if (!versioner.consume(mContext, &mFinalTable)) {
                mContext->getDiagnostics()->error(DiagMessage() << "failed versioning styles");
                return 1;
            }
        }

        if (mOptions.staticLib) {
            if (!flattenTableToPb(&mFinalTable, archiveWriter.get())) {
                mContext->getDiagnostics()->error(DiagMessage()
                                                  << "failed to write resources.arsc.flat");
                return 1;
            }
        } else {
            if (!flattenTable(&mFinalTable, archiveWriter.get())) {
                mContext->getDiagnostics()->error(DiagMessage()
                                                  << "failed to write resources.arsc");
                return 1;
            }
        }

        if (mOptions.generateJavaClassPath) {
            JavaClassGeneratorOptions options;
            options.types = JavaClassGeneratorOptions::SymbolTypes::kAll;
            options.javadocAnnotations = mOptions.javadocAnnotations;

            if (mOptions.staticLib || mOptions.generateNonFinalIds) {
                options.useFinal = false;
            }

            const StringPiece16 actualPackage = mContext->getCompilationPackage();
            StringPiece16 outputPackage = mContext->getCompilationPackage();
            if (mOptions.customJavaPackage) {
                // Override the output java package to the custom one.
                outputPackage = mOptions.customJavaPackage.value();
            }

            if (mOptions.privateSymbols) {
                // If we defined a private symbols package, we only emit Public symbols
                // to the original package, and private and public symbols to the private package.

                options.types = JavaClassGeneratorOptions::SymbolTypes::kPublic;
                if (!writeJavaFile(&mFinalTable, mContext->getCompilationPackage(),
                                   outputPackage, options)) {
                    return 1;
                }

                options.types = JavaClassGeneratorOptions::SymbolTypes::kPublicPrivate;
                outputPackage = mOptions.privateSymbols.value();
            }

            if (!writeJavaFile(&mFinalTable, actualPackage, outputPackage, options)) {
                return 1;
            }

            for (const std::u16string& extraPackage : mOptions.extraJavaPackages) {
                if (!writeJavaFile(&mFinalTable, actualPackage, extraPackage, options)) {
                    return 1;
                }
            }
        }

        if (mOptions.generateProguardRulesPath) {
            if (!writeProguardFile(proguardKeepSet)) {
                return 1;
            }
        }

        if (mContext->verbose()) {
            DebugPrintTableOptions debugPrintTableOptions;
            debugPrintTableOptions.showSources = true;
            Debug::printTable(&mFinalTable, debugPrintTableOptions);
        }
        return 0;
    }

private:
    LinkOptions mOptions;
    LinkContext* mContext;
    ResourceTable mFinalTable;

    std::unique_ptr<TableMerger> mTableMerger;

    // A pointer to the FileCollection representing the filesystem (not archives).
    std::unique_ptr<io::FileCollection> mFileCollection;

    // A vector of IFileCollections. This is mainly here to keep ownership of the collections.
    std::vector<std::unique_ptr<io::IFileCollection>> mCollections;

    // A vector of ResourceTables. This is here to retain ownership, so that the SymbolTable
    // can use these.
    std::vector<std::unique_ptr<ResourceTable>> mStaticTableIncludes;
};

int link(const std::vector<StringPiece>& args) {
    LinkContext context;
    LinkOptions options;
    Maybe<std::string> privateSymbolsPackage;
    Maybe<std::string> minSdkVersion, targetSdkVersion;
    Maybe<std::string> renameManifestPackage, renameInstrumentationTargetPackage;
    Maybe<std::string> versionCode, versionName;
    Maybe<std::string> customJavaPackage;
    std::vector<std::string> extraJavaPackages;
    Maybe<std::string> configs;
    Maybe<std::string> preferredDensity;
    Maybe<std::string> productList;
    bool legacyXFlag = false;
    bool requireLocalization = false;
    bool verbose = false;
    Flags flags = Flags()
            .requiredFlag("-o", "Output path", &options.outputPath)
            .requiredFlag("--manifest", "Path to the Android manifest to build",
                          &options.manifestPath)
            .optionalFlagList("-I", "Adds an Android APK to link against", &options.includePaths)
            .optionalFlagList("-R", "Compilation unit to link, using `overlay` semantics.\n"
                              "The last conflicting resource given takes precedence.",
                              &options.overlayFiles)
            .optionalFlag("--java", "Directory in which to generate R.java",
                          &options.generateJavaClassPath)
            .optionalFlag("--proguard", "Output file for generated Proguard rules",
                          &options.generateProguardRulesPath)
            .optionalSwitch("--no-auto-version",
                            "Disables automatic style and layout SDK versioning",
                            &options.noAutoVersion)
            .optionalSwitch("--no-version-vectors",
                            "Disables automatic versioning of vector drawables. Use this only\n"
                            "when building with vector drawable support library",
                            &options.noVersionVectors)
            .optionalSwitch("-x", "Legacy flag that specifies to use the package identifier 0x01",
                            &legacyXFlag)
            .optionalSwitch("-z", "Require localization of strings marked 'suggested'",
                            &requireLocalization)
            .optionalFlag("-c", "Comma separated list of configurations to include. The default\n"
                                "is all configurations", &configs)
            .optionalFlag("--preferred-density",
                          "Selects the closest matching density and strips out all others.",
                          &preferredDensity)
            .optionalFlag("--product", "Comma separated list of product names to keep",
                          &productList)
            .optionalSwitch("--output-to-dir", "Outputs the APK contents to a directory specified "
                            "by -o",
                            &options.outputToDirectory)
            .optionalFlag("--min-sdk-version", "Default minimum SDK version to use for "
                          "AndroidManifest.xml", &minSdkVersion)
            .optionalFlag("--target-sdk-version", "Default target SDK version to use for "
                          "AndroidManifest.xml", &targetSdkVersion)
            .optionalFlag("--version-code", "Version code (integer) to inject into the "
                          "AndroidManifest.xml if none is present", &versionCode)
            .optionalFlag("--version-name", "Version name to inject into the AndroidManifest.xml "
                          "if none is present", &versionName)
            .optionalSwitch("--static-lib", "Generate a static Android library", &options.staticLib)
            .optionalSwitch("--no-static-lib-packages",
                            "Merge all library resources under the app's package",
                            &options.noStaticLibPackages)
            .optionalSwitch("--non-final-ids", "Generates R.java without the final modifier.\n"
                            "This is implied when --static-lib is specified.",
                            &options.generateNonFinalIds)
            .optionalFlag("--private-symbols", "Package name to use when generating R.java for "
                          "private symbols.\n"
                          "If not specified, public and private symbols will use the application's "
                          "package name", &privateSymbolsPackage)
            .optionalFlag("--custom-package", "Custom Java package under which to generate R.java",
                          &customJavaPackage)
            .optionalFlagList("--extra-packages", "Generate the same R.java but with different "
                              "package names", &extraJavaPackages)
            .optionalFlagList("--add-javadoc-annotation", "Adds a JavaDoc annotation to all "
                            "generated Java classes", &options.javadocAnnotations)
            .optionalSwitch("--auto-add-overlay", "Allows the addition of new resources in "
                            "overlays without <add-resource> tags", &options.autoAddOverlay)
            .optionalFlag("--rename-manifest-package", "Renames the package in AndroidManifest.xml",
                          &renameManifestPackage)
            .optionalFlag("--rename-instrumentation-target-package",
                          "Changes the name of the target package for instrumentation. Most useful "
                          "when used\nin conjunction with --rename-manifest-package",
                          &renameInstrumentationTargetPackage)
            .optionalFlagList("-0", "File extensions not to compress",
                              &options.extensionsToNotCompress)
            .optionalSwitch("-v", "Enables verbose logging", &verbose);

    if (!flags.parse("aapt2 link", args, &std::cerr)) {
        return 1;
    }

    // Expand all argument-files passed into the command line. These start with '@'.
    std::vector<std::string> argList;
    for (const std::string& arg : flags.getArgs()) {
        if (util::stringStartsWith<char>(arg, "@")) {
            const std::string path = arg.substr(1, arg.size() - 1);
            std::string error;
            if (!file::appendArgsFromFile(path, &argList, &error)) {
                context.getDiagnostics()->error(DiagMessage(path) << error);
                return 1;
            }
        } else {
            argList.push_back(arg);
        }
    }

    if (verbose) {
        context.setVerbose(verbose);
    }

    if (privateSymbolsPackage) {
        options.privateSymbols = util::utf8ToUtf16(privateSymbolsPackage.value());
    }

    if (minSdkVersion) {
        options.manifestFixerOptions.minSdkVersionDefault =
                util::utf8ToUtf16(minSdkVersion.value());
    }

    if (targetSdkVersion) {
        options.manifestFixerOptions.targetSdkVersionDefault =
                util::utf8ToUtf16(targetSdkVersion.value());
    }

    if (renameManifestPackage) {
        options.manifestFixerOptions.renameManifestPackage =
                util::utf8ToUtf16(renameManifestPackage.value());
    }

    if (renameInstrumentationTargetPackage) {
        options.manifestFixerOptions.renameInstrumentationTargetPackage =
                util::utf8ToUtf16(renameInstrumentationTargetPackage.value());
    }

    if (versionCode) {
        options.manifestFixerOptions.versionCodeDefault = util::utf8ToUtf16(versionCode.value());
    }

    if (versionName) {
        options.manifestFixerOptions.versionNameDefault = util::utf8ToUtf16(versionName.value());
    }

    if (customJavaPackage) {
        options.customJavaPackage = util::utf8ToUtf16(customJavaPackage.value());
    }

    // Populate the set of extra packages for which to generate R.java.
    for (std::string& extraPackage : extraJavaPackages) {
        // A given package can actually be a colon separated list of packages.
        for (StringPiece package : util::split(extraPackage, ':')) {
            options.extraJavaPackages.insert(util::utf8ToUtf16(package));
        }
    }

    if (productList) {
        for (StringPiece product : util::tokenize<char>(productList.value(), ',')) {
            if (product != "" && product != "default") {
                options.products.insert(product.toString());
            }
        }
    }

    AxisConfigFilter filter;
    if (configs) {
        for (const StringPiece& configStr : util::tokenize<char>(configs.value(), ',')) {
            ConfigDescription config;
            LocaleValue lv;
            if (lv.initFromFilterString(configStr)) {
                lv.writeTo(&config);
            } else if (!ConfigDescription::parse(configStr, &config)) {
                context.getDiagnostics()->error(
                        DiagMessage() << "invalid config '" << configStr << "' for -c option");
                return 1;
            }

            if (config.density != 0) {
                context.getDiagnostics()->warn(
                        DiagMessage() << "ignoring density '" << config << "' for -c option");
            } else {
                filter.addConfig(config);
            }
        }

        options.tableSplitterOptions.configFilter = &filter;
    }

    if (preferredDensity) {
        ConfigDescription preferredDensityConfig;
        if (!ConfigDescription::parse(preferredDensity.value(), &preferredDensityConfig)) {
            context.getDiagnostics()->error(DiagMessage() << "invalid density '"
                                            << preferredDensity.value()
                                            << "' for --preferred-density option");
            return 1;
        }

        // Clear the version that can be automatically added.
        preferredDensityConfig.sdkVersion = 0;

        if (preferredDensityConfig.diff(ConfigDescription::defaultConfig())
                != ConfigDescription::CONFIG_DENSITY) {
            context.getDiagnostics()->error(DiagMessage() << "invalid preferred density '"
                                            << preferredDensity.value() << "'. "
                                            << "Preferred density must only be a density value");
            return 1;
        }
        options.tableSplitterOptions.preferredDensity = preferredDensityConfig.density;
    }

    // Turn off auto versioning for static-libs.
    if (options.staticLib) {
        options.noAutoVersion = true;
        options.noVersionVectors = true;
    }

    LinkCommand cmd(&context, options);
    return cmd.run(argList);
}

} // namespace aapt