blob: f1b7777174817160db6fc8fd5919e0edcf90309b [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);
Adam Lesinski769de982015-04-10 19:43:55 -0700161 newStyle.weak = true;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800162
163 // Move the recorded stripped attributes into this new style.
164 std::move(stripped.begin(), stripped.end(),
165 std::back_inserter(newStyle.entries));
166
167 // We will add this style to the table later. If we do it now, we will
168 // mess up iteration.
169 addStack.push(std::move(value));
170 }
171 });
172 }
173
174 auto comparator =
175 [](const ResourceConfigValue& lhs, const ConfigDescription& rhs) -> bool {
176 return lhs.config < rhs;
177 };
178
179 while (!addStack.empty()) {
180 ResourceConfigValue& value = addStack.top();
181 auto iter = std::lower_bound(entry->values.begin(), entry->values.end(),
182 value.config, comparator);
183 if (iter == entry->values.end() || iter->config != value.config) {
184 entry->values.insert(iter, std::move(value));
185 }
186 addStack.pop();
187 }
188 }
189 }
190}
191
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700192struct CompileItem {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800193 Source source;
194 ResourceName name;
195 ConfigDescription config;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700196 std::string extension;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800197};
198
Adam Lesinski769de982015-04-10 19:43:55 -0700199struct LinkItem {
200 Source source;
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700201 ResourceName name;
202 ConfigDescription config;
203 std::string originalPath;
204 ZipFile* apk;
Adam Lesinski24aad162015-04-24 19:19:30 -0700205 std::u16string originalPackage;
Adam Lesinski769de982015-04-10 19:43:55 -0700206};
207
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700208template <typename TChar>
209static BasicStringPiece<TChar> getExtension(const BasicStringPiece<TChar>& str) {
210 auto iter = std::find(str.begin(), str.end(), static_cast<TChar>('.'));
211 if (iter == str.end()) {
212 return BasicStringPiece<TChar>();
Adam Lesinski769de982015-04-10 19:43:55 -0700213 }
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700214 size_t offset = (iter - str.begin()) + 1;
215 return str.substr(offset, str.size() - offset);
216}
217
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700218std::string buildFileReference(const ResourceNameRef& name, const ConfigDescription& config,
219 const StringPiece& extension) {
220 std::stringstream path;
221 path << "res/" << name.type;
222 if (config != ConfigDescription{}) {
223 path << "-" << config;
224 }
225 path << "/" << util::utf16ToUtf8(name.entry);
226 if (!extension.empty()) {
227 path << "." << extension;
228 }
Adam Lesinski769de982015-04-10 19:43:55 -0700229 return path.str();
230}
231
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700232std::string buildFileReference(const CompileItem& item) {
233 return buildFileReference(item.name, item.config, item.extension);
234}
235
236std::string buildFileReference(const LinkItem& item) {
237 return buildFileReference(item.name, item.config, getExtension<char>(item.originalPath));
238}
239
Adam Lesinski769de982015-04-10 19:43:55 -0700240bool addFileReference(const std::shared_ptr<ResourceTable>& table, const CompileItem& item) {
241 StringPool& pool = table->getValueStringPool();
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700242 StringPool::Ref ref = pool.makeRef(util::utf8ToUtf16(buildFileReference(item)),
243 StringPool::Context{ 0, item.config });
Adam Lesinski769de982015-04-10 19:43:55 -0700244 return table->addResource(item.name, item.config, item.source.line(0),
245 util::make_unique<FileReference>(ref));
246}
247
248struct AaptOptions {
249 enum class Phase {
250 Link,
251 Compile,
Adam Lesinski330edcd2015-05-04 17:40:56 -0700252 Dump,
253 DumpStyleGraph,
Adam Lesinski769de982015-04-10 19:43:55 -0700254 };
255
Adam Lesinski6d8e4c42015-05-01 14:47:28 -0700256 enum class PackageType {
257 StandardApp,
258 StaticLibrary,
259 };
260
Adam Lesinski769de982015-04-10 19:43:55 -0700261 // The phase to process.
262 Phase phase;
263
Adam Lesinski6d8e4c42015-05-01 14:47:28 -0700264 // The type of package to produce.
265 PackageType packageType = PackageType::StandardApp;
266
Adam Lesinski769de982015-04-10 19:43:55 -0700267 // Details about the app.
268 AppInfo appInfo;
269
270 // The location of the manifest file.
271 Source manifest;
272
273 // The APK files to link.
274 std::vector<Source> input;
275
276 // The libraries these files may reference.
277 std::vector<Source> libraries;
278
279 // Output path. This can be a directory or file
280 // depending on the phase.
281 Source output;
282
283 // Directory in which to write binding xml files.
284 Source bindingOutput;
285
286 // Directory to in which to generate R.java.
287 Maybe<Source> generateJavaClass;
288
289 // Whether to output verbose details about
290 // compilation.
291 bool verbose = false;
Adam Lesinski5886a922015-04-15 20:29:22 -0700292
293 // Whether or not to auto-version styles or layouts
294 // referencing attributes defined in a newer SDK
295 // level than the style or layout is defined for.
296 bool versionStylesAndLayouts = true;
Adam Lesinski769de982015-04-10 19:43:55 -0700297};
298
299bool compileXml(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table,
Adam Lesinski330edcd2015-05-04 17:40:56 -0700300 const CompileItem& item, ZipFile* outApk) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800301 std::ifstream in(item.source.path, std::ifstream::binary);
302 if (!in) {
303 Logger::error(item.source) << strerror(errno) << std::endl;
304 return false;
305 }
306
Adam Lesinski4d3a9872015-04-09 19:53:22 -0700307 BigBuffer outBuffer(1024);
Adam Lesinski769de982015-04-10 19:43:55 -0700308
309 // No resolver, since we are not compiling attributes here.
310 XmlFlattener flattener(table, {});
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800311
Adam Lesinski769de982015-04-10 19:43:55 -0700312 XmlFlattener::Options xmlOptions;
Adam Lesinski24aad162015-04-24 19:19:30 -0700313 xmlOptions.defaultPackage = table->getPackage();
Adam Lesinski330edcd2015-05-04 17:40:56 -0700314 xmlOptions.keepRawValues = true;
315
316 std::shared_ptr<XmlPullParser> parser = std::make_shared<SourceXmlPullParser>(in);
317
318 Maybe<size_t> minStrippedSdk = flattener.flatten(item.source, parser, &outBuffer,
319 xmlOptions);
320 if (!minStrippedSdk) {
321 return false;
322 }
323
324 // Write the resulting compiled XML file to the output APK.
325 if (outApk->add(outBuffer, buildFileReference(item).data(), ZipEntry::kCompressStored,
326 nullptr) != android::NO_ERROR) {
327 Logger::error(options.output) << "failed to write compiled '" << item.source
328 << "' to apk." << std::endl;
329 return false;
330 }
331 return true;
332}
333
334bool linkXml(const AaptOptions& options, const std::shared_ptr<IResolver>& resolver,
335 const LinkItem& item, const void* data, size_t dataLen, ZipFile* outApk,
336 std::queue<LinkItem>* outQueue) {
337 std::shared_ptr<android::ResXMLTree> tree = std::make_shared<android::ResXMLTree>();
338 if (tree->setTo(data, dataLen, false) != android::NO_ERROR) {
339 return false;
340 }
341
342 std::shared_ptr<XmlPullParser> parser = std::make_shared<BinaryXmlPullParser>(tree);
343
344 BigBuffer outBuffer(1024);
345 XmlFlattener flattener({}, resolver);
346
347 XmlFlattener::Options xmlOptions;
348 xmlOptions.defaultPackage = item.originalPackage;
349
350 if (options.packageType == AaptOptions::PackageType::StaticLibrary) {
351 xmlOptions.keepRawValues = true;
352 }
Adam Lesinski24aad162015-04-24 19:19:30 -0700353
Adam Lesinski5886a922015-04-15 20:29:22 -0700354 if (options.versionStylesAndLayouts) {
355 // We strip attributes that do not belong in this version of the resource.
356 // Non-version qualified resources have an implicit version 1 requirement.
357 xmlOptions.maxSdkAttribute = item.config.sdkVersion ? item.config.sdkVersion : 1;
358 }
Adam Lesinski769de982015-04-10 19:43:55 -0700359
360 std::shared_ptr<BindingXmlPullParser> binding;
Adam Lesinski769de982015-04-10 19:43:55 -0700361 if (item.name.type == ResourceType::kLayout) {
362 // Layouts may have defined bindings, so we need to make sure they get processed.
363 binding = std::make_shared<BindingXmlPullParser>(parser);
364 parser = binding;
365 }
366
Adam Lesinski330edcd2015-05-04 17:40:56 -0700367 Maybe<size_t> minStrippedSdk = flattener.flatten(item.source, parser, &outBuffer,
368 xmlOptions);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800369 if (!minStrippedSdk) {
370 return false;
371 }
372
373 if (minStrippedSdk.value() > 0) {
374 // Something was stripped, so let's generate a new file
375 // with the version of the smallest SDK version stripped.
Adam Lesinski330edcd2015-05-04 17:40:56 -0700376 LinkItem newWork = item;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800377 newWork.config.sdkVersion = minStrippedSdk.value();
Adam Lesinski769de982015-04-10 19:43:55 -0700378 outQueue->push(newWork);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800379 }
380
Adam Lesinski330edcd2015-05-04 17:40:56 -0700381 if (outApk->add(outBuffer, buildFileReference(item).data(), ZipEntry::kCompressDeflated,
Adam Lesinski769de982015-04-10 19:43:55 -0700382 nullptr) != android::NO_ERROR) {
Adam Lesinski330edcd2015-05-04 17:40:56 -0700383 Logger::error(options.output) << "failed to write linked file '"
384 << buildFileReference(item) << "' to apk." << std::endl;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800385 return false;
386 }
387
Adam Lesinski769de982015-04-10 19:43:55 -0700388 if (binding && !options.bindingOutput.path.empty()) {
389 // We generated a binding xml file, write it out.
390 Source bindingOutput = options.bindingOutput;
391 appendPath(&bindingOutput.path, buildFileReference(item));
Adam Lesinski4d3a9872015-04-09 19:53:22 -0700392
Adam Lesinski769de982015-04-10 19:43:55 -0700393 if (!mkdirs(bindingOutput.path)) {
394 Logger::error(bindingOutput) << strerror(errno) << std::endl;
395 return false;
396 }
397
398 appendPath(&bindingOutput.path, "bind.xml");
399
Adam Lesinski4d3a9872015-04-09 19:53:22 -0700400 std::ofstream bout(bindingOutput.path);
401 if (!bout) {
402 Logger::error(bindingOutput) << strerror(errno) << std::endl;
403 return false;
404 }
405
406 if (!binding->writeToFile(bout)) {
407 Logger::error(bindingOutput) << strerror(errno) << std::endl;
408 return false;
409 }
410 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800411 return true;
412}
413
Adam Lesinski769de982015-04-10 19:43:55 -0700414bool compilePng(const AaptOptions& options, const CompileItem& item, ZipFile* outApk) {
415 std::ifstream in(item.source.path, std::ifstream::binary);
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700416 if (!in) {
Adam Lesinski769de982015-04-10 19:43:55 -0700417 Logger::error(item.source) << strerror(errno) << std::endl;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700418 return false;
419 }
420
Adam Lesinski769de982015-04-10 19:43:55 -0700421 BigBuffer outBuffer(4096);
422 std::string err;
423 Png png;
424 if (!png.process(item.source, in, &outBuffer, {}, &err)) {
425 Logger::error(item.source) << err << std::endl;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700426 return false;
427 }
428
Adam Lesinski769de982015-04-10 19:43:55 -0700429 if (outApk->add(outBuffer, buildFileReference(item).data(), ZipEntry::kCompressStored,
430 nullptr) != android::NO_ERROR) {
431 Logger::error(options.output) << "failed to write compiled '" << item.source
432 << "' to apk." << std::endl;
433 return false;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700434 }
Adam Lesinski769de982015-04-10 19:43:55 -0700435 return true;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700436}
437
Adam Lesinski769de982015-04-10 19:43:55 -0700438bool copyFile(const AaptOptions& options, const CompileItem& item, ZipFile* outApk) {
439 if (outApk->add(item.source.path.data(), buildFileReference(item).data(),
440 ZipEntry::kCompressStored, nullptr) != android::NO_ERROR) {
441 Logger::error(options.output) << "failed to copy file '" << item.source << "' to apk."
442 << std::endl;
443 return false;
444 }
445 return true;
446}
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800447
Adam Lesinski330edcd2015-05-04 17:40:56 -0700448bool compileManifest(const AaptOptions& options, const std::shared_ptr<IResolver>& resolver,
449 const android::ResTable& table, ZipFile* outApk) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800450 if (options.verbose) {
Adam Lesinski769de982015-04-10 19:43:55 -0700451 Logger::note(options.manifest) << "compiling AndroidManifest.xml." << std::endl;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800452 }
453
454 std::ifstream in(options.manifest.path, std::ifstream::binary);
455 if (!in) {
456 Logger::error(options.manifest) << strerror(errno) << std::endl;
457 return false;
458 }
459
460 BigBuffer outBuffer(1024);
461 std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<SourceXmlPullParser>(in);
Adam Lesinski769de982015-04-10 19:43:55 -0700462 XmlFlattener flattener({}, resolver);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800463
Adam Lesinski24aad162015-04-24 19:19:30 -0700464 XmlFlattener::Options xmlOptions;
465 xmlOptions.defaultPackage = options.appInfo.package;
466 if (!flattener.flatten(options.manifest, xmlParser, &outBuffer, xmlOptions)) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800467 return false;
468 }
469
Adam Lesinski769de982015-04-10 19:43:55 -0700470 std::unique_ptr<uint8_t[]> data = util::copy(outBuffer);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800471
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700472 android::ResXMLTree tree;
Adam Lesinski769de982015-04-10 19:43:55 -0700473 if (tree.setTo(data.get(), outBuffer.size(), false) != android::NO_ERROR) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800474 return false;
475 }
476
Adam Lesinski330edcd2015-05-04 17:40:56 -0700477 ManifestValidator validator(table);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800478 if (!validator.validate(options.manifest, &tree)) {
479 return false;
480 }
481
Adam Lesinski769de982015-04-10 19:43:55 -0700482 if (outApk->add(data.get(), outBuffer.size(), "AndroidManifest.xml",
483 ZipEntry::kCompressStored, nullptr) != android::NO_ERROR) {
484 Logger::error(options.output) << "failed to write 'AndroidManifest.xml' to apk."
485 << std::endl;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800486 return false;
487 }
488 return true;
489}
490
Adam Lesinski769de982015-04-10 19:43:55 -0700491static bool compileValues(const std::shared_ptr<ResourceTable>& table, const Source& source,
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700492 const ConfigDescription& config) {
493 std::ifstream in(source.path, std::ifstream::binary);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800494 if (!in) {
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700495 Logger::error(source) << strerror(errno) << std::endl;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800496 return false;
497 }
498
499 std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<SourceXmlPullParser>(in);
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700500 ResourceParser parser(table, source, config, xmlParser);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800501 return parser.parse();
502}
503
504struct ResourcePathData {
505 std::u16string resourceDir;
506 std::u16string name;
507 std::string extension;
508 ConfigDescription config;
509};
510
511/**
512 * Resource file paths are expected to look like:
513 * [--/res/]type[-config]/name
514 */
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700515static Maybe<ResourcePathData> extractResourcePathData(const Source& source) {
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700516 // TODO(adamlesinski): Use Windows path separator on windows.
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800517 std::vector<std::string> parts = util::splitAndLowercase(source.path, '/');
518 if (parts.size() < 2) {
519 Logger::error(source) << "bad resource path." << std::endl;
520 return {};
521 }
522
523 std::string& dir = parts[parts.size() - 2];
524 StringPiece dirStr = dir;
525
526 ConfigDescription config;
527 size_t dashPos = dir.find('-');
528 if (dashPos != std::string::npos) {
529 StringPiece configStr = dirStr.substr(dashPos + 1, dir.size() - (dashPos + 1));
530 if (!ConfigDescription::parse(configStr, &config)) {
531 Logger::error(source)
532 << "invalid configuration '"
533 << configStr
534 << "'."
535 << std::endl;
536 return {};
537 }
538 dirStr = dirStr.substr(0, dashPos);
539 }
540
541 std::string& filename = parts[parts.size() - 1];
542 StringPiece name = filename;
543 StringPiece extension;
544 size_t dotPos = filename.find('.');
545 if (dotPos != std::string::npos) {
546 extension = name.substr(dotPos + 1, filename.size() - (dotPos + 1));
547 name = name.substr(0, dotPos);
548 }
549
550 return ResourcePathData{
551 util::utf8ToUtf16(dirStr),
552 util::utf8ToUtf16(name),
553 extension.toString(),
554 config
555 };
556}
557
Adam Lesinski769de982015-04-10 19:43:55 -0700558bool writeResourceTable(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table,
559 const TableFlattener::Options& flattenerOptions, ZipFile* outApk) {
560 if (table->begin() != table->end()) {
561 BigBuffer buffer(1024);
562 TableFlattener flattener(flattenerOptions);
563 if (!flattener.flatten(&buffer, *table)) {
564 Logger::error() << "failed to flatten resource table." << std::endl;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700565 return false;
566 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800567
Adam Lesinski769de982015-04-10 19:43:55 -0700568 if (options.verbose) {
569 Logger::note() << "Final resource table size=" << util::formatSize(buffer.size())
570 << std::endl;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700571 }
572
Adam Lesinski769de982015-04-10 19:43:55 -0700573 if (outApk->add(buffer, "resources.arsc", ZipEntry::kCompressStored, nullptr) !=
574 android::NO_ERROR) {
575 Logger::note(options.output) << "failed to store resource table." << std::endl;
576 return false;
577 }
578 }
579 return true;
580}
581
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700582/**
583 * For each FileReference in the table, adds a LinkItem to the link queue for processing.
584 */
585static void addApkFilesToLinkQueue(const std::u16string& package, const Source& source,
586 const std::shared_ptr<ResourceTable>& table,
587 const std::unique_ptr<ZipFile>& apk,
588 std::queue<LinkItem>* outLinkQueue) {
589 bool mangle = package != table->getPackage();
590 for (auto& type : *table) {
591 for (auto& entry : type->entries) {
592 ResourceName name = { package, type->type, entry->name };
593 if (mangle) {
594 NameMangler::mangle(table->getPackage(), &name.entry);
595 }
596
597 for (auto& value : entry->values) {
598 visitFunc<FileReference>(*value.value, [&](FileReference& ref) {
599 std::string pathUtf8 = util::utf16ToUtf8(*ref.path);
Adam Lesinski24aad162015-04-24 19:19:30 -0700600 Source newSource = source;
601 newSource.path += "/";
602 newSource.path += pathUtf8;
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700603 outLinkQueue->push(LinkItem{
Adam Lesinski24aad162015-04-24 19:19:30 -0700604 newSource, name, value.config, pathUtf8, apk.get(),
605 table->getPackage() });
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700606 // Now rewrite the file path.
607 if (mangle) {
608 ref.path = table->getValueStringPool().makeRef(util::utf8ToUtf16(
609 buildFileReference(name, value.config,
610 getExtension<char>(pathUtf8))));
611 }
612 });
613 }
614 }
615 }
616}
617
Adam Lesinski769de982015-04-10 19:43:55 -0700618static constexpr int kOpenFlags = ZipFile::kOpenCreate | ZipFile::kOpenTruncate |
619 ZipFile::kOpenReadWrite;
620
Adam Lesinski24aad162015-04-24 19:19:30 -0700621struct DeleteMalloc {
622 void operator()(void* ptr) {
623 free(ptr);
624 }
625};
626
627struct StaticLibraryData {
628 Source source;
629 std::unique_ptr<ZipFile> apk;
630};
631
Adam Lesinski769de982015-04-10 19:43:55 -0700632bool link(const AaptOptions& options, const std::shared_ptr<ResourceTable>& outTable,
Adam Lesinski330edcd2015-05-04 17:40:56 -0700633 const std::shared_ptr<IResolver>& resolver) {
Adam Lesinski24aad162015-04-24 19:19:30 -0700634 std::map<std::shared_ptr<ResourceTable>, StaticLibraryData> apkFiles;
Adam Lesinski769de982015-04-10 19:43:55 -0700635 std::unordered_set<std::u16string> linkedPackages;
636
637 // Populate the linkedPackages with our own.
638 linkedPackages.insert(options.appInfo.package);
639
640 // Load all APK files.
641 for (const Source& source : options.input) {
642 std::unique_ptr<ZipFile> zipFile = util::make_unique<ZipFile>();
643 if (zipFile->open(source.path.data(), ZipFile::kOpenReadOnly) != android::NO_ERROR) {
644 Logger::error(source) << "failed to open: " << strerror(errno) << std::endl;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700645 return false;
646 }
647
Adam Lesinski769de982015-04-10 19:43:55 -0700648 std::shared_ptr<ResourceTable> table = std::make_shared<ResourceTable>();
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700649
Adam Lesinski769de982015-04-10 19:43:55 -0700650 ZipEntry* entry = zipFile->getEntryByName("resources.arsc");
651 if (!entry) {
652 Logger::error(source) << "missing 'resources.arsc'." << std::endl;
653 return false;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700654 }
655
Adam Lesinski24aad162015-04-24 19:19:30 -0700656 std::unique_ptr<void, DeleteMalloc> uncompressedData = std::unique_ptr<void, DeleteMalloc>(
657 zipFile->uncompress(entry));
Adam Lesinski769de982015-04-10 19:43:55 -0700658 assert(uncompressedData);
659
Adam Lesinski24aad162015-04-24 19:19:30 -0700660 BinaryResourceParser parser(table, resolver, source, uncompressedData.get(),
Adam Lesinski769de982015-04-10 19:43:55 -0700661 entry->getUncompressedLen());
662 if (!parser.parse()) {
Adam Lesinski769de982015-04-10 19:43:55 -0700663 return false;
664 }
Adam Lesinski769de982015-04-10 19:43:55 -0700665
666 // Keep track of where this table came from.
Adam Lesinski24aad162015-04-24 19:19:30 -0700667 apkFiles[table] = StaticLibraryData{ source, std::move(zipFile) };
Adam Lesinski769de982015-04-10 19:43:55 -0700668
669 // Add the package to the set of linked packages.
670 linkedPackages.insert(table->getPackage());
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700671 }
672
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700673 std::queue<LinkItem> linkQueue;
Adam Lesinski769de982015-04-10 19:43:55 -0700674 for (auto& p : apkFiles) {
675 const std::shared_ptr<ResourceTable>& inTable = p.first;
676
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700677 // Collect all FileReferences and add them to the queue for processing.
Adam Lesinski24aad162015-04-24 19:19:30 -0700678 addApkFilesToLinkQueue(options.appInfo.package, p.second.source, inTable, p.second.apk,
679 &linkQueue);
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700680
681 // Merge the tables.
Adam Lesinski769de982015-04-10 19:43:55 -0700682 if (!outTable->merge(std::move(*inTable))) {
683 return false;
684 }
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700685 }
686
Adam Lesinski330edcd2015-05-04 17:40:56 -0700687 // Version all styles referencing attributes outside of their specified SDK version.
688 if (options.versionStylesAndLayouts) {
689 versionStylesForCompat(outTable);
690 }
691
Adam Lesinski769de982015-04-10 19:43:55 -0700692 {
693 // Now that everything is merged, let's link it.
Adam Lesinski330edcd2015-05-04 17:40:56 -0700694 Linker::Options linkerOptions;
695 if (options.packageType == AaptOptions::PackageType::StaticLibrary) {
696 linkerOptions.linkResourceIds = false;
697 }
698 Linker linker(outTable, resolver, linkerOptions);
Adam Lesinski769de982015-04-10 19:43:55 -0700699 if (!linker.linkAndValidate()) {
700 return false;
701 }
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700702
Adam Lesinski769de982015-04-10 19:43:55 -0700703 // Verify that all symbols exist.
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700704 const auto& unresolvedRefs = linker.getUnresolvedReferences();
705 if (!unresolvedRefs.empty()) {
706 for (const auto& entry : unresolvedRefs) {
707 for (const auto& source : entry.second) {
708 Logger::error(source) << "unresolved symbol '" << entry.first << "'."
709 << std::endl;
710 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800711 }
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700712 return false;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800713 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800714 }
715
Adam Lesinski769de982015-04-10 19:43:55 -0700716 // Open the output APK file for writing.
717 ZipFile outApk;
718 if (outApk.open(options.output.path.data(), kOpenFlags) != android::NO_ERROR) {
719 Logger::error(options.output) << "failed to open: " << strerror(errno) << std::endl;
720 return false;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700721 }
722
Adam Lesinski330edcd2015-05-04 17:40:56 -0700723 android::ResTable binTable;
724 if (!compileManifest(options, resolver, binTable, &outApk)) {
Adam Lesinski769de982015-04-10 19:43:55 -0700725 return false;
726 }
727
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700728 for (; !linkQueue.empty(); linkQueue.pop()) {
729 const LinkItem& item = linkQueue.front();
Adam Lesinski769de982015-04-10 19:43:55 -0700730
Adam Lesinski24aad162015-04-24 19:19:30 -0700731 assert(!item.originalPackage.empty());
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700732 ZipEntry* entry = item.apk->getEntryByName(item.originalPath.data());
733 if (!entry) {
734 Logger::error(item.source) << "failed to find '" << item.originalPath << "'."
735 << std::endl;
736 return false;
737 }
Adam Lesinski769de982015-04-10 19:43:55 -0700738
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700739 if (util::stringEndsWith<char>(item.originalPath, ".xml")) {
740 void* uncompressedData = item.apk->uncompress(entry);
741 assert(uncompressedData);
Adam Lesinski769de982015-04-10 19:43:55 -0700742
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700743 if (!linkXml(options, resolver, item, uncompressedData, entry->getUncompressedLen(),
Adam Lesinski330edcd2015-05-04 17:40:56 -0700744 &outApk, &linkQueue)) {
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700745 Logger::error(options.output) << "failed to link '" << item.originalPath << "'."
746 << std::endl;
747 return false;
Adam Lesinski769de982015-04-10 19:43:55 -0700748 }
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700749 } else {
750 if (outApk.add(item.apk, entry, buildFileReference(item).data(), 0, nullptr) !=
751 android::NO_ERROR) {
752 Logger::error(options.output) << "failed to copy '" << item.originalPath << "'."
753 << std::endl;
754 return false;
Adam Lesinski769de982015-04-10 19:43:55 -0700755 }
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700756 }
757 }
758
759 // Generate the Java class file.
Adam Lesinski769de982015-04-10 19:43:55 -0700760 if (options.generateJavaClass) {
Adam Lesinski6d8e4c42015-05-01 14:47:28 -0700761 JavaClassGenerator::Options javaOptions;
762 if (options.packageType == AaptOptions::PackageType::StaticLibrary) {
763 javaOptions.useFinal = false;
764 }
765 JavaClassGenerator generator(outTable, javaOptions);
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700766
Adam Lesinski769de982015-04-10 19:43:55 -0700767 for (const std::u16string& package : linkedPackages) {
768 Source outPath = options.generateJavaClass.value();
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800769
Adam Lesinski769de982015-04-10 19:43:55 -0700770 // Build the output directory from the package name.
771 // Eg. com.android.app -> com/android/app
772 const std::string packageUtf8 = util::utf16ToUtf8(package);
773 for (StringPiece part : util::tokenize<char>(packageUtf8, '.')) {
774 appendPath(&outPath.path, part);
775 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800776
Adam Lesinski769de982015-04-10 19:43:55 -0700777 if (!mkdirs(outPath.path)) {
778 Logger::error(outPath) << strerror(errno) << std::endl;
779 return false;
780 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800781
Adam Lesinski769de982015-04-10 19:43:55 -0700782 appendPath(&outPath.path, "R.java");
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800783
Adam Lesinski769de982015-04-10 19:43:55 -0700784 if (options.verbose) {
785 Logger::note(outPath) << "writing Java symbols." << std::endl;
786 }
787
788 std::ofstream fout(outPath.path);
789 if (!fout) {
790 Logger::error(outPath) << strerror(errno) << std::endl;
791 return false;
792 }
793
794 if (!generator.generate(package, fout)) {
795 Logger::error(outPath) << generator.getError() << "." << std::endl;
796 return false;
797 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800798 }
799 }
800
Adam Lesinski24aad162015-04-24 19:19:30 -0700801 outTable->getValueStringPool().prune();
802 outTable->getValueStringPool().sort(
803 [](const StringPool::Entry& a, const StringPool::Entry& b) -> bool {
804 if (a.context.priority < b.context.priority) {
805 return true;
806 }
807
808 if (a.context.priority > b.context.priority) {
809 return false;
810 }
811 return a.value < b.value;
812 });
813
814
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700815 // Flatten the resource table.
Adam Lesinski769de982015-04-10 19:43:55 -0700816 TableFlattener::Options flattenerOptions;
Adam Lesinski330edcd2015-05-04 17:40:56 -0700817 if (options.packageType != AaptOptions::PackageType::StaticLibrary) {
818 flattenerOptions.useExtendedChunks = false;
Adam Lesinski6d8e4c42015-05-01 14:47:28 -0700819 }
820
Adam Lesinski769de982015-04-10 19:43:55 -0700821 if (!writeResourceTable(options, outTable, flattenerOptions, &outApk)) {
822 return false;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800823 }
Adam Lesinski769de982015-04-10 19:43:55 -0700824
825 outApk.flush();
826 return true;
827}
828
829bool compile(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table,
Adam Lesinski24aad162015-04-24 19:19:30 -0700830 const std::shared_ptr<IResolver>& resolver) {
Adam Lesinski769de982015-04-10 19:43:55 -0700831 std::queue<CompileItem> compileQueue;
832 bool error = false;
833
834 // Compile all the resource files passed in on the command line.
835 for (const Source& source : options.input) {
836 // Need to parse the resource type/config/filename.
837 Maybe<ResourcePathData> maybePathData = extractResourcePathData(source);
838 if (!maybePathData) {
839 return false;
840 }
841
842 const ResourcePathData& pathData = maybePathData.value();
843 if (pathData.resourceDir == u"values") {
844 // The file is in the values directory, which means its contents will
845 // go into the resource table.
846 if (options.verbose) {
847 Logger::note(source) << "compiling values." << std::endl;
848 }
849
850 error |= !compileValues(table, source, pathData.config);
851 } else {
852 // The file is in a directory like 'layout' or 'drawable'. Find out
853 // the type.
854 const ResourceType* type = parseResourceType(pathData.resourceDir);
855 if (!type) {
856 Logger::error(source) << "invalid resource type '" << pathData.resourceDir << "'."
857 << std::endl;
858 return false;
859 }
860
861 compileQueue.push(CompileItem{
862 source,
863 ResourceName{ table->getPackage(), *type, pathData.name },
864 pathData.config,
865 pathData.extension
866 });
867 }
868 }
869
870 if (error) {
871 return false;
872 }
Adam Lesinski769de982015-04-10 19:43:55 -0700873 // Open the output APK file for writing.
874 ZipFile outApk;
875 if (outApk.open(options.output.path.data(), kOpenFlags) != android::NO_ERROR) {
876 Logger::error(options.output) << "failed to open: " << strerror(errno) << std::endl;
877 return false;
878 }
879
880 // Compile each file.
881 for (; !compileQueue.empty(); compileQueue.pop()) {
882 const CompileItem& item = compileQueue.front();
883
884 // Add the file name to the resource table.
885 error |= !addFileReference(table, item);
886
887 if (item.extension == "xml") {
Adam Lesinski330edcd2015-05-04 17:40:56 -0700888 error |= !compileXml(options, table, item, &outApk);
Adam Lesinski769de982015-04-10 19:43:55 -0700889 } else if (item.extension == "png" || item.extension == "9.png") {
890 error |= !compilePng(options, item, &outApk);
891 } else {
892 error |= !copyFile(options, item, &outApk);
893 }
894 }
895
896 if (error) {
897 return false;
898 }
899
900 // Link and assign resource IDs.
Adam Lesinski330edcd2015-05-04 17:40:56 -0700901 Linker linker(table, resolver, {});
Adam Lesinski769de982015-04-10 19:43:55 -0700902 if (!linker.linkAndValidate()) {
903 return false;
904 }
905
906 // Flatten the resource table.
907 if (!writeResourceTable(options, table, {}, &outApk)) {
908 return false;
909 }
910
911 outApk.flush();
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800912 return true;
913}
914
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700915bool loadAppInfo(const Source& source, AppInfo* outInfo) {
916 std::ifstream ifs(source.path, std::ifstream::in | std::ifstream::binary);
917 if (!ifs) {
918 Logger::error(source) << strerror(errno) << std::endl;
919 return false;
920 }
921
922 ManifestParser parser;
923 std::shared_ptr<XmlPullParser> pullParser = std::make_shared<SourceXmlPullParser>(ifs);
924 return parser.parse(source, pullParser, outInfo);
925}
926
927static void printCommandsAndDie() {
928 std::cerr << "The following commands are supported:" << std::endl << std::endl;
929 std::cerr << "compile compiles a subset of resources" << std::endl;
930 std::cerr << "link links together compiled resources and libraries" << std::endl;
Adam Lesinski330edcd2015-05-04 17:40:56 -0700931 std::cerr << "dump dumps resource contents to to standard out" << std::endl;
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700932 std::cerr << std::endl;
933 std::cerr << "run aapt2 with one of the commands and the -h flag for extra details."
934 << std::endl;
935 exit(1);
936}
937
938static AaptOptions prepareArgs(int argc, char** argv) {
939 if (argc < 2) {
940 std::cerr << "no command specified." << std::endl << std::endl;
941 printCommandsAndDie();
942 }
943
944 const StringPiece command(argv[1]);
945 argc -= 2;
946 argv += 2;
947
948 AaptOptions options;
949
950 if (command == "--version" || command == "version") {
951 std::cout << kAaptVersionStr << std::endl;
952 exit(0);
953 } else if (command == "link") {
954 options.phase = AaptOptions::Phase::Link;
955 } else if (command == "compile") {
956 options.phase = AaptOptions::Phase::Compile;
Adam Lesinski330edcd2015-05-04 17:40:56 -0700957 } else if (command == "dump") {
958 options.phase = AaptOptions::Phase::Dump;
959 } else if (command == "dump-style-graph") {
960 options.phase = AaptOptions::Phase::DumpStyleGraph;
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700961 } else {
962 std::cerr << "invalid command '" << command << "'." << std::endl << std::endl;
963 printCommandsAndDie();
964 }
965
Adam Lesinski6d8e4c42015-05-01 14:47:28 -0700966 bool isStaticLib = false;
Adam Lesinski330edcd2015-05-04 17:40:56 -0700967 if (options.phase == AaptOptions::Phase::Compile ||
968 options.phase == AaptOptions::Phase::Link) {
969 if (options.phase == AaptOptions::Phase::Compile) {
970 flag::requiredFlag("--package", "Android package name",
971 [&options](const StringPiece& arg) {
972 options.appInfo.package = util::utf8ToUtf16(arg);
973 });
974 } else if (options.phase == AaptOptions::Phase::Link) {
975 flag::requiredFlag("--manifest", "AndroidManifest.xml of your app",
976 [&options](const StringPiece& arg) {
977 options.manifest = Source{ arg.toString() };
978 });
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700979
Adam Lesinski330edcd2015-05-04 17:40:56 -0700980 flag::optionalFlag("-I", "add an Android APK to link against",
981 [&options](const StringPiece& arg) {
982 options.libraries.push_back(Source{ arg.toString() });
983 });
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700984
Adam Lesinski330edcd2015-05-04 17:40:56 -0700985 flag::optionalFlag("--java", "directory in which to generate R.java",
986 [&options](const StringPiece& arg) {
987 options.generateJavaClass = Source{ arg.toString() };
988 });
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700989
Adam Lesinski330edcd2015-05-04 17:40:56 -0700990 flag::optionalSwitch("--static-lib", "generate a static Android library", true,
991 &isStaticLib);
992
993 flag::optionalFlag("--binding", "Output directory for binding XML files",
994 [&options](const StringPiece& arg) {
995 options.bindingOutput = Source{ arg.toString() };
996 });
997 flag::optionalSwitch("--no-version", "Disables automatic style and layout versioning",
998 false, &options.versionStylesAndLayouts);
999 }
1000
1001 // Common flags for all steps.
1002 flag::requiredFlag("-o", "Output path", [&options](const StringPiece& arg) {
1003 options.output = Source{ arg.toString() };
1004 });
Adam Lesinskid5c4f872015-04-21 13:56:10 -07001005 }
1006
Adam Lesinskid5c4f872015-04-21 13:56:10 -07001007 bool help = false;
1008 flag::optionalSwitch("-v", "enables verbose logging", true, &options.verbose);
1009 flag::optionalSwitch("-h", "displays this help menu", true, &help);
1010
1011 // Build the command string for output (eg. "aapt2 compile").
1012 std::string fullCommand = "aapt2";
1013 fullCommand += " ";
1014 fullCommand += command.toString();
1015
1016 // Actually read the command line flags.
1017 flag::parse(argc, argv, fullCommand);
1018
1019 if (help) {
1020 flag::usageAndDie(fullCommand);
1021 }
1022
Adam Lesinski6d8e4c42015-05-01 14:47:28 -07001023 if (isStaticLib) {
1024 options.packageType = AaptOptions::PackageType::StaticLibrary;
1025 }
1026
Adam Lesinskid5c4f872015-04-21 13:56:10 -07001027 // Copy all the remaining arguments.
1028 for (const std::string& arg : flag::getArgs()) {
1029 options.input.push_back(Source{ arg });
1030 }
1031 return options;
1032}
1033
Adam Lesinski330edcd2015-05-04 17:40:56 -07001034static bool doDump(const AaptOptions& options) {
1035 for (const Source& source : options.input) {
1036 std::unique_ptr<ZipFile> zipFile = util::make_unique<ZipFile>();
1037 if (zipFile->open(source.path.data(), ZipFile::kOpenReadOnly) != android::NO_ERROR) {
1038 Logger::error(source) << "failed to open: " << strerror(errno) << std::endl;
1039 return false;
1040 }
1041
1042 std::shared_ptr<ResourceTable> table = std::make_shared<ResourceTable>();
1043 std::shared_ptr<ResourceTableResolver> resolver =
1044 std::make_shared<ResourceTableResolver>(
1045 table, std::vector<std::shared_ptr<const android::AssetManager>>());
1046
1047 ZipEntry* entry = zipFile->getEntryByName("resources.arsc");
1048 if (!entry) {
1049 Logger::error(source) << "missing 'resources.arsc'." << std::endl;
1050 return false;
1051 }
1052
1053 std::unique_ptr<void, DeleteMalloc> uncompressedData = std::unique_ptr<void, DeleteMalloc>(
1054 zipFile->uncompress(entry));
1055 assert(uncompressedData);
1056
1057 BinaryResourceParser parser(table, resolver, source, uncompressedData.get(),
1058 entry->getUncompressedLen());
1059 if (!parser.parse()) {
1060 return false;
1061 }
1062
1063 if (options.phase == AaptOptions::Phase::Dump) {
1064 Debug::printTable(table);
1065 } else if (options.phase == AaptOptions::Phase::DumpStyleGraph) {
1066 Debug::printStyleGraph(table);
1067 }
1068 }
1069 return true;
1070}
1071
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001072int main(int argc, char** argv) {
1073 Logger::setLog(std::make_shared<Log>(std::cerr, std::cerr));
Adam Lesinski98aa3ad2015-04-06 11:46:52 -07001074 AaptOptions options = prepareArgs(argc, argv);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001075
Adam Lesinski330edcd2015-05-04 17:40:56 -07001076 if (options.phase == AaptOptions::Phase::Dump ||
1077 options.phase == AaptOptions::Phase::DumpStyleGraph) {
1078 if (!doDump(options)) {
1079 return 1;
1080 }
1081 return 0;
1082 }
1083
Adam Lesinski98aa3ad2015-04-06 11:46:52 -07001084 // If we specified a manifest, go ahead and load the package name from the manifest.
1085 if (!options.manifest.path.empty()) {
1086 if (!loadAppInfo(options.manifest, &options.appInfo)) {
1087 return false;
1088 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001089 }
1090
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001091 // Verify we have some common options set.
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001092 if (options.appInfo.package.empty()) {
1093 Logger::error() << "no package name specified." << std::endl;
1094 return false;
1095 }
1096
Adam Lesinski98aa3ad2015-04-06 11:46:52 -07001097 // Every phase needs a resource table.
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001098 std::shared_ptr<ResourceTable> table = std::make_shared<ResourceTable>();
1099 table->setPackage(options.appInfo.package);
1100 if (options.appInfo.package == u"android") {
1101 table->setPackageId(0x01);
1102 } else {
1103 table->setPackageId(0x7f);
1104 }
1105
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001106 // Load the included libraries.
Adam Lesinski330edcd2015-05-04 17:40:56 -07001107 std::vector<std::shared_ptr<const android::AssetManager>> sources;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001108 for (const Source& source : options.libraries) {
Adam Lesinski330edcd2015-05-04 17:40:56 -07001109 std::shared_ptr<android::AssetManager> assetManager =
1110 std::make_shared<android::AssetManager>();
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001111 int32_t cookie;
Adam Lesinski330edcd2015-05-04 17:40:56 -07001112 if (!assetManager->addAssetPath(android::String8(source.path.data()), &cookie)) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001113 Logger::error(source) << "failed to load library." << std::endl;
1114 return false;
1115 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001116
Adam Lesinski330edcd2015-05-04 17:40:56 -07001117 if (cookie == 0) {
1118 Logger::error(source) << "failed to load library." << std::endl;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001119 return false;
1120 }
Adam Lesinski330edcd2015-05-04 17:40:56 -07001121 sources.push_back(assetManager);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001122 }
1123
1124 // Make the resolver that will cache IDs for us.
Adam Lesinski24aad162015-04-24 19:19:30 -07001125 std::shared_ptr<ResourceTableResolver> resolver = std::make_shared<ResourceTableResolver>(
Adam Lesinski330edcd2015-05-04 17:40:56 -07001126 table, sources);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001127
Adam Lesinski769de982015-04-10 19:43:55 -07001128 if (options.phase == AaptOptions::Phase::Compile) {
1129 if (!compile(options, table, resolver)) {
1130 Logger::error() << "aapt exiting with failures." << std::endl;
1131 return 1;
1132 }
1133 } else if (options.phase == AaptOptions::Phase::Link) {
1134 if (!link(options, table, resolver)) {
1135 Logger::error() << "aapt exiting with failures." << std::endl;
1136 return 1;
1137 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001138 }
1139 return 0;
1140}