AAPT2: Add library support

Change-Id: I307f56d9631784ab29ee4156d94886f9b2f25b30
diff --git a/tools/aapt2/Main.cpp b/tools/aapt2/Main.cpp
index 87127fd..b3e2768 100644
--- a/tools/aapt2/Main.cpp
+++ b/tools/aapt2/Main.cpp
@@ -17,6 +17,7 @@
 #include "AppInfo.h"
 #include "BigBuffer.h"
 #include "BinaryResourceParser.h"
+#include "BinaryXmlPullParser.h"
 #include "BindingXmlPullParser.h"
 #include "Files.h"
 #include "Flag.h"
@@ -34,6 +35,7 @@
 #include "TableFlattener.h"
 #include "Util.h"
 #include "XmlFlattener.h"
+#include "ZipFile.h"
 
 #include <algorithm>
 #include <androidfw/AssetManager.h>
@@ -44,6 +46,7 @@
 #include <iostream>
 #include <sstream>
 #include <sys/stat.h>
+#include <unordered_set>
 #include <utils/Errors.h>
 
 using namespace aapt;
@@ -96,17 +99,6 @@
     }
 }
 
-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'.
@@ -148,30 +140,6 @@
     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) {
@@ -195,7 +163,7 @@
     return result;
 }
 
-void versionStylesForCompat(std::shared_ptr<ResourceTable> table) {
+void versionStylesForCompat(const std::shared_ptr<ResourceTable>& table) {
     for (auto& type : *table) {
         if (type->type != ResourceType::kStyle) {
             continue;
@@ -251,10 +219,12 @@
                                 {},
 
                                 // Create a copy of the original style.
-                                std::unique_ptr<Value>(configValue.value->clone())
+                                std::unique_ptr<Value>(configValue.value->clone(
+                                            &table->getValueStringPool()))
                         };
 
                         Style& newStyle = static_cast<Style&>(*value.value);
+                        newStyle.weak = true;
 
                         // Move the recorded stripped attributes into this new style.
                         std::move(stripped.begin(), stripped.end(),
@@ -285,59 +255,6 @@
     }
 }
 
-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 parser(in);
-    while (XmlPullParser::isGoodEvent(parser.next())) {
-        if (parser.getEvent() != XmlPullParser::Event::kStartElement) {
-            continue;
-        }
-
-        const auto endIter = parser.endAttributes();
-        for (auto iter = parser.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(parser.getLineNumber()),
-                                   util::make_unique<Id>());
-            }
-        }
-    }
-
-    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 CompileItem {
     Source source;
     ResourceName name;
@@ -345,28 +262,91 @@
     std::string extension;
 };
 
