blob: e0977b8bb88aa97cd41586474b5c64b3e4aa6324 [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 Lesinski769de982015-04-10 19:43:55 -0700296};
297
298bool compileXml(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table,
Adam Lesinski330edcd2015-05-04 17:40:56 -0700299 const CompileItem& item, ZipFile* outApk) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800300 std::ifstream in(item.source.path, std::ifstream::binary);
301 if (!in) {
302 Logger::error(item.source) << strerror(errno) << std::endl;
303 return false;
304 }
305
Adam Lesinski4d3a9872015-04-09 19:53:22 -0700306 BigBuffer outBuffer(1024);
Adam Lesinski769de982015-04-10 19:43:55 -0700307
308 // No resolver, since we are not compiling attributes here.
309 XmlFlattener flattener(table, {});
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800310
Adam Lesinski769de982015-04-10 19:43:55 -0700311 XmlFlattener::Options xmlOptions;
Adam Lesinski24aad162015-04-24 19:19:30 -0700312 xmlOptions.defaultPackage = table->getPackage();
Adam Lesinski330edcd2015-05-04 17:40:56 -0700313 xmlOptions.keepRawValues = true;
314
315 std::shared_ptr<XmlPullParser> parser = std::make_shared<SourceXmlPullParser>(in);
316
317 Maybe<size_t> minStrippedSdk = flattener.flatten(item.source, parser, &outBuffer,
318 xmlOptions);
319 if (!minStrippedSdk) {
320 return false;
321 }
322
323 // Write the resulting compiled XML file to the output APK.
324 if (outApk->add(outBuffer, buildFileReference(item).data(), ZipEntry::kCompressStored,
325 nullptr) != android::NO_ERROR) {
326 Logger::error(options.output) << "failed to write compiled '" << item.source
327 << "' to apk." << std::endl;
328 return false;
329 }
330 return true;
331}
332
333bool linkXml(const AaptOptions& options, const std::shared_ptr<IResolver>& resolver,
334 const LinkItem& item, const void* data, size_t dataLen, ZipFile* outApk,
335 std::queue<LinkItem>* outQueue) {
336 std::shared_ptr<android::ResXMLTree> tree = std::make_shared<android::ResXMLTree>();
337 if (tree->setTo(data, dataLen, false) != android::NO_ERROR) {
338 return false;
339 }
340
341 std::shared_ptr<XmlPullParser> parser = std::make_shared<BinaryXmlPullParser>(tree);
342
343 BigBuffer outBuffer(1024);
344 XmlFlattener flattener({}, resolver);
345
346 XmlFlattener::Options xmlOptions;
347 xmlOptions.defaultPackage = item.originalPackage;
348
349 if (options.packageType == AaptOptions::PackageType::StaticLibrary) {
350 xmlOptions.keepRawValues = true;
351 }
Adam Lesinski24aad162015-04-24 19:19:30 -0700352
Adam Lesinski5886a922015-04-15 20:29:22 -0700353 if (options.versionStylesAndLayouts) {
354 // We strip attributes that do not belong in this version of the resource.
355 // Non-version qualified resources have an implicit version 1 requirement.
356 xmlOptions.maxSdkAttribute = item.config.sdkVersion ? item.config.sdkVersion : 1;
357 }
Adam Lesinski769de982015-04-10 19:43:55 -0700358
359 std::shared_ptr<BindingXmlPullParser> binding;
Adam Lesinski769de982015-04-10 19:43:55 -0700360 if (item.name.type == ResourceType::kLayout) {
361 // Layouts may have defined bindings, so we need to make sure they get processed.
362 binding = std::make_shared<BindingXmlPullParser>(parser);
363 parser = binding;
364 }
365
Adam Lesinski330edcd2015-05-04 17:40:56 -0700366 Maybe<size_t> minStrippedSdk = flattener.flatten(item.source, parser, &outBuffer,
367 xmlOptions);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800368 if (!minStrippedSdk) {
369 return false;
370 }
371
372 if (minStrippedSdk.value() > 0) {
373 // Something was stripped, so let's generate a new file
374 // with the version of the smallest SDK version stripped.
Adam Lesinski330edcd2015-05-04 17:40:56 -0700375 LinkItem newWork = item;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800376 newWork.config.sdkVersion = minStrippedSdk.value();
Adam Lesinski769de982015-04-10 19:43:55 -0700377 outQueue->push(newWork);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800378 }
379
Adam Lesinski330edcd2015-05-04 17:40:56 -0700380 if (outApk->add(outBuffer, buildFileReference(item).data(), ZipEntry::kCompressDeflated,
Adam Lesinski769de982015-04-10 19:43:55 -0700381 nullptr) != android::NO_ERROR) {
Adam Lesinski330edcd2015-05-04 17:40:56 -0700382 Logger::error(options.output) << "failed to write linked file '"
383 << buildFileReference(item) << "' to apk." << std::endl;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800384 return false;
385 }
386
Adam Lesinski769de982015-04-10 19:43:55 -0700387 if (binding && !options.bindingOutput.path.empty()) {
388 // We generated a binding xml file, write it out.
389 Source bindingOutput = options.bindingOutput;
390 appendPath(&bindingOutput.path, buildFileReference(item));
Adam Lesinski4d3a9872015-04-09 19:53:22 -0700391
Adam Lesinski769de982015-04-10 19:43:55 -0700392 if (!mkdirs(bindingOutput.path)) {
393 Logger::error(bindingOutput) << strerror(errno) << std::endl;
394 return false;
395 }
396
397 appendPath(&bindingOutput.path, "bind.xml");
398
Adam Lesinski4d3a9872015-04-09 19:53:22 -0700399 std::ofstream bout(bindingOutput.path);
400 if (!bout) {
401 Logger::error(bindingOutput) << strerror(errno) << std::endl;
402 return false;
403 }
404
405 if (!binding->writeToFile(bout)) {
406 Logger::error(bindingOutput) << strerror(errno) << std::endl;
407 return false;
408 }
409 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800410 return true;
411}
412
Adam Lesinski769de982015-04-10 19:43:55 -0700413bool compilePng(const AaptOptions& options, const CompileItem& item, ZipFile* outApk) {
414 std::ifstream in(item.source.path, std::ifstream::binary);
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700415 if (!in) {
Adam Lesinski769de982015-04-10 19:43:55 -0700416 Logger::error(item.source) << strerror(errno) << std::endl;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700417 return false;
418 }
419
Adam Lesinski769de982015-04-10 19:43:55 -0700420 BigBuffer outBuffer(4096);
421 std::string err;
422 Png png;
423 if (!png.process(item.source, in, &outBuffer, {}, &err)) {
424 Logger::error(item.source) << err << std::endl;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700425 return false;
426 }
427
Adam Lesinski769de982015-04-10 19:43:55 -0700428 if (outApk->add(outBuffer, buildFileReference(item).data(), ZipEntry::kCompressStored,
429 nullptr) != android::NO_ERROR) {
430 Logger::error(options.output) << "failed to write compiled '" << item.source
431 << "' to apk." << std::endl;
432 return false;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700433 }
Adam Lesinski769de982015-04-10 19:43:55 -0700434 return true;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700435}
436
Adam Lesinski769de982015-04-10 19:43:55 -0700437bool copyFile(const AaptOptions& options, const CompileItem& item, ZipFile* outApk) {
438 if (outApk->add(item.source.path.data(), buildFileReference(item).data(),
439 ZipEntry::kCompressStored, nullptr) != android::NO_ERROR) {
440 Logger::error(options.output) << "failed to copy file '" << item.source << "' to apk."
441 << std::endl;
442 return false;
443 }
444 return true;
445}
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800446
Adam Lesinski330edcd2015-05-04 17:40:56 -0700447bool compileManifest(const AaptOptions& options, const std::shared_ptr<IResolver>& resolver,
448 const android::ResTable& table, ZipFile* outApk) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800449 if (options.verbose) {
Adam Lesinski769de982015-04-10 19:43:55 -0700450 Logger::note(options.manifest) << "compiling AndroidManifest.xml." << std::endl;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800451 }
452
453 std::ifstream in(options.manifest.path, std::ifstream::binary);
454 if (!in) {
455 Logger::error(options.manifest) << strerror(errno) << std::endl;
456 return false;
457 }
458
459 BigBuffer outBuffer(1024);
460 std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<SourceXmlPullParser>(in);
Adam Lesinski769de982015-04-10 19:43:55 -0700461 XmlFlattener flattener({}, resolver);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800462
Adam Lesinski24aad162015-04-24 19:19:30 -0700463 XmlFlattener::Options xmlOptions;
464 xmlOptions.defaultPackage = options.appInfo.package;
465 if (!flattener.flatten(options.manifest, xmlParser, &outBuffer, xmlOptions)) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800466 return false;
467 }
468
Adam Lesinski769de982015-04-10 19:43:55 -0700469 std::unique_ptr<uint8_t[]> data = util::copy(outBuffer);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800470
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700471 android::ResXMLTree tree;
Adam Lesinski769de982015-04-10 19:43:55 -0700472 if (tree.setTo(data.get(), outBuffer.size(), false) != android::NO_ERROR) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800473 return false;
474 }
475
Adam Lesinski330edcd2015-05-04 17:40:56 -0700476 ManifestValidator validator(table);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800477 if (!validator.validate(options.manifest, &tree)) {
478 return false;
479 }
480
Adam Lesinski769de982015-04-10 19:43:55 -0700481 if (outApk->add(data.get(), outBuffer.size(), "AndroidManifest.xml",
482 ZipEntry::kCompressStored, nullptr) != android::NO_ERROR) {
483 Logger::error(options.output) << "failed to write 'AndroidManifest.xml' to apk."
484 << std::endl;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800485 return false;
486 }
487 return true;
488}
489
Adam Lesinski769de982015-04-10 19:43:55 -0700490static bool compileValues(const std::shared_ptr<ResourceTable>& table, const Source& source,
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700491 const ConfigDescription& config) {
492 std::ifstream in(source.path, std::ifstream::binary);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800493 if (!in) {
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700494 Logger::error(source) << strerror(errno) << std::endl;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800495 return false;
496 }
497
498 std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<SourceXmlPullParser>(in);
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700499 ResourceParser parser(table, source, config, xmlParser);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800500 return parser.parse();
501}
502
503struct ResourcePathData {
504 std::u16string resourceDir;
505 std::u16string name;
506 std::string extension;
507 ConfigDescription config;
508};
509
510/**
511 * Resource file paths are expected to look like:
512 * [--/res/]type[-config]/name
513 */
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700514static Maybe<ResourcePathData> extractResourcePathData(const Source& source) {
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700515 // TODO(adamlesinski): Use Windows path separator on windows.
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800516 std::vector<std::string> parts = util::splitAndLowercase(source.path, '/');
517 if (parts.size() < 2) {
518 Logger::error(source) << "bad resource path." << std::endl;
519 return {};
520 }
521
522 std::string& dir = parts[parts.size() - 2];
523 StringPiece dirStr = dir;
524
525 ConfigDescription config;
526 size_t dashPos = dir.find('-');
527 if (dashPos != std::string::npos) {
528 StringPiece configStr = dirStr.substr(dashPos + 1, dir.size() - (dashPos + 1));
529 if (!ConfigDescription::parse(configStr, &config)) {
530 Logger::error(source)
531 << "invalid configuration '"
532 << configStr
533 << "'."
534 << std::endl;
535 return {};
536 }
537 dirStr = dirStr.substr(0, dashPos);
538 }
539
540 std::string& filename = parts[parts.size() - 1];
541 StringPiece name = filename;
542 StringPiece extension;
543 size_t dotPos = filename.find('.');
544 if (dotPos != std::string::npos) {
545 extension = name.substr(dotPos + 1, filename.size() - (dotPos + 1));
546 name = name.substr(0, dotPos);
547 }
548
549 return ResourcePathData{
550 util::utf8ToUtf16(dirStr),
551 util::utf8ToUtf16(name),
552 extension.toString(),
553 config
554 };
555}
556
Adam Lesinski769de982015-04-10 19:43:55 -0700557bool writeResourceTable(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table,
558 const TableFlattener::Options& flattenerOptions, ZipFile* outApk) {
559 if (table->begin() != table->end()) {
560 BigBuffer buffer(1024);
561 TableFlattener flattener(flattenerOptions);
562 if (!flattener.flatten(&buffer, *table)) {
563 Logger::error() << "failed to flatten resource table." << std::endl;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700564 return false;
565 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800566
Adam Lesinski769de982015-04-10 19:43:55 -0700567 if (options.verbose) {
568 Logger::note() << "Final resource table size=" << util::formatSize(buffer.size())
569 << std::endl;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700570 }
571
Adam Lesinski769de982015-04-10 19:43:55 -0700572 if (outApk->add(buffer, "resources.arsc", ZipEntry::kCompressStored, nullptr) !=
573 android::NO_ERROR) {
574 Logger::note(options.output) << "failed to store resource table." << std::endl;
575 return false;
576 }
577 }
578 return true;
579}
580
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700581/**
582 * For each FileReference in the table, adds a LinkItem to the link queue for processing.
583 */
584static void addApkFilesToLinkQueue(const std::u16string& package, const Source& source,
585 const std::shared_ptr<ResourceTable>& table,
586 const std::unique_ptr<ZipFile>& apk,
587 std::queue<LinkItem>* outLinkQueue) {
588 bool mangle = package != table->getPackage();
589 for (auto& type : *table) {
590 for (auto& entry : type->entries) {
591 ResourceName name = { package, type->type, entry->name };
592 if (mangle) {
593 NameMangler::mangle(table->getPackage(), &name.entry);
594 }
595
596 for (auto& value : entry->values) {
597 visitFunc<FileReference>(*value.value, [&](FileReference& ref) {
598 std::string pathUtf8 = util::utf16ToUtf8(*ref.path);
Adam Lesinski24aad162015-04-24 19:19:30 -0700599 Source newSource = source;
600 newSource.path += "/";
601 newSource.path += pathUtf8;
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700602 outLinkQueue->push(LinkItem{
Adam Lesinski24aad162015-04-24 19:19:30 -0700603 newSource, name, value.config, pathUtf8, apk.get(),
604 table->getPackage() });
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700605 // Now rewrite the file path.
606 if (mangle) {
607 ref.path = table->getValueStringPool().makeRef(util::utf8ToUtf16(
608 buildFileReference(name, value.config,
609 getExtension<char>(pathUtf8))));
610 }
611 });
612 }
613 }
614 }
615}
616
Adam Lesinski769de982015-04-10 19:43:55 -0700617static constexpr int kOpenFlags = ZipFile::kOpenCreate | ZipFile::kOpenTruncate |
618 ZipFile::kOpenReadWrite;
619
Adam Lesinski24aad162015-04-24 19:19:30 -0700620struct DeleteMalloc {
621 void operator()(void* ptr) {
622 free(ptr);
623 }
624};
625
626struct StaticLibraryData {
627 Source source;
628 std::unique_ptr<ZipFile> apk;
629};
630
Adam Lesinski769de982015-04-10 19:43:55 -0700631bool link(const AaptOptions& options, const std::shared_ptr<ResourceTable>& outTable,
Adam Lesinski330edcd2015-05-04 17:40:56 -0700632 const std::shared_ptr<IResolver>& resolver) {
Adam Lesinski24aad162015-04-24 19:19:30 -0700633 std::map<std::shared_ptr<ResourceTable>, StaticLibraryData> apkFiles;
Adam Lesinski769de982015-04-10 19:43:55 -0700634 std::unordered_set<std::u16string> linkedPackages;
635
636 // Populate the linkedPackages with our own.
637 linkedPackages.insert(options.appInfo.package);
638
639 // Load all APK files.
640 for (const Source& source : options.input) {
641 std::unique_ptr<ZipFile> zipFile = util::make_unique<ZipFile>();
642 if (zipFile->open(source.path.data(), ZipFile::kOpenReadOnly) != android::NO_ERROR) {
643 Logger::error(source) << "failed to open: " << strerror(errno) << std::endl;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700644 return false;
645 }
646
Adam Lesinski769de982015-04-10 19:43:55 -0700647 std::shared_ptr<ResourceTable> table = std::make_shared<ResourceTable>();
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700648
Adam Lesinski769de982015-04-10 19:43:55 -0700649 ZipEntry* entry = zipFile->getEntryByName("resources.arsc");
650 if (!entry) {
651 Logger::error(source) << "missing 'resources.arsc'." << std::endl;
652 return false;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700653 }
654
Adam Lesinski24aad162015-04-24 19:19:30 -0700655 std::unique_ptr<void, DeleteMalloc> uncompressedData = std::unique_ptr<void, DeleteMalloc>(
656 zipFile->uncompress(entry));
Adam Lesinski769de982015-04-10 19:43:55 -0700657 assert(uncompressedData);
658
Adam Lesinski24aad162015-04-24 19:19:30 -0700659 BinaryResourceParser parser(table, resolver, source, uncompressedData.get(),
Adam Lesinski769de982015-04-10 19:43:55 -0700660 entry->getUncompressedLen());
661 if (!parser.parse()) {
Adam Lesinski769de982015-04-10 19:43:55 -0700662 return false;
663 }
Adam Lesinski769de982015-04-10 19:43:55 -0700664
665 // Keep track of where this table came from.
Adam Lesinski24aad162015-04-24 19:19:30 -0700666 apkFiles[table] = StaticLibraryData{ source, std::move(zipFile) };
Adam Lesinski769de982015-04-10 19:43:55 -0700667
668 // Add the package to the set of linked packages.
669 linkedPackages.insert(table->getPackage());
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700670 }
671
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700672 std::queue<LinkItem> linkQueue;
Adam Lesinski769de982015-04-10 19:43:55 -0700673 for (auto& p : apkFiles) {
674 const std::shared_ptr<ResourceTable>& inTable = p.first;
675
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700676 // Collect all FileReferences and add them to the queue for processing.
Adam Lesinski24aad162015-04-24 19:19:30 -0700677 addApkFilesToLinkQueue(options.appInfo.package, p.second.source, inTable, p.second.apk,
678 &linkQueue);
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700679
680 // Merge the tables.
Adam Lesinski769de982015-04-10 19:43:55 -0700681 if (!outTable->merge(std::move(*inTable))) {
682 return false;
683 }
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700684 }
685
Adam Lesinski330edcd2015-05-04 17:40:56 -0700686 // Version all styles referencing attributes outside of their specified SDK version.
687 if (options.versionStylesAndLayouts) {
688 versionStylesForCompat(outTable);
689 }
690
Adam Lesinski769de982015-04-10 19:43:55 -0700691 {
692 // Now that everything is merged, let's link it.
Adam Lesinski330edcd2015-05-04 17:40:56 -0700693 Linker::Options linkerOptions;
694 if (options.packageType == AaptOptions::PackageType::StaticLibrary) {
695 linkerOptions.linkResourceIds = false;
696 }
697 Linker linker(outTable, resolver, linkerOptions);
Adam Lesinski769de982015-04-10 19:43:55 -0700698 if (!linker.linkAndValidate()) {
699 return false;
700 }
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700701
Adam Lesinski769de982015-04-10 19:43:55 -0700702 // Verify that all symbols exist.
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700703 const auto& unresolvedRefs = linker.getUnresolvedReferences();
704 if (!unresolvedRefs.empty()) {
705 for (const auto& entry : unresolvedRefs) {
706 for (const auto& source : entry.second) {
707 Logger::error(source) << "unresolved symbol '" << entry.first << "'."
708 << std::endl;
709 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800710 }
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700711 return false;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800712 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800713 }
714
Adam Lesinski769de982015-04-10 19:43:55 -0700715 // Open the output APK file for writing.
716 ZipFile outApk;
717 if (outApk.open(options.output.path.data(), kOpenFlags) != android::NO_ERROR) {
718 Logger::error(options.output) << "failed to open: " << strerror(errno) << std::endl;
719 return false;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700720 }
721
Adam Lesinski330edcd2015-05-04 17:40:56 -0700722 android::ResTable binTable;
723 if (!compileManifest(options, resolver, binTable, &outApk)) {
Adam Lesinski769de982015-04-10 19:43:55 -0700724 return false;
725 }
726
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700727 for (; !linkQueue.empty(); linkQueue.pop()) {
728 const LinkItem& item = linkQueue.front();
Adam Lesinski769de982015-04-10 19:43:55 -0700729
Adam Lesinski24aad162015-04-24 19:19:30 -0700730 assert(!item.originalPackage.empty());
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700731 ZipEntry* entry = item.apk->getEntryByName(item.originalPath.data());
732 if (!entry) {
733 Logger::error(item.source) << "failed to find '" << item.originalPath << "'."
734 << std::endl;
735 return false;
736 }
Adam Lesinski769de982015-04-10 19:43:55 -0700737
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700738 if (util::stringEndsWith<char>(item.originalPath, ".xml")) {
739 void* uncompressedData = item.apk->uncompress(entry);
740 assert(uncompressedData);
Adam Lesinski769de982015-04-10 19:43:55 -0700741
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700742 if (!linkXml(options, resolver, item, uncompressedData, entry->getUncompressedLen(),
Adam Lesinski330edcd2015-05-04 17:40:56 -0700743 &outApk, &linkQueue)) {
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700744 Logger::error(options.output) << "failed to link '" << item.originalPath << "'."
745 << std::endl;
746 return false;
Adam Lesinski769de982015-04-10 19:43:55 -0700747 }
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700748 } else {
749 if (outApk.add(item.apk, entry, buildFileReference(item).data(), 0, nullptr) !=
750 android::NO_ERROR) {
751 Logger::error(options.output) << "failed to copy '" << item.originalPath << "'."
752 << std::endl;
753 return false;
Adam Lesinski769de982015-04-10 19:43:55 -0700754 }
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700755 }
756 }
757
758 // Generate the Java class file.
Adam Lesinski769de982015-04-10 19:43:55 -0700759 if (options.generateJavaClass) {
Adam Lesinski6d8e4c42015-05-01 14:47:28 -0700760 JavaClassGenerator::Options javaOptions;
761 if (options.packageType == AaptOptions::PackageType::StaticLibrary) {
762 javaOptions.useFinal = false;
763 }
764 JavaClassGenerator generator(outTable, javaOptions);
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700765
Adam Lesinski769de982015-04-10 19:43:55 -0700766 for (const std::u16string& package : linkedPackages) {
767 Source outPath = options.generateJavaClass.value();
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800768
Adam Lesinski769de982015-04-10 19:43:55 -0700769 // Build the output directory from the package name.
770 // Eg. com.android.app -> com/android/app
771 const std::string packageUtf8 = util::utf16ToUtf8(package);
772 for (StringPiece part : util::tokenize<char>(packageUtf8, '.')) {
773 appendPath(&outPath.path, part);
774 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800775
Adam Lesinski769de982015-04-10 19:43:55 -0700776 if (!mkdirs(outPath.path)) {
777 Logger::error(outPath) << strerror(errno) << std::endl;
778 return false;
779 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800780
Adam Lesinski769de982015-04-10 19:43:55 -0700781 appendPath(&outPath.path, "R.java");
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800782
Adam Lesinski769de982015-04-10 19:43:55 -0700783 if (options.verbose) {
784 Logger::note(outPath) << "writing Java symbols." << std::endl;
785 }
786
787 std::ofstream fout(outPath.path);
788 if (!fout) {
789 Logger::error(outPath) << strerror(errno) << std::endl;
790 return false;
791 }
792
793 if (!generator.generate(package, fout)) {
794 Logger::error(outPath) << generator.getError() << "." << std::endl;
795 return false;
796 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800797 }
798 }
799
Adam Lesinski24aad162015-04-24 19:19:30 -0700800 outTable->getValueStringPool().prune();
801 outTable->getValueStringPool().sort(
802 [](const StringPool::Entry& a, const StringPool::Entry& b) -> bool {
803 if (a.context.priority < b.context.priority) {
804 return true;
805 }
806
807 if (a.context.priority > b.context.priority) {
808 return false;
809 }
810 return a.value < b.value;
811 });
812
813
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700814 // Flatten the resource table.
Adam Lesinski769de982015-04-10 19:43:55 -0700815 TableFlattener::Options flattenerOptions;
Adam Lesinski330edcd2015-05-04 17:40:56 -0700816 if (options.packageType != AaptOptions::PackageType::StaticLibrary) {
817 flattenerOptions.useExtendedChunks = false;
Adam Lesinski6d8e4c42015-05-01 14:47:28 -0700818 }
819
Adam Lesinski769de982015-04-10 19:43:55 -0700820 if (!writeResourceTable(options, outTable, flattenerOptions, &outApk)) {
821 return false;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800822 }
Adam Lesinski769de982015-04-10 19:43:55 -0700823
824 outApk.flush();
825 return true;
826}
827
828bool compile(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table,
Adam Lesinski24aad162015-04-24 19:19:30 -0700829 const std::shared_ptr<IResolver>& resolver) {
Adam Lesinski769de982015-04-10 19:43:55 -0700830 std::queue<CompileItem> compileQueue;
831 bool error = false;
832
833 // Compile all the resource files passed in on the command line.
834 for (const Source& source : options.input) {
835 // Need to parse the resource type/config/filename.
836 Maybe<ResourcePathData> maybePathData = extractResourcePathData(source);
837 if (!maybePathData) {
838 return false;
839 }
840
841 const ResourcePathData& pathData = maybePathData.value();
842 if (pathData.resourceDir == u"values") {
843 // The file is in the values directory, which means its contents will
844 // go into the resource table.
845 if (options.verbose) {
846 Logger::note(source) << "compiling values." << std::endl;
847 }
848
849 error |= !compileValues(table, source, pathData.config);
850 } else {
851 // The file is in a directory like 'layout' or 'drawable'. Find out
852 // the type.
853 const ResourceType* type = parseResourceType(pathData.resourceDir);
854 if (!type) {
855 Logger::error(source) << "invalid resource type '" << pathData.resourceDir << "'."
856 << std::endl;
857 return false;
858 }
859
860 compileQueue.push(CompileItem{
861 source,
862 ResourceName{ table->getPackage(), *type, pathData.name },
863 pathData.config,
864 pathData.extension
865 });
866 }
867 }
868
869 if (error) {
870 return false;
871 }
Adam Lesinski769de982015-04-10 19:43:55 -0700872 // Open the output APK file for writing.
873 ZipFile outApk;
874 if (outApk.open(options.output.path.data(), kOpenFlags) != android::NO_ERROR) {
875 Logger::error(options.output) << "failed to open: " << strerror(errno) << std::endl;
876 return false;
877 }
878
879 // Compile each file.
880 for (; !compileQueue.empty(); compileQueue.pop()) {
881 const CompileItem& item = compileQueue.front();
882
883 // Add the file name to the resource table.
884 error |= !addFileReference(table, item);
885
886 if (item.extension == "xml") {
Adam Lesinski330edcd2015-05-04 17:40:56 -0700887 error |= !compileXml(options, table, item, &outApk);
Adam Lesinski769de982015-04-10 19:43:55 -0700888 } else if (item.extension == "png" || item.extension == "9.png") {
889 error |= !compilePng(options, item, &outApk);
890 } else {
891 error |= !copyFile(options, item, &outApk);
892 }
893 }
894
895 if (error) {
896 return false;
897 }
898
899 // Link and assign resource IDs.
Adam Lesinski330edcd2015-05-04 17:40:56 -0700900 Linker linker(table, resolver, {});
Adam Lesinski769de982015-04-10 19:43:55 -0700901 if (!linker.linkAndValidate()) {
902 return false;
903 }
904
905 // Flatten the resource table.
906 if (!writeResourceTable(options, table, {}, &outApk)) {
907 return false;
908 }
909
910 outApk.flush();
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800911 return true;
912}
913
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700914bool loadAppInfo(const Source& source, AppInfo* outInfo) {
915 std::ifstream ifs(source.path, std::ifstream::in | std::ifstream::binary);
916 if (!ifs) {
917 Logger::error(source) << strerror(errno) << std::endl;
918 return false;
919 }
920
921 ManifestParser parser;
922 std::shared_ptr<XmlPullParser> pullParser = std::make_shared<SourceXmlPullParser>(ifs);
923 return parser.parse(source, pullParser, outInfo);
924}
925
926static void printCommandsAndDie() {
927 std::cerr << "The following commands are supported:" << std::endl << std::endl;
928 std::cerr << "compile compiles a subset of resources" << std::endl;
929 std::cerr << "link links together compiled resources and libraries" << std::endl;
Adam Lesinski330edcd2015-05-04 17:40:56 -0700930 std::cerr << "dump dumps resource contents to to standard out" << std::endl;
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700931 std::cerr << std::endl;
932 std::cerr << "run aapt2 with one of the commands and the -h flag for extra details."
933 << std::endl;
934 exit(1);
935}
936
937static AaptOptions prepareArgs(int argc, char** argv) {
938 if (argc < 2) {
939 std::cerr << "no command specified." << std::endl << std::endl;
940 printCommandsAndDie();
941 }
942
943 const StringPiece command(argv[1]);
944 argc -= 2;
945 argv += 2;
946
947 AaptOptions options;
948
949 if (command == "--version" || command == "version") {
950 std::cout << kAaptVersionStr << std::endl;
951 exit(0);
952 } else if (command == "link") {
953 options.phase = AaptOptions::Phase::Link;
954 } else if (command == "compile") {
955 options.phase = AaptOptions::Phase::Compile;
Adam Lesinski330edcd2015-05-04 17:40:56 -0700956 } else if (command == "dump") {
957 options.phase = AaptOptions::Phase::Dump;
958 } else if (command == "dump-style-graph") {
959 options.phase = AaptOptions::Phase::DumpStyleGraph;
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700960 } else {
961 std::cerr << "invalid command '" << command << "'." << std::endl << std::endl;
962 printCommandsAndDie();
963 }
964
Adam Lesinski6d8e4c42015-05-01 14:47:28 -0700965 bool isStaticLib = false;
Adam Lesinski330edcd2015-05-04 17:40:56 -0700966 if (options.phase == AaptOptions::Phase::Compile ||
967 options.phase == AaptOptions::Phase::Link) {
968 if (options.phase == AaptOptions::Phase::Compile) {
969 flag::requiredFlag("--package", "Android package name",
970 [&options](const StringPiece& arg) {
971 options.appInfo.package = util::utf8ToUtf16(arg);
972 });
973 } else if (options.phase == AaptOptions::Phase::Link) {
974 flag::requiredFlag("--manifest", "AndroidManifest.xml of your app",
975 [&options](const StringPiece& arg) {
976 options.manifest = Source{ arg.toString() };
977 });
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700978
Adam Lesinski330edcd2015-05-04 17:40:56 -0700979 flag::optionalFlag("-I", "add an Android APK to link against",
980 [&options](const StringPiece& arg) {
981 options.libraries.push_back(Source{ arg.toString() });
982 });
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700983
Adam Lesinski330edcd2015-05-04 17:40:56 -0700984 flag::optionalFlag("--java", "directory in which to generate R.java",
985 [&options](const StringPiece& arg) {
986 options.generateJavaClass = Source{ arg.toString() };
987 });
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700988
Adam Lesinski330edcd2015-05-04 17:40:56 -0700989 flag::optionalSwitch("--static-lib", "generate a static Android library", true,
990 &isStaticLib);
991
992 flag::optionalFlag("--binding", "Output directory for binding XML files",
993 [&options](const StringPiece& arg) {
994 options.bindingOutput = Source{ arg.toString() };
995 });
996 flag::optionalSwitch("--no-version", "Disables automatic style and layout versioning",
997 false, &options.versionStylesAndLayouts);
998 }
999
1000 // Common flags for all steps.
1001 flag::requiredFlag("-o", "Output path", [&options](const StringPiece& arg) {
1002 options.output = Source{ arg.toString() };
1003 });
Adam Lesinskid5c4f872015-04-21 13:56:10 -07001004 }
1005
Adam Lesinskid5c4f872015-04-21 13:56:10 -07001006 bool help = false;
1007 flag::optionalSwitch("-v", "enables verbose logging", true, &options.verbose);
1008 flag::optionalSwitch("-h", "displays this help menu", true, &help);
1009
1010 // Build the command string for output (eg. "aapt2 compile").
1011 std::string fullCommand = "aapt2";
1012 fullCommand += " ";
1013 fullCommand += command.toString();
1014
1015 // Actually read the command line flags.
1016 flag::parse(argc, argv, fullCommand);
1017
1018 if (help) {
1019 flag::usageAndDie(fullCommand);
1020 }
1021
Adam Lesinski6d8e4c42015-05-01 14:47:28 -07001022 if (isStaticLib) {
1023 options.packageType = AaptOptions::PackageType::StaticLibrary;
1024 }
1025
Adam Lesinskid5c4f872015-04-21 13:56:10 -07001026 // Copy all the remaining arguments.
1027 for (const std::string& arg : flag::getArgs()) {
1028 options.input.push_back(Source{ arg });
1029 }
1030 return options;
1031}
1032
Adam Lesinski330edcd2015-05-04 17:40:56 -07001033static bool doDump(const AaptOptions& options) {
1034 for (const Source& source : options.input) {
1035 std::unique_ptr<ZipFile> zipFile = util::make_unique<ZipFile>();
1036 if (zipFile->open(source.path.data(), ZipFile::kOpenReadOnly) != android::NO_ERROR) {
1037 Logger::error(source) << "failed to open: " << strerror(errno) << std::endl;
1038 return false;
1039 }
1040
1041 std::shared_ptr<ResourceTable> table = std::make_shared<ResourceTable>();
1042 std::shared_ptr<ResourceTableResolver> resolver =
1043 std::make_shared<ResourceTableResolver>(
1044 table, std::vector<std::shared_ptr<const android::AssetManager>>());
1045
1046 ZipEntry* entry = zipFile->getEntryByName("resources.arsc");
1047 if (!entry) {
1048 Logger::error(source) << "missing 'resources.arsc'." << std::endl;
1049 return false;
1050 }
1051
1052 std::unique_ptr<void, DeleteMalloc> uncompressedData = std::unique_ptr<void, DeleteMalloc>(
1053 zipFile->uncompress(entry));
1054 assert(uncompressedData);
1055
1056 BinaryResourceParser parser(table, resolver, source, uncompressedData.get(),
1057 entry->getUncompressedLen());
1058 if (!parser.parse()) {
1059 return false;
1060 }
1061
1062 if (options.phase == AaptOptions::Phase::Dump) {
1063 Debug::printTable(table);
1064 } else if (options.phase == AaptOptions::Phase::DumpStyleGraph) {
1065 Debug::printStyleGraph(table);
1066 }
1067 }
1068 return true;
1069}
1070
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001071int main(int argc, char** argv) {
1072 Logger::setLog(std::make_shared<Log>(std::cerr, std::cerr));
Adam Lesinski98aa3ad2015-04-06 11:46:52 -07001073 AaptOptions options = prepareArgs(argc, argv);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001074
Adam Lesinski330edcd2015-05-04 17:40:56 -07001075 if (options.phase == AaptOptions::Phase::Dump ||
1076 options.phase == AaptOptions::Phase::DumpStyleGraph) {
1077 if (!doDump(options)) {
1078 return 1;
1079 }
1080 return 0;
1081 }
1082
Adam Lesinski98aa3ad2015-04-06 11:46:52 -07001083 // If we specified a manifest, go ahead and load the package name from the manifest.
1084 if (!options.manifest.path.empty()) {
1085 if (!loadAppInfo(options.manifest, &options.appInfo)) {
1086 return false;
1087 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001088 }
1089
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001090 // Verify we have some common options set.
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001091 if (options.appInfo.package.empty()) {
1092 Logger::error() << "no package name specified." << std::endl;
1093 return false;
1094 }
1095
Adam Lesinski98aa3ad2015-04-06 11:46:52 -07001096 // Every phase needs a resource table.
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001097 std::shared_ptr<ResourceTable> table = std::make_shared<ResourceTable>();
1098 table->setPackage(options.appInfo.package);
1099 if (options.appInfo.package == u"android") {
1100 table->setPackageId(0x01);
1101 } else {
1102 table->setPackageId(0x7f);
1103 }
1104
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001105 // Load the included libraries.
Adam Lesinski330edcd2015-05-04 17:40:56 -07001106 std::vector<std::shared_ptr<const android::AssetManager>> sources;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001107 for (const Source& source : options.libraries) {
Adam Lesinski330edcd2015-05-04 17:40:56 -07001108 std::shared_ptr<android::AssetManager> assetManager =
1109 std::make_shared<android::AssetManager>();
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001110 int32_t cookie;
Adam Lesinski330edcd2015-05-04 17:40:56 -07001111 if (!assetManager->addAssetPath(android::String8(source.path.data()), &cookie)) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001112 Logger::error(source) << "failed to load library." << std::endl;
1113 return false;
1114 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001115
Adam Lesinski330edcd2015-05-04 17:40:56 -07001116 if (cookie == 0) {
1117 Logger::error(source) << "failed to load library." << std::endl;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001118 return false;
1119 }
Adam Lesinski330edcd2015-05-04 17:40:56 -07001120 sources.push_back(assetManager);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001121 }
1122
1123 // Make the resolver that will cache IDs for us.
Adam Lesinski24aad162015-04-24 19:19:30 -07001124 std::shared_ptr<ResourceTableResolver> resolver = std::make_shared<ResourceTableResolver>(
Adam Lesinski330edcd2015-05-04 17:40:56 -07001125 table, sources);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001126
Adam Lesinski769de982015-04-10 19:43:55 -07001127 if (options.phase == AaptOptions::Phase::Compile) {
1128 if (!compile(options, table, resolver)) {
1129 Logger::error() << "aapt exiting with failures." << std::endl;
1130 return 1;
1131 }
1132 } else if (options.phase == AaptOptions::Phase::Link) {
1133 if (!link(options, table, resolver)) {
1134 Logger::error() << "aapt exiting with failures." << std::endl;
1135 return 1;
1136 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001137 }
1138 return 0;
1139}