summaryrefslogtreecommitdiffstats
path: root/tools/aapt2/Main.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'tools/aapt2/Main.cpp')
-rw-r--r--tools/aapt2/Main.cpp1152
1 files changed, 1152 insertions, 0 deletions
diff --git a/tools/aapt2/Main.cpp b/tools/aapt2/Main.cpp
new file mode 100644
index 0000000..3377f07
--- /dev/null
+++ b/tools/aapt2/Main.cpp
@@ -0,0 +1,1152 @@
+/*
+ * 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 "BinaryXmlPullParser.h"
+#include "BindingXmlPullParser.h"
+#include "Files.h"
+#include "Flag.h"
+#include "JavaClassGenerator.h"
+#include "Linker.h"
+#include "ManifestParser.h"
+#include "ManifestValidator.h"
+#include "NameMangler.h"
+#include "Png.h"
+#include "ResourceParser.h"
+#include "ResourceTable.h"
+#include "ResourceTableResolver.h"
+#include "ResourceValues.h"
+#include "SdkConstants.h"
+#include "SourceXmlPullParser.h"
+#include "StringPiece.h"
+#include "TableFlattener.h"
+#include "Util.h"
+#include "XmlFlattener.h"
+#include "ZipFile.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>
+#include <unordered_set>
+#include <utils/Errors.h>
+
+constexpr const char* kAaptVersionStr = "2.0-alpha";
+
+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++;
+ }
+}
+
+/**
+ * Collect files from 'root', filtering out any files that do not
+ * match the FileFilter 'filter'.
+ */
+bool walkTree(const Source& root, const FileFilter& filter,
+ std::vector<Source>* outEntries) {
+ bool error = false;
+
+ for (const std::string& dirName : listFiles(root.path)) {
+ std::string dir = root.path;
+ 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->push_back(Source{ file });
+ }
+ }
+ return !error;
+}
+
+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(const 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(
+ &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(),
+ 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();
+ }
+ }
+ }
+}
+
+struct CompileItem {
+ Source source;
+ ResourceName name;
+ ConfigDescription config;
+ std::string extension;
+};
+
+struct LinkItem {
+ Source source;
+ ResourceName name;
+ ConfigDescription config;
+ std::string originalPath;
+ ZipFile* apk;
+ std::u16string originalPackage;
+};
+
+template <typename TChar>
+static BasicStringPiece<TChar> getExtension(const BasicStringPiece<TChar>& str) {
+ auto iter = std::find(str.begin(), str.end(), static_cast<TChar>('.'));
+ if (iter == str.end()) {
+ return BasicStringPiece<TChar>();
+ }
+ size_t offset = (iter - str.begin()) + 1;
+ return str.substr(offset, str.size() - offset);
+}
+
+
+
+std::string buildFileReference(const ResourceNameRef& name, const ConfigDescription& config,
+ const StringPiece& extension) {
+ std::stringstream path;
+ path << "res/" << name.type;
+ if (config != ConfigDescription{}) {
+ path << "-" << config;
+ }
+ path << "/" << util::utf16ToUtf8(name.entry);
+ if (!extension.empty()) {
+ path << "." << extension;
+ }
+ return path.str();
+}
+
+std::string buildFileReference(const CompileItem& item) {
+ return buildFileReference(item.name, item.config, item.extension);
+}
+
+std::string buildFileReference(const LinkItem& item) {
+ return buildFileReference(item.name, item.config, getExtension<char>(item.originalPath));
+}
+
+bool addFileReference(const std::shared_ptr<ResourceTable>& table, const CompileItem& item) {
+ StringPool& pool = table->getValueStringPool();
+ StringPool::Ref ref = pool.makeRef(util::utf8ToUtf16(buildFileReference(item)),
+ StringPool::Context{ 0, item.config });
+ return table->addResource(item.name, item.config, item.source.line(0),
+ util::make_unique<FileReference>(ref));
+}
+
+struct AaptOptions {
+ enum class Phase {
+ Link,
+ Compile,
+ };
+
+ enum class PackageType {
+ StandardApp,
+ StaticLibrary,
+ };
+
+ // The phase to process.
+ Phase phase;
+
+ // The type of package to produce.
+ PackageType packageType = PackageType::StandardApp;
+
+ // 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;
+
+ // Whether or not to auto-version styles or layouts
+ // referencing attributes defined in a newer SDK
+ // level than the style or layout is defined for.
+ bool versionStylesAndLayouts = true;
+};
+
+
+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;
+ }
+
+ BigBuffer outBuffer(1024);
+
+ // No resolver, since we are not compiling attributes here.
+ XmlFlattener flattener(table, {});
+
+ XmlFlattener::Options xmlOptions;
+ xmlOptions.defaultPackage = table->getPackage();
+
+ if (options.versionStylesAndLayouts) {
+ // We strip attributes that do not belong in this version of the resource.
+ // Non-version qualified resources have an implicit version 1 requirement.
+ 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;
+ }
+
+ if (minStrippedSdk.value() > 0) {
+ // Something was stripped, so let's generate a new file
+ // with the version of the smallest SDK version stripped.
+ CompileItem newWork = item;
+ newWork.config.sdkVersion = minStrippedSdk.value();
+ outQueue->push(newWork);
+ }
+
+ // 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 (binding && !options.bindingOutput.path.empty()) {
+ // We generated a binding xml file, write it out.
+ Source bindingOutput = options.bindingOutput;
+ appendPath(&bindingOutput.path, buildFileReference(item));
+
+ 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;
+ return false;
+ }
+
+ if (!binding->writeToFile(bout)) {
+ Logger::error(bindingOutput) << strerror(errno) << std::endl;
+ return false;
+ }
+ }
+ return true;
+}
+
+bool linkXml(const AaptOptions& options, const std::shared_ptr<IResolver>& 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::shared_ptr<XmlPullParser> xmlParser = std::make_shared<BinaryXmlPullParser>(tree);
+
+ BigBuffer outBuffer(1024);
+ XmlFlattener flattener({}, resolver);
+
+ XmlFlattener::Options xmlOptions;
+ xmlOptions.defaultPackage = item.originalPackage;
+ if (!flattener.flatten(item.source, xmlParser, &outBuffer, xmlOptions)) {
+ return false;
+ }
+
+ if (outApk->add(outBuffer, buildFileReference(item).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 compilePng(const AaptOptions& options, const CompileItem& item, ZipFile* outApk) {
+ std::ifstream in(item.source.path, std::ifstream::binary);
+ if (!in) {
+ Logger::error(item.source) << strerror(errno) << std::endl;
+ return false;
+ }
+
+ 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 (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 true;
+}
+
+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;
+}
+
+bool compileManifest(const AaptOptions& options,
+ const std::shared_ptr<ResourceTableResolver>& resolver, ZipFile* outApk) {
+ if (options.verbose) {
+ Logger::note(options.manifest) << "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);
+
+ XmlFlattener::Options xmlOptions;
+ xmlOptions.defaultPackage = options.appInfo.package;
+ if (!flattener.flatten(options.manifest, xmlParser, &outBuffer, xmlOptions)) {
+ return false;
+ }
+
+ std::unique_ptr<uint8_t[]> data = util::copy(outBuffer);
+
+ android::ResXMLTree tree;
+ if (tree.setTo(data.get(), outBuffer.size(), false) != android::NO_ERROR) {
+ return false;
+ }
+
+ ManifestValidator validator(resolver->getResTable());
+ if (!validator.validate(options.manifest, &tree)) {
+ return false;
+ }
+
+ 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;
+}
+
+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) {
+ Logger::error(source) << strerror(errno) << std::endl;
+ return false;
+ }
+
+ std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<SourceXmlPullParser>(in);
+ ResourceParser parser(table, source, 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
+ */
+static Maybe<ResourcePathData> extractResourcePathData(const Source& source) {
+ // TODO(adamlesinski): Use Windows path separator on windows.
+ 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
+ };
+}
+
+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;
+ }
+
+ if (options.verbose) {
+ Logger::note() << "Final resource table size=" << util::formatSize(buffer.size())
+ << 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;
+}
+
+/**
+ * For each FileReference in the table, adds a LinkItem to the link queue for processing.
+ */
+static void addApkFilesToLinkQueue(const std::u16string& package, const Source& source,
+ const std::shared_ptr<ResourceTable>& table,
+ const std::unique_ptr<ZipFile>& apk,
+ std::queue<LinkItem>* outLinkQueue) {
+ bool mangle = package != table->getPackage();
+ for (auto& type : *table) {
+ for (auto& entry : type->entries) {
+ ResourceName name = { package, type->type, entry->name };
+ if (mangle) {
+ NameMangler::mangle(table->getPackage(), &name.entry);
+ }
+
+ for (auto& value : entry->values) {
+ visitFunc<FileReference>(*value.value, [&](FileReference& ref) {
+ std::string pathUtf8 = util::utf16ToUtf8(*ref.path);
+ Source newSource = source;
+ newSource.path += "/";
+ newSource.path += pathUtf8;
+ outLinkQueue->push(LinkItem{
+ newSource, name, value.config, pathUtf8, apk.get(),
+ table->getPackage() });
+ // Now rewrite the file path.
+ if (mangle) {
+ ref.path = table->getValueStringPool().makeRef(util::utf8ToUtf16(
+ buildFileReference(name, value.config,
+ getExtension<char>(pathUtf8))));
+ }
+ });
+ }
+ }
+ }
+}
+
+static constexpr int kOpenFlags = ZipFile::kOpenCreate | ZipFile::kOpenTruncate |
+ ZipFile::kOpenReadWrite;
+
+struct DeleteMalloc {
+ void operator()(void* ptr) {
+ free(ptr);
+ }
+};
+
+struct StaticLibraryData {
+ Source source;
+ std::unique_ptr<ZipFile> apk;
+};
+
+bool link(const AaptOptions& options, const std::shared_ptr<ResourceTable>& outTable,
+ const std::shared_ptr<ResourceTableResolver>& resolver) {
+ std::map<std::shared_ptr<ResourceTable>, StaticLibraryData> 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;
+ }
+
+ std::shared_ptr<ResourceTable> table = std::make_shared<ResourceTable>();
+
+ ZipEntry* entry = zipFile->getEntryByName("resources.arsc");
+ if (!entry) {
+ Logger::error(source) << "missing 'resources.arsc'." << std::endl;
+ return false;
+ }
+
+ std::unique_ptr<void, DeleteMalloc> uncompressedData = std::unique_ptr<void, DeleteMalloc>(
+ zipFile->uncompress(entry));
+ assert(uncompressedData);
+
+ BinaryResourceParser parser(table, resolver, source, uncompressedData.get(),
+ entry->getUncompressedLen());
+ if (!parser.parse()) {
+ return false;
+ }
+
+ // Keep track of where this table came from.
+ apkFiles[table] = StaticLibraryData{ source, std::move(zipFile) };
+
+ // Add the package to the set of linked packages.
+ linkedPackages.insert(table->getPackage());
+ }
+
+ std::queue<LinkItem> linkQueue;
+ for (auto& p : apkFiles) {
+ const std::shared_ptr<ResourceTable>& inTable = p.first;
+
+ // Collect all FileReferences and add them to the queue for processing.
+ addApkFilesToLinkQueue(options.appInfo.package, p.second.source, inTable, p.second.apk,
+ &linkQueue);
+
+ // Merge the tables.
+ if (!outTable->merge(std::move(*inTable))) {
+ return false;
+ }
+ }
+
+ {
+ // Now that everything is merged, let's link it.
+ Linker linker(outTable, resolver);
+ if (!linker.linkAndValidate()) {
+ return false;
+ }
+
+ // Verify that all symbols exist.
+ 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;
+ }
+ }
+
+ // 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;
+ }
+
+ if (!compileManifest(options, resolver, &outApk)) {
+ return false;
+ }
+
+ for (; !linkQueue.empty(); linkQueue.pop()) {
+ const LinkItem& item = linkQueue.front();
+
+ assert(!item.originalPackage.empty());
+ ZipEntry* entry = item.apk->getEntryByName(item.originalPath.data());
+ if (!entry) {
+ Logger::error(item.source) << "failed to find '" << item.originalPath << "'."
+ << std::endl;
+ return false;
+ }
+
+ if (util::stringEndsWith<char>(item.originalPath, ".xml")) {
+ void* uncompressedData = item.apk->uncompress(entry);
+ assert(uncompressedData);
+
+ if (!linkXml(options, resolver, item, uncompressedData, entry->getUncompressedLen(),
+ &outApk)) {
+ Logger::error(options.output) << "failed to link '" << item.originalPath << "'."
+ << std::endl;
+ return false;
+ }
+ } else {
+ if (outApk.add(item.apk, entry, buildFileReference(item).data(), 0, nullptr) !=
+ android::NO_ERROR) {
+ Logger::error(options.output) << "failed to copy '" << item.originalPath << "'."
+ << std::endl;
+ return false;
+ }
+ }
+ }
+
+ // Generate the Java class file.
+ if (options.generateJavaClass) {
+ JavaClassGenerator::Options javaOptions;
+ if (options.packageType == AaptOptions::PackageType::StaticLibrary) {
+ javaOptions.useFinal = false;
+ }
+ JavaClassGenerator generator(outTable, javaOptions);
+
+ for (const std::u16string& package : linkedPackages) {
+ Source outPath = options.generateJavaClass.value();
+
+ // 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);
+ }
+
+ if (!mkdirs(outPath.path)) {
+ Logger::error(outPath) << strerror(errno) << std::endl;
+ return false;
+ }
+
+ appendPath(&outPath.path, "R.java");
+
+ 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;
+ }
+ }
+ }
+
+ outTable->getValueStringPool().prune();
+ outTable->getValueStringPool().sort(
+ [](const StringPool::Entry& a, const StringPool::Entry& b) -> bool {
+ if (a.context.priority < b.context.priority) {
+ return true;
+ }
+
+ if (a.context.priority > b.context.priority) {
+ return false;
+ }
+ return a.value < b.value;
+ });
+
+
+ // Flatten the resource table.
+ TableFlattener::Options flattenerOptions;
+ if (options.packageType == AaptOptions::PackageType::StaticLibrary) {
+ flattenerOptions.useExtendedChunks = true;
+ }
+
+ 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<IResolver>& 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.
+ if (options.versionStylesAndLayouts) {
+ 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;
+}
+
+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);
+}
+
+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 << std::endl;
+ printCommandsAndDie();
+ }
+
+ const StringPiece command(argv[1]);
+ argc -= 2;
+ argv += 2;
+
+ AaptOptions options;
+
+ if (command == "--version" || command == "version") {
+ std::cout << kAaptVersionStr << std::endl;
+ exit(0);
+ } else if (command == "link") {
+ options.phase = AaptOptions::Phase::Link;
+ } else if (command == "compile") {
+ options.phase = AaptOptions::Phase::Compile;
+ } else {
+ std::cerr << "invalid command '" << command << "'." << std::endl << std::endl;
+ printCommandsAndDie();
+ }
+
+ bool isStaticLib = false;
+ if (options.phase == AaptOptions::Phase::Compile) {
+ flag::requiredFlag("--package", "Android package name",
+ [&options](const StringPiece& arg) {
+ 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::optionalSwitch("--no-version", "Disables automatic style and layout versioning",
+ false, &options.versionStylesAndLayouts);
+
+ } else if (options.phase == AaptOptions::Phase::Link) {
+ flag::requiredFlag("--manifest", "AndroidManifest.xml of your app",
+ [&options](const StringPiece& arg) {
+ options.manifest = Source{ arg.toString() };
+ });
+
+ flag::optionalFlag("-I", "add an Android APK to link against",
+ [&options](const StringPiece& arg) {
+ options.libraries.push_back(Source{ arg.toString() });
+ });
+
+ flag::optionalFlag("--java", "directory in which to generate R.java",
+ [&options](const StringPiece& arg) {
+ options.generateJavaClass = Source{ arg.toString() };
+ });
+ flag::optionalSwitch("--static-lib", "generate a static Android library", true,
+ &isStaticLib);
+ }
+
+ // Common flags for all steps.
+ flag::requiredFlag("-o", "Output path", [&options](const StringPiece& arg) {
+ options.output = Source{ arg.toString() };
+ });
+
+ bool help = false;
+ flag::optionalSwitch("-v", "enables verbose logging", true, &options.verbose);
+ flag::optionalSwitch("-h", "displays this help menu", true, &help);
+
+ // Build the command string for output (eg. "aapt2 compile").
+ std::string fullCommand = "aapt2";
+ fullCommand += " ";
+ fullCommand += command.toString();
+
+ // Actually read the command line flags.
+ flag::parse(argc, argv, fullCommand);
+
+ if (help) {
+ flag::usageAndDie(fullCommand);
+ }
+
+ if (isStaticLib) {
+ options.packageType = AaptOptions::PackageType::StaticLibrary;
+ }
+
+ // Copy all the remaining arguments.
+ for (const std::string& arg : flag::getArgs()) {
+ options.input.push_back(Source{ arg });
+ }
+ return options;
+}
+
+int main(int argc, char** argv) {
+ Logger::setLog(std::make_shared<Log>(std::cerr, std::cerr));
+ AaptOptions options = prepareArgs(argc, argv);
+
+ // If we specified a manifest, go ahead and load the package name from the manifest.
+ if (!options.manifest.path.empty()) {
+ if (!loadAppInfo(options.manifest, &options.appInfo)) {
+ return false;
+ }
+ }
+
+ // Verify we have some common options set.
+ if (options.appInfo.package.empty()) {
+ Logger::error() << "no package name specified." << std::endl;
+ return false;
+ }
+
+ // Every phase needs a resource table.
+ 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<char>(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<char>(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<ResourceTableResolver> resolver = std::make_shared<ResourceTableResolver>(
+ table, libraries);
+
+ 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;
+}