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