blob: 87127fd3a75b894dc26b6b775b60223607894d8e [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"
Adam Lesinski4d3a9872015-04-09 19:53:22 -070020#include "BindingXmlPullParser.h"
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080021#include "Files.h"
Adam Lesinski98aa3ad2015-04-06 11:46:52 -070022#include "Flag.h"
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080023#include "JavaClassGenerator.h"
24#include "Linker.h"
25#include "ManifestParser.h"
26#include "ManifestValidator.h"
Adam Lesinski98aa3ad2015-04-06 11:46:52 -070027#include "Png.h"
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080028#include "ResourceParser.h"
29#include "ResourceTable.h"
30#include "ResourceValues.h"
31#include "SdkConstants.h"
32#include "SourceXmlPullParser.h"
33#include "StringPiece.h"
34#include "TableFlattener.h"
35#include "Util.h"
36#include "XmlFlattener.h"
37
38#include <algorithm>
39#include <androidfw/AssetManager.h>
40#include <cstdlib>
41#include <dirent.h>
42#include <errno.h>
43#include <fstream>
44#include <iostream>
45#include <sstream>
46#include <sys/stat.h>
Adam Lesinski98aa3ad2015-04-06 11:46:52 -070047#include <utils/Errors.h>
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080048
49using namespace aapt;
50
51void printTable(const ResourceTable& table) {
52 std::cout << "ResourceTable package=" << table.getPackage();
53 if (table.getPackageId() != ResourceTable::kUnsetPackageId) {
54 std::cout << " id=" << std::hex << table.getPackageId() << std::dec;
55 }
56 std::cout << std::endl
57 << "---------------------------------------------------------" << std::endl;
58
59 for (const auto& type : table) {
60 std::cout << "Type " << type->type;
61 if (type->typeId != ResourceTableType::kUnsetTypeId) {
62 std::cout << " [" << type->typeId << "]";
63 }
64 std::cout << " (" << type->entries.size() << " entries)" << std::endl;
65 for (const auto& entry : type->entries) {
66 std::cout << " " << entry->name;
67 if (entry->entryId != ResourceEntry::kUnsetEntryId) {
68 std::cout << " [" << entry->entryId << "]";
69 }
70 std::cout << " (" << entry->values.size() << " configurations)";
71 if (entry->publicStatus.isPublic) {
72 std::cout << " PUBLIC";
73 }
74 std::cout << std::endl;
75 for (const auto& value : entry->values) {
76 std::cout << " " << value.config << " (" << value.source << ") : ";
77 value.value->print(std::cout);
78 std::cout << std::endl;
79 }
80 }
81 }
82}
83
84void printStringPool(const StringPool& pool) {
85 std::cout << "String pool of length " << pool.size() << std::endl
86 << "---------------------------------------------------------" << std::endl;
87
88 size_t i = 0;
89 for (const auto& entry : pool) {
90 std::cout << "[" << i << "]: "
91 << entry->value
92 << " (Priority " << entry->context.priority
93 << ", Config '" << entry->context.config << "')"
94 << std::endl;
95 i++;
96 }
97}
98
99std::unique_ptr<FileReference> makeFileReference(StringPool& pool, const StringPiece& filename,
100 ResourceType type, const ConfigDescription& config) {
101 std::stringstream path;
102 path << "res/" << type;
103 if (config != ConfigDescription{}) {
104 path << "-" << config;
105 }
106 path << "/" << filename;
107 return util::make_unique<FileReference>(pool.makeRef(util::utf8ToUtf16(path.str())));
108}
109
110/**
111 * Collect files from 'root', filtering out any files that do not
112 * match the FileFilter 'filter'.
113 */
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700114bool walkTree(const Source& root, const FileFilter& filter,
115 std::vector<Source>* outEntries) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800116 bool error = false;
117
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700118 for (const std::string& dirName : listFiles(root.path)) {
119 std::string dir = root.path;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800120 appendPath(&dir, dirName);
121
122 FileType ft = getFileType(dir);
123 if (!filter(dirName, ft)) {
124 continue;
125 }
126
127 if (ft != FileType::kDirectory) {
128 continue;
129 }
130
131 for (const std::string& fileName : listFiles(dir)) {
132 std::string file(dir);
133 appendPath(&file, fileName);
134
135 FileType ft = getFileType(file);
136 if (!filter(fileName, ft)) {
137 continue;
138 }
139
140 if (ft != FileType::kRegular) {
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700141 Logger::error(Source{ file }) << "not a regular file." << std::endl;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800142 error = true;
143 continue;
144 }
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700145 outEntries->push_back(Source{ file });
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800146 }
147 }
148 return !error;
149}
150
151bool loadBinaryResourceTable(std::shared_ptr<ResourceTable> table, const Source& source) {
152 std::ifstream ifs(source.path, std::ifstream::in | std::ifstream::binary);
153 if (!ifs) {
154 Logger::error(source) << strerror(errno) << std::endl;
155 return false;
156 }
157
158 std::streampos fsize = ifs.tellg();
159 ifs.seekg(0, std::ios::end);
160 fsize = ifs.tellg() - fsize;
161 ifs.seekg(0, std::ios::beg);
162
163 assert(fsize >= 0);
164 size_t dataSize = static_cast<size_t>(fsize);
165 char* buf = new char[dataSize];
166 ifs.read(buf, dataSize);
167
168 BinaryResourceParser parser(table, source, buf, dataSize);
169 bool result = parser.parse();
170
171 delete [] buf;
172 return result;
173}
174
175bool loadResTable(android::ResTable* table, const Source& source) {
176 std::ifstream ifs(source.path, std::ifstream::in | std::ifstream::binary);
177 if (!ifs) {
178 Logger::error(source) << strerror(errno) << std::endl;
179 return false;
180 }
181
182 std::streampos fsize = ifs.tellg();
183 ifs.seekg(0, std::ios::end);
184 fsize = ifs.tellg() - fsize;
185 ifs.seekg(0, std::ios::beg);
186
187 assert(fsize >= 0);
188 size_t dataSize = static_cast<size_t>(fsize);
189 char* buf = new char[dataSize];
190 ifs.read(buf, dataSize);
191
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700192 bool result = table->add(buf, dataSize, -1, true) == android::NO_ERROR;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800193
194 delete [] buf;
195 return result;
196}
197
198void versionStylesForCompat(std::shared_ptr<ResourceTable> table) {
199 for (auto& type : *table) {
200 if (type->type != ResourceType::kStyle) {
201 continue;
202 }
203
204 for (auto& entry : type->entries) {
205 // Add the versioned styles we want to create
206 // here. They are added to the table after
207 // iterating over the original set of styles.
208 //
209 // A stack is used since auto-generated styles
210 // from later versions should override
211 // auto-generated styles from earlier versions.
212 // Iterating over the styles is done in order,
213 // so we will always visit sdkVersions from smallest
214 // to largest.
215 std::stack<ResourceConfigValue> addStack;
216
217 for (ResourceConfigValue& configValue : entry->values) {
218 visitFunc<Style>(*configValue.value, [&](Style& style) {
219 // Collect which entries we've stripped and the smallest
220 // SDK level which was stripped.
221 size_t minSdkStripped = std::numeric_limits<size_t>::max();
222 std::vector<Style::Entry> stripped;
223
224 // Iterate over the style's entries and erase/record the
225 // attributes whose SDK level exceeds the config's sdkVersion.
226 auto iter = style.entries.begin();
227 while (iter != style.entries.end()) {
228 if (iter->key.name.package == u"android") {
229 size_t sdkLevel = findAttributeSdkLevel(iter->key.name.entry);
230 if (sdkLevel > 1 && sdkLevel > configValue.config.sdkVersion) {
231 // Record that we are about to strip this.
232 stripped.emplace_back(std::move(*iter));
233 minSdkStripped = std::min(minSdkStripped, sdkLevel);
234
235 // Erase this from this style.
236 iter = style.entries.erase(iter);
237 continue;
238 }
239 }
240 ++iter;
241 }
242
243 if (!stripped.empty()) {
244 // We have stripped attributes, so let's create a new style to hold them.
245 ConfigDescription versionConfig(configValue.config);
246 versionConfig.sdkVersion = minSdkStripped;
247
248 ResourceConfigValue value = {
249 versionConfig,
250 configValue.source,
251 {},
252
253 // Create a copy of the original style.
254 std::unique_ptr<Value>(configValue.value->clone())
255 };
256
257 Style& newStyle = static_cast<Style&>(*value.value);
258
259 // Move the recorded stripped attributes into this new style.
260 std::move(stripped.begin(), stripped.end(),
261 std::back_inserter(newStyle.entries));
262
263 // We will add this style to the table later. If we do it now, we will
264 // mess up iteration.
265 addStack.push(std::move(value));
266 }
267 });
268 }
269
270 auto comparator =
271 [](const ResourceConfigValue& lhs, const ConfigDescription& rhs) -> bool {
272 return lhs.config < rhs;
273 };
274
275 while (!addStack.empty()) {
276 ResourceConfigValue& value = addStack.top();
277 auto iter = std::lower_bound(entry->values.begin(), entry->values.end(),
278 value.config, comparator);
279 if (iter == entry->values.end() || iter->config != value.config) {
280 entry->values.insert(iter, std::move(value));
281 }
282 addStack.pop();
283 }
284 }
285 }
286}
287
288bool collectXml(std::shared_ptr<ResourceTable> table, const Source& source,
Adam Lesinski4d3a9872015-04-09 19:53:22 -0700289 const ResourceName& name, const ConfigDescription& config) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800290 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
Adam Lesinski4d3a9872015-04-09 19:53:22 -0700298 SourceXmlPullParser parser(in);
299 while (XmlPullParser::isGoodEvent(parser.next())) {
300 if (parser.getEvent() != XmlPullParser::Event::kStartElement) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800301 continue;
302 }
303
Adam Lesinski4d3a9872015-04-09 19:53:22 -0700304 const auto endIter = parser.endAttributes();
305 for (auto iter = parser.beginAttributes(); iter != endIter; ++iter) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800306 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) {
Adam Lesinski4d3a9872015-04-09 19:53:22 -0700318 table->addResource(refName, {}, source.line(parser.getLineNumber()),
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800319 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
Adam Lesinski4d3a9872015-04-09 19:53:22 -0700356 std::shared_ptr<BindingXmlPullParser> binding;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800357 std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<SourceXmlPullParser>(in);
Adam Lesinski4d3a9872015-04-09 19:53:22 -0700358 if (item.name.type == ResourceType::kLayout) {
359 binding = std::make_shared<BindingXmlPullParser>(xmlParser);
360 xmlParser = binding;
361 }
362
363 BigBuffer outBuffer(1024);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800364 XmlFlattener flattener(resolver);
365
366 // We strip attributes that do not belong in this version of the resource.
367 // Non-version qualified resources have an implicit version 1 requirement.
368 XmlFlattener::Options options = { item.config.sdkVersion ? item.config.sdkVersion : 1 };
369 Maybe<size_t> minStrippedSdk = flattener.flatten(item.source, xmlParser, &outBuffer, options);
370 if (!minStrippedSdk) {
371 return false;
372 }
373
374 if (minStrippedSdk.value() > 0) {
375 // Something was stripped, so let's generate a new file
376 // with the version of the smallest SDK version stripped.
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700377 CompileItem newWork = item;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800378 newWork.config.sdkVersion = minStrippedSdk.value();
379 queue->push(newWork);
380 }
381
382 std::ofstream out(outputSource.path, std::ofstream::binary);
383 if (!out) {
384 Logger::error(outputSource) << strerror(errno) << std::endl;
385 return false;
386 }
387
388 if (!util::writeAll(out, outBuffer)) {
389 Logger::error(outputSource) << strerror(errno) << std::endl;
390 return false;
391 }
Adam Lesinski4d3a9872015-04-09 19:53:22 -0700392
393 if (binding) {
394 // We generated a binding xml file, write it out beside the output file.
395 Source bindingOutput = outputSource;
396 bindingOutput.path += ".bind.xml";
397 std::ofstream bout(bindingOutput.path);
398 if (!bout) {
399 Logger::error(bindingOutput) << strerror(errno) << std::endl;
400 return false;
401 }
402
403 if (!binding->writeToFile(bout)) {
404 Logger::error(bindingOutput) << strerror(errno) << std::endl;
405 return false;
406 }
407 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800408 return true;
409}
410
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700411bool compilePng(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 std::string err;
425 Png png;
426 if (!png.process(source, in, out, {}, &err)) {
427 Logger::error(source) << err << std::endl;
428 return false;
429 }
430 return true;
431}
432
433bool copyFile(const Source& source, const Source& output) {
434 std::ifstream in(source.path, std::ifstream::binary);
435 if (!in) {
436 Logger::error(source) << strerror(errno) << std::endl;
437 return false;
438 }
439
440 std::ofstream out(output.path, std::ofstream::binary);
441 if (!out) {
442 Logger::error(output) << strerror(errno) << std::endl;
443 return false;
444 }
445
446 if (out << in.rdbuf()) {
447 Logger::error(output) << strerror(errno) << std::endl;
448 return true;
449 }
450 return false;
451}
452
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800453struct AaptOptions {
454 enum class Phase {
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700455 Full,
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800456 Collect,
457 Link,
458 Compile,
Adam Lesinskic7e24322015-04-10 15:52:36 -0700459 Manifest
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800460 };
461
462 // The phase to process.
463 Phase phase;
464
465 // Details about the app.
466 AppInfo appInfo;
467
468 // The location of the manifest file.
469 Source manifest;
470
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700471 // The source directories to walk and find resource files.
472 std::vector<Source> sourceDirs;
473
474 // The resource files to process and collect.
475 std::vector<Source> collectFiles;
476
477 // The binary table files to link.
478 std::vector<Source> linkFiles;
479
480 // The resource files to compile.
481 std::vector<Source> compileFiles;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800482
483 // The libraries these files may reference.
484 std::vector<Source> libraries;
485
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700486 // Output path. This can be a directory or file
487 // depending on the phase.
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800488 Source output;
489
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700490 // Directory to in which to generate R.java.
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800491 Maybe<Source> generateJavaClass;
492
493 // Whether to output verbose details about
494 // compilation.
495 bool verbose = false;
496};
497
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700498bool compileAndroidManifest(const std::shared_ptr<Resolver>& resolver,
499 const AaptOptions& options) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800500 Source outSource = options.output;
501 appendPath(&outSource.path, "AndroidManifest.xml");
502
503 if (options.verbose) {
504 Logger::note(outSource) << "compiling AndroidManifest.xml." << std::endl;
505 }
506
507 std::ifstream in(options.manifest.path, std::ifstream::binary);
508 if (!in) {
509 Logger::error(options.manifest) << strerror(errno) << std::endl;
510 return false;
511 }
512
513 BigBuffer outBuffer(1024);
514 std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<SourceXmlPullParser>(in);
515 XmlFlattener flattener(resolver);
516
517 Maybe<size_t> result = flattener.flatten(options.manifest, xmlParser, &outBuffer,
518 XmlFlattener::Options{});
519 if (!result) {
520 return false;
521 }
522
523 std::unique_ptr<uint8_t[]> data = std::unique_ptr<uint8_t[]>(new uint8_t[outBuffer.size()]);
524 uint8_t* p = data.get();
525 for (const auto& b : outBuffer) {
526 memcpy(p, b.buffer.get(), b.size);
527 p += b.size;
528 }
529
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700530 android::ResXMLTree tree;
531 if (tree.setTo(data.get(), outBuffer.size()) != android::NO_ERROR) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800532 return false;
533 }
534
535 ManifestValidator validator(resolver->getResTable());
536 if (!validator.validate(options.manifest, &tree)) {
537 return false;
538 }
539
540 std::ofstream out(outSource.path, std::ofstream::binary);
541 if (!out) {
542 Logger::error(outSource) << strerror(errno) << std::endl;
543 return false;
544 }
545
546 if (!util::writeAll(out, outBuffer)) {
547 Logger::error(outSource) << strerror(errno) << std::endl;
548 return false;
549 }
550 return true;
551}
552
553bool loadAppInfo(const Source& source, AppInfo* outInfo) {
554 std::ifstream ifs(source.path, std::ifstream::in | std::ifstream::binary);
555 if (!ifs) {
556 Logger::error(source) << strerror(errno) << std::endl;
557 return false;
558 }
559
560 ManifestParser parser;
561 std::shared_ptr<XmlPullParser> pullParser = std::make_shared<SourceXmlPullParser>(ifs);
562 return parser.parse(source, pullParser, outInfo);
563}
564
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700565static AaptOptions prepareArgs(int argc, char** argv) {
566 if (argc < 2) {
567 std::cerr << "no command specified." << std::endl;
568 exit(1);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800569 }
570
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700571 const StringPiece command(argv[1]);
572 argc -= 2;
573 argv += 2;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800574
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700575 AaptOptions options;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800576
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700577 StringPiece outputDescription = "place output in file";
578 if (command == "package") {
579 options.phase = AaptOptions::Phase::Full;
580 outputDescription = "place output in directory";
581 } else if (command == "collect") {
582 options.phase = AaptOptions::Phase::Collect;
583 } else if (command == "link") {
584 options.phase = AaptOptions::Phase::Link;
585 } else if (command == "compile") {
586 options.phase = AaptOptions::Phase::Compile;
587 outputDescription = "place output in directory";
Adam Lesinskic7e24322015-04-10 15:52:36 -0700588 } else if (command == "manifest") {
589 options.phase = AaptOptions::Phase::Manifest;
590 outputDescription = "place AndroidManifest.xml in directory";
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800591 } else {
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700592 std::cerr << "invalid command '" << command << "'." << std::endl;
593 exit(1);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800594 }
595
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700596 if (options.phase == AaptOptions::Phase::Full) {
597 flag::requiredFlag("-S", "add a directory in which to find resources",
598 [&options](const StringPiece& arg) {
599 options.sourceDirs.push_back(Source{ arg.toString() });
600 });
601
602 flag::requiredFlag("-M", "path to AndroidManifest.xml",
603 [&options](const StringPiece& arg) {
604 options.manifest = Source{ arg.toString() };
605 });
606
607 flag::optionalFlag("-I", "add an Android APK to link against",
608 [&options](const StringPiece& arg) {
609 options.libraries.push_back(Source{ arg.toString() });
610 });
611
612 flag::optionalFlag("--java", "directory in which to generate R.java",
613 [&options](const StringPiece& arg) {
614 options.generateJavaClass = Source{ arg.toString() };
615 });
616
617 } else {
Adam Lesinskic7e24322015-04-10 15:52:36 -0700618 if (options.phase != AaptOptions::Phase::Manifest) {
619 flag::requiredFlag("--package", "Android package name",
620 [&options](const StringPiece& arg) {
621 options.appInfo.package = util::utf8ToUtf16(arg);
622 });
623 }
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700624
625 if (options.phase != AaptOptions::Phase::Collect) {
626 flag::optionalFlag("-I", "add an Android APK to link against",
627 [&options](const StringPiece& arg) {
628 options.libraries.push_back(Source{ arg.toString() });
629 });
630 }
631
632 if (options.phase == AaptOptions::Phase::Link) {
633 flag::optionalFlag("--java", "directory in which to generate R.java",
634 [&options](const StringPiece& arg) {
635 options.generateJavaClass = Source{ arg.toString() };
636 });
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800637 }
638 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800639
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700640 // Common flags for all steps.
641 flag::requiredFlag("-o", outputDescription, [&options](const StringPiece& arg) {
642 options.output = Source{ arg.toString() };
643 });
644 flag::optionalSwitch("-v", "enables verbose logging", &options.verbose);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800645
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700646 // Build the command string for output (eg. "aapt2 compile").
647 std::string fullCommand = "aapt2";
648 fullCommand += " ";
649 fullCommand += command.toString();
650
651 // Actually read the command line flags.
652 flag::parse(argc, argv, fullCommand);
653
654 // Copy all the remaining arguments.
655 if (options.phase == AaptOptions::Phase::Collect) {
656 for (const std::string& arg : flag::getArgs()) {
657 options.collectFiles.push_back(Source{ arg });
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800658 }
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700659 } else if (options.phase == AaptOptions::Phase::Compile) {
660 for (const std::string& arg : flag::getArgs()) {
661 options.compileFiles.push_back(Source{ arg });
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800662 }
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700663 } else if (options.phase == AaptOptions::Phase::Link) {
664 for (const std::string& arg : flag::getArgs()) {
665 options.linkFiles.push_back(Source{ arg });
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800666 }
Adam Lesinskic7e24322015-04-10 15:52:36 -0700667 } else if (options.phase == AaptOptions::Phase::Manifest) {
668 if (!flag::getArgs().empty()) {
669 options.manifest = Source{ flag::getArgs()[0] };
670 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800671 }
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700672 return options;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800673}
674
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700675static bool collectValues(const std::shared_ptr<ResourceTable>& table, const Source& source,
676 const ConfigDescription& config) {
677 std::ifstream in(source.path, std::ifstream::binary);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800678 if (!in) {
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700679 Logger::error(source) << strerror(errno) << std::endl;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800680 return false;
681 }
682
683 std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<SourceXmlPullParser>(in);
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700684 ResourceParser parser(table, source, config, xmlParser);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800685 return parser.parse();
686}
687
688struct ResourcePathData {
689 std::u16string resourceDir;
690 std::u16string name;
691 std::string extension;
692 ConfigDescription config;
693};
694
695/**
696 * Resource file paths are expected to look like:
697 * [--/res/]type[-config]/name
698 */
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700699static Maybe<ResourcePathData> extractResourcePathData(const Source& source) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800700 std::vector<std::string> parts = util::splitAndLowercase(source.path, '/');
701 if (parts.size() < 2) {
702 Logger::error(source) << "bad resource path." << std::endl;
703 return {};
704 }
705
706 std::string& dir = parts[parts.size() - 2];
707 StringPiece dirStr = dir;
708
709 ConfigDescription config;
710 size_t dashPos = dir.find('-');
711 if (dashPos != std::string::npos) {
712 StringPiece configStr = dirStr.substr(dashPos + 1, dir.size() - (dashPos + 1));
713 if (!ConfigDescription::parse(configStr, &config)) {
714 Logger::error(source)
715 << "invalid configuration '"
716 << configStr
717 << "'."
718 << std::endl;
719 return {};
720 }
721 dirStr = dirStr.substr(0, dashPos);
722 }
723
724 std::string& filename = parts[parts.size() - 1];
725 StringPiece name = filename;
726 StringPiece extension;
727 size_t dotPos = filename.find('.');
728 if (dotPos != std::string::npos) {
729 extension = name.substr(dotPos + 1, filename.size() - (dotPos + 1));
730 name = name.substr(0, dotPos);
731 }
732
733 return ResourcePathData{
734 util::utf8ToUtf16(dirStr),
735 util::utf8ToUtf16(name),
736 extension.toString(),
737 config
738 };
739}
740
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700741bool doAll(AaptOptions* options, const std::shared_ptr<ResourceTable>& table,
742 const std::shared_ptr<Resolver>& resolver) {
743 const bool versionStyles = (options->phase == AaptOptions::Phase::Full ||
744 options->phase == AaptOptions::Phase::Link);
745 const bool verifyNoMissingSymbols = (options->phase == AaptOptions::Phase::Full ||
746 options->phase == AaptOptions::Phase::Link);
747 const bool compileFiles = (options->phase == AaptOptions::Phase::Full ||
748 options->phase == AaptOptions::Phase::Compile);
749 const bool flattenTable = (options->phase == AaptOptions::Phase::Full ||
750 options->phase == AaptOptions::Phase::Collect ||
751 options->phase == AaptOptions::Phase::Link);
752 const bool useExtendedChunks = options->phase == AaptOptions::Phase::Collect;
753
754 // Build the output table path.
755 Source outputTable = options->output;
756 if (options->phase == AaptOptions::Phase::Full) {
757 appendPath(&outputTable.path, "resources.arsc");
758 }
759
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800760 bool error = false;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700761 std::queue<CompileItem> compileQueue;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800762
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700763 // If source directories were specified, walk them looking for resource files.
764 if (!options->sourceDirs.empty()) {
765 const char* customIgnore = getenv("ANDROID_AAPT_IGNORE");
766 FileFilter fileFilter;
767 if (customIgnore && customIgnore[0]) {
768 fileFilter.setPattern(customIgnore);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800769 } else {
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700770 fileFilter.setPattern(
771 "!.svn:!.git:!.ds_store:!*.scc:.*:<dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~");
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800772 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800773
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700774 for (const Source& source : options->sourceDirs) {
775 if (!walkTree(source, fileFilter, &options->collectFiles)) {
776 return false;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800777 }
778 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800779 }
780
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700781 // Load all binary resource tables.
782 for (const Source& source : options->linkFiles) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800783 error |= !loadBinaryResourceTable(table, source);
784 }
785
786 if (error) {
787 return false;
788 }
789
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700790 // Collect all the resource files.
791 // Need to parse the resource type/config/filename.
792 for (const Source& source : options->collectFiles) {
793 Maybe<ResourcePathData> maybePathData = extractResourcePathData(source);
794 if (!maybePathData) {
795 return false;
796 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800797
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700798 const ResourcePathData& pathData = maybePathData.value();
799 if (pathData.resourceDir == u"values") {
800 if (options->verbose) {
801 Logger::note(source) << "collecting values..." << std::endl;
802 }
803
804 error |= !collectValues(table, source, pathData.config);
805 continue;
806 }
807
808 const ResourceType* type = parseResourceType(pathData.resourceDir);
809 if (!type) {
810 Logger::error(source) << "invalid resource type '" << pathData.resourceDir << "'."
811 << std::endl;
812 return false;
813 }
814
815 ResourceName resourceName = { table->getPackage(), *type, pathData.name };
816
817 // Add the file name to the resource table.
818 std::unique_ptr<FileReference> fileReference = makeFileReference(
819 table->getValueStringPool(),
820 util::utf16ToUtf8(pathData.name) + "." + pathData.extension,
821 *type, pathData.config);
822 error |= !table->addResource(resourceName, pathData.config, source.line(0),
823 std::move(fileReference));
824
825 if (pathData.extension == "xml") {
826 error |= !collectXml(table, source, resourceName, pathData.config);
827 }
828
829 compileQueue.push(
830 CompileItem{ source, resourceName, pathData.config, pathData.extension });
831 }
832
833 if (error) {
834 return false;
835 }
836
837 // Version all styles referencing attributes outside of their specified SDK version.
838 if (versionStyles) {
839 versionStylesForCompat(table);
840 }
841
842 // Verify that all references are valid.
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800843 Linker linker(table, resolver);
844 if (!linker.linkAndValidate()) {
845 return false;
846 }
847
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700848 // Verify that all symbols exist.
849 if (verifyNoMissingSymbols) {
850 const auto& unresolvedRefs = linker.getUnresolvedReferences();
851 if (!unresolvedRefs.empty()) {
852 for (const auto& entry : unresolvedRefs) {
853 for (const auto& source : entry.second) {
854 Logger::error(source) << "unresolved symbol '" << entry.first << "'."
855 << std::endl;
856 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800857 }
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700858 return false;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800859 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800860 }
861
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700862 // Compile files.
863 if (compileFiles) {
864 // First process any input compile files.
865 for (const Source& source : options->compileFiles) {
866 Maybe<ResourcePathData> maybePathData = extractResourcePathData(source);
867 if (!maybePathData) {
868 return false;
869 }
870
871 const ResourcePathData& pathData = maybePathData.value();
872 const ResourceType* type = parseResourceType(pathData.resourceDir);
873 if (!type) {
874 Logger::error(source) << "invalid resource type '" << pathData.resourceDir
875 << "'." << std::endl;
876 return false;
877 }
878
879 ResourceName resourceName = { table->getPackage(), *type, pathData.name };
880 compileQueue.push(
881 CompileItem{ source, resourceName, pathData.config, pathData.extension });
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800882 }
883
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700884 // Now process the actual compile queue.
885 for (; !compileQueue.empty(); compileQueue.pop()) {
886 const CompileItem& item = compileQueue.front();
887
888 // Create the output directory path from the resource type and config.
889 std::stringstream outputPath;
890 outputPath << item.name.type;
891 if (item.config != ConfigDescription{}) {
892 outputPath << "-" << item.config.toString();
893 }
894
895 Source outSource = options->output;
896 appendPath(&outSource.path, "res");
897 appendPath(&outSource.path, outputPath.str());
898
899 // Make the directory.
900 if (!mkdirs(outSource.path)) {
901 Logger::error(outSource) << strerror(errno) << std::endl;
902 return false;
903 }
904
905 // Add the file name to the directory path.
906 appendPath(&outSource.path, util::utf16ToUtf8(item.name.entry) + "." + item.extension);
907
908 if (item.extension == "xml") {
909 if (options->verbose) {
910 Logger::note(outSource) << "compiling XML file." << std::endl;
911 }
912
913 error |= !compileXml(resolver, item, outSource, &compileQueue);
914 } else if (item.extension == "png" || item.extension == "9.png") {
915 if (options->verbose) {
916 Logger::note(outSource) << "compiling png file." << std::endl;
917 }
918
919 error |= !compilePng(item.source, outSource);
920 } else {
921 error |= !copyFile(item.source, outSource);
922 }
923 }
924
925 if (error) {
926 return false;
927 }
928 }
929
930 // Compile and validate the AndroidManifest.xml.
931 if (!options->manifest.path.empty()) {
932 if (!compileAndroidManifest(resolver, *options)) {
933 return false;
934 }
935 }
936
937 // Generate the Java class file.
938 if (options->generateJavaClass) {
939 Source outPath = options->generateJavaClass.value();
940 if (options->verbose) {
941 Logger::note() << "writing symbols to " << outPath << "." << std::endl;
942 }
943
944 // Build the output directory from the package name.
945 // Eg. com.android.app -> com/android/app
946 const std::string packageUtf8 = util::utf16ToUtf8(table->getPackage());
947 for (StringPiece part : util::tokenize<char>(packageUtf8, '.')) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800948 appendPath(&outPath.path, part);
949 }
950
951 if (!mkdirs(outPath.path)) {
952 Logger::error(outPath) << strerror(errno) << std::endl;
953 return false;
954 }
955
956 appendPath(&outPath.path, "R.java");
957
958 std::ofstream fout(outPath.path);
959 if (!fout) {
960 Logger::error(outPath) << strerror(errno) << std::endl;
961 return false;
962 }
963
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700964 JavaClassGenerator generator(table, {});
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800965 if (!generator.generate(fout)) {
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700966 Logger::error(outPath) << generator.getError() << "." << std::endl;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800967 return false;
968 }
969 }
970
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700971 // Flatten the resource table.
972 if (flattenTable && table->begin() != table->end()) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800973 BigBuffer buffer(1024);
974 TableFlattener::Options tableOptions;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700975 tableOptions.useExtendedChunks = useExtendedChunks;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800976 TableFlattener flattener(tableOptions);
977 if (!flattener.flatten(&buffer, *table)) {
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700978 Logger::error() << "failed to flatten resource table." << std::endl;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800979 return false;
980 }
981
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700982 if (options->verbose) {
983 Logger::note() << "Final resource table size=" << util::formatSize(buffer.size())
984 << std::endl;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800985 }
986
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700987 std::ofstream fout(outputTable.path, std::ofstream::binary);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800988 if (!fout) {
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700989 Logger::error(outputTable) << strerror(errno) << "." << std::endl;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800990 return false;
991 }
992
993 if (!util::writeAll(fout, buffer)) {
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700994 Logger::error(outputTable) << strerror(errno) << "." << std::endl;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800995 return false;
996 }
997 fout.flush();
998 }
999 return true;
1000}
1001
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001002int main(int argc, char** argv) {
1003 Logger::setLog(std::make_shared<Log>(std::cerr, std::cerr));
Adam Lesinski98aa3ad2015-04-06 11:46:52 -07001004 AaptOptions options = prepareArgs(argc, argv);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001005
Adam Lesinski98aa3ad2015-04-06 11:46:52 -07001006 // If we specified a manifest, go ahead and load the package name from the manifest.
1007 if (!options.manifest.path.empty()) {
1008 if (!loadAppInfo(options.manifest, &options.appInfo)) {
1009 return false;
1010 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001011 }
1012
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001013 // Verify we have some common options set.
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001014 if (options.appInfo.package.empty()) {
1015 Logger::error() << "no package name specified." << std::endl;
1016 return false;
1017 }
1018
Adam Lesinski98aa3ad2015-04-06 11:46:52 -07001019 // Every phase needs a resource table.
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001020 std::shared_ptr<ResourceTable> table = std::make_shared<ResourceTable>();
1021 table->setPackage(options.appInfo.package);
1022 if (options.appInfo.package == u"android") {
1023 table->setPackageId(0x01);
1024 } else {
1025 table->setPackageId(0x7f);
1026 }
1027
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001028 // Load the included libraries.
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001029 std::shared_ptr<android::AssetManager> libraries = std::make_shared<android::AssetManager>();
1030 for (const Source& source : options.libraries) {
Adam Lesinski4d3a9872015-04-09 19:53:22 -07001031 if (util::stringEndsWith<char>(source.path, ".arsc")) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001032 // We'll process these last so as to avoid a cookie issue.
1033 continue;
1034 }
1035
1036 int32_t cookie;
1037 if (!libraries->addAssetPath(android::String8(source.path.data()), &cookie)) {
1038 Logger::error(source) << "failed to load library." << std::endl;
1039 return false;
1040 }
1041 }
1042
1043 for (const Source& source : options.libraries) {
Adam Lesinski4d3a9872015-04-09 19:53:22 -07001044 if (!util::stringEndsWith<char>(source.path, ".arsc")) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001045 // We've already processed this.
1046 continue;
1047 }
1048
1049 // Dirty hack but there is no other way to get a
1050 // writeable ResTable.
1051 if (!loadResTable(const_cast<android::ResTable*>(&libraries->getResources(false)),
1052 source)) {
1053 return false;
1054 }
1055 }
1056
1057 // Make the resolver that will cache IDs for us.
1058 std::shared_ptr<Resolver> resolver = std::make_shared<Resolver>(table, libraries);
1059
Adam Lesinski98aa3ad2015-04-06 11:46:52 -07001060 // Do the work.
1061 if (!doAll(&options, table, resolver)) {
1062 Logger::error() << "aapt exiting with failures." << std::endl;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001063 return 1;
1064 }
1065 return 0;
1066}