-bool compileXml(std::shared_ptr<Resolver> resolver, const CompileItem& item,
-                const Source& outputSource, std::queue<CompileItem>* queue) {
+struct LinkItem {
+    Source source;
+    std::string apkPath;
+};
+
+std::string buildFileReference(const CompileItem& item) {
+    std::stringstream path;
+    path << "res/" << item.name.type;
+    if (item.config != ConfigDescription{}) {
+        path << "-" << item.config;
+    }
+    path << "/" << util::utf16ToUtf8(item.name.entry) + "." + item.extension;
+    return path.str();
+}
+
+bool addFileReference(const std::shared_ptr<ResourceTable>& table, const CompileItem& item) {
+    StringPool& pool = table->getValueStringPool();
+    StringPool::Ref ref = pool.makeRef(util::utf8ToUtf16(buildFileReference(item)));
+    return table->addResource(item.name, item.config, item.source.line(0),
+                              util::make_unique<FileReference>(ref));
+}
+
+struct AaptOptions {
+    enum class Phase {
+        Link,
+        Compile,
+    };
+
+    // The phase to process.
+    Phase phase;
+
+    // Details about the app.
+    AppInfo appInfo;
+
+    // The location of the manifest file.
+    Source manifest;
+
+    // The APK files to link.
+    std::vector<Source> input;
+
+    // The libraries these files may reference.
+    std::vector<Source> libraries;
+
+    // Output path. This can be a directory or file
+    // depending on the phase.
+    Source output;
+
+    // Directory in which to write binding xml files.
+    Source bindingOutput;
+
+    // Directory to in which to generate R.java.
+    Maybe<Source> generateJavaClass;
+
+    // Whether to output verbose details about
+    // compilation.
+    bool verbose = false;
+};
+
+bool compileXml(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table,
+                const CompileItem& item, std::queue<CompileItem>* outQueue, ZipFile* outApk) {
     std::ifstream in(item.source.path, std::ifstream::binary);
     if (!in) {
         Logger::error(item.source) << strerror(errno) << std::endl;
         return false;
     }
 
-    std::shared_ptr<BindingXmlPullParser> binding;
-    std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<SourceXmlPullParser>(in);
-    if (item.name.type == ResourceType::kLayout) {
-        binding = std::make_shared<BindingXmlPullParser>(xmlParser);
-        xmlParser = binding;
-    }
-
     BigBuffer outBuffer(1024);
-    XmlFlattener flattener(resolver);
+
+    // No resolver, since we are not compiling attributes here.
+    XmlFlattener flattener(table, {});
 
     // 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);
+    XmlFlattener::Options xmlOptions;
+    xmlOptions.maxSdkAttribute = item.config.sdkVersion ? item.config.sdkVersion : 1;
+
+    std::shared_ptr<BindingXmlPullParser> binding;
+    std::shared_ptr<XmlPullParser> parser = std::make_shared<SourceXmlPullParser>(in);
+    if (item.name.type == ResourceType::kLayout) {
+        // Layouts may have defined bindings, so we need to make sure they get processed.
+        binding = std::make_shared<BindingXmlPullParser>(parser);
+        parser = binding;
+    }
+
+    Maybe<size_t> minStrippedSdk = flattener.flatten(item.source, parser, &outBuffer, xmlOptions);
     if (!minStrippedSdk) {
         return false;
     }
@@ -376,24 +356,29 @@
         // with the version of the smallest SDK version stripped.
         CompileItem newWork = item;
         newWork.config.sdkVersion = minStrippedSdk.value();
-        queue->push(newWork);
+        outQueue->push(newWork);
     }
 
-    std::ofstream out(outputSource.path, std::ofstream::binary);
-    if (!out) {
-        Logger::error(outputSource) << strerror(errno) << std::endl;
+    // Write the resulting compiled XML file to the output APK.
+    if (outApk->add(outBuffer, buildFileReference(item).data(), ZipEntry::kCompressStored,
+                nullptr) != android::NO_ERROR) {
+        Logger::error(options.output) << "failed to write compiled '" << item.source << "' to apk."
+                                      << std::endl;
         return false;
     }
 
-    if (!util::writeAll(out, outBuffer)) {
-        Logger::error(outputSource) << strerror(errno) << std::endl;
-        return false;
-    }
+    if (binding && !options.bindingOutput.path.empty()) {
+        // We generated a binding xml file, write it out.
+        Source bindingOutput = options.bindingOutput;
+        appendPath(&bindingOutput.path, buildFileReference(item));
 
-    if (binding) {
-        // We generated a binding xml file, write it out beside the output file.
-        Source bindingOutput = outputSource;
-        bindingOutput.path += ".bind.xml";
+        if (!mkdirs(bindingOutput.path)) {
+            Logger::error(bindingOutput) << strerror(errno) << std::endl;
+            return false;
+        }
+
+        appendPath(&bindingOutput.path, "bind.xml");
+
         std::ofstream bout(bindingOutput.path);
         if (!bout) {
             Logger::error(bindingOutput) << strerror(errno) << std::endl;
@@ -408,100 +393,68 @@
     return true;
 }
 
-bool compilePng(const Source& source, const Source& output) {
-    std::ifstream in(source.path, std::ifstream::binary);
-    if (!in) {
-        Logger::error(source) << strerror(errno) << std::endl;
+bool linkXml(const AaptOptions& options, const std::shared_ptr<Resolver>& resolver,
+             const LinkItem& item, const void* data, size_t dataLen, ZipFile* outApk) {
+    std::shared_ptr<android::ResXMLTree> tree = std::make_shared<android::ResXMLTree>();
+    if (tree->setTo(data, dataLen, false) != android::NO_ERROR) {
         return false;
     }
 
-    std::ofstream out(output.path, std::ofstream::binary);
-    if (!out) {
-        Logger::error(output) << strerror(errno) << std::endl;
+    std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<BinaryXmlPullParser>(tree);
+
+    BigBuffer outBuffer(1024);
+    XmlFlattener flattener({}, resolver);
+    if (!flattener.flatten(item.source, xmlParser, &outBuffer, {})) {
         return false;
     }
 
-    std::string err;
-    Png png;
-    if (!png.process(source, in, out, {}, &err)) {
-        Logger::error(source) << err << std::endl;
+    if (outApk->add(outBuffer, item.apkPath.data(), ZipEntry::kCompressDeflated, nullptr) !=
+            android::NO_ERROR) {
+        Logger::error(options.output) << "failed to write linked file '" << item.source
+                                      << "' to apk." << std::endl;
         return false;
     }
     return true;
 }
 
-bool copyFile(const Source& source, const Source& output) {
-    std::ifstream in(source.path, std::ifstream::binary);
+bool compilePng(const AaptOptions& options, const CompileItem& item, ZipFile* outApk) {
+    std::ifstream in(item.source.path, std::ifstream::binary);
     if (!in) {
-        Logger::error(source) << strerror(errno) << std::endl;
+        Logger::error(item.source) << strerror(errno) << std::endl;
         return false;
     }
 
-    std::ofstream out(output.path, std::ofstream::binary);
-    if (!out) {
-        Logger::error(output) << strerror(errno) << std::endl;
+    BigBuffer outBuffer(4096);
+    std::string err;
+    Png png;
+    if (!png.process(item.source, in, &outBuffer, {}, &err)) {
+        Logger::error(item.source) << err << std::endl;
         return false;
     }
 
-    if (out << in.rdbuf()) {
-        Logger::error(output) << strerror(errno) << std::endl;
-        return true;
+    if (outApk->add(outBuffer, buildFileReference(item).data(), ZipEntry::kCompressStored,
+                nullptr) != android::NO_ERROR) {
+        Logger::error(options.output) << "failed to write compiled '" << item.source
+                                      << "' to apk." << std::endl;
+        return false;
     }
-    return false;
+    return true;
 }
 
-struct AaptOptions {
-    enum class Phase {
-        Full,
-        Collect,
-        Link,
-        Compile,
-        Manifest
-    };
+bool copyFile(const AaptOptions& options, const CompileItem& item, ZipFile* outApk) {
+    if (outApk->add(item.source.path.data(), buildFileReference(item).data(),
+                ZipEntry::kCompressStored, nullptr) != android::NO_ERROR) {
+        Logger::error(options.output) << "failed to copy file '" << item.source << "' to apk."
+                                      << std::endl;
+        return false;
+    }
+    return true;
+}
 
-    // The phase to process.
-    Phase phase;
-
-    // Details about the app.
-    AppInfo appInfo;
-
-    // The location of the manifest file.
-    Source manifest;
-
-    // The source directories to walk and find resource files.
-    std::vector<Source> sourceDirs;
-
-    // The resource files to process and collect.
-    std::vector<Source> collectFiles;
-
-    // The binary table files to link.
-    std::vector<Source> linkFiles;
-
-    // The resource files to compile.
-    std::vector<Source> compileFiles;
-
-    // The libraries these files may reference.
-    std::vector<Source> libraries;
-
-    // Output path. This can be a directory or file
-    // depending on the phase.
-    Source output;
-
-    // Directory to in which to generate R.java.
-    Maybe<Source> generateJavaClass;
-
-    // Whether to output verbose details about
-    // compilation.
-    bool verbose = false;
-};
-
-bool compileAndroidManifest(const std::shared_ptr<Resolver>& resolver,
-                            const AaptOptions& options) {
-    Source outSource = options.output;
-    appendPath(&outSource.path, "AndroidManifest.xml");
-
+bool compileManifest(const AaptOptions& options, const std::shared_ptr<Resolver>& resolver,
+                     ZipFile* outApk) {
     if (options.verbose) {
-        Logger::note(outSource) << "compiling AndroidManifest.xml." << std::endl;
+        Logger::note(options.manifest) << "compiling AndroidManifest.xml." << std::endl;
     }
 
     std::ifstream in(options.manifest.path, std::ifstream::binary);
@@ -512,23 +465,16 @@
 
     BigBuffer outBuffer(1024);
     std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<SourceXmlPullParser>(in);
-    XmlFlattener flattener(resolver);
+    XmlFlattener flattener({}, resolver);
 
-    Maybe<size_t> result = flattener.flatten(options.manifest, xmlParser, &outBuffer,
-                                             XmlFlattener::Options{});
-    if (!result) {
+    if (!flattener.flatten(options.manifest, xmlParser, &outBuffer, {})) {
         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;
-    }
+    std::unique_ptr<uint8_t[]> data = util::copy(outBuffer);
 
     android::ResXMLTree tree;
-    if (tree.setTo(data.get(), outBuffer.size()) != android::NO_ERROR) {
+    if (tree.setTo(data.get(), outBuffer.size(), false) != android::NO_ERROR) {
         return false;
     }
 
@@ -537,14 +483,10 @@
         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;
+    if (outApk->add(data.get(), outBuffer.size(), "AndroidManifest.xml",
+                ZipEntry::kCompressStored, nullptr) != android::NO_ERROR) {
+        Logger::error(options.output) << "failed to write 'AndroidManifest.xml' to apk."
+                                      << std::endl;
         return false;
     }
     return true;
@@ -562,10 +504,20 @@
     return parser.parse(source, pullParser, outInfo);
 }
 
+static void printCommandsAndDie() {
+    std::cerr << "The following commands are supported:" << std::endl << std::endl;
+    std::cerr << "compile       compiles a subset of resources" << std::endl;
+    std::cerr << "link          links together compiled resources and libraries" << std::endl;
+    std::cerr << std::endl;
+    std::cerr << "run aapt2 with one of the commands and the -h flag for extra details."
+              << std::endl;
+    exit(1);
+}
+
 static AaptOptions prepareArgs(int argc, char** argv) {
     if (argc < 2) {
-        std::cerr << "no command specified." << std::endl;
-        exit(1);
+        std::cerr << "no command specified." << std::endl << std::endl;
+        printCommandsAndDie();
     }
 
     const StringPiece command(argv[1]);
@@ -574,32 +526,27 @@
 
     AaptOptions options;
 
-    StringPiece outputDescription = "place output in file";
-    if (command == "package") {
-        options.phase = AaptOptions::Phase::Full;
-        outputDescription = "place output in directory";
-    } else if (command == "collect") {
-        options.phase = AaptOptions::Phase::Collect;
-    } else if (command == "link") {
+    if (command == "link") {
         options.phase = AaptOptions::Phase::Link;
     } else if (command == "compile") {
         options.phase = AaptOptions::Phase::Compile;
-        outputDescription = "place output in directory";
-    } else if (command == "manifest") {
-        options.phase = AaptOptions::Phase::Manifest;
-        outputDescription = "place AndroidManifest.xml in directory";
     } else {
-        std::cerr << "invalid command '" << command << "'." << std::endl;
-        exit(1);
+        std::cerr << "invalid command '" << command << "'." << std::endl << std::endl;
+        printCommandsAndDie();
     }
 
-    if (options.phase == AaptOptions::Phase::Full) {
-        flag::requiredFlag("-S", "add a directory in which to find resources",
+    if (options.phase == AaptOptions::Phase::Compile) {
+        flag::requiredFlag("--package", "Android package name",
                 [&options](const StringPiece& arg) {
-                    options.sourceDirs.push_back(Source{ arg.toString() });
+                    options.appInfo.package = util::utf8ToUtf16(arg);
+                });
+        flag::optionalFlag("--binding", "Output directory for binding XML files",
+                [&options](const StringPiece& arg) {
+                    options.bindingOutput = Source{ arg.toString() };
                 });
 
-        flag::requiredFlag("-M", "path to AndroidManifest.xml",
+    } else if (options.phase == AaptOptions::Phase::Link) {
+        flag::requiredFlag("--manifest", "AndroidManifest.xml of your app",
                 [&options](const StringPiece& arg) {
                     options.manifest = Source{ arg.toString() };
                 });
@@ -613,35 +560,16 @@
                 [&options](const StringPiece& arg) {
                     options.generateJavaClass = Source{ arg.toString() };
                 });
-
-    } else {
-        if (options.phase != AaptOptions::Phase::Manifest) {
-            flag::requiredFlag("--package", "Android package name",
-                    [&options](const StringPiece& arg) {
-                        options.appInfo.package = util::utf8ToUtf16(arg);
-                    });
-        }
-
-        if (options.phase != AaptOptions::Phase::Collect) {
-            flag::optionalFlag("-I", "add an Android APK to link against",
-                    [&options](const StringPiece& arg) {
-                        options.libraries.push_back(Source{ arg.toString() });
-                    });
-        }
-
-        if (options.phase == AaptOptions::Phase::Link) {
-            flag::optionalFlag("--java", "directory in which to generate R.java",
-                    [&options](const StringPiece& arg) {
-                        options.generateJavaClass = Source{ arg.toString() };
-                    });
-        }
     }
 
     // Common flags for all steps.
-    flag::requiredFlag("-o", outputDescription, [&options](const StringPiece& arg) {
+    flag::requiredFlag("-o", "Output path", [&options](const StringPiece& arg) {
         options.output = Source{ arg.toString() };
     });
+
+    bool help = false;
     flag::optionalSwitch("-v", "enables verbose logging", &options.verbose);
+    flag::optionalSwitch("-h", "displays this help menu", &help);
 
     // Build the command string for output (eg. "aapt2 compile").
     std::string fullCommand = "aapt2";
@@ -651,28 +579,18 @@
     // Actually read the command line flags.
     flag::parse(argc, argv, fullCommand);
 
+    if (help) {
+        flag::usageAndDie(fullCommand);
+    }
+
     // Copy all the remaining arguments.
-    if (options.phase == AaptOptions::Phase::Collect) {
-        for (const std::string& arg : flag::getArgs()) {
-            options.collectFiles.push_back(Source{ arg });
-        }
-    } else if (options.phase == AaptOptions::Phase::Compile) {
-        for (const std::string& arg : flag::getArgs()) {
-            options.compileFiles.push_back(Source{ arg });
-        }
-    } else if (options.phase == AaptOptions::Phase::Link) {
-        for (const std::string& arg : flag::getArgs()) {
-            options.linkFiles.push_back(Source{ arg });
-        }
-    } else if (options.phase == AaptOptions::Phase::Manifest) {
-        if (!flag::getArgs().empty()) {
-            options.manifest = Source{ flag::getArgs()[0] };
-        }
+    for (const std::string& arg : flag::getArgs()) {
+        options.input.push_back(Source{ arg });
     }
     return options;
 }
 
-static bool collectValues(const std::shared_ptr<ResourceTable>& table, const Source& source,
+static bool compileValues(const std::shared_ptr<ResourceTable>& table, const Source& source,
                           const ConfigDescription& config) {
     std::ifstream in(source.path, std::ifstream::binary);
     if (!in) {
@@ -738,115 +656,91 @@
     };
 }
 
-bool doAll(AaptOptions* options, const std::shared_ptr<ResourceTable>& table,
-           const std::shared_ptr<Resolver>& resolver) {
-    const bool versionStyles = (options->phase == AaptOptions::Phase::Full ||
-            options->phase == AaptOptions::Phase::Link);
-    const bool verifyNoMissingSymbols = (options->phase == AaptOptions::Phase::Full ||
-            options->phase == AaptOptions::Phase::Link);
-    const bool compileFiles = (options->phase == AaptOptions::Phase::Full ||
-            options->phase == AaptOptions::Phase::Compile);
-    const bool flattenTable = (options->phase == AaptOptions::Phase::Full ||
-            options->phase == AaptOptions::Phase::Collect ||
-            options->phase == AaptOptions::Phase::Link);
-    const bool useExtendedChunks = options->phase == AaptOptions::Phase::Collect;
-
-    // Build the output table path.
-    Source outputTable = options->output;
-    if (options->phase == AaptOptions::Phase::Full) {
-        appendPath(&outputTable.path, "resources.arsc");
-    }
-
-    bool error = false;
-    std::queue<CompileItem> compileQueue;
-
-    // If source directories were specified, walk them looking for resource files.
-    if (!options->sourceDirs.empty()) {
-        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:!*~");
-        }
-
-        for (const Source& source : options->sourceDirs) {
-            if (!walkTree(source, fileFilter, &options->collectFiles)) {
-                return false;
-            }
-        }
-    }
-
-    // Load all binary resource tables.
-    for (const Source& source : options->linkFiles) {
-        error |= !loadBinaryResourceTable(table, source);
-    }
-
-    if (error) {
-        return false;
-    }
-
-    // Collect all the resource files.
-    // Need to parse the resource type/config/filename.
-    for (const Source& source : options->collectFiles) {
-        Maybe<ResourcePathData> maybePathData = extractResourcePathData(source);
-        if (!maybePathData) {
+bool writeResourceTable(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table,
+                        const TableFlattener::Options& flattenerOptions, ZipFile* outApk) {
+    if (table->begin() != table->end()) {
+        BigBuffer buffer(1024);
+        TableFlattener flattener(flattenerOptions);
+        if (!flattener.flatten(&buffer, *table)) {
+            Logger::error() << "failed to flatten resource table." << std::endl;
             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, source, pathData.config);
-            continue;
+        if (options.verbose) {
+            Logger::note() << "Final resource table size=" << util::formatSize(buffer.size())
+                           << std::endl;
         }
 
-        const ResourceType* type = parseResourceType(pathData.resourceDir);
-        if (!type) {
-            Logger::error(source) << "invalid resource type '" << pathData.resourceDir << "'."
-                                  << std::endl;
+        if (outApk->add(buffer, "resources.arsc", ZipEntry::kCompressStored, nullptr) !=
+                android::NO_ERROR) {
+            Logger::note(options.output) << "failed to store resource table." << std::endl;
+            return false;
+        }
+    }
+    return true;
+}
+
+static constexpr int kOpenFlags = ZipFile::kOpenCreate | ZipFile::kOpenTruncate |
+        ZipFile::kOpenReadWrite;
+
+bool link(const AaptOptions& options, const std::shared_ptr<ResourceTable>& outTable,
+          const std::shared_ptr<Resolver>& resolver) {
+    std::map<std::shared_ptr<ResourceTable>, std::unique_ptr<ZipFile>> apkFiles;
+    std::unordered_set<std::u16string> linkedPackages;
+
+    // Populate the linkedPackages with our own.
+    linkedPackages.insert(options.appInfo.package);
+
+    // Load all APK files.
+    for (const Source& source : options.input) {
+        std::unique_ptr<ZipFile> zipFile = util::make_unique<ZipFile>();
+        if (zipFile->open(source.path.data(), ZipFile::kOpenReadOnly) != android::NO_ERROR) {
+            Logger::error(source) << "failed to open: " << strerror(errno) << std::endl;
             return false;
         }
 
-        ResourceName resourceName = { table->getPackage(), *type, pathData.name };
+        std::shared_ptr<ResourceTable> table = std::make_shared<ResourceTable>();
 
-        // Add the file name to the resource table.
-        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 (pathData.extension == "xml") {
-            error |= !collectXml(table, source, resourceName, pathData.config);
+        ZipEntry* entry = zipFile->getEntryByName("resources.arsc");
+        if (!entry) {
+            Logger::error(source) << "missing 'resources.arsc'." << std::endl;
+            return false;
         }
 
-        compileQueue.push(
-                CompileItem{ source, resourceName, pathData.config, pathData.extension });
+        void* uncompressedData = zipFile->uncompress(entry);
+        assert(uncompressedData);
+
+        BinaryResourceParser parser(table, resolver, source, uncompressedData,
+                                    entry->getUncompressedLen());
+        if (!parser.parse()) {
+            free(uncompressedData);
+            return false;
+        }
+        free(uncompressedData);
+
+        // Keep track of where this table came from.
+        apkFiles[table] = std::move(zipFile);
+
+        // Add the package to the set of linked packages.
+        linkedPackages.insert(table->getPackage());
     }
 
-    if (error) {
-        return false;
+    for (auto& p : apkFiles) {
+        const std::shared_ptr<ResourceTable>& inTable = p.first;
+
+        if (!outTable->merge(std::move(*inTable))) {
+            return false;
+        }
     }
 
-    // Version all styles referencing attributes outside of their specified SDK version.
-    if (versionStyles) {
-        versionStylesForCompat(table);
-    }
+    {
+        // Now that everything is merged, let's link it.
+        Linker linker(outTable, resolver);
+        if (!linker.linkAndValidate()) {
+            return false;
+        }
 
-    // Verify that all references are valid.
-    Linker linker(table, resolver);
-    if (!linker.linkAndValidate()) {
-        return false;
-    }
-
-    // Verify that all symbols exist.
-    if (verifyNoMissingSymbols) {
+        // Verify that all symbols exist.
         const auto& unresolvedRefs = linker.getUnresolvedReferences();
         if (!unresolvedRefs.empty()) {
             for (const auto& entry : unresolvedRefs) {
@@ -859,143 +753,190 @@
         }
     }
 
-    // Compile files.
-    if (compileFiles) {
-        // First process any input compile files.
-        for (const Source& source : options->compileFiles) {
-            Maybe<ResourcePathData> maybePathData = extractResourcePathData(source);
-            if (!maybePathData) {
-                return false;
-            }
-
-            const 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 };
-            compileQueue.push(
-                    CompileItem{ source, resourceName, pathData.config, pathData.extension });
-        }
-
-        // Now process the actual compile queue.
-        for (; !compileQueue.empty(); compileQueue.pop()) {
-            const CompileItem& item = compileQueue.front();
-
-            // Create the output directory path from the resource type and config.
-            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());
-
-            // Make the directory.
-            if (!mkdirs(outSource.path)) {
-                Logger::error(outSource) << strerror(errno) << std::endl;
-                return false;
-            }
-
-            // Add the file name to the directory path.
-            appendPath(&outSource.path, util::utf16ToUtf8(item.name.entry) + "." + item.extension);
-
-            if (item.extension == "xml") {
-                if (options->verbose) {
-                    Logger::note(outSource) << "compiling XML file." << std::endl;
-                }
-
-                error |= !compileXml(resolver, item, outSource, &compileQueue);
-            } else if (item.extension == "png" || item.extension == "9.png") {
-                if (options->verbose) {
-                    Logger::note(outSource) << "compiling png file." << std::endl;
-                }
-
-                error |= !compilePng(item.source, outSource);
-            } else {
-                error |= !copyFile(item.source, outSource);
-            }
-        }
-
-        if (error) {
-            return false;
-        }
+    // Open the output APK file for writing.
+    ZipFile outApk;
+    if (outApk.open(options.output.path.data(), kOpenFlags) != android::NO_ERROR) {
+        Logger::error(options.output) << "failed to open: " << strerror(errno) << std::endl;
+        return false;
     }
 
-    // Compile and validate the AndroidManifest.xml.
-    if (!options->manifest.path.empty()) {
-        if (!compileAndroidManifest(resolver, *options)) {
-            return false;
+    if (!compileManifest(options, resolver, &outApk)) {
+        return false;
+    }
+
+    for (auto& p : apkFiles) {
+        std::unique_ptr<ZipFile>& zipFile = p.second;
+
+        // TODO(adamlesinski): Get list of files to read when processing config filter.
+
+        const int numEntries = zipFile->getNumEntries();
+        for (int i = 0; i < numEntries; i++) {
+            ZipEntry* entry = zipFile->getEntryByIndex(i);
+            assert(entry);
+
+            StringPiece filename = entry->getFileName();
+            if (!util::stringStartsWith<char>(filename, "res/")) {
+                continue;
+            }
+
+            if (util::stringEndsWith<char>(filename, ".xml")) {
+                void* uncompressedData = zipFile->uncompress(entry);
+                assert(uncompressedData);
+
+                LinkItem item = { Source{ filename.toString() }, filename.toString() };
+
+                if (!linkXml(options, resolver, item, uncompressedData,
+                            entry->getUncompressedLen(), &outApk)) {
+                    Logger::error(options.output) << "failed to link '" << filename << "'."
+                                                  << std::endl;
+                    return false;
+                }
+            } else {
+                if (outApk.add(zipFile.get(), entry, 0, nullptr) != android::NO_ERROR) {
+                    Logger::error(options.output) << "failed to copy '" << filename << "'."
+                                                  << std::endl;
+                    return false;
+                }
+            }
         }
     }
 
     // Generate the Java class file.
-    if (options->generateJavaClass) {
-        Source outPath = options->generateJavaClass.value();
-        if (options->verbose) {
-            Logger::note() << "writing symbols to " << outPath << "." << std::endl;
-        }
+    if (options.generateJavaClass) {
+        JavaClassGenerator generator(outTable, {});
 
-        // Build the output directory from the package name.
-        // Eg. com.android.app -> com/android/app
-        const std::string packageUtf8 = util::utf16ToUtf8(table->getPackage());
-        for (StringPiece part : util::tokenize<char>(packageUtf8, '.')) {
-            appendPath(&outPath.path, part);
-        }
+        for (const std::u16string& package : linkedPackages) {
+            Source outPath = options.generateJavaClass.value();
 
-        if (!mkdirs(outPath.path)) {
-            Logger::error(outPath) << strerror(errno) << std::endl;
-            return false;
-        }
+            // Build the output directory from the package name.
+            // Eg. com.android.app -> com/android/app
+            const std::string packageUtf8 = util::utf16ToUtf8(package);
+            for (StringPiece part : util::tokenize<char>(packageUtf8, '.')) {
+                appendPath(&outPath.path, part);
+            }
 
-        appendPath(&outPath.path, "R.java");
+            if (!mkdirs(outPath.path)) {
+                Logger::error(outPath) << strerror(errno) << std::endl;
+                return false;
+            }
 
-        std::ofstream fout(outPath.path);
-        if (!fout) {
-            Logger::error(outPath) << strerror(errno) << std::endl;
-            return false;
-        }
+            appendPath(&outPath.path, "R.java");
 
-        JavaClassGenerator generator(table, {});
-        if (!generator.generate(fout)) {
-            Logger::error(outPath) << generator.getError() << "." << std::endl;
-            return false;
+            if (options.verbose) {
+                Logger::note(outPath) << "writing Java symbols." << std::endl;
+            }
+
+            std::ofstream fout(outPath.path);
+            if (!fout) {
+                Logger::error(outPath) << strerror(errno) << std::endl;
+                return false;
+            }
+
+            if (!generator.generate(package, fout)) {
+                Logger::error(outPath) << generator.getError() << "." << std::endl;
+                return false;
+            }
         }
     }
 
     // Flatten the resource table.
-    if (flattenTable && table->begin() != table->end()) {
-        BigBuffer buffer(1024);
-        TableFlattener::Options tableOptions;
-        tableOptions.useExtendedChunks = useExtendedChunks;
-        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(outputTable.path, std::ofstream::binary);
-        if (!fout) {
-            Logger::error(outputTable) << strerror(errno) << "." << std::endl;
-            return false;
-        }
-
-        if (!util::writeAll(fout, buffer)) {
-            Logger::error(outputTable) << strerror(errno) << "." << std::endl;
-            return false;
-        }
-        fout.flush();
+    TableFlattener::Options flattenerOptions;
+    flattenerOptions.useExtendedChunks = false;
+    if (!writeResourceTable(options, outTable, flattenerOptions, &outApk)) {
+        return false;
     }
+
+    outApk.flush();
+    return true;
+}
+
+bool compile(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table,
+             const std::shared_ptr<Resolver>& resolver) {
+    std::queue<CompileItem> compileQueue;
+    bool error = false;
+
+    // Compile all the resource files passed in on the command line.
+    for (const Source& source : options.input) {
+        // Need to parse the resource type/config/filename.
+        Maybe<ResourcePathData> maybePathData = extractResourcePathData(source);
+        if (!maybePathData) {
+            return false;
+        }
+
+        const ResourcePathData& pathData = maybePathData.value();
+        if (pathData.resourceDir == u"values") {
+            // The file is in the values directory, which means its contents will
+            // go into the resource table.
+            if (options.verbose) {
+                Logger::note(source) << "compiling values." << std::endl;
+            }
+
+            error |= !compileValues(table, source, pathData.config);
+        } else {
+            // The file is in a directory like 'layout' or 'drawable'. Find out
+            // the type.
+            const ResourceType* type = parseResourceType(pathData.resourceDir);
+            if (!type) {
+                Logger::error(source) << "invalid resource type '" << pathData.resourceDir << "'."
+                                      << std::endl;
+                return false;
+            }
+
+            compileQueue.push(CompileItem{
+                    source,
+                    ResourceName{ table->getPackage(), *type, pathData.name },
+                    pathData.config,
+                    pathData.extension
+            });
+        }
+    }
+
+    if (error) {
+        return false;
+    }
+
+    // Version all styles referencing attributes outside of their specified SDK version.
+    versionStylesForCompat(table);
+
+    // Open the output APK file for writing.
+    ZipFile outApk;
+    if (outApk.open(options.output.path.data(), kOpenFlags) != android::NO_ERROR) {
+        Logger::error(options.output) << "failed to open: " << strerror(errno) << std::endl;
+        return false;
+    }
+
+    // Compile each file.
+    for (; !compileQueue.empty(); compileQueue.pop()) {
+        const CompileItem& item = compileQueue.front();
+
+        // Add the file name to the resource table.
+        error |= !addFileReference(table, item);
+
+        if (item.extension == "xml") {
+            error |= !compileXml(options, table, item, &compileQueue, &outApk);
+        } else if (item.extension == "png" || item.extension == "9.png") {
+            error |= !compilePng(options, item, &outApk);
+        } else {
+            error |= !copyFile(options, item, &outApk);
+        }
+    }
+
+    if (error) {
+        return false;
+    }
+
+    // Link and assign resource IDs.
+    Linker linker(table, resolver);
+    if (!linker.linkAndValidate()) {
+        return false;
+    }
+
+    // Flatten the resource table.
+    if (!writeResourceTable(options, table, {}, &outApk)) {
+        return false;
+    }
+
+    outApk.flush();
     return true;
 }
 
@@ -1057,10 +998,16 @@
     // Make the resolver that will cache IDs for us.
     std::shared_ptr<Resolver> resolver = std::make_shared<Resolver>(table, libraries);
 
-    // Do the work.
-    if (!doAll(&options, table, resolver)) {
-        Logger::error() << "aapt exiting with failures." << std::endl;
-        return 1;
+    if (options.phase == AaptOptions::Phase::Compile) {
+        if (!compile(options, table, resolver)) {
+            Logger::error() << "aapt exiting with failures." << std::endl;
+            return 1;
+        }
+    } else if (options.phase == AaptOptions::Phase::Link) {
+        if (!link(options, table, resolver)) {
+            Logger::error() << "aapt exiting with failures." << std::endl;
+            return 1;
+        }
     }
     return 0;
 }