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
56constexpr const char* kAaptVersionStr = "2.0-alpha";
57
58using namespace aapt;
59
60/**
61 * Used with smart pointers to free malloc'ed memory.
62 */
63struct DeleteMalloc {
64    void operator()(void* ptr) {
65        free(ptr);
66    }
67};
68
69struct 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 */
78bool 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
115void 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
206struct CompileItem {
207    ResourceName name;
208    ConfigDescription config;
209    Source source;
210    std::string extension;
211};
212
213struct LinkItem {
214    ResourceName name;
215    ConfigDescription config;
216    Source source;
217    std::string originalPath;
218    ZipFile* apk;
219    std::u16string originalPackage;
220};
221
222template <typename TChar>
223static 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
232std::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
246std::string buildFileReference(const CompileItem& item) {
247    return buildFileReference(item.name, item.config, item.extension);
248}
249
250std::string buildFileReference(const LinkItem& item) {
251    return buildFileReference(item.name, item.config, getExtension<char>(item.originalPath));
252}
253
254template <typename T>
255bool 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
263struct 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
321struct 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
352private:
353    Source mSource;
354    std::shared_ptr<ResourceTable> mTable;
355};
356
357bool 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 */
396bool 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
422bool 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
485bool 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
509bool 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
519bool 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
601static 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
614struct 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 */
625static 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
668bool 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 */
695static 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
728static constexpr int kOpenFlags = ZipFile::kOpenCreate | ZipFile::kOpenTruncate |
729        ZipFile::kOpenReadWrite;
730
731bool 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
950bool 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
1036bool 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
1048static 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
1059static 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
1168static 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
1206int 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