blob: be806c9624e12e47fbcfb1651ac73daa8c07051d [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 Lesinski769de982015-04-10 19:43:55 -070020#include "BinaryXmlPullParser.h"
Adam Lesinski4d3a9872015-04-09 19:53:22 -070021#include "BindingXmlPullParser.h"
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080022#include "Files.h"
Adam Lesinski98aa3ad2015-04-06 11:46:52 -070023#include "Flag.h"
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080024#include "JavaClassGenerator.h"
25#include "Linker.h"
26#include "ManifestParser.h"
27#include "ManifestValidator.h"
Adam Lesinskid5c4f872015-04-21 13:56:10 -070028#include "NameMangler.h"
Adam Lesinski98aa3ad2015-04-06 11:46:52 -070029#include "Png.h"
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080030#include "ResourceParser.h"
31#include "ResourceTable.h"
32#include "ResourceValues.h"
33#include "SdkConstants.h"
34#include "SourceXmlPullParser.h"
35#include "StringPiece.h"
36#include "TableFlattener.h"
37#include "Util.h"
38#include "XmlFlattener.h"
Adam Lesinski769de982015-04-10 19:43:55 -070039#include "ZipFile.h"
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080040
41#include <algorithm>
42#include <androidfw/AssetManager.h>
43#include <cstdlib>
44#include <dirent.h>
45#include <errno.h>
46#include <fstream>
47#include <iostream>
48#include <sstream>
49#include <sys/stat.h>
Adam Lesinski769de982015-04-10 19:43:55 -070050#include <unordered_set>
Adam Lesinski98aa3ad2015-04-06 11:46:52 -070051#include <utils/Errors.h>
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080052
Adam Lesinski5886a922015-04-15 20:29:22 -070053constexpr const char* kAaptVersionStr = "2.0-alpha";
54
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080055using namespace aapt;
56
57void printTable(const ResourceTable& table) {
58 std::cout << "ResourceTable package=" << table.getPackage();
59 if (table.getPackageId() != ResourceTable::kUnsetPackageId) {
60 std::cout << " id=" << std::hex << table.getPackageId() << std::dec;
61 }
62 std::cout << std::endl
63 << "---------------------------------------------------------" << std::endl;
64
65 for (const auto& type : table) {
66 std::cout << "Type " << type->type;
67 if (type->typeId != ResourceTableType::kUnsetTypeId) {
68 std::cout << " [" << type->typeId << "]";
69 }
70 std::cout << " (" << type->entries.size() << " entries)" << std::endl;
71 for (const auto& entry : type->entries) {
72 std::cout << " " << entry->name;
73 if (entry->entryId != ResourceEntry::kUnsetEntryId) {
74 std::cout << " [" << entry->entryId << "]";
75 }
76 std::cout << " (" << entry->values.size() << " configurations)";
77 if (entry->publicStatus.isPublic) {
78 std::cout << " PUBLIC";
79 }
80 std::cout << std::endl;
81 for (const auto& value : entry->values) {
82 std::cout << " " << value.config << " (" << value.source << ") : ";
83 value.value->print(std::cout);
84 std::cout << std::endl;
85 }
86 }
87 }
88}
89
90void printStringPool(const StringPool& pool) {
91 std::cout << "String pool of length " << pool.size() << std::endl
92 << "---------------------------------------------------------" << std::endl;
93
94 size_t i = 0;
95 for (const auto& entry : pool) {
96 std::cout << "[" << i << "]: "
97 << entry->value
98 << " (Priority " << entry->context.priority
99 << ", Config '" << entry->context.config << "')"
100 << std::endl;
101 i++;
102 }
103}
104
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800105/**
106 * Collect files from 'root', filtering out any files that do not
107 * match the FileFilter 'filter'.
108 */
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700109bool walkTree(const Source& root, const FileFilter& filter,
110 std::vector<Source>* outEntries) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800111 bool error = false;
112
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700113 for (const std::string& dirName : listFiles(root.path)) {
114 std::string dir = root.path;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800115 appendPath(&dir, dirName);
116
117 FileType ft = getFileType(dir);
118 if (!filter(dirName, ft)) {
119 continue;
120 }
121
122 if (ft != FileType::kDirectory) {
123 continue;
124 }
125
126 for (const std::string& fileName : listFiles(dir)) {
127 std::string file(dir);
128 appendPath(&file, fileName);
129
130 FileType ft = getFileType(file);
131 if (!filter(fileName, ft)) {
132 continue;
133 }
134
135 if (ft != FileType::kRegular) {
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700136 Logger::error(Source{ file }) << "not a regular file." << std::endl;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800137 error = true;
138 continue;
139 }
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700140 outEntries->push_back(Source{ file });
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800141 }
142 }
143 return !error;
144}
145
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800146bool loadResTable(android::ResTable* table, const Source& source) {
147 std::ifstream ifs(source.path, std::ifstream::in | std::ifstream::binary);
148 if (!ifs) {
149 Logger::error(source) << strerror(errno) << std::endl;
150 return false;
151 }
152
153 std::streampos fsize = ifs.tellg();
154 ifs.seekg(0, std::ios::end);
155 fsize = ifs.tellg() - fsize;
156 ifs.seekg(0, std::ios::beg);
157
158 assert(fsize >= 0);
159 size_t dataSize = static_cast<size_t>(fsize);
160 char* buf = new char[dataSize];
161 ifs.read(buf, dataSize);
162
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700163 bool result = table->add(buf, dataSize, -1, true) == android::NO_ERROR;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800164
165 delete [] buf;
166 return result;
167}
168
Adam Lesinski769de982015-04-10 19:43:55 -0700169void versionStylesForCompat(const std::shared_ptr<ResourceTable>& table) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800170 for (auto& type : *table) {
171 if (type->type != ResourceType::kStyle) {
172 continue;
173 }
174
175 for (auto& entry : type->entries) {
176 // Add the versioned styles we want to create
177 // here. They are added to the table after
178 // iterating over the original set of styles.
179 //
180 // A stack is used since auto-generated styles
181 // from later versions should override
182 // auto-generated styles from earlier versions.
183 // Iterating over the styles is done in order,
184 // so we will always visit sdkVersions from smallest
185 // to largest.
186 std::stack<ResourceConfigValue> addStack;
187
188 for (ResourceConfigValue& configValue : entry->values) {
189 visitFunc<Style>(*configValue.value, [&](Style& style) {
190 // Collect which entries we've stripped and the smallest
191 // SDK level which was stripped.
192 size_t minSdkStripped = std::numeric_limits<size_t>::max();
193 std::vector<Style::Entry> stripped;
194
195 // Iterate over the style's entries and erase/record the
196 // attributes whose SDK level exceeds the config's sdkVersion.
197 auto iter = style.entries.begin();
198 while (iter != style.entries.end()) {
199 if (iter->key.name.package == u"android") {
200 size_t sdkLevel = findAttributeSdkLevel(iter->key.name.entry);
201 if (sdkLevel > 1 && sdkLevel > configValue.config.sdkVersion) {
202 // Record that we are about to strip this.
203 stripped.emplace_back(std::move(*iter));
204 minSdkStripped = std::min(minSdkStripped, sdkLevel);
205
206 // Erase this from this style.
207 iter = style.entries.erase(iter);
208 continue;
209 }
210 }
211 ++iter;
212 }
213
214 if (!stripped.empty()) {
215 // We have stripped attributes, so let's create a new style to hold them.
216 ConfigDescription versionConfig(configValue.config);
217 versionConfig.sdkVersion = minSdkStripped;
218
219 ResourceConfigValue value = {
220 versionConfig,
221 configValue.source,
222 {},
223
224 // Create a copy of the original style.
Adam Lesinski769de982015-04-10 19:43:55 -0700225 std::unique_ptr<Value>(configValue.value->clone(
226 &table->getValueStringPool()))
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800227 };
228
229 Style& newStyle = static_cast<Style&>(*value.value);
Adam Lesinski769de982015-04-10 19:43:55 -0700230 newStyle.weak = true;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800231
232 // Move the recorded stripped attributes into this new style.
233 std::move(stripped.begin(), stripped.end(),
234 std::back_inserter(newStyle.entries));
235
236 // We will add this style to the table later. If we do it now, we will
237 // mess up iteration.
238 addStack.push(std::move(value));
239 }
240 });
241 }
242
243 auto comparator =
244 [](const ResourceConfigValue& lhs, const ConfigDescription& rhs) -> bool {
245 return lhs.config < rhs;
246 };
247
248 while (!addStack.empty()) {
249 ResourceConfigValue& value = addStack.top();
250 auto iter = std::lower_bound(entry->values.begin(), entry->values.end(),
251 value.config, comparator);
252 if (iter == entry->values.end() || iter->config != value.config) {
253 entry->values.insert(iter, std::move(value));
254 }
255 addStack.pop();
256 }
257 }
258 }
259}
260
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700261struct CompileItem {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800262 Source source;
263 ResourceName name;
264 ConfigDescription config;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700265 std::string extension;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800266};
267
Adam Lesinski769de982015-04-10 19:43:55 -0700268struct LinkItem {
269 Source source;
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700270 ResourceName name;
271 ConfigDescription config;
272 std::string originalPath;
273 ZipFile* apk;
Adam Lesinski769de982015-04-10 19:43:55 -0700274};
275
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700276template <typename TChar>
277static BasicStringPiece<TChar> getExtension(const BasicStringPiece<TChar>& str) {
278 auto iter = std::find(str.begin(), str.end(), static_cast<TChar>('.'));
279 if (iter == str.end()) {
280 return BasicStringPiece<TChar>();
Adam Lesinski769de982015-04-10 19:43:55 -0700281 }
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700282 size_t offset = (iter - str.begin()) + 1;
283 return str.substr(offset, str.size() - offset);
284}
285
286
287
288std::string buildFileReference(const ResourceNameRef& name, const ConfigDescription& config,
289 const StringPiece& extension) {
290 std::stringstream path;
291 path << "res/" << name.type;
292 if (config != ConfigDescription{}) {
293 path << "-" << config;
294 }
295 path << "/" << util::utf16ToUtf8(name.entry);
296 if (!extension.empty()) {
297 path << "." << extension;
298 }
Adam Lesinski769de982015-04-10 19:43:55 -0700299 return path.str();
300}
301
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700302std::string buildFileReference(const CompileItem& item) {
303 return buildFileReference(item.name, item.config, item.extension);
304}
305
306std::string buildFileReference(const LinkItem& item) {
307 return buildFileReference(item.name, item.config, getExtension<char>(item.originalPath));
308}
309
Adam Lesinski769de982015-04-10 19:43:55 -0700310bool addFileReference(const std::shared_ptr<ResourceTable>& table, const CompileItem& item) {
311 StringPool& pool = table->getValueStringPool();
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700312 StringPool::Ref ref = pool.makeRef(util::utf8ToUtf16(buildFileReference(item)),
313 StringPool::Context{ 0, item.config });
Adam Lesinski769de982015-04-10 19:43:55 -0700314 return table->addResource(item.name, item.config, item.source.line(0),
315 util::make_unique<FileReference>(ref));
316}
317
318struct AaptOptions {
319 enum class Phase {
320 Link,
321 Compile,
322 };
323
324 // The phase to process.
325 Phase phase;
326
327 // Details about the app.
328 AppInfo appInfo;
329
330 // The location of the manifest file.
331 Source manifest;
332
333 // The APK files to link.
334 std::vector<Source> input;
335
336 // The libraries these files may reference.
337 std::vector<Source> libraries;
338
339 // Output path. This can be a directory or file
340 // depending on the phase.
341 Source output;
342
343 // Directory in which to write binding xml files.
344 Source bindingOutput;
345
346 // Directory to in which to generate R.java.
347 Maybe<Source> generateJavaClass;
348
349 // Whether to output verbose details about
350 // compilation.
351 bool verbose = false;
Adam Lesinski5886a922015-04-15 20:29:22 -0700352
353 // Whether or not to auto-version styles or layouts
354 // referencing attributes defined in a newer SDK
355 // level than the style or layout is defined for.
356 bool versionStylesAndLayouts = true;
Adam Lesinski769de982015-04-10 19:43:55 -0700357};
358
Adam Lesinski5886a922015-04-15 20:29:22 -0700359
Adam Lesinski769de982015-04-10 19:43:55 -0700360bool compileXml(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table,
361 const CompileItem& item, std::queue<CompileItem>* outQueue, ZipFile* outApk) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800362 std::ifstream in(item.source.path, std::ifstream::binary);
363 if (!in) {
364 Logger::error(item.source) << strerror(errno) << std::endl;
365 return false;
366 }
367
Adam Lesinski4d3a9872015-04-09 19:53:22 -0700368 BigBuffer outBuffer(1024);
Adam Lesinski769de982015-04-10 19:43:55 -0700369
370 // No resolver, since we are not compiling attributes here.
371 XmlFlattener flattener(table, {});
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800372
Adam Lesinski769de982015-04-10 19:43:55 -0700373 XmlFlattener::Options xmlOptions;
Adam Lesinski5886a922015-04-15 20:29:22 -0700374 if (options.versionStylesAndLayouts) {
375 // We strip attributes that do not belong in this version of the resource.
376 // Non-version qualified resources have an implicit version 1 requirement.
377 xmlOptions.maxSdkAttribute = item.config.sdkVersion ? item.config.sdkVersion : 1;
378 }
Adam Lesinski769de982015-04-10 19:43:55 -0700379
380 std::shared_ptr<BindingXmlPullParser> binding;
381 std::shared_ptr<XmlPullParser> parser = std::make_shared<SourceXmlPullParser>(in);
382 if (item.name.type == ResourceType::kLayout) {
383 // Layouts may have defined bindings, so we need to make sure they get processed.
384 binding = std::make_shared<BindingXmlPullParser>(parser);
385 parser = binding;
386 }
387
388 Maybe<size_t> minStrippedSdk = flattener.flatten(item.source, parser, &outBuffer, xmlOptions);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800389 if (!minStrippedSdk) {
390 return false;
391 }
392
393 if (minStrippedSdk.value() > 0) {
394 // Something was stripped, so let's generate a new file
395 // with the version of the smallest SDK version stripped.
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700396 CompileItem newWork = item;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800397 newWork.config.sdkVersion = minStrippedSdk.value();
Adam Lesinski769de982015-04-10 19:43:55 -0700398 outQueue->push(newWork);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800399 }
400
Adam Lesinski769de982015-04-10 19:43:55 -0700401 // Write the resulting compiled XML file to the output APK.
402 if (outApk->add(outBuffer, buildFileReference(item).data(), ZipEntry::kCompressStored,
403 nullptr) != android::NO_ERROR) {
404 Logger::error(options.output) << "failed to write compiled '" << item.source << "' to apk."
405 << std::endl;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800406 return false;
407 }
408
Adam Lesinski769de982015-04-10 19:43:55 -0700409 if (binding && !options.bindingOutput.path.empty()) {
410 // We generated a binding xml file, write it out.
411 Source bindingOutput = options.bindingOutput;
412 appendPath(&bindingOutput.path, buildFileReference(item));
Adam Lesinski4d3a9872015-04-09 19:53:22 -0700413
Adam Lesinski769de982015-04-10 19:43:55 -0700414 if (!mkdirs(bindingOutput.path)) {
415 Logger::error(bindingOutput) << strerror(errno) << std::endl;
416 return false;
417 }
418
419 appendPath(&bindingOutput.path, "bind.xml");
420
Adam Lesinski4d3a9872015-04-09 19:53:22 -0700421 std::ofstream bout(bindingOutput.path);
422 if (!bout) {
423 Logger::error(bindingOutput) << strerror(errno) << std::endl;
424 return false;
425 }
426
427 if (!binding->writeToFile(bout)) {
428 Logger::error(bindingOutput) << strerror(errno) << std::endl;
429 return false;
430 }
431 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800432 return true;
433}
434
Adam Lesinski769de982015-04-10 19:43:55 -0700435bool linkXml(const AaptOptions& options, const std::shared_ptr<Resolver>& resolver,
436 const LinkItem& item, const void* data, size_t dataLen, ZipFile* outApk) {
437 std::shared_ptr<android::ResXMLTree> tree = std::make_shared<android::ResXMLTree>();
438 if (tree->setTo(data, dataLen, false) != android::NO_ERROR) {
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700439 return false;
440 }
441
Adam Lesinski769de982015-04-10 19:43:55 -0700442 std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<BinaryXmlPullParser>(tree);
443
444 BigBuffer outBuffer(1024);
445 XmlFlattener flattener({}, resolver);
446 if (!flattener.flatten(item.source, xmlParser, &outBuffer, {})) {
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700447 return false;
448 }
449
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700450 if (outApk->add(outBuffer, buildFileReference(item).data(), ZipEntry::kCompressDeflated,
451 nullptr) != android::NO_ERROR) {
Adam Lesinski769de982015-04-10 19:43:55 -0700452 Logger::error(options.output) << "failed to write linked file '" << item.source
453 << "' to apk." << std::endl;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700454 return false;
455 }
456 return true;
457}
458
Adam Lesinski769de982015-04-10 19:43:55 -0700459bool compilePng(const AaptOptions& options, const CompileItem& item, ZipFile* outApk) {
460 std::ifstream in(item.source.path, std::ifstream::binary);
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700461 if (!in) {
Adam Lesinski769de982015-04-10 19:43:55 -0700462 Logger::error(item.source) << strerror(errno) << std::endl;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700463 return false;
464 }
465
Adam Lesinski769de982015-04-10 19:43:55 -0700466 BigBuffer outBuffer(4096);
467 std::string err;
468 Png png;
469 if (!png.process(item.source, in, &outBuffer, {}, &err)) {
470 Logger::error(item.source) << err << std::endl;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700471 return false;
472 }
473
Adam Lesinski769de982015-04-10 19:43:55 -0700474 if (outApk->add(outBuffer, buildFileReference(item).data(), ZipEntry::kCompressStored,
475 nullptr) != android::NO_ERROR) {
476 Logger::error(options.output) << "failed to write compiled '" << item.source
477 << "' to apk." << std::endl;
478 return false;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700479 }
Adam Lesinski769de982015-04-10 19:43:55 -0700480 return true;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700481}
482
Adam Lesinski769de982015-04-10 19:43:55 -0700483bool copyFile(const AaptOptions& options, const CompileItem& item, ZipFile* outApk) {
484 if (outApk->add(item.source.path.data(), buildFileReference(item).data(),
485 ZipEntry::kCompressStored, nullptr) != android::NO_ERROR) {
486 Logger::error(options.output) << "failed to copy file '" << item.source << "' to apk."
487 << std::endl;
488 return false;
489 }
490 return true;
491}
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800492
Adam Lesinski769de982015-04-10 19:43:55 -0700493bool compileManifest(const AaptOptions& options, const std::shared_ptr<Resolver>& resolver,
494 ZipFile* outApk) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800495 if (options.verbose) {
Adam Lesinski769de982015-04-10 19:43:55 -0700496 Logger::note(options.manifest) << "compiling AndroidManifest.xml." << std::endl;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800497 }
498
499 std::ifstream in(options.manifest.path, std::ifstream::binary);
500 if (!in) {
501 Logger::error(options.manifest) << strerror(errno) << std::endl;
502 return false;
503 }
504
505 BigBuffer outBuffer(1024);
506 std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<SourceXmlPullParser>(in);
Adam Lesinski769de982015-04-10 19:43:55 -0700507 XmlFlattener flattener({}, resolver);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800508
Adam Lesinski769de982015-04-10 19:43:55 -0700509 if (!flattener.flatten(options.manifest, xmlParser, &outBuffer, {})) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800510 return false;
511 }
512
Adam Lesinski769de982015-04-10 19:43:55 -0700513 std::unique_ptr<uint8_t[]> data = util::copy(outBuffer);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800514
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700515 android::ResXMLTree tree;
Adam Lesinski769de982015-04-10 19:43:55 -0700516 if (tree.setTo(data.get(), outBuffer.size(), false) != android::NO_ERROR) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800517 return false;
518 }
519
520 ManifestValidator validator(resolver->getResTable());
521 if (!validator.validate(options.manifest, &tree)) {
522 return false;
523 }
524
Adam Lesinski769de982015-04-10 19:43:55 -0700525 if (outApk->add(data.get(), outBuffer.size(), "AndroidManifest.xml",
526 ZipEntry::kCompressStored, nullptr) != android::NO_ERROR) {
527 Logger::error(options.output) << "failed to write 'AndroidManifest.xml' to apk."
528 << std::endl;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800529 return false;
530 }
531 return true;
532}
533
Adam Lesinski769de982015-04-10 19:43:55 -0700534static bool compileValues(const std::shared_ptr<ResourceTable>& table, const Source& source,
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700535 const ConfigDescription& config) {
536 std::ifstream in(source.path, std::ifstream::binary);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800537 if (!in) {
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700538 Logger::error(source) << strerror(errno) << std::endl;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800539 return false;
540 }
541
542 std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<SourceXmlPullParser>(in);
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700543 ResourceParser parser(table, source, config, xmlParser);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800544 return parser.parse();
545}
546
547struct ResourcePathData {
548 std::u16string resourceDir;
549 std::u16string name;
550 std::string extension;
551 ConfigDescription config;
552};
553
554/**
555 * Resource file paths are expected to look like:
556 * [--/res/]type[-config]/name
557 */
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700558static Maybe<ResourcePathData> extractResourcePathData(const Source& source) {
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700559 // TODO(adamlesinski): Use Windows path separator on windows.
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800560 std::vector<std::string> parts = util::splitAndLowercase(source.path, '/');
561 if (parts.size() < 2) {
562 Logger::error(source) << "bad resource path." << std::endl;
563 return {};
564 }
565
566 std::string& dir = parts[parts.size() - 2];
567 StringPiece dirStr = dir;
568
569 ConfigDescription config;
570 size_t dashPos = dir.find('-');
571 if (dashPos != std::string::npos) {
572 StringPiece configStr = dirStr.substr(dashPos + 1, dir.size() - (dashPos + 1));
573 if (!ConfigDescription::parse(configStr, &config)) {
574 Logger::error(source)
575 << "invalid configuration '"
576 << configStr
577 << "'."
578 << std::endl;
579 return {};
580 }
581 dirStr = dirStr.substr(0, dashPos);
582 }
583
584 std::string& filename = parts[parts.size() - 1];
585 StringPiece name = filename;
586 StringPiece extension;
587 size_t dotPos = filename.find('.');
588 if (dotPos != std::string::npos) {
589 extension = name.substr(dotPos + 1, filename.size() - (dotPos + 1));
590 name = name.substr(0, dotPos);
591 }
592
593 return ResourcePathData{
594 util::utf8ToUtf16(dirStr),
595 util::utf8ToUtf16(name),
596 extension.toString(),
597 config
598 };
599}
600
Adam Lesinski769de982015-04-10 19:43:55 -0700601bool writeResourceTable(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table,
602 const TableFlattener::Options& flattenerOptions, ZipFile* outApk) {
603 if (table->begin() != table->end()) {
604 BigBuffer buffer(1024);
605 TableFlattener flattener(flattenerOptions);
606 if (!flattener.flatten(&buffer, *table)) {
607 Logger::error() << "failed to flatten resource table." << std::endl;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700608 return false;
609 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800610
Adam Lesinski769de982015-04-10 19:43:55 -0700611 if (options.verbose) {
612 Logger::note() << "Final resource table size=" << util::formatSize(buffer.size())
613 << std::endl;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700614 }
615
Adam Lesinski769de982015-04-10 19:43:55 -0700616 if (outApk->add(buffer, "resources.arsc", ZipEntry::kCompressStored, nullptr) !=
617 android::NO_ERROR) {
618 Logger::note(options.output) << "failed to store resource table." << std::endl;
619 return false;
620 }
621 }
622 return true;
623}
624
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700625/**
626 * For each FileReference in the table, adds a LinkItem to the link queue for processing.
627 */
628static void addApkFilesToLinkQueue(const std::u16string& package, const Source& source,
629 const std::shared_ptr<ResourceTable>& table,
630 const std::unique_ptr<ZipFile>& apk,
631 std::queue<LinkItem>* outLinkQueue) {
632 bool mangle = package != table->getPackage();
633 for (auto& type : *table) {
634 for (auto& entry : type->entries) {
635 ResourceName name = { package, type->type, entry->name };
636 if (mangle) {
637 NameMangler::mangle(table->getPackage(), &name.entry);
638 }
639
640 for (auto& value : entry->values) {
641 visitFunc<FileReference>(*value.value, [&](FileReference& ref) {
642 std::string pathUtf8 = util::utf16ToUtf8(*ref.path);
643 outLinkQueue->push(LinkItem{
644 source, name, value.config, pathUtf8, apk.get() });
645 // Now rewrite the file path.
646 if (mangle) {
647 ref.path = table->getValueStringPool().makeRef(util::utf8ToUtf16(
648 buildFileReference(name, value.config,
649 getExtension<char>(pathUtf8))));
650 }
651 });
652 }
653 }
654 }
655}
656
Adam Lesinski769de982015-04-10 19:43:55 -0700657static constexpr int kOpenFlags = ZipFile::kOpenCreate | ZipFile::kOpenTruncate |
658 ZipFile::kOpenReadWrite;
659
660bool link(const AaptOptions& options, const std::shared_ptr<ResourceTable>& outTable,
661 const std::shared_ptr<Resolver>& resolver) {
662 std::map<std::shared_ptr<ResourceTable>, std::unique_ptr<ZipFile>> apkFiles;
663 std::unordered_set<std::u16string> linkedPackages;
664
665 // Populate the linkedPackages with our own.
666 linkedPackages.insert(options.appInfo.package);
667
668 // Load all APK files.
669 for (const Source& source : options.input) {
670 std::unique_ptr<ZipFile> zipFile = util::make_unique<ZipFile>();
671 if (zipFile->open(source.path.data(), ZipFile::kOpenReadOnly) != android::NO_ERROR) {
672 Logger::error(source) << "failed to open: " << strerror(errno) << std::endl;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700673 return false;
674 }
675
Adam Lesinski769de982015-04-10 19:43:55 -0700676 std::shared_ptr<ResourceTable> table = std::make_shared<ResourceTable>();
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700677
Adam Lesinski769de982015-04-10 19:43:55 -0700678 ZipEntry* entry = zipFile->getEntryByName("resources.arsc");
679 if (!entry) {
680 Logger::error(source) << "missing 'resources.arsc'." << std::endl;
681 return false;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700682 }
683
Adam Lesinski769de982015-04-10 19:43:55 -0700684 void* uncompressedData = zipFile->uncompress(entry);
685 assert(uncompressedData);
686
687 BinaryResourceParser parser(table, resolver, source, uncompressedData,
688 entry->getUncompressedLen());
689 if (!parser.parse()) {
690 free(uncompressedData);
691 return false;
692 }
693 free(uncompressedData);
694
695 // Keep track of where this table came from.
696 apkFiles[table] = std::move(zipFile);
697
698 // Add the package to the set of linked packages.
699 linkedPackages.insert(table->getPackage());
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700700 }
701
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700702 std::queue<LinkItem> linkQueue;
Adam Lesinski769de982015-04-10 19:43:55 -0700703 for (auto& p : apkFiles) {
704 const std::shared_ptr<ResourceTable>& inTable = p.first;
705
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700706 // Collect all FileReferences and add them to the queue for processing.
707 addApkFilesToLinkQueue(options.appInfo.package, Source{}, inTable, p.second, &linkQueue);
708
709 // Merge the tables.
Adam Lesinski769de982015-04-10 19:43:55 -0700710 if (!outTable->merge(std::move(*inTable))) {
711 return false;
712 }
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700713 }
714
Adam Lesinski769de982015-04-10 19:43:55 -0700715 {
716 // Now that everything is merged, let's link it.
717 Linker linker(outTable, resolver);
718 if (!linker.linkAndValidate()) {
719 return false;
720 }
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700721
Adam Lesinski769de982015-04-10 19:43:55 -0700722 // Verify that all symbols exist.
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700723 const auto& unresolvedRefs = linker.getUnresolvedReferences();
724 if (!unresolvedRefs.empty()) {
725 for (const auto& entry : unresolvedRefs) {
726 for (const auto& source : entry.second) {
727 Logger::error(source) << "unresolved symbol '" << entry.first << "'."
728 << std::endl;
729 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800730 }
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700731 return false;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800732 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800733 }
734
Adam Lesinski769de982015-04-10 19:43:55 -0700735 // Open the output APK file for writing.
736 ZipFile outApk;
737 if (outApk.open(options.output.path.data(), kOpenFlags) != android::NO_ERROR) {
738 Logger::error(options.output) << "failed to open: " << strerror(errno) << std::endl;
739 return false;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700740 }
741
Adam Lesinski769de982015-04-10 19:43:55 -0700742 if (!compileManifest(options, resolver, &outApk)) {
743 return false;
744 }
745
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700746 for (; !linkQueue.empty(); linkQueue.pop()) {
747 const LinkItem& item = linkQueue.front();
Adam Lesinski769de982015-04-10 19:43:55 -0700748
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700749 ZipEntry* entry = item.apk->getEntryByName(item.originalPath.data());
750 if (!entry) {
751 Logger::error(item.source) << "failed to find '" << item.originalPath << "'."
752 << std::endl;
753 return false;
754 }
Adam Lesinski769de982015-04-10 19:43:55 -0700755
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700756 if (util::stringEndsWith<char>(item.originalPath, ".xml")) {
757 void* uncompressedData = item.apk->uncompress(entry);
758 assert(uncompressedData);
Adam Lesinski769de982015-04-10 19:43:55 -0700759
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700760 if (!linkXml(options, resolver, item, uncompressedData, entry->getUncompressedLen(),
761 &outApk)) {
762 Logger::error(options.output) << "failed to link '" << item.originalPath << "'."
763 << std::endl;
764 return false;
Adam Lesinski769de982015-04-10 19:43:55 -0700765 }
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700766 } else {
767 if (outApk.add(item.apk, entry, buildFileReference(item).data(), 0, nullptr) !=
768 android::NO_ERROR) {
769 Logger::error(options.output) << "failed to copy '" << item.originalPath << "'."
770 << std::endl;
771 return false;
Adam Lesinski769de982015-04-10 19:43:55 -0700772 }
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700773 }
774 }
775
776 // Generate the Java class file.
Adam Lesinski769de982015-04-10 19:43:55 -0700777 if (options.generateJavaClass) {
778 JavaClassGenerator generator(outTable, {});
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700779
Adam Lesinski769de982015-04-10 19:43:55 -0700780 for (const std::u16string& package : linkedPackages) {
781 Source outPath = options.generateJavaClass.value();
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800782
Adam Lesinski769de982015-04-10 19:43:55 -0700783 // Build the output directory from the package name.
784 // Eg. com.android.app -> com/android/app
785 const std::string packageUtf8 = util::utf16ToUtf8(package);
786 for (StringPiece part : util::tokenize<char>(packageUtf8, '.')) {
787 appendPath(&outPath.path, part);
788 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800789
Adam Lesinski769de982015-04-10 19:43:55 -0700790 if (!mkdirs(outPath.path)) {
791 Logger::error(outPath) << strerror(errno) << std::endl;
792 return false;
793 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800794
Adam Lesinski769de982015-04-10 19:43:55 -0700795 appendPath(&outPath.path, "R.java");
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800796
Adam Lesinski769de982015-04-10 19:43:55 -0700797 if (options.verbose) {
798 Logger::note(outPath) << "writing Java symbols." << std::endl;
799 }
800
801 std::ofstream fout(outPath.path);
802 if (!fout) {
803 Logger::error(outPath) << strerror(errno) << std::endl;
804 return false;
805 }
806
807 if (!generator.generate(package, fout)) {
808 Logger::error(outPath) << generator.getError() << "." << std::endl;
809 return false;
810 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800811 }
812 }
813
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700814 // Flatten the resource table.
Adam Lesinski769de982015-04-10 19:43:55 -0700815 TableFlattener::Options flattenerOptions;
816 flattenerOptions.useExtendedChunks = false;
817 if (!writeResourceTable(options, outTable, flattenerOptions, &outApk)) {
818 return false;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800819 }
Adam Lesinski769de982015-04-10 19:43:55 -0700820
821 outApk.flush();
822 return true;
823}
824
825bool compile(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table,
826 const std::shared_ptr<Resolver>& resolver) {
827 std::queue<CompileItem> compileQueue;
828 bool error = false;
829
830 // Compile all the resource files passed in on the command line.
831 for (const Source& source : options.input) {
832 // Need to parse the resource type/config/filename.
833 Maybe<ResourcePathData> maybePathData = extractResourcePathData(source);
834 if (!maybePathData) {
835 return false;
836 }
837
838 const ResourcePathData& pathData = maybePathData.value();
839 if (pathData.resourceDir == u"values") {
840 // The file is in the values directory, which means its contents will
841 // go into the resource table.
842 if (options.verbose) {
843 Logger::note(source) << "compiling values." << std::endl;
844 }
845
846 error |= !compileValues(table, source, pathData.config);
847 } else {
848 // The file is in a directory like 'layout' or 'drawable'. Find out
849 // the type.
850 const ResourceType* type = parseResourceType(pathData.resourceDir);
851 if (!type) {
852 Logger::error(source) << "invalid resource type '" << pathData.resourceDir << "'."
853 << std::endl;
854 return false;
855 }
856
857 compileQueue.push(CompileItem{
858 source,
859 ResourceName{ table->getPackage(), *type, pathData.name },
860 pathData.config,
861 pathData.extension
862 });
863 }
864 }
865
866 if (error) {
867 return false;
868 }
869
870 // Version all styles referencing attributes outside of their specified SDK version.
Adam Lesinski5886a922015-04-15 20:29:22 -0700871 if (options.versionStylesAndLayouts) {
872 versionStylesForCompat(table);
873 }
Adam Lesinski769de982015-04-10 19:43:55 -0700874
875 // Open the output APK file for writing.
876 ZipFile outApk;
877 if (outApk.open(options.output.path.data(), kOpenFlags) != android::NO_ERROR) {
878 Logger::error(options.output) << "failed to open: " << strerror(errno) << std::endl;
879 return false;
880 }
881
882 // Compile each file.
883 for (; !compileQueue.empty(); compileQueue.pop()) {
884 const CompileItem& item = compileQueue.front();
885
886 // Add the file name to the resource table.
887 error |= !addFileReference(table, item);
888
889 if (item.extension == "xml") {
890 error |= !compileXml(options, table, item, &compileQueue, &outApk);
891 } else if (item.extension == "png" || item.extension == "9.png") {
892 error |= !compilePng(options, item, &outApk);
893 } else {
894 error |= !copyFile(options, item, &outApk);
895 }
896 }
897
898 if (error) {
899 return false;
900 }
901
902 // Link and assign resource IDs.
903 Linker linker(table, resolver);
904 if (!linker.linkAndValidate()) {
905 return false;
906 }
907
908 // Flatten the resource table.
909 if (!writeResourceTable(options, table, {}, &outApk)) {
910 return false;
911 }
912
913 outApk.flush();
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800914 return true;
915}
916
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700917bool loadAppInfo(const Source& source, AppInfo* outInfo) {
918 std::ifstream ifs(source.path, std::ifstream::in | std::ifstream::binary);
919 if (!ifs) {
920 Logger::error(source) << strerror(errno) << std::endl;
921 return false;
922 }
923
924 ManifestParser parser;
925 std::shared_ptr<XmlPullParser> pullParser = std::make_shared<SourceXmlPullParser>(ifs);
926 return parser.parse(source, pullParser, outInfo);
927}
928
929static void printCommandsAndDie() {
930 std::cerr << "The following commands are supported:" << std::endl << std::endl;
931 std::cerr << "compile compiles a subset of resources" << std::endl;
932 std::cerr << "link links together compiled resources and libraries" << std::endl;
933 std::cerr << std::endl;
934 std::cerr << "run aapt2 with one of the commands and the -h flag for extra details."
935 << std::endl;
936 exit(1);
937}
938
939static AaptOptions prepareArgs(int argc, char** argv) {
940 if (argc < 2) {
941 std::cerr << "no command specified." << std::endl << std::endl;
942 printCommandsAndDie();
943 }
944
945 const StringPiece command(argv[1]);
946 argc -= 2;
947 argv += 2;
948
949 AaptOptions options;
950
951 if (command == "--version" || command == "version") {
952 std::cout << kAaptVersionStr << std::endl;
953 exit(0);
954 } else if (command == "link") {
955 options.phase = AaptOptions::Phase::Link;
956 } else if (command == "compile") {
957 options.phase = AaptOptions::Phase::Compile;
958 } else {
959 std::cerr << "invalid command '" << command << "'." << std::endl << std::endl;
960 printCommandsAndDie();
961 }
962
963 if (options.phase == AaptOptions::Phase::Compile) {
964 flag::requiredFlag("--package", "Android package name",
965 [&options](const StringPiece& arg) {
966 options.appInfo.package = util::utf8ToUtf16(arg);
967 });
968 flag::optionalFlag("--binding", "Output directory for binding XML files",
969 [&options](const StringPiece& arg) {
970 options.bindingOutput = Source{ arg.toString() };
971 });
972 flag::optionalSwitch("--no-version", "Disables automatic style and layout versioning",
973 false, &options.versionStylesAndLayouts);
974
975 } else if (options.phase == AaptOptions::Phase::Link) {
976 flag::requiredFlag("--manifest", "AndroidManifest.xml of your app",
977 [&options](const StringPiece& arg) {
978 options.manifest = Source{ arg.toString() };
979 });
980
981 flag::optionalFlag("-I", "add an Android APK to link against",
982 [&options](const StringPiece& arg) {
983 options.libraries.push_back(Source{ arg.toString() });
984 });
985
986 flag::optionalFlag("--java", "directory in which to generate R.java",
987 [&options](const StringPiece& arg) {
988 options.generateJavaClass = Source{ arg.toString() };
989 });
990 }
991
992 // Common flags for all steps.
993 flag::requiredFlag("-o", "Output path", [&options](const StringPiece& arg) {
994 options.output = Source{ arg.toString() };
995 });
996
997 bool help = false;
998 flag::optionalSwitch("-v", "enables verbose logging", true, &options.verbose);
999 flag::optionalSwitch("-h", "displays this help menu", true, &help);
1000
1001 // Build the command string for output (eg. "aapt2 compile").
1002 std::string fullCommand = "aapt2";
1003 fullCommand += " ";
1004 fullCommand += command.toString();
1005
1006 // Actually read the command line flags.
1007 flag::parse(argc, argv, fullCommand);
1008
1009 if (help) {
1010 flag::usageAndDie(fullCommand);
1011 }
1012
1013 // Copy all the remaining arguments.
1014 for (const std::string& arg : flag::getArgs()) {
1015 options.input.push_back(Source{ arg });
1016 }
1017 return options;
1018}
1019
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001020int main(int argc, char** argv) {
1021 Logger::setLog(std::make_shared<Log>(std::cerr, std::cerr));
Adam Lesinski98aa3ad2015-04-06 11:46:52 -07001022 AaptOptions options = prepareArgs(argc, argv);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001023
Adam Lesinski98aa3ad2015-04-06 11:46:52 -07001024 // If we specified a manifest, go ahead and load the package name from the manifest.
1025 if (!options.manifest.path.empty()) {
1026 if (!loadAppInfo(options.manifest, &options.appInfo)) {
1027 return false;
1028 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001029 }
1030
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001031 // Verify we have some common options set.
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001032 if (options.appInfo.package.empty()) {
1033 Logger::error() << "no package name specified." << std::endl;
1034 return false;
1035 }
1036
Adam Lesinski98aa3ad2015-04-06 11:46:52 -07001037 // Every phase needs a resource table.
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001038 std::shared_ptr<ResourceTable> table = std::make_shared<ResourceTable>();
1039 table->setPackage(options.appInfo.package);
1040 if (options.appInfo.package == u"android") {
1041 table->setPackageId(0x01);
1042 } else {
1043 table->setPackageId(0x7f);
1044 }
1045
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001046 // Load the included libraries.
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001047 std::shared_ptr<android::AssetManager> libraries = std::make_shared<android::AssetManager>();
1048 for (const Source& source : options.libraries) {
Adam Lesinski4d3a9872015-04-09 19:53:22 -07001049 if (util::stringEndsWith<char>(source.path, ".arsc")) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001050 // We'll process these last so as to avoid a cookie issue.
1051 continue;
1052 }
1053
1054 int32_t cookie;
1055 if (!libraries->addAssetPath(android::String8(source.path.data()), &cookie)) {
1056 Logger::error(source) << "failed to load library." << std::endl;
1057 return false;
1058 }
1059 }
1060
1061 for (const Source& source : options.libraries) {
Adam Lesinski4d3a9872015-04-09 19:53:22 -07001062 if (!util::stringEndsWith<char>(source.path, ".arsc")) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001063 // We've already processed this.
1064 continue;
1065 }
1066
1067 // Dirty hack but there is no other way to get a
1068 // writeable ResTable.
1069 if (!loadResTable(const_cast<android::ResTable*>(&libraries->getResources(false)),
1070 source)) {
1071 return false;
1072 }
1073 }
1074
1075 // Make the resolver that will cache IDs for us.
1076 std::shared_ptr<Resolver> resolver = std::make_shared<Resolver>(table, libraries);
1077
Adam Lesinski769de982015-04-10 19:43:55 -07001078 if (options.phase == AaptOptions::Phase::Compile) {
1079 if (!compile(options, table, resolver)) {
1080 Logger::error() << "aapt exiting with failures." << std::endl;
1081 return 1;
1082 }
1083 } else if (options.phase == AaptOptions::Phase::Link) {
1084 if (!link(options, table, resolver)) {
1085 Logger::error() << "aapt exiting with failures." << std::endl;
1086 return 1;
1087 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001088 }
1089 return 0;
1090}