blob: 025ede559acdebe469cf454eabaa8352b3099c9e [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 Lesinski330edcd2015-05-04 17:40:56 -070022#include "Debug.h"
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080023#include "Files.h"
Adam Lesinski98aa3ad2015-04-06 11:46:52 -070024#include "Flag.h"
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080025#include "JavaClassGenerator.h"
26#include "Linker.h"
27#include "ManifestParser.h"
28#include "ManifestValidator.h"
Adam Lesinskid5c4f872015-04-21 13:56:10 -070029#include "NameMangler.h"
Adam Lesinski98aa3ad2015-04-06 11:46:52 -070030#include "Png.h"
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080031#include "ResourceParser.h"
32#include "ResourceTable.h"
Adam Lesinski24aad162015-04-24 19:19:30 -070033#include "ResourceTableResolver.h"
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080034#include "ResourceValues.h"
35#include "SdkConstants.h"
36#include "SourceXmlPullParser.h"
37#include "StringPiece.h"
38#include "TableFlattener.h"
39#include "Util.h"
40#include "XmlFlattener.h"
Adam Lesinski769de982015-04-10 19:43:55 -070041#include "ZipFile.h"
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080042
43#include <algorithm>
44#include <androidfw/AssetManager.h>
45#include <cstdlib>
46#include <dirent.h>
47#include <errno.h>
48#include <fstream>
49#include <iostream>
50#include <sstream>
51#include <sys/stat.h>
Adam Lesinski769de982015-04-10 19:43:55 -070052#include <unordered_set>
Adam Lesinski98aa3ad2015-04-06 11:46:52 -070053#include <utils/Errors.h>
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080054
Adam Lesinski5886a922015-04-15 20:29:22 -070055constexpr const char* kAaptVersionStr = "2.0-alpha";
56
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080057using namespace aapt;
58
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080059/**
60 * Collect files from 'root', filtering out any files that do not
61 * match the FileFilter 'filter'.
62 */
Adam Lesinski98aa3ad2015-04-06 11:46:52 -070063bool walkTree(const Source& root, const FileFilter& filter,
64 std::vector<Source>* outEntries) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080065 bool error = false;
66
Adam Lesinski98aa3ad2015-04-06 11:46:52 -070067 for (const std::string& dirName : listFiles(root.path)) {
68 std::string dir = root.path;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080069 appendPath(&dir, dirName);
70
71 FileType ft = getFileType(dir);
72 if (!filter(dirName, ft)) {
73 continue;
74 }
75
76 if (ft != FileType::kDirectory) {
77 continue;
78 }
79
80 for (const std::string& fileName : listFiles(dir)) {
81 std::string file(dir);
82 appendPath(&file, fileName);
83
84 FileType ft = getFileType(file);
85 if (!filter(fileName, ft)) {
86 continue;
87 }
88
89 if (ft != FileType::kRegular) {
Adam Lesinski98aa3ad2015-04-06 11:46:52 -070090 Logger::error(Source{ file }) << "not a regular file." << std::endl;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080091 error = true;
92 continue;
93 }
Adam Lesinski98aa3ad2015-04-06 11:46:52 -070094 outEntries->push_back(Source{ file });
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080095 }
96 }
97 return !error;
98}
99
Adam Lesinski769de982015-04-10 19:43:55 -0700100void versionStylesForCompat(const std::shared_ptr<ResourceTable>& table) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800101 for (auto& type : *table) {
102 if (type->type != ResourceType::kStyle) {
103 continue;
104 }
105
106 for (auto& entry : type->entries) {
107 // Add the versioned styles we want to create
108 // here. They are added to the table after
109 // iterating over the original set of styles.
110 //
111 // A stack is used since auto-generated styles
112 // from later versions should override
113 // auto-generated styles from earlier versions.
114 // Iterating over the styles is done in order,
115 // so we will always visit sdkVersions from smallest
116 // to largest.
117 std::stack<ResourceConfigValue> addStack;
118
119 for (ResourceConfigValue& configValue : entry->values) {
120 visitFunc<Style>(*configValue.value, [&](Style& style) {
121 // Collect which entries we've stripped and the smallest
122 // SDK level which was stripped.
123 size_t minSdkStripped = std::numeric_limits<size_t>::max();
124 std::vector<Style::Entry> stripped;
125
126 // Iterate over the style's entries and erase/record the
127 // attributes whose SDK level exceeds the config's sdkVersion.
128 auto iter = style.entries.begin();
129 while (iter != style.entries.end()) {
130 if (iter->key.name.package == u"android") {
131 size_t sdkLevel = findAttributeSdkLevel(iter->key.name.entry);
132 if (sdkLevel > 1 && sdkLevel > configValue.config.sdkVersion) {
133 // Record that we are about to strip this.
134 stripped.emplace_back(std::move(*iter));
135 minSdkStripped = std::min(minSdkStripped, sdkLevel);
136
137 // Erase this from this style.
138 iter = style.entries.erase(iter);
139 continue;
140 }
141 }
142 ++iter;
143 }
144
145 if (!stripped.empty()) {
146 // We have stripped attributes, so let's create a new style to hold them.
147 ConfigDescription versionConfig(configValue.config);
148 versionConfig.sdkVersion = minSdkStripped;
149
150 ResourceConfigValue value = {
151 versionConfig,
152 configValue.source,
153 {},
154
155 // Create a copy of the original style.
Adam Lesinski769de982015-04-10 19:43:55 -0700156 std::unique_ptr<Value>(configValue.value->clone(
157 &table->getValueStringPool()))
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800158 };
159
160 Style& newStyle = static_cast<Style&>(*value.value);
161
162 // Move the recorded stripped attributes into this new style.
163 std::move(stripped.begin(), stripped.end(),
164 std::back_inserter(newStyle.entries));
165
166 // We will add this style to the table later. If we do it now, we will
167 // mess up iteration.
168 addStack.push(std::move(value));
169 }
170 });
171 }
172
173 auto comparator =
174 [](const ResourceConfigValue& lhs, const ConfigDescription& rhs) -> bool {
175 return lhs.config < rhs;
176 };
177
178 while (!addStack.empty()) {
179 ResourceConfigValue& value = addStack.top();
180 auto iter = std::lower_bound(entry->values.begin(), entry->values.end(),
181 value.config, comparator);
182 if (iter == entry->values.end() || iter->config != value.config) {
183 entry->values.insert(iter, std::move(value));
184 }
185 addStack.pop();
186 }
187 }
188 }
189}
190
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700191struct CompileItem {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800192 Source source;
193 ResourceName name;
194 ConfigDescription config;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700195 std::string extension;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800196};
197
Adam Lesinski769de982015-04-10 19:43:55 -0700198struct LinkItem {
199 Source source;
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700200 ResourceName name;
201 ConfigDescription config;
202 std::string originalPath;
203 ZipFile* apk;
Adam Lesinski24aad162015-04-24 19:19:30 -0700204 std::u16string originalPackage;
Adam Lesinski769de982015-04-10 19:43:55 -0700205};
206
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700207template <typename TChar>
208static BasicStringPiece<TChar> getExtension(const BasicStringPiece<TChar>& str) {
209 auto iter = std::find(str.begin(), str.end(), static_cast<TChar>('.'));
210 if (iter == str.end()) {
211 return BasicStringPiece<TChar>();
Adam Lesinski769de982015-04-10 19:43:55 -0700212 }
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700213 size_t offset = (iter - str.begin()) + 1;
214 return str.substr(offset, str.size() - offset);
215}
216
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700217std::string buildFileReference(const ResourceNameRef& name, const ConfigDescription& config,
218 const StringPiece& extension) {
219 std::stringstream path;
220 path << "res/" << name.type;
221 if (config != ConfigDescription{}) {
222 path << "-" << config;
223 }
224 path << "/" << util::utf16ToUtf8(name.entry);
225 if (!extension.empty()) {
226 path << "." << extension;
227 }
Adam Lesinski769de982015-04-10 19:43:55 -0700228 return path.str();
229}
230
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700231std::string buildFileReference(const CompileItem& item) {
232 return buildFileReference(item.name, item.config, item.extension);
233}
234
235std::string buildFileReference(const LinkItem& item) {
236 return buildFileReference(item.name, item.config, getExtension<char>(item.originalPath));
237}
238
Adam Lesinski769de982015-04-10 19:43:55 -0700239bool addFileReference(const std::shared_ptr<ResourceTable>& table, const CompileItem& item) {
240 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
302bool compileXml(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table,
Adam Lesinski330edcd2015-05-04 17:40:56 -0700303 const CompileItem& item, ZipFile* outApk) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800304 std::ifstream in(item.source.path, std::ifstream::binary);
305 if (!in) {
306 Logger::error(item.source) << strerror(errno) << std::endl;
307 return false;
308 }
309
Adam Lesinski4d3a9872015-04-09 19:53:22 -0700310 BigBuffer outBuffer(1024);
Adam Lesinski769de982015-04-10 19:43:55 -0700311
312 // No resolver, since we are not compiling attributes here.
313 XmlFlattener flattener(table, {});
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800314
Adam Lesinski769de982015-04-10 19:43:55 -0700315 XmlFlattener::Options xmlOptions;
Adam Lesinski24aad162015-04-24 19:19:30 -0700316 xmlOptions.defaultPackage = table->getPackage();
Adam Lesinski330edcd2015-05-04 17:40:56 -0700317 xmlOptions.keepRawValues = true;
318
319 std::shared_ptr<XmlPullParser> parser = std::make_shared<SourceXmlPullParser>(in);
320
321 Maybe<size_t> minStrippedSdk = flattener.flatten(item.source, parser, &outBuffer,
322 xmlOptions);
323 if (!minStrippedSdk) {
324 return false;
325 }
326
327 // Write the resulting compiled XML file to the output APK.
328 if (outApk->add(outBuffer, buildFileReference(item).data(), ZipEntry::kCompressStored,
329 nullptr) != android::NO_ERROR) {
330 Logger::error(options.output) << "failed to write compiled '" << item.source
331 << "' to apk." << std::endl;
332 return false;
333 }
334 return true;
335}
336
337bool linkXml(const AaptOptions& options, const std::shared_ptr<IResolver>& resolver,
338 const LinkItem& item, const void* data, size_t dataLen, ZipFile* outApk,
339 std::queue<LinkItem>* outQueue) {
340 std::shared_ptr<android::ResXMLTree> tree = std::make_shared<android::ResXMLTree>();
341 if (tree->setTo(data, dataLen, false) != android::NO_ERROR) {
342 return false;
343 }
344
345 std::shared_ptr<XmlPullParser> parser = std::make_shared<BinaryXmlPullParser>(tree);
346
347 BigBuffer outBuffer(1024);
348 XmlFlattener flattener({}, resolver);
349
350 XmlFlattener::Options xmlOptions;
351 xmlOptions.defaultPackage = item.originalPackage;
352
353 if (options.packageType == AaptOptions::PackageType::StaticLibrary) {
354 xmlOptions.keepRawValues = true;
355 }
Adam Lesinski24aad162015-04-24 19:19:30 -0700356
Adam Lesinski5886a922015-04-15 20:29:22 -0700357 if (options.versionStylesAndLayouts) {
358 // We strip attributes that do not belong in this version of the resource.
359 // Non-version qualified resources have an implicit version 1 requirement.
360 xmlOptions.maxSdkAttribute = item.config.sdkVersion ? item.config.sdkVersion : 1;
361 }
Adam Lesinski769de982015-04-10 19:43:55 -0700362
363 std::shared_ptr<BindingXmlPullParser> binding;
Adam Lesinski769de982015-04-10 19:43:55 -0700364 if (item.name.type == ResourceType::kLayout) {
365 // Layouts may have defined bindings, so we need to make sure they get processed.
366 binding = std::make_shared<BindingXmlPullParser>(parser);
367 parser = binding;
368 }
369
Adam Lesinski330edcd2015-05-04 17:40:56 -0700370 Maybe<size_t> minStrippedSdk = flattener.flatten(item.source, parser, &outBuffer,
371 xmlOptions);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800372 if (!minStrippedSdk) {
373 return false;
374 }
375
376 if (minStrippedSdk.value() > 0) {
377 // Something was stripped, so let's generate a new file
378 // with the version of the smallest SDK version stripped.
Adam Lesinski330edcd2015-05-04 17:40:56 -0700379 LinkItem newWork = item;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800380 newWork.config.sdkVersion = minStrippedSdk.value();
Adam Lesinski769de982015-04-10 19:43:55 -0700381 outQueue->push(newWork);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800382 }
383
Adam Lesinski330edcd2015-05-04 17:40:56 -0700384 if (outApk->add(outBuffer, buildFileReference(item).data(), ZipEntry::kCompressDeflated,
Adam Lesinski769de982015-04-10 19:43:55 -0700385 nullptr) != android::NO_ERROR) {
Adam Lesinski330edcd2015-05-04 17:40:56 -0700386 Logger::error(options.output) << "failed to write linked file '"
387 << buildFileReference(item) << "' to apk." << std::endl;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800388 return false;
389 }
390
Adam Lesinski769de982015-04-10 19:43:55 -0700391 if (binding && !options.bindingOutput.path.empty()) {
392 // We generated a binding xml file, write it out.
393 Source bindingOutput = options.bindingOutput;
394 appendPath(&bindingOutput.path, buildFileReference(item));
Adam Lesinski4d3a9872015-04-09 19:53:22 -0700395
Adam Lesinski769de982015-04-10 19:43:55 -0700396 if (!mkdirs(bindingOutput.path)) {
397 Logger::error(bindingOutput) << strerror(errno) << std::endl;
398 return false;
399 }
400
401 appendPath(&bindingOutput.path, "bind.xml");
402
Adam Lesinski4d3a9872015-04-09 19:53:22 -0700403 std::ofstream bout(bindingOutput.path);
404 if (!bout) {
405 Logger::error(bindingOutput) << strerror(errno) << std::endl;
406 return false;
407 }
408
409 if (!binding->writeToFile(bout)) {
410 Logger::error(bindingOutput) << strerror(errno) << std::endl;
411 return false;
412 }
413 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800414 return true;
415}
416
Adam Lesinski769de982015-04-10 19:43:55 -0700417bool compilePng(const AaptOptions& options, const CompileItem& item, ZipFile* outApk) {
418 std::ifstream in(item.source.path, std::ifstream::binary);
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700419 if (!in) {
Adam Lesinski769de982015-04-10 19:43:55 -0700420 Logger::error(item.source) << strerror(errno) << std::endl;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700421 return false;
422 }
423
Adam Lesinski769de982015-04-10 19:43:55 -0700424 BigBuffer outBuffer(4096);
425 std::string err;
426 Png png;
427 if (!png.process(item.source, in, &outBuffer, {}, &err)) {
428 Logger::error(item.source) << err << std::endl;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700429 return false;
430 }
431
Adam Lesinski769de982015-04-10 19:43:55 -0700432 if (outApk->add(outBuffer, buildFileReference(item).data(), ZipEntry::kCompressStored,
433 nullptr) != android::NO_ERROR) {
434 Logger::error(options.output) << "failed to write compiled '" << item.source
435 << "' to apk." << std::endl;
436 return false;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700437 }
Adam Lesinski769de982015-04-10 19:43:55 -0700438 return true;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700439}
440
Adam Lesinski769de982015-04-10 19:43:55 -0700441bool copyFile(const AaptOptions& options, const CompileItem& item, ZipFile* outApk) {
442 if (outApk->add(item.source.path.data(), buildFileReference(item).data(),
443 ZipEntry::kCompressStored, nullptr) != android::NO_ERROR) {
444 Logger::error(options.output) << "failed to copy file '" << item.source << "' to apk."
445 << std::endl;
446 return false;
447 }
448 return true;
449}
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800450
Adam Lesinski330edcd2015-05-04 17:40:56 -0700451bool compileManifest(const AaptOptions& options, const std::shared_ptr<IResolver>& resolver,
452 const android::ResTable& table, ZipFile* outApk) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800453 if (options.verbose) {
Adam Lesinski769de982015-04-10 19:43:55 -0700454 Logger::note(options.manifest) << "compiling AndroidManifest.xml." << std::endl;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800455 }
456
457 std::ifstream in(options.manifest.path, std::ifstream::binary);
458 if (!in) {
459 Logger::error(options.manifest) << strerror(errno) << std::endl;
460 return false;
461 }
462
463 BigBuffer outBuffer(1024);
464 std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<SourceXmlPullParser>(in);
Adam Lesinski769de982015-04-10 19:43:55 -0700465 XmlFlattener flattener({}, resolver);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800466
Adam Lesinski24aad162015-04-24 19:19:30 -0700467 XmlFlattener::Options xmlOptions;
468 xmlOptions.defaultPackage = options.appInfo.package;
469 if (!flattener.flatten(options.manifest, xmlParser, &outBuffer, xmlOptions)) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800470 return false;
471 }
472
Adam Lesinski769de982015-04-10 19:43:55 -0700473 std::unique_ptr<uint8_t[]> data = util::copy(outBuffer);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800474
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700475 android::ResXMLTree tree;
Adam Lesinski769de982015-04-10 19:43:55 -0700476 if (tree.setTo(data.get(), outBuffer.size(), false) != android::NO_ERROR) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800477 return false;
478 }
479
Adam Lesinski330edcd2015-05-04 17:40:56 -0700480 ManifestValidator validator(table);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800481 if (!validator.validate(options.manifest, &tree)) {
482 return false;
483 }
484
Adam Lesinski769de982015-04-10 19:43:55 -0700485 if (outApk->add(data.get(), outBuffer.size(), "AndroidManifest.xml",
486 ZipEntry::kCompressStored, nullptr) != android::NO_ERROR) {
487 Logger::error(options.output) << "failed to write 'AndroidManifest.xml' to apk."
488 << std::endl;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800489 return false;
490 }
491 return true;
492}
493
Adam Lesinski769de982015-04-10 19:43:55 -0700494static bool compileValues(const std::shared_ptr<ResourceTable>& table, const Source& source,
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700495 const ConfigDescription& config) {
496 std::ifstream in(source.path, std::ifstream::binary);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800497 if (!in) {
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700498 Logger::error(source) << strerror(errno) << std::endl;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800499 return false;
500 }
501
502 std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<SourceXmlPullParser>(in);
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700503 ResourceParser parser(table, source, config, xmlParser);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800504 return parser.parse();
505}
506
507struct ResourcePathData {
508 std::u16string resourceDir;
509 std::u16string name;
510 std::string extension;
511 ConfigDescription config;
512};
513
514/**
515 * Resource file paths are expected to look like:
516 * [--/res/]type[-config]/name
517 */
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700518static Maybe<ResourcePathData> extractResourcePathData(const Source& source) {
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700519 // TODO(adamlesinski): Use Windows path separator on windows.
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800520 std::vector<std::string> parts = util::splitAndLowercase(source.path, '/');
521 if (parts.size() < 2) {
522 Logger::error(source) << "bad resource path." << std::endl;
523 return {};
524 }
525
526 std::string& dir = parts[parts.size() - 2];
527 StringPiece dirStr = dir;
528
529 ConfigDescription config;
530 size_t dashPos = dir.find('-');
531 if (dashPos != std::string::npos) {
532 StringPiece configStr = dirStr.substr(dashPos + 1, dir.size() - (dashPos + 1));
533 if (!ConfigDescription::parse(configStr, &config)) {
534 Logger::error(source)
535 << "invalid configuration '"
536 << configStr
537 << "'."
538 << std::endl;
539 return {};
540 }
541 dirStr = dirStr.substr(0, dashPos);
542 }
543
544 std::string& filename = parts[parts.size() - 1];
545 StringPiece name = filename;
546 StringPiece extension;
547 size_t dotPos = filename.find('.');
548 if (dotPos != std::string::npos) {
549 extension = name.substr(dotPos + 1, filename.size() - (dotPos + 1));
550 name = name.substr(0, dotPos);
551 }
552
553 return ResourcePathData{
554 util::utf8ToUtf16(dirStr),
555 util::utf8ToUtf16(name),
556 extension.toString(),
557 config
558 };
559}
560
Adam Lesinski769de982015-04-10 19:43:55 -0700561bool writeResourceTable(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table,
562 const TableFlattener::Options& flattenerOptions, ZipFile* outApk) {
563 if (table->begin() != table->end()) {
564 BigBuffer buffer(1024);
565 TableFlattener flattener(flattenerOptions);
566 if (!flattener.flatten(&buffer, *table)) {
567 Logger::error() << "failed to flatten resource table." << std::endl;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700568 return false;
569 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800570
Adam Lesinski769de982015-04-10 19:43:55 -0700571 if (options.verbose) {
572 Logger::note() << "Final resource table size=" << util::formatSize(buffer.size())
573 << std::endl;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700574 }
575
Adam Lesinski769de982015-04-10 19:43:55 -0700576 if (outApk->add(buffer, "resources.arsc", ZipEntry::kCompressStored, nullptr) !=
577 android::NO_ERROR) {
578 Logger::note(options.output) << "failed to store resource table." << std::endl;
579 return false;
580 }
581 }
582 return true;
583}
584
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700585/**
586 * For each FileReference in the table, adds a LinkItem to the link queue for processing.
587 */
588static void addApkFilesToLinkQueue(const std::u16string& package, const Source& source,
589 const std::shared_ptr<ResourceTable>& table,
590 const std::unique_ptr<ZipFile>& apk,
591 std::queue<LinkItem>* outLinkQueue) {
592 bool mangle = package != table->getPackage();
593 for (auto& type : *table) {
594 for (auto& entry : type->entries) {
595 ResourceName name = { package, type->type, entry->name };
596 if (mangle) {
597 NameMangler::mangle(table->getPackage(), &name.entry);
598 }
599
600 for (auto& value : entry->values) {
601 visitFunc<FileReference>(*value.value, [&](FileReference& ref) {
602 std::string pathUtf8 = util::utf16ToUtf8(*ref.path);
Adam Lesinski24aad162015-04-24 19:19:30 -0700603 Source newSource = source;
604 newSource.path += "/";
605 newSource.path += pathUtf8;
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700606 outLinkQueue->push(LinkItem{
Adam Lesinski24aad162015-04-24 19:19:30 -0700607 newSource, name, value.config, pathUtf8, apk.get(),
608 table->getPackage() });
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700609 // Now rewrite the file path.
610 if (mangle) {
611 ref.path = table->getValueStringPool().makeRef(util::utf8ToUtf16(
612 buildFileReference(name, value.config,
613 getExtension<char>(pathUtf8))));
614 }
615 });
616 }
617 }
618 }
619}
620
Adam Lesinski769de982015-04-10 19:43:55 -0700621static constexpr int kOpenFlags = ZipFile::kOpenCreate | ZipFile::kOpenTruncate |
622 ZipFile::kOpenReadWrite;
623
Adam Lesinski24aad162015-04-24 19:19:30 -0700624struct DeleteMalloc {
625 void operator()(void* ptr) {
626 free(ptr);
627 }
628};
629
630struct StaticLibraryData {
631 Source source;
632 std::unique_ptr<ZipFile> apk;
633};
634
Adam Lesinski769de982015-04-10 19:43:55 -0700635bool link(const AaptOptions& options, const std::shared_ptr<ResourceTable>& outTable,
Adam Lesinski330edcd2015-05-04 17:40:56 -0700636 const std::shared_ptr<IResolver>& resolver) {
Adam Lesinski24aad162015-04-24 19:19:30 -0700637 std::map<std::shared_ptr<ResourceTable>, StaticLibraryData> apkFiles;
Adam Lesinski769de982015-04-10 19:43:55 -0700638 std::unordered_set<std::u16string> linkedPackages;
639
640 // Populate the linkedPackages with our own.
641 linkedPackages.insert(options.appInfo.package);
642
643 // Load all APK files.
644 for (const Source& source : options.input) {
645 std::unique_ptr<ZipFile> zipFile = util::make_unique<ZipFile>();
646 if (zipFile->open(source.path.data(), ZipFile::kOpenReadOnly) != android::NO_ERROR) {
647 Logger::error(source) << "failed to open: " << strerror(errno) << std::endl;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700648 return false;
649 }
650
Adam Lesinski769de982015-04-10 19:43:55 -0700651 std::shared_ptr<ResourceTable> table = std::make_shared<ResourceTable>();
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700652
Adam Lesinski769de982015-04-10 19:43:55 -0700653 ZipEntry* entry = zipFile->getEntryByName("resources.arsc");
654 if (!entry) {
655 Logger::error(source) << "missing 'resources.arsc'." << std::endl;
656 return false;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700657 }
658
Adam Lesinski24aad162015-04-24 19:19:30 -0700659 std::unique_ptr<void, DeleteMalloc> uncompressedData = std::unique_ptr<void, DeleteMalloc>(
660 zipFile->uncompress(entry));
Adam Lesinski769de982015-04-10 19:43:55 -0700661 assert(uncompressedData);
662
Adam Lesinski24aad162015-04-24 19:19:30 -0700663 BinaryResourceParser parser(table, resolver, source, uncompressedData.get(),
Adam Lesinski769de982015-04-10 19:43:55 -0700664 entry->getUncompressedLen());
665 if (!parser.parse()) {
Adam Lesinski769de982015-04-10 19:43:55 -0700666 return false;
667 }
Adam Lesinski769de982015-04-10 19:43:55 -0700668
669 // Keep track of where this table came from.
Adam Lesinski24aad162015-04-24 19:19:30 -0700670 apkFiles[table] = StaticLibraryData{ source, std::move(zipFile) };
Adam Lesinski769de982015-04-10 19:43:55 -0700671
672 // Add the package to the set of linked packages.
673 linkedPackages.insert(table->getPackage());
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700674 }
675
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700676 std::queue<LinkItem> linkQueue;
Adam Lesinski769de982015-04-10 19:43:55 -0700677 for (auto& p : apkFiles) {
678 const std::shared_ptr<ResourceTable>& inTable = p.first;
679
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700680 // Collect all FileReferences and add them to the queue for processing.
Adam Lesinski24aad162015-04-24 19:19:30 -0700681 addApkFilesToLinkQueue(options.appInfo.package, p.second.source, inTable, p.second.apk,
682 &linkQueue);
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700683
684 // Merge the tables.
Adam Lesinski769de982015-04-10 19:43:55 -0700685 if (!outTable->merge(std::move(*inTable))) {
686 return false;
687 }
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700688 }
689
Adam Lesinski330edcd2015-05-04 17:40:56 -0700690 // Version all styles referencing attributes outside of their specified SDK version.
691 if (options.versionStylesAndLayouts) {
692 versionStylesForCompat(outTable);
693 }
694
Adam Lesinski769de982015-04-10 19:43:55 -0700695 {
696 // Now that everything is merged, let's link it.
Adam Lesinski330edcd2015-05-04 17:40:56 -0700697 Linker::Options linkerOptions;
698 if (options.packageType == AaptOptions::PackageType::StaticLibrary) {
699 linkerOptions.linkResourceIds = false;
700 }
701 Linker linker(outTable, resolver, linkerOptions);
Adam Lesinski769de982015-04-10 19:43:55 -0700702 if (!linker.linkAndValidate()) {
703 return false;
704 }
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700705
Adam Lesinski769de982015-04-10 19:43:55 -0700706 // Verify that all symbols exist.
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700707 const auto& unresolvedRefs = linker.getUnresolvedReferences();
708 if (!unresolvedRefs.empty()) {
709 for (const auto& entry : unresolvedRefs) {
710 for (const auto& source : entry.second) {
711 Logger::error(source) << "unresolved symbol '" << entry.first << "'."
712 << std::endl;
713 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800714 }
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700715 return false;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800716 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800717 }
718
Adam Lesinski769de982015-04-10 19:43:55 -0700719 // Open the output APK file for writing.
720 ZipFile outApk;
721 if (outApk.open(options.output.path.data(), kOpenFlags) != android::NO_ERROR) {
722 Logger::error(options.output) << "failed to open: " << strerror(errno) << std::endl;
723 return false;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700724 }
725
Adam Lesinski330edcd2015-05-04 17:40:56 -0700726 android::ResTable binTable;
727 if (!compileManifest(options, resolver, binTable, &outApk)) {
Adam Lesinski769de982015-04-10 19:43:55 -0700728 return false;
729 }
730
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700731 for (; !linkQueue.empty(); linkQueue.pop()) {
732 const LinkItem& item = linkQueue.front();
Adam Lesinski769de982015-04-10 19:43:55 -0700733
Adam Lesinski24aad162015-04-24 19:19:30 -0700734 assert(!item.originalPackage.empty());
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700735 ZipEntry* entry = item.apk->getEntryByName(item.originalPath.data());
736 if (!entry) {
737 Logger::error(item.source) << "failed to find '" << item.originalPath << "'."
738 << std::endl;
739 return false;
740 }
Adam Lesinski769de982015-04-10 19:43:55 -0700741
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700742 if (util::stringEndsWith<char>(item.originalPath, ".xml")) {
743 void* uncompressedData = item.apk->uncompress(entry);
744 assert(uncompressedData);
Adam Lesinski769de982015-04-10 19:43:55 -0700745
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700746 if (!linkXml(options, resolver, item, uncompressedData, entry->getUncompressedLen(),
Adam Lesinski330edcd2015-05-04 17:40:56 -0700747 &outApk, &linkQueue)) {
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700748 Logger::error(options.output) << "failed to link '" << item.originalPath << "'."
749 << std::endl;
750 return false;
Adam Lesinski769de982015-04-10 19:43:55 -0700751 }
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700752 } else {
753 if (outApk.add(item.apk, entry, buildFileReference(item).data(), 0, nullptr) !=
754 android::NO_ERROR) {
755 Logger::error(options.output) << "failed to copy '" << item.originalPath << "'."
756 << std::endl;
757 return false;
Adam Lesinski769de982015-04-10 19:43:55 -0700758 }
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700759 }
760 }
761
762 // Generate the Java class file.
Adam Lesinski769de982015-04-10 19:43:55 -0700763 if (options.generateJavaClass) {
Adam Lesinski6d8e4c42015-05-01 14:47:28 -0700764 JavaClassGenerator::Options javaOptions;
765 if (options.packageType == AaptOptions::PackageType::StaticLibrary) {
766 javaOptions.useFinal = false;
767 }
768 JavaClassGenerator generator(outTable, javaOptions);
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700769
Adam Lesinski769de982015-04-10 19:43:55 -0700770 for (const std::u16string& package : linkedPackages) {
771 Source outPath = options.generateJavaClass.value();
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800772
Adam Lesinski769de982015-04-10 19:43:55 -0700773 // Build the output directory from the package name.
774 // Eg. com.android.app -> com/android/app
775 const std::string packageUtf8 = util::utf16ToUtf8(package);
776 for (StringPiece part : util::tokenize<char>(packageUtf8, '.')) {
777 appendPath(&outPath.path, part);
778 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800779
Adam Lesinski769de982015-04-10 19:43:55 -0700780 if (!mkdirs(outPath.path)) {
781 Logger::error(outPath) << strerror(errno) << std::endl;
782 return false;
783 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800784
Adam Lesinski769de982015-04-10 19:43:55 -0700785 appendPath(&outPath.path, "R.java");
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800786
Adam Lesinski769de982015-04-10 19:43:55 -0700787 if (options.verbose) {
788 Logger::note(outPath) << "writing Java symbols." << std::endl;
789 }
790
791 std::ofstream fout(outPath.path);
792 if (!fout) {
793 Logger::error(outPath) << strerror(errno) << std::endl;
794 return false;
795 }
796
797 if (!generator.generate(package, fout)) {
798 Logger::error(outPath) << generator.getError() << "." << std::endl;
799 return false;
800 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800801 }
802 }
803
Adam Lesinski24aad162015-04-24 19:19:30 -0700804 outTable->getValueStringPool().prune();
805 outTable->getValueStringPool().sort(
806 [](const StringPool::Entry& a, const StringPool::Entry& b) -> bool {
807 if (a.context.priority < b.context.priority) {
808 return true;
809 }
810
811 if (a.context.priority > b.context.priority) {
812 return false;
813 }
814 return a.value < b.value;
815 });
816
817
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700818 // Flatten the resource table.
Adam Lesinski769de982015-04-10 19:43:55 -0700819 TableFlattener::Options flattenerOptions;
Adam Lesinski330edcd2015-05-04 17:40:56 -0700820 if (options.packageType != AaptOptions::PackageType::StaticLibrary) {
821 flattenerOptions.useExtendedChunks = false;
Adam Lesinski6d8e4c42015-05-01 14:47:28 -0700822 }
823
Adam Lesinski769de982015-04-10 19:43:55 -0700824 if (!writeResourceTable(options, outTable, flattenerOptions, &outApk)) {
825 return false;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800826 }
Adam Lesinski769de982015-04-10 19:43:55 -0700827
828 outApk.flush();
829 return true;
830}
831
832bool compile(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table,
Adam Lesinski24aad162015-04-24 19:19:30 -0700833 const std::shared_ptr<IResolver>& resolver) {
Adam Lesinski769de982015-04-10 19:43:55 -0700834 std::queue<CompileItem> compileQueue;
835 bool error = false;
836
837 // Compile all the resource files passed in on the command line.
838 for (const Source& source : options.input) {
839 // Need to parse the resource type/config/filename.
840 Maybe<ResourcePathData> maybePathData = extractResourcePathData(source);
841 if (!maybePathData) {
842 return false;
843 }
844
845 const ResourcePathData& pathData = maybePathData.value();
846 if (pathData.resourceDir == u"values") {
847 // The file is in the values directory, which means its contents will
848 // go into the resource table.
849 if (options.verbose) {
850 Logger::note(source) << "compiling values." << std::endl;
851 }
852
853 error |= !compileValues(table, source, pathData.config);
854 } else {
855 // The file is in a directory like 'layout' or 'drawable'. Find out
856 // the type.
857 const ResourceType* type = parseResourceType(pathData.resourceDir);
858 if (!type) {
859 Logger::error(source) << "invalid resource type '" << pathData.resourceDir << "'."
860 << std::endl;
861 return false;
862 }
863
864 compileQueue.push(CompileItem{
865 source,
866 ResourceName{ table->getPackage(), *type, pathData.name },
867 pathData.config,
868 pathData.extension
869 });
870 }
871 }
872
873 if (error) {
874 return false;
875 }
Adam Lesinski769de982015-04-10 19:43:55 -0700876 // Open the output APK file for writing.
877 ZipFile outApk;
878 if (outApk.open(options.output.path.data(), kOpenFlags) != android::NO_ERROR) {
879 Logger::error(options.output) << "failed to open: " << strerror(errno) << std::endl;
880 return false;
881 }
882
883 // Compile each file.
884 for (; !compileQueue.empty(); compileQueue.pop()) {
885 const CompileItem& item = compileQueue.front();
886
887 // Add the file name to the resource table.
888 error |= !addFileReference(table, item);
889
890 if (item.extension == "xml") {
Adam Lesinski330edcd2015-05-04 17:40:56 -0700891 error |= !compileXml(options, table, item, &outApk);
Adam Lesinski769de982015-04-10 19:43:55 -0700892 } else if (item.extension == "png" || item.extension == "9.png") {
893 error |= !compilePng(options, item, &outApk);
894 } else {
895 error |= !copyFile(options, item, &outApk);
896 }
897 }
898
899 if (error) {
900 return false;
901 }
902
903 // Link and assign resource IDs.
Adam Lesinski330edcd2015-05-04 17:40:56 -0700904 Linker linker(table, resolver, {});
Adam Lesinski769de982015-04-10 19:43:55 -0700905 if (!linker.linkAndValidate()) {
906 return false;
907 }
908
909 // Flatten the resource table.
910 if (!writeResourceTable(options, table, {}, &outApk)) {
911 return false;
912 }
913
914 outApk.flush();
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800915 return true;
916}
917
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700918bool loadAppInfo(const Source& source, AppInfo* outInfo) {
919 std::ifstream ifs(source.path, std::ifstream::in | std::ifstream::binary);
920 if (!ifs) {
921 Logger::error(source) << strerror(errno) << std::endl;
922 return false;
923 }
924
925 ManifestParser parser;
926 std::shared_ptr<XmlPullParser> pullParser = std::make_shared<SourceXmlPullParser>(ifs);
927 return parser.parse(source, pullParser, outInfo);
928}
929
930static void printCommandsAndDie() {
931 std::cerr << "The following commands are supported:" << std::endl << std::endl;
932 std::cerr << "compile compiles a subset of resources" << std::endl;
933 std::cerr << "link links together compiled resources and libraries" << std::endl;
Adam Lesinski330edcd2015-05-04 17:40:56 -0700934 std::cerr << "dump dumps resource contents to to standard out" << std::endl;
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700935 std::cerr << std::endl;
936 std::cerr << "run aapt2 with one of the commands and the -h flag for extra details."
937 << std::endl;
938 exit(1);
939}
940
941static AaptOptions prepareArgs(int argc, char** argv) {
942 if (argc < 2) {
943 std::cerr << "no command specified." << std::endl << std::endl;
944 printCommandsAndDie();
945 }
946
947 const StringPiece command(argv[1]);
948 argc -= 2;
949 argv += 2;
950
951 AaptOptions options;
952
953 if (command == "--version" || command == "version") {
954 std::cout << kAaptVersionStr << std::endl;
955 exit(0);
956 } else if (command == "link") {
957 options.phase = AaptOptions::Phase::Link;
958 } else if (command == "compile") {
959 options.phase = AaptOptions::Phase::Compile;
Adam Lesinski330edcd2015-05-04 17:40:56 -0700960 } else if (command == "dump") {
961 options.phase = AaptOptions::Phase::Dump;
962 } else if (command == "dump-style-graph") {
963 options.phase = AaptOptions::Phase::DumpStyleGraph;
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700964 } else {
965 std::cerr << "invalid command '" << command << "'." << std::endl << std::endl;
966 printCommandsAndDie();
967 }
968
Adam Lesinski6d8e4c42015-05-01 14:47:28 -0700969 bool isStaticLib = false;
Adam Lesinski330edcd2015-05-04 17:40:56 -0700970 if (options.phase == AaptOptions::Phase::Compile ||
971 options.phase == AaptOptions::Phase::Link) {
972 if (options.phase == AaptOptions::Phase::Compile) {
973 flag::requiredFlag("--package", "Android package name",
974 [&options](const StringPiece& arg) {
975 options.appInfo.package = util::utf8ToUtf16(arg);
976 });
977 } else if (options.phase == AaptOptions::Phase::Link) {
978 flag::requiredFlag("--manifest", "AndroidManifest.xml of your app",
979 [&options](const StringPiece& arg) {
980 options.manifest = Source{ arg.toString() };
981 });
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700982
Adam Lesinski330edcd2015-05-04 17:40:56 -0700983 flag::optionalFlag("-I", "add an Android APK to link against",
984 [&options](const StringPiece& arg) {
985 options.libraries.push_back(Source{ arg.toString() });
986 });
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700987
Adam Lesinski330edcd2015-05-04 17:40:56 -0700988 flag::optionalFlag("--java", "directory in which to generate R.java",
989 [&options](const StringPiece& arg) {
990 options.generateJavaClass = Source{ arg.toString() };
991 });
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700992
Adam Lesinski330edcd2015-05-04 17:40:56 -0700993 flag::optionalSwitch("--static-lib", "generate a static Android library", true,
994 &isStaticLib);
995
996 flag::optionalFlag("--binding", "Output directory for binding XML files",
997 [&options](const StringPiece& arg) {
998 options.bindingOutput = Source{ arg.toString() };
999 });
1000 flag::optionalSwitch("--no-version", "Disables automatic style and layout versioning",
1001 false, &options.versionStylesAndLayouts);
1002 }
1003
1004 // Common flags for all steps.
1005 flag::requiredFlag("-o", "Output path", [&options](const StringPiece& arg) {
1006 options.output = Source{ arg.toString() };
1007 });
Adam Lesinskid13fb242015-05-12 20:40:48 -07001008 } else if (options.phase == AaptOptions::Phase::DumpStyleGraph) {
1009 flag::requiredFlag("--style", "Name of the style to dump",
1010 [&options](const StringPiece& arg, std::string* outError) -> bool {
1011 Reference styleReference;
1012 if (!ResourceParser::parseStyleParentReference(util::utf8ToUtf16(arg),
1013 &styleReference, outError)) {
1014 return false;
1015 }
1016 options.dumpStyleTarget = styleReference.name;
1017 return true;
1018 });
Adam Lesinskid5c4f872015-04-21 13:56:10 -07001019 }
1020
Adam Lesinskid5c4f872015-04-21 13:56:10 -07001021 bool help = false;
1022 flag::optionalSwitch("-v", "enables verbose logging", true, &options.verbose);
1023 flag::optionalSwitch("-h", "displays this help menu", true, &help);
1024
1025 // Build the command string for output (eg. "aapt2 compile").
1026 std::string fullCommand = "aapt2";
1027 fullCommand += " ";
1028 fullCommand += command.toString();
1029
1030 // Actually read the command line flags.
1031 flag::parse(argc, argv, fullCommand);
1032
1033 if (help) {
1034 flag::usageAndDie(fullCommand);
1035 }
1036
Adam Lesinski6d8e4c42015-05-01 14:47:28 -07001037 if (isStaticLib) {
1038 options.packageType = AaptOptions::PackageType::StaticLibrary;
1039 }
1040
Adam Lesinskid5c4f872015-04-21 13:56:10 -07001041 // Copy all the remaining arguments.
1042 for (const std::string& arg : flag::getArgs()) {
1043 options.input.push_back(Source{ arg });
1044 }
1045 return options;
1046}
1047
Adam Lesinski330edcd2015-05-04 17:40:56 -07001048static bool doDump(const AaptOptions& options) {
1049 for (const Source& source : options.input) {
1050 std::unique_ptr<ZipFile> zipFile = util::make_unique<ZipFile>();
1051 if (zipFile->open(source.path.data(), ZipFile::kOpenReadOnly) != android::NO_ERROR) {
1052 Logger::error(source) << "failed to open: " << strerror(errno) << std::endl;
1053 return false;
1054 }
1055
1056 std::shared_ptr<ResourceTable> table = std::make_shared<ResourceTable>();
1057 std::shared_ptr<ResourceTableResolver> resolver =
1058 std::make_shared<ResourceTableResolver>(
1059 table, std::vector<std::shared_ptr<const android::AssetManager>>());
1060
1061 ZipEntry* entry = zipFile->getEntryByName("resources.arsc");
1062 if (!entry) {
1063 Logger::error(source) << "missing 'resources.arsc'." << std::endl;
1064 return false;
1065 }
1066
1067 std::unique_ptr<void, DeleteMalloc> uncompressedData = std::unique_ptr<void, DeleteMalloc>(
1068 zipFile->uncompress(entry));
1069 assert(uncompressedData);
1070
1071 BinaryResourceParser parser(table, resolver, source, uncompressedData.get(),
1072 entry->getUncompressedLen());
1073 if (!parser.parse()) {
1074 return false;
1075 }
1076
1077 if (options.phase == AaptOptions::Phase::Dump) {
1078 Debug::printTable(table);
1079 } else if (options.phase == AaptOptions::Phase::DumpStyleGraph) {
Adam Lesinskid13fb242015-05-12 20:40:48 -07001080 Debug::printStyleGraph(table, options.dumpStyleTarget);
Adam Lesinski330edcd2015-05-04 17:40:56 -07001081 }
1082 }
1083 return true;
1084}
1085
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001086int main(int argc, char** argv) {
1087 Logger::setLog(std::make_shared<Log>(std::cerr, std::cerr));
Adam Lesinski98aa3ad2015-04-06 11:46:52 -07001088 AaptOptions options = prepareArgs(argc, argv);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001089
Adam Lesinski330edcd2015-05-04 17:40:56 -07001090 if (options.phase == AaptOptions::Phase::Dump ||
1091 options.phase == AaptOptions::Phase::DumpStyleGraph) {
1092 if (!doDump(options)) {
1093 return 1;
1094 }
1095 return 0;
1096 }
1097
Adam Lesinski98aa3ad2015-04-06 11:46:52 -07001098 // If we specified a manifest, go ahead and load the package name from the manifest.
1099 if (!options.manifest.path.empty()) {
1100 if (!loadAppInfo(options.manifest, &options.appInfo)) {
1101 return false;
1102 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001103 }
1104
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001105 // Verify we have some common options set.
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001106 if (options.appInfo.package.empty()) {
1107 Logger::error() << "no package name specified." << std::endl;
1108 return false;
1109 }
1110
Adam Lesinski98aa3ad2015-04-06 11:46:52 -07001111 // Every phase needs a resource table.
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001112 std::shared_ptr<ResourceTable> table = std::make_shared<ResourceTable>();
1113 table->setPackage(options.appInfo.package);
1114 if (options.appInfo.package == u"android") {
1115 table->setPackageId(0x01);
1116 } else {
1117 table->setPackageId(0x7f);
1118 }
1119
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001120 // Load the included libraries.
Adam Lesinski330edcd2015-05-04 17:40:56 -07001121 std::vector<std::shared_ptr<const android::AssetManager>> sources;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001122 for (const Source& source : options.libraries) {
Adam Lesinski330edcd2015-05-04 17:40:56 -07001123 std::shared_ptr<android::AssetManager> assetManager =
1124 std::make_shared<android::AssetManager>();
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001125 int32_t cookie;
Adam Lesinski330edcd2015-05-04 17:40:56 -07001126 if (!assetManager->addAssetPath(android::String8(source.path.data()), &cookie)) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001127 Logger::error(source) << "failed to load library." << std::endl;
1128 return false;
1129 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001130
Adam Lesinski330edcd2015-05-04 17:40:56 -07001131 if (cookie == 0) {
1132 Logger::error(source) << "failed to load library." << std::endl;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001133 return false;
1134 }
Adam Lesinski330edcd2015-05-04 17:40:56 -07001135 sources.push_back(assetManager);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001136 }
1137
1138 // Make the resolver that will cache IDs for us.
Adam Lesinski24aad162015-04-24 19:19:30 -07001139 std::shared_ptr<ResourceTableResolver> resolver = std::make_shared<ResourceTableResolver>(
Adam Lesinski330edcd2015-05-04 17:40:56 -07001140 table, sources);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001141
Adam Lesinski769de982015-04-10 19:43:55 -07001142 if (options.phase == AaptOptions::Phase::Compile) {
1143 if (!compile(options, table, resolver)) {
1144 Logger::error() << "aapt exiting with failures." << std::endl;
1145 return 1;
1146 }
1147 } else if (options.phase == AaptOptions::Phase::Link) {
1148 if (!link(options, table, resolver)) {
1149 Logger::error() << "aapt exiting with failures." << std::endl;
1150 return 1;
1151 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001152 }
1153 return 0;
1154}