blob: 84957b4fbbd1d5409f9b39cdbc0e705a4355f438 [file] [log] [blame]
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include "AppInfo.h"
18#include "BigBuffer.h"
19#include "BinaryResourceParser.h"
Adam Lesinski4d3a9872015-04-09 19:53:22 -070020#include "BindingXmlPullParser.h"
Adam Lesinski330edcd2015-05-04 17:40:56 -070021#include "Debug.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
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080058/**
59 * Collect files from 'root', filtering out any files that do not
60 * match the FileFilter 'filter'.
61 */
Adam Lesinski98aa3ad2015-04-06 11:46:52 -070062bool walkTree(const Source& root, const FileFilter& filter,
63 std::vector<Source>* outEntries) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080064 bool error = false;
65
Adam Lesinski98aa3ad2015-04-06 11:46:52 -070066 for (const std::string& dirName : listFiles(root.path)) {
67 std::string dir = root.path;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080068 appendPath(&dir, dirName);
69
70 FileType ft = getFileType(dir);
71 if (!filter(dirName, ft)) {
72 continue;
73 }
74
75 if (ft != FileType::kDirectory) {
76 continue;
77 }
78
79 for (const std::string& fileName : listFiles(dir)) {
80 std::string file(dir);
81 appendPath(&file, fileName);
82
83 FileType ft = getFileType(file);
84 if (!filter(fileName, ft)) {
85 continue;
86 }
87
88 if (ft != FileType::kRegular) {
Adam Lesinski98aa3ad2015-04-06 11:46:52 -070089 Logger::error(Source{ file }) << "not a regular file." << std::endl;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080090 error = true;
91 continue;
92 }
Adam Lesinski98aa3ad2015-04-06 11:46:52 -070093 outEntries->push_back(Source{ file });
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080094 }
95 }
96 return !error;
97}
98
Adam Lesinski769de982015-04-10 19:43:55 -070099void versionStylesForCompat(const std::shared_ptr<ResourceTable>& table) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800100 for (auto& type : *table) {
101 if (type->type != ResourceType::kStyle) {
102 continue;
103 }
104
105 for (auto& entry : type->entries) {
106 // Add the versioned styles we want to create
107 // here. They are added to the table after
108 // iterating over the original set of styles.
109 //
110 // A stack is used since auto-generated styles
111 // from later versions should override
112 // auto-generated styles from earlier versions.
113 // Iterating over the styles is done in order,
114 // so we will always visit sdkVersions from smallest
115 // to largest.
116 std::stack<ResourceConfigValue> addStack;
117
118 for (ResourceConfigValue& configValue : entry->values) {
119 visitFunc<Style>(*configValue.value, [&](Style& style) {
120 // Collect which entries we've stripped and the smallest
121 // SDK level which was stripped.
122 size_t minSdkStripped = std::numeric_limits<size_t>::max();
123 std::vector<Style::Entry> stripped;
124
125 // Iterate over the style's entries and erase/record the
126 // attributes whose SDK level exceeds the config's sdkVersion.
127 auto iter = style.entries.begin();
128 while (iter != style.entries.end()) {
129 if (iter->key.name.package == u"android") {
Adam Lesinski75f3a552015-06-03 14:54:23 -0700130 size_t sdkLevel = findAttributeSdkLevel(iter->key.name);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800131 if (sdkLevel > 1 && sdkLevel > configValue.config.sdkVersion) {
132 // Record that we are about to strip this.
133 stripped.emplace_back(std::move(*iter));
134 minSdkStripped = std::min(minSdkStripped, sdkLevel);
135
136 // Erase this from this style.
137 iter = style.entries.erase(iter);
138 continue;
139 }
140 }
141 ++iter;
142 }
143
144 if (!stripped.empty()) {
145 // We have stripped attributes, so let's create a new style to hold them.
146 ConfigDescription versionConfig(configValue.config);
147 versionConfig.sdkVersion = minSdkStripped;
148
149 ResourceConfigValue value = {
150 versionConfig,
151 configValue.source,
152 {},
153
154 // Create a copy of the original style.
Adam Lesinski769de982015-04-10 19:43:55 -0700155 std::unique_ptr<Value>(configValue.value->clone(
156 &table->getValueStringPool()))
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800157 };
158
159 Style& newStyle = static_cast<Style&>(*value.value);
160
161 // Move the recorded stripped attributes into this new style.
162 std::move(stripped.begin(), stripped.end(),
163 std::back_inserter(newStyle.entries));
164
165 // We will add this style to the table later. If we do it now, we will
166 // mess up iteration.
167 addStack.push(std::move(value));
168 }
169 });
170 }
171
172 auto comparator =
173 [](const ResourceConfigValue& lhs, const ConfigDescription& rhs) -> bool {
174 return lhs.config < rhs;
175 };
176
177 while (!addStack.empty()) {
178 ResourceConfigValue& value = addStack.top();
179 auto iter = std::lower_bound(entry->values.begin(), entry->values.end(),
180 value.config, comparator);
181 if (iter == entry->values.end() || iter->config != value.config) {
182 entry->values.insert(iter, std::move(value));
183 }
184 addStack.pop();
185 }
186 }
187 }
188}
189
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700190struct CompileItem {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800191 ResourceName name;
192 ConfigDescription config;
Adam Lesinski39c353a2015-05-14 17:58:14 -0700193 Source source;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700194 std::string extension;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800195};
196
Adam Lesinski769de982015-04-10 19:43:55 -0700197struct LinkItem {
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700198 ResourceName name;
199 ConfigDescription config;
Adam Lesinski39c353a2015-05-14 17:58:14 -0700200 Source source;
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700201 std::string originalPath;
202 ZipFile* apk;
Adam Lesinski24aad162015-04-24 19:19:30 -0700203 std::u16string originalPackage;
Adam Lesinski769de982015-04-10 19:43:55 -0700204};
205
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700206template <typename TChar>
207static BasicStringPiece<TChar> getExtension(const BasicStringPiece<TChar>& str) {
208 auto iter = std::find(str.begin(), str.end(), static_cast<TChar>('.'));
209 if (iter == str.end()) {
210 return BasicStringPiece<TChar>();
Adam Lesinski769de982015-04-10 19:43:55 -0700211 }
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700212 size_t offset = (iter - str.begin()) + 1;
213 return str.substr(offset, str.size() - offset);
214}
215
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700216std::string buildFileReference(const ResourceNameRef& name, const ConfigDescription& config,
217 const StringPiece& extension) {
218 std::stringstream path;
219 path << "res/" << name.type;
220 if (config != ConfigDescription{}) {
221 path << "-" << config;
222 }
223 path << "/" << util::utf16ToUtf8(name.entry);
224 if (!extension.empty()) {
225 path << "." << extension;
226 }
Adam Lesinski769de982015-04-10 19:43:55 -0700227 return path.str();
228}
229
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700230std::string buildFileReference(const CompileItem& item) {
231 return buildFileReference(item.name, item.config, item.extension);
232}
233
234std::string buildFileReference(const LinkItem& item) {
235 return buildFileReference(item.name, item.config, getExtension<char>(item.originalPath));
236}
237
Adam Lesinski39c353a2015-05-14 17:58:14 -0700238template <typename T>
239bool addFileReference(const std::shared_ptr<ResourceTable>& table, const T& item) {
Adam Lesinski769de982015-04-10 19:43:55 -0700240 StringPool& pool = table->getValueStringPool();
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700241 StringPool::Ref ref = pool.makeRef(util::utf8ToUtf16(buildFileReference(item)),
242 StringPool::Context{ 0, item.config });
Adam Lesinski769de982015-04-10 19:43:55 -0700243 return table->addResource(item.name, item.config, item.source.line(0),
244 util::make_unique<FileReference>(ref));
245}
246
247struct AaptOptions {
248 enum class Phase {
249 Link,
250 Compile,
Adam Lesinski330edcd2015-05-04 17:40:56 -0700251 Dump,
252 DumpStyleGraph,
Adam Lesinski769de982015-04-10 19:43:55 -0700253 };
254
Adam Lesinski6d8e4c42015-05-01 14:47:28 -0700255 enum class PackageType {
256 StandardApp,
257 StaticLibrary,
258 };
259
Adam Lesinski769de982015-04-10 19:43:55 -0700260 // The phase to process.
261 Phase phase;
262
Adam Lesinski6d8e4c42015-05-01 14:47:28 -0700263 // The type of package to produce.
264 PackageType packageType = PackageType::StandardApp;
265
Adam Lesinski769de982015-04-10 19:43:55 -0700266 // Details about the app.
267 AppInfo appInfo;
268
269 // The location of the manifest file.
270 Source manifest;
271
272 // The APK files to link.
273 std::vector<Source> input;
274
275 // The libraries these files may reference.
276 std::vector<Source> libraries;
277
278 // Output path. This can be a directory or file
279 // depending on the phase.
280 Source output;
281
282 // Directory in which to write binding xml files.
283 Source bindingOutput;
284
285 // Directory to in which to generate R.java.
286 Maybe<Source> generateJavaClass;
287
288 // Whether to output verbose details about
289 // compilation.
290 bool verbose = false;
Adam Lesinski5886a922015-04-15 20:29:22 -0700291
292 // Whether or not to auto-version styles or layouts
293 // referencing attributes defined in a newer SDK
294 // level than the style or layout is defined for.
295 bool versionStylesAndLayouts = true;
Adam Lesinskid13fb242015-05-12 20:40:48 -0700296
297 // The target style that will have it's style hierarchy dumped
298 // when the phase is DumpStyleGraph.
299 ResourceName dumpStyleTarget;
Adam Lesinski769de982015-04-10 19:43:55 -0700300};
301
Adam Lesinski75f3a552015-06-03 14:54:23 -0700302struct IdCollector : public xml::Visitor {
303 IdCollector(const Source& source, const std::shared_ptr<ResourceTable>& table) :
304 mSource(source), mTable(table) {
305 }
306
307 virtual void visit(xml::Text* node) override {}
308
309 virtual void visit(xml::Namespace* node) override {
310 for (const auto& child : node->children) {
311 child->accept(this);
312 }
313 }
314
315 virtual void visit(xml::Element* node) override {
316 for (const xml::Attribute& attr : node->attributes) {
317 bool create = false;
318 bool priv = false;
319 ResourceNameRef nameRef;
320 if (ResourceParser::tryParseReference(attr.value, &nameRef, &create, &priv)) {
321 if (create) {
322 mTable->addResource(nameRef, {}, mSource.line(node->lineNumber),
323 util::make_unique<Id>());
324 }
325 }
326 }
327
328 for (const auto& child : node->children) {
329 child->accept(this);
330 }
331 }
332
333private:
334 Source mSource;
335 std::shared_ptr<ResourceTable> mTable;
336};
337
Adam Lesinski769de982015-04-10 19:43:55 -0700338bool compileXml(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table,
Adam Lesinski330edcd2015-05-04 17:40:56 -0700339 const CompileItem& item, ZipFile* outApk) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800340 std::ifstream in(item.source.path, std::ifstream::binary);
341 if (!in) {
342 Logger::error(item.source) << strerror(errno) << std::endl;
343 return false;
344 }
345
Adam Lesinski75f3a552015-06-03 14:54:23 -0700346 SourceLogger logger(item.source);
347 std::unique_ptr<xml::Node> root = xml::inflate(&in, &logger);
348 if (!root) {
349 return false;
350 }
351
352 // Collect any resource ID's declared here.
353 IdCollector idCollector(item.source, table);
354 root->accept(&idCollector);
355
Adam Lesinski4d3a9872015-04-09 19:53:22 -0700356 BigBuffer outBuffer(1024);
Adam Lesinski75f3a552015-06-03 14:54:23 -0700357 if (!xml::flatten(root.get(), options.appInfo.package, &outBuffer)) {
358 logger.error() << "failed to encode XML." << std::endl;
Adam Lesinski330edcd2015-05-04 17:40:56 -0700359 return false;
360 }
361
362 // Write the resulting compiled XML file to the output APK.
363 if (outApk->add(outBuffer, buildFileReference(item).data(), ZipEntry::kCompressStored,
364 nullptr) != android::NO_ERROR) {
365 Logger::error(options.output) << "failed to write compiled '" << item.source
366 << "' to apk." << std::endl;
367 return false;
368 }
369 return true;
370}
371
Adam Lesinski39c353a2015-05-14 17:58:14 -0700372/**
373 * Determines if a layout should be auto generated based on SDK level. We do not
374 * generate a layout if there is already a layout defined whose SDK version is greater than
375 * the one we want to generate.
376 */
377bool shouldGenerateVersionedResource(const std::shared_ptr<const ResourceTable>& table,
378 const ResourceName& name, const ConfigDescription& config,
379 int sdkVersionToGenerate) {
380 assert(sdkVersionToGenerate > config.sdkVersion);
381 const ResourceTableType* type;
382 const ResourceEntry* entry;
383 std::tie(type, entry) = table->findResource(name);
384 assert(type && entry);
385
386 auto iter = std::lower_bound(entry->values.begin(), entry->values.end(), config,
387 [](const ResourceConfigValue& lhs, const ConfigDescription& config) -> bool {
388 return lhs.config < config;
389 });
390
391 assert(iter != entry->values.end());
392 ++iter;
393
394 if (iter == entry->values.end()) {
395 return true;
396 }
397
398 ConfigDescription newConfig = config;
399 newConfig.sdkVersion = sdkVersionToGenerate;
400 return newConfig < iter->config;
401}
402
403bool linkXml(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table,
404 const std::shared_ptr<IResolver>& resolver, const LinkItem& item,
405 const void* data, size_t dataLen, ZipFile* outApk, std::queue<LinkItem>* outQueue) {
Adam Lesinski75f3a552015-06-03 14:54:23 -0700406 SourceLogger logger(item.source);
407 std::unique_ptr<xml::Node> root = xml::inflate(data, dataLen, &logger);
408 if (!root) {
Adam Lesinski330edcd2015-05-04 17:40:56 -0700409 return false;
410 }
411
Adam Lesinski75f3a552015-06-03 14:54:23 -0700412 xml::FlattenOptions xmlOptions;
Adam Lesinski330edcd2015-05-04 17:40:56 -0700413 if (options.packageType == AaptOptions::PackageType::StaticLibrary) {
414 xmlOptions.keepRawValues = true;
415 }
Adam Lesinski24aad162015-04-24 19:19:30 -0700416
Adam Lesinski5886a922015-04-15 20:29:22 -0700417 if (options.versionStylesAndLayouts) {
418 // We strip attributes that do not belong in this version of the resource.
419 // Non-version qualified resources have an implicit version 1 requirement.
420 xmlOptions.maxSdkAttribute = item.config.sdkVersion ? item.config.sdkVersion : 1;
421 }
Adam Lesinski769de982015-04-10 19:43:55 -0700422
Adam Lesinski75f3a552015-06-03 14:54:23 -0700423 BigBuffer outBuffer(1024);
424 Maybe<size_t> minStrippedSdk = xml::flattenAndLink(item.source, root.get(),
425 item.originalPackage, resolver,
426 xmlOptions, &outBuffer);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800427 if (!minStrippedSdk) {
Adam Lesinski75f3a552015-06-03 14:54:23 -0700428 logger.error() << "failed to encode XML." << std::endl;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800429 return false;
430 }
431
432 if (minStrippedSdk.value() > 0) {
433 // Something was stripped, so let's generate a new file
434 // with the version of the smallest SDK version stripped.
Adam Lesinski39c353a2015-05-14 17:58:14 -0700435 // We can only generate a versioned layout if there doesn't exist a layout
436 // with sdk version greater than the current one but less than the one we
437 // want to generate.
438 if (shouldGenerateVersionedResource(table, item.name, item.config,
439 minStrippedSdk.value())) {
440 LinkItem newWork = item;
441 newWork.config.sdkVersion = minStrippedSdk.value();
442 outQueue->push(newWork);
443
444 if (!addFileReference(table, newWork)) {
445 Logger::error(options.output) << "failed to add auto-versioned resource '"
446 << newWork.name << "'." << std::endl;
447 return false;
448 }
449 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800450 }
451
Adam Lesinski330edcd2015-05-04 17:40:56 -0700452 if (outApk->add(outBuffer, buildFileReference(item).data(), ZipEntry::kCompressDeflated,
Adam Lesinski769de982015-04-10 19:43:55 -0700453 nullptr) != android::NO_ERROR) {
Adam Lesinski330edcd2015-05-04 17:40:56 -0700454 Logger::error(options.output) << "failed to write linked file '"
455 << buildFileReference(item) << "' to apk." << std::endl;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800456 return false;
457 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800458 return true;
459}
460
Adam Lesinski769de982015-04-10 19:43:55 -0700461bool compilePng(const AaptOptions& options, const CompileItem& item, ZipFile* outApk) {
462 std::ifstream in(item.source.path, std::ifstream::binary);
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700463 if (!in) {
Adam Lesinski769de982015-04-10 19:43:55 -0700464 Logger::error(item.source) << strerror(errno) << std::endl;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700465 return false;
466 }
467
Adam Lesinski769de982015-04-10 19:43:55 -0700468 BigBuffer outBuffer(4096);
469 std::string err;
470 Png png;
471 if (!png.process(item.source, in, &outBuffer, {}, &err)) {
472 Logger::error(item.source) << err << std::endl;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700473 return false;
474 }
475
Adam Lesinski769de982015-04-10 19:43:55 -0700476 if (outApk->add(outBuffer, buildFileReference(item).data(), ZipEntry::kCompressStored,
477 nullptr) != android::NO_ERROR) {
478 Logger::error(options.output) << "failed to write compiled '" << item.source
479 << "' to apk." << std::endl;
480 return false;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700481 }
Adam Lesinski769de982015-04-10 19:43:55 -0700482 return true;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700483}
484
Adam Lesinski769de982015-04-10 19:43:55 -0700485bool copyFile(const AaptOptions& options, const CompileItem& item, ZipFile* outApk) {
486 if (outApk->add(item.source.path.data(), buildFileReference(item).data(),
487 ZipEntry::kCompressStored, nullptr) != android::NO_ERROR) {
488 Logger::error(options.output) << "failed to copy file '" << item.source << "' to apk."
489 << std::endl;
490 return false;
491 }
492 return true;
493}
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800494
Adam Lesinski330edcd2015-05-04 17:40:56 -0700495bool compileManifest(const AaptOptions& options, const std::shared_ptr<IResolver>& resolver,
496 const android::ResTable& table, ZipFile* outApk) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800497 if (options.verbose) {
Adam Lesinski769de982015-04-10 19:43:55 -0700498 Logger::note(options.manifest) << "compiling AndroidManifest.xml." << std::endl;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800499 }
500
501 std::ifstream in(options.manifest.path, std::ifstream::binary);
502 if (!in) {
503 Logger::error(options.manifest) << strerror(errno) << std::endl;
504 return false;
505 }
506
Adam Lesinski75f3a552015-06-03 14:54:23 -0700507 SourceLogger logger(options.manifest);
508 std::unique_ptr<xml::Node> root = xml::inflate(&in, &logger);
509 if (!root) {
510 return false;
511 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800512
Adam Lesinski75f3a552015-06-03 14:54:23 -0700513 BigBuffer outBuffer(1024);
514 if (!xml::flattenAndLink(options.manifest, root.get(), options.appInfo.package, resolver, {},
515 &outBuffer)) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800516 return false;
517 }
518
Adam Lesinski769de982015-04-10 19:43:55 -0700519 std::unique_ptr<uint8_t[]> data = util::copy(outBuffer);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800520
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700521 android::ResXMLTree tree;
Adam Lesinski769de982015-04-10 19:43:55 -0700522 if (tree.setTo(data.get(), outBuffer.size(), false) != android::NO_ERROR) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800523 return false;
524 }
525
Adam Lesinski330edcd2015-05-04 17:40:56 -0700526 ManifestValidator validator(table);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800527 if (!validator.validate(options.manifest, &tree)) {
528 return false;
529 }
530
Adam Lesinski769de982015-04-10 19:43:55 -0700531 if (outApk->add(data.get(), outBuffer.size(), "AndroidManifest.xml",
532 ZipEntry::kCompressStored, nullptr) != android::NO_ERROR) {
533 Logger::error(options.output) << "failed to write 'AndroidManifest.xml' to apk."
534 << std::endl;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800535 return false;
536 }
537 return true;
538}
539
Adam Lesinski769de982015-04-10 19:43:55 -0700540static bool compileValues(const std::shared_ptr<ResourceTable>& table, const Source& source,
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700541 const ConfigDescription& config) {
542 std::ifstream in(source.path, std::ifstream::binary);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800543 if (!in) {
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700544 Logger::error(source) << strerror(errno) << std::endl;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800545 return false;
546 }
547
548 std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<SourceXmlPullParser>(in);
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700549 ResourceParser parser(table, source, config, xmlParser);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800550 return parser.parse();
551}
552
553struct ResourcePathData {
554 std::u16string resourceDir;
555 std::u16string name;
556 std::string extension;
557 ConfigDescription config;
558};
559
560/**
561 * Resource file paths are expected to look like:
562 * [--/res/]type[-config]/name
563 */
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700564static Maybe<ResourcePathData> extractResourcePathData(const Source& source) {
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700565 // TODO(adamlesinski): Use Windows path separator on windows.
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800566 std::vector<std::string> parts = util::splitAndLowercase(source.path, '/');
567 if (parts.size() < 2) {
568 Logger::error(source) << "bad resource path." << std::endl;
569 return {};
570 }
571
572 std::string& dir = parts[parts.size() - 2];
573 StringPiece dirStr = dir;
574
575 ConfigDescription config;
576 size_t dashPos = dir.find('-');
577 if (dashPos != std::string::npos) {
578 StringPiece configStr = dirStr.substr(dashPos + 1, dir.size() - (dashPos + 1));
579 if (!ConfigDescription::parse(configStr, &config)) {
580 Logger::error(source)
581 << "invalid configuration '"
582 << configStr
583 << "'."
584 << std::endl;
585 return {};
586 }
587 dirStr = dirStr.substr(0, dashPos);
588 }
589
590 std::string& filename = parts[parts.size() - 1];
591 StringPiece name = filename;
592 StringPiece extension;
593 size_t dotPos = filename.find('.');
594 if (dotPos != std::string::npos) {
595 extension = name.substr(dotPos + 1, filename.size() - (dotPos + 1));
596 name = name.substr(0, dotPos);
597 }
598
599 return ResourcePathData{
600 util::utf8ToUtf16(dirStr),
601 util::utf8ToUtf16(name),
602 extension.toString(),
603 config
604 };
605}
606
Adam Lesinski769de982015-04-10 19:43:55 -0700607bool writeResourceTable(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table,
608 const TableFlattener::Options& flattenerOptions, ZipFile* outApk) {
609 if (table->begin() != table->end()) {
610 BigBuffer buffer(1024);
611 TableFlattener flattener(flattenerOptions);
612 if (!flattener.flatten(&buffer, *table)) {
613 Logger::error() << "failed to flatten resource table." << std::endl;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700614 return false;
615 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800616
Adam Lesinski769de982015-04-10 19:43:55 -0700617 if (options.verbose) {
618 Logger::note() << "Final resource table size=" << util::formatSize(buffer.size())
619 << std::endl;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700620 }
621
Adam Lesinski769de982015-04-10 19:43:55 -0700622 if (outApk->add(buffer, "resources.arsc", ZipEntry::kCompressStored, nullptr) !=
623 android::NO_ERROR) {
624 Logger::note(options.output) << "failed to store resource table." << std::endl;
625 return false;
626 }
627 }
628 return true;
629}
630
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700631/**
632 * For each FileReference in the table, adds a LinkItem to the link queue for processing.
633 */
634static void addApkFilesToLinkQueue(const std::u16string& package, const Source& source,
635 const std::shared_ptr<ResourceTable>& table,
636 const std::unique_ptr<ZipFile>& apk,
637 std::queue<LinkItem>* outLinkQueue) {
638 bool mangle = package != table->getPackage();
639 for (auto& type : *table) {
640 for (auto& entry : type->entries) {
641 ResourceName name = { package, type->type, entry->name };
642 if (mangle) {
643 NameMangler::mangle(table->getPackage(), &name.entry);
644 }
645
646 for (auto& value : entry->values) {
647 visitFunc<FileReference>(*value.value, [&](FileReference& ref) {
648 std::string pathUtf8 = util::utf16ToUtf8(*ref.path);
Adam Lesinski24aad162015-04-24 19:19:30 -0700649 Source newSource = source;
650 newSource.path += "/";
651 newSource.path += pathUtf8;
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700652 outLinkQueue->push(LinkItem{
Adam Lesinski39c353a2015-05-14 17:58:14 -0700653 name, value.config, newSource, pathUtf8, apk.get(),
Adam Lesinski24aad162015-04-24 19:19:30 -0700654 table->getPackage() });
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700655 // Now rewrite the file path.
656 if (mangle) {
657 ref.path = table->getValueStringPool().makeRef(util::utf8ToUtf16(
658 buildFileReference(name, value.config,
659 getExtension<char>(pathUtf8))));
660 }
661 });
662 }
663 }
664 }
665}
666
Adam Lesinski769de982015-04-10 19:43:55 -0700667static constexpr int kOpenFlags = ZipFile::kOpenCreate | ZipFile::kOpenTruncate |
668 ZipFile::kOpenReadWrite;
669
Adam Lesinski24aad162015-04-24 19:19:30 -0700670struct DeleteMalloc {
671 void operator()(void* ptr) {
672 free(ptr);
673 }
674};
675
676struct StaticLibraryData {
677 Source source;
678 std::unique_ptr<ZipFile> apk;
679};
680
Adam Lesinski769de982015-04-10 19:43:55 -0700681bool link(const AaptOptions& options, const std::shared_ptr<ResourceTable>& outTable,
Adam Lesinski330edcd2015-05-04 17:40:56 -0700682 const std::shared_ptr<IResolver>& resolver) {
Adam Lesinski24aad162015-04-24 19:19:30 -0700683 std::map<std::shared_ptr<ResourceTable>, StaticLibraryData> apkFiles;
Adam Lesinski769de982015-04-10 19:43:55 -0700684 std::unordered_set<std::u16string> linkedPackages;
685
686 // Populate the linkedPackages with our own.
687 linkedPackages.insert(options.appInfo.package);
688
689 // Load all APK files.
690 for (const Source& source : options.input) {
691 std::unique_ptr<ZipFile> zipFile = util::make_unique<ZipFile>();
692 if (zipFile->open(source.path.data(), ZipFile::kOpenReadOnly) != android::NO_ERROR) {
693 Logger::error(source) << "failed to open: " << strerror(errno) << std::endl;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700694 return false;
695 }
696
Adam Lesinski769de982015-04-10 19:43:55 -0700697 std::shared_ptr<ResourceTable> table = std::make_shared<ResourceTable>();
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700698
Adam Lesinski769de982015-04-10 19:43:55 -0700699 ZipEntry* entry = zipFile->getEntryByName("resources.arsc");
700 if (!entry) {
701 Logger::error(source) << "missing 'resources.arsc'." << std::endl;
702 return false;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700703 }
704
Adam Lesinski24aad162015-04-24 19:19:30 -0700705 std::unique_ptr<void, DeleteMalloc> uncompressedData = std::unique_ptr<void, DeleteMalloc>(
706 zipFile->uncompress(entry));
Adam Lesinski769de982015-04-10 19:43:55 -0700707 assert(uncompressedData);
708
Adam Lesinski24aad162015-04-24 19:19:30 -0700709 BinaryResourceParser parser(table, resolver, source, uncompressedData.get(),
Adam Lesinski769de982015-04-10 19:43:55 -0700710 entry->getUncompressedLen());
711 if (!parser.parse()) {
Adam Lesinski769de982015-04-10 19:43:55 -0700712 return false;
713 }
Adam Lesinski769de982015-04-10 19:43:55 -0700714
715 // Keep track of where this table came from.
Adam Lesinski24aad162015-04-24 19:19:30 -0700716 apkFiles[table] = StaticLibraryData{ source, std::move(zipFile) };
Adam Lesinski769de982015-04-10 19:43:55 -0700717
718 // Add the package to the set of linked packages.
719 linkedPackages.insert(table->getPackage());
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700720 }
721
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700722 std::queue<LinkItem> linkQueue;
Adam Lesinski769de982015-04-10 19:43:55 -0700723 for (auto& p : apkFiles) {
724 const std::shared_ptr<ResourceTable>& inTable = p.first;
725
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700726 // Collect all FileReferences and add them to the queue for processing.
Adam Lesinski24aad162015-04-24 19:19:30 -0700727 addApkFilesToLinkQueue(options.appInfo.package, p.second.source, inTable, p.second.apk,
728 &linkQueue);
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700729
730 // Merge the tables.
Adam Lesinski769de982015-04-10 19:43:55 -0700731 if (!outTable->merge(std::move(*inTable))) {
732 return false;
733 }
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700734 }
735
Adam Lesinski330edcd2015-05-04 17:40:56 -0700736 // Version all styles referencing attributes outside of their specified SDK version.
737 if (options.versionStylesAndLayouts) {
738 versionStylesForCompat(outTable);
739 }
740
Adam Lesinski769de982015-04-10 19:43:55 -0700741 {
742 // Now that everything is merged, let's link it.
Adam Lesinski330edcd2015-05-04 17:40:56 -0700743 Linker::Options linkerOptions;
744 if (options.packageType == AaptOptions::PackageType::StaticLibrary) {
745 linkerOptions.linkResourceIds = false;
746 }
747 Linker linker(outTable, resolver, linkerOptions);
Adam Lesinski769de982015-04-10 19:43:55 -0700748 if (!linker.linkAndValidate()) {
749 return false;
750 }
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700751
Adam Lesinski769de982015-04-10 19:43:55 -0700752 // Verify that all symbols exist.
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700753 const auto& unresolvedRefs = linker.getUnresolvedReferences();
754 if (!unresolvedRefs.empty()) {
755 for (const auto& entry : unresolvedRefs) {
756 for (const auto& source : entry.second) {
757 Logger::error(source) << "unresolved symbol '" << entry.first << "'."
758 << std::endl;
759 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800760 }
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700761 return false;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800762 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800763 }
764
Adam Lesinski769de982015-04-10 19:43:55 -0700765 // Open the output APK file for writing.
766 ZipFile outApk;
767 if (outApk.open(options.output.path.data(), kOpenFlags) != android::NO_ERROR) {
768 Logger::error(options.output) << "failed to open: " << strerror(errno) << std::endl;
769 return false;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700770 }
771
Adam Lesinski330edcd2015-05-04 17:40:56 -0700772 android::ResTable binTable;
773 if (!compileManifest(options, resolver, binTable, &outApk)) {
Adam Lesinski769de982015-04-10 19:43:55 -0700774 return false;
775 }
776
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700777 for (; !linkQueue.empty(); linkQueue.pop()) {
778 const LinkItem& item = linkQueue.front();
Adam Lesinski769de982015-04-10 19:43:55 -0700779
Adam Lesinski24aad162015-04-24 19:19:30 -0700780 assert(!item.originalPackage.empty());
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700781 ZipEntry* entry = item.apk->getEntryByName(item.originalPath.data());
782 if (!entry) {
783 Logger::error(item.source) << "failed to find '" << item.originalPath << "'."
784 << std::endl;
785 return false;
786 }
Adam Lesinski769de982015-04-10 19:43:55 -0700787
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700788 if (util::stringEndsWith<char>(item.originalPath, ".xml")) {
789 void* uncompressedData = item.apk->uncompress(entry);
790 assert(uncompressedData);
Adam Lesinski769de982015-04-10 19:43:55 -0700791
Adam Lesinski39c353a2015-05-14 17:58:14 -0700792 if (!linkXml(options, outTable, resolver, item, uncompressedData,
793 entry->getUncompressedLen(), &outApk, &linkQueue)) {
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700794 Logger::error(options.output) << "failed to link '" << item.originalPath << "'."
795 << std::endl;
796 return false;
Adam Lesinski769de982015-04-10 19:43:55 -0700797 }
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700798 } else {
799 if (outApk.add(item.apk, entry, buildFileReference(item).data(), 0, nullptr) !=
800 android::NO_ERROR) {
801 Logger::error(options.output) << "failed to copy '" << item.originalPath << "'."
802 << std::endl;
803 return false;
Adam Lesinski769de982015-04-10 19:43:55 -0700804 }
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700805 }
806 }
807
808 // Generate the Java class file.
Adam Lesinski769de982015-04-10 19:43:55 -0700809 if (options.generateJavaClass) {
Adam Lesinski6d8e4c42015-05-01 14:47:28 -0700810 JavaClassGenerator::Options javaOptions;
811 if (options.packageType == AaptOptions::PackageType::StaticLibrary) {
812 javaOptions.useFinal = false;
813 }
814 JavaClassGenerator generator(outTable, javaOptions);
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700815
Adam Lesinski769de982015-04-10 19:43:55 -0700816 for (const std::u16string& package : linkedPackages) {
817 Source outPath = options.generateJavaClass.value();
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800818
Adam Lesinski769de982015-04-10 19:43:55 -0700819 // Build the output directory from the package name.
820 // Eg. com.android.app -> com/android/app
821 const std::string packageUtf8 = util::utf16ToUtf8(package);
822 for (StringPiece part : util::tokenize<char>(packageUtf8, '.')) {
823 appendPath(&outPath.path, part);
824 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800825
Adam Lesinski769de982015-04-10 19:43:55 -0700826 if (!mkdirs(outPath.path)) {
827 Logger::error(outPath) << strerror(errno) << std::endl;
828 return false;
829 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800830
Adam Lesinski769de982015-04-10 19:43:55 -0700831 appendPath(&outPath.path, "R.java");
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800832
Adam Lesinski769de982015-04-10 19:43:55 -0700833 if (options.verbose) {
834 Logger::note(outPath) << "writing Java symbols." << std::endl;
835 }
836
837 std::ofstream fout(outPath.path);
838 if (!fout) {
839 Logger::error(outPath) << strerror(errno) << std::endl;
840 return false;
841 }
842
843 if (!generator.generate(package, fout)) {
844 Logger::error(outPath) << generator.getError() << "." << std::endl;
845 return false;
846 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800847 }
848 }
849
Adam Lesinski24aad162015-04-24 19:19:30 -0700850 outTable->getValueStringPool().prune();
851 outTable->getValueStringPool().sort(
852 [](const StringPool::Entry& a, const StringPool::Entry& b) -> bool {
853 if (a.context.priority < b.context.priority) {
854 return true;
855 }
856
857 if (a.context.priority > b.context.priority) {
858 return false;
859 }
860 return a.value < b.value;
861 });
862
863
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700864 // Flatten the resource table.
Adam Lesinski769de982015-04-10 19:43:55 -0700865 TableFlattener::Options flattenerOptions;
Adam Lesinski330edcd2015-05-04 17:40:56 -0700866 if (options.packageType != AaptOptions::PackageType::StaticLibrary) {
867 flattenerOptions.useExtendedChunks = false;
Adam Lesinski6d8e4c42015-05-01 14:47:28 -0700868 }
869
Adam Lesinski769de982015-04-10 19:43:55 -0700870 if (!writeResourceTable(options, outTable, flattenerOptions, &outApk)) {
871 return false;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800872 }
Adam Lesinski769de982015-04-10 19:43:55 -0700873
874 outApk.flush();
875 return true;
876}
877
878bool compile(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table,
Adam Lesinski24aad162015-04-24 19:19:30 -0700879 const std::shared_ptr<IResolver>& resolver) {
Adam Lesinski769de982015-04-10 19:43:55 -0700880 std::queue<CompileItem> compileQueue;
881 bool error = false;
882
883 // Compile all the resource files passed in on the command line.
884 for (const Source& source : options.input) {
885 // Need to parse the resource type/config/filename.
886 Maybe<ResourcePathData> maybePathData = extractResourcePathData(source);
887 if (!maybePathData) {
888 return false;
889 }
890
891 const ResourcePathData& pathData = maybePathData.value();
892 if (pathData.resourceDir == u"values") {
893 // The file is in the values directory, which means its contents will
894 // go into the resource table.
895 if (options.verbose) {
896 Logger::note(source) << "compiling values." << std::endl;
897 }
898
899 error |= !compileValues(table, source, pathData.config);
900 } else {
901 // The file is in a directory like 'layout' or 'drawable'. Find out
902 // the type.
903 const ResourceType* type = parseResourceType(pathData.resourceDir);
904 if (!type) {
905 Logger::error(source) << "invalid resource type '" << pathData.resourceDir << "'."
906 << std::endl;
907 return false;
908 }
909
910 compileQueue.push(CompileItem{
Adam Lesinski769de982015-04-10 19:43:55 -0700911 ResourceName{ table->getPackage(), *type, pathData.name },
912 pathData.config,
Adam Lesinski39c353a2015-05-14 17:58:14 -0700913 source,
Adam Lesinski769de982015-04-10 19:43:55 -0700914 pathData.extension
915 });
916 }
917 }
918
919 if (error) {
920 return false;
921 }
Adam Lesinski769de982015-04-10 19:43:55 -0700922 // Open the output APK file for writing.
923 ZipFile outApk;
924 if (outApk.open(options.output.path.data(), kOpenFlags) != android::NO_ERROR) {
925 Logger::error(options.output) << "failed to open: " << strerror(errno) << std::endl;
926 return false;
927 }
928
929 // Compile each file.
930 for (; !compileQueue.empty(); compileQueue.pop()) {
931 const CompileItem& item = compileQueue.front();
932
933 // Add the file name to the resource table.
934 error |= !addFileReference(table, item);
935
936 if (item.extension == "xml") {
Adam Lesinski330edcd2015-05-04 17:40:56 -0700937 error |= !compileXml(options, table, item, &outApk);
Adam Lesinski769de982015-04-10 19:43:55 -0700938 } else if (item.extension == "png" || item.extension == "9.png") {
939 error |= !compilePng(options, item, &outApk);
940 } else {
941 error |= !copyFile(options, item, &outApk);
942 }
943 }
944
945 if (error) {
946 return false;
947 }
948
949 // Link and assign resource IDs.
Adam Lesinski330edcd2015-05-04 17:40:56 -0700950 Linker linker(table, resolver, {});
Adam Lesinski769de982015-04-10 19:43:55 -0700951 if (!linker.linkAndValidate()) {
952 return false;
953 }
954
955 // Flatten the resource table.
956 if (!writeResourceTable(options, table, {}, &outApk)) {
957 return false;
958 }
959
960 outApk.flush();
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800961 return true;
962}
963
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700964bool loadAppInfo(const Source& source, AppInfo* outInfo) {
965 std::ifstream ifs(source.path, std::ifstream::in | std::ifstream::binary);
966 if (!ifs) {
967 Logger::error(source) << strerror(errno) << std::endl;
968 return false;
969 }
970
971 ManifestParser parser;
972 std::shared_ptr<XmlPullParser> pullParser = std::make_shared<SourceXmlPullParser>(ifs);
973 return parser.parse(source, pullParser, outInfo);
974}
975
976static void printCommandsAndDie() {
977 std::cerr << "The following commands are supported:" << std::endl << std::endl;
978 std::cerr << "compile compiles a subset of resources" << std::endl;
979 std::cerr << "link links together compiled resources and libraries" << std::endl;
Adam Lesinski330edcd2015-05-04 17:40:56 -0700980 std::cerr << "dump dumps resource contents to to standard out" << std::endl;
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700981 std::cerr << std::endl;
982 std::cerr << "run aapt2 with one of the commands and the -h flag for extra details."
983 << std::endl;
984 exit(1);
985}
986
987static AaptOptions prepareArgs(int argc, char** argv) {
988 if (argc < 2) {
989 std::cerr << "no command specified." << std::endl << std::endl;
990 printCommandsAndDie();
991 }
992
993 const StringPiece command(argv[1]);
994 argc -= 2;
995 argv += 2;
996
997 AaptOptions options;
998
999 if (command == "--version" || command == "version") {
1000 std::cout << kAaptVersionStr << std::endl;
1001 exit(0);
1002 } else if (command == "link") {
1003 options.phase = AaptOptions::Phase::Link;
1004 } else if (command == "compile") {
1005 options.phase = AaptOptions::Phase::Compile;
Adam Lesinski330edcd2015-05-04 17:40:56 -07001006 } else if (command == "dump") {
1007 options.phase = AaptOptions::Phase::Dump;
1008 } else if (command == "dump-style-graph") {
1009 options.phase = AaptOptions::Phase::DumpStyleGraph;
Adam Lesinskid5c4f872015-04-21 13:56:10 -07001010 } else {
1011 std::cerr << "invalid command '" << command << "'." << std::endl << std::endl;
1012 printCommandsAndDie();
1013 }
1014
Adam Lesinski6d8e4c42015-05-01 14:47:28 -07001015 bool isStaticLib = false;
Adam Lesinski330edcd2015-05-04 17:40:56 -07001016 if (options.phase == AaptOptions::Phase::Compile ||
1017 options.phase == AaptOptions::Phase::Link) {
1018 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 } else if (options.phase == AaptOptions::Phase::Link) {
1024 flag::requiredFlag("--manifest", "AndroidManifest.xml of your app",
1025 [&options](const StringPiece& arg) {
1026 options.manifest = Source{ arg.toString() };
1027 });
Adam Lesinskid5c4f872015-04-21 13:56:10 -07001028
Adam Lesinski330edcd2015-05-04 17:40:56 -07001029 flag::optionalFlag("-I", "add an Android APK to link against",
1030 [&options](const StringPiece& arg) {
1031 options.libraries.push_back(Source{ arg.toString() });
1032 });
Adam Lesinskid5c4f872015-04-21 13:56:10 -07001033
Adam Lesinski330edcd2015-05-04 17:40:56 -07001034 flag::optionalFlag("--java", "directory in which to generate R.java",
1035 [&options](const StringPiece& arg) {
1036 options.generateJavaClass = Source{ arg.toString() };
1037 });
Adam Lesinskid5c4f872015-04-21 13:56:10 -07001038
Adam Lesinski330edcd2015-05-04 17:40:56 -07001039 flag::optionalSwitch("--static-lib", "generate a static Android library", true,
1040 &isStaticLib);
1041
1042 flag::optionalFlag("--binding", "Output directory for binding XML files",
1043 [&options](const StringPiece& arg) {
1044 options.bindingOutput = Source{ arg.toString() };
1045 });
1046 flag::optionalSwitch("--no-version", "Disables automatic style and layout versioning",
1047 false, &options.versionStylesAndLayouts);
1048 }
1049
1050 // Common flags for all steps.
1051 flag::requiredFlag("-o", "Output path", [&options](const StringPiece& arg) {
1052 options.output = Source{ arg.toString() };
1053 });
Adam Lesinskid13fb242015-05-12 20:40:48 -07001054 } else if (options.phase == AaptOptions::Phase::DumpStyleGraph) {
1055 flag::requiredFlag("--style", "Name of the style to dump",
1056 [&options](const StringPiece& arg, std::string* outError) -> bool {
1057 Reference styleReference;
1058 if (!ResourceParser::parseStyleParentReference(util::utf8ToUtf16(arg),
1059 &styleReference, outError)) {
1060 return false;
1061 }
1062 options.dumpStyleTarget = styleReference.name;
1063 return true;
1064 });
Adam Lesinskid5c4f872015-04-21 13:56:10 -07001065 }
1066
Adam Lesinskid5c4f872015-04-21 13:56:10 -07001067 bool help = false;
1068 flag::optionalSwitch("-v", "enables verbose logging", true, &options.verbose);
1069 flag::optionalSwitch("-h", "displays this help menu", true, &help);
1070
1071 // Build the command string for output (eg. "aapt2 compile").
1072 std::string fullCommand = "aapt2";
1073 fullCommand += " ";
1074 fullCommand += command.toString();
1075
1076 // Actually read the command line flags.
1077 flag::parse(argc, argv, fullCommand);
1078
1079 if (help) {
1080 flag::usageAndDie(fullCommand);
1081 }
1082
Adam Lesinski6d8e4c42015-05-01 14:47:28 -07001083 if (isStaticLib) {
1084 options.packageType = AaptOptions::PackageType::StaticLibrary;
1085 }
1086
Adam Lesinskid5c4f872015-04-21 13:56:10 -07001087 // Copy all the remaining arguments.
1088 for (const std::string& arg : flag::getArgs()) {
1089 options.input.push_back(Source{ arg });
1090 }
1091 return options;
1092}
1093
Adam Lesinski330edcd2015-05-04 17:40:56 -07001094static bool doDump(const AaptOptions& options) {
1095 for (const Source& source : options.input) {
1096 std::unique_ptr<ZipFile> zipFile = util::make_unique<ZipFile>();
1097 if (zipFile->open(source.path.data(), ZipFile::kOpenReadOnly) != android::NO_ERROR) {
1098 Logger::error(source) << "failed to open: " << strerror(errno) << std::endl;
1099 return false;
1100 }
1101
1102 std::shared_ptr<ResourceTable> table = std::make_shared<ResourceTable>();
1103 std::shared_ptr<ResourceTableResolver> resolver =
1104 std::make_shared<ResourceTableResolver>(
1105 table, std::vector<std::shared_ptr<const android::AssetManager>>());
1106
1107 ZipEntry* entry = zipFile->getEntryByName("resources.arsc");
1108 if (!entry) {
1109 Logger::error(source) << "missing 'resources.arsc'." << std::endl;
1110 return false;
1111 }
1112
1113 std::unique_ptr<void, DeleteMalloc> uncompressedData = std::unique_ptr<void, DeleteMalloc>(
1114 zipFile->uncompress(entry));
1115 assert(uncompressedData);
1116
1117 BinaryResourceParser parser(table, resolver, source, uncompressedData.get(),
1118 entry->getUncompressedLen());
1119 if (!parser.parse()) {
1120 return false;
1121 }
1122
1123 if (options.phase == AaptOptions::Phase::Dump) {
1124 Debug::printTable(table);
1125 } else if (options.phase == AaptOptions::Phase::DumpStyleGraph) {
Adam Lesinskid13fb242015-05-12 20:40:48 -07001126 Debug::printStyleGraph(table, options.dumpStyleTarget);
Adam Lesinski330edcd2015-05-04 17:40:56 -07001127 }
1128 }
1129 return true;
1130}
1131
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001132int main(int argc, char** argv) {
1133 Logger::setLog(std::make_shared<Log>(std::cerr, std::cerr));
Adam Lesinski98aa3ad2015-04-06 11:46:52 -07001134 AaptOptions options = prepareArgs(argc, argv);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001135
Adam Lesinski330edcd2015-05-04 17:40:56 -07001136 if (options.phase == AaptOptions::Phase::Dump ||
1137 options.phase == AaptOptions::Phase::DumpStyleGraph) {
1138 if (!doDump(options)) {
1139 return 1;
1140 }
1141 return 0;
1142 }
1143
Adam Lesinski98aa3ad2015-04-06 11:46:52 -07001144 // If we specified a manifest, go ahead and load the package name from the manifest.
1145 if (!options.manifest.path.empty()) {
1146 if (!loadAppInfo(options.manifest, &options.appInfo)) {
1147 return false;
1148 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001149 }
1150
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001151 // Verify we have some common options set.
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001152 if (options.appInfo.package.empty()) {
1153 Logger::error() << "no package name specified." << std::endl;
1154 return false;
1155 }
1156
Adam Lesinski98aa3ad2015-04-06 11:46:52 -07001157 // Every phase needs a resource table.
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001158 std::shared_ptr<ResourceTable> table = std::make_shared<ResourceTable>();
1159 table->setPackage(options.appInfo.package);
1160 if (options.appInfo.package == u"android") {
1161 table->setPackageId(0x01);
1162 } else {
1163 table->setPackageId(0x7f);
1164 }
1165
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001166 // Load the included libraries.
Adam Lesinski330edcd2015-05-04 17:40:56 -07001167 std::vector<std::shared_ptr<const android::AssetManager>> sources;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001168 for (const Source& source : options.libraries) {
Adam Lesinski330edcd2015-05-04 17:40:56 -07001169 std::shared_ptr<android::AssetManager> assetManager =
1170 std::make_shared<android::AssetManager>();
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001171 int32_t cookie;
Adam Lesinski330edcd2015-05-04 17:40:56 -07001172 if (!assetManager->addAssetPath(android::String8(source.path.data()), &cookie)) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001173 Logger::error(source) << "failed to load library." << std::endl;
1174 return false;
1175 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001176
Adam Lesinski330edcd2015-05-04 17:40:56 -07001177 if (cookie == 0) {
1178 Logger::error(source) << "failed to load library." << std::endl;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001179 return false;
1180 }
Adam Lesinski330edcd2015-05-04 17:40:56 -07001181 sources.push_back(assetManager);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001182 }
1183
1184 // Make the resolver that will cache IDs for us.
Adam Lesinski24aad162015-04-24 19:19:30 -07001185 std::shared_ptr<ResourceTableResolver> resolver = std::make_shared<ResourceTableResolver>(
Adam Lesinski330edcd2015-05-04 17:40:56 -07001186 table, sources);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001187
Adam Lesinski769de982015-04-10 19:43:55 -07001188 if (options.phase == AaptOptions::Phase::Compile) {
1189 if (!compile(options, table, resolver)) {
1190 Logger::error() << "aapt exiting with failures." << std::endl;
1191 return 1;
1192 }
1193 } else if (options.phase == AaptOptions::Phase::Link) {
1194 if (!link(options, table, resolver)) {
1195 Logger::error() << "aapt exiting with failures." << std::endl;
1196 return 1;
1197 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001198 }
1199 return 0;
1200}