blob: 91639c5b33a66fece9495a6b48b56a8835b7b66d [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 ResourceName name;
193 ConfigDescription config;
Adam Lesinski39c353a2015-05-14 17:58:14 -0700194 Source source;
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 {
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700199 ResourceName name;
200 ConfigDescription config;
Adam Lesinski39c353a2015-05-14 17:58:14 -0700201 Source source;
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700202 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 Lesinski39c353a2015-05-14 17:58:14 -0700239template <typename T>
240bool addFileReference(const std::shared_ptr<ResourceTable>& table, const T& item) {
Adam Lesinski769de982015-04-10 19:43:55 -0700241 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 Lesinskid13fb242015-05-12 20:40:48 -0700297
298 // The target style that will have it's style hierarchy dumped
299 // when the phase is DumpStyleGraph.
300 ResourceName dumpStyleTarget;
Adam Lesinski769de982015-04-10 19:43:55 -0700301};
302
303bool compileXml(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table,
Adam Lesinski330edcd2015-05-04 17:40:56 -0700304 const CompileItem& item, ZipFile* outApk) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800305 std::ifstream in(item.source.path, std::ifstream::binary);
306 if (!in) {
307 Logger::error(item.source) << strerror(errno) << std::endl;
308 return false;
309 }
310
Adam Lesinski4d3a9872015-04-09 19:53:22 -0700311 BigBuffer outBuffer(1024);
Adam Lesinski769de982015-04-10 19:43:55 -0700312
313 // No resolver, since we are not compiling attributes here.
314 XmlFlattener flattener(table, {});
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800315
Adam Lesinski769de982015-04-10 19:43:55 -0700316 XmlFlattener::Options xmlOptions;
Adam Lesinski24aad162015-04-24 19:19:30 -0700317 xmlOptions.defaultPackage = table->getPackage();
Adam Lesinski330edcd2015-05-04 17:40:56 -0700318 xmlOptions.keepRawValues = true;
319
320 std::shared_ptr<XmlPullParser> parser = std::make_shared<SourceXmlPullParser>(in);
321
322 Maybe<size_t> minStrippedSdk = flattener.flatten(item.source, parser, &outBuffer,
323 xmlOptions);
324 if (!minStrippedSdk) {
325 return false;
326 }
327
328 // Write the resulting compiled XML file to the output APK.
329 if (outApk->add(outBuffer, buildFileReference(item).data(), ZipEntry::kCompressStored,
330 nullptr) != android::NO_ERROR) {
331 Logger::error(options.output) << "failed to write compiled '" << item.source
332 << "' to apk." << std::endl;
333 return false;
334 }
335 return true;
336}
337
Adam Lesinski39c353a2015-05-14 17:58:14 -0700338/**
339 * Determines if a layout should be auto generated based on SDK level. We do not
340 * generate a layout if there is already a layout defined whose SDK version is greater than
341 * the one we want to generate.
342 */
343bool shouldGenerateVersionedResource(const std::shared_ptr<const ResourceTable>& table,
344 const ResourceName& name, const ConfigDescription& config,
345 int sdkVersionToGenerate) {
346 assert(sdkVersionToGenerate > config.sdkVersion);
347 const ResourceTableType* type;
348 const ResourceEntry* entry;
349 std::tie(type, entry) = table->findResource(name);
350 assert(type && entry);
351
352 auto iter = std::lower_bound(entry->values.begin(), entry->values.end(), config,
353 [](const ResourceConfigValue& lhs, const ConfigDescription& config) -> bool {
354 return lhs.config < config;
355 });
356
357 assert(iter != entry->values.end());
358 ++iter;
359
360 if (iter == entry->values.end()) {
361 return true;
362 }
363
364 ConfigDescription newConfig = config;
365 newConfig.sdkVersion = sdkVersionToGenerate;
366 return newConfig < iter->config;
367}
368
369bool linkXml(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table,
370 const std::shared_ptr<IResolver>& resolver, const LinkItem& item,
371 const void* data, size_t dataLen, ZipFile* outApk, std::queue<LinkItem>* outQueue) {
Adam Lesinski330edcd2015-05-04 17:40:56 -0700372 std::shared_ptr<android::ResXMLTree> tree = std::make_shared<android::ResXMLTree>();
373 if (tree->setTo(data, dataLen, false) != android::NO_ERROR) {
374 return false;
375 }
376
377 std::shared_ptr<XmlPullParser> parser = std::make_shared<BinaryXmlPullParser>(tree);
378
379 BigBuffer outBuffer(1024);
380 XmlFlattener flattener({}, resolver);
381
382 XmlFlattener::Options xmlOptions;
383 xmlOptions.defaultPackage = item.originalPackage;
384
385 if (options.packageType == AaptOptions::PackageType::StaticLibrary) {
386 xmlOptions.keepRawValues = true;
387 }
Adam Lesinski24aad162015-04-24 19:19:30 -0700388
Adam Lesinski5886a922015-04-15 20:29:22 -0700389 if (options.versionStylesAndLayouts) {
390 // We strip attributes that do not belong in this version of the resource.
391 // Non-version qualified resources have an implicit version 1 requirement.
392 xmlOptions.maxSdkAttribute = item.config.sdkVersion ? item.config.sdkVersion : 1;
393 }
Adam Lesinski769de982015-04-10 19:43:55 -0700394
395 std::shared_ptr<BindingXmlPullParser> binding;
Adam Lesinski769de982015-04-10 19:43:55 -0700396 if (item.name.type == ResourceType::kLayout) {
397 // Layouts may have defined bindings, so we need to make sure they get processed.
398 binding = std::make_shared<BindingXmlPullParser>(parser);
399 parser = binding;
400 }
401
Adam Lesinski330edcd2015-05-04 17:40:56 -0700402 Maybe<size_t> minStrippedSdk = flattener.flatten(item.source, parser, &outBuffer,
403 xmlOptions);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800404 if (!minStrippedSdk) {
405 return false;
406 }
407
408 if (minStrippedSdk.value() > 0) {
409 // Something was stripped, so let's generate a new file
410 // with the version of the smallest SDK version stripped.
Adam Lesinski39c353a2015-05-14 17:58:14 -0700411 // We can only generate a versioned layout if there doesn't exist a layout
412 // with sdk version greater than the current one but less than the one we
413 // want to generate.
414 if (shouldGenerateVersionedResource(table, item.name, item.config,
415 minStrippedSdk.value())) {
416 LinkItem newWork = item;
417 newWork.config.sdkVersion = minStrippedSdk.value();
418 outQueue->push(newWork);
419
420 if (!addFileReference(table, newWork)) {
421 Logger::error(options.output) << "failed to add auto-versioned resource '"
422 << newWork.name << "'." << std::endl;
423 return false;
424 }
425 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800426 }
427
Adam Lesinski330edcd2015-05-04 17:40:56 -0700428 if (outApk->add(outBuffer, buildFileReference(item).data(), ZipEntry::kCompressDeflated,
Adam Lesinski769de982015-04-10 19:43:55 -0700429 nullptr) != android::NO_ERROR) {
Adam Lesinski330edcd2015-05-04 17:40:56 -0700430 Logger::error(options.output) << "failed to write linked file '"
431 << buildFileReference(item) << "' to apk." << std::endl;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800432 return false;
433 }
434
Adam Lesinski769de982015-04-10 19:43:55 -0700435 if (binding && !options.bindingOutput.path.empty()) {
436 // We generated a binding xml file, write it out.
437 Source bindingOutput = options.bindingOutput;
438 appendPath(&bindingOutput.path, buildFileReference(item));
Adam Lesinski4d3a9872015-04-09 19:53:22 -0700439
Adam Lesinski769de982015-04-10 19:43:55 -0700440 if (!mkdirs(bindingOutput.path)) {
441 Logger::error(bindingOutput) << strerror(errno) << std::endl;
442 return false;
443 }
444
445 appendPath(&bindingOutput.path, "bind.xml");
446
Adam Lesinski4d3a9872015-04-09 19:53:22 -0700447 std::ofstream bout(bindingOutput.path);
448 if (!bout) {
449 Logger::error(bindingOutput) << strerror(errno) << std::endl;
450 return false;
451 }
452
453 if (!binding->writeToFile(bout)) {
454 Logger::error(bindingOutput) << strerror(errno) << std::endl;
455 return false;
456 }
457 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800458 return true;
459}
460
Adam Lesinski769de982015-04-10 19:43:55 -0700461bool compilePng(const AaptOptions& options, const CompileItem& item, ZipFile* outApk) {
462 std::ifstream in(item.source.path, std::ifstream::binary);
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700463 if (!in) {
Adam Lesinski769de982015-04-10 19:43:55 -0700464 Logger::error(item.source) << strerror(errno) << std::endl;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700465 return false;
466 }
467
Adam Lesinski769de982015-04-10 19:43:55 -0700468 BigBuffer outBuffer(4096);
469 std::string err;
470 Png png;
471 if (!png.process(item.source, in, &outBuffer, {}, &err)) {
472 Logger::error(item.source) << err << std::endl;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700473 return false;
474 }
475
Adam Lesinski769de982015-04-10 19:43:55 -0700476 if (outApk->add(outBuffer, buildFileReference(item).data(), ZipEntry::kCompressStored,
477 nullptr) != android::NO_ERROR) {
478 Logger::error(options.output) << "failed to write compiled '" << item.source
479 << "' to apk." << std::endl;
480 return false;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700481 }
Adam Lesinski769de982015-04-10 19:43:55 -0700482 return true;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700483}
484
Adam Lesinski769de982015-04-10 19:43:55 -0700485bool copyFile(const AaptOptions& options, const CompileItem& item, ZipFile* outApk) {
486 if (outApk->add(item.source.path.data(), buildFileReference(item).data(),
487 ZipEntry::kCompressStored, nullptr) != android::NO_ERROR) {
488 Logger::error(options.output) << "failed to copy file '" << item.source << "' to apk."
489 << std::endl;
490 return false;
491 }
492 return true;
493}
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800494
Adam Lesinski330edcd2015-05-04 17:40:56 -0700495bool compileManifest(const AaptOptions& options, const std::shared_ptr<IResolver>& resolver,
496 const android::ResTable& table, ZipFile* outApk) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800497 if (options.verbose) {
Adam Lesinski769de982015-04-10 19:43:55 -0700498 Logger::note(options.manifest) << "compiling AndroidManifest.xml." << std::endl;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800499 }
500
501 std::ifstream in(options.manifest.path, std::ifstream::binary);
502 if (!in) {
503 Logger::error(options.manifest) << strerror(errno) << std::endl;
504 return false;
505 }
506
507 BigBuffer outBuffer(1024);
508 std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<SourceXmlPullParser>(in);
Adam Lesinski769de982015-04-10 19:43:55 -0700509 XmlFlattener flattener({}, resolver);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800510
Adam Lesinski24aad162015-04-24 19:19:30 -0700511 XmlFlattener::Options xmlOptions;
512 xmlOptions.defaultPackage = options.appInfo.package;
513 if (!flattener.flatten(options.manifest, xmlParser, &outBuffer, xmlOptions)) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800514 return false;
515 }
516
Adam Lesinski769de982015-04-10 19:43:55 -0700517 std::unique_ptr<uint8_t[]> data = util::copy(outBuffer);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800518
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700519 android::ResXMLTree tree;
Adam Lesinski769de982015-04-10 19:43:55 -0700520 if (tree.setTo(data.get(), outBuffer.size(), false) != android::NO_ERROR) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800521 return false;
522 }
523
Adam Lesinski330edcd2015-05-04 17:40:56 -0700524 ManifestValidator validator(table);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800525 if (!validator.validate(options.manifest, &tree)) {
526 return false;
527 }
528
Adam Lesinski769de982015-04-10 19:43:55 -0700529 if (outApk->add(data.get(), outBuffer.size(), "AndroidManifest.xml",
530 ZipEntry::kCompressStored, nullptr) != android::NO_ERROR) {
531 Logger::error(options.output) << "failed to write 'AndroidManifest.xml' to apk."
532 << std::endl;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800533 return false;
534 }
535 return true;
536}
537
Adam Lesinski769de982015-04-10 19:43:55 -0700538static bool compileValues(const std::shared_ptr<ResourceTable>& table, const Source& source,
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700539 const ConfigDescription& config) {
540 std::ifstream in(source.path, std::ifstream::binary);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800541 if (!in) {
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700542 Logger::error(source) << strerror(errno) << std::endl;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800543 return false;
544 }
545
546 std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<SourceXmlPullParser>(in);
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700547 ResourceParser parser(table, source, config, xmlParser);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800548 return parser.parse();
549}
550
551struct ResourcePathData {
552 std::u16string resourceDir;
553 std::u16string name;
554 std::string extension;
555 ConfigDescription config;
556};
557
558/**
559 * Resource file paths are expected to look like:
560 * [--/res/]type[-config]/name
561 */
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700562static Maybe<ResourcePathData> extractResourcePathData(const Source& source) {
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700563 // TODO(adamlesinski): Use Windows path separator on windows.
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800564 std::vector<std::string> parts = util::splitAndLowercase(source.path, '/');
565 if (parts.size() < 2) {
566 Logger::error(source) << "bad resource path." << std::endl;
567 return {};
568 }
569
570 std::string& dir = parts[parts.size() - 2];
571 StringPiece dirStr = dir;
572
573 ConfigDescription config;
574 size_t dashPos = dir.find('-');
575 if (dashPos != std::string::npos) {
576 StringPiece configStr = dirStr.substr(dashPos + 1, dir.size() - (dashPos + 1));
577 if (!ConfigDescription::parse(configStr, &config)) {
578 Logger::error(source)
579 << "invalid configuration '"
580 << configStr
581 << "'."
582 << std::endl;
583 return {};
584 }
585 dirStr = dirStr.substr(0, dashPos);
586 }
587
588 std::string& filename = parts[parts.size() - 1];
589 StringPiece name = filename;
590 StringPiece extension;
591 size_t dotPos = filename.find('.');
592 if (dotPos != std::string::npos) {
593 extension = name.substr(dotPos + 1, filename.size() - (dotPos + 1));
594 name = name.substr(0, dotPos);
595 }
596
597 return ResourcePathData{
598 util::utf8ToUtf16(dirStr),
599 util::utf8ToUtf16(name),
600 extension.toString(),
601 config
602 };
603}
604
Adam Lesinski769de982015-04-10 19:43:55 -0700605bool writeResourceTable(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table,
606 const TableFlattener::Options& flattenerOptions, ZipFile* outApk) {
607 if (table->begin() != table->end()) {
608 BigBuffer buffer(1024);
609 TableFlattener flattener(flattenerOptions);
610 if (!flattener.flatten(&buffer, *table)) {
611 Logger::error() << "failed to flatten resource table." << std::endl;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700612 return false;
613 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800614
Adam Lesinski769de982015-04-10 19:43:55 -0700615 if (options.verbose) {
616 Logger::note() << "Final resource table size=" << util::formatSize(buffer.size())
617 << std::endl;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700618 }
619
Adam Lesinski769de982015-04-10 19:43:55 -0700620 if (outApk->add(buffer, "resources.arsc", ZipEntry::kCompressStored, nullptr) !=
621 android::NO_ERROR) {
622 Logger::note(options.output) << "failed to store resource table." << std::endl;
623 return false;
624 }
625 }
626 return true;
627}
628
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700629/**
630 * For each FileReference in the table, adds a LinkItem to the link queue for processing.
631 */
632static void addApkFilesToLinkQueue(const std::u16string& package, const Source& source,
633 const std::shared_ptr<ResourceTable>& table,
634 const std::unique_ptr<ZipFile>& apk,
635 std::queue<LinkItem>* outLinkQueue) {
636 bool mangle = package != table->getPackage();
637 for (auto& type : *table) {
638 for (auto& entry : type->entries) {
639 ResourceName name = { package, type->type, entry->name };
640 if (mangle) {
641 NameMangler::mangle(table->getPackage(), &name.entry);
642 }
643
644 for (auto& value : entry->values) {
645 visitFunc<FileReference>(*value.value, [&](FileReference& ref) {
646 std::string pathUtf8 = util::utf16ToUtf8(*ref.path);
Adam Lesinski24aad162015-04-24 19:19:30 -0700647 Source newSource = source;
648 newSource.path += "/";
649 newSource.path += pathUtf8;
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700650 outLinkQueue->push(LinkItem{
Adam Lesinski39c353a2015-05-14 17:58:14 -0700651 name, value.config, newSource, pathUtf8, apk.get(),
Adam Lesinski24aad162015-04-24 19:19:30 -0700652 table->getPackage() });
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700653 // Now rewrite the file path.
654 if (mangle) {
655 ref.path = table->getValueStringPool().makeRef(util::utf8ToUtf16(
656 buildFileReference(name, value.config,
657 getExtension<char>(pathUtf8))));
658 }
659 });
660 }
661 }
662 }
663}
664
Adam Lesinski769de982015-04-10 19:43:55 -0700665static constexpr int kOpenFlags = ZipFile::kOpenCreate | ZipFile::kOpenTruncate |
666 ZipFile::kOpenReadWrite;
667
Adam Lesinski24aad162015-04-24 19:19:30 -0700668struct DeleteMalloc {
669 void operator()(void* ptr) {
670 free(ptr);
671 }
672};
673
674struct StaticLibraryData {
675 Source source;
676 std::unique_ptr<ZipFile> apk;
677};
678
Adam Lesinski769de982015-04-10 19:43:55 -0700679bool link(const AaptOptions& options, const std::shared_ptr<ResourceTable>& outTable,
Adam Lesinski330edcd2015-05-04 17:40:56 -0700680 const std::shared_ptr<IResolver>& resolver) {
Adam Lesinski24aad162015-04-24 19:19:30 -0700681 std::map<std::shared_ptr<ResourceTable>, StaticLibraryData> apkFiles;
Adam Lesinski769de982015-04-10 19:43:55 -0700682 std::unordered_set<std::u16string> linkedPackages;
683
684 // Populate the linkedPackages with our own.
685 linkedPackages.insert(options.appInfo.package);
686
687 // Load all APK files.
688 for (const Source& source : options.input) {
689 std::unique_ptr<ZipFile> zipFile = util::make_unique<ZipFile>();
690 if (zipFile->open(source.path.data(), ZipFile::kOpenReadOnly) != android::NO_ERROR) {
691 Logger::error(source) << "failed to open: " << strerror(errno) << std::endl;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700692 return false;
693 }
694
Adam Lesinski769de982015-04-10 19:43:55 -0700695 std::shared_ptr<ResourceTable> table = std::make_shared<ResourceTable>();
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700696
Adam Lesinski769de982015-04-10 19:43:55 -0700697 ZipEntry* entry = zipFile->getEntryByName("resources.arsc");
698 if (!entry) {
699 Logger::error(source) << "missing 'resources.arsc'." << std::endl;
700 return false;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700701 }
702
Adam Lesinski24aad162015-04-24 19:19:30 -0700703 std::unique_ptr<void, DeleteMalloc> uncompressedData = std::unique_ptr<void, DeleteMalloc>(
704 zipFile->uncompress(entry));
Adam Lesinski769de982015-04-10 19:43:55 -0700705 assert(uncompressedData);
706
Adam Lesinski24aad162015-04-24 19:19:30 -0700707 BinaryResourceParser parser(table, resolver, source, uncompressedData.get(),
Adam Lesinski769de982015-04-10 19:43:55 -0700708 entry->getUncompressedLen());
709 if (!parser.parse()) {
Adam Lesinski769de982015-04-10 19:43:55 -0700710 return false;
711 }
Adam Lesinski769de982015-04-10 19:43:55 -0700712
713 // Keep track of where this table came from.
Adam Lesinski24aad162015-04-24 19:19:30 -0700714 apkFiles[table] = StaticLibraryData{ source, std::move(zipFile) };
Adam Lesinski769de982015-04-10 19:43:55 -0700715
716 // Add the package to the set of linked packages.
717 linkedPackages.insert(table->getPackage());
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700718 }
719
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700720 std::queue<LinkItem> linkQueue;
Adam Lesinski769de982015-04-10 19:43:55 -0700721 for (auto& p : apkFiles) {
722 const std::shared_ptr<ResourceTable>& inTable = p.first;
723
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700724 // Collect all FileReferences and add them to the queue for processing.
Adam Lesinski24aad162015-04-24 19:19:30 -0700725 addApkFilesToLinkQueue(options.appInfo.package, p.second.source, inTable, p.second.apk,
726 &linkQueue);
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700727
728 // Merge the tables.
Adam Lesinski769de982015-04-10 19:43:55 -0700729 if (!outTable->merge(std::move(*inTable))) {
730 return false;
731 }
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700732 }
733
Adam Lesinski330edcd2015-05-04 17:40:56 -0700734 // Version all styles referencing attributes outside of their specified SDK version.
735 if (options.versionStylesAndLayouts) {
736 versionStylesForCompat(outTable);
737 }
738
Adam Lesinski769de982015-04-10 19:43:55 -0700739 {
740 // Now that everything is merged, let's link it.
Adam Lesinski330edcd2015-05-04 17:40:56 -0700741 Linker::Options linkerOptions;
742 if (options.packageType == AaptOptions::PackageType::StaticLibrary) {
743 linkerOptions.linkResourceIds = false;
744 }
745 Linker linker(outTable, resolver, linkerOptions);
Adam Lesinski769de982015-04-10 19:43:55 -0700746 if (!linker.linkAndValidate()) {
747 return false;
748 }
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700749
Adam Lesinski769de982015-04-10 19:43:55 -0700750 // Verify that all symbols exist.
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700751 const auto& unresolvedRefs = linker.getUnresolvedReferences();
752 if (!unresolvedRefs.empty()) {
753 for (const auto& entry : unresolvedRefs) {
754 for (const auto& source : entry.second) {
755 Logger::error(source) << "unresolved symbol '" << entry.first << "'."
756 << std::endl;
757 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800758 }
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700759 return false;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800760 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800761 }
762
Adam Lesinski769de982015-04-10 19:43:55 -0700763 // Open the output APK file for writing.
764 ZipFile outApk;
765 if (outApk.open(options.output.path.data(), kOpenFlags) != android::NO_ERROR) {
766 Logger::error(options.output) << "failed to open: " << strerror(errno) << std::endl;
767 return false;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700768 }
769
Adam Lesinski330edcd2015-05-04 17:40:56 -0700770 android::ResTable binTable;
771 if (!compileManifest(options, resolver, binTable, &outApk)) {
Adam Lesinski769de982015-04-10 19:43:55 -0700772 return false;
773 }
774
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700775 for (; !linkQueue.empty(); linkQueue.pop()) {
776 const LinkItem& item = linkQueue.front();
Adam Lesinski769de982015-04-10 19:43:55 -0700777
Adam Lesinski24aad162015-04-24 19:19:30 -0700778 assert(!item.originalPackage.empty());
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700779 ZipEntry* entry = item.apk->getEntryByName(item.originalPath.data());
780 if (!entry) {
781 Logger::error(item.source) << "failed to find '" << item.originalPath << "'."
782 << std::endl;
783 return false;
784 }
Adam Lesinski769de982015-04-10 19:43:55 -0700785
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700786 if (util::stringEndsWith<char>(item.originalPath, ".xml")) {
787 void* uncompressedData = item.apk->uncompress(entry);
788 assert(uncompressedData);
Adam Lesinski769de982015-04-10 19:43:55 -0700789
Adam Lesinski39c353a2015-05-14 17:58:14 -0700790 if (!linkXml(options, outTable, resolver, item, uncompressedData,
791 entry->getUncompressedLen(), &outApk, &linkQueue)) {
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700792 Logger::error(options.output) << "failed to link '" << item.originalPath << "'."
793 << std::endl;
794 return false;
Adam Lesinski769de982015-04-10 19:43:55 -0700795 }
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700796 } else {
797 if (outApk.add(item.apk, entry, buildFileReference(item).data(), 0, nullptr) !=
798 android::NO_ERROR) {
799 Logger::error(options.output) << "failed to copy '" << item.originalPath << "'."
800 << std::endl;
801 return false;
Adam Lesinski769de982015-04-10 19:43:55 -0700802 }
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700803 }
804 }
805
806 // Generate the Java class file.
Adam Lesinski769de982015-04-10 19:43:55 -0700807 if (options.generateJavaClass) {
Adam Lesinski6d8e4c42015-05-01 14:47:28 -0700808 JavaClassGenerator::Options javaOptions;
809 if (options.packageType == AaptOptions::PackageType::StaticLibrary) {
810 javaOptions.useFinal = false;
811 }
812 JavaClassGenerator generator(outTable, javaOptions);
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700813
Adam Lesinski769de982015-04-10 19:43:55 -0700814 for (const std::u16string& package : linkedPackages) {
815 Source outPath = options.generateJavaClass.value();
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800816
Adam Lesinski769de982015-04-10 19:43:55 -0700817 // Build the output directory from the package name.
818 // Eg. com.android.app -> com/android/app
819 const std::string packageUtf8 = util::utf16ToUtf8(package);
820 for (StringPiece part : util::tokenize<char>(packageUtf8, '.')) {
821 appendPath(&outPath.path, part);
822 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800823
Adam Lesinski769de982015-04-10 19:43:55 -0700824 if (!mkdirs(outPath.path)) {
825 Logger::error(outPath) << strerror(errno) << std::endl;
826 return false;
827 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800828
Adam Lesinski769de982015-04-10 19:43:55 -0700829 appendPath(&outPath.path, "R.java");
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800830
Adam Lesinski769de982015-04-10 19:43:55 -0700831 if (options.verbose) {
832 Logger::note(outPath) << "writing Java symbols." << std::endl;
833 }
834
835 std::ofstream fout(outPath.path);
836 if (!fout) {
837 Logger::error(outPath) << strerror(errno) << std::endl;
838 return false;
839 }
840
841 if (!generator.generate(package, fout)) {
842 Logger::error(outPath) << generator.getError() << "." << std::endl;
843 return false;
844 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800845 }
846 }
847
Adam Lesinski24aad162015-04-24 19:19:30 -0700848 outTable->getValueStringPool().prune();
849 outTable->getValueStringPool().sort(
850 [](const StringPool::Entry& a, const StringPool::Entry& b) -> bool {
851 if (a.context.priority < b.context.priority) {
852 return true;
853 }
854
855 if (a.context.priority > b.context.priority) {
856 return false;
857 }
858 return a.value < b.value;
859 });
860
861
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700862 // Flatten the resource table.
Adam Lesinski769de982015-04-10 19:43:55 -0700863 TableFlattener::Options flattenerOptions;
Adam Lesinski330edcd2015-05-04 17:40:56 -0700864 if (options.packageType != AaptOptions::PackageType::StaticLibrary) {
865 flattenerOptions.useExtendedChunks = false;
Adam Lesinski6d8e4c42015-05-01 14:47:28 -0700866 }
867
Adam Lesinski769de982015-04-10 19:43:55 -0700868 if (!writeResourceTable(options, outTable, flattenerOptions, &outApk)) {
869 return false;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800870 }
Adam Lesinski769de982015-04-10 19:43:55 -0700871
872 outApk.flush();
873 return true;
874}
875
876bool compile(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table,
Adam Lesinski24aad162015-04-24 19:19:30 -0700877 const std::shared_ptr<IResolver>& resolver) {
Adam Lesinski769de982015-04-10 19:43:55 -0700878 std::queue<CompileItem> compileQueue;
879 bool error = false;
880
881 // Compile all the resource files passed in on the command line.
882 for (const Source& source : options.input) {
883 // Need to parse the resource type/config/filename.
884 Maybe<ResourcePathData> maybePathData = extractResourcePathData(source);
885 if (!maybePathData) {
886 return false;
887 }
888
889 const ResourcePathData& pathData = maybePathData.value();
890 if (pathData.resourceDir == u"values") {
891 // The file is in the values directory, which means its contents will
892 // go into the resource table.
893 if (options.verbose) {
894 Logger::note(source) << "compiling values." << std::endl;
895 }
896
897 error |= !compileValues(table, source, pathData.config);
898 } else {
899 // The file is in a directory like 'layout' or 'drawable'. Find out
900 // the type.
901 const ResourceType* type = parseResourceType(pathData.resourceDir);
902 if (!type) {
903 Logger::error(source) << "invalid resource type '" << pathData.resourceDir << "'."
904 << std::endl;
905 return false;
906 }
907
908 compileQueue.push(CompileItem{
Adam Lesinski769de982015-04-10 19:43:55 -0700909 ResourceName{ table->getPackage(), *type, pathData.name },
910 pathData.config,
Adam Lesinski39c353a2015-05-14 17:58:14 -0700911 source,
Adam Lesinski769de982015-04-10 19:43:55 -0700912 pathData.extension
913 });
914 }
915 }
916
917 if (error) {
918 return false;
919 }
Adam Lesinski769de982015-04-10 19:43:55 -0700920 // Open the output APK file for writing.
921 ZipFile outApk;
922 if (outApk.open(options.output.path.data(), kOpenFlags) != android::NO_ERROR) {
923 Logger::error(options.output) << "failed to open: " << strerror(errno) << std::endl;
924 return false;
925 }
926
927 // Compile each file.
928 for (; !compileQueue.empty(); compileQueue.pop()) {
929 const CompileItem& item = compileQueue.front();
930
931 // Add the file name to the resource table.
932 error |= !addFileReference(table, item);
933
934 if (item.extension == "xml") {
Adam Lesinski330edcd2015-05-04 17:40:56 -0700935 error |= !compileXml(options, table, item, &outApk);
Adam Lesinski769de982015-04-10 19:43:55 -0700936 } else if (item.extension == "png" || item.extension == "9.png") {
937 error |= !compilePng(options, item, &outApk);
938 } else {
939 error |= !copyFile(options, item, &outApk);
940 }
941 }
942
943 if (error) {
944 return false;
945 }
946
947 // Link and assign resource IDs.
Adam Lesinski330edcd2015-05-04 17:40:56 -0700948 Linker linker(table, resolver, {});
Adam Lesinski769de982015-04-10 19:43:55 -0700949 if (!linker.linkAndValidate()) {
950 return false;
951 }
952
953 // Flatten the resource table.
954 if (!writeResourceTable(options, table, {}, &outApk)) {
955 return false;
956 }
957
958 outApk.flush();
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800959 return true;
960}
961
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700962bool loadAppInfo(const Source& source, AppInfo* outInfo) {
963 std::ifstream ifs(source.path, std::ifstream::in | std::ifstream::binary);
964 if (!ifs) {
965 Logger::error(source) << strerror(errno) << std::endl;
966 return false;
967 }
968
969 ManifestParser parser;
970 std::shared_ptr<XmlPullParser> pullParser = std::make_shared<SourceXmlPullParser>(ifs);
971 return parser.parse(source, pullParser, outInfo);
972}
973
974static void printCommandsAndDie() {
975 std::cerr << "The following commands are supported:" << std::endl << std::endl;
976 std::cerr << "compile compiles a subset of resources" << std::endl;
977 std::cerr << "link links together compiled resources and libraries" << std::endl;
Adam Lesinski330edcd2015-05-04 17:40:56 -0700978 std::cerr << "dump dumps resource contents to to standard out" << std::endl;
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700979 std::cerr << std::endl;
980 std::cerr << "run aapt2 with one of the commands and the -h flag for extra details."
981 << std::endl;
982 exit(1);
983}
984
985static AaptOptions prepareArgs(int argc, char** argv) {
986 if (argc < 2) {
987 std::cerr << "no command specified." << std::endl << std::endl;
988 printCommandsAndDie();
989 }
990
991 const StringPiece command(argv[1]);
992 argc -= 2;
993 argv += 2;
994
995 AaptOptions options;
996
997 if (command == "--version" || command == "version") {
998 std::cout << kAaptVersionStr << std::endl;
999 exit(0);
1000 } else if (command == "link") {
1001 options.phase = AaptOptions::Phase::Link;
1002 } else if (command == "compile") {
1003 options.phase = AaptOptions::Phase::Compile;
Adam Lesinski330edcd2015-05-04 17:40:56 -07001004 } else if (command == "dump") {
1005 options.phase = AaptOptions::Phase::Dump;
1006 } else if (command == "dump-style-graph") {
1007 options.phase = AaptOptions::Phase::DumpStyleGraph;
Adam Lesinskid5c4f872015-04-21 13:56:10 -07001008 } else {
1009 std::cerr << "invalid command '" << command << "'." << std::endl << std::endl;
1010 printCommandsAndDie();
1011 }
1012
Adam Lesinski6d8e4c42015-05-01 14:47:28 -07001013 bool isStaticLib = false;
Adam Lesinski330edcd2015-05-04 17:40:56 -07001014 if (options.phase == AaptOptions::Phase::Compile ||
1015 options.phase == AaptOptions::Phase::Link) {
1016 if (options.phase == AaptOptions::Phase::Compile) {
1017 flag::requiredFlag("--package", "Android package name",
1018 [&options](const StringPiece& arg) {
1019 options.appInfo.package = util::utf8ToUtf16(arg);
1020 });
1021 } else if (options.phase == AaptOptions::Phase::Link) {
1022 flag::requiredFlag("--manifest", "AndroidManifest.xml of your app",
1023 [&options](const StringPiece& arg) {
1024 options.manifest = Source{ arg.toString() };
1025 });
Adam Lesinskid5c4f872015-04-21 13:56:10 -07001026
Adam Lesinski330edcd2015-05-04 17:40:56 -07001027 flag::optionalFlag("-I", "add an Android APK to link against",
1028 [&options](const StringPiece& arg) {
1029 options.libraries.push_back(Source{ arg.toString() });
1030 });
Adam Lesinskid5c4f872015-04-21 13:56:10 -07001031
Adam Lesinski330edcd2015-05-04 17:40:56 -07001032 flag::optionalFlag("--java", "directory in which to generate R.java",
1033 [&options](const StringPiece& arg) {
1034 options.generateJavaClass = Source{ arg.toString() };
1035 });
Adam Lesinskid5c4f872015-04-21 13:56:10 -07001036
Adam Lesinski330edcd2015-05-04 17:40:56 -07001037 flag::optionalSwitch("--static-lib", "generate a static Android library", true,
1038 &isStaticLib);
1039
1040 flag::optionalFlag("--binding", "Output directory for binding XML files",
1041 [&options](const StringPiece& arg) {
1042 options.bindingOutput = Source{ arg.toString() };
1043 });
1044 flag::optionalSwitch("--no-version", "Disables automatic style and layout versioning",
1045 false, &options.versionStylesAndLayouts);
1046 }
1047
1048 // Common flags for all steps.
1049 flag::requiredFlag("-o", "Output path", [&options](const StringPiece& arg) {
1050 options.output = Source{ arg.toString() };
1051 });
Adam Lesinskid13fb242015-05-12 20:40:48 -07001052 } else if (options.phase == AaptOptions::Phase::DumpStyleGraph) {
1053 flag::requiredFlag("--style", "Name of the style to dump",
1054 [&options](const StringPiece& arg, std::string* outError) -> bool {
1055 Reference styleReference;
1056 if (!ResourceParser::parseStyleParentReference(util::utf8ToUtf16(arg),
1057 &styleReference, outError)) {
1058 return false;
1059 }
1060 options.dumpStyleTarget = styleReference.name;
1061 return true;
1062 });
Adam Lesinskid5c4f872015-04-21 13:56:10 -07001063 }
1064
Adam Lesinskid5c4f872015-04-21 13:56:10 -07001065 bool help = false;
1066 flag::optionalSwitch("-v", "enables verbose logging", true, &options.verbose);
1067 flag::optionalSwitch("-h", "displays this help menu", true, &help);
1068
1069 // Build the command string for output (eg. "aapt2 compile").
1070 std::string fullCommand = "aapt2";
1071 fullCommand += " ";
1072 fullCommand += command.toString();
1073
1074 // Actually read the command line flags.
1075 flag::parse(argc, argv, fullCommand);
1076
1077 if (help) {
1078 flag::usageAndDie(fullCommand);
1079 }
1080
Adam Lesinski6d8e4c42015-05-01 14:47:28 -07001081 if (isStaticLib) {
1082 options.packageType = AaptOptions::PackageType::StaticLibrary;
1083 }
1084
Adam Lesinskid5c4f872015-04-21 13:56:10 -07001085 // Copy all the remaining arguments.
1086 for (const std::string& arg : flag::getArgs()) {
1087 options.input.push_back(Source{ arg });
1088 }
1089 return options;
1090}
1091
Adam Lesinski330edcd2015-05-04 17:40:56 -07001092static bool doDump(const AaptOptions& options) {
1093 for (const Source& source : options.input) {
1094 std::unique_ptr<ZipFile> zipFile = util::make_unique<ZipFile>();
1095 if (zipFile->open(source.path.data(), ZipFile::kOpenReadOnly) != android::NO_ERROR) {
1096 Logger::error(source) << "failed to open: " << strerror(errno) << std::endl;
1097 return false;
1098 }
1099
1100 std::shared_ptr<ResourceTable> table = std::make_shared<ResourceTable>();
1101 std::shared_ptr<ResourceTableResolver> resolver =
1102 std::make_shared<ResourceTableResolver>(
1103 table, std::vector<std::shared_ptr<const android::AssetManager>>());
1104
1105 ZipEntry* entry = zipFile->getEntryByName("resources.arsc");
1106 if (!entry) {
1107 Logger::error(source) << "missing 'resources.arsc'." << std::endl;
1108 return false;
1109 }
1110
1111 std::unique_ptr<void, DeleteMalloc> uncompressedData = std::unique_ptr<void, DeleteMalloc>(
1112 zipFile->uncompress(entry));
1113 assert(uncompressedData);
1114
1115 BinaryResourceParser parser(table, resolver, source, uncompressedData.get(),
1116 entry->getUncompressedLen());
1117 if (!parser.parse()) {
1118 return false;
1119 }
1120
1121 if (options.phase == AaptOptions::Phase::Dump) {
1122 Debug::printTable(table);
1123 } else if (options.phase == AaptOptions::Phase::DumpStyleGraph) {
Adam Lesinskid13fb242015-05-12 20:40:48 -07001124 Debug::printStyleGraph(table, options.dumpStyleTarget);
Adam Lesinski330edcd2015-05-04 17:40:56 -07001125 }
1126 }
1127 return true;
1128}
1129
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001130int main(int argc, char** argv) {
1131 Logger::setLog(std::make_shared<Log>(std::cerr, std::cerr));
Adam Lesinski98aa3ad2015-04-06 11:46:52 -07001132 AaptOptions options = prepareArgs(argc, argv);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001133
Adam Lesinski330edcd2015-05-04 17:40:56 -07001134 if (options.phase == AaptOptions::Phase::Dump ||
1135 options.phase == AaptOptions::Phase::DumpStyleGraph) {
1136 if (!doDump(options)) {
1137 return 1;
1138 }
1139 return 0;
1140 }
1141
Adam Lesinski98aa3ad2015-04-06 11:46:52 -07001142 // If we specified a manifest, go ahead and load the package name from the manifest.
1143 if (!options.manifest.path.empty()) {
1144 if (!loadAppInfo(options.manifest, &options.appInfo)) {
1145 return false;
1146 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001147 }
1148
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001149 // Verify we have some common options set.
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001150 if (options.appInfo.package.empty()) {
1151 Logger::error() << "no package name specified." << std::endl;
1152 return false;
1153 }
1154
Adam Lesinski98aa3ad2015-04-06 11:46:52 -07001155 // Every phase needs a resource table.
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001156 std::shared_ptr<ResourceTable> table = std::make_shared<ResourceTable>();
1157 table->setPackage(options.appInfo.package);
1158 if (options.appInfo.package == u"android") {
1159 table->setPackageId(0x01);
1160 } else {
1161 table->setPackageId(0x7f);
1162 }
1163
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001164 // Load the included libraries.
Adam Lesinski330edcd2015-05-04 17:40:56 -07001165 std::vector<std::shared_ptr<const android::AssetManager>> sources;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001166 for (const Source& source : options.libraries) {
Adam Lesinski330edcd2015-05-04 17:40:56 -07001167 std::shared_ptr<android::AssetManager> assetManager =
1168 std::make_shared<android::AssetManager>();
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001169 int32_t cookie;
Adam Lesinski330edcd2015-05-04 17:40:56 -07001170 if (!assetManager->addAssetPath(android::String8(source.path.data()), &cookie)) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001171 Logger::error(source) << "failed to load library." << std::endl;
1172 return false;
1173 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001174
Adam Lesinski330edcd2015-05-04 17:40:56 -07001175 if (cookie == 0) {
1176 Logger::error(source) << "failed to load library." << std::endl;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001177 return false;
1178 }
Adam Lesinski330edcd2015-05-04 17:40:56 -07001179 sources.push_back(assetManager);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001180 }
1181
1182 // Make the resolver that will cache IDs for us.
Adam Lesinski24aad162015-04-24 19:19:30 -07001183 std::shared_ptr<ResourceTableResolver> resolver = std::make_shared<ResourceTableResolver>(
Adam Lesinski330edcd2015-05-04 17:40:56 -07001184 table, sources);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001185
Adam Lesinski769de982015-04-10 19:43:55 -07001186 if (options.phase == AaptOptions::Phase::Compile) {
1187 if (!compile(options, table, resolver)) {
1188 Logger::error() << "aapt exiting with failures." << std::endl;
1189 return 1;
1190 }
1191 } else if (options.phase == AaptOptions::Phase::Link) {
1192 if (!link(options, table, resolver)) {
1193 Logger::error() << "aapt exiting with failures." << std::endl;
1194 return 1;
1195 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001196 }
1197 return 0;
1198}