blob: dedf8b469159a3015a885cd073df239e58757643 [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"
Adam Lesinski24aad162015-04-24 19:19:30 -070032#include "ResourceTableResolver.h"
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080033#include "ResourceValues.h"
34#include "SdkConstants.h"
35#include "SourceXmlPullParser.h"
36#include "StringPiece.h"
37#include "TableFlattener.h"
38#include "Util.h"
39#include "XmlFlattener.h"
Adam Lesinski769de982015-04-10 19:43:55 -070040#include "ZipFile.h"
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080041
42#include <algorithm>
43#include <androidfw/AssetManager.h>
44#include <cstdlib>
45#include <dirent.h>
46#include <errno.h>
47#include <fstream>
48#include <iostream>
49#include <sstream>
50#include <sys/stat.h>
Adam Lesinski769de982015-04-10 19:43:55 -070051#include <unordered_set>
Adam Lesinski98aa3ad2015-04-06 11:46:52 -070052#include <utils/Errors.h>
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080053
Adam Lesinski5886a922015-04-15 20:29:22 -070054constexpr const char* kAaptVersionStr = "2.0-alpha";
55
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080056using namespace aapt;
57
58void printTable(const ResourceTable& table) {
59 std::cout << "ResourceTable package=" << table.getPackage();
60 if (table.getPackageId() != ResourceTable::kUnsetPackageId) {
61 std::cout << " id=" << std::hex << table.getPackageId() << std::dec;
62 }
63 std::cout << std::endl
64 << "---------------------------------------------------------" << std::endl;
65
66 for (const auto& type : table) {
67 std::cout << "Type " << type->type;
68 if (type->typeId != ResourceTableType::kUnsetTypeId) {
69 std::cout << " [" << type->typeId << "]";
70 }
71 std::cout << " (" << type->entries.size() << " entries)" << std::endl;
72 for (const auto& entry : type->entries) {
73 std::cout << " " << entry->name;
74 if (entry->entryId != ResourceEntry::kUnsetEntryId) {
75 std::cout << " [" << entry->entryId << "]";
76 }
77 std::cout << " (" << entry->values.size() << " configurations)";
78 if (entry->publicStatus.isPublic) {
79 std::cout << " PUBLIC";
80 }
81 std::cout << std::endl;
82 for (const auto& value : entry->values) {
83 std::cout << " " << value.config << " (" << value.source << ") : ";
84 value.value->print(std::cout);
85 std::cout << std::endl;
86 }
87 }
88 }
89}
90
91void printStringPool(const StringPool& pool) {
92 std::cout << "String pool of length " << pool.size() << std::endl
93 << "---------------------------------------------------------" << std::endl;
94
95 size_t i = 0;
96 for (const auto& entry : pool) {
97 std::cout << "[" << i << "]: "
98 << entry->value
99 << " (Priority " << entry->context.priority
100 << ", Config '" << entry->context.config << "')"
101 << std::endl;
102 i++;
103 }
104}
105
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800106/**
107 * Collect files from 'root', filtering out any files that do not
108 * match the FileFilter 'filter'.
109 */
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700110bool walkTree(const Source& root, const FileFilter& filter,
111 std::vector<Source>* outEntries) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800112 bool error = false;
113
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700114 for (const std::string& dirName : listFiles(root.path)) {
115 std::string dir = root.path;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800116 appendPath(&dir, dirName);
117
118 FileType ft = getFileType(dir);
119 if (!filter(dirName, ft)) {
120 continue;
121 }
122
123 if (ft != FileType::kDirectory) {
124 continue;
125 }
126
127 for (const std::string& fileName : listFiles(dir)) {
128 std::string file(dir);
129 appendPath(&file, fileName);
130
131 FileType ft = getFileType(file);
132 if (!filter(fileName, ft)) {
133 continue;
134 }
135
136 if (ft != FileType::kRegular) {
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700137 Logger::error(Source{ file }) << "not a regular file." << std::endl;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800138 error = true;
139 continue;
140 }
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700141 outEntries->push_back(Source{ file });
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800142 }
143 }
144 return !error;
145}
146
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800147bool loadResTable(android::ResTable* table, const Source& source) {
148 std::ifstream ifs(source.path, std::ifstream::in | std::ifstream::binary);
149 if (!ifs) {
150 Logger::error(source) << strerror(errno) << std::endl;
151 return false;
152 }
153
154 std::streampos fsize = ifs.tellg();
155 ifs.seekg(0, std::ios::end);
156 fsize = ifs.tellg() - fsize;
157 ifs.seekg(0, std::ios::beg);
158
159 assert(fsize >= 0);
160 size_t dataSize = static_cast<size_t>(fsize);
161 char* buf = new char[dataSize];
162 ifs.read(buf, dataSize);
163
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700164 bool result = table->add(buf, dataSize, -1, true) == android::NO_ERROR;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800165
166 delete [] buf;
167 return result;
168}
169
Adam Lesinski769de982015-04-10 19:43:55 -0700170void versionStylesForCompat(const std::shared_ptr<ResourceTable>& table) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800171 for (auto& type : *table) {
172 if (type->type != ResourceType::kStyle) {
173 continue;
174 }
175
176 for (auto& entry : type->entries) {
177 // Add the versioned styles we want to create
178 // here. They are added to the table after
179 // iterating over the original set of styles.
180 //
181 // A stack is used since auto-generated styles
182 // from later versions should override
183 // auto-generated styles from earlier versions.
184 // Iterating over the styles is done in order,
185 // so we will always visit sdkVersions from smallest
186 // to largest.
187 std::stack<ResourceConfigValue> addStack;
188
189 for (ResourceConfigValue& configValue : entry->values) {
190 visitFunc<Style>(*configValue.value, [&](Style& style) {
191 // Collect which entries we've stripped and the smallest
192 // SDK level which was stripped.
193 size_t minSdkStripped = std::numeric_limits<size_t>::max();
194 std::vector<Style::Entry> stripped;
195
196 // Iterate over the style's entries and erase/record the
197 // attributes whose SDK level exceeds the config's sdkVersion.
198 auto iter = style.entries.begin();
199 while (iter != style.entries.end()) {
200 if (iter->key.name.package == u"android") {
201 size_t sdkLevel = findAttributeSdkLevel(iter->key.name.entry);
202 if (sdkLevel > 1 && sdkLevel > configValue.config.sdkVersion) {
203 // Record that we are about to strip this.
204 stripped.emplace_back(std::move(*iter));
205 minSdkStripped = std::min(minSdkStripped, sdkLevel);
206
207 // Erase this from this style.
208 iter = style.entries.erase(iter);
209 continue;
210 }
211 }
212 ++iter;
213 }
214
215 if (!stripped.empty()) {
216 // We have stripped attributes, so let's create a new style to hold them.
217 ConfigDescription versionConfig(configValue.config);
218 versionConfig.sdkVersion = minSdkStripped;
219
220 ResourceConfigValue value = {
221 versionConfig,
222 configValue.source,
223 {},
224
225 // Create a copy of the original style.
Adam Lesinski769de982015-04-10 19:43:55 -0700226 std::unique_ptr<Value>(configValue.value->clone(
227 &table->getValueStringPool()))
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800228 };
229
230 Style& newStyle = static_cast<Style&>(*value.value);
Adam Lesinski769de982015-04-10 19:43:55 -0700231 newStyle.weak = true;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800232
233 // Move the recorded stripped attributes into this new style.
234 std::move(stripped.begin(), stripped.end(),
235 std::back_inserter(newStyle.entries));
236
237 // We will add this style to the table later. If we do it now, we will
238 // mess up iteration.
239 addStack.push(std::move(value));
240 }
241 });
242 }
243
244 auto comparator =
245 [](const ResourceConfigValue& lhs, const ConfigDescription& rhs) -> bool {
246 return lhs.config < rhs;
247 };
248
249 while (!addStack.empty()) {
250 ResourceConfigValue& value = addStack.top();
251 auto iter = std::lower_bound(entry->values.begin(), entry->values.end(),
252 value.config, comparator);
253 if (iter == entry->values.end() || iter->config != value.config) {
254 entry->values.insert(iter, std::move(value));
255 }
256 addStack.pop();
257 }
258 }
259 }
260}
261
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700262struct CompileItem {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800263 Source source;
264 ResourceName name;
265 ConfigDescription config;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700266 std::string extension;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800267};
268
Adam Lesinski769de982015-04-10 19:43:55 -0700269struct LinkItem {
270 Source source;
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700271 ResourceName name;
272 ConfigDescription config;
273 std::string originalPath;
274 ZipFile* apk;
Adam Lesinski24aad162015-04-24 19:19:30 -0700275 std::u16string originalPackage;
Adam Lesinski769de982015-04-10 19:43:55 -0700276};
277
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700278template <typename TChar>
279static BasicStringPiece<TChar> getExtension(const BasicStringPiece<TChar>& str) {
280 auto iter = std::find(str.begin(), str.end(), static_cast<TChar>('.'));
281 if (iter == str.end()) {
282 return BasicStringPiece<TChar>();
Adam Lesinski769de982015-04-10 19:43:55 -0700283 }
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700284 size_t offset = (iter - str.begin()) + 1;
285 return str.substr(offset, str.size() - offset);
286}
287
288
289
290std::string buildFileReference(const ResourceNameRef& name, const ConfigDescription& config,
291 const StringPiece& extension) {
292 std::stringstream path;
293 path << "res/" << name.type;
294 if (config != ConfigDescription{}) {
295 path << "-" << config;
296 }
297 path << "/" << util::utf16ToUtf8(name.entry);
298 if (!extension.empty()) {
299 path << "." << extension;
300 }
Adam Lesinski769de982015-04-10 19:43:55 -0700301 return path.str();
302}
303
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700304std::string buildFileReference(const CompileItem& item) {
305 return buildFileReference(item.name, item.config, item.extension);
306}
307
308std::string buildFileReference(const LinkItem& item) {
309 return buildFileReference(item.name, item.config, getExtension<char>(item.originalPath));
310}
311
Adam Lesinski769de982015-04-10 19:43:55 -0700312bool addFileReference(const std::shared_ptr<ResourceTable>& table, const CompileItem& item) {
313 StringPool& pool = table->getValueStringPool();
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700314 StringPool::Ref ref = pool.makeRef(util::utf8ToUtf16(buildFileReference(item)),
315 StringPool::Context{ 0, item.config });
Adam Lesinski769de982015-04-10 19:43:55 -0700316 return table->addResource(item.name, item.config, item.source.line(0),
317 util::make_unique<FileReference>(ref));
318}
319
320struct AaptOptions {
321 enum class Phase {
322 Link,
323 Compile,
324 };
325
326 // The phase to process.
327 Phase phase;
328
329 // Details about the app.
330 AppInfo appInfo;
331
332 // The location of the manifest file.
333 Source manifest;
334
335 // The APK files to link.
336 std::vector<Source> input;
337
338 // The libraries these files may reference.
339 std::vector<Source> libraries;
340
341 // Output path. This can be a directory or file
342 // depending on the phase.
343 Source output;
344
345 // Directory in which to write binding xml files.
346 Source bindingOutput;
347
348 // Directory to in which to generate R.java.
349 Maybe<Source> generateJavaClass;
350
351 // Whether to output verbose details about
352 // compilation.
353 bool verbose = false;
Adam Lesinski5886a922015-04-15 20:29:22 -0700354
355 // Whether or not to auto-version styles or layouts
356 // referencing attributes defined in a newer SDK
357 // level than the style or layout is defined for.
358 bool versionStylesAndLayouts = true;
Adam Lesinski769de982015-04-10 19:43:55 -0700359};
360
Adam Lesinski5886a922015-04-15 20:29:22 -0700361
Adam Lesinski769de982015-04-10 19:43:55 -0700362bool compileXml(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table,
363 const CompileItem& item, std::queue<CompileItem>* outQueue, ZipFile* outApk) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800364 std::ifstream in(item.source.path, std::ifstream::binary);
365 if (!in) {
366 Logger::error(item.source) << strerror(errno) << std::endl;
367 return false;
368 }
369
Adam Lesinski4d3a9872015-04-09 19:53:22 -0700370 BigBuffer outBuffer(1024);
Adam Lesinski769de982015-04-10 19:43:55 -0700371
372 // No resolver, since we are not compiling attributes here.
373 XmlFlattener flattener(table, {});
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800374
Adam Lesinski769de982015-04-10 19:43:55 -0700375 XmlFlattener::Options xmlOptions;
Adam Lesinski24aad162015-04-24 19:19:30 -0700376 xmlOptions.defaultPackage = table->getPackage();
377
Adam Lesinski5886a922015-04-15 20:29:22 -0700378 if (options.versionStylesAndLayouts) {
379 // We strip attributes that do not belong in this version of the resource.
380 // Non-version qualified resources have an implicit version 1 requirement.
381 xmlOptions.maxSdkAttribute = item.config.sdkVersion ? item.config.sdkVersion : 1;
382 }
Adam Lesinski769de982015-04-10 19:43:55 -0700383
384 std::shared_ptr<BindingXmlPullParser> binding;
385 std::shared_ptr<XmlPullParser> parser = std::make_shared<SourceXmlPullParser>(in);
386 if (item.name.type == ResourceType::kLayout) {
387 // Layouts may have defined bindings, so we need to make sure they get processed.
388 binding = std::make_shared<BindingXmlPullParser>(parser);
389 parser = binding;
390 }
391
392 Maybe<size_t> minStrippedSdk = flattener.flatten(item.source, parser, &outBuffer, xmlOptions);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800393 if (!minStrippedSdk) {
394 return false;
395 }
396
397 if (minStrippedSdk.value() > 0) {
398 // Something was stripped, so let's generate a new file
399 // with the version of the smallest SDK version stripped.
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700400 CompileItem newWork = item;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800401 newWork.config.sdkVersion = minStrippedSdk.value();
Adam Lesinski769de982015-04-10 19:43:55 -0700402 outQueue->push(newWork);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800403 }
404
Adam Lesinski769de982015-04-10 19:43:55 -0700405 // Write the resulting compiled XML file to the output APK.
406 if (outApk->add(outBuffer, buildFileReference(item).data(), ZipEntry::kCompressStored,
407 nullptr) != android::NO_ERROR) {
408 Logger::error(options.output) << "failed to write compiled '" << item.source << "' to apk."
409 << std::endl;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800410 return false;
411 }
412
Adam Lesinski769de982015-04-10 19:43:55 -0700413 if (binding && !options.bindingOutput.path.empty()) {
414 // We generated a binding xml file, write it out.
415 Source bindingOutput = options.bindingOutput;
416 appendPath(&bindingOutput.path, buildFileReference(item));
Adam Lesinski4d3a9872015-04-09 19:53:22 -0700417
Adam Lesinski769de982015-04-10 19:43:55 -0700418 if (!mkdirs(bindingOutput.path)) {
419 Logger::error(bindingOutput) << strerror(errno) << std::endl;
420 return false;
421 }
422
423 appendPath(&bindingOutput.path, "bind.xml");
424
Adam Lesinski4d3a9872015-04-09 19:53:22 -0700425 std::ofstream bout(bindingOutput.path);
426 if (!bout) {
427 Logger::error(bindingOutput) << strerror(errno) << std::endl;
428 return false;
429 }
430
431 if (!binding->writeToFile(bout)) {
432 Logger::error(bindingOutput) << strerror(errno) << std::endl;
433 return false;
434 }
435 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800436 return true;
437}
438
Adam Lesinski24aad162015-04-24 19:19:30 -0700439bool linkXml(const AaptOptions& options, const std::shared_ptr<IResolver>& resolver,
Adam Lesinski769de982015-04-10 19:43:55 -0700440 const LinkItem& item, const void* data, size_t dataLen, ZipFile* outApk) {
441 std::shared_ptr<android::ResXMLTree> tree = std::make_shared<android::ResXMLTree>();
442 if (tree->setTo(data, dataLen, false) != android::NO_ERROR) {
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700443 return false;
444 }
445
Adam Lesinski769de982015-04-10 19:43:55 -0700446 std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<BinaryXmlPullParser>(tree);
447
448 BigBuffer outBuffer(1024);
449 XmlFlattener flattener({}, resolver);
Adam Lesinski24aad162015-04-24 19:19:30 -0700450
451 XmlFlattener::Options xmlOptions;
452 xmlOptions.defaultPackage = item.originalPackage;
453 if (!flattener.flatten(item.source, xmlParser, &outBuffer, xmlOptions)) {
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700454 return false;
455 }
456
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700457 if (outApk->add(outBuffer, buildFileReference(item).data(), ZipEntry::kCompressDeflated,
458 nullptr) != android::NO_ERROR) {
Adam Lesinski769de982015-04-10 19:43:55 -0700459 Logger::error(options.output) << "failed to write linked file '" << item.source
460 << "' to apk." << std::endl;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700461 return false;
462 }
463 return true;
464}
465
Adam Lesinski769de982015-04-10 19:43:55 -0700466bool compilePng(const AaptOptions& options, const CompileItem& item, ZipFile* outApk) {
467 std::ifstream in(item.source.path, std::ifstream::binary);
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700468 if (!in) {
Adam Lesinski769de982015-04-10 19:43:55 -0700469 Logger::error(item.source) << strerror(errno) << std::endl;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700470 return false;
471 }
472
Adam Lesinski769de982015-04-10 19:43:55 -0700473 BigBuffer outBuffer(4096);
474 std::string err;
475 Png png;
476 if (!png.process(item.source, in, &outBuffer, {}, &err)) {
477 Logger::error(item.source) << err << std::endl;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700478 return false;
479 }
480
Adam Lesinski769de982015-04-10 19:43:55 -0700481 if (outApk->add(outBuffer, buildFileReference(item).data(), ZipEntry::kCompressStored,
482 nullptr) != android::NO_ERROR) {
483 Logger::error(options.output) << "failed to write compiled '" << item.source
484 << "' to apk." << std::endl;
485 return false;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700486 }
Adam Lesinski769de982015-04-10 19:43:55 -0700487 return true;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700488}
489
Adam Lesinski769de982015-04-10 19:43:55 -0700490bool copyFile(const AaptOptions& options, const CompileItem& item, ZipFile* outApk) {
491 if (outApk->add(item.source.path.data(), buildFileReference(item).data(),
492 ZipEntry::kCompressStored, nullptr) != android::NO_ERROR) {
493 Logger::error(options.output) << "failed to copy file '" << item.source << "' to apk."
494 << std::endl;
495 return false;
496 }
497 return true;
498}
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800499
Adam Lesinski24aad162015-04-24 19:19:30 -0700500bool compileManifest(const AaptOptions& options,
501 const std::shared_ptr<ResourceTableResolver>& resolver, ZipFile* outApk) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800502 if (options.verbose) {
Adam Lesinski769de982015-04-10 19:43:55 -0700503 Logger::note(options.manifest) << "compiling AndroidManifest.xml." << std::endl;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800504 }
505
506 std::ifstream in(options.manifest.path, std::ifstream::binary);
507 if (!in) {
508 Logger::error(options.manifest) << strerror(errno) << std::endl;
509 return false;
510 }
511
512 BigBuffer outBuffer(1024);
513 std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<SourceXmlPullParser>(in);
Adam Lesinski769de982015-04-10 19:43:55 -0700514 XmlFlattener flattener({}, resolver);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800515
Adam Lesinski24aad162015-04-24 19:19:30 -0700516 XmlFlattener::Options xmlOptions;
517 xmlOptions.defaultPackage = options.appInfo.package;
518 if (!flattener.flatten(options.manifest, xmlParser, &outBuffer, xmlOptions)) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800519 return false;
520 }
521
Adam Lesinski769de982015-04-10 19:43:55 -0700522 std::unique_ptr<uint8_t[]> data = util::copy(outBuffer);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800523
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700524 android::ResXMLTree tree;
Adam Lesinski769de982015-04-10 19:43:55 -0700525 if (tree.setTo(data.get(), outBuffer.size(), false) != android::NO_ERROR) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800526 return false;
527 }
528
529 ManifestValidator validator(resolver->getResTable());
530 if (!validator.validate(options.manifest, &tree)) {
531 return false;
532 }
533
Adam Lesinski769de982015-04-10 19:43:55 -0700534 if (outApk->add(data.get(), outBuffer.size(), "AndroidManifest.xml",
535 ZipEntry::kCompressStored, nullptr) != android::NO_ERROR) {
536 Logger::error(options.output) << "failed to write 'AndroidManifest.xml' to apk."
537 << std::endl;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800538 return false;
539 }
540 return true;
541}
542
Adam Lesinski769de982015-04-10 19:43:55 -0700543static bool compileValues(const std::shared_ptr<ResourceTable>& table, const Source& source,
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700544 const ConfigDescription& config) {
545 std::ifstream in(source.path, std::ifstream::binary);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800546 if (!in) {
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700547 Logger::error(source) << strerror(errno) << std::endl;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800548 return false;
549 }
550
551 std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<SourceXmlPullParser>(in);
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700552 ResourceParser parser(table, source, config, xmlParser);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800553 return parser.parse();
554}
555
556struct ResourcePathData {
557 std::u16string resourceDir;
558 std::u16string name;
559 std::string extension;
560 ConfigDescription config;
561};
562
563/**
564 * Resource file paths are expected to look like:
565 * [--/res/]type[-config]/name
566 */
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700567static Maybe<ResourcePathData> extractResourcePathData(const Source& source) {
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700568 // TODO(adamlesinski): Use Windows path separator on windows.
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800569 std::vector<std::string> parts = util::splitAndLowercase(source.path, '/');
570 if (parts.size() < 2) {
571 Logger::error(source) << "bad resource path." << std::endl;
572 return {};
573 }
574
575 std::string& dir = parts[parts.size() - 2];
576 StringPiece dirStr = dir;
577
578 ConfigDescription config;
579 size_t dashPos = dir.find('-');
580 if (dashPos != std::string::npos) {
581 StringPiece configStr = dirStr.substr(dashPos + 1, dir.size() - (dashPos + 1));
582 if (!ConfigDescription::parse(configStr, &config)) {
583 Logger::error(source)
584 << "invalid configuration '"
585 << configStr
586 << "'."
587 << std::endl;
588 return {};
589 }
590 dirStr = dirStr.substr(0, dashPos);
591 }
592
593 std::string& filename = parts[parts.size() - 1];
594 StringPiece name = filename;
595 StringPiece extension;
596 size_t dotPos = filename.find('.');
597 if (dotPos != std::string::npos) {
598 extension = name.substr(dotPos + 1, filename.size() - (dotPos + 1));
599 name = name.substr(0, dotPos);
600 }
601
602 return ResourcePathData{
603 util::utf8ToUtf16(dirStr),
604 util::utf8ToUtf16(name),
605 extension.toString(),
606 config
607 };
608}
609
Adam Lesinski769de982015-04-10 19:43:55 -0700610bool writeResourceTable(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table,
611 const TableFlattener::Options& flattenerOptions, ZipFile* outApk) {
612 if (table->begin() != table->end()) {
613 BigBuffer buffer(1024);
614 TableFlattener flattener(flattenerOptions);
615 if (!flattener.flatten(&buffer, *table)) {
616 Logger::error() << "failed to flatten resource table." << std::endl;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700617 return false;
618 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800619
Adam Lesinski769de982015-04-10 19:43:55 -0700620 if (options.verbose) {
621 Logger::note() << "Final resource table size=" << util::formatSize(buffer.size())
622 << std::endl;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700623 }
624
Adam Lesinski769de982015-04-10 19:43:55 -0700625 if (outApk->add(buffer, "resources.arsc", ZipEntry::kCompressStored, nullptr) !=
626 android::NO_ERROR) {
627 Logger::note(options.output) << "failed to store resource table." << std::endl;
628 return false;
629 }
630 }
631 return true;
632}
633
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700634/**
635 * For each FileReference in the table, adds a LinkItem to the link queue for processing.
636 */
637static void addApkFilesToLinkQueue(const std::u16string& package, const Source& source,
638 const std::shared_ptr<ResourceTable>& table,
639 const std::unique_ptr<ZipFile>& apk,
640 std::queue<LinkItem>* outLinkQueue) {
641 bool mangle = package != table->getPackage();
642 for (auto& type : *table) {
643 for (auto& entry : type->entries) {
644 ResourceName name = { package, type->type, entry->name };
645 if (mangle) {
646 NameMangler::mangle(table->getPackage(), &name.entry);
647 }
648
649 for (auto& value : entry->values) {
650 visitFunc<FileReference>(*value.value, [&](FileReference& ref) {
651 std::string pathUtf8 = util::utf16ToUtf8(*ref.path);
Adam Lesinski24aad162015-04-24 19:19:30 -0700652 Source newSource = source;
653 newSource.path += "/";
654 newSource.path += pathUtf8;
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700655 outLinkQueue->push(LinkItem{
Adam Lesinski24aad162015-04-24 19:19:30 -0700656 newSource, name, value.config, pathUtf8, apk.get(),
657 table->getPackage() });
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700658 // Now rewrite the file path.
659 if (mangle) {
660 ref.path = table->getValueStringPool().makeRef(util::utf8ToUtf16(
661 buildFileReference(name, value.config,
662 getExtension<char>(pathUtf8))));
663 }
664 });
665 }
666 }
667 }
668}
669
Adam Lesinski769de982015-04-10 19:43:55 -0700670static constexpr int kOpenFlags = ZipFile::kOpenCreate | ZipFile::kOpenTruncate |
671 ZipFile::kOpenReadWrite;
672
Adam Lesinski24aad162015-04-24 19:19:30 -0700673struct DeleteMalloc {
674 void operator()(void* ptr) {
675 free(ptr);
676 }
677};
678
679struct StaticLibraryData {
680 Source source;
681 std::unique_ptr<ZipFile> apk;
682};
683
Adam Lesinski769de982015-04-10 19:43:55 -0700684bool link(const AaptOptions& options, const std::shared_ptr<ResourceTable>& outTable,
Adam Lesinski24aad162015-04-24 19:19:30 -0700685 const std::shared_ptr<ResourceTableResolver>& resolver) {
686 std::map<std::shared_ptr<ResourceTable>, StaticLibraryData> apkFiles;
Adam Lesinski769de982015-04-10 19:43:55 -0700687 std::unordered_set<std::u16string> linkedPackages;
688
689 // Populate the linkedPackages with our own.
690 linkedPackages.insert(options.appInfo.package);
691
692 // Load all APK files.
693 for (const Source& source : options.input) {
694 std::unique_ptr<ZipFile> zipFile = util::make_unique<ZipFile>();
695 if (zipFile->open(source.path.data(), ZipFile::kOpenReadOnly) != android::NO_ERROR) {
696 Logger::error(source) << "failed to open: " << strerror(errno) << std::endl;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700697 return false;
698 }
699
Adam Lesinski769de982015-04-10 19:43:55 -0700700 std::shared_ptr<ResourceTable> table = std::make_shared<ResourceTable>();
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700701
Adam Lesinski769de982015-04-10 19:43:55 -0700702 ZipEntry* entry = zipFile->getEntryByName("resources.arsc");
703 if (!entry) {
704 Logger::error(source) << "missing 'resources.arsc'." << std::endl;
705 return false;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700706 }
707
Adam Lesinski24aad162015-04-24 19:19:30 -0700708 std::unique_ptr<void, DeleteMalloc> uncompressedData = std::unique_ptr<void, DeleteMalloc>(
709 zipFile->uncompress(entry));
Adam Lesinski769de982015-04-10 19:43:55 -0700710 assert(uncompressedData);
711
Adam Lesinski24aad162015-04-24 19:19:30 -0700712 BinaryResourceParser parser(table, resolver, source, uncompressedData.get(),
Adam Lesinski769de982015-04-10 19:43:55 -0700713 entry->getUncompressedLen());
714 if (!parser.parse()) {
Adam Lesinski769de982015-04-10 19:43:55 -0700715 return false;
716 }
Adam Lesinski769de982015-04-10 19:43:55 -0700717
718 // Keep track of where this table came from.
Adam Lesinski24aad162015-04-24 19:19:30 -0700719 apkFiles[table] = StaticLibraryData{ source, std::move(zipFile) };
Adam Lesinski769de982015-04-10 19:43:55 -0700720
721 // Add the package to the set of linked packages.
722 linkedPackages.insert(table->getPackage());
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700723 }
724
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700725 std::queue<LinkItem> linkQueue;
Adam Lesinski769de982015-04-10 19:43:55 -0700726 for (auto& p : apkFiles) {
727 const std::shared_ptr<ResourceTable>& inTable = p.first;
728
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700729 // Collect all FileReferences and add them to the queue for processing.
Adam Lesinski24aad162015-04-24 19:19:30 -0700730 addApkFilesToLinkQueue(options.appInfo.package, p.second.source, inTable, p.second.apk,
731 &linkQueue);
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700732
733 // Merge the tables.
Adam Lesinski769de982015-04-10 19:43:55 -0700734 if (!outTable->merge(std::move(*inTable))) {
735 return false;
736 }
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700737 }
738
Adam Lesinski769de982015-04-10 19:43:55 -0700739 {
740 // Now that everything is merged, let's link it.
741 Linker linker(outTable, resolver);
742 if (!linker.linkAndValidate()) {
743 return false;
744 }
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700745
Adam Lesinski769de982015-04-10 19:43:55 -0700746 // Verify that all symbols exist.
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700747 const auto& unresolvedRefs = linker.getUnresolvedReferences();
748 if (!unresolvedRefs.empty()) {
749 for (const auto& entry : unresolvedRefs) {
750 for (const auto& source : entry.second) {
751 Logger::error(source) << "unresolved symbol '" << entry.first << "'."
752 << std::endl;
753 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800754 }
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700755 return false;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800756 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800757 }
758
Adam Lesinski769de982015-04-10 19:43:55 -0700759 // Open the output APK file for writing.
760 ZipFile outApk;
761 if (outApk.open(options.output.path.data(), kOpenFlags) != android::NO_ERROR) {
762 Logger::error(options.output) << "failed to open: " << strerror(errno) << std::endl;
763 return false;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700764 }
765
Adam Lesinski769de982015-04-10 19:43:55 -0700766 if (!compileManifest(options, resolver, &outApk)) {
767 return false;
768 }
769
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700770 for (; !linkQueue.empty(); linkQueue.pop()) {
771 const LinkItem& item = linkQueue.front();
Adam Lesinski769de982015-04-10 19:43:55 -0700772
Adam Lesinski24aad162015-04-24 19:19:30 -0700773 assert(!item.originalPackage.empty());
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700774 ZipEntry* entry = item.apk->getEntryByName(item.originalPath.data());
775 if (!entry) {
776 Logger::error(item.source) << "failed to find '" << item.originalPath << "'."
777 << std::endl;
778 return false;
779 }
Adam Lesinski769de982015-04-10 19:43:55 -0700780
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700781 if (util::stringEndsWith<char>(item.originalPath, ".xml")) {
782 void* uncompressedData = item.apk->uncompress(entry);
783 assert(uncompressedData);
Adam Lesinski769de982015-04-10 19:43:55 -0700784
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700785 if (!linkXml(options, resolver, item, uncompressedData, entry->getUncompressedLen(),
786 &outApk)) {
787 Logger::error(options.output) << "failed to link '" << item.originalPath << "'."
788 << std::endl;
789 return false;
Adam Lesinski769de982015-04-10 19:43:55 -0700790 }
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700791 } else {
792 if (outApk.add(item.apk, entry, buildFileReference(item).data(), 0, nullptr) !=
793 android::NO_ERROR) {
794 Logger::error(options.output) << "failed to copy '" << item.originalPath << "'."
795 << std::endl;
796 return false;
Adam Lesinski769de982015-04-10 19:43:55 -0700797 }
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700798 }
799 }
800
801 // Generate the Java class file.
Adam Lesinski769de982015-04-10 19:43:55 -0700802 if (options.generateJavaClass) {
803 JavaClassGenerator generator(outTable, {});
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700804
Adam Lesinski769de982015-04-10 19:43:55 -0700805 for (const std::u16string& package : linkedPackages) {
806 Source outPath = options.generateJavaClass.value();
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800807
Adam Lesinski769de982015-04-10 19:43:55 -0700808 // Build the output directory from the package name.
809 // Eg. com.android.app -> com/android/app
810 const std::string packageUtf8 = util::utf16ToUtf8(package);
811 for (StringPiece part : util::tokenize<char>(packageUtf8, '.')) {
812 appendPath(&outPath.path, part);
813 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800814
Adam Lesinski769de982015-04-10 19:43:55 -0700815 if (!mkdirs(outPath.path)) {
816 Logger::error(outPath) << strerror(errno) << std::endl;
817 return false;
818 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800819
Adam Lesinski769de982015-04-10 19:43:55 -0700820 appendPath(&outPath.path, "R.java");
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800821
Adam Lesinski769de982015-04-10 19:43:55 -0700822 if (options.verbose) {
823 Logger::note(outPath) << "writing Java symbols." << std::endl;
824 }
825
826 std::ofstream fout(outPath.path);
827 if (!fout) {
828 Logger::error(outPath) << strerror(errno) << std::endl;
829 return false;
830 }
831
832 if (!generator.generate(package, fout)) {
833 Logger::error(outPath) << generator.getError() << "." << std::endl;
834 return false;
835 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800836 }
837 }
838
Adam Lesinski24aad162015-04-24 19:19:30 -0700839 outTable->getValueStringPool().prune();
840 outTable->getValueStringPool().sort(
841 [](const StringPool::Entry& a, const StringPool::Entry& b) -> bool {
842 if (a.context.priority < b.context.priority) {
843 return true;
844 }
845
846 if (a.context.priority > b.context.priority) {
847 return false;
848 }
849 return a.value < b.value;
850 });
851
852
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700853 // Flatten the resource table.
Adam Lesinski769de982015-04-10 19:43:55 -0700854 TableFlattener::Options flattenerOptions;
855 flattenerOptions.useExtendedChunks = false;
856 if (!writeResourceTable(options, outTable, flattenerOptions, &outApk)) {
857 return false;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800858 }
Adam Lesinski769de982015-04-10 19:43:55 -0700859
860 outApk.flush();
861 return true;
862}
863
864bool compile(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table,
Adam Lesinski24aad162015-04-24 19:19:30 -0700865 const std::shared_ptr<IResolver>& resolver) {
Adam Lesinski769de982015-04-10 19:43:55 -0700866 std::queue<CompileItem> compileQueue;
867 bool error = false;
868
869 // Compile all the resource files passed in on the command line.
870 for (const Source& source : options.input) {
871 // Need to parse the resource type/config/filename.
872 Maybe<ResourcePathData> maybePathData = extractResourcePathData(source);
873 if (!maybePathData) {
874 return false;
875 }
876
877 const ResourcePathData& pathData = maybePathData.value();
878 if (pathData.resourceDir == u"values") {
879 // The file is in the values directory, which means its contents will
880 // go into the resource table.
881 if (options.verbose) {
882 Logger::note(source) << "compiling values." << std::endl;
883 }
884
885 error |= !compileValues(table, source, pathData.config);
886 } else {
887 // The file is in a directory like 'layout' or 'drawable'. Find out
888 // the type.
889 const ResourceType* type = parseResourceType(pathData.resourceDir);
890 if (!type) {
891 Logger::error(source) << "invalid resource type '" << pathData.resourceDir << "'."
892 << std::endl;
893 return false;
894 }
895
896 compileQueue.push(CompileItem{
897 source,
898 ResourceName{ table->getPackage(), *type, pathData.name },
899 pathData.config,
900 pathData.extension
901 });
902 }
903 }
904
905 if (error) {
906 return false;
907 }
908
909 // Version all styles referencing attributes outside of their specified SDK version.
Adam Lesinski5886a922015-04-15 20:29:22 -0700910 if (options.versionStylesAndLayouts) {
911 versionStylesForCompat(table);
912 }
Adam Lesinski769de982015-04-10 19:43:55 -0700913
914 // Open the output APK file for writing.
915 ZipFile outApk;
916 if (outApk.open(options.output.path.data(), kOpenFlags) != android::NO_ERROR) {
917 Logger::error(options.output) << "failed to open: " << strerror(errno) << std::endl;
918 return false;
919 }
920
921 // Compile each file.
922 for (; !compileQueue.empty(); compileQueue.pop()) {
923 const CompileItem& item = compileQueue.front();
924
925 // Add the file name to the resource table.
926 error |= !addFileReference(table, item);
927
928 if (item.extension == "xml") {
929 error |= !compileXml(options, table, item, &compileQueue, &outApk);
930 } else if (item.extension == "png" || item.extension == "9.png") {
931 error |= !compilePng(options, item, &outApk);
932 } else {
933 error |= !copyFile(options, item, &outApk);
934 }
935 }
936
937 if (error) {
938 return false;
939 }
940
941 // Link and assign resource IDs.
942 Linker linker(table, resolver);
943 if (!linker.linkAndValidate()) {
944 return false;
945 }
946
947 // Flatten the resource table.
948 if (!writeResourceTable(options, table, {}, &outApk)) {
949 return false;
950 }
951
952 outApk.flush();
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800953 return true;
954}
955
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700956bool loadAppInfo(const Source& source, AppInfo* outInfo) {
957 std::ifstream ifs(source.path, std::ifstream::in | std::ifstream::binary);
958 if (!ifs) {
959 Logger::error(source) << strerror(errno) << std::endl;
960 return false;
961 }
962
963 ManifestParser parser;
964 std::shared_ptr<XmlPullParser> pullParser = std::make_shared<SourceXmlPullParser>(ifs);
965 return parser.parse(source, pullParser, outInfo);
966}
967
968static void printCommandsAndDie() {
969 std::cerr << "The following commands are supported:" << std::endl << std::endl;
970 std::cerr << "compile compiles a subset of resources" << std::endl;
971 std::cerr << "link links together compiled resources and libraries" << std::endl;
972 std::cerr << std::endl;
973 std::cerr << "run aapt2 with one of the commands and the -h flag for extra details."
974 << std::endl;
975 exit(1);
976}
977
978static AaptOptions prepareArgs(int argc, char** argv) {
979 if (argc < 2) {
980 std::cerr << "no command specified." << std::endl << std::endl;
981 printCommandsAndDie();
982 }
983
984 const StringPiece command(argv[1]);
985 argc -= 2;
986 argv += 2;
987
988 AaptOptions options;
989
990 if (command == "--version" || command == "version") {
991 std::cout << kAaptVersionStr << std::endl;
992 exit(0);
993 } else if (command == "link") {
994 options.phase = AaptOptions::Phase::Link;
995 } else if (command == "compile") {
996 options.phase = AaptOptions::Phase::Compile;
997 } else {
998 std::cerr << "invalid command '" << command << "'." << std::endl << std::endl;
999 printCommandsAndDie();
1000 }
1001
1002 if (options.phase == AaptOptions::Phase::Compile) {
1003 flag::requiredFlag("--package", "Android package name",
1004 [&options](const StringPiece& arg) {
1005 options.appInfo.package = util::utf8ToUtf16(arg);
1006 });
1007 flag::optionalFlag("--binding", "Output directory for binding XML files",
1008 [&options](const StringPiece& arg) {
1009 options.bindingOutput = Source{ arg.toString() };
1010 });
1011 flag::optionalSwitch("--no-version", "Disables automatic style and layout versioning",
1012 false, &options.versionStylesAndLayouts);
1013
1014 } else if (options.phase == AaptOptions::Phase::Link) {
1015 flag::requiredFlag("--manifest", "AndroidManifest.xml of your app",
1016 [&options](const StringPiece& arg) {
1017 options.manifest = Source{ arg.toString() };
1018 });
1019
1020 flag::optionalFlag("-I", "add an Android APK to link against",
1021 [&options](const StringPiece& arg) {
1022 options.libraries.push_back(Source{ arg.toString() });
1023 });
1024
1025 flag::optionalFlag("--java", "directory in which to generate R.java",
1026 [&options](const StringPiece& arg) {
1027 options.generateJavaClass = Source{ arg.toString() };
1028 });
1029 }
1030
1031 // Common flags for all steps.
1032 flag::requiredFlag("-o", "Output path", [&options](const StringPiece& arg) {
1033 options.output = Source{ arg.toString() };
1034 });
1035
1036 bool help = false;
1037 flag::optionalSwitch("-v", "enables verbose logging", true, &options.verbose);
1038 flag::optionalSwitch("-h", "displays this help menu", true, &help);
1039
1040 // Build the command string for output (eg. "aapt2 compile").
1041 std::string fullCommand = "aapt2";
1042 fullCommand += " ";
1043 fullCommand += command.toString();
1044
1045 // Actually read the command line flags.
1046 flag::parse(argc, argv, fullCommand);
1047
1048 if (help) {
1049 flag::usageAndDie(fullCommand);
1050 }
1051
1052 // Copy all the remaining arguments.
1053 for (const std::string& arg : flag::getArgs()) {
1054 options.input.push_back(Source{ arg });
1055 }
1056 return options;
1057}
1058
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001059int main(int argc, char** argv) {
1060 Logger::setLog(std::make_shared<Log>(std::cerr, std::cerr));
Adam Lesinski98aa3ad2015-04-06 11:46:52 -07001061 AaptOptions options = prepareArgs(argc, argv);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001062
Adam Lesinski98aa3ad2015-04-06 11:46:52 -07001063 // If we specified a manifest, go ahead and load the package name from the manifest.
1064 if (!options.manifest.path.empty()) {
1065 if (!loadAppInfo(options.manifest, &options.appInfo)) {
1066 return false;
1067 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001068 }
1069
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001070 // Verify we have some common options set.
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001071 if (options.appInfo.package.empty()) {
1072 Logger::error() << "no package name specified." << std::endl;
1073 return false;
1074 }
1075
Adam Lesinski98aa3ad2015-04-06 11:46:52 -07001076 // Every phase needs a resource table.
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001077 std::shared_ptr<ResourceTable> table = std::make_shared<ResourceTable>();
1078 table->setPackage(options.appInfo.package);
1079 if (options.appInfo.package == u"android") {
1080 table->setPackageId(0x01);
1081 } else {
1082 table->setPackageId(0x7f);
1083 }
1084
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001085 // Load the included libraries.
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001086 std::shared_ptr<android::AssetManager> libraries = std::make_shared<android::AssetManager>();
1087 for (const Source& source : options.libraries) {
Adam Lesinski4d3a9872015-04-09 19:53:22 -07001088 if (util::stringEndsWith<char>(source.path, ".arsc")) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001089 // We'll process these last so as to avoid a cookie issue.
1090 continue;
1091 }
1092
1093 int32_t cookie;
1094 if (!libraries->addAssetPath(android::String8(source.path.data()), &cookie)) {
1095 Logger::error(source) << "failed to load library." << std::endl;
1096 return false;
1097 }
1098 }
1099
1100 for (const Source& source : options.libraries) {
Adam Lesinski4d3a9872015-04-09 19:53:22 -07001101 if (!util::stringEndsWith<char>(source.path, ".arsc")) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001102 // We've already processed this.
1103 continue;
1104 }
1105
1106 // Dirty hack but there is no other way to get a
1107 // writeable ResTable.
1108 if (!loadResTable(const_cast<android::ResTable*>(&libraries->getResources(false)),
1109 source)) {
1110 return false;
1111 }
1112 }
1113
1114 // Make the resolver that will cache IDs for us.
Adam Lesinski24aad162015-04-24 19:19:30 -07001115 std::shared_ptr<ResourceTableResolver> resolver = std::make_shared<ResourceTableResolver>(
1116 table, libraries);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001117
Adam Lesinski769de982015-04-10 19:43:55 -07001118 if (options.phase == AaptOptions::Phase::Compile) {
1119 if (!compile(options, table, resolver)) {
1120 Logger::error() << "aapt exiting with failures." << std::endl;
1121 return 1;
1122 }
1123 } else if (options.phase == AaptOptions::Phase::Link) {
1124 if (!link(options, table, resolver)) {
1125 Logger::error() << "aapt exiting with failures." << std::endl;
1126 return 1;
1127 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001128 }
1129 return 0;
1130}