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