blob: 3a4b444c98d095e4e3d5789f77cb0927d479e5fd [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"
Adam Lesinski98aa3ad2015-04-06 11:46:52 -070021#include "Flag.h"
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080022#include "JavaClassGenerator.h"
23#include "Linker.h"
24#include "ManifestParser.h"
25#include "ManifestValidator.h"
Adam Lesinski98aa3ad2015-04-06 11:46:52 -070026#include "Png.h"
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080027#include "ResourceParser.h"
28#include "ResourceTable.h"
29#include "ResourceValues.h"
30#include "SdkConstants.h"
31#include "SourceXmlPullParser.h"
32#include "StringPiece.h"
33#include "TableFlattener.h"
34#include "Util.h"
35#include "XmlFlattener.h"
36
37#include <algorithm>
38#include <androidfw/AssetManager.h>
39#include <cstdlib>
40#include <dirent.h>
41#include <errno.h>
42#include <fstream>
43#include <iostream>
44#include <sstream>
45#include <sys/stat.h>
Adam Lesinski98aa3ad2015-04-06 11:46:52 -070046#include <utils/Errors.h>
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080047
48using namespace aapt;
49
50void printTable(const ResourceTable& table) {
51 std::cout << "ResourceTable package=" << table.getPackage();
52 if (table.getPackageId() != ResourceTable::kUnsetPackageId) {
53 std::cout << " id=" << std::hex << table.getPackageId() << std::dec;
54 }
55 std::cout << std::endl
56 << "---------------------------------------------------------" << std::endl;
57
58 for (const auto& type : table) {
59 std::cout << "Type " << type->type;
60 if (type->typeId != ResourceTableType::kUnsetTypeId) {
61 std::cout << " [" << type->typeId << "]";
62 }
63 std::cout << " (" << type->entries.size() << " entries)" << std::endl;
64 for (const auto& entry : type->entries) {
65 std::cout << " " << entry->name;
66 if (entry->entryId != ResourceEntry::kUnsetEntryId) {
67 std::cout << " [" << entry->entryId << "]";
68 }
69 std::cout << " (" << entry->values.size() << " configurations)";
70 if (entry->publicStatus.isPublic) {
71 std::cout << " PUBLIC";
72 }
73 std::cout << std::endl;
74 for (const auto& value : entry->values) {
75 std::cout << " " << value.config << " (" << value.source << ") : ";
76 value.value->print(std::cout);
77 std::cout << std::endl;
78 }
79 }
80 }
81}
82
83void printStringPool(const StringPool& pool) {
84 std::cout << "String pool of length " << pool.size() << std::endl
85 << "---------------------------------------------------------" << std::endl;
86
87 size_t i = 0;
88 for (const auto& entry : pool) {
89 std::cout << "[" << i << "]: "
90 << entry->value
91 << " (Priority " << entry->context.priority
92 << ", Config '" << entry->context.config << "')"
93 << std::endl;
94 i++;
95 }
96}
97
98std::unique_ptr<FileReference> makeFileReference(StringPool& pool, const StringPiece& filename,
99 ResourceType type, const ConfigDescription& config) {
100 std::stringstream path;
101 path << "res/" << type;
102 if (config != ConfigDescription{}) {
103 path << "-" << config;
104 }
105 path << "/" << filename;
106 return util::make_unique<FileReference>(pool.makeRef(util::utf8ToUtf16(path.str())));
107}
108
109/**
110 * Collect files from 'root', filtering out any files that do not
111 * match the FileFilter 'filter'.
112 */
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700113bool walkTree(const Source& root, const FileFilter& filter,
114 std::vector<Source>* outEntries) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800115 bool error = false;
116
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700117 for (const std::string& dirName : listFiles(root.path)) {
118 std::string dir = root.path;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800119 appendPath(&dir, dirName);
120
121 FileType ft = getFileType(dir);
122 if (!filter(dirName, ft)) {
123 continue;
124 }
125
126 if (ft != FileType::kDirectory) {
127 continue;
128 }
129
130 for (const std::string& fileName : listFiles(dir)) {
131 std::string file(dir);
132 appendPath(&file, fileName);
133
134 FileType ft = getFileType(file);
135 if (!filter(fileName, ft)) {
136 continue;
137 }
138
139 if (ft != FileType::kRegular) {
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700140 Logger::error(Source{ file }) << "not a regular file." << std::endl;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800141 error = true;
142 continue;
143 }
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700144 outEntries->push_back(Source{ file });
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800145 }
146 }
147 return !error;
148}
149
150bool loadBinaryResourceTable(std::shared_ptr<ResourceTable> table, const Source& source) {
151 std::ifstream ifs(source.path, std::ifstream::in | std::ifstream::binary);
152 if (!ifs) {
153 Logger::error(source) << strerror(errno) << std::endl;
154 return false;
155 }
156
157 std::streampos fsize = ifs.tellg();
158 ifs.seekg(0, std::ios::end);
159 fsize = ifs.tellg() - fsize;
160 ifs.seekg(0, std::ios::beg);
161
162 assert(fsize >= 0);
163 size_t dataSize = static_cast<size_t>(fsize);
164 char* buf = new char[dataSize];
165 ifs.read(buf, dataSize);
166
167 BinaryResourceParser parser(table, source, buf, dataSize);
168 bool result = parser.parse();
169
170 delete [] buf;
171 return result;
172}
173
174bool loadResTable(android::ResTable* table, const Source& source) {
175 std::ifstream ifs(source.path, std::ifstream::in | std::ifstream::binary);
176 if (!ifs) {
177 Logger::error(source) << strerror(errno) << std::endl;
178 return false;
179 }
180
181 std::streampos fsize = ifs.tellg();
182 ifs.seekg(0, std::ios::end);
183 fsize = ifs.tellg() - fsize;
184 ifs.seekg(0, std::ios::beg);
185
186 assert(fsize >= 0);
187 size_t dataSize = static_cast<size_t>(fsize);
188 char* buf = new char[dataSize];
189 ifs.read(buf, dataSize);
190
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700191 bool result = table->add(buf, dataSize, -1, true) == android::NO_ERROR;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800192
193 delete [] buf;
194 return result;
195}
196
197void versionStylesForCompat(std::shared_ptr<ResourceTable> table) {
198 for (auto& type : *table) {
199 if (type->type != ResourceType::kStyle) {
200 continue;
201 }
202
203 for (auto& entry : type->entries) {
204 // Add the versioned styles we want to create
205 // here. They are added to the table after
206 // iterating over the original set of styles.
207 //
208 // A stack is used since auto-generated styles
209 // from later versions should override
210 // auto-generated styles from earlier versions.
211 // Iterating over the styles is done in order,
212 // so we will always visit sdkVersions from smallest
213 // to largest.
214 std::stack<ResourceConfigValue> addStack;
215
216 for (ResourceConfigValue& configValue : entry->values) {
217 visitFunc<Style>(*configValue.value, [&](Style& style) {
218 // Collect which entries we've stripped and the smallest
219 // SDK level which was stripped.
220 size_t minSdkStripped = std::numeric_limits<size_t>::max();
221 std::vector<Style::Entry> stripped;
222
223 // Iterate over the style's entries and erase/record the
224 // attributes whose SDK level exceeds the config's sdkVersion.
225 auto iter = style.entries.begin();
226 while (iter != style.entries.end()) {
227 if (iter->key.name.package == u"android") {
228 size_t sdkLevel = findAttributeSdkLevel(iter->key.name.entry);
229 if (sdkLevel > 1 && sdkLevel > configValue.config.sdkVersion) {
230 // Record that we are about to strip this.
231 stripped.emplace_back(std::move(*iter));
232 minSdkStripped = std::min(minSdkStripped, sdkLevel);
233
234 // Erase this from this style.
235 iter = style.entries.erase(iter);
236 continue;
237 }
238 }
239 ++iter;
240 }
241
242 if (!stripped.empty()) {
243 // We have stripped attributes, so let's create a new style to hold them.
244 ConfigDescription versionConfig(configValue.config);
245 versionConfig.sdkVersion = minSdkStripped;
246
247 ResourceConfigValue value = {
248 versionConfig,
249 configValue.source,
250 {},
251
252 // Create a copy of the original style.
253 std::unique_ptr<Value>(configValue.value->clone())
254 };
255
256 Style& newStyle = static_cast<Style&>(*value.value);
257
258 // Move the recorded stripped attributes into this new style.
259 std::move(stripped.begin(), stripped.end(),
260 std::back_inserter(newStyle.entries));
261
262 // We will add this style to the table later. If we do it now, we will
263 // mess up iteration.
264 addStack.push(std::move(value));
265 }
266 });
267 }
268
269 auto comparator =
270 [](const ResourceConfigValue& lhs, const ConfigDescription& rhs) -> bool {
271 return lhs.config < rhs;
272 };
273
274 while (!addStack.empty()) {
275 ResourceConfigValue& value = addStack.top();
276 auto iter = std::lower_bound(entry->values.begin(), entry->values.end(),
277 value.config, comparator);
278 if (iter == entry->values.end() || iter->config != value.config) {
279 entry->values.insert(iter, std::move(value));
280 }
281 addStack.pop();
282 }
283 }
284 }
285}
286
287bool collectXml(std::shared_ptr<ResourceTable> table, const Source& source,
288 const ResourceName& name,
289 const ConfigDescription& config) {
290 std::ifstream in(source.path, std::ifstream::binary);
291 if (!in) {
292 Logger::error(source) << strerror(errno) << std::endl;
293 return false;
294 }
295
296 std::set<size_t> sdkLevels;
297
298 SourceXmlPullParser pullParser(in);
299 while (XmlPullParser::isGoodEvent(pullParser.next())) {
300 if (pullParser.getEvent() != XmlPullParser::Event::kStartElement) {
301 continue;
302 }
303
304 const auto endIter = pullParser.endAttributes();
305 for (auto iter = pullParser.beginAttributes(); iter != endIter; ++iter) {
306 if (iter->namespaceUri == u"http://schemas.android.com/apk/res/android") {
307 size_t sdkLevel = findAttributeSdkLevel(iter->name);
308 if (sdkLevel > 1) {
309 sdkLevels.insert(sdkLevel);
310 }
311 }
312
313 ResourceNameRef refName;
314 bool create = false;
315 bool privateRef = false;
316 if (ResourceParser::tryParseReference(iter->value, &refName, &create, &privateRef) &&
317 create) {
318 table->addResource(refName, {}, source.line(pullParser.getLineNumber()),
319 util::make_unique<Id>());
320 }
321 }
322 }
323
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800324 for (size_t level : sdkLevels) {
325 Logger::note(source)
326 << "creating v" << level << " versioned file."
327 << std::endl;
328 ConfigDescription newConfig = config;
329 newConfig.sdkVersion = level;
330
331 std::unique_ptr<FileReference> fileResource = makeFileReference(
332 table->getValueStringPool(),
333 util::utf16ToUtf8(name.entry) + ".xml",
334 name.type,
335 newConfig);
336 table->addResource(name, newConfig, source.line(0), std::move(fileResource));
337 }
338 return true;
339}
340
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700341struct CompileItem {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800342 Source source;
343 ResourceName name;
344 ConfigDescription config;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700345 std::string extension;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800346};
347
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700348bool compileXml(std::shared_ptr<Resolver> resolver, const CompileItem& item,
349 const Source& outputSource, std::queue<CompileItem>* queue) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800350 std::ifstream in(item.source.path, std::ifstream::binary);
351 if (!in) {
352 Logger::error(item.source) << strerror(errno) << std::endl;
353 return false;
354 }
355
356 BigBuffer outBuffer(1024);
357 std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<SourceXmlPullParser>(in);
358 XmlFlattener flattener(resolver);
359
360 // We strip attributes that do not belong in this version of the resource.
361 // Non-version qualified resources have an implicit version 1 requirement.
362 XmlFlattener::Options options = { item.config.sdkVersion ? item.config.sdkVersion : 1 };
363 Maybe<size_t> minStrippedSdk = flattener.flatten(item.source, xmlParser, &outBuffer, options);
364 if (!minStrippedSdk) {
365 return false;
366 }
367
368 if (minStrippedSdk.value() > 0) {
369 // Something was stripped, so let's generate a new file
370 // with the version of the smallest SDK version stripped.
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700371 CompileItem newWork = item;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800372 newWork.config.sdkVersion = minStrippedSdk.value();
373 queue->push(newWork);
374 }
375
376 std::ofstream out(outputSource.path, std::ofstream::binary);
377 if (!out) {
378 Logger::error(outputSource) << strerror(errno) << std::endl;
379 return false;
380 }
381
382 if (!util::writeAll(out, outBuffer)) {
383 Logger::error(outputSource) << strerror(errno) << std::endl;
384 return false;
385 }
386 return true;
387}
388
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700389bool compilePng(const Source& source, const Source& output) {
390 std::ifstream in(source.path, std::ifstream::binary);
391 if (!in) {
392 Logger::error(source) << strerror(errno) << std::endl;
393 return false;
394 }
395
396 std::ofstream out(output.path, std::ofstream::binary);
397 if (!out) {
398 Logger::error(output) << strerror(errno) << std::endl;
399 return false;
400 }
401
402 std::string err;
403 Png png;
404 if (!png.process(source, in, out, {}, &err)) {
405 Logger::error(source) << err << std::endl;
406 return false;
407 }
408 return true;
409}
410
411bool copyFile(const Source& source, const Source& output) {
412 std::ifstream in(source.path, std::ifstream::binary);
413 if (!in) {
414 Logger::error(source) << strerror(errno) << std::endl;
415 return false;
416 }
417
418 std::ofstream out(output.path, std::ofstream::binary);
419 if (!out) {
420 Logger::error(output) << strerror(errno) << std::endl;
421 return false;
422 }
423
424 if (out << in.rdbuf()) {
425 Logger::error(output) << strerror(errno) << std::endl;
426 return true;
427 }
428 return false;
429}
430
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800431struct AaptOptions {
432 enum class Phase {
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700433 Full,
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800434 Collect,
435 Link,
436 Compile,
437 };
438
439 // The phase to process.
440 Phase phase;
441
442 // Details about the app.
443 AppInfo appInfo;
444
445 // The location of the manifest file.
446 Source manifest;
447
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700448 // The source directories to walk and find resource files.
449 std::vector<Source> sourceDirs;
450
451 // The resource files to process and collect.
452 std::vector<Source> collectFiles;
453
454 // The binary table files to link.
455 std::vector<Source> linkFiles;
456
457 // The resource files to compile.
458 std::vector<Source> compileFiles;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800459
460 // The libraries these files may reference.
461 std::vector<Source> libraries;
462
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700463 // Output path. This can be a directory or file
464 // depending on the phase.
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800465 Source output;
466
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700467 // Directory to in which to generate R.java.
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800468 Maybe<Source> generateJavaClass;
469
470 // Whether to output verbose details about
471 // compilation.
472 bool verbose = false;
473};
474
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700475bool compileAndroidManifest(const std::shared_ptr<Resolver>& resolver,
476 const AaptOptions& options) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800477 Source outSource = options.output;
478 appendPath(&outSource.path, "AndroidManifest.xml");
479
480 if (options.verbose) {
481 Logger::note(outSource) << "compiling AndroidManifest.xml." << std::endl;
482 }
483
484 std::ifstream in(options.manifest.path, std::ifstream::binary);
485 if (!in) {
486 Logger::error(options.manifest) << strerror(errno) << std::endl;
487 return false;
488 }
489
490 BigBuffer outBuffer(1024);
491 std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<SourceXmlPullParser>(in);
492 XmlFlattener flattener(resolver);
493
494 Maybe<size_t> result = flattener.flatten(options.manifest, xmlParser, &outBuffer,
495 XmlFlattener::Options{});
496 if (!result) {
497 return false;
498 }
499
500 std::unique_ptr<uint8_t[]> data = std::unique_ptr<uint8_t[]>(new uint8_t[outBuffer.size()]);
501 uint8_t* p = data.get();
502 for (const auto& b : outBuffer) {
503 memcpy(p, b.buffer.get(), b.size);
504 p += b.size;
505 }
506
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700507 android::ResXMLTree tree;
508 if (tree.setTo(data.get(), outBuffer.size()) != android::NO_ERROR) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800509 return false;
510 }
511
512 ManifestValidator validator(resolver->getResTable());
513 if (!validator.validate(options.manifest, &tree)) {
514 return false;
515 }
516
517 std::ofstream out(outSource.path, std::ofstream::binary);
518 if (!out) {
519 Logger::error(outSource) << strerror(errno) << std::endl;
520 return false;
521 }
522
523 if (!util::writeAll(out, outBuffer)) {
524 Logger::error(outSource) << strerror(errno) << std::endl;
525 return false;
526 }
527 return true;
528}
529
530bool loadAppInfo(const Source& source, AppInfo* outInfo) {
531 std::ifstream ifs(source.path, std::ifstream::in | std::ifstream::binary);
532 if (!ifs) {
533 Logger::error(source) << strerror(errno) << std::endl;
534 return false;
535 }
536
537 ManifestParser parser;
538 std::shared_ptr<XmlPullParser> pullParser = std::make_shared<SourceXmlPullParser>(ifs);
539 return parser.parse(source, pullParser, outInfo);
540}
541
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700542static AaptOptions prepareArgs(int argc, char** argv) {
543 if (argc < 2) {
544 std::cerr << "no command specified." << std::endl;
545 exit(1);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800546 }
547
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700548 const StringPiece command(argv[1]);
549 argc -= 2;
550 argv += 2;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800551
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700552 AaptOptions options;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800553
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700554 StringPiece outputDescription = "place output in file";
555 if (command == "package") {
556 options.phase = AaptOptions::Phase::Full;
557 outputDescription = "place output in directory";
558 } else if (command == "collect") {
559 options.phase = AaptOptions::Phase::Collect;
560 } else if (command == "link") {
561 options.phase = AaptOptions::Phase::Link;
562 } else if (command == "compile") {
563 options.phase = AaptOptions::Phase::Compile;
564 outputDescription = "place output in directory";
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800565 } else {
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700566 std::cerr << "invalid command '" << command << "'." << std::endl;
567 exit(1);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800568 }
569
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700570 if (options.phase == AaptOptions::Phase::Full) {
571 flag::requiredFlag("-S", "add a directory in which to find resources",
572 [&options](const StringPiece& arg) {
573 options.sourceDirs.push_back(Source{ arg.toString() });
574 });
575
576 flag::requiredFlag("-M", "path to AndroidManifest.xml",
577 [&options](const StringPiece& arg) {
578 options.manifest = Source{ arg.toString() };
579 });
580
581 flag::optionalFlag("-I", "add an Android APK to link against",
582 [&options](const StringPiece& arg) {
583 options.libraries.push_back(Source{ arg.toString() });
584 });
585
586 flag::optionalFlag("--java", "directory in which to generate R.java",
587 [&options](const StringPiece& arg) {
588 options.generateJavaClass = Source{ arg.toString() };
589 });
590
591 } else {
592 flag::requiredFlag("--package", "Android package name",
593 [&options](const StringPiece& arg) {
594 options.appInfo.package = util::utf8ToUtf16(arg);
595 });
596
597 if (options.phase != AaptOptions::Phase::Collect) {
598 flag::optionalFlag("-I", "add an Android APK to link against",
599 [&options](const StringPiece& arg) {
600 options.libraries.push_back(Source{ arg.toString() });
601 });
602 }
603
604 if (options.phase == AaptOptions::Phase::Link) {
605 flag::optionalFlag("--java", "directory in which to generate R.java",
606 [&options](const StringPiece& arg) {
607 options.generateJavaClass = Source{ arg.toString() };
608 });
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800609 }
610 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800611
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700612 // Common flags for all steps.
613 flag::requiredFlag("-o", outputDescription, [&options](const StringPiece& arg) {
614 options.output = Source{ arg.toString() };
615 });
616 flag::optionalSwitch("-v", "enables verbose logging", &options.verbose);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800617
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700618 // Build the command string for output (eg. "aapt2 compile").
619 std::string fullCommand = "aapt2";
620 fullCommand += " ";
621 fullCommand += command.toString();
622
623 // Actually read the command line flags.
624 flag::parse(argc, argv, fullCommand);
625
626 // Copy all the remaining arguments.
627 if (options.phase == AaptOptions::Phase::Collect) {
628 for (const std::string& arg : flag::getArgs()) {
629 options.collectFiles.push_back(Source{ arg });
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800630 }
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700631 } else if (options.phase == AaptOptions::Phase::Compile) {
632 for (const std::string& arg : flag::getArgs()) {
633 options.compileFiles.push_back(Source{ arg });
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800634 }
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700635 } else if (options.phase == AaptOptions::Phase::Link) {
636 for (const std::string& arg : flag::getArgs()) {
637 options.linkFiles.push_back(Source{ arg });
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800638 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800639 }
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700640 return options;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800641}
642
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700643static bool collectValues(const std::shared_ptr<ResourceTable>& table, const Source& source,
644 const ConfigDescription& config) {
645 std::ifstream in(source.path, std::ifstream::binary);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800646 if (!in) {
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700647 Logger::error(source) << strerror(errno) << std::endl;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800648 return false;
649 }
650
651 std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<SourceXmlPullParser>(in);
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700652 ResourceParser parser(table, source, config, xmlParser);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800653 return parser.parse();
654}
655
656struct ResourcePathData {
657 std::u16string resourceDir;
658 std::u16string name;
659 std::string extension;
660 ConfigDescription config;
661};
662
663/**
664 * Resource file paths are expected to look like:
665 * [--/res/]type[-config]/name
666 */
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700667static Maybe<ResourcePathData> extractResourcePathData(const Source& source) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800668 std::vector<std::string> parts = util::splitAndLowercase(source.path, '/');
669 if (parts.size() < 2) {
670 Logger::error(source) << "bad resource path." << std::endl;
671 return {};
672 }
673
674 std::string& dir = parts[parts.size() - 2];
675 StringPiece dirStr = dir;
676
677 ConfigDescription config;
678 size_t dashPos = dir.find('-');
679 if (dashPos != std::string::npos) {
680 StringPiece configStr = dirStr.substr(dashPos + 1, dir.size() - (dashPos + 1));
681 if (!ConfigDescription::parse(configStr, &config)) {
682 Logger::error(source)
683 << "invalid configuration '"
684 << configStr
685 << "'."
686 << std::endl;
687 return {};
688 }
689 dirStr = dirStr.substr(0, dashPos);
690 }
691
692 std::string& filename = parts[parts.size() - 1];
693 StringPiece name = filename;
694 StringPiece extension;
695 size_t dotPos = filename.find('.');
696 if (dotPos != std::string::npos) {
697 extension = name.substr(dotPos + 1, filename.size() - (dotPos + 1));
698 name = name.substr(0, dotPos);
699 }
700
701 return ResourcePathData{
702 util::utf8ToUtf16(dirStr),
703 util::utf8ToUtf16(name),
704 extension.toString(),
705 config
706 };
707}
708
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700709bool doAll(AaptOptions* options, const std::shared_ptr<ResourceTable>& table,
710 const std::shared_ptr<Resolver>& resolver) {
711 const bool versionStyles = (options->phase == AaptOptions::Phase::Full ||
712 options->phase == AaptOptions::Phase::Link);
713 const bool verifyNoMissingSymbols = (options->phase == AaptOptions::Phase::Full ||
714 options->phase == AaptOptions::Phase::Link);
715 const bool compileFiles = (options->phase == AaptOptions::Phase::Full ||
716 options->phase == AaptOptions::Phase::Compile);
717 const bool flattenTable = (options->phase == AaptOptions::Phase::Full ||
718 options->phase == AaptOptions::Phase::Collect ||
719 options->phase == AaptOptions::Phase::Link);
720 const bool useExtendedChunks = options->phase == AaptOptions::Phase::Collect;
721
722 // Build the output table path.
723 Source outputTable = options->output;
724 if (options->phase == AaptOptions::Phase::Full) {
725 appendPath(&outputTable.path, "resources.arsc");
726 }
727
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800728 bool error = false;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700729 std::queue<CompileItem> compileQueue;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800730
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700731 // If source directories were specified, walk them looking for resource files.
732 if (!options->sourceDirs.empty()) {
733 const char* customIgnore = getenv("ANDROID_AAPT_IGNORE");
734 FileFilter fileFilter;
735 if (customIgnore && customIgnore[0]) {
736 fileFilter.setPattern(customIgnore);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800737 } else {
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700738 fileFilter.setPattern(
739 "!.svn:!.git:!.ds_store:!*.scc:.*:<dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~");
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800740 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800741
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700742 for (const Source& source : options->sourceDirs) {
743 if (!walkTree(source, fileFilter, &options->collectFiles)) {
744 return false;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800745 }
746 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800747 }
748
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700749 // Load all binary resource tables.
750 for (const Source& source : options->linkFiles) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800751 error |= !loadBinaryResourceTable(table, source);
752 }
753
754 if (error) {
755 return false;
756 }
757
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700758 // Collect all the resource files.
759 // Need to parse the resource type/config/filename.
760 for (const Source& source : options->collectFiles) {
761 Maybe<ResourcePathData> maybePathData = extractResourcePathData(source);
762 if (!maybePathData) {
763 return false;
764 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800765
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700766 const ResourcePathData& pathData = maybePathData.value();
767 if (pathData.resourceDir == u"values") {
768 if (options->verbose) {
769 Logger::note(source) << "collecting values..." << std::endl;
770 }
771
772 error |= !collectValues(table, source, pathData.config);
773 continue;
774 }
775
776 const ResourceType* type = parseResourceType(pathData.resourceDir);
777 if (!type) {
778 Logger::error(source) << "invalid resource type '" << pathData.resourceDir << "'."
779 << std::endl;
780 return false;
781 }
782
783 ResourceName resourceName = { table->getPackage(), *type, pathData.name };
784
785 // Add the file name to the resource table.
786 std::unique_ptr<FileReference> fileReference = makeFileReference(
787 table->getValueStringPool(),
788 util::utf16ToUtf8(pathData.name) + "." + pathData.extension,
789 *type, pathData.config);
790 error |= !table->addResource(resourceName, pathData.config, source.line(0),
791 std::move(fileReference));
792
793 if (pathData.extension == "xml") {
794 error |= !collectXml(table, source, resourceName, pathData.config);
795 }
796
797 compileQueue.push(
798 CompileItem{ source, resourceName, pathData.config, pathData.extension });
799 }
800
801 if (error) {
802 return false;
803 }
804
805 // Version all styles referencing attributes outside of their specified SDK version.
806 if (versionStyles) {
807 versionStylesForCompat(table);
808 }
809
810 // Verify that all references are valid.
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800811 Linker linker(table, resolver);
812 if (!linker.linkAndValidate()) {
813 return false;
814 }
815
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700816 // Verify that all symbols exist.
817 if (verifyNoMissingSymbols) {
818 const auto& unresolvedRefs = linker.getUnresolvedReferences();
819 if (!unresolvedRefs.empty()) {
820 for (const auto& entry : unresolvedRefs) {
821 for (const auto& source : entry.second) {
822 Logger::error(source) << "unresolved symbol '" << entry.first << "'."
823 << std::endl;
824 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800825 }
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700826 return false;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800827 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800828 }
829
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700830 // Compile files.
831 if (compileFiles) {
832 // First process any input compile files.
833 for (const Source& source : options->compileFiles) {
834 Maybe<ResourcePathData> maybePathData = extractResourcePathData(source);
835 if (!maybePathData) {
836 return false;
837 }
838
839 const ResourcePathData& pathData = maybePathData.value();
840 const ResourceType* type = parseResourceType(pathData.resourceDir);
841 if (!type) {
842 Logger::error(source) << "invalid resource type '" << pathData.resourceDir
843 << "'." << std::endl;
844 return false;
845 }
846
847 ResourceName resourceName = { table->getPackage(), *type, pathData.name };
848 compileQueue.push(
849 CompileItem{ source, resourceName, pathData.config, pathData.extension });
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800850 }
851
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700852 // Now process the actual compile queue.
853 for (; !compileQueue.empty(); compileQueue.pop()) {
854 const CompileItem& item = compileQueue.front();
855
856 // Create the output directory path from the resource type and config.
857 std::stringstream outputPath;
858 outputPath << item.name.type;
859 if (item.config != ConfigDescription{}) {
860 outputPath << "-" << item.config.toString();
861 }
862
863 Source outSource = options->output;
864 appendPath(&outSource.path, "res");
865 appendPath(&outSource.path, outputPath.str());
866
867 // Make the directory.
868 if (!mkdirs(outSource.path)) {
869 Logger::error(outSource) << strerror(errno) << std::endl;
870 return false;
871 }
872
873 // Add the file name to the directory path.
874 appendPath(&outSource.path, util::utf16ToUtf8(item.name.entry) + "." + item.extension);
875
876 if (item.extension == "xml") {
877 if (options->verbose) {
878 Logger::note(outSource) << "compiling XML file." << std::endl;
879 }
880
881 error |= !compileXml(resolver, item, outSource, &compileQueue);
882 } else if (item.extension == "png" || item.extension == "9.png") {
883 if (options->verbose) {
884 Logger::note(outSource) << "compiling png file." << std::endl;
885 }
886
887 error |= !compilePng(item.source, outSource);
888 } else {
889 error |= !copyFile(item.source, outSource);
890 }
891 }
892
893 if (error) {
894 return false;
895 }
896 }
897
898 // Compile and validate the AndroidManifest.xml.
899 if (!options->manifest.path.empty()) {
900 if (!compileAndroidManifest(resolver, *options)) {
901 return false;
902 }
903 }
904
905 // Generate the Java class file.
906 if (options->generateJavaClass) {
907 Source outPath = options->generateJavaClass.value();
908 if (options->verbose) {
909 Logger::note() << "writing symbols to " << outPath << "." << std::endl;
910 }
911
912 // Build the output directory from the package name.
913 // Eg. com.android.app -> com/android/app
914 const std::string packageUtf8 = util::utf16ToUtf8(table->getPackage());
915 for (StringPiece part : util::tokenize<char>(packageUtf8, '.')) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800916 appendPath(&outPath.path, part);
917 }
918
919 if (!mkdirs(outPath.path)) {
920 Logger::error(outPath) << strerror(errno) << std::endl;
921 return false;
922 }
923
924 appendPath(&outPath.path, "R.java");
925
926 std::ofstream fout(outPath.path);
927 if (!fout) {
928 Logger::error(outPath) << strerror(errno) << std::endl;
929 return false;
930 }
931
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700932 JavaClassGenerator generator(table, {});
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800933 if (!generator.generate(fout)) {
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700934 Logger::error(outPath) << generator.getError() << "." << std::endl;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800935 return false;
936 }
937 }
938
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700939 // Flatten the resource table.
940 if (flattenTable && table->begin() != table->end()) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800941 BigBuffer buffer(1024);
942 TableFlattener::Options tableOptions;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700943 tableOptions.useExtendedChunks = useExtendedChunks;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800944 TableFlattener flattener(tableOptions);
945 if (!flattener.flatten(&buffer, *table)) {
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700946 Logger::error() << "failed to flatten resource table." << std::endl;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800947 return false;
948 }
949
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700950 if (options->verbose) {
951 Logger::note() << "Final resource table size=" << util::formatSize(buffer.size())
952 << std::endl;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800953 }
954
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700955 std::ofstream fout(outputTable.path, std::ofstream::binary);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800956 if (!fout) {
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700957 Logger::error(outputTable) << strerror(errno) << "." << std::endl;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800958 return false;
959 }
960
961 if (!util::writeAll(fout, buffer)) {
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700962 Logger::error(outputTable) << strerror(errno) << "." << std::endl;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800963 return false;
964 }
965 fout.flush();
966 }
967 return true;
968}
969
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800970int main(int argc, char** argv) {
971 Logger::setLog(std::make_shared<Log>(std::cerr, std::cerr));
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700972 AaptOptions options = prepareArgs(argc, argv);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800973
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700974 // If we specified a manifest, go ahead and load the package name from the manifest.
975 if (!options.manifest.path.empty()) {
976 if (!loadAppInfo(options.manifest, &options.appInfo)) {
977 return false;
978 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800979 }
980
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800981 // Verify we have some common options set.
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800982 if (options.appInfo.package.empty()) {
983 Logger::error() << "no package name specified." << std::endl;
984 return false;
985 }
986
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700987 // Every phase needs a resource table.
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800988 std::shared_ptr<ResourceTable> table = std::make_shared<ResourceTable>();
989 table->setPackage(options.appInfo.package);
990 if (options.appInfo.package == u"android") {
991 table->setPackageId(0x01);
992 } else {
993 table->setPackageId(0x7f);
994 }
995
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800996 // Load the included libraries.
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800997 std::shared_ptr<android::AssetManager> libraries = std::make_shared<android::AssetManager>();
998 for (const Source& source : options.libraries) {
999 if (util::stringEndsWith(source.path, ".arsc")) {
1000 // We'll process these last so as to avoid a cookie issue.
1001 continue;
1002 }
1003
1004 int32_t cookie;
1005 if (!libraries->addAssetPath(android::String8(source.path.data()), &cookie)) {
1006 Logger::error(source) << "failed to load library." << std::endl;
1007 return false;
1008 }
1009 }
1010
1011 for (const Source& source : options.libraries) {
1012 if (!util::stringEndsWith(source.path, ".arsc")) {
1013 // We've already processed this.
1014 continue;
1015 }
1016
1017 // Dirty hack but there is no other way to get a
1018 // writeable ResTable.
1019 if (!loadResTable(const_cast<android::ResTable*>(&libraries->getResources(false)),
1020 source)) {
1021 return false;
1022 }
1023 }
1024
1025 // Make the resolver that will cache IDs for us.
1026 std::shared_ptr<Resolver> resolver = std::make_shared<Resolver>(table, libraries);
1027
Adam Lesinski98aa3ad2015-04-06 11:46:52 -07001028 // Do the work.
1029 if (!doAll(&options, table, resolver)) {
1030 Logger::error() << "aapt exiting with failures." << std::endl;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001031 return 1;
1032 }
1033 return 0;
1034}