diff options
Diffstat (limited to 'tools/aapt2/Main.cpp')
-rw-r--r-- | tools/aapt2/Main.cpp | 1265 |
1 files changed, 21 insertions, 1244 deletions
diff --git a/tools/aapt2/Main.cpp b/tools/aapt2/Main.cpp index 54a7329359f1..248e7ad73a82 100644 --- a/tools/aapt2/Main.cpp +++ b/tools/aapt2/Main.cpp @@ -14,1262 +14,39 @@ * limitations under the License. */ -#include "AppInfo.h" -#include "BigBuffer.h" -#include "BinaryResourceParser.h" -#include "BindingXmlPullParser.h" -#include "Debug.h" -#include "Files.h" -#include "Flag.h" -#include "JavaClassGenerator.h" -#include "Linker.h" -#include "ManifestMerger.h" -#include "ManifestParser.h" -#include "ManifestValidator.h" -#include "NameMangler.h" -#include "Png.h" -#include "ProguardRules.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 "util/StringPiece.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> +#include <vector> -constexpr const char* kAaptVersionStr = "2.0-alpha"; +namespace aapt { -using namespace aapt; +extern int compile(const std::vector<StringPiece>& args); +extern int link(const std::vector<StringPiece>& args); -/** - * Used with smart pointers to free malloc'ed memory. - */ -struct DeleteMalloc { - void operator()(void* ptr) { - free(ptr); - } -}; - -struct StaticLibraryData { - Source source; - std::unique_ptr<ZipFile> apk; -}; - -/** - * 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; -} - -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); - 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); - - // 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 { - ResourceName name; - ConfigDescription config; - Source source; - std::string extension; -}; - -struct LinkItem { - ResourceName name; - ConfigDescription config; - Source source; - 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)); -} - -template <typename T> -bool addFileReference(const std::shared_ptr<ResourceTable>& table, const T& 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, - Dump, - DumpStyleGraph, - }; - - 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; - - // File in which to produce proguard rules. - Maybe<Source> generateProguardRules; - - // 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; - - // The target style that will have it's style hierarchy dumped - // when the phase is DumpStyleGraph. - ResourceName dumpStyleTarget; -}; - -struct IdCollector : public xml::Visitor { - IdCollector(const Source& source, const std::shared_ptr<ResourceTable>& table) : - mSource(source), mTable(table) { - } - - virtual void visit(xml::Text* node) override {} - - virtual void visit(xml::Namespace* node) override { - for (const auto& child : node->children) { - child->accept(this); - } - } - - virtual void visit(xml::Element* node) override { - for (const xml::Attribute& attr : node->attributes) { - bool create = false; - bool priv = false; - ResourceNameRef nameRef; - if (ResourceParser::tryParseReference(attr.value, &nameRef, &create, &priv)) { - if (create) { - mTable->addResource(nameRef, {}, mSource.line(node->lineNumber), - util::make_unique<Id>()); - } - } - } - - for (const auto& child : node->children) { - child->accept(this); - } - } - -private: - Source mSource; - std::shared_ptr<ResourceTable> mTable; -}; - -bool compileXml(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table, - 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; - } - - SourceLogger logger(item.source); - std::unique_ptr<xml::Node> root = xml::inflate(&in, &logger); - if (!root) { - return false; - } - - // Collect any resource ID's declared here. - IdCollector idCollector(item.source, table); - root->accept(&idCollector); - - BigBuffer outBuffer(1024); - if (!xml::flatten(root.get(), options.appInfo.package, &outBuffer)) { - logger.error() << "failed to encode XML." << std::endl; - return false; - } - - // 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; - } - return true; -} - -/** - * Determines if a layout should be auto generated based on SDK level. We do not - * generate a layout if there is already a layout defined whose SDK version is greater than - * the one we want to generate. - */ -bool shouldGenerateVersionedResource(const std::shared_ptr<const ResourceTable>& table, - const ResourceName& name, const ConfigDescription& config, - int sdkVersionToGenerate) { - assert(sdkVersionToGenerate > config.sdkVersion); - const ResourceTableType* type; - const ResourceEntry* entry; - std::tie(type, entry) = table->findResource(name); - assert(type && entry); - - auto iter = std::lower_bound(entry->values.begin(), entry->values.end(), config, - [](const ResourceConfigValue& lhs, const ConfigDescription& config) -> bool { - return lhs.config < config; - }); - - assert(iter != entry->values.end()); - ++iter; - - if (iter == entry->values.end()) { - return true; - } - - ConfigDescription newConfig = config; - newConfig.sdkVersion = sdkVersionToGenerate; - return newConfig < iter->config; -} - -bool linkXml(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table, - const std::shared_ptr<IResolver>& resolver, const LinkItem& item, - const void* data, size_t dataLen, ZipFile* outApk, std::queue<LinkItem>* outQueue, - proguard::KeepSet* keepSet) { - SourceLogger logger(item.source); - std::unique_ptr<xml::Node> root = xml::inflate(data, dataLen, &logger); - if (!root) { - return false; - } - - xml::FlattenOptions xmlOptions; - if (options.packageType == AaptOptions::PackageType::StaticLibrary) { - xmlOptions.keepRawValues = true; - } - - 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; - } - - if (options.generateProguardRules) { - proguard::collectProguardRules(item.name.type, item.source, root.get(), keepSet); - } - - BigBuffer outBuffer(1024); - Maybe<size_t> minStrippedSdk = xml::flattenAndLink(item.source, root.get(), - item.originalPackage, resolver, - xmlOptions, &outBuffer); - if (!minStrippedSdk) { - logger.error() << "failed to encode XML." << std::endl; - 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. - // We can only generate a versioned layout if there doesn't exist a layout - // with sdk version greater than the current one but less than the one we - // want to generate. - if (shouldGenerateVersionedResource(table, item.name, item.config, - minStrippedSdk.value())) { - LinkItem newWork = item; - newWork.config.sdkVersion = minStrippedSdk.value(); - outQueue->push(newWork); - - if (!addFileReference(table, newWork)) { - Logger::error(options.output) << "failed to add auto-versioned resource '" - << newWork.name << "'." << std::endl; - return false; - } - } - } - - if (outApk->add(outBuffer, buildFileReference(item).data(), ZipEntry::kCompressDeflated, - nullptr) != android::NO_ERROR) { - Logger::error(options.output) << "failed to write linked file '" - << buildFileReference(item) << "' 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<IResolver>& resolver, - const std::map<std::shared_ptr<ResourceTable>, StaticLibraryData>& libApks, - const android::ResTable& table, ZipFile* outApk, proguard::KeepSet* keepSet) { - 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; - } - - SourceLogger logger(options.manifest); - std::unique_ptr<xml::Node> root = xml::inflate(&in, &logger); - if (!root) { - return false; - } - - ManifestMerger merger({}); - if (!merger.setAppManifest(options.manifest, options.appInfo.package, std::move(root))) { - return false; - } - - for (const auto& entry : libApks) { - ZipFile* libApk = entry.second.apk.get(); - const std::u16string& libPackage = entry.first->getPackage(); - const Source& libSource = entry.second.source; - - ZipEntry* zipEntry = libApk->getEntryByName("AndroidManifest.xml"); - if (!zipEntry) { - continue; - } - - std::unique_ptr<void, DeleteMalloc> uncompressedData = std::unique_ptr<void, DeleteMalloc>( - libApk->uncompress(zipEntry)); - assert(uncompressedData); - - SourceLogger logger(libSource); - std::unique_ptr<xml::Node> libRoot = xml::inflate(uncompressedData.get(), - zipEntry->getUncompressedLen(), &logger); - if (!libRoot) { - return false; - } - - if (!merger.mergeLibraryManifest(libSource, libPackage, std::move(libRoot))) { - return false; - } - } - - if (options.generateProguardRules) { - proguard::collectProguardRulesForManifest(options.manifest, merger.getMergedXml(), - keepSet); - } - - BigBuffer outBuffer(1024); - if (!xml::flattenAndLink(options.manifest, merger.getMergedXml(), options.appInfo.package, - resolver, {}, &outBuffer)) { - 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(table); - 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{ - name, value.config, newSource, 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; - -bool link(const AaptOptions& options, const std::shared_ptr<ResourceTable>& outTable, - const std::shared_ptr<IResolver>& 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, options.appInfo.package, - 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; - } - } - - // Version all styles referencing attributes outside of their specified SDK version. - if (options.versionStylesAndLayouts) { - versionStylesForCompat(outTable); - } - - { - // Now that everything is merged, let's link it. - Linker::Options linkerOptions; - if (options.packageType == AaptOptions::PackageType::StaticLibrary) { - linkerOptions.linkResourceIds = false; - } - Linker linker(outTable, resolver, linkerOptions); - 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; - } - - proguard::KeepSet keepSet; - - android::ResTable binTable; - if (!compileManifest(options, resolver, apkFiles, binTable, &outApk, &keepSet)) { - 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, outTable, resolver, item, uncompressedData, - entry->getUncompressedLen(), &outApk, &linkQueue, &keepSet)) { - 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; - } - } - } - - // Generate the Proguard rules file. - if (options.generateProguardRules) { - const Source& outPath = options.generateProguardRules.value(); - - if (options.verbose) { - Logger::note(outPath) << "writing proguard rules." << std::endl; - } - - std::ofstream fout(outPath.path); - if (!fout) { - Logger::error(outPath) << strerror(errno) << std::endl; - return false; - } - - if (!proguard::writeKeepSet(&fout, keepSet)) { - Logger::error(outPath) << "failed to write proguard rules." << 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 = 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<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{ - ResourceName{ table->getPackage(), *type, pathData.name }, - pathData.config, - source, - pathData.extension - }); - } - } - - 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 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, &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 << "dump dumps resource contents to to standard out" << 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 if (command == "dump") { - options.phase = AaptOptions::Phase::Dump; - } else if (command == "dump-style-graph") { - options.phase = AaptOptions::Phase::DumpStyleGraph; - } else { - std::cerr << "invalid command '" << command << "'." << std::endl << std::endl; - printCommandsAndDie(); - } - - bool isStaticLib = false; - 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::optionalFlag("--proguard", "file in which to output proguard rules", - [&options](const StringPiece& arg) { - options.generateProguardRules = Source{ arg.toString() }; - }); - - flag::optionalSwitch("--static-lib", "generate a static Android library", true, - &isStaticLib); - - 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); - } - - if (options.phase == AaptOptions::Phase::Compile || - options.phase == AaptOptions::Phase::Link) { - // Common flags for all steps. - flag::requiredFlag("-o", "Output path", [&options](const StringPiece& arg) { - options.output = Source{ arg.toString() }; - }); - } - - if (options.phase == AaptOptions::Phase::DumpStyleGraph) { - flag::requiredFlag("--style", "Name of the style to dump", - [&options](const StringPiece& arg, std::string* outError) -> bool { - Reference styleReference; - if (!ResourceParser::parseStyleParentReference(util::utf8ToUtf16(arg), - &styleReference, outError)) { - return false; - } - options.dumpStyleTarget = styleReference.name; - return true; - }); - } - - 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; -} - -static bool doDump(const AaptOptions& options) { - 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>(); - std::shared_ptr<ResourceTableResolver> resolver = - std::make_shared<ResourceTableResolver>( - table, std::vector<std::shared_ptr<const android::AssetManager>>()); - - 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; - } - - if (options.phase == AaptOptions::Phase::Dump) { - Debug::printTable(table); - } else if (options.phase == AaptOptions::Phase::DumpStyleGraph) { - Debug::printStyleGraph(table, options.dumpStyleTarget); - } - } - return true; -} +} // namespace aapt int main(int argc, char** argv) { - Logger::setLog(std::make_shared<Log>(std::cerr, std::cerr)); - AaptOptions options = prepareArgs(argc, argv); + if (argc >= 2) { + argv += 1; + argc -= 1; - if (options.phase == AaptOptions::Phase::Dump || - options.phase == AaptOptions::Phase::DumpStyleGraph) { - if (!doDump(options)) { - return 1; - } - return 0; - } - - // 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; + std::vector<aapt::StringPiece> args; + for (int i = 1; i < argc; i++) { + args.push_back(argv[i]); } - if (options.appInfo.package.empty()) { - Logger::error() << "no package name specified." << std::endl; - return false; + aapt::StringPiece command(argv[0]); + if (command == "compile" || command == "c") { + return aapt::compile(args); + } else if (command == "link" || command == "l") { + return aapt::link(args); } - } - - // Every phase needs a resource table. - std::shared_ptr<ResourceTable> table = std::make_shared<ResourceTable>(); - - // The package name is empty when in the compile phase. - table->setPackage(options.appInfo.package); - if (options.appInfo.package == u"android") { - table->setPackageId(0x01); + std::cerr << "unknown command '" << command << "'\n"; } else { - table->setPackageId(0x7f); - } - - // Load the included libraries. - std::vector<std::shared_ptr<const android::AssetManager>> sources; - for (const Source& source : options.libraries) { - std::shared_ptr<android::AssetManager> assetManager = - std::make_shared<android::AssetManager>(); - int32_t cookie; - if (!assetManager->addAssetPath(android::String8(source.path.data()), &cookie)) { - Logger::error(source) << "failed to load library." << std::endl; - return false; - } - - if (cookie == 0) { - Logger::error(source) << "failed to load library." << std::endl; - return false; - } - sources.push_back(assetManager); + std::cerr << "no command specified\n"; } - // Make the resolver that will cache IDs for us. - std::shared_ptr<ResourceTableResolver> resolver = std::make_shared<ResourceTableResolver>( - table, sources); - - 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; + std::cerr << "\nusage: aapt2 [compile|link] ..." << std::endl; + return 1; } |