AAPT2

First checking of AAPT2. The individual phases of AAPT2 work, but there
are some missing pieces.

For early testing we are missing:
- Need to properly mark file references and include them in package
- Need to package into zip

Final AAPT for apps we are missing:
- Need to crush PNGs
- Need to parse 9-patches
- Need to validate all of AndroidManifest.xml
- Need to write align method to align resource tables for splits.

Final AAPT for apps + system we are missing:
- Need to handle overlays
- Need to store comments for R file
- Need to handle --shared-lib (dynamic references too).

New AAPT features coming:
- Need to import compiled libraries
    - Name mangling
    - R file generation for library code

Change-Id: I95f8a63581b81a1f424ae6fb2c373c883b72c18d
diff --git a/tools/aapt2/Main.cpp b/tools/aapt2/Main.cpp
new file mode 100644
index 0000000..f4e80c5
--- /dev/null
+++ b/tools/aapt2/Main.cpp
@@ -0,0 +1,1421 @@
+/*
+ * 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 "BigBuffer.h"
+#include "BinaryResourceParser.h"
+#include "Files.h"
+#include "JavaClassGenerator.h"
+#include "Linker.h"
+#include "ManifestParser.h"
+#include "ManifestValidator.h"
+#include "ResourceParser.h"
+#include "ResourceTable.h"
+#include "ResourceValues.h"
+#include "SdkConstants.h"
+#include "SourceXmlPullParser.h"
+#include "StringPiece.h"
+#include "TableFlattener.h"
+#include "Util.h"
+#include "XmlFlattener.h"
+
+#include <algorithm>
+#include <androidfw/AssetManager.h>
+#include <cstdlib>
+#include <dirent.h>
+#include <errno.h>
+#include <fstream>
+#include <iostream>
+#include <sstream>
+#include <sys/stat.h>
+
+using namespace aapt;
+
+void printTable(const ResourceTable& table) {
+    std::cout << "ResourceTable package=" << table.getPackage();
+    if (table.getPackageId() != ResourceTable::kUnsetPackageId) {
+        std::cout << " id=" << std::hex << table.getPackageId() << std::dec;
+    }
+    std::cout << std::endl
+         << "---------------------------------------------------------" << std::endl;
+
+    for (const auto& type : table) {
+        std::cout << "Type " << type->type;
+        if (type->typeId != ResourceTableType::kUnsetTypeId) {
+            std::cout << " [" << type->typeId << "]";
+        }
+        std::cout << " (" << type->entries.size() << " entries)" << std::endl;
+        for (const auto& entry : type->entries) {
+            std::cout << "  " << entry->name;
+            if (entry->entryId != ResourceEntry::kUnsetEntryId) {
+                std::cout << " [" << entry->entryId << "]";
+            }
+            std::cout << " (" << entry->values.size() << " configurations)";
+            if (entry->publicStatus.isPublic) {
+                std::cout << " PUBLIC";
+            }
+            std::cout << std::endl;
+            for (const auto& value : entry->values) {
+                std::cout << "    " << value.config << " (" << value.source << ") : ";
+                value.value->print(std::cout);
+                std::cout << std::endl;
+            }
+        }
+    }
+}
+
+void printStringPool(const StringPool& pool) {
+    std::cout << "String pool of length " << pool.size() << std::endl
+         << "---------------------------------------------------------" << std::endl;
+
+    size_t i = 0;
+    for (const auto& entry : pool) {
+        std::cout << "[" << i << "]: "
+             << entry->value
+             << " (Priority " << entry->context.priority
+             << ", Config '" << entry->context.config << "')"
+             << std::endl;
+        i++;
+    }
+}
+
+std::unique_ptr<FileReference> makeFileReference(StringPool& pool, const StringPiece& filename,
+        ResourceType type, const ConfigDescription& config) {
+    std::stringstream path;
+    path << "res/" << type;
+    if (config != ConfigDescription{}) {
+        path << "-" << config;
+    }
+    path << "/" << filename;
+    return util::make_unique<FileReference>(pool.makeRef(util::utf8ToUtf16(path.str())));
+}
+
+/**
+ * Collect files from 'root', filtering out any files that do not
+ * match the FileFilter 'filter'.
+ */
+bool walkTree(const StringPiece& root, const FileFilter& filter,
+        std::vector<Source>& outEntries) {
+    bool error = false;
+
+    for (const std::string& dirName : listFiles(root)) {
+        std::string dir(root.toString());
+        appendPath(&dir, dirName);
+
+        FileType ft = getFileType(dir);
+        if (!filter(dirName, ft)) {
+            continue;
+        }
+
+        if (ft != FileType::kDirectory) {
+            continue;
+        }
+
+        for (const std::string& fileName : listFiles(dir)) {
+            std::string file(dir);
+            appendPath(&file, fileName);
+
+            FileType ft = getFileType(file);
+            if (!filter(fileName, ft)) {
+                continue;
+            }
+
+            if (ft != FileType::kRegular) {
+                Logger::error(Source{ file })
+                    << "not a regular file."
+                    << std::endl;
+                error = true;
+                continue;
+            }
+            outEntries.emplace_back(Source{ file });
+        }
+    }
+    return !error;
+}
+
+bool loadBinaryResourceTable(std::shared_ptr<ResourceTable> table, const Source& source) {
+    std::ifstream ifs(source.path, std::ifstream::in | std::ifstream::binary);
+    if (!ifs) {
+        Logger::error(source) << strerror(errno) << std::endl;
+        return false;
+    }
+
+    std::streampos fsize = ifs.tellg();
+    ifs.seekg(0, std::ios::end);
+    fsize = ifs.tellg() - fsize;
+    ifs.seekg(0, std::ios::beg);
+
+    assert(fsize >= 0);
+    size_t dataSize = static_cast<size_t>(fsize);
+    char* buf = new char[dataSize];
+    ifs.read(buf, dataSize);
+
+    BinaryResourceParser parser(table, source, buf, dataSize);
+    bool result = parser.parse();
+
+    delete [] buf;
+    return result;
+}
+
+bool loadResTable(android::ResTable* table, const Source& source) {
+    std::ifstream ifs(source.path, std::ifstream::in | std::ifstream::binary);
+    if (!ifs) {
+        Logger::error(source) << strerror(errno) << std::endl;
+        return false;
+    }
+
+    std::streampos fsize = ifs.tellg();
+    ifs.seekg(0, std::ios::end);
+    fsize = ifs.tellg() - fsize;
+    ifs.seekg(0, std::ios::beg);
+
+    assert(fsize >= 0);
+    size_t dataSize = static_cast<size_t>(fsize);
+    char* buf = new char[dataSize];
+    ifs.read(buf, dataSize);
+
+    bool result = table->add(buf, dataSize, -1, true) == android::NO_ERROR;
+
+    delete [] buf;
+    return result;
+}
+
+void versionStylesForCompat(std::shared_ptr<ResourceTable> table) {
+    for (auto& type : *table) {
+        if (type->type != ResourceType::kStyle) {
+            continue;
+        }
+
+        for (auto& entry : type->entries) {
+            // Add the versioned styles we want to create
+            // here. They are added to the table after
+            // iterating over the original set of styles.
+            //
+            // A stack is used since auto-generated styles
+            // from later versions should override
+            // auto-generated styles from earlier versions.
+            // Iterating over the styles is done in order,
+            // so we will always visit sdkVersions from smallest
+            // to largest.
+            std::stack<ResourceConfigValue> addStack;
+
+            for (ResourceConfigValue& configValue : entry->values) {
+                visitFunc<Style>(*configValue.value, [&](Style& style) {
+                    // Collect which entries we've stripped and the smallest
+                    // SDK level which was stripped.
+                    size_t minSdkStripped = std::numeric_limits<size_t>::max();
+                    std::vector<Style::Entry> stripped;
+
+                    // Iterate over the style's entries and erase/record the
+                    // attributes whose SDK level exceeds the config's sdkVersion.
+                    auto iter = style.entries.begin();
+                    while (iter != style.entries.end()) {
+                        if (iter->key.name.package == u"android") {
+                            size_t sdkLevel = findAttributeSdkLevel(iter->key.name.entry);
+                            if (sdkLevel > 1 && sdkLevel > configValue.config.sdkVersion) {
+                                // Record that we are about to strip this.
+                                stripped.emplace_back(std::move(*iter));
+                                minSdkStripped = std::min(minSdkStripped, sdkLevel);
+
+                                // Erase this from this style.
+                                iter = style.entries.erase(iter);
+                                continue;
+                            }
+                        }
+                        ++iter;
+                    }
+
+                    if (!stripped.empty()) {
+                        // We have stripped attributes, so let's create a new style to hold them.
+                        ConfigDescription versionConfig(configValue.config);
+                        versionConfig.sdkVersion = minSdkStripped;
+
+                        ResourceConfigValue value = {
+                                versionConfig,
+                                configValue.source,
+                                {},
+
+                                // Create a copy of the original style.
+                                std::unique_ptr<Value>(configValue.value->clone())
+                        };
+
+                        Style& newStyle = static_cast<Style&>(*value.value);
+
+                        // Move the recorded stripped attributes into this new style.
+                        std::move(stripped.begin(), stripped.end(),
+                                  std::back_inserter(newStyle.entries));
+
+                        // We will add this style to the table later. If we do it now, we will
+                        // mess up iteration.
+                        addStack.push(std::move(value));
+                    }
+                });
+            }
+
+            auto comparator =
+                    [](const ResourceConfigValue& lhs, const ConfigDescription& rhs) -> bool {
+                        return lhs.config < rhs;
+                    };
+
+            while (!addStack.empty()) {
+                ResourceConfigValue& value = addStack.top();
+                auto iter = std::lower_bound(entry->values.begin(), entry->values.end(),
+                                             value.config, comparator);
+                if (iter == entry->values.end() || iter->config != value.config) {
+                    entry->values.insert(iter, std::move(value));
+                }
+                addStack.pop();
+            }
+        }
+    }
+}
+
+bool collectXml(std::shared_ptr<ResourceTable> table, const Source& source,
+                const ResourceName& name,
+                const ConfigDescription& config) {
+    std::ifstream in(source.path, std::ifstream::binary);
+    if (!in) {
+        Logger::error(source) << strerror(errno) << std::endl;
+        return false;
+    }
+
+    std::set<size_t> sdkLevels;
+
+    SourceXmlPullParser pullParser(in);
+    while (XmlPullParser::isGoodEvent(pullParser.next())) {
+        if (pullParser.getEvent() != XmlPullParser::Event::kStartElement) {
+            continue;
+        }
+
+        const auto endIter = pullParser.endAttributes();
+        for (auto iter = pullParser.beginAttributes(); iter != endIter; ++iter) {
+            if (iter->namespaceUri == u"http://schemas.android.com/apk/res/android") {
+                size_t sdkLevel = findAttributeSdkLevel(iter->name);
+                if (sdkLevel > 1) {
+                    sdkLevels.insert(sdkLevel);
+                }
+            }
+
+            ResourceNameRef refName;
+            bool create = false;
+            bool privateRef = false;
+            if (ResourceParser::tryParseReference(iter->value, &refName, &create, &privateRef) &&
+                    create) {
+                table->addResource(refName, {}, source.line(pullParser.getLineNumber()),
+                                   util::make_unique<Id>());
+            }
+        }
+    }
+
+    std::unique_ptr<FileReference> fileResource = makeFileReference(
+            table->getValueStringPool(),
+            util::utf16ToUtf8(name.entry) + ".xml",
+            name.type,
+            config);
+    table->addResource(name, config, source.line(0), std::move(fileResource));
+
+    for (size_t level : sdkLevels) {
+        Logger::note(source)
+                << "creating v" << level << " versioned file."
+                << std::endl;
+        ConfigDescription newConfig = config;
+        newConfig.sdkVersion = level;
+
+        std::unique_ptr<FileReference> fileResource = makeFileReference(
+                table->getValueStringPool(),
+                util::utf16ToUtf8(name.entry) + ".xml",
+                name.type,
+                newConfig);
+        table->addResource(name, newConfig, source.line(0), std::move(fileResource));
+    }
+    return true;
+}
+
+struct CompileXml {
+    Source source;
+    ResourceName name;
+    ConfigDescription config;
+};
+
+bool compileXml(std::shared_ptr<Resolver> resolver, const CompileXml& item,
+                const Source& outputSource, std::queue<CompileXml>* queue) {
+    std::ifstream in(item.source.path, std::ifstream::binary);
+    if (!in) {
+        Logger::error(item.source) << strerror(errno) << std::endl;
+        return false;
+    }
+
+    BigBuffer outBuffer(1024);
+    std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<SourceXmlPullParser>(in);
+    XmlFlattener flattener(resolver);
+
+    // We strip attributes that do not belong in this version of the resource.
+    // Non-version qualified resources have an implicit version 1 requirement.
+    XmlFlattener::Options options = { item.config.sdkVersion ? item.config.sdkVersion : 1 };
+    Maybe<size_t> minStrippedSdk = flattener.flatten(item.source, xmlParser, &outBuffer, options);
+    if (!minStrippedSdk) {
+        return false;
+    }
+
+    if (minStrippedSdk.value() > 0) {
+        // Something was stripped, so let's generate a new file
+        // with the version of the smallest SDK version stripped.
+        CompileXml newWork = item;
+        newWork.config.sdkVersion = minStrippedSdk.value();
+        queue->push(newWork);
+    }
+
+    std::ofstream out(outputSource.path, std::ofstream::binary);
+    if (!out) {
+        Logger::error(outputSource) << strerror(errno) << std::endl;
+        return false;
+    }
+
+    if (!util::writeAll(out, outBuffer)) {
+        Logger::error(outputSource) << strerror(errno) << std::endl;
+        return false;
+    }
+    return true;
+}
+
+struct AaptOptions {
+    enum class Phase {
+        LegacyFull,
+        Collect,
+        Link,
+        Compile,
+    };
+
+    // The phase to process.
+    Phase phase;
+
+    // Details about the app.
+    AppInfo appInfo;
+
+    // The location of the manifest file.
+    Source manifest;
+
+    // The files to process.
+    std::vector<Source> sources;
+
+    // The libraries these files may reference.
+    std::vector<Source> libraries;
+
+    // Output directory.
+    Source output;
+
+    // Whether to generate a Java Class.
+    Maybe<Source> generateJavaClass;
+
+    // Whether to output verbose details about
+    // compilation.
+    bool verbose = false;
+};
+
+bool compileAndroidManifest(std::shared_ptr<Resolver> resolver, const AaptOptions& options) {
+    Source outSource = options.output;
+    appendPath(&outSource.path, "AndroidManifest.xml");
+
+    if (options.verbose) {
+        Logger::note(outSource) << "compiling AndroidManifest.xml." << std::endl;
+    }
+
+    std::ifstream in(options.manifest.path, std::ifstream::binary);
+    if (!in) {
+        Logger::error(options.manifest) << strerror(errno) << std::endl;
+        return false;
+    }
+
+    BigBuffer outBuffer(1024);
+    std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<SourceXmlPullParser>(in);
+    XmlFlattener flattener(resolver);
+
+    Maybe<size_t> result = flattener.flatten(options.manifest, xmlParser, &outBuffer,
+                                             XmlFlattener::Options{});
+    if (!result) {
+        return false;
+    }
+
+    std::unique_ptr<uint8_t[]> data = std::unique_ptr<uint8_t[]>(new uint8_t[outBuffer.size()]);
+    uint8_t* p = data.get();
+    for (const auto& b : outBuffer) {
+        memcpy(p, b.buffer.get(), b.size);
+        p += b.size;
+    }
+
+    android::ResXMLTree tree;
+    if (tree.setTo(data.get(), outBuffer.size()) != android::NO_ERROR) {
+        return false;
+    }
+
+    ManifestValidator validator(resolver->getResTable());
+    if (!validator.validate(options.manifest, &tree)) {
+        return false;
+    }
+
+    std::ofstream out(outSource.path, std::ofstream::binary);
+    if (!out) {
+        Logger::error(outSource) << strerror(errno) << std::endl;
+        return false;
+    }
+
+    if (!util::writeAll(out, outBuffer)) {
+        Logger::error(outSource) << strerror(errno) << std::endl;
+        return false;
+    }
+    return true;
+}
+
+bool loadAppInfo(const Source& source, AppInfo* outInfo) {
+    std::ifstream ifs(source.path, std::ifstream::in | std::ifstream::binary);
+    if (!ifs) {
+        Logger::error(source) << strerror(errno) << std::endl;
+        return false;
+    }
+
+    ManifestParser parser;
+    std::shared_ptr<XmlPullParser> pullParser = std::make_shared<SourceXmlPullParser>(ifs);
+    return parser.parse(source, pullParser, outInfo);
+}
+
+/**
+ * Parses legacy options and walks the source directories collecting
+ * files to process.
+ */
+bool prepareLegacy(std::vector<StringPiece>::const_iterator argsIter,
+        const std::vector<StringPiece>::const_iterator argsEndIter,
+        AaptOptions &options) {
+    options.phase = AaptOptions::Phase::LegacyFull;
+
+    std::vector<StringPiece> sourceDirs;
+    while (argsIter != argsEndIter) {
+        if (*argsIter == "-S") {
+            ++argsIter;
+            if (argsIter == argsEndIter) {
+                Logger::error() << "-S missing argument." << std::endl;
+                return false;
+            }
+            sourceDirs.push_back(*argsIter);
+        } else if (*argsIter == "-I") {
+            ++argsIter;
+            if (argsIter == argsEndIter) {
+                Logger::error() << "-I missing argument." << std::endl;
+                return false;
+            }
+            options.libraries.push_back(Source{ argsIter->toString() });
+        } else if (*argsIter == "-M") {
+            ++argsIter;
+            if (argsIter == argsEndIter) {
+                Logger::error() << "-M missing argument." << std::endl;
+                return false;
+            }
+
+            if (!options.manifest.path.empty()) {
+                Logger::error() << "multiple -M flags are not allowed." << std::endl;
+                return false;
+            }
+            options.manifest.path = argsIter->toString();
+        } else if (*argsIter == "-o") {
+            ++argsIter;
+            if (argsIter == argsEndIter) {
+                Logger::error() << "-o missing argument." << std::endl;
+                return false;
+            }
+            options.output = Source{ argsIter->toString() };
+        } else if (*argsIter == "-J") {
+            ++argsIter;
+            if (argsIter == argsEndIter) {
+                Logger::error() << "-J missing argument." << std::endl;
+                return false;
+            }
+            options.generateJavaClass = make_value<Source>(Source{ argsIter->toString() });
+        } else if (*argsIter == "-v") {
+            options.verbose = true;
+        } else {
+            Logger::error() << "unrecognized option '" << *argsIter << "'." << std::endl;
+            return false;
+        }
+
+        ++argsIter;
+    }
+
+    if (options.manifest.path.empty()) {
+        Logger::error() << "must specify manifest file with -M." << std::endl;
+        return false;
+    }
+
+    // Load the App's package name, etc.
+    if (!loadAppInfo(options.manifest, &options.appInfo)) {
+        return false;
+    }
+
+    /**
+     * Set up the file filter to ignore certain files.
+     */
+    const char* customIgnore = getenv("ANDROID_AAPT_IGNORE");
+    FileFilter fileFilter;
+    if (customIgnore && customIgnore[0]) {
+        fileFilter.setPattern(customIgnore);
+    } else {
+        fileFilter.setPattern(
+                "!.svn:!.git:!.ds_store:!*.scc:.*:<dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~");
+    }
+
+    /*
+     * Enumerate the files in each source directory.
+     */
+    for (const StringPiece& source : sourceDirs) {
+        if (!walkTree(source, fileFilter, options.sources)) {
+            return false;
+        }
+    }
+    return true;
+}
+
+bool prepareCollect(std::vector<StringPiece>::const_iterator argsIter,
+        const std::vector<StringPiece>::const_iterator argsEndIter,
+        AaptOptions& options) {
+    options.phase = AaptOptions::Phase::Collect;
+
+    while (argsIter != argsEndIter) {
+        if (*argsIter == "--package") {
+            ++argsIter;
+            if (argsIter == argsEndIter) {
+                Logger::error() << "--package missing argument." << std::endl;
+                return false;
+            }
+            options.appInfo.package = util::utf8ToUtf16(*argsIter);
+        } else if (*argsIter == "-o") {
+            ++argsIter;
+            if (argsIter == argsEndIter) {
+                Logger::error() << "-o missing argument." << std::endl;
+                return false;
+            }
+            options.output = Source{ argsIter->toString() };
+        } else if (*argsIter == "-v") {
+            options.verbose = true;
+        } else if (argsIter->data()[0] != '-') {
+            options.sources.push_back(Source{ argsIter->toString() });
+        } else {
+            Logger::error()
+                    << "unknown option '"
+                    << *argsIter
+                    << "'."
+                    << std::endl;
+            return false;
+        }
+        ++argsIter;
+    }
+    return true;
+}
+
+bool prepareLink(std::vector<StringPiece>::const_iterator argsIter,
+        const std::vector<StringPiece>::const_iterator argsEndIter,
+        AaptOptions& options) {
+    options.phase = AaptOptions::Phase::Link;
+
+    while (argsIter != argsEndIter) {
+        if (*argsIter == "--package") {
+            ++argsIter;
+            if (argsIter == argsEndIter) {
+                Logger::error() << "--package missing argument." << std::endl;
+                return false;
+            }
+            options.appInfo.package = util::utf8ToUtf16(*argsIter);
+        } else if (*argsIter == "-o") {
+            ++argsIter;
+            if (argsIter == argsEndIter) {
+                Logger::error() << "-o missing argument." << std::endl;
+                return false;
+            }
+            options.output = Source{ argsIter->toString() };
+        } else if (*argsIter == "-I") {
+            ++argsIter;
+            if (argsIter == argsEndIter) {
+                Logger::error() << "-I missing argument." << std::endl;
+                return false;
+            }
+            options.libraries.push_back(Source{ argsIter->toString() });
+        } else if (*argsIter == "--java") {
+            ++argsIter;
+            if (argsIter == argsEndIter) {
+                Logger::error() << "--java missing argument." << std::endl;
+                return false;
+            }
+            options.generateJavaClass = make_value<Source>(Source{ argsIter->toString() });
+        } else if (*argsIter == "-v") {
+            options.verbose = true;
+        } else if (argsIter->data()[0] != '-') {
+            options.sources.push_back(Source{ argsIter->toString() });
+        } else {
+            Logger::error()
+                    << "unknown option '"
+                    << *argsIter
+                    << "'."
+                    << std::endl;
+            return false;
+        }
+        ++argsIter;
+    }
+    return true;
+}
+
+bool prepareCompile(std::vector<StringPiece>::const_iterator argsIter,
+        const std::vector<StringPiece>::const_iterator argsEndIter,
+        AaptOptions& options) {
+    options.phase = AaptOptions::Phase::Compile;
+
+    while (argsIter != argsEndIter) {
+        if (*argsIter == "--package") {
+            ++argsIter;
+            if (argsIter == argsEndIter) {
+                Logger::error() << "--package missing argument." << std::endl;
+                return false;
+            }
+            options.appInfo.package = util::utf8ToUtf16(*argsIter);
+        } else if (*argsIter == "-o") {
+            ++argsIter;
+            if (argsIter == argsEndIter) {
+                Logger::error() << "-o missing argument." << std::endl;
+                return false;
+            }
+            options.output = Source{ argsIter->toString() };
+        } else if (*argsIter == "-I") {
+            ++argsIter;
+            if (argsIter == argsEndIter) {
+                Logger::error() << "-I missing argument." << std::endl;
+                return false;
+            }
+            options.libraries.push_back(Source{ argsIter->toString() });
+        } else if (*argsIter == "-v") {
+            options.verbose = true;
+        } else if (argsIter->data()[0] != '-') {
+            options.sources.push_back(Source{ argsIter->toString() });
+        } else {
+            Logger::error()
+                    << "unknown option '"
+                    << *argsIter
+                    << "'."
+                    << std::endl;
+            return false;
+        }
+        ++argsIter;
+    }
+    return true;
+}
+
+struct CollectValuesItem {
+    Source source;
+    ConfigDescription config;
+};
+
+bool collectValues(std::shared_ptr<ResourceTable> table, const CollectValuesItem& item) {
+    std::ifstream in(item.source.path, std::ifstream::binary);
+    if (!in) {
+        Logger::error(item.source) << strerror(errno) << std::endl;
+        return false;
+    }
+
+    std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<SourceXmlPullParser>(in);
+    ResourceParser parser(table, item.source, item.config, xmlParser);
+    return parser.parse();
+}
+
+struct ResourcePathData {
+    std::u16string resourceDir;
+    std::u16string name;
+    std::string extension;
+    ConfigDescription config;
+};
+
+/**
+ * Resource file paths are expected to look like:
+ * [--/res/]type[-config]/name
+ */
+Maybe<ResourcePathData> extractResourcePathData(const Source& source) {
+    std::vector<std::string> parts = util::splitAndLowercase(source.path, '/');
+    if (parts.size() < 2) {
+        Logger::error(source) << "bad resource path." << std::endl;
+        return {};
+    }
+
+    std::string& dir = parts[parts.size() - 2];
+    StringPiece dirStr = dir;
+
+    ConfigDescription config;
+    size_t dashPos = dir.find('-');
+    if (dashPos != std::string::npos) {
+        StringPiece configStr = dirStr.substr(dashPos + 1, dir.size() - (dashPos + 1));
+        if (!ConfigDescription::parse(configStr, &config)) {
+            Logger::error(source)
+                    << "invalid configuration '"
+                    << configStr
+                    << "'."
+                    << std::endl;
+            return {};
+        }
+        dirStr = dirStr.substr(0, dashPos);
+    }
+
+    std::string& filename = parts[parts.size() - 1];
+    StringPiece name = filename;
+    StringPiece extension;
+    size_t dotPos = filename.find('.');
+    if (dotPos != std::string::npos) {
+        extension = name.substr(dotPos + 1, filename.size() - (dotPos + 1));
+        name = name.substr(0, dotPos);
+    }
+
+    return ResourcePathData{
+            util::utf8ToUtf16(dirStr),
+            util::utf8ToUtf16(name),
+            extension.toString(),
+            config
+    };
+}
+
+static bool doLegacy(std::shared_ptr<ResourceTable> table, std::shared_ptr<Resolver> resolver,
+                     const AaptOptions& options) {
+    bool error = false;
+    std::queue<CompileXml> xmlCompileQueue;
+
+    //
+    // Read values XML files and XML/PNG files.
+    // Need to parse the resource type/config/filename.
+    //
+    for (const Source& source : options.sources) {
+        Maybe<ResourcePathData> maybePathData = extractResourcePathData(source);
+        if (!maybePathData) {
+            return false;
+        }
+
+        const ResourcePathData& pathData = maybePathData.value();
+        if (pathData.resourceDir == u"values") {
+            if (options.verbose) {
+                Logger::note(source) << "collecting values..." << std::endl;
+            }
+
+            error |= !collectValues(table, CollectValuesItem{ source, pathData.config });
+            continue;
+        }
+
+        const ResourceType* type = parseResourceType(pathData.resourceDir);
+        if (!type) {
+            Logger::error(source)
+                    << "invalid resource type '"
+                    << pathData.resourceDir
+                    << "'."
+                    << std::endl;
+            return false;
+        }
+
+        ResourceName resourceName = { table->getPackage(), *type, pathData.name };
+        if (pathData.extension == "xml") {
+            if (options.verbose) {
+                Logger::note(source) << "collecting XML..." << std::endl;
+            }
+
+            error |= !collectXml(table, source, resourceName, pathData.config);
+            xmlCompileQueue.push(CompileXml{
+                    source,
+                    resourceName,
+                    pathData.config
+            });
+        } else {
+            std::unique_ptr<FileReference> fileReference = makeFileReference(
+                    table->getValueStringPool(),
+                    util::utf16ToUtf8(pathData.name) + "." + pathData.extension,
+                    *type, pathData.config);
+
+            error |= !table->addResource(resourceName, pathData.config, source.line(0),
+                                         std::move(fileReference));
+        }
+    }
+
+    if (error) {
+        return false;
+    }
+
+    versionStylesForCompat(table);
+
+    //
+    // Verify all references and data types.
+    //
+    Linker linker(table, resolver);
+    if (!linker.linkAndValidate()) {
+        Logger::error()
+                << "linking failed."
+                << std::endl;
+        return false;
+    }
+
+    const auto& unresolvedRefs = linker.getUnresolvedReferences();
+    if (!unresolvedRefs.empty()) {
+        for (const auto& entry : unresolvedRefs) {
+            for (const auto& source : entry.second) {
+                Logger::error(source)
+                        << "unresolved symbol '"
+                        << entry.first
+                        << "'."
+                        << std::endl;
+            }
+        }
+        return false;
+    }
+
+    //
+    // Compile the XML files.
+    //
+    while (!xmlCompileQueue.empty()) {
+        const CompileXml& item = xmlCompileQueue.front();
+
+        // Create the output path from the resource name.
+        std::stringstream outputPath;
+        outputPath << item.name.type;
+        if (item.config != ConfigDescription{}) {
+            outputPath << "-" << item.config.toString();
+        }
+
+        Source outSource = options.output;
+        appendPath(&outSource.path, "res");
+        appendPath(&outSource.path, outputPath.str());
+
+        if (!mkdirs(outSource.path)) {
+            Logger::error(outSource) << strerror(errno) << std::endl;
+            return false;
+        }
+
+        appendPath(&outSource.path, util::utf16ToUtf8(item.name.entry) + ".xml");
+
+        if (options.verbose) {
+            Logger::note(outSource) << "compiling XML file." << std::endl;
+        }
+
+        error |= !compileXml(resolver, item, outSource, &xmlCompileQueue);
+        xmlCompileQueue.pop();
+    }
+
+    if (error) {
+        return false;
+    }
+
+    //
+    // Compile the AndroidManifest.xml file.
+    //
+    if (!compileAndroidManifest(resolver, options)) {
+        return false;
+    }
+
+    //
+    // Generate the Java R class.
+    //
+    if (options.generateJavaClass) {
+        Source outPath = options.generateJavaClass.value();
+        if (options.verbose) {
+            Logger::note()
+                    << "writing symbols to "
+                    << outPath
+                    << "."
+                    << std::endl;
+        }
+
+        for (std::string& part : util::split(util::utf16ToUtf8(table->getPackage()), '.')) {
+            appendPath(&outPath.path, part);
+        }
+
+        if (!mkdirs(outPath.path)) {
+            Logger::error(outPath) << strerror(errno) << std::endl;
+            return false;
+        }
+
+        appendPath(&outPath.path, "R.java");
+
+        std::ofstream fout(outPath.path);
+        if (!fout) {
+            Logger::error(outPath) << strerror(errno) << std::endl;
+            return false;
+        }
+
+        JavaClassGenerator generator(table, JavaClassGenerator::Options{});
+        if (!generator.generate(fout)) {
+            Logger::error(outPath)
+                    << generator.getError()
+                    << "."
+                    << std::endl;
+            return false;
+        }
+    }
+
+    //
+    // Flatten resource table.
+    //
+    if (table->begin() != table->end()) {
+        BigBuffer buffer(1024);
+        TableFlattener::Options tableOptions;
+        tableOptions.useExtendedChunks = false;
+        TableFlattener flattener(tableOptions);
+        if (!flattener.flatten(&buffer, *table)) {
+            Logger::error()
+                    << "failed to flatten resource table->"
+                    << std::endl;
+            return false;
+        }
+
+        if (options.verbose) {
+            Logger::note()
+                    << "Final resource table size="
+                    << util::formatSize(buffer.size())
+                    << std::endl;
+        }
+
+        std::string outTable(options.output.path);
+        appendPath(&outTable, "resources.arsc");
+
+        std::ofstream fout(outTable, std::ofstream::binary);
+        if (!fout) {
+            Logger::error(Source{outTable})
+                    << strerror(errno)
+                    << "."
+                    << std::endl;
+            return false;
+        }
+
+        if (!util::writeAll(fout, buffer)) {
+            Logger::error(Source{outTable})
+                    << strerror(errno)
+                    << "."
+                    << std::endl;
+            return false;
+        }
+        fout.flush();
+    }
+    return true;
+}
+
+static bool doCollect(std::shared_ptr<ResourceTable> table, std::shared_ptr<Resolver> resolver,
+                      const AaptOptions& options) {
+    bool error = false;
+
+    //
+    // Read values XML files and XML/PNG files.
+    // Need to parse the resource type/config/filename.
+    //
+    for (const Source& source : options.sources) {
+        Maybe<ResourcePathData> maybePathData = extractResourcePathData(source);
+        if (!maybePathData) {
+            return false;
+        }
+
+        const ResourcePathData& pathData = maybePathData.value();
+        if (pathData.resourceDir == u"values") {
+            if (options.verbose) {
+                Logger::note(source) << "collecting values..." << std::endl;
+            }
+
+            error |= !collectValues(table, CollectValuesItem{ source, pathData.config });
+            continue;
+        }
+
+        const ResourceType* type = parseResourceType(pathData.resourceDir);
+        if (!type) {
+            Logger::error(source)
+                    << "invalid resource type '"
+                    << pathData.resourceDir
+                    << "'."
+                    << std::endl;
+            return false;
+        }
+
+        ResourceName resourceName = { table->getPackage(), *type, pathData.name };
+        if (pathData.extension == "xml") {
+            if (options.verbose) {
+                Logger::note(source) << "collecting XML..." << std::endl;
+            }
+
+            error |= !collectXml(table, source, resourceName, pathData.config);
+        } else {
+            std::unique_ptr<FileReference> fileReference = makeFileReference(
+                    table->getValueStringPool(),
+                    util::utf16ToUtf8(pathData.name) + "." + pathData.extension,
+                    *type,
+                    pathData.config);
+            error |= !table->addResource(resourceName, pathData.config, source.line(0),
+                                         std::move(fileReference));
+        }
+    }
+
+    if (error) {
+        return false;
+    }
+
+    Linker linker(table, resolver);
+    if (!linker.linkAndValidate()) {
+        return false;
+    }
+
+    //
+    // Flatten resource table->
+    //
+    if (table->begin() != table->end()) {
+        BigBuffer buffer(1024);
+        TableFlattener::Options tableOptions;
+        tableOptions.useExtendedChunks = true;
+        TableFlattener flattener(tableOptions);
+        if (!flattener.flatten(&buffer, *table)) {
+            Logger::error()
+                    << "failed to flatten resource table->"
+                    << std::endl;
+            return false;
+        }
+
+        std::ofstream fout(options.output.path, std::ofstream::binary);
+        if (!fout) {
+            Logger::error(options.output)
+                    << strerror(errno)
+                    << "."
+                    << std::endl;
+            return false;
+        }
+
+        if (!util::writeAll(fout, buffer)) {
+            Logger::error(options.output)
+                    << strerror(errno)
+                    << "."
+                    << std::endl;
+            return false;
+        }
+        fout.flush();
+    }
+    return true;
+}
+
+static bool doLink(std::shared_ptr<ResourceTable> table, std::shared_ptr<Resolver> resolver,
+                   const AaptOptions& options) {
+    bool error = false;
+
+    for (const Source& source : options.sources) {
+        error |= !loadBinaryResourceTable(table, source);
+    }
+
+    if (error) {
+        return false;
+    }
+
+    versionStylesForCompat(table);
+
+    Linker linker(table, resolver);
+    if (!linker.linkAndValidate()) {
+        return false;
+    }
+
+    const auto& unresolvedRefs = linker.getUnresolvedReferences();
+    if (!unresolvedRefs.empty()) {
+        for (const auto& entry : unresolvedRefs) {
+            for (const auto& source : entry.second) {
+                Logger::error(source)
+                        << "unresolved symbol '"
+                        << entry.first
+                        << "'."
+                        << std::endl;
+            }
+        }
+        return false;
+    }
+
+    //
+    // Generate the Java R class.
+    //
+    if (options.generateJavaClass) {
+        Source outPath = options.generateJavaClass.value();
+        if (options.verbose) {
+            Logger::note()
+                    << "writing symbols to "
+                    << outPath
+                    << "."
+                    << std::endl;
+        }
+
+        for (std::string& part : util::split(util::utf16ToUtf8(table->getPackage()), '.')) {
+            appendPath(&outPath.path, part);
+        }
+
+        if (!mkdirs(outPath.path)) {
+            Logger::error(outPath) << strerror(errno) << std::endl;
+            return false;
+        }
+
+        appendPath(&outPath.path, "R.java");
+
+        std::ofstream fout(outPath.path);
+        if (!fout) {
+            Logger::error(outPath) << strerror(errno) << std::endl;
+            return false;
+        }
+
+        JavaClassGenerator generator(table, JavaClassGenerator::Options{});
+        if (!generator.generate(fout)) {
+            Logger::error(outPath)
+                    << generator.getError()
+                    << "."
+                    << std::endl;
+            return false;
+        }
+    }
+
+    //
+    // Flatten resource table.
+    //
+    if (table->begin() != table->end()) {
+        BigBuffer buffer(1024);
+        TableFlattener::Options tableOptions;
+        tableOptions.useExtendedChunks = false;
+        TableFlattener flattener(tableOptions);
+        if (!flattener.flatten(&buffer, *table)) {
+            Logger::error()
+                    << "failed to flatten resource table->"
+                    << std::endl;
+            return false;
+        }
+
+        if (options.verbose) {
+            Logger::note()
+                    << "Final resource table size="
+                    << util::formatSize(buffer.size())
+                    << std::endl;
+        }
+
+        std::ofstream fout(options.output.path, std::ofstream::binary);
+        if (!fout) {
+            Logger::error(options.output)
+                    << strerror(errno)
+                    << "."
+                    << std::endl;
+            return false;
+        }
+
+        if (!util::writeAll(fout, buffer)) {
+            Logger::error(options.output)
+                    << strerror(errno)
+                    << "."
+                    << std::endl;
+            return false;
+        }
+        fout.flush();
+    }
+    return true;
+}
+
+static bool doCompile(std::shared_ptr<ResourceTable> table, std::shared_ptr<Resolver> resolver,
+                      const AaptOptions& options) {
+    std::queue<CompileXml> xmlCompileQueue;
+
+    for (const Source& source : options.sources) {
+        Maybe<ResourcePathData> maybePathData = extractResourcePathData(source);
+        if (!maybePathData) {
+            return false;
+        }
+
+        ResourcePathData& pathData = maybePathData.value();
+        const ResourceType* type = parseResourceType(pathData.resourceDir);
+        if (!type) {
+            Logger::error(source)
+                    << "invalid resource type '"
+                    << pathData.resourceDir
+                    << "'."
+                    << std::endl;
+            return false;
+        }
+
+        ResourceName resourceName = { table->getPackage(), *type, pathData.name };
+        if (pathData.extension == "xml") {
+            xmlCompileQueue.push(CompileXml{
+                    source,
+                    resourceName,
+                    pathData.config
+            });
+        } else {
+            // TODO(adamlesinski): Handle images here.
+        }
+    }
+
+    bool error = false;
+    while (!xmlCompileQueue.empty()) {
+        const CompileXml& item = xmlCompileQueue.front();
+
+        // Create the output path from the resource name.
+        std::stringstream outputPath;
+        outputPath << item.name.type;
+        if (item.config != ConfigDescription{}) {
+            outputPath << "-" << item.config.toString();
+        }
+
+        Source outSource = options.output;
+        appendPath(&outSource.path, "res");
+        appendPath(&outSource.path, outputPath.str());
+
+        if (!mkdirs(outSource.path)) {
+            Logger::error(outSource) << strerror(errno) << std::endl;
+            return false;
+        }
+
+        appendPath(&outSource.path, util::utf16ToUtf8(item.name.entry) + ".xml");
+
+        if (options.verbose) {
+            Logger::note(outSource) << "compiling XML file." << std::endl;
+        }
+
+        error |= !compileXml(resolver, item, outSource, &xmlCompileQueue);
+        xmlCompileQueue.pop();
+    }
+    return !error;
+}
+
+int main(int argc, char** argv) {
+    Logger::setLog(std::make_shared<Log>(std::cerr, std::cerr));
+
+    std::vector<StringPiece> args;
+    args.reserve(argc - 1);
+    for (int i = 1; i < argc; i++) {
+        args.emplace_back(argv[i], strlen(argv[i]));
+    }
+
+    if (args.empty()) {
+        Logger::error() << "no command specified." << std::endl;
+        return 1;
+    }
+
+    AaptOptions options;
+
+    // Check the command we're running.
+    const StringPiece& command = args.front();
+    if (command == "package") {
+        if (!prepareLegacy(std::begin(args) + 1, std::end(args), options)) {
+            return 1;
+        }
+    } else if (command == "collect") {
+        if (!prepareCollect(std::begin(args) + 1, std::end(args), options)) {
+            return 1;
+        }
+    } else if (command == "link") {
+        if (!prepareLink(std::begin(args) + 1, std::end(args), options)) {
+            return 1;
+        }
+    } else if (command == "compile") {
+        if (!prepareCompile(std::begin(args) + 1, std::end(args), options)) {
+            return 1;
+        }
+    } else {
+        Logger::error() << "unknown command '" << command << "'." << std::endl;
+        return 1;
+    }
+
+    //
+    // Verify we have some common options set.
+    //
+
+    if (options.sources.empty()) {
+        Logger::error() << "no sources specified." << std::endl;
+        return false;
+    }
+
+    if (options.output.path.empty()) {
+        Logger::error() << "no output directory specified." << std::endl;
+        return false;
+    }
+
+    if (options.appInfo.package.empty()) {
+        Logger::error() << "no package name specified." << std::endl;
+        return false;
+    }
+
+
+    //
+    // Every phase needs a resource table and a resolver/linker.
+    //
+
+    std::shared_ptr<ResourceTable> table = std::make_shared<ResourceTable>();
+    table->setPackage(options.appInfo.package);
+    if (options.appInfo.package == u"android") {
+        table->setPackageId(0x01);
+    } else {
+        table->setPackageId(0x7f);
+    }
+
+    //
+    // Load the included libraries.
+    //
+    std::shared_ptr<android::AssetManager> libraries = std::make_shared<android::AssetManager>();
+    for (const Source& source : options.libraries) {
+        if (util::stringEndsWith(source.path, ".arsc")) {
+            // We'll process these last so as to avoid a cookie issue.
+            continue;
+        }
+
+        int32_t cookie;
+        if (!libraries->addAssetPath(android::String8(source.path.data()), &cookie)) {
+            Logger::error(source) << "failed to load library." << std::endl;
+            return false;
+        }
+    }
+
+    for (const Source& source : options.libraries) {
+        if (!util::stringEndsWith(source.path, ".arsc")) {
+            // We've already processed this.
+            continue;
+        }
+
+        // Dirty hack but there is no other way to get a
+        // writeable ResTable.
+        if (!loadResTable(const_cast<android::ResTable*>(&libraries->getResources(false)),
+                          source)) {
+            return false;
+        }
+    }
+
+    // Make the resolver that will cache IDs for us.
+    std::shared_ptr<Resolver> resolver = std::make_shared<Resolver>(table, libraries);
+
+    //
+    // Dispatch to the real phase here.
+    //
+
+    bool result = true;
+    switch (options.phase) {
+        case AaptOptions::Phase::LegacyFull:
+            result = doLegacy(table, resolver, options);
+            break;
+
+        case AaptOptions::Phase::Collect:
+            result = doCollect(table, resolver, options);
+            break;
+
+        case AaptOptions::Phase::Link:
+            result = doLink(table, resolver, options);
+            break;
+
+        case AaptOptions::Phase::Compile:
+            result = doCompile(table, resolver, options);
+            break;
+    }
+
+    if (!result) {
+        Logger::error()
+                << "aapt exiting with failures."
+                << std::endl;
+        return 1;
+    }
+    return 0;
+}