blob: cfc5874cdd7f53ac30efd7f204afe4873c283aae [file] [log] [blame]
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001/*
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 "Files.h"
21#include "JavaClassGenerator.h"
22#include "Linker.h"
23#include "ManifestParser.h"
24#include "ManifestValidator.h"
25#include "ResourceParser.h"
26#include "ResourceTable.h"
27#include "ResourceValues.h"
28#include "SdkConstants.h"
29#include "SourceXmlPullParser.h"
30#include "StringPiece.h"
31#include "TableFlattener.h"
32#include "Util.h"
33#include "XmlFlattener.h"
34
35#include <algorithm>
36#include <androidfw/AssetManager.h>
37#include <cstdlib>
38#include <dirent.h>
39#include <errno.h>
40#include <fstream>
41#include <iostream>
42#include <sstream>
43#include <sys/stat.h>
44
45using namespace aapt;
46
47void printTable(const ResourceTable& table) {
48 std::cout << "ResourceTable package=" << table.getPackage();
49 if (table.getPackageId() != ResourceTable::kUnsetPackageId) {
50 std::cout << " id=" << std::hex << table.getPackageId() << std::dec;
51 }
52 std::cout << std::endl
53 << "---------------------------------------------------------" << std::endl;
54
55 for (const auto& type : table) {
56 std::cout << "Type " << type->type;
57 if (type->typeId != ResourceTableType::kUnsetTypeId) {
58 std::cout << " [" << type->typeId << "]";
59 }
60 std::cout << " (" << type->entries.size() << " entries)" << std::endl;
61 for (const auto& entry : type->entries) {
62 std::cout << " " << entry->name;
63 if (entry->entryId != ResourceEntry::kUnsetEntryId) {
64 std::cout << " [" << entry->entryId << "]";
65 }
66 std::cout << " (" << entry->values.size() << " configurations)";
67 if (entry->publicStatus.isPublic) {
68 std::cout << " PUBLIC";
69 }
70 std::cout << std::endl;
71 for (const auto& value : entry->values) {
72 std::cout << " " << value.config << " (" << value.source << ") : ";
73 value.value->print(std::cout);
74 std::cout << std::endl;
75 }
76 }
77 }
78}
79
80void printStringPool(const StringPool& pool) {
81 std::cout << "String pool of length " << pool.size() << std::endl
82 << "---------------------------------------------------------" << std::endl;
83
84 size_t i = 0;
85 for (const auto& entry : pool) {
86 std::cout << "[" << i << "]: "
87 << entry->value
88 << " (Priority " << entry->context.priority
89 << ", Config '" << entry->context.config << "')"
90 << std::endl;
91 i++;
92 }
93}
94
95std::unique_ptr<FileReference> makeFileReference(StringPool& pool, const StringPiece& filename,
96 ResourceType type, const ConfigDescription& config) {
97 std::stringstream path;
98 path << "res/" << type;
99 if (config != ConfigDescription{}) {
100 path << "-" << config;
101 }
102 path << "/" << filename;
103 return util::make_unique<FileReference>(pool.makeRef(util::utf8ToUtf16(path.str())));
104}
105
106/**
107 * Collect files from 'root', filtering out any files that do not
108 * match the FileFilter 'filter'.
109 */
110bool walkTree(const StringPiece& root, const FileFilter& filter,
111 std::vector<Source>& outEntries) {
112 bool error = false;
113
114 for (const std::string& dirName : listFiles(root)) {
115 std::string dir(root.toString());
116 appendPath(&dir, dirName);
117
118 FileType ft = getFileType(dir);
119 if (!filter(dirName, ft)) {
120 continue;
121 }
122
123 if (ft != FileType::kDirectory) {
124 continue;
125 }
126
127 for (const std::string& fileName : listFiles(dir)) {
128 std::string file(dir);
129 appendPath(&file, fileName);
130
131 FileType ft = getFileType(file);
132 if (!filter(fileName, ft)) {
133 continue;
134 }
135
136 if (ft != FileType::kRegular) {
137 Logger::error(Source{ file })
138 << "not a regular file."
139 << std::endl;
140 error = true;
141 continue;
142 }
143 outEntries.emplace_back(Source{ file });
144 }
145 }
146 return !error;
147}
148
149bool loadBinaryResourceTable(std::shared_ptr<ResourceTable> table, const Source& source) {
150 std::ifstream ifs(source.path, std::ifstream::in | std::ifstream::binary);
151 if (!ifs) {
152 Logger::error(source) << strerror(errno) << std::endl;
153 return false;
154 }
155
156 std::streampos fsize = ifs.tellg();
157 ifs.seekg(0, std::ios::end);
158 fsize = ifs.tellg() - fsize;
159 ifs.seekg(0, std::ios::beg);
160
161 assert(fsize >= 0);
162 size_t dataSize = static_cast<size_t>(fsize);
163 char* buf = new char[dataSize];
164 ifs.read(buf, dataSize);
165
166 BinaryResourceParser parser(table, source, buf, dataSize);
167 bool result = parser.parse();
168
169 delete [] buf;
170 return result;
171}
172
173bool loadResTable(android::ResTable* table, const Source& source) {
Adam Lesinskica2fc352015-04-03 12:08:26 -0700174 // For NO_ERROR (which on Windows is a MACRO).
175 using namespace android;
176
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800177 std::ifstream ifs(source.path, std::ifstream::in | std::ifstream::binary);
178 if (!ifs) {
179 Logger::error(source) << strerror(errno) << std::endl;
180 return false;
181 }
182
183 std::streampos fsize = ifs.tellg();
184 ifs.seekg(0, std::ios::end);
185 fsize = ifs.tellg() - fsize;
186 ifs.seekg(0, std::ios::beg);
187
188 assert(fsize >= 0);
189 size_t dataSize = static_cast<size_t>(fsize);
190 char* buf = new char[dataSize];
191 ifs.read(buf, dataSize);
192
Adam Lesinskica2fc352015-04-03 12:08:26 -0700193 bool result = table->add(buf, dataSize, -1, true) == NO_ERROR;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800194
195 delete [] buf;
196 return result;
197}
198
199void versionStylesForCompat(std::shared_ptr<ResourceTable> table) {
200 for (auto& type : *table) {
201 if (type->type != ResourceType::kStyle) {
202 continue;
203 }
204
205 for (auto& entry : type->entries) {
206 // Add the versioned styles we want to create
207 // here. They are added to the table after
208 // iterating over the original set of styles.
209 //
210 // A stack is used since auto-generated styles
211 // from later versions should override
212 // auto-generated styles from earlier versions.
213 // Iterating over the styles is done in order,
214 // so we will always visit sdkVersions from smallest
215 // to largest.
216 std::stack<ResourceConfigValue> addStack;
217
218 for (ResourceConfigValue& configValue : entry->values) {
219 visitFunc<Style>(*configValue.value, [&](Style& style) {
220 // Collect which entries we've stripped and the smallest
221 // SDK level which was stripped.
222 size_t minSdkStripped = std::numeric_limits<size_t>::max();
223 std::vector<Style::Entry> stripped;
224
225 // Iterate over the style's entries and erase/record the
226 // attributes whose SDK level exceeds the config's sdkVersion.
227 auto iter = style.entries.begin();
228 while (iter != style.entries.end()) {
229 if (iter->key.name.package == u"android") {
230 size_t sdkLevel = findAttributeSdkLevel(iter->key.name.entry);
231 if (sdkLevel > 1 && sdkLevel > configValue.config.sdkVersion) {
232 // Record that we are about to strip this.
233 stripped.emplace_back(std::move(*iter));
234 minSdkStripped = std::min(minSdkStripped, sdkLevel);
235
236 // Erase this from this style.
237 iter = style.entries.erase(iter);
238 continue;
239 }
240 }
241 ++iter;
242 }
243
244 if (!stripped.empty()) {
245 // We have stripped attributes, so let's create a new style to hold them.
246 ConfigDescription versionConfig(configValue.config);
247 versionConfig.sdkVersion = minSdkStripped;
248
249 ResourceConfigValue value = {
250 versionConfig,
251 configValue.source,
252 {},
253
254 // Create a copy of the original style.
255 std::unique_ptr<Value>(configValue.value->clone())
256 };
257
258 Style& newStyle = static_cast<Style&>(*value.value);
259
260 // Move the recorded stripped attributes into this new style.
261 std::move(stripped.begin(), stripped.end(),
262 std::back_inserter(newStyle.entries));
263
264 // We will add this style to the table later. If we do it now, we will
265 // mess up iteration.
266 addStack.push(std::move(value));
267 }
268 });
269 }
270
271 auto comparator =
272 [](const ResourceConfigValue& lhs, const ConfigDescription& rhs) -> bool {
273 return lhs.config < rhs;
274 };
275
276 while (!addStack.empty()) {
277 ResourceConfigValue& value = addStack.top();
278 auto iter = std::lower_bound(entry->values.begin(), entry->values.end(),
279 value.config, comparator);
280 if (iter == entry->values.end() || iter->config != value.config) {
281 entry->values.insert(iter, std::move(value));
282 }
283 addStack.pop();
284 }
285 }
286 }
287}
288
289bool collectXml(std::shared_ptr<ResourceTable> table, const Source& source,
290 const ResourceName& name,
291 const ConfigDescription& config) {
292 std::ifstream in(source.path, std::ifstream::binary);
293 if (!in) {
294 Logger::error(source) << strerror(errno) << std::endl;
295 return false;
296 }
297
298 std::set<size_t> sdkLevels;
299
300 SourceXmlPullParser pullParser(in);
301 while (XmlPullParser::isGoodEvent(pullParser.next())) {
302 if (pullParser.getEvent() != XmlPullParser::Event::kStartElement) {
303 continue;
304 }
305
306 const auto endIter = pullParser.endAttributes();
307 for (auto iter = pullParser.beginAttributes(); iter != endIter; ++iter) {
308 if (iter->namespaceUri == u"http://schemas.android.com/apk/res/android") {
309 size_t sdkLevel = findAttributeSdkLevel(iter->name);
310 if (sdkLevel > 1) {
311 sdkLevels.insert(sdkLevel);
312 }
313 }
314
315 ResourceNameRef refName;
316 bool create = false;
317 bool privateRef = false;
318 if (ResourceParser::tryParseReference(iter->value, &refName, &create, &privateRef) &&
319 create) {
320 table->addResource(refName, {}, source.line(pullParser.getLineNumber()),
321 util::make_unique<Id>());
322 }
323 }
324 }
325
326 std::unique_ptr<FileReference> fileResource = makeFileReference(
327 table->getValueStringPool(),
328 util::utf16ToUtf8(name.entry) + ".xml",
329 name.type,
330 config);
331 table->addResource(name, config, source.line(0), std::move(fileResource));
332
333 for (size_t level : sdkLevels) {
334 Logger::note(source)
335 << "creating v" << level << " versioned file."
336 << std::endl;
337 ConfigDescription newConfig = config;
338 newConfig.sdkVersion = level;
339
340 std::unique_ptr<FileReference> fileResource = makeFileReference(
341 table->getValueStringPool(),
342 util::utf16ToUtf8(name.entry) + ".xml",
343 name.type,
344 newConfig);
345 table->addResource(name, newConfig, source.line(0), std::move(fileResource));
346 }
347 return true;
348}
349
350struct CompileXml {
351 Source source;
352 ResourceName name;
353 ConfigDescription config;
354};
355
356bool compileXml(std::shared_ptr<Resolver> resolver, const CompileXml& item,
357 const Source& outputSource, std::queue<CompileXml>* queue) {
358 std::ifstream in(item.source.path, std::ifstream::binary);
359 if (!in) {
360 Logger::error(item.source) << strerror(errno) << std::endl;
361 return false;
362 }
363
364 BigBuffer outBuffer(1024);
365 std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<SourceXmlPullParser>(in);
366 XmlFlattener flattener(resolver);
367
368 // We strip attributes that do not belong in this version of the resource.
369 // Non-version qualified resources have an implicit version 1 requirement.
370 XmlFlattener::Options options = { item.config.sdkVersion ? item.config.sdkVersion : 1 };
371 Maybe<size_t> minStrippedSdk = flattener.flatten(item.source, xmlParser, &outBuffer, options);
372 if (!minStrippedSdk) {
373 return false;
374 }
375
376 if (minStrippedSdk.value() > 0) {
377 // Something was stripped, so let's generate a new file
378 // with the version of the smallest SDK version stripped.
379 CompileXml newWork = item;
380 newWork.config.sdkVersion = minStrippedSdk.value();
381 queue->push(newWork);
382 }
383
384 std::ofstream out(outputSource.path, std::ofstream::binary);
385 if (!out) {
386 Logger::error(outputSource) << strerror(errno) << std::endl;
387 return false;
388 }
389
390 if (!util::writeAll(out, outBuffer)) {
391 Logger::error(outputSource) << strerror(errno) << std::endl;
392 return false;
393 }
394 return true;
395}
396
397struct AaptOptions {
398 enum class Phase {
399 LegacyFull,
400 Collect,
401 Link,
402 Compile,
403 };
404
405 // The phase to process.
406 Phase phase;
407
408 // Details about the app.
409 AppInfo appInfo;
410
411 // The location of the manifest file.
412 Source manifest;
413
414 // The files to process.
415 std::vector<Source> sources;
416
417 // The libraries these files may reference.
418 std::vector<Source> libraries;
419
420 // Output directory.
421 Source output;
422
423 // Whether to generate a Java Class.
424 Maybe<Source> generateJavaClass;
425
426 // Whether to output verbose details about
427 // compilation.
428 bool verbose = false;
429};
430
431bool compileAndroidManifest(std::shared_ptr<Resolver> resolver, const AaptOptions& options) {
Adam Lesinskica2fc352015-04-03 12:08:26 -0700432 using namespace android;
433
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800434 Source outSource = options.output;
435 appendPath(&outSource.path, "AndroidManifest.xml");
436
437 if (options.verbose) {
438 Logger::note(outSource) << "compiling AndroidManifest.xml." << std::endl;
439 }
440
441 std::ifstream in(options.manifest.path, std::ifstream::binary);
442 if (!in) {
443 Logger::error(options.manifest) << strerror(errno) << std::endl;
444 return false;
445 }
446
447 BigBuffer outBuffer(1024);
448 std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<SourceXmlPullParser>(in);
449 XmlFlattener flattener(resolver);
450
451 Maybe<size_t> result = flattener.flatten(options.manifest, xmlParser, &outBuffer,
452 XmlFlattener::Options{});
453 if (!result) {
454 return false;
455 }
456
457 std::unique_ptr<uint8_t[]> data = std::unique_ptr<uint8_t[]>(new uint8_t[outBuffer.size()]);
458 uint8_t* p = data.get();
459 for (const auto& b : outBuffer) {
460 memcpy(p, b.buffer.get(), b.size);
461 p += b.size;
462 }
463
Adam Lesinskica2fc352015-04-03 12:08:26 -0700464 ResXMLTree tree;
465 if (tree.setTo(data.get(), outBuffer.size()) != NO_ERROR) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800466 return false;
467 }
468
469 ManifestValidator validator(resolver->getResTable());
470 if (!validator.validate(options.manifest, &tree)) {
471 return false;
472 }
473
474 std::ofstream out(outSource.path, std::ofstream::binary);
475 if (!out) {
476 Logger::error(outSource) << strerror(errno) << std::endl;
477 return false;
478 }
479
480 if (!util::writeAll(out, outBuffer)) {
481 Logger::error(outSource) << strerror(errno) << std::endl;
482 return false;
483 }
484 return true;
485}
486
487bool loadAppInfo(const Source& source, AppInfo* outInfo) {
488 std::ifstream ifs(source.path, std::ifstream::in | std::ifstream::binary);
489 if (!ifs) {
490 Logger::error(source) << strerror(errno) << std::endl;
491 return false;
492 }
493
494 ManifestParser parser;
495 std::shared_ptr<XmlPullParser> pullParser = std::make_shared<SourceXmlPullParser>(ifs);
496 return parser.parse(source, pullParser, outInfo);
497}
498
499/**
500 * Parses legacy options and walks the source directories collecting
501 * files to process.
502 */
503bool prepareLegacy(std::vector<StringPiece>::const_iterator argsIter,
504 const std::vector<StringPiece>::const_iterator argsEndIter,
505 AaptOptions &options) {
506 options.phase = AaptOptions::Phase::LegacyFull;
507
508 std::vector<StringPiece> sourceDirs;
509 while (argsIter != argsEndIter) {
510 if (*argsIter == "-S") {
511 ++argsIter;
512 if (argsIter == argsEndIter) {
513 Logger::error() << "-S missing argument." << std::endl;
514 return false;
515 }
516 sourceDirs.push_back(*argsIter);
517 } else if (*argsIter == "-I") {
518 ++argsIter;
519 if (argsIter == argsEndIter) {
520 Logger::error() << "-I missing argument." << std::endl;
521 return false;
522 }
523 options.libraries.push_back(Source{ argsIter->toString() });
524 } else if (*argsIter == "-M") {
525 ++argsIter;
526 if (argsIter == argsEndIter) {
527 Logger::error() << "-M missing argument." << std::endl;
528 return false;
529 }
530
531 if (!options.manifest.path.empty()) {
532 Logger::error() << "multiple -M flags are not allowed." << std::endl;
533 return false;
534 }
535 options.manifest.path = argsIter->toString();
536 } else if (*argsIter == "-o") {
537 ++argsIter;
538 if (argsIter == argsEndIter) {
539 Logger::error() << "-o missing argument." << std::endl;
540 return false;
541 }
542 options.output = Source{ argsIter->toString() };
543 } else if (*argsIter == "-J") {
544 ++argsIter;
545 if (argsIter == argsEndIter) {
546 Logger::error() << "-J missing argument." << std::endl;
547 return false;
548 }
549 options.generateJavaClass = make_value<Source>(Source{ argsIter->toString() });
550 } else if (*argsIter == "-v") {
551 options.verbose = true;
552 } else {
553 Logger::error() << "unrecognized option '" << *argsIter << "'." << std::endl;
554 return false;
555 }
556
557 ++argsIter;
558 }
559
560 if (options.manifest.path.empty()) {
561 Logger::error() << "must specify manifest file with -M." << std::endl;
562 return false;
563 }
564
565 // Load the App's package name, etc.
566 if (!loadAppInfo(options.manifest, &options.appInfo)) {
567 return false;
568 }
569
570 /**
571 * Set up the file filter to ignore certain files.
572 */
573 const char* customIgnore = getenv("ANDROID_AAPT_IGNORE");
574 FileFilter fileFilter;
575 if (customIgnore && customIgnore[0]) {
576 fileFilter.setPattern(customIgnore);
577 } else {
578 fileFilter.setPattern(
579 "!.svn:!.git:!.ds_store:!*.scc:.*:<dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~");
580 }
581
582 /*
583 * Enumerate the files in each source directory.
584 */
585 for (const StringPiece& source : sourceDirs) {
586 if (!walkTree(source, fileFilter, options.sources)) {
587 return false;
588 }
589 }
590 return true;
591}
592
593bool prepareCollect(std::vector<StringPiece>::const_iterator argsIter,
594 const std::vector<StringPiece>::const_iterator argsEndIter,
595 AaptOptions& options) {
596 options.phase = AaptOptions::Phase::Collect;
597
598 while (argsIter != argsEndIter) {
599 if (*argsIter == "--package") {
600 ++argsIter;
601 if (argsIter == argsEndIter) {
602 Logger::error() << "--package missing argument." << std::endl;
603 return false;
604 }
605 options.appInfo.package = util::utf8ToUtf16(*argsIter);
606 } else if (*argsIter == "-o") {
607 ++argsIter;
608 if (argsIter == argsEndIter) {
609 Logger::error() << "-o missing argument." << std::endl;
610 return false;
611 }
612 options.output = Source{ argsIter->toString() };
613 } else if (*argsIter == "-v") {
614 options.verbose = true;
615 } else if (argsIter->data()[0] != '-') {
616 options.sources.push_back(Source{ argsIter->toString() });
617 } else {
618 Logger::error()
619 << "unknown option '"
620 << *argsIter
621 << "'."
622 << std::endl;
623 return false;
624 }
625 ++argsIter;
626 }
627 return true;
628}
629
630bool prepareLink(std::vector<StringPiece>::const_iterator argsIter,
631 const std::vector<StringPiece>::const_iterator argsEndIter,
632 AaptOptions& options) {
633 options.phase = AaptOptions::Phase::Link;
634
635 while (argsIter != argsEndIter) {
636 if (*argsIter == "--package") {
637 ++argsIter;
638 if (argsIter == argsEndIter) {
639 Logger::error() << "--package missing argument." << std::endl;
640 return false;
641 }
642 options.appInfo.package = util::utf8ToUtf16(*argsIter);
643 } else if (*argsIter == "-o") {
644 ++argsIter;
645 if (argsIter == argsEndIter) {
646 Logger::error() << "-o missing argument." << std::endl;
647 return false;
648 }
649 options.output = Source{ argsIter->toString() };
650 } else if (*argsIter == "-I") {
651 ++argsIter;
652 if (argsIter == argsEndIter) {
653 Logger::error() << "-I missing argument." << std::endl;
654 return false;
655 }
656 options.libraries.push_back(Source{ argsIter->toString() });
657 } else if (*argsIter == "--java") {
658 ++argsIter;
659 if (argsIter == argsEndIter) {
660 Logger::error() << "--java missing argument." << std::endl;
661 return false;
662 }
663 options.generateJavaClass = make_value<Source>(Source{ argsIter->toString() });
664 } else if (*argsIter == "-v") {
665 options.verbose = true;
666 } else if (argsIter->data()[0] != '-') {
667 options.sources.push_back(Source{ argsIter->toString() });
668 } else {
669 Logger::error()
670 << "unknown option '"
671 << *argsIter
672 << "'."
673 << std::endl;
674 return false;
675 }
676 ++argsIter;
677 }
678 return true;
679}
680
681bool prepareCompile(std::vector<StringPiece>::const_iterator argsIter,
682 const std::vector<StringPiece>::const_iterator argsEndIter,
683 AaptOptions& options) {
684 options.phase = AaptOptions::Phase::Compile;
685
686 while (argsIter != argsEndIter) {
687 if (*argsIter == "--package") {
688 ++argsIter;
689 if (argsIter == argsEndIter) {
690 Logger::error() << "--package missing argument." << std::endl;
691 return false;
692 }
693 options.appInfo.package = util::utf8ToUtf16(*argsIter);
694 } else if (*argsIter == "-o") {
695 ++argsIter;
696 if (argsIter == argsEndIter) {
697 Logger::error() << "-o missing argument." << std::endl;
698 return false;
699 }
700 options.output = Source{ argsIter->toString() };
701 } else if (*argsIter == "-I") {
702 ++argsIter;
703 if (argsIter == argsEndIter) {
704 Logger::error() << "-I missing argument." << std::endl;
705 return false;
706 }
707 options.libraries.push_back(Source{ argsIter->toString() });
708 } else if (*argsIter == "-v") {
709 options.verbose = true;
710 } else if (argsIter->data()[0] != '-') {
711 options.sources.push_back(Source{ argsIter->toString() });
712 } else {
713 Logger::error()
714 << "unknown option '"
715 << *argsIter
716 << "'."
717 << std::endl;
718 return false;
719 }
720 ++argsIter;
721 }
722 return true;
723}
724
725struct CollectValuesItem {
726 Source source;
727 ConfigDescription config;
728};
729
730bool collectValues(std::shared_ptr<ResourceTable> table, const CollectValuesItem& item) {
731 std::ifstream in(item.source.path, std::ifstream::binary);
732 if (!in) {
733 Logger::error(item.source) << strerror(errno) << std::endl;
734 return false;
735 }
736
737 std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<SourceXmlPullParser>(in);
738 ResourceParser parser(table, item.source, item.config, xmlParser);
739 return parser.parse();
740}
741
742struct ResourcePathData {
743 std::u16string resourceDir;
744 std::u16string name;
745 std::string extension;
746 ConfigDescription config;
747};
748
749/**
750 * Resource file paths are expected to look like:
751 * [--/res/]type[-config]/name
752 */
753Maybe<ResourcePathData> extractResourcePathData(const Source& source) {
754 std::vector<std::string> parts = util::splitAndLowercase(source.path, '/');
755 if (parts.size() < 2) {
756 Logger::error(source) << "bad resource path." << std::endl;
757 return {};
758 }
759
760 std::string& dir = parts[parts.size() - 2];
761 StringPiece dirStr = dir;
762
763 ConfigDescription config;
764 size_t dashPos = dir.find('-');
765 if (dashPos != std::string::npos) {
766 StringPiece configStr = dirStr.substr(dashPos + 1, dir.size() - (dashPos + 1));
767 if (!ConfigDescription::parse(configStr, &config)) {
768 Logger::error(source)
769 << "invalid configuration '"
770 << configStr
771 << "'."
772 << std::endl;
773 return {};
774 }
775 dirStr = dirStr.substr(0, dashPos);
776 }
777
778 std::string& filename = parts[parts.size() - 1];
779 StringPiece name = filename;
780 StringPiece extension;
781 size_t dotPos = filename.find('.');
782 if (dotPos != std::string::npos) {
783 extension = name.substr(dotPos + 1, filename.size() - (dotPos + 1));
784 name = name.substr(0, dotPos);
785 }
786
787 return ResourcePathData{
788 util::utf8ToUtf16(dirStr),
789 util::utf8ToUtf16(name),
790 extension.toString(),
791 config
792 };
793}
794
795static bool doLegacy(std::shared_ptr<ResourceTable> table, std::shared_ptr<Resolver> resolver,
796 const AaptOptions& options) {
797 bool error = false;
798 std::queue<CompileXml> xmlCompileQueue;
799
800 //
801 // Read values XML files and XML/PNG files.
802 // Need to parse the resource type/config/filename.
803 //
804 for (const Source& source : options.sources) {
805 Maybe<ResourcePathData> maybePathData = extractResourcePathData(source);
806 if (!maybePathData) {
807 return false;
808 }
809
810 const ResourcePathData& pathData = maybePathData.value();
811 if (pathData.resourceDir == u"values") {
812 if (options.verbose) {
813 Logger::note(source) << "collecting values..." << std::endl;
814 }
815
816 error |= !collectValues(table, CollectValuesItem{ source, pathData.config });
817 continue;
818 }
819
820 const ResourceType* type = parseResourceType(pathData.resourceDir);
821 if (!type) {
822 Logger::error(source)
823 << "invalid resource type '"
824 << pathData.resourceDir
825 << "'."
826 << std::endl;
827 return false;
828 }
829
830 ResourceName resourceName = { table->getPackage(), *type, pathData.name };
831 if (pathData.extension == "xml") {
832 if (options.verbose) {
833 Logger::note(source) << "collecting XML..." << std::endl;
834 }
835
836 error |= !collectXml(table, source, resourceName, pathData.config);
837 xmlCompileQueue.push(CompileXml{
838 source,
839 resourceName,
840 pathData.config
841 });
842 } else {
843 std::unique_ptr<FileReference> fileReference = makeFileReference(
844 table->getValueStringPool(),
845 util::utf16ToUtf8(pathData.name) + "." + pathData.extension,
846 *type, pathData.config);
847
848 error |= !table->addResource(resourceName, pathData.config, source.line(0),
849 std::move(fileReference));
850 }
851 }
852
853 if (error) {
854 return false;
855 }
856
857 versionStylesForCompat(table);
858
859 //
860 // Verify all references and data types.
861 //
862 Linker linker(table, resolver);
863 if (!linker.linkAndValidate()) {
864 Logger::error()
865 << "linking failed."
866 << std::endl;
867 return false;
868 }
869
870 const auto& unresolvedRefs = linker.getUnresolvedReferences();
871 if (!unresolvedRefs.empty()) {
872 for (const auto& entry : unresolvedRefs) {
873 for (const auto& source : entry.second) {
874 Logger::error(source)
875 << "unresolved symbol '"
876 << entry.first
877 << "'."
878 << std::endl;
879 }
880 }
881 return false;
882 }
883
884 //
885 // Compile the XML files.
886 //
887 while (!xmlCompileQueue.empty()) {
888 const CompileXml& item = xmlCompileQueue.front();
889
890 // Create the output path from the resource name.
891 std::stringstream outputPath;
892 outputPath << item.name.type;
893 if (item.config != ConfigDescription{}) {
894 outputPath << "-" << item.config.toString();
895 }
896
897 Source outSource = options.output;
898 appendPath(&outSource.path, "res");
899 appendPath(&outSource.path, outputPath.str());
900
901 if (!mkdirs(outSource.path)) {
902 Logger::error(outSource) << strerror(errno) << std::endl;
903 return false;
904 }
905
906 appendPath(&outSource.path, util::utf16ToUtf8(item.name.entry) + ".xml");
907
908 if (options.verbose) {
909 Logger::note(outSource) << "compiling XML file." << std::endl;
910 }
911
912 error |= !compileXml(resolver, item, outSource, &xmlCompileQueue);
913 xmlCompileQueue.pop();
914 }
915
916 if (error) {
917 return false;
918 }
919
920 //
921 // Compile the AndroidManifest.xml file.
922 //
923 if (!compileAndroidManifest(resolver, options)) {
924 return false;
925 }
926
927 //
928 // Generate the Java R class.
929 //
930 if (options.generateJavaClass) {
931 Source outPath = options.generateJavaClass.value();
932 if (options.verbose) {
933 Logger::note()
934 << "writing symbols to "
935 << outPath
936 << "."
937 << std::endl;
938 }
939
940 for (std::string& part : util::split(util::utf16ToUtf8(table->getPackage()), '.')) {
941 appendPath(&outPath.path, part);
942 }
943
944 if (!mkdirs(outPath.path)) {
945 Logger::error(outPath) << strerror(errno) << std::endl;
946 return false;
947 }
948
949 appendPath(&outPath.path, "R.java");
950
951 std::ofstream fout(outPath.path);
952 if (!fout) {
953 Logger::error(outPath) << strerror(errno) << std::endl;
954 return false;
955 }
956
957 JavaClassGenerator generator(table, JavaClassGenerator::Options{});
958 if (!generator.generate(fout)) {
959 Logger::error(outPath)
960 << generator.getError()
961 << "."
962 << std::endl;
963 return false;
964 }
965 }
966
967 //
968 // Flatten resource table.
969 //
970 if (table->begin() != table->end()) {
971 BigBuffer buffer(1024);
972 TableFlattener::Options tableOptions;
973 tableOptions.useExtendedChunks = false;
974 TableFlattener flattener(tableOptions);
975 if (!flattener.flatten(&buffer, *table)) {
976 Logger::error()
977 << "failed to flatten resource table->"
978 << std::endl;
979 return false;
980 }
981
982 if (options.verbose) {
983 Logger::note()
984 << "Final resource table size="
985 << util::formatSize(buffer.size())
986 << std::endl;
987 }
988
989 std::string outTable(options.output.path);
990 appendPath(&outTable, "resources.arsc");
991
992 std::ofstream fout(outTable, std::ofstream::binary);
993 if (!fout) {
994 Logger::error(Source{outTable})
995 << strerror(errno)
996 << "."
997 << std::endl;
998 return false;
999 }
1000
1001 if (!util::writeAll(fout, buffer)) {
1002 Logger::error(Source{outTable})
1003 << strerror(errno)
1004 << "."
1005 << std::endl;
1006 return false;
1007 }
1008 fout.flush();
1009 }
1010 return true;
1011}
1012
1013static bool doCollect(std::shared_ptr<ResourceTable> table, std::shared_ptr<Resolver> resolver,
1014 const AaptOptions& options) {
1015 bool error = false;
1016
1017 //
1018 // Read values XML files and XML/PNG files.
1019 // Need to parse the resource type/config/filename.
1020 //
1021 for (const Source& source : options.sources) {
1022 Maybe<ResourcePathData> maybePathData = extractResourcePathData(source);
1023 if (!maybePathData) {
1024 return false;
1025 }
1026
1027 const ResourcePathData& pathData = maybePathData.value();
1028 if (pathData.resourceDir == u"values") {
1029 if (options.verbose) {
1030 Logger::note(source) << "collecting values..." << std::endl;
1031 }
1032
1033 error |= !collectValues(table, CollectValuesItem{ source, pathData.config });
1034 continue;
1035 }
1036
1037 const ResourceType* type = parseResourceType(pathData.resourceDir);
1038 if (!type) {
1039 Logger::error(source)
1040 << "invalid resource type '"
1041 << pathData.resourceDir
1042 << "'."
1043 << std::endl;
1044 return false;
1045 }
1046
1047 ResourceName resourceName = { table->getPackage(), *type, pathData.name };
1048 if (pathData.extension == "xml") {
1049 if (options.verbose) {
1050 Logger::note(source) << "collecting XML..." << std::endl;
1051 }
1052
1053 error |= !collectXml(table, source, resourceName, pathData.config);
1054 } else {
1055 std::unique_ptr<FileReference> fileReference = makeFileReference(
1056 table->getValueStringPool(),
1057 util::utf16ToUtf8(pathData.name) + "." + pathData.extension,
1058 *type,
1059 pathData.config);
1060 error |= !table->addResource(resourceName, pathData.config, source.line(0),
1061 std::move(fileReference));
1062 }
1063 }
1064
1065 if (error) {
1066 return false;
1067 }
1068
1069 Linker linker(table, resolver);
1070 if (!linker.linkAndValidate()) {
1071 return false;
1072 }
1073
1074 //
1075 // Flatten resource table->
1076 //
1077 if (table->begin() != table->end()) {
1078 BigBuffer buffer(1024);
1079 TableFlattener::Options tableOptions;
1080 tableOptions.useExtendedChunks = true;
1081 TableFlattener flattener(tableOptions);
1082 if (!flattener.flatten(&buffer, *table)) {
1083 Logger::error()
1084 << "failed to flatten resource table->"
1085 << std::endl;
1086 return false;
1087 }
1088
1089 std::ofstream fout(options.output.path, std::ofstream::binary);
1090 if (!fout) {
1091 Logger::error(options.output)
1092 << strerror(errno)
1093 << "."
1094 << std::endl;
1095 return false;
1096 }
1097
1098 if (!util::writeAll(fout, buffer)) {
1099 Logger::error(options.output)
1100 << strerror(errno)
1101 << "."
1102 << std::endl;
1103 return false;
1104 }
1105 fout.flush();
1106 }
1107 return true;
1108}
1109
1110static bool doLink(std::shared_ptr<ResourceTable> table, std::shared_ptr<Resolver> resolver,
1111 const AaptOptions& options) {
1112 bool error = false;
1113
1114 for (const Source& source : options.sources) {
1115 error |= !loadBinaryResourceTable(table, source);
1116 }
1117
1118 if (error) {
1119 return false;
1120 }
1121
1122 versionStylesForCompat(table);
1123
1124 Linker linker(table, resolver);
1125 if (!linker.linkAndValidate()) {
1126 return false;
1127 }
1128
1129 const auto& unresolvedRefs = linker.getUnresolvedReferences();
1130 if (!unresolvedRefs.empty()) {
1131 for (const auto& entry : unresolvedRefs) {
1132 for (const auto& source : entry.second) {
1133 Logger::error(source)
1134 << "unresolved symbol '"
1135 << entry.first
1136 << "'."
1137 << std::endl;
1138 }
1139 }
1140 return false;
1141 }
1142
1143 //
1144 // Generate the Java R class.
1145 //
1146 if (options.generateJavaClass) {
1147 Source outPath = options.generateJavaClass.value();
1148 if (options.verbose) {
1149 Logger::note()
1150 << "writing symbols to "
1151 << outPath
1152 << "."
1153 << std::endl;
1154 }
1155
1156 for (std::string& part : util::split(util::utf16ToUtf8(table->getPackage()), '.')) {
1157 appendPath(&outPath.path, part);
1158 }
1159
1160 if (!mkdirs(outPath.path)) {
1161 Logger::error(outPath) << strerror(errno) << std::endl;
1162 return false;
1163 }
1164
1165 appendPath(&outPath.path, "R.java");
1166
1167 std::ofstream fout(outPath.path);
1168 if (!fout) {
1169 Logger::error(outPath) << strerror(errno) << std::endl;
1170 return false;
1171 }
1172
1173 JavaClassGenerator generator(table, JavaClassGenerator::Options{});
1174 if (!generator.generate(fout)) {
1175 Logger::error(outPath)
1176 << generator.getError()
1177 << "."
1178 << std::endl;
1179 return false;
1180 }
1181 }
1182
1183 //
1184 // Flatten resource table.
1185 //
1186 if (table->begin() != table->end()) {
1187 BigBuffer buffer(1024);
1188 TableFlattener::Options tableOptions;
1189 tableOptions.useExtendedChunks = false;
1190 TableFlattener flattener(tableOptions);
1191 if (!flattener.flatten(&buffer, *table)) {
1192 Logger::error()
1193 << "failed to flatten resource table->"
1194 << std::endl;
1195 return false;
1196 }
1197
1198 if (options.verbose) {
1199 Logger::note()
1200 << "Final resource table size="
1201 << util::formatSize(buffer.size())
1202 << std::endl;
1203 }
1204
1205 std::ofstream fout(options.output.path, std::ofstream::binary);
1206 if (!fout) {
1207 Logger::error(options.output)
1208 << strerror(errno)
1209 << "."
1210 << std::endl;
1211 return false;
1212 }
1213
1214 if (!util::writeAll(fout, buffer)) {
1215 Logger::error(options.output)
1216 << strerror(errno)
1217 << "."
1218 << std::endl;
1219 return false;
1220 }
1221 fout.flush();
1222 }
1223 return true;
1224}
1225
1226static bool doCompile(std::shared_ptr<ResourceTable> table, std::shared_ptr<Resolver> resolver,
1227 const AaptOptions& options) {
1228 std::queue<CompileXml> xmlCompileQueue;
1229
1230 for (const Source& source : options.sources) {
1231 Maybe<ResourcePathData> maybePathData = extractResourcePathData(source);
1232 if (!maybePathData) {
1233 return false;
1234 }
1235
1236 ResourcePathData& pathData = maybePathData.value();
1237 const ResourceType* type = parseResourceType(pathData.resourceDir);
1238 if (!type) {
1239 Logger::error(source)
1240 << "invalid resource type '"
1241 << pathData.resourceDir
1242 << "'."
1243 << std::endl;
1244 return false;
1245 }
1246
1247 ResourceName resourceName = { table->getPackage(), *type, pathData.name };
1248 if (pathData.extension == "xml") {
1249 xmlCompileQueue.push(CompileXml{
1250 source,
1251 resourceName,
1252 pathData.config
1253 });
1254 } else {
1255 // TODO(adamlesinski): Handle images here.
1256 }
1257 }
1258
1259 bool error = false;
1260 while (!xmlCompileQueue.empty()) {
1261 const CompileXml& item = xmlCompileQueue.front();
1262
1263 // Create the output path from the resource name.
1264 std::stringstream outputPath;
1265 outputPath << item.name.type;
1266 if (item.config != ConfigDescription{}) {
1267 outputPath << "-" << item.config.toString();
1268 }
1269
1270 Source outSource = options.output;
1271 appendPath(&outSource.path, "res");
1272 appendPath(&outSource.path, outputPath.str());
1273
1274 if (!mkdirs(outSource.path)) {
1275 Logger::error(outSource) << strerror(errno) << std::endl;
1276 return false;
1277 }
1278
1279 appendPath(&outSource.path, util::utf16ToUtf8(item.name.entry) + ".xml");
1280
1281 if (options.verbose) {
1282 Logger::note(outSource) << "compiling XML file." << std::endl;
1283 }
1284
1285 error |= !compileXml(resolver, item, outSource, &xmlCompileQueue);
1286 xmlCompileQueue.pop();
1287 }
1288 return !error;
1289}
1290
1291int main(int argc, char** argv) {
1292 Logger::setLog(std::make_shared<Log>(std::cerr, std::cerr));
1293
1294 std::vector<StringPiece> args;
1295 args.reserve(argc - 1);
1296 for (int i = 1; i < argc; i++) {
1297 args.emplace_back(argv[i], strlen(argv[i]));
1298 }
1299
1300 if (args.empty()) {
1301 Logger::error() << "no command specified." << std::endl;
1302 return 1;
1303 }
1304
1305 AaptOptions options;
1306
1307 // Check the command we're running.
1308 const StringPiece& command = args.front();
1309 if (command == "package") {
1310 if (!prepareLegacy(std::begin(args) + 1, std::end(args), options)) {
1311 return 1;
1312 }
1313 } else if (command == "collect") {
1314 if (!prepareCollect(std::begin(args) + 1, std::end(args), options)) {
1315 return 1;
1316 }
1317 } else if (command == "link") {
1318 if (!prepareLink(std::begin(args) + 1, std::end(args), options)) {
1319 return 1;
1320 }
1321 } else if (command == "compile") {
1322 if (!prepareCompile(std::begin(args) + 1, std::end(args), options)) {
1323 return 1;
1324 }
1325 } else {
1326 Logger::error() << "unknown command '" << command << "'." << std::endl;
1327 return 1;
1328 }
1329
1330 //
1331 // Verify we have some common options set.
1332 //
1333
1334 if (options.sources.empty()) {
1335 Logger::error() << "no sources specified." << std::endl;
1336 return false;
1337 }
1338
1339 if (options.output.path.empty()) {
1340 Logger::error() << "no output directory specified." << std::endl;
1341 return false;
1342 }
1343
1344 if (options.appInfo.package.empty()) {
1345 Logger::error() << "no package name specified." << std::endl;
1346 return false;
1347 }
1348
1349
1350 //
1351 // Every phase needs a resource table and a resolver/linker.
1352 //
1353
1354 std::shared_ptr<ResourceTable> table = std::make_shared<ResourceTable>();
1355 table->setPackage(options.appInfo.package);
1356 if (options.appInfo.package == u"android") {
1357 table->setPackageId(0x01);
1358 } else {
1359 table->setPackageId(0x7f);
1360 }
1361
1362 //
1363 // Load the included libraries.
1364 //
1365 std::shared_ptr<android::AssetManager> libraries = std::make_shared<android::AssetManager>();
1366 for (const Source& source : options.libraries) {
1367 if (util::stringEndsWith(source.path, ".arsc")) {
1368 // We'll process these last so as to avoid a cookie issue.
1369 continue;
1370 }
1371
1372 int32_t cookie;
1373 if (!libraries->addAssetPath(android::String8(source.path.data()), &cookie)) {
1374 Logger::error(source) << "failed to load library." << std::endl;
1375 return false;
1376 }
1377 }
1378
1379 for (const Source& source : options.libraries) {
1380 if (!util::stringEndsWith(source.path, ".arsc")) {
1381 // We've already processed this.
1382 continue;
1383 }
1384
1385 // Dirty hack but there is no other way to get a
1386 // writeable ResTable.
1387 if (!loadResTable(const_cast<android::ResTable*>(&libraries->getResources(false)),
1388 source)) {
1389 return false;
1390 }
1391 }
1392
1393 // Make the resolver that will cache IDs for us.
1394 std::shared_ptr<Resolver> resolver = std::make_shared<Resolver>(table, libraries);
1395
1396 //
1397 // Dispatch to the real phase here.
1398 //
1399
1400 bool result = true;
1401 switch (options.phase) {
1402 case AaptOptions::Phase::LegacyFull:
1403 result = doLegacy(table, resolver, options);
1404 break;
1405
1406 case AaptOptions::Phase::Collect:
1407 result = doCollect(table, resolver, options);
1408 break;
1409
1410 case AaptOptions::Phase::Link:
1411 result = doLink(table, resolver, options);
1412 break;
1413
1414 case AaptOptions::Phase::Compile:
1415 result = doCompile(table, resolver, options);
1416 break;
1417 }
1418
1419 if (!result) {
1420 Logger::error()
1421 << "aapt exiting with failures."
1422 << std::endl;
1423 return 1;
1424 }
1425 return 0;
1426}