blob: 3377f07efb8708abd8ed5d88cd09f36c6f98d33d [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
Adam Lesinski6d8e4c42015-05-01 14:47:28 -0700326 enum class PackageType {
327 StandardApp,
328 StaticLibrary,
329 };
330
Adam Lesinski769de982015-04-10 19:43:55 -0700331 // The phase to process.
332 Phase phase;
333
Adam Lesinski6d8e4c42015-05-01 14:47:28 -0700334 // The type of package to produce.
335 PackageType packageType = PackageType::StandardApp;
336
Adam Lesinski769de982015-04-10 19:43:55 -0700337 // Details about the app.
338 AppInfo appInfo;
339
340 // The location of the manifest file.
341 Source manifest;
342
343 // The APK files to link.
344 std::vector<Source> input;
345
346 // The libraries these files may reference.
347 std::vector<Source> libraries;
348
349 // Output path. This can be a directory or file
350 // depending on the phase.
351 Source output;
352
353 // Directory in which to write binding xml files.
354 Source bindingOutput;
355
356 // Directory to in which to generate R.java.
357 Maybe<Source> generateJavaClass;
358
359 // Whether to output verbose details about
360 // compilation.
361 bool verbose = false;
Adam Lesinski5886a922015-04-15 20:29:22 -0700362
363 // Whether or not to auto-version styles or layouts
364 // referencing attributes defined in a newer SDK
365 // level than the style or layout is defined for.
366 bool versionStylesAndLayouts = true;
Adam Lesinski769de982015-04-10 19:43:55 -0700367};
368
Adam Lesinski5886a922015-04-15 20:29:22 -0700369
Adam Lesinski769de982015-04-10 19:43:55 -0700370bool compileXml(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table,
371 const CompileItem& item, std::queue<CompileItem>* outQueue, ZipFile* outApk) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800372 std::ifstream in(item.source.path, std::ifstream::binary);
373 if (!in) {
374 Logger::error(item.source) << strerror(errno) << std::endl;
375 return false;
376 }
377
Adam Lesinski4d3a9872015-04-09 19:53:22 -0700378 BigBuffer outBuffer(1024);
Adam Lesinski769de982015-04-10 19:43:55 -0700379
380 // No resolver, since we are not compiling attributes here.
381 XmlFlattener flattener(table, {});
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800382
Adam Lesinski769de982015-04-10 19:43:55 -0700383 XmlFlattener::Options xmlOptions;
Adam Lesinski24aad162015-04-24 19:19:30 -0700384 xmlOptions.defaultPackage = table->getPackage();
385
Adam Lesinski5886a922015-04-15 20:29:22 -0700386 if (options.versionStylesAndLayouts) {
387 // We strip attributes that do not belong in this version of the resource.
388 // Non-version qualified resources have an implicit version 1 requirement.
389 xmlOptions.maxSdkAttribute = item.config.sdkVersion ? item.config.sdkVersion : 1;
390 }
Adam Lesinski769de982015-04-10 19:43:55 -0700391
392 std::shared_ptr<BindingXmlPullParser> binding;
393 std::shared_ptr<XmlPullParser> parser = std::make_shared<SourceXmlPullParser>(in);
394 if (item.name.type == ResourceType::kLayout) {
395 // Layouts may have defined bindings, so we need to make sure they get processed.
396 binding = std::make_shared<BindingXmlPullParser>(parser);
397 parser = binding;
398 }
399
400 Maybe<size_t> minStrippedSdk = flattener.flatten(item.source, parser, &outBuffer, xmlOptions);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800401 if (!minStrippedSdk) {
402 return false;
403 }
404
405 if (minStrippedSdk.value() > 0) {
406 // Something was stripped, so let's generate a new file
407 // with the version of the smallest SDK version stripped.
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700408 CompileItem newWork = item;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800409 newWork.config.sdkVersion = minStrippedSdk.value();
Adam Lesinski769de982015-04-10 19:43:55 -0700410 outQueue->push(newWork);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800411 }
412
Adam Lesinski769de982015-04-10 19:43:55 -0700413 // Write the resulting compiled XML file to the output APK.
414 if (outApk->add(outBuffer, buildFileReference(item).data(), ZipEntry::kCompressStored,
415 nullptr) != android::NO_ERROR) {
416 Logger::error(options.output) << "failed to write compiled '" << item.source << "' to apk."
417 << std::endl;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800418 return false;
419 }
420
Adam Lesinski769de982015-04-10 19:43:55 -0700421 if (binding && !options.bindingOutput.path.empty()) {
422 // We generated a binding xml file, write it out.
423 Source bindingOutput = options.bindingOutput;
424 appendPath(&bindingOutput.path, buildFileReference(item));
Adam Lesinski4d3a9872015-04-09 19:53:22 -0700425
Adam Lesinski769de982015-04-10 19:43:55 -0700426 if (!mkdirs(bindingOutput.path)) {
427 Logger::error(bindingOutput) << strerror(errno) << std::endl;
428 return false;
429 }
430
431 appendPath(&bindingOutput.path, "bind.xml");
432
Adam Lesinski4d3a9872015-04-09 19:53:22 -0700433 std::ofstream bout(bindingOutput.path);
434 if (!bout) {
435 Logger::error(bindingOutput) << strerror(errno) << std::endl;
436 return false;
437 }
438
439 if (!binding->writeToFile(bout)) {
440 Logger::error(bindingOutput) << strerror(errno) << std::endl;
441 return false;
442 }
443 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800444 return true;
445}
446
Adam Lesinski24aad162015-04-24 19:19:30 -0700447bool linkXml(const AaptOptions& options, const std::shared_ptr<IResolver>& resolver,
Adam Lesinski769de982015-04-10 19:43:55 -0700448 const LinkItem& item, const void* data, size_t dataLen, ZipFile* outApk) {
449 std::shared_ptr<android::ResXMLTree> tree = std::make_shared<android::ResXMLTree>();
450 if (tree->setTo(data, dataLen, false) != android::NO_ERROR) {
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700451 return false;
452 }
453
Adam Lesinski769de982015-04-10 19:43:55 -0700454 std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<BinaryXmlPullParser>(tree);
455
456 BigBuffer outBuffer(1024);
457 XmlFlattener flattener({}, resolver);
Adam Lesinski24aad162015-04-24 19:19:30 -0700458
459 XmlFlattener::Options xmlOptions;
460 xmlOptions.defaultPackage = item.originalPackage;
461 if (!flattener.flatten(item.source, xmlParser, &outBuffer, xmlOptions)) {
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700462 return false;
463 }
464
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700465 if (outApk->add(outBuffer, buildFileReference(item).data(), ZipEntry::kCompressDeflated,
466 nullptr) != android::NO_ERROR) {
Adam Lesinski769de982015-04-10 19:43:55 -0700467 Logger::error(options.output) << "failed to write linked file '" << item.source
468 << "' to apk." << std::endl;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700469 return false;
470 }
471 return true;
472}
473
Adam Lesinski769de982015-04-10 19:43:55 -0700474bool compilePng(const AaptOptions& options, const CompileItem& item, ZipFile* outApk) {
475 std::ifstream in(item.source.path, std::ifstream::binary);
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700476 if (!in) {
Adam Lesinski769de982015-04-10 19:43:55 -0700477 Logger::error(item.source) << strerror(errno) << std::endl;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700478 return false;
479 }
480
Adam Lesinski769de982015-04-10 19:43:55 -0700481 BigBuffer outBuffer(4096);
482 std::string err;
483 Png png;
484 if (!png.process(item.source, in, &outBuffer, {}, &err)) {
485 Logger::error(item.source) << err << std::endl;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700486 return false;
487 }
488
Adam Lesinski769de982015-04-10 19:43:55 -0700489 if (outApk->add(outBuffer, buildFileReference(item).data(), ZipEntry::kCompressStored,
490 nullptr) != android::NO_ERROR) {
491 Logger::error(options.output) << "failed to write compiled '" << item.source
492 << "' to apk." << std::endl;
493 return false;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700494 }
Adam Lesinski769de982015-04-10 19:43:55 -0700495 return true;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700496}
497
Adam Lesinski769de982015-04-10 19:43:55 -0700498bool copyFile(const AaptOptions& options, const CompileItem& item, ZipFile* outApk) {
499 if (outApk->add(item.source.path.data(), buildFileReference(item).data(),
500 ZipEntry::kCompressStored, nullptr) != android::NO_ERROR) {
501 Logger::error(options.output) << "failed to copy file '" << item.source << "' to apk."
502 << std::endl;
503 return false;
504 }
505 return true;
506}
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800507
Adam Lesinski24aad162015-04-24 19:19:30 -0700508bool compileManifest(const AaptOptions& options,
509 const std::shared_ptr<ResourceTableResolver>& resolver, ZipFile* outApk) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800510 if (options.verbose) {
Adam Lesinski769de982015-04-10 19:43:55 -0700511 Logger::note(options.manifest) << "compiling AndroidManifest.xml." << std::endl;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800512 }
513
514 std::ifstream in(options.manifest.path, std::ifstream::binary);
515 if (!in) {
516 Logger::error(options.manifest) << strerror(errno) << std::endl;
517 return false;
518 }
519
520 BigBuffer outBuffer(1024);
521 std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<SourceXmlPullParser>(in);
Adam Lesinski769de982015-04-10 19:43:55 -0700522 XmlFlattener flattener({}, resolver);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800523
Adam Lesinski24aad162015-04-24 19:19:30 -0700524 XmlFlattener::Options xmlOptions;
525 xmlOptions.defaultPackage = options.appInfo.package;
526 if (!flattener.flatten(options.manifest, xmlParser, &outBuffer, xmlOptions)) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800527 return false;
528 }
529
Adam Lesinski769de982015-04-10 19:43:55 -0700530 std::unique_ptr<uint8_t[]> data = util::copy(outBuffer);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800531
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700532 android::ResXMLTree tree;
Adam Lesinski769de982015-04-10 19:43:55 -0700533 if (tree.setTo(data.get(), outBuffer.size(), false) != android::NO_ERROR) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800534 return false;
535 }
536
537 ManifestValidator validator(resolver->getResTable());
538 if (!validator.validate(options.manifest, &tree)) {
539 return false;
540 }
541
Adam Lesinski769de982015-04-10 19:43:55 -0700542 if (outApk->add(data.get(), outBuffer.size(), "AndroidManifest.xml",
543 ZipEntry::kCompressStored, nullptr) != android::NO_ERROR) {
544 Logger::error(options.output) << "failed to write 'AndroidManifest.xml' to apk."
545 << std::endl;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800546 return false;
547 }
548 return true;
549}
550
Adam Lesinski769de982015-04-10 19:43:55 -0700551static bool compileValues(const std::shared_ptr<ResourceTable>& table, const Source& source,
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700552 const ConfigDescription& config) {
553 std::ifstream in(source.path, std::ifstream::binary);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800554 if (!in) {
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700555 Logger::error(source) << strerror(errno) << std::endl;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800556 return false;
557 }
558
559 std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<SourceXmlPullParser>(in);
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700560 ResourceParser parser(table, source, config, xmlParser);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800561 return parser.parse();
562}
563
564struct ResourcePathData {
565 std::u16string resourceDir;
566 std::u16string name;
567 std::string extension;
568 ConfigDescription config;
569};
570
571/**
572 * Resource file paths are expected to look like:
573 * [--/res/]type[-config]/name
574 */
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700575static Maybe<ResourcePathData> extractResourcePathData(const Source& source) {
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700576 // TODO(adamlesinski): Use Windows path separator on windows.
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800577 std::vector<std::string> parts = util::splitAndLowercase(source.path, '/');
578 if (parts.size() < 2) {
579 Logger::error(source) << "bad resource path." << std::endl;
580 return {};
581 }
582
583 std::string& dir = parts[parts.size() - 2];
584 StringPiece dirStr = dir;
585
586 ConfigDescription config;
587 size_t dashPos = dir.find('-');
588 if (dashPos != std::string::npos) {
589 StringPiece configStr = dirStr.substr(dashPos + 1, dir.size() - (dashPos + 1));
590 if (!ConfigDescription::parse(configStr, &config)) {
591 Logger::error(source)
592 << "invalid configuration '"
593 << configStr
594 << "'."
595 << std::endl;
596 return {};
597 }
598 dirStr = dirStr.substr(0, dashPos);
599 }
600
601 std::string& filename = parts[parts.size() - 1];
602 StringPiece name = filename;
603 StringPiece extension;
604 size_t dotPos = filename.find('.');
605 if (dotPos != std::string::npos) {
606 extension = name.substr(dotPos + 1, filename.size() - (dotPos + 1));
607 name = name.substr(0, dotPos);
608 }
609
610 return ResourcePathData{
611 util::utf8ToUtf16(dirStr),
612 util::utf8ToUtf16(name),
613 extension.toString(),
614 config
615 };
616}
617
Adam Lesinski769de982015-04-10 19:43:55 -0700618bool writeResourceTable(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table,
619 const TableFlattener::Options& flattenerOptions, ZipFile* outApk) {
620 if (table->begin() != table->end()) {
621 BigBuffer buffer(1024);
622 TableFlattener flattener(flattenerOptions);
623 if (!flattener.flatten(&buffer, *table)) {
624 Logger::error() << "failed to flatten resource table." << std::endl;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700625 return false;
626 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800627
Adam Lesinski769de982015-04-10 19:43:55 -0700628 if (options.verbose) {
629 Logger::note() << "Final resource table size=" << util::formatSize(buffer.size())
630 << std::endl;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700631 }
632
Adam Lesinski769de982015-04-10 19:43:55 -0700633 if (outApk->add(buffer, "resources.arsc", ZipEntry::kCompressStored, nullptr) !=
634 android::NO_ERROR) {
635 Logger::note(options.output) << "failed to store resource table." << std::endl;
636 return false;
637 }
638 }
639 return true;
640}
641
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700642/**
643 * For each FileReference in the table, adds a LinkItem to the link queue for processing.
644 */
645static void addApkFilesToLinkQueue(const std::u16string& package, const Source& source,
646 const std::shared_ptr<ResourceTable>& table,
647 const std::unique_ptr<ZipFile>& apk,
648 std::queue<LinkItem>* outLinkQueue) {
649 bool mangle = package != table->getPackage();
650 for (auto& type : *table) {
651 for (auto& entry : type->entries) {
652 ResourceName name = { package, type->type, entry->name };
653 if (mangle) {
654 NameMangler::mangle(table->getPackage(), &name.entry);
655 }
656
657 for (auto& value : entry->values) {
658 visitFunc<FileReference>(*value.value, [&](FileReference& ref) {
659 std::string pathUtf8 = util::utf16ToUtf8(*ref.path);
Adam Lesinski24aad162015-04-24 19:19:30 -0700660 Source newSource = source;
661 newSource.path += "/";
662 newSource.path += pathUtf8;
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700663 outLinkQueue->push(LinkItem{
Adam Lesinski24aad162015-04-24 19:19:30 -0700664 newSource, name, value.config, pathUtf8, apk.get(),
665 table->getPackage() });
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700666 // Now rewrite the file path.
667 if (mangle) {
668 ref.path = table->getValueStringPool().makeRef(util::utf8ToUtf16(
669 buildFileReference(name, value.config,
670 getExtension<char>(pathUtf8))));
671 }
672 });
673 }
674 }
675 }
676}
677
Adam Lesinski769de982015-04-10 19:43:55 -0700678static constexpr int kOpenFlags = ZipFile::kOpenCreate | ZipFile::kOpenTruncate |
679 ZipFile::kOpenReadWrite;
680
Adam Lesinski24aad162015-04-24 19:19:30 -0700681struct DeleteMalloc {
682 void operator()(void* ptr) {
683 free(ptr);
684 }
685};
686
687struct StaticLibraryData {
688 Source source;
689 std::unique_ptr<ZipFile> apk;
690};
691
Adam Lesinski769de982015-04-10 19:43:55 -0700692bool link(const AaptOptions& options, const std::shared_ptr<ResourceTable>& outTable,
Adam Lesinski24aad162015-04-24 19:19:30 -0700693 const std::shared_ptr<ResourceTableResolver>& resolver) {
694 std::map<std::shared_ptr<ResourceTable>, StaticLibraryData> apkFiles;
Adam Lesinski769de982015-04-10 19:43:55 -0700695 std::unordered_set<std::u16string> linkedPackages;
696
697 // Populate the linkedPackages with our own.
698 linkedPackages.insert(options.appInfo.package);
699
700 // Load all APK files.
701 for (const Source& source : options.input) {
702 std::unique_ptr<ZipFile> zipFile = util::make_unique<ZipFile>();
703 if (zipFile->open(source.path.data(), ZipFile::kOpenReadOnly) != android::NO_ERROR) {
704 Logger::error(source) << "failed to open: " << strerror(errno) << std::endl;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700705 return false;
706 }
707
Adam Lesinski769de982015-04-10 19:43:55 -0700708 std::shared_ptr<ResourceTable> table = std::make_shared<ResourceTable>();
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700709
Adam Lesinski769de982015-04-10 19:43:55 -0700710 ZipEntry* entry = zipFile->getEntryByName("resources.arsc");
711 if (!entry) {
712 Logger::error(source) << "missing 'resources.arsc'." << std::endl;
713 return false;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700714 }
715
Adam Lesinski24aad162015-04-24 19:19:30 -0700716 std::unique_ptr<void, DeleteMalloc> uncompressedData = std::unique_ptr<void, DeleteMalloc>(
717 zipFile->uncompress(entry));
Adam Lesinski769de982015-04-10 19:43:55 -0700718 assert(uncompressedData);
719
Adam Lesinski24aad162015-04-24 19:19:30 -0700720 BinaryResourceParser parser(table, resolver, source, uncompressedData.get(),
Adam Lesinski769de982015-04-10 19:43:55 -0700721 entry->getUncompressedLen());
722 if (!parser.parse()) {
Adam Lesinski769de982015-04-10 19:43:55 -0700723 return false;
724 }
Adam Lesinski769de982015-04-10 19:43:55 -0700725
726 // Keep track of where this table came from.
Adam Lesinski24aad162015-04-24 19:19:30 -0700727 apkFiles[table] = StaticLibraryData{ source, std::move(zipFile) };
Adam Lesinski769de982015-04-10 19:43:55 -0700728
729 // Add the package to the set of linked packages.
730 linkedPackages.insert(table->getPackage());
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700731 }
732
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700733 std::queue<LinkItem> linkQueue;
Adam Lesinski769de982015-04-10 19:43:55 -0700734 for (auto& p : apkFiles) {
735 const std::shared_ptr<ResourceTable>& inTable = p.first;
736
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700737 // Collect all FileReferences and add them to the queue for processing.
Adam Lesinski24aad162015-04-24 19:19:30 -0700738 addApkFilesToLinkQueue(options.appInfo.package, p.second.source, inTable, p.second.apk,
739 &linkQueue);
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700740
741 // Merge the tables.
Adam Lesinski769de982015-04-10 19:43:55 -0700742 if (!outTable->merge(std::move(*inTable))) {
743 return false;
744 }
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700745 }
746
Adam Lesinski769de982015-04-10 19:43:55 -0700747 {
748 // Now that everything is merged, let's link it.
749 Linker linker(outTable, resolver);
750 if (!linker.linkAndValidate()) {
751 return false;
752 }
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700753
Adam Lesinski769de982015-04-10 19:43:55 -0700754 // Verify that all symbols exist.
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700755 const auto& unresolvedRefs = linker.getUnresolvedReferences();
756 if (!unresolvedRefs.empty()) {
757 for (const auto& entry : unresolvedRefs) {
758 for (const auto& source : entry.second) {
759 Logger::error(source) << "unresolved symbol '" << entry.first << "'."
760 << std::endl;
761 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800762 }
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700763 return false;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800764 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800765 }
766
Adam Lesinski769de982015-04-10 19:43:55 -0700767 // Open the output APK file for writing.
768 ZipFile outApk;
769 if (outApk.open(options.output.path.data(), kOpenFlags) != android::NO_ERROR) {
770 Logger::error(options.output) << "failed to open: " << strerror(errno) << std::endl;
771 return false;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700772 }
773
Adam Lesinski769de982015-04-10 19:43:55 -0700774 if (!compileManifest(options, resolver, &outApk)) {
775 return false;
776 }
777
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700778 for (; !linkQueue.empty(); linkQueue.pop()) {
779 const LinkItem& item = linkQueue.front();
Adam Lesinski769de982015-04-10 19:43:55 -0700780
Adam Lesinski24aad162015-04-24 19:19:30 -0700781 assert(!item.originalPackage.empty());
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700782 ZipEntry* entry = item.apk->getEntryByName(item.originalPath.data());
783 if (!entry) {
784 Logger::error(item.source) << "failed to find '" << item.originalPath << "'."
785 << std::endl;
786 return false;
787 }
Adam Lesinski769de982015-04-10 19:43:55 -0700788
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700789 if (util::stringEndsWith<char>(item.originalPath, ".xml")) {
790 void* uncompressedData = item.apk->uncompress(entry);
791 assert(uncompressedData);
Adam Lesinski769de982015-04-10 19:43:55 -0700792
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700793 if (!linkXml(options, resolver, item, uncompressedData, entry->getUncompressedLen(),
794 &outApk)) {
795 Logger::error(options.output) << "failed to link '" << item.originalPath << "'."
796 << std::endl;
797 return false;
Adam Lesinski769de982015-04-10 19:43:55 -0700798 }
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700799 } else {
800 if (outApk.add(item.apk, entry, buildFileReference(item).data(), 0, nullptr) !=
801 android::NO_ERROR) {
802 Logger::error(options.output) << "failed to copy '" << item.originalPath << "'."
803 << std::endl;
804 return false;
Adam Lesinski769de982015-04-10 19:43:55 -0700805 }
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700806 }
807 }
808
809 // Generate the Java class file.
Adam Lesinski769de982015-04-10 19:43:55 -0700810 if (options.generateJavaClass) {
Adam Lesinski6d8e4c42015-05-01 14:47:28 -0700811 JavaClassGenerator::Options javaOptions;
812 if (options.packageType == AaptOptions::PackageType::StaticLibrary) {
813 javaOptions.useFinal = false;
814 }
815 JavaClassGenerator generator(outTable, javaOptions);
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700816
Adam Lesinski769de982015-04-10 19:43:55 -0700817 for (const std::u16string& package : linkedPackages) {
818 Source outPath = options.generateJavaClass.value();
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800819
Adam Lesinski769de982015-04-10 19:43:55 -0700820 // Build the output directory from the package name.
821 // Eg. com.android.app -> com/android/app
822 const std::string packageUtf8 = util::utf16ToUtf8(package);
823 for (StringPiece part : util::tokenize<char>(packageUtf8, '.')) {
824 appendPath(&outPath.path, part);
825 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800826
Adam Lesinski769de982015-04-10 19:43:55 -0700827 if (!mkdirs(outPath.path)) {
828 Logger::error(outPath) << strerror(errno) << std::endl;
829 return false;
830 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800831
Adam Lesinski769de982015-04-10 19:43:55 -0700832 appendPath(&outPath.path, "R.java");
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800833
Adam Lesinski769de982015-04-10 19:43:55 -0700834 if (options.verbose) {
835 Logger::note(outPath) << "writing Java symbols." << std::endl;
836 }
837
838 std::ofstream fout(outPath.path);
839 if (!fout) {
840 Logger::error(outPath) << strerror(errno) << std::endl;
841 return false;
842 }
843
844 if (!generator.generate(package, fout)) {
845 Logger::error(outPath) << generator.getError() << "." << std::endl;
846 return false;
847 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800848 }
849 }
850
Adam Lesinski24aad162015-04-24 19:19:30 -0700851 outTable->getValueStringPool().prune();
852 outTable->getValueStringPool().sort(
853 [](const StringPool::Entry& a, const StringPool::Entry& b) -> bool {
854 if (a.context.priority < b.context.priority) {
855 return true;
856 }
857
858 if (a.context.priority > b.context.priority) {
859 return false;
860 }
861 return a.value < b.value;
862 });
863
864
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700865 // Flatten the resource table.
Adam Lesinski769de982015-04-10 19:43:55 -0700866 TableFlattener::Options flattenerOptions;
Adam Lesinski6d8e4c42015-05-01 14:47:28 -0700867 if (options.packageType == AaptOptions::PackageType::StaticLibrary) {
868 flattenerOptions.useExtendedChunks = true;
869 }
870
Adam Lesinski769de982015-04-10 19:43:55 -0700871 if (!writeResourceTable(options, outTable, flattenerOptions, &outApk)) {
872 return false;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800873 }
Adam Lesinski769de982015-04-10 19:43:55 -0700874
875 outApk.flush();
876 return true;
877}
878
879bool compile(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table,
Adam Lesinski24aad162015-04-24 19:19:30 -0700880 const std::shared_ptr<IResolver>& resolver) {
Adam Lesinski769de982015-04-10 19:43:55 -0700881 std::queue<CompileItem> compileQueue;
882 bool error = false;
883
884 // Compile all the resource files passed in on the command line.
885 for (const Source& source : options.input) {
886 // Need to parse the resource type/config/filename.
887 Maybe<ResourcePathData> maybePathData = extractResourcePathData(source);
888 if (!maybePathData) {
889 return false;
890 }
891
892 const ResourcePathData& pathData = maybePathData.value();
893 if (pathData.resourceDir == u"values") {
894 // The file is in the values directory, which means its contents will
895 // go into the resource table.
896 if (options.verbose) {
897 Logger::note(source) << "compiling values." << std::endl;
898 }
899
900 error |= !compileValues(table, source, pathData.config);
901 } else {
902 // The file is in a directory like 'layout' or 'drawable'. Find out
903 // the type.
904 const ResourceType* type = parseResourceType(pathData.resourceDir);
905 if (!type) {
906 Logger::error(source) << "invalid resource type '" << pathData.resourceDir << "'."
907 << std::endl;
908 return false;
909 }
910
911 compileQueue.push(CompileItem{
912 source,
913 ResourceName{ table->getPackage(), *type, pathData.name },
914 pathData.config,
915 pathData.extension
916 });
917 }
918 }
919
920 if (error) {
921 return false;
922 }
923
924 // Version all styles referencing attributes outside of their specified SDK version.
Adam Lesinski5886a922015-04-15 20:29:22 -0700925 if (options.versionStylesAndLayouts) {
926 versionStylesForCompat(table);
927 }
Adam Lesinski769de982015-04-10 19:43:55 -0700928
929 // Open the output APK file for writing.
930 ZipFile outApk;
931 if (outApk.open(options.output.path.data(), kOpenFlags) != android::NO_ERROR) {
932 Logger::error(options.output) << "failed to open: " << strerror(errno) << std::endl;
933 return false;
934 }
935
936 // Compile each file.
937 for (; !compileQueue.empty(); compileQueue.pop()) {
938 const CompileItem& item = compileQueue.front();
939
940 // Add the file name to the resource table.
941 error |= !addFileReference(table, item);
942
943 if (item.extension == "xml") {
944 error |= !compileXml(options, table, item, &compileQueue, &outApk);
945 } else if (item.extension == "png" || item.extension == "9.png") {
946 error |= !compilePng(options, item, &outApk);
947 } else {
948 error |= !copyFile(options, item, &outApk);
949 }
950 }
951
952 if (error) {
953 return false;
954 }
955
956 // Link and assign resource IDs.
957 Linker linker(table, resolver);
958 if (!linker.linkAndValidate()) {
959 return false;
960 }
961
962 // Flatten the resource table.
963 if (!writeResourceTable(options, table, {}, &outApk)) {
964 return false;
965 }
966
967 outApk.flush();
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800968 return true;
969}
970
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700971bool loadAppInfo(const Source& source, AppInfo* outInfo) {
972 std::ifstream ifs(source.path, std::ifstream::in | std::ifstream::binary);
973 if (!ifs) {
974 Logger::error(source) << strerror(errno) << std::endl;
975 return false;
976 }
977
978 ManifestParser parser;
979 std::shared_ptr<XmlPullParser> pullParser = std::make_shared<SourceXmlPullParser>(ifs);
980 return parser.parse(source, pullParser, outInfo);
981}
982
983static void printCommandsAndDie() {
984 std::cerr << "The following commands are supported:" << std::endl << std::endl;
985 std::cerr << "compile compiles a subset of resources" << std::endl;
986 std::cerr << "link links together compiled resources and libraries" << std::endl;
987 std::cerr << std::endl;
988 std::cerr << "run aapt2 with one of the commands and the -h flag for extra details."
989 << std::endl;
990 exit(1);
991}
992
993static AaptOptions prepareArgs(int argc, char** argv) {
994 if (argc < 2) {
995 std::cerr << "no command specified." << std::endl << std::endl;
996 printCommandsAndDie();
997 }
998
999 const StringPiece command(argv[1]);
1000 argc -= 2;
1001 argv += 2;
1002
1003 AaptOptions options;
1004
1005 if (command == "--version" || command == "version") {
1006 std::cout << kAaptVersionStr << std::endl;
1007 exit(0);
1008 } else if (command == "link") {
1009 options.phase = AaptOptions::Phase::Link;
1010 } else if (command == "compile") {
1011 options.phase = AaptOptions::Phase::Compile;
1012 } else {
1013 std::cerr << "invalid command '" << command << "'." << std::endl << std::endl;
1014 printCommandsAndDie();
1015 }
1016
Adam Lesinski6d8e4c42015-05-01 14:47:28 -07001017 bool isStaticLib = false;
Adam Lesinskid5c4f872015-04-21 13:56:10 -07001018 if (options.phase == AaptOptions::Phase::Compile) {
1019 flag::requiredFlag("--package", "Android package name",
1020 [&options](const StringPiece& arg) {
1021 options.appInfo.package = util::utf8ToUtf16(arg);
1022 });
1023 flag::optionalFlag("--binding", "Output directory for binding XML files",
1024 [&options](const StringPiece& arg) {
1025 options.bindingOutput = Source{ arg.toString() };
1026 });
1027 flag::optionalSwitch("--no-version", "Disables automatic style and layout versioning",
1028 false, &options.versionStylesAndLayouts);
1029
1030 } else if (options.phase == AaptOptions::Phase::Link) {
1031 flag::requiredFlag("--manifest", "AndroidManifest.xml of your app",
1032 [&options](const StringPiece& arg) {
1033 options.manifest = Source{ arg.toString() };
1034 });
1035
1036 flag::optionalFlag("-I", "add an Android APK to link against",
1037 [&options](const StringPiece& arg) {
1038 options.libraries.push_back(Source{ arg.toString() });
1039 });
1040
1041 flag::optionalFlag("--java", "directory in which to generate R.java",
1042 [&options](const StringPiece& arg) {
1043 options.generateJavaClass = Source{ arg.toString() };
1044 });
Adam Lesinski6d8e4c42015-05-01 14:47:28 -07001045 flag::optionalSwitch("--static-lib", "generate a static Android library", true,
1046 &isStaticLib);
Adam Lesinskid5c4f872015-04-21 13:56:10 -07001047 }
1048
1049 // Common flags for all steps.
1050 flag::requiredFlag("-o", "Output path", [&options](const StringPiece& arg) {
1051 options.output = Source{ arg.toString() };
1052 });
1053
1054 bool help = false;
1055 flag::optionalSwitch("-v", "enables verbose logging", true, &options.verbose);
1056 flag::optionalSwitch("-h", "displays this help menu", true, &help);
1057
1058 // Build the command string for output (eg. "aapt2 compile").
1059 std::string fullCommand = "aapt2";
1060 fullCommand += " ";
1061 fullCommand += command.toString();
1062
1063 // Actually read the command line flags.
1064 flag::parse(argc, argv, fullCommand);
1065
1066 if (help) {
1067 flag::usageAndDie(fullCommand);
1068 }
1069
Adam Lesinski6d8e4c42015-05-01 14:47:28 -07001070 if (isStaticLib) {
1071 options.packageType = AaptOptions::PackageType::StaticLibrary;
1072 }
1073
Adam Lesinskid5c4f872015-04-21 13:56:10 -07001074 // Copy all the remaining arguments.
1075 for (const std::string& arg : flag::getArgs()) {
1076 options.input.push_back(Source{ arg });
1077 }
1078 return options;
1079}
1080
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001081int main(int argc, char** argv) {
1082 Logger::setLog(std::make_shared<Log>(std::cerr, std::cerr));
Adam Lesinski98aa3ad2015-04-06 11:46:52 -07001083 AaptOptions options = prepareArgs(argc, argv);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001084
Adam Lesinski98aa3ad2015-04-06 11:46:52 -07001085 // If we specified a manifest, go ahead and load the package name from the manifest.
1086 if (!options.manifest.path.empty()) {
1087 if (!loadAppInfo(options.manifest, &options.appInfo)) {
1088 return false;
1089 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001090 }
1091
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001092 // Verify we have some common options set.
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001093 if (options.appInfo.package.empty()) {
1094 Logger::error() << "no package name specified." << std::endl;
1095 return false;
1096 }
1097
Adam Lesinski98aa3ad2015-04-06 11:46:52 -07001098 // Every phase needs a resource table.
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001099 std::shared_ptr<ResourceTable> table = std::make_shared<ResourceTable>();
1100 table->setPackage(options.appInfo.package);
1101 if (options.appInfo.package == u"android") {
1102 table->setPackageId(0x01);
1103 } else {
1104 table->setPackageId(0x7f);
1105 }
1106
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001107 // Load the included libraries.
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001108 std::shared_ptr<android::AssetManager> libraries = std::make_shared<android::AssetManager>();
1109 for (const Source& source : options.libraries) {
Adam Lesinski4d3a9872015-04-09 19:53:22 -07001110 if (util::stringEndsWith<char>(source.path, ".arsc")) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001111 // We'll process these last so as to avoid a cookie issue.
1112 continue;
1113 }
1114
1115 int32_t cookie;
1116 if (!libraries->addAssetPath(android::String8(source.path.data()), &cookie)) {
1117 Logger::error(source) << "failed to load library." << std::endl;
1118 return false;
1119 }
1120 }
1121
1122 for (const Source& source : options.libraries) {
Adam Lesinski4d3a9872015-04-09 19:53:22 -07001123 if (!util::stringEndsWith<char>(source.path, ".arsc")) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001124 // We've already processed this.
1125 continue;
1126 }
1127
1128 // Dirty hack but there is no other way to get a
1129 // writeable ResTable.
1130 if (!loadResTable(const_cast<android::ResTable*>(&libraries->getResources(false)),
1131 source)) {
1132 return false;
1133 }
1134 }
1135
1136 // Make the resolver that will cache IDs for us.
Adam Lesinski24aad162015-04-24 19:19:30 -07001137 std::shared_ptr<ResourceTableResolver> resolver = std::make_shared<ResourceTableResolver>(
1138 table, libraries);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001139
Adam Lesinski769de982015-04-10 19:43:55 -07001140 if (options.phase == AaptOptions::Phase::Compile) {
1141 if (!compile(options, table, resolver)) {
1142 Logger::error() << "aapt exiting with failures." << std::endl;
1143 return 1;
1144 }
1145 } else if (options.phase == AaptOptions::Phase::Link) {
1146 if (!link(options, table, resolver)) {
1147 Logger::error() << "aapt exiting with failures." << std::endl;
1148 return 1;
1149 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001150 }
1151 return 0;
1152}