Home | History | Annotate | Download | only in aapt2
      1 /*
      2  * Copyright (C) 2015 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 #include "AppInfo.h"
     18 #include "BigBuffer.h"
     19 #include "BinaryResourceParser.h"
     20 #include "BindingXmlPullParser.h"
     21 #include "Debug.h"
     22 #include "Files.h"
     23 #include "Flag.h"
     24 #include "JavaClassGenerator.h"
     25 #include "Linker.h"
     26 #include "ManifestMerger.h"
     27 #include "ManifestParser.h"
     28 #include "ManifestValidator.h"
     29 #include "NameMangler.h"
     30 #include "Png.h"
     31 #include "ProguardRules.h"
     32 #include "ResourceParser.h"
     33 #include "ResourceTable.h"
     34 #include "ResourceTableResolver.h"
     35 #include "ResourceValues.h"
     36 #include "SdkConstants.h"
     37 #include "SourceXmlPullParser.h"
     38 #include "StringPiece.h"
     39 #include "TableFlattener.h"
     40 #include "Util.h"
     41 #include "XmlFlattener.h"
     42 #include "ZipFile.h"
     43 
     44 #include <algorithm>
     45 #include <androidfw/AssetManager.h>
     46 #include <cstdlib>
     47 #include <dirent.h>
     48 #include <errno.h>
     49 #include <fstream>
     50 #include <iostream>
     51 #include <sstream>
     52 #include <sys/stat.h>
     53 #include <unordered_set>
     54 #include <utils/Errors.h>
     55 
     56 constexpr const char* kAaptVersionStr = "2.0-alpha";
     57 
     58 using namespace aapt;
     59 
     60 /**
     61  * Used with smart pointers to free malloc'ed memory.
     62  */
     63 struct DeleteMalloc {
     64     void operator()(void* ptr) {
     65         free(ptr);
     66     }
     67 };
     68 
     69 struct StaticLibraryData {
     70     Source source;
     71     std::unique_ptr<ZipFile> apk;
     72 };
     73 
     74 /**
     75  * Collect files from 'root', filtering out any files that do not
     76  * match the FileFilter 'filter'.
     77  */
     78 bool walkTree(const Source& root, const FileFilter& filter,
     79               std::vector<Source>* outEntries) {
     80     bool error = false;
     81 
     82     for (const std::string& dirName : listFiles(root.path)) {
     83         std::string dir = root.path;
     84         appendPath(&dir, dirName);
     85 
     86         FileType ft = getFileType(dir);
     87         if (!filter(dirName, ft)) {
     88             continue;
     89         }
     90 
     91         if (ft != FileType::kDirectory) {
     92             continue;
     93         }
     94 
     95         for (const std::string& fileName : listFiles(dir)) {
     96             std::string file(dir);
     97             appendPath(&file, fileName);
     98 
     99             FileType ft = getFileType(file);
    100             if (!filter(fileName, ft)) {
    101                 continue;
    102             }
    103 
    104             if (ft != FileType::kRegular) {
    105                 Logger::error(Source{ file }) << "not a regular file." << std::endl;
    106                 error = true;
    107                 continue;
    108             }
    109             outEntries->push_back(Source{ file });
    110         }
    111     }
    112     return !error;
    113 }
    114 
    115 void versionStylesForCompat(const std::shared_ptr<ResourceTable>& table) {
    116     for (auto& type : *table) {
    117         if (type->type != ResourceType::kStyle) {
    118             continue;
    119         }
    120 
    121         for (auto& entry : type->entries) {
    122             // Add the versioned styles we want to create
    123             // here. They are added to the table after
    124             // iterating over the original set of styles.
    125             //
    126             // A stack is used since auto-generated styles
    127             // from later versions should override
    128             // auto-generated styles from earlier versions.
    129             // Iterating over the styles is done in order,
    130             // so we will always visit sdkVersions from smallest
    131             // to largest.
    132             std::stack<ResourceConfigValue> addStack;
    133 
    134             for (ResourceConfigValue& configValue : entry->values) {
    135                 visitFunc<Style>(*configValue.value, [&](Style& style) {
    136                     // Collect which entries we've stripped and the smallest
    137                     // SDK level which was stripped.
    138                     size_t minSdkStripped = std::numeric_limits<size_t>::max();
    139                     std::vector<Style::Entry> stripped;
    140 
    141                     // Iterate over the style's entries and erase/record the
    142                     // attributes whose SDK level exceeds the config's sdkVersion.
    143                     auto iter = style.entries.begin();
    144                     while (iter != style.entries.end()) {
    145                         if (iter->key.name.package == u"android") {
    146                             size_t sdkLevel = findAttributeSdkLevel(iter->key.name);
    147                             if (sdkLevel > 1 && sdkLevel > configValue.config.sdkVersion) {
    148                                 // Record that we are about to strip this.
    149                                 stripped.emplace_back(std::move(*iter));
    150                                 minSdkStripped = std::min(minSdkStripped, sdkLevel);
    151 
    152                                 // Erase this from this style.
    153                                 iter = style.entries.erase(iter);
    154                                 continue;
    155                             }
    156                         }
    157                         ++iter;
    158                     }
    159 
    160                     if (!stripped.empty()) {
    161                         // We have stripped attributes, so let's create a new style to hold them.
    162                         ConfigDescription versionConfig(configValue.config);
    163                         versionConfig.sdkVersion = minSdkStripped;
    164 
    165                         ResourceConfigValue value = {
    166                                 versionConfig,
    167                                 configValue.source,
    168                                 {},
    169 
    170                                 // Create a copy of the original style.
    171                                 std::unique_ptr<Value>(configValue.value->clone(
    172                                             &table->getValueStringPool()))
    173                         };
    174 
    175                         Style& newStyle = static_cast<Style&>(*value.value);
    176 
    177                         // Move the recorded stripped attributes into this new style.
    178                         std::move(stripped.begin(), stripped.end(),
    179                                   std::back_inserter(newStyle.entries));
    180 
    181                         // We will add this style to the table later. If we do it now, we will
    182                         // mess up iteration.
    183                         addStack.push(std::move(value));
    184                     }
    185                 });
    186             }
    187 
    188             auto comparator =
    189                     [](const ResourceConfigValue& lhs, const ConfigDescription& rhs) -> bool {
    190                         return lhs.config < rhs;
    191                     };
    192 
    193             while (!addStack.empty()) {
    194                 ResourceConfigValue& value = addStack.top();
    195                 auto iter = std::lower_bound(entry->values.begin(), entry->values.end(),
    196                                              value.config, comparator);
    197                 if (iter == entry->values.end() || iter->config != value.config) {
    198                     entry->values.insert(iter, std::move(value));
    199                 }
    200                 addStack.pop();
    201             }
    202         }
    203     }
    204 }
    205 
    206 struct CompileItem {
    207     ResourceName name;
    208     ConfigDescription config;
    209     Source source;
    210     std::string extension;
    211 };
    212 
    213 struct LinkItem {
    214     ResourceName name;
    215     ConfigDescription config;
    216     Source source;
    217     std::string originalPath;
    218     ZipFile* apk;
    219     std::u16string originalPackage;
    220 };
    221 
    222 template <typename TChar>
    223 static BasicStringPiece<TChar> getExtension(const BasicStringPiece<TChar>& str) {
    224     auto iter = std::find(str.begin(), str.end(), static_cast<TChar>('.'));
    225     if (iter == str.end()) {
    226         return BasicStringPiece<TChar>();
    227     }
    228     size_t offset = (iter - str.begin()) + 1;
    229     return str.substr(offset, str.size() - offset);
    230 }
    231 
    232 std::string buildFileReference(const ResourceNameRef& name, const ConfigDescription& config,
    233                                const StringPiece& extension) {
    234     std::stringstream path;
    235     path << "res/" << name.type;
    236     if (config != ConfigDescription{}) {
    237         path << "-" << config;
    238     }
    239     path << "/" << util::utf16ToUtf8(name.entry);
    240     if (!extension.empty()) {
    241         path << "." << extension;
    242     }
    243     return path.str();
    244 }
    245 
    246 std::string buildFileReference(const CompileItem& item) {
    247     return buildFileReference(item.name, item.config, item.extension);
    248 }
    249 
    250 std::string buildFileReference(const LinkItem& item) {
    251     return buildFileReference(item.name, item.config, getExtension<char>(item.originalPath));
    252 }
    253 
    254 template <typename T>
    255 bool addFileReference(const std::shared_ptr<ResourceTable>& table, const T& item) {
    256     StringPool& pool = table->getValueStringPool();
    257     StringPool::Ref ref = pool.makeRef(util::utf8ToUtf16(buildFileReference(item)),
    258                                        StringPool::Context{ 0, item.config });
    259     return table->addResource(item.name, item.config, item.source.line(0),
    260                               util::make_unique<FileReference>(ref));
    261 }
    262 
    263 struct AaptOptions {
    264     enum class Phase {
    265         Link,
    266         Compile,
    267         Dump,
    268         DumpStyleGraph,
    269     };
    270 
    271     enum class PackageType {
    272         StandardApp,
    273         StaticLibrary,
    274     };
    275 
    276     // The phase to process.
    277     Phase phase;
    278 
    279     // The type of package to produce.
    280     PackageType packageType = PackageType::StandardApp;
    281 
    282     // Details about the app.
    283     AppInfo appInfo;
    284 
    285     // The location of the manifest file.
    286     Source manifest;
    287 
    288     // The APK files to link.
    289     std::vector<Source> input;
    290 
    291     // The libraries these files may reference.
    292     std::vector<Source> libraries;
    293 
    294     // Output path. This can be a directory or file
    295     // depending on the phase.
    296     Source output;
    297 
    298     // Directory in which to write binding xml files.
    299     Source bindingOutput;
    300 
    301     // Directory to in which to generate R.java.
    302     Maybe<Source> generateJavaClass;
    303 
    304     // File in which to produce proguard rules.
    305     Maybe<Source> generateProguardRules;
    306 
    307     // Whether to output verbose details about
    308     // compilation.
    309     bool verbose = false;
    310 
    311     // Whether or not to auto-version styles or layouts
    312     // referencing attributes defined in a newer SDK
    313     // level than the style or layout is defined for.
    314     bool versionStylesAndLayouts = true;
    315 
    316     // The target style that will have it's style hierarchy dumped
    317     // when the phase is DumpStyleGraph.
    318     ResourceName dumpStyleTarget;
    319 };
    320 
    321 struct IdCollector : public xml::Visitor {
    322     IdCollector(const Source& source, const std::shared_ptr<ResourceTable>& table) :
    323             mSource(source), mTable(table) {
    324     }
    325 
    326     virtual void visit(xml::Text* node) override {}
    327 
    328     virtual void visit(xml::Namespace* node) override {
    329         for (const auto& child : node->children) {
    330             child->accept(this);
    331         }
    332     }
    333 
    334     virtual void visit(xml::Element* node) override {
    335         for (const xml::Attribute& attr : node->attributes) {
    336             bool create = false;
    337             bool priv = false;
    338             ResourceNameRef nameRef;
    339             if (ResourceParser::tryParseReference(attr.value, &nameRef, &create, &priv)) {
    340                 if (create) {
    341                     mTable->addResource(nameRef, {}, mSource.line(node->lineNumber),
    342                                         util::make_unique<Id>());
    343                 }
    344             }
    345         }
    346 
    347         for (const auto& child : node->children) {
    348             child->accept(this);
    349         }
    350     }
    351 
    352 private:
    353     Source mSource;
    354     std::shared_ptr<ResourceTable> mTable;
    355 };
    356 
    357 bool compileXml(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table,
    358                 const CompileItem& item, ZipFile* outApk) {
    359     std::ifstream in(item.source.path, std::ifstream::binary);
    360     if (!in) {
    361         Logger::error(item.source) << strerror(errno) << std::endl;
    362         return false;
    363     }
    364 
    365     SourceLogger logger(item.source);
    366     std::unique_ptr<xml::Node> root = xml::inflate(&in, &logger);
    367     if (!root) {
    368         return false;
    369     }
    370 
    371     // Collect any resource ID's declared here.
    372     IdCollector idCollector(item.source, table);
    373     root->accept(&idCollector);
    374 
    375     BigBuffer outBuffer(1024);
    376     if (!xml::flatten(root.get(), options.appInfo.package, &outBuffer)) {
    377         logger.error() << "failed to encode XML." << std::endl;
    378         return false;
    379     }
    380 
    381     // Write the resulting compiled XML file to the output APK.
    382     if (outApk->add(outBuffer, buildFileReference(item).data(), ZipEntry::kCompressStored,
    383                 nullptr) != android::NO_ERROR) {
    384         Logger::error(options.output) << "failed to write compiled '" << item.source
    385                                       << "' to apk." << std::endl;
    386         return false;
    387     }
    388     return true;
    389 }
    390 
    391 /**
    392  * Determines if a layout should be auto generated based on SDK level. We do not
    393  * generate a layout if there is already a layout defined whose SDK version is greater than
    394  * the one we want to generate.
    395  */
    396 bool shouldGenerateVersionedResource(const std::shared_ptr<const ResourceTable>& table,
    397                                      const ResourceName& name, const ConfigDescription& config,
    398                                      int sdkVersionToGenerate) {
    399     assert(sdkVersionToGenerate > config.sdkVersion);
    400     const ResourceTableType* type;
    401     const ResourceEntry* entry;
    402     std::tie(type, entry) = table->findResource(name);
    403     assert(type && entry);
    404 
    405     auto iter = std::lower_bound(entry->values.begin(), entry->values.end(), config,
    406             [](const ResourceConfigValue& lhs, const ConfigDescription& config) -> bool {
    407         return lhs.config < config;
    408     });
    409 
    410     assert(iter != entry->values.end());
    411     ++iter;
    412 
    413     if (iter == entry->values.end()) {
    414         return true;
    415     }
    416 
    417     ConfigDescription newConfig = config;
    418     newConfig.sdkVersion = sdkVersionToGenerate;
    419     return newConfig < iter->config;
    420 }
    421 
    422 bool linkXml(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table,
    423              const std::shared_ptr<IResolver>& resolver, const LinkItem& item,
    424              const void* data, size_t dataLen, ZipFile* outApk, std::queue<LinkItem>* outQueue,
    425              proguard::KeepSet* keepSet) {
    426     SourceLogger logger(item.source);
    427     std::unique_ptr<xml::Node> root = xml::inflate(data, dataLen, &logger);
    428     if (!root) {
    429         return false;
    430     }
    431 
    432     xml::FlattenOptions xmlOptions;
    433     if (options.packageType == AaptOptions::PackageType::StaticLibrary) {
    434         xmlOptions.keepRawValues = true;
    435     }
    436 
    437     if (options.versionStylesAndLayouts) {
    438         // We strip attributes that do not belong in this version of the resource.
    439         // Non-version qualified resources have an implicit version 1 requirement.
    440         xmlOptions.maxSdkAttribute = item.config.sdkVersion ? item.config.sdkVersion : 1;
    441     }
    442 
    443     if (options.generateProguardRules) {
    444         proguard::collectProguardRules(item.name.type, item.source, root.get(), keepSet);
    445     }
    446 
    447     BigBuffer outBuffer(1024);
    448     Maybe<size_t> minStrippedSdk = xml::flattenAndLink(item.source, root.get(),
    449                                                        item.originalPackage, resolver,
    450                                                        xmlOptions, &outBuffer);
    451     if (!minStrippedSdk) {
    452         logger.error() << "failed to encode XML." << std::endl;
    453         return false;
    454     }
    455 
    456     if (minStrippedSdk.value() > 0) {
    457         // Something was stripped, so let's generate a new file
    458         // with the version of the smallest SDK version stripped.
    459         // We can only generate a versioned layout if there doesn't exist a layout
    460         // with sdk version greater than the current one but less than the one we
    461         // want to generate.
    462         if (shouldGenerateVersionedResource(table, item.name, item.config,
    463                     minStrippedSdk.value())) {
    464             LinkItem newWork = item;
    465             newWork.config.sdkVersion = minStrippedSdk.value();
    466             outQueue->push(newWork);
    467 
    468             if (!addFileReference(table, newWork)) {
    469                 Logger::error(options.output) << "failed to add auto-versioned resource '"
    470                                               << newWork.name << "'." << std::endl;
    471                 return false;
    472             }
    473         }
    474     }
    475 
    476     if (outApk->add(outBuffer, buildFileReference(item).data(), ZipEntry::kCompressDeflated,
    477                 nullptr) != android::NO_ERROR) {
    478         Logger::error(options.output) << "failed to write linked file '"
    479                                       << buildFileReference(item) << "' to apk." << std::endl;
    480         return false;
    481     }
    482     return true;
    483 }
    484 
    485 bool compilePng(const AaptOptions& options, const CompileItem& item, ZipFile* outApk) {
    486     std::ifstream in(item.source.path, std::ifstream::binary);
    487     if (!in) {
    488         Logger::error(item.source) << strerror(errno) << std::endl;
    489         return false;
    490     }
    491 
    492     BigBuffer outBuffer(4096);
    493     std::string err;
    494     Png png;
    495     if (!png.process(item.source, in, &outBuffer, {}, &err)) {
    496         Logger::error(item.source) << err << std::endl;
    497         return false;
    498     }
    499 
    500     if (outApk->add(outBuffer, buildFileReference(item).data(), ZipEntry::kCompressStored,
    501                 nullptr) != android::NO_ERROR) {
    502         Logger::error(options.output) << "failed to write compiled '" << item.source
    503                                       << "' to apk." << std::endl;
    504         return false;
    505     }
    506     return true;
    507 }
    508 
    509 bool copyFile(const AaptOptions& options, const CompileItem& item, ZipFile* outApk) {
    510     if (outApk->add(item.source.path.data(), buildFileReference(item).data(),
    511                 ZipEntry::kCompressStored, nullptr) != android::NO_ERROR) {
    512         Logger::error(options.output) << "failed to copy file '" << item.source << "' to apk."
    513                                       << std::endl;
    514         return false;
    515     }
    516     return true;
    517 }
    518 
    519 bool compileManifest(const AaptOptions& options, const std::shared_ptr<IResolver>& resolver,
    520                      const std::map<std::shared_ptr<ResourceTable>, StaticLibraryData>& libApks,
    521                      const android::ResTable& table, ZipFile* outApk, proguard::KeepSet* keepSet) {
    522     if (options.verbose) {
    523         Logger::note(options.manifest) << "compiling AndroidManifest.xml." << std::endl;
    524     }
    525 
    526     std::ifstream in(options.manifest.path, std::ifstream::binary);
    527     if (!in) {
    528         Logger::error(options.manifest) << strerror(errno) << std::endl;
    529         return false;
    530     }
    531 
    532     SourceLogger logger(options.manifest);
    533     std::unique_ptr<xml::Node> root = xml::inflate(&in, &logger);
    534     if (!root) {
    535         return false;
    536     }
    537 
    538     ManifestMerger merger({});
    539     if (!merger.setAppManifest(options.manifest, options.appInfo.package, std::move(root))) {
    540         return false;
    541     }
    542 
    543     for (const auto& entry : libApks) {
    544         ZipFile* libApk = entry.second.apk.get();
    545         const std::u16string& libPackage = entry.first->getPackage();
    546         const Source& libSource = entry.second.source;
    547 
    548         ZipEntry* zipEntry = libApk->getEntryByName("AndroidManifest.xml");
    549         if (!zipEntry) {
    550             continue;
    551         }
    552 
    553         std::unique_ptr<void, DeleteMalloc> uncompressedData = std::unique_ptr<void, DeleteMalloc>(
    554                 libApk->uncompress(zipEntry));
    555         assert(uncompressedData);
    556 
    557         SourceLogger logger(libSource);
    558         std::unique_ptr<xml::Node> libRoot = xml::inflate(uncompressedData.get(),
    559                                                           zipEntry->getUncompressedLen(), &logger);
    560         if (!libRoot) {
    561             return false;
    562         }
    563 
    564         if (!merger.mergeLibraryManifest(libSource, libPackage, std::move(libRoot))) {
    565             return false;
    566         }
    567     }
    568 
    569     if (options.generateProguardRules) {
    570         proguard::collectProguardRulesForManifest(options.manifest, merger.getMergedXml(),
    571                                                   keepSet);
    572     }
    573 
    574     BigBuffer outBuffer(1024);
    575     if (!xml::flattenAndLink(options.manifest, merger.getMergedXml(), options.appInfo.package,
    576                 resolver, {}, &outBuffer)) {
    577         return false;
    578     }
    579 
    580     std::unique_ptr<uint8_t[]> data = util::copy(outBuffer);
    581 
    582     android::ResXMLTree tree;
    583     if (tree.setTo(data.get(), outBuffer.size(), false) != android::NO_ERROR) {
    584         return false;
    585     }
    586 
    587     ManifestValidator validator(table);
    588     if (!validator.validate(options.manifest, &tree)) {
    589         return false;
    590     }
    591 
    592     if (outApk->add(data.get(), outBuffer.size(), "AndroidManifest.xml",
    593                 ZipEntry::kCompressStored, nullptr) != android::NO_ERROR) {
    594         Logger::error(options.output) << "failed to write 'AndroidManifest.xml' to apk."
    595                                       << std::endl;
    596         return false;
    597     }
    598     return true;
    599 }
    600 
    601 static bool compileValues(const std::shared_ptr<ResourceTable>& table, const Source& source,
    602                           const ConfigDescription& config) {
    603     std::ifstream in(source.path, std::ifstream::binary);
    604     if (!in) {
    605         Logger::error(source) << strerror(errno) << std::endl;
    606         return false;
    607     }
    608 
    609     std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<SourceXmlPullParser>(in);
    610     ResourceParser parser(table, source, config, xmlParser);
    611     return parser.parse();
    612 }
    613 
    614 struct ResourcePathData {
    615     std::u16string resourceDir;
    616     std::u16string name;
    617     std::string extension;
    618     ConfigDescription config;
    619 };
    620 
    621 /**
    622  * Resource file paths are expected to look like:
    623  * [--/res/]type[-config]/name
    624  */
    625 static Maybe<ResourcePathData> extractResourcePathData(const Source& source) {
    626     // TODO(adamlesinski): Use Windows path separator on windows.
    627     std::vector<std::string> parts = util::splitAndLowercase(source.path, '/');
    628     if (parts.size() < 2) {
    629         Logger::error(source) << "bad resource path." << std::endl;
    630         return {};
    631     }
    632 
    633     std::string& dir = parts[parts.size() - 2];
    634     StringPiece dirStr = dir;
    635 
    636     ConfigDescription config;
    637     size_t dashPos = dir.find('-');
    638     if (dashPos != std::string::npos) {
    639         StringPiece configStr = dirStr.substr(dashPos + 1, dir.size() - (dashPos + 1));
    640         if (!ConfigDescription::parse(configStr, &config)) {
    641             Logger::error(source)
    642                     << "invalid configuration '"
    643                     << configStr
    644                     << "'."
    645                     << std::endl;
    646             return {};
    647         }
    648         dirStr = dirStr.substr(0, dashPos);
    649     }
    650 
    651     std::string& filename = parts[parts.size() - 1];
    652     StringPiece name = filename;
    653     StringPiece extension;
    654     size_t dotPos = filename.find('.');
    655     if (dotPos != std::string::npos) {
    656         extension = name.substr(dotPos + 1, filename.size() - (dotPos + 1));
    657         name = name.substr(0, dotPos);
    658     }
    659 
    660     return ResourcePathData{
    661             util::utf8ToUtf16(dirStr),
    662             util::utf8ToUtf16(name),
    663             extension.toString(),
    664             config
    665     };
    666 }
    667 
    668 bool writeResourceTable(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table,
    669                         const TableFlattener::Options& flattenerOptions, ZipFile* outApk) {
    670     if (table->begin() != table->end()) {
    671         BigBuffer buffer(1024);
    672         TableFlattener flattener(flattenerOptions);
    673         if (!flattener.flatten(&buffer, *table)) {
    674             Logger::error() << "failed to flatten resource table." << std::endl;
    675             return false;
    676         }
    677 
    678         if (options.verbose) {
    679             Logger::note() << "Final resource table size=" << util::formatSize(buffer.size())
    680                            << std::endl;
    681         }
    682 
    683         if (outApk->add(buffer, "resources.arsc", ZipEntry::kCompressStored, nullptr) !=
    684                 android::NO_ERROR) {
    685             Logger::note(options.output) << "failed to store resource table." << std::endl;
    686             return false;
    687         }
    688     }
    689     return true;
    690 }
    691 
    692 /**
    693  * For each FileReference in the table, adds a LinkItem to the link queue for processing.
    694  */
    695 static void addApkFilesToLinkQueue(const std::u16string& package, const Source& source,
    696                                    const std::shared_ptr<ResourceTable>& table,
    697                                    const std::unique_ptr<ZipFile>& apk,
    698                                    std::queue<LinkItem>* outLinkQueue) {
    699     bool mangle = package != table->getPackage();
    700     for (auto& type : *table) {
    701         for (auto& entry : type->entries) {
    702             ResourceName name = { package, type->type, entry->name };
    703             if (mangle) {
    704                 NameMangler::mangle(table->getPackage(), &name.entry);
    705             }
    706 
    707             for (auto& value : entry->values) {
    708                 visitFunc<FileReference>(*value.value, [&](FileReference& ref) {
    709                     std::string pathUtf8 = util::utf16ToUtf8(*ref.path);
    710                     Source newSource = source;
    711                     newSource.path += "/";
    712                     newSource.path += pathUtf8;
    713                     outLinkQueue->push(LinkItem{
    714                             name, value.config, newSource, pathUtf8, apk.get(),
    715                             table->getPackage() });
    716                     // Now rewrite the file path.
    717                     if (mangle) {
    718                         ref.path = table->getValueStringPool().makeRef(util::utf8ToUtf16(
    719                                     buildFileReference(name, value.config,
    720                                                        getExtension<char>(pathUtf8))));
    721                     }
    722                 });
    723             }
    724         }
    725     }
    726 }
    727 
    728 static constexpr int kOpenFlags = ZipFile::kOpenCreate | ZipFile::kOpenTruncate |
    729         ZipFile::kOpenReadWrite;
    730 
    731 bool link(const AaptOptions& options, const std::shared_ptr<ResourceTable>& outTable,
    732           const std::shared_ptr<IResolver>& resolver) {
    733     std::map<std::shared_ptr<ResourceTable>, StaticLibraryData> apkFiles;
    734     std::unordered_set<std::u16string> linkedPackages;
    735 
    736     // Populate the linkedPackages with our own.
    737     linkedPackages.insert(options.appInfo.package);
    738 
    739     // Load all APK files.
    740     for (const Source& source : options.input) {
    741         std::unique_ptr<ZipFile> zipFile = util::make_unique<ZipFile>();
    742         if (zipFile->open(source.path.data(), ZipFile::kOpenReadOnly) != android::NO_ERROR) {
    743             Logger::error(source) << "failed to open: " << strerror(errno) << std::endl;
    744             return false;
    745         }
    746 
    747         std::shared_ptr<ResourceTable> table = std::make_shared<ResourceTable>();
    748 
    749         ZipEntry* entry = zipFile->getEntryByName("resources.arsc");
    750         if (!entry) {
    751             Logger::error(source) << "missing 'resources.arsc'." << std::endl;
    752             return false;
    753         }
    754 
    755         std::unique_ptr<void, DeleteMalloc> uncompressedData = std::unique_ptr<void, DeleteMalloc>(
    756                 zipFile->uncompress(entry));
    757         assert(uncompressedData);
    758 
    759         BinaryResourceParser parser(table, resolver, source, options.appInfo.package,
    760                                     uncompressedData.get(), entry->getUncompressedLen());
    761         if (!parser.parse()) {
    762             return false;
    763         }
    764 
    765         // Keep track of where this table came from.
    766         apkFiles[table] = StaticLibraryData{ source, std::move(zipFile) };
    767 
    768         // Add the package to the set of linked packages.
    769         linkedPackages.insert(table->getPackage());
    770     }
    771 
    772     std::queue<LinkItem> linkQueue;
    773     for (auto& p : apkFiles) {
    774         const std::shared_ptr<ResourceTable>& inTable = p.first;
    775 
    776         // Collect all FileReferences and add them to the queue for processing.
    777         addApkFilesToLinkQueue(options.appInfo.package, p.second.source, inTable, p.second.apk,
    778                                &linkQueue);
    779 
    780         // Merge the tables.
    781         if (!outTable->merge(std::move(*inTable))) {
    782             return false;
    783         }
    784     }
    785 
    786     // Version all styles referencing attributes outside of their specified SDK version.
    787     if (options.versionStylesAndLayouts) {
    788         versionStylesForCompat(outTable);
    789     }
    790 
    791     {
    792         // Now that everything is merged, let's link it.
    793         Linker::Options linkerOptions;
    794         if (options.packageType == AaptOptions::PackageType::StaticLibrary) {
    795             linkerOptions.linkResourceIds = false;
    796         }
    797         Linker linker(outTable, resolver, linkerOptions);
    798         if (!linker.linkAndValidate()) {
    799             return false;
    800         }
    801 
    802         // Verify that all symbols exist.
    803         const auto& unresolvedRefs = linker.getUnresolvedReferences();
    804         if (!unresolvedRefs.empty()) {
    805             for (const auto& entry : unresolvedRefs) {
    806                 for (const auto& source : entry.second) {
    807                     Logger::error(source) << "unresolved symbol '" << entry.first << "'."
    808                                           << std::endl;
    809                 }
    810             }
    811             return false;
    812         }
    813     }
    814 
    815     // Open the output APK file for writing.
    816     ZipFile outApk;
    817     if (outApk.open(options.output.path.data(), kOpenFlags) != android::NO_ERROR) {
    818         Logger::error(options.output) << "failed to open: " << strerror(errno) << std::endl;
    819         return false;
    820     }
    821 
    822     proguard::KeepSet keepSet;
    823 
    824     android::ResTable binTable;
    825     if (!compileManifest(options, resolver, apkFiles, binTable, &outApk, &keepSet)) {
    826         return false;
    827     }
    828 
    829     for (; !linkQueue.empty(); linkQueue.pop()) {
    830         const LinkItem& item = linkQueue.front();
    831 
    832         assert(!item.originalPackage.empty());
    833         ZipEntry* entry = item.apk->getEntryByName(item.originalPath.data());
    834         if (!entry) {
    835             Logger::error(item.source) << "failed to find '" << item.originalPath << "'."
    836                                        << std::endl;
    837             return false;
    838         }
    839 
    840         if (util::stringEndsWith<char>(item.originalPath, ".xml")) {
    841             void* uncompressedData = item.apk->uncompress(entry);
    842             assert(uncompressedData);
    843 
    844             if (!linkXml(options, outTable, resolver, item, uncompressedData,
    845                         entry->getUncompressedLen(), &outApk, &linkQueue, &keepSet)) {
    846                 Logger::error(options.output) << "failed to link '" << item.originalPath << "'."
    847                                               << std::endl;
    848                 return false;
    849             }
    850         } else {
    851             if (outApk.add(item.apk, entry, buildFileReference(item).data(), 0, nullptr) !=
    852                     android::NO_ERROR) {
    853                 Logger::error(options.output) << "failed to copy '" << item.originalPath << "'."
    854                                               << std::endl;
    855                 return false;
    856             }
    857         }
    858     }
    859 
    860     // Generate the Java class file.
    861     if (options.generateJavaClass) {
    862         JavaClassGenerator::Options javaOptions;
    863         if (options.packageType == AaptOptions::PackageType::StaticLibrary) {
    864             javaOptions.useFinal = false;
    865         }
    866         JavaClassGenerator generator(outTable, javaOptions);
    867 
    868         for (const std::u16string& package : linkedPackages) {
    869             Source outPath = options.generateJavaClass.value();
    870 
    871             // Build the output directory from the package name.
    872             // Eg. com.android.app -> com/android/app
    873             const std::string packageUtf8 = util::utf16ToUtf8(package);
    874             for (StringPiece part : util::tokenize<char>(packageUtf8, '.')) {
    875                 appendPath(&outPath.path, part);
    876             }
    877 
    878             if (!mkdirs(outPath.path)) {
    879                 Logger::error(outPath) << strerror(errno) << std::endl;
    880                 return false;
    881             }
    882 
    883             appendPath(&outPath.path, "R.java");
    884 
    885             if (options.verbose) {
    886                 Logger::note(outPath) << "writing Java symbols." << std::endl;
    887             }
    888 
    889             std::ofstream fout(outPath.path);
    890             if (!fout) {
    891                 Logger::error(outPath) << strerror(errno) << std::endl;
    892                 return false;
    893             }
    894 
    895             if (!generator.generate(package, fout)) {
    896                 Logger::error(outPath) << generator.getError() << "." << std::endl;
    897                 return false;
    898             }
    899         }
    900     }
    901 
    902     // Generate the Proguard rules file.
    903     if (options.generateProguardRules) {
    904         const Source& outPath = options.generateProguardRules.value();
    905 
    906         if (options.verbose) {
    907             Logger::note(outPath) << "writing proguard rules." << std::endl;
    908         }
    909 
    910         std::ofstream fout(outPath.path);
    911         if (!fout) {
    912             Logger::error(outPath) << strerror(errno) << std::endl;
    913             return false;
    914         }
    915 
    916         if (!proguard::writeKeepSet(&fout, keepSet)) {
    917             Logger::error(outPath) << "failed to write proguard rules." << std::endl;
    918             return false;
    919         }
    920     }
    921 
    922     outTable->getValueStringPool().prune();
    923     outTable->getValueStringPool().sort(
    924             [](const StringPool::Entry& a, const StringPool::Entry& b) -> bool {
    925                 if (a.context.priority < b.context.priority) {
    926                     return true;
    927                 }
    928 
    929                 if (a.context.priority > b.context.priority) {
    930                     return false;
    931                 }
    932                 return a.value < b.value;
    933             });
    934 
    935 
    936     // Flatten the resource table.
    937     TableFlattener::Options flattenerOptions;
    938     if (options.packageType != AaptOptions::PackageType::StaticLibrary) {
    939         flattenerOptions.useExtendedChunks = false;
    940     }
    941 
    942     if (!writeResourceTable(options, outTable, flattenerOptions, &outApk)) {
    943         return false;
    944     }
    945 
    946     outApk.flush();
    947     return true;
    948 }
    949 
    950 bool compile(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table,
    951              const std::shared_ptr<IResolver>& resolver) {
    952     std::queue<CompileItem> compileQueue;
    953     bool error = false;
    954 
    955     // Compile all the resource files passed in on the command line.
    956     for (const Source& source : options.input) {
    957         // Need to parse the resource type/config/filename.
    958         Maybe<ResourcePathData> maybePathData = extractResourcePathData(source);
    959         if (!maybePathData) {
    960             return false;
    961         }
    962 
    963         const ResourcePathData& pathData = maybePathData.value();
    964         if (pathData.resourceDir == u"values") {
    965             // The file is in the values directory, which means its contents will
    966             // go into the resource table.
    967             if (options.verbose) {
    968                 Logger::note(source) << "compiling values." << std::endl;
    969             }
    970 
    971             error |= !compileValues(table, source, pathData.config);
    972         } else {
    973             // The file is in a directory like 'layout' or 'drawable'. Find out
    974             // the type.
    975             const ResourceType* type = parseResourceType(pathData.resourceDir);
    976             if (!type) {
    977                 Logger::error(source) << "invalid resource type '" << pathData.resourceDir << "'."
    978                                       << std::endl;
    979                 return false;
    980             }
    981 
    982             compileQueue.push(CompileItem{
    983                     ResourceName{ table->getPackage(), *type, pathData.name },
    984                     pathData.config,
    985                     source,
    986                     pathData.extension
    987             });
    988         }
    989     }
    990 
    991     if (error) {
    992         return false;
    993     }
    994     // Open the output APK file for writing.
    995     ZipFile outApk;
    996     if (outApk.open(options.output.path.data(), kOpenFlags) != android::NO_ERROR) {
    997         Logger::error(options.output) << "failed to open: " << strerror(errno) << std::endl;
    998         return false;
    999     }
   1000 
   1001     // Compile each file.
   1002     for (; !compileQueue.empty(); compileQueue.pop()) {
   1003         const CompileItem& item = compileQueue.front();
   1004 
   1005         // Add the file name to the resource table.
   1006         error |= !addFileReference(table, item);
   1007 
   1008         if (item.extension == "xml") {
   1009             error |= !compileXml(options, table, item, &outApk);
   1010         } else if (item.extension == "png" || item.extension == "9.png") {
   1011             error |= !compilePng(options, item, &outApk);
   1012         } else {
   1013             error |= !copyFile(options, item, &outApk);
   1014         }
   1015     }
   1016 
   1017     if (error) {
   1018         return false;
   1019     }
   1020 
   1021     // Link and assign resource IDs.
   1022     Linker linker(table, resolver, {});
   1023     if (!linker.linkAndValidate()) {
   1024         return false;
   1025     }
   1026 
   1027     // Flatten the resource table.
   1028     if (!writeResourceTable(options, table, {}, &outApk)) {
   1029         return false;
   1030     }
   1031 
   1032     outApk.flush();
   1033     return true;
   1034 }
   1035 
   1036 bool loadAppInfo(const Source& source, AppInfo* outInfo) {
   1037     std::ifstream ifs(source.path, std::ifstream::in | std::ifstream::binary);
   1038     if (!ifs) {
   1039         Logger::error(source) << strerror(errno) << std::endl;
   1040         return false;
   1041     }
   1042 
   1043     ManifestParser parser;
   1044     std::shared_ptr<XmlPullParser> pullParser = std::make_shared<SourceXmlPullParser>(ifs);
   1045     return parser.parse(source, pullParser, outInfo);
   1046 }
   1047 
   1048 static void printCommandsAndDie() {
   1049     std::cerr << "The following commands are supported:" << std::endl << std::endl;
   1050     std::cerr << "compile       compiles a subset of resources" << std::endl;
   1051     std::cerr << "link          links together compiled resources and libraries" << std::endl;
   1052     std::cerr << "dump          dumps resource contents to to standard out" << std::endl;
   1053     std::cerr << std::endl;
   1054     std::cerr << "run aapt2 with one of the commands and the -h flag for extra details."
   1055               << std::endl;
   1056     exit(1);
   1057 }
   1058 
   1059 static AaptOptions prepareArgs(int argc, char** argv) {
   1060     if (argc < 2) {
   1061         std::cerr << "no command specified." << std::endl << std::endl;
   1062         printCommandsAndDie();
   1063     }
   1064 
   1065     const StringPiece command(argv[1]);
   1066     argc -= 2;
   1067     argv += 2;
   1068 
   1069     AaptOptions options;
   1070 
   1071     if (command == "--version" || command == "version") {
   1072         std::cout << kAaptVersionStr << std::endl;
   1073         exit(0);
   1074     } else if (command == "link") {
   1075         options.phase = AaptOptions::Phase::Link;
   1076     } else if (command == "compile") {
   1077         options.phase = AaptOptions::Phase::Compile;
   1078     } else if (command == "dump") {
   1079         options.phase = AaptOptions::Phase::Dump;
   1080     } else if (command == "dump-style-graph") {
   1081         options.phase = AaptOptions::Phase::DumpStyleGraph;
   1082     } else {
   1083         std::cerr << "invalid command '" << command << "'." << std::endl << std::endl;
   1084         printCommandsAndDie();
   1085     }
   1086 
   1087     bool isStaticLib = false;
   1088     if (options.phase == AaptOptions::Phase::Link) {
   1089         flag::requiredFlag("--manifest", "AndroidManifest.xml of your app",
   1090                 [&options](const StringPiece& arg) {
   1091                     options.manifest = Source{ arg.toString() };
   1092                 });
   1093 
   1094         flag::optionalFlag("-I", "add an Android APK to link against",
   1095                 [&options](const StringPiece& arg) {
   1096                     options.libraries.push_back(Source{ arg.toString() });
   1097                 });
   1098 
   1099         flag::optionalFlag("--java", "directory in which to generate R.java",
   1100                 [&options](const StringPiece& arg) {
   1101                     options.generateJavaClass = Source{ arg.toString() };
   1102                 });
   1103 
   1104         flag::optionalFlag("--proguard", "file in which to output proguard rules",
   1105                 [&options](const StringPiece& arg) {
   1106                     options.generateProguardRules = Source{ arg.toString() };
   1107                 });
   1108 
   1109         flag::optionalSwitch("--static-lib", "generate a static Android library", true,
   1110                              &isStaticLib);
   1111 
   1112         flag::optionalFlag("--binding", "Output directory for binding XML files",
   1113                 [&options](const StringPiece& arg) {
   1114                     options.bindingOutput = Source{ arg.toString() };
   1115                 });
   1116         flag::optionalSwitch("--no-version", "Disables automatic style and layout versioning",
   1117                              false, &options.versionStylesAndLayouts);
   1118     }
   1119 
   1120     if (options.phase == AaptOptions::Phase::Compile ||
   1121             options.phase == AaptOptions::Phase::Link) {
   1122         // Common flags for all steps.
   1123         flag::requiredFlag("-o", "Output path", [&options](const StringPiece& arg) {
   1124             options.output = Source{ arg.toString() };
   1125         });
   1126     }
   1127 
   1128     if (options.phase == AaptOptions::Phase::DumpStyleGraph) {
   1129         flag::requiredFlag("--style", "Name of the style to dump",
   1130                 [&options](const StringPiece& arg, std::string* outError) -> bool {
   1131                     Reference styleReference;
   1132                     if (!ResourceParser::parseStyleParentReference(util::utf8ToUtf16(arg),
   1133                                 &styleReference, outError)) {
   1134                         return false;
   1135                     }
   1136                     options.dumpStyleTarget = styleReference.name;
   1137                     return true;
   1138                 });
   1139     }
   1140 
   1141     bool help = false;
   1142     flag::optionalSwitch("-v", "enables verbose logging", true, &options.verbose);
   1143     flag::optionalSwitch("-h", "displays this help menu", true, &help);
   1144 
   1145     // Build the command string for output (eg. "aapt2 compile").
   1146     std::string fullCommand = "aapt2";
   1147     fullCommand += " ";
   1148     fullCommand += command.toString();
   1149 
   1150     // Actually read the command line flags.
   1151     flag::parse(argc, argv, fullCommand);
   1152 
   1153     if (help) {
   1154         flag::usageAndDie(fullCommand);
   1155     }
   1156 
   1157     if (isStaticLib) {
   1158         options.packageType = AaptOptions::PackageType::StaticLibrary;
   1159     }
   1160 
   1161     // Copy all the remaining arguments.
   1162     for (const std::string& arg : flag::getArgs()) {
   1163         options.input.push_back(Source{ arg });
   1164     }
   1165     return options;
   1166 }
   1167 
   1168 static bool doDump(const AaptOptions& options) {
   1169     for (const Source& source : options.input) {
   1170         std::unique_ptr<ZipFile> zipFile = util::make_unique<ZipFile>();
   1171         if (zipFile->open(source.path.data(), ZipFile::kOpenReadOnly) != android::NO_ERROR) {
   1172             Logger::error(source) << "failed to open: " << strerror(errno) << std::endl;
   1173             return false;
   1174         }
   1175 
   1176         std::shared_ptr<ResourceTable> table = std::make_shared<ResourceTable>();
   1177         std::shared_ptr<ResourceTableResolver> resolver =
   1178                 std::make_shared<ResourceTableResolver>(
   1179                         table, std::vector<std::shared_ptr<const android::AssetManager>>());
   1180 
   1181         ZipEntry* entry = zipFile->getEntryByName("resources.arsc");
   1182         if (!entry) {
   1183             Logger::error(source) << "missing 'resources.arsc'." << std::endl;
   1184             return false;
   1185         }
   1186 
   1187         std::unique_ptr<void, DeleteMalloc> uncompressedData = std::unique_ptr<void, DeleteMalloc>(
   1188                 zipFile->uncompress(entry));
   1189         assert(uncompressedData);
   1190 
   1191         BinaryResourceParser parser(table, resolver, source, {}, uncompressedData.get(),
   1192                                     entry->getUncompressedLen());
   1193         if (!parser.parse()) {
   1194             return false;
   1195         }
   1196 
   1197         if (options.phase == AaptOptions::Phase::Dump) {
   1198             Debug::printTable(table);
   1199         } else if (options.phase == AaptOptions::Phase::DumpStyleGraph) {
   1200             Debug::printStyleGraph(table, options.dumpStyleTarget);
   1201         }
   1202     }
   1203     return true;
   1204 }
   1205 
   1206 int main(int argc, char** argv) {
   1207     Logger::setLog(std::make_shared<Log>(std::cerr, std::cerr));
   1208     AaptOptions options = prepareArgs(argc, argv);
   1209 
   1210     if (options.phase == AaptOptions::Phase::Dump ||
   1211             options.phase == AaptOptions::Phase::DumpStyleGraph) {
   1212         if (!doDump(options)) {
   1213             return 1;
   1214         }
   1215         return 0;
   1216     }
   1217 
   1218     // If we specified a manifest, go ahead and load the package name from the manifest.
   1219     if (!options.manifest.path.empty()) {
   1220         if (!loadAppInfo(options.manifest, &options.appInfo)) {
   1221             return false;
   1222         }
   1223 
   1224         if (options.appInfo.package.empty()) {
   1225             Logger::error() << "no package name specified." << std::endl;
   1226             return false;
   1227         }
   1228     }
   1229 
   1230     // Every phase needs a resource table.
   1231     std::shared_ptr<ResourceTable> table = std::make_shared<ResourceTable>();
   1232 
   1233     // The package name is empty when in the compile phase.
   1234     table->setPackage(options.appInfo.package);
   1235     if (options.appInfo.package == u"android") {
   1236         table->setPackageId(0x01);
   1237     } else {
   1238         table->setPackageId(0x7f);
   1239     }
   1240 
   1241     // Load the included libraries.
   1242     std::vector<std::shared_ptr<const android::AssetManager>> sources;
   1243     for (const Source& source : options.libraries) {
   1244         std::shared_ptr<android::AssetManager> assetManager =
   1245                 std::make_shared<android::AssetManager>();
   1246         int32_t cookie;
   1247         if (!assetManager->addAssetPath(android::String8(source.path.data()), &cookie)) {
   1248             Logger::error(source) << "failed to load library." << std::endl;
   1249             return false;
   1250         }
   1251 
   1252         if (cookie == 0) {
   1253             Logger::error(source) << "failed to load library." << std::endl;
   1254             return false;
   1255         }
   1256         sources.push_back(assetManager);
   1257     }
   1258 
   1259     // Make the resolver that will cache IDs for us.
   1260     std::shared_ptr<ResourceTableResolver> resolver = std::make_shared<ResourceTableResolver>(
   1261             table, sources);
   1262 
   1263     if (options.phase == AaptOptions::Phase::Compile) {
   1264         if (!compile(options, table, resolver)) {
   1265             Logger::error() << "aapt exiting with failures." << std::endl;
   1266             return 1;
   1267         }
   1268     } else if (options.phase == AaptOptions::Phase::Link) {
   1269         if (!link(options, table, resolver)) {
   1270             Logger::error() << "aapt exiting with failures." << std::endl;
   1271             return 1;
   1272         }
   1273     }
   1274     return 0;
   1275 }
   1276