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