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