blob: 54a7329359f18cdd43a47878bb7e401156de9ee1 [file] [log] [blame]
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include "AppInfo.h"
18#include "BigBuffer.h"
19#include "BinaryResourceParser.h"
Adam Lesinski4d3a9872015-04-09 19:53:22 -070020#include "BindingXmlPullParser.h"
Adam Lesinski330edcd2015-05-04 17:40:56 -070021#include "Debug.h"
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080022#include "Files.h"
Adam Lesinski98aa3ad2015-04-06 11:46:52 -070023#include "Flag.h"
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080024#include "JavaClassGenerator.h"
25#include "Linker.h"
Adam Lesinski8c831ca2015-05-20 15:24:01 -070026#include "ManifestMerger.h"
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080027#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 Lesinskia1ad4a82015-06-08 11:41:09 -070031#include "ProguardRules.h"
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080032#include "ResourceParser.h"
33#include "ResourceTable.h"
Adam Lesinski24aad162015-04-24 19:19:30 -070034#include "ResourceTableResolver.h"
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080035#include "ResourceValues.h"
36#include "SdkConstants.h"
37#include "SourceXmlPullParser.h"
38#include "StringPiece.h"
39#include "TableFlattener.h"
40#include "Util.h"
41#include "XmlFlattener.h"
Adam Lesinski769de982015-04-10 19:43:55 -070042#include "ZipFile.h"
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080043
44#include <algorithm>
45#include <androidfw/AssetManager.h>
46#include <cstdlib>
47#include <dirent.h>
48#include <errno.h>
49#include <fstream>
50#include <iostream>
51#include <sstream>
52#include <sys/stat.h>
Adam Lesinski769de982015-04-10 19:43:55 -070053#include <unordered_set>
Adam Lesinski98aa3ad2015-04-06 11:46:52 -070054#include <utils/Errors.h>
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080055
Adam Lesinski5886a922015-04-15 20:29:22 -070056constexpr const char* kAaptVersionStr = "2.0-alpha";
57
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080058using namespace aapt;
59
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080060/**
Adam Lesinski8c831ca2015-05-20 15:24:01 -070061 * Used with smart pointers to free malloc'ed memory.
62 */
63struct DeleteMalloc {
64 void operator()(void* ptr) {
65 free(ptr);
66 }
67};
68
69struct StaticLibraryData {
70 Source source;
71 std::unique_ptr<ZipFile> apk;
72};
73
74/**
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080075 * Collect files from 'root', filtering out any files that do not
76 * match the FileFilter 'filter'.
77 */
Adam Lesinski98aa3ad2015-04-06 11:46:52 -070078bool walkTree(const Source& root, const FileFilter& filter,
79 std::vector<Source>* outEntries) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080080 bool error = false;
81
Adam Lesinski98aa3ad2015-04-06 11:46:52 -070082 for (const std::string& dirName : listFiles(root.path)) {
83 std::string dir = root.path;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080084 appendPath(&dir, dirName);
85
86 FileType ft = getFileType(dir);
87 if (!filter(dirName, ft)) {
88 continue;
89 }
90
91 if (ft != FileType::kDirectory) {
92 continue;
93 }
94
95 for (const std::string& fileName : listFiles(dir)) {
96 std::string file(dir);
97 appendPath(&file, fileName);
98
99 FileType ft = getFileType(file);
100 if (!filter(fileName, ft)) {
101 continue;
102 }
103
104 if (ft != FileType::kRegular) {
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700105 Logger::error(Source{ file }) << "not a regular file." << std::endl;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800106 error = true;
107 continue;
108 }
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700109 outEntries->push_back(Source{ file });
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800110 }
111 }
112 return !error;
113}
114
Adam Lesinski769de982015-04-10 19:43:55 -0700115void versionStylesForCompat(const std::shared_ptr<ResourceTable>& table) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800116 for (auto& type : *table) {
117 if (type->type != ResourceType::kStyle) {
118 continue;
119 }
120
121 for (auto& entry : type->entries) {
122 // Add the versioned styles we want to create
123 // here. They are added to the table after
124 // iterating over the original set of styles.
125 //
126 // A stack is used since auto-generated styles
127 // from later versions should override
128 // auto-generated styles from earlier versions.
129 // Iterating over the styles is done in order,
130 // so we will always visit sdkVersions from smallest
131 // to largest.
132 std::stack<ResourceConfigValue> addStack;
133
134 for (ResourceConfigValue& configValue : entry->values) {
135 visitFunc<Style>(*configValue.value, [&](Style& style) {
136 // Collect which entries we've stripped and the smallest
137 // SDK level which was stripped.
138 size_t minSdkStripped = std::numeric_limits<size_t>::max();
139 std::vector<Style::Entry> stripped;
140
141 // Iterate over the style's entries and erase/record the
142 // attributes whose SDK level exceeds the config's sdkVersion.
143 auto iter = style.entries.begin();
144 while (iter != style.entries.end()) {
145 if (iter->key.name.package == u"android") {
Adam Lesinski75f3a552015-06-03 14:54:23 -0700146 size_t sdkLevel = findAttributeSdkLevel(iter->key.name);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800147 if (sdkLevel > 1 && sdkLevel > configValue.config.sdkVersion) {
148 // Record that we are about to strip this.
149 stripped.emplace_back(std::move(*iter));
150 minSdkStripped = std::min(minSdkStripped, sdkLevel);
151
152 // Erase this from this style.
153 iter = style.entries.erase(iter);
154 continue;
155 }
156 }
157 ++iter;
158 }
159
160 if (!stripped.empty()) {
161 // We have stripped attributes, so let's create a new style to hold them.
162 ConfigDescription versionConfig(configValue.config);
163 versionConfig.sdkVersion = minSdkStripped;
164
165 ResourceConfigValue value = {
166 versionConfig,
167 configValue.source,
168 {},
169
170 // Create a copy of the original style.
Adam Lesinski769de982015-04-10 19:43:55 -0700171 std::unique_ptr<Value>(configValue.value->clone(
172 &table->getValueStringPool()))
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800173 };
174
175 Style& newStyle = static_cast<Style&>(*value.value);
176
177 // Move the recorded stripped attributes into this new style.
178 std::move(stripped.begin(), stripped.end(),
179 std::back_inserter(newStyle.entries));
180
181 // We will add this style to the table later. If we do it now, we will
182 // mess up iteration.
183 addStack.push(std::move(value));
184 }
185 });
186 }
187
188 auto comparator =
189 [](const ResourceConfigValue& lhs, const ConfigDescription& rhs) -> bool {
190 return lhs.config < rhs;
191 };
192
193 while (!addStack.empty()) {
194 ResourceConfigValue& value = addStack.top();
195 auto iter = std::lower_bound(entry->values.begin(), entry->values.end(),
196 value.config, comparator);
197 if (iter == entry->values.end() || iter->config != value.config) {
198 entry->values.insert(iter, std::move(value));
199 }
200 addStack.pop();
201 }
202 }
203 }
204}
205
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700206struct CompileItem {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800207 ResourceName name;
208 ConfigDescription config;
Adam Lesinski39c353a2015-05-14 17:58:14 -0700209 Source source;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700210 std::string extension;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800211};
212
Adam Lesinski769de982015-04-10 19:43:55 -0700213struct LinkItem {
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700214 ResourceName name;
215 ConfigDescription config;
Adam Lesinski39c353a2015-05-14 17:58:14 -0700216 Source source;
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700217 std::string originalPath;
218 ZipFile* apk;
Adam Lesinski24aad162015-04-24 19:19:30 -0700219 std::u16string originalPackage;
Adam Lesinski769de982015-04-10 19:43:55 -0700220};
221
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700222template <typename TChar>
223static BasicStringPiece<TChar> getExtension(const BasicStringPiece<TChar>& str) {
224 auto iter = std::find(str.begin(), str.end(), static_cast<TChar>('.'));
225 if (iter == str.end()) {
226 return BasicStringPiece<TChar>();
Adam Lesinski769de982015-04-10 19:43:55 -0700227 }
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700228 size_t offset = (iter - str.begin()) + 1;
229 return str.substr(offset, str.size() - offset);
230}
231
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700232std::string buildFileReference(const ResourceNameRef& name, const ConfigDescription& config,
233 const StringPiece& extension) {
234 std::stringstream path;
235 path << "res/" << name.type;
236 if (config != ConfigDescription{}) {
237 path << "-" << config;
238 }
239 path << "/" << util::utf16ToUtf8(name.entry);
240 if (!extension.empty()) {
241 path << "." << extension;
242 }
Adam Lesinski769de982015-04-10 19:43:55 -0700243 return path.str();
244}
245
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700246std::string buildFileReference(const CompileItem& item) {
247 return buildFileReference(item.name, item.config, item.extension);
248}
249
250std::string buildFileReference(const LinkItem& item) {
251 return buildFileReference(item.name, item.config, getExtension<char>(item.originalPath));
252}
253
Adam Lesinski39c353a2015-05-14 17:58:14 -0700254template <typename T>
255bool addFileReference(const std::shared_ptr<ResourceTable>& table, const T& item) {
Adam Lesinski769de982015-04-10 19:43:55 -0700256 StringPool& pool = table->getValueStringPool();
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700257 StringPool::Ref ref = pool.makeRef(util::utf8ToUtf16(buildFileReference(item)),
258 StringPool::Context{ 0, item.config });
Adam Lesinski769de982015-04-10 19:43:55 -0700259 return table->addResource(item.name, item.config, item.source.line(0),
260 util::make_unique<FileReference>(ref));
261}
262
263struct AaptOptions {
264 enum class Phase {
265 Link,
266 Compile,
Adam Lesinski330edcd2015-05-04 17:40:56 -0700267 Dump,
268 DumpStyleGraph,
Adam Lesinski769de982015-04-10 19:43:55 -0700269 };
270
Adam Lesinski6d8e4c42015-05-01 14:47:28 -0700271 enum class PackageType {
272 StandardApp,
273 StaticLibrary,
274 };
275
Adam Lesinski769de982015-04-10 19:43:55 -0700276 // The phase to process.
277 Phase phase;
278
Adam Lesinski6d8e4c42015-05-01 14:47:28 -0700279 // The type of package to produce.
280 PackageType packageType = PackageType::StandardApp;
281
Adam Lesinski769de982015-04-10 19:43:55 -0700282 // Details about the app.
283 AppInfo appInfo;
284
285 // The location of the manifest file.
286 Source manifest;
287
288 // The APK files to link.
289 std::vector<Source> input;
290
291 // The libraries these files may reference.
292 std::vector<Source> libraries;
293
294 // Output path. This can be a directory or file
295 // depending on the phase.
296 Source output;
297
298 // Directory in which to write binding xml files.
299 Source bindingOutput;
300
301 // Directory to in which to generate R.java.
302 Maybe<Source> generateJavaClass;
303
Adam Lesinskia1ad4a82015-06-08 11:41:09 -0700304 // File in which to produce proguard rules.
305 Maybe<Source> generateProguardRules;
306
Adam Lesinski769de982015-04-10 19:43:55 -0700307 // Whether to output verbose details about
308 // compilation.
309 bool verbose = false;
Adam Lesinski5886a922015-04-15 20:29:22 -0700310
311 // Whether or not to auto-version styles or layouts
312 // referencing attributes defined in a newer SDK
313 // level than the style or layout is defined for.
314 bool versionStylesAndLayouts = true;
Adam Lesinskid13fb242015-05-12 20:40:48 -0700315
316 // The target style that will have it's style hierarchy dumped
317 // when the phase is DumpStyleGraph.
318 ResourceName dumpStyleTarget;
Adam Lesinski769de982015-04-10 19:43:55 -0700319};
320
Adam Lesinski75f3a552015-06-03 14:54:23 -0700321struct IdCollector : public xml::Visitor {
322 IdCollector(const Source& source, const std::shared_ptr<ResourceTable>& table) :
323 mSource(source), mTable(table) {
324 }
325
326 virtual void visit(xml::Text* node) override {}
327
328 virtual void visit(xml::Namespace* node) override {
329 for (const auto& child : node->children) {
330 child->accept(this);
331 }
332 }
333
334 virtual void visit(xml::Element* node) override {
335 for (const xml::Attribute& attr : node->attributes) {
336 bool create = false;
337 bool priv = false;
338 ResourceNameRef nameRef;
339 if (ResourceParser::tryParseReference(attr.value, &nameRef, &create, &priv)) {
340 if (create) {
341 mTable->addResource(nameRef, {}, mSource.line(node->lineNumber),
342 util::make_unique<Id>());
343 }
344 }
345 }
346
347 for (const auto& child : node->children) {
348 child->accept(this);
349 }
350 }
351
352private:
353 Source mSource;
354 std::shared_ptr<ResourceTable> mTable;
355};
356
Adam Lesinski769de982015-04-10 19:43:55 -0700357bool compileXml(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table,
Adam Lesinski330edcd2015-05-04 17:40:56 -0700358 const CompileItem& item, ZipFile* outApk) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800359 std::ifstream in(item.source.path, std::ifstream::binary);
360 if (!in) {
361 Logger::error(item.source) << strerror(errno) << std::endl;
362 return false;
363 }
364
Adam Lesinski75f3a552015-06-03 14:54:23 -0700365 SourceLogger logger(item.source);
366 std::unique_ptr<xml::Node> root = xml::inflate(&in, &logger);
367 if (!root) {
368 return false;
369 }
370
371 // Collect any resource ID's declared here.
372 IdCollector idCollector(item.source, table);
373 root->accept(&idCollector);
374
Adam Lesinski4d3a9872015-04-09 19:53:22 -0700375 BigBuffer outBuffer(1024);
Adam Lesinski75f3a552015-06-03 14:54:23 -0700376 if (!xml::flatten(root.get(), options.appInfo.package, &outBuffer)) {
377 logger.error() << "failed to encode XML." << std::endl;
Adam Lesinski330edcd2015-05-04 17:40:56 -0700378 return false;
379 }
380
381 // Write the resulting compiled XML file to the output APK.
382 if (outApk->add(outBuffer, buildFileReference(item).data(), ZipEntry::kCompressStored,
383 nullptr) != android::NO_ERROR) {
384 Logger::error(options.output) << "failed to write compiled '" << item.source
385 << "' to apk." << std::endl;
386 return false;
387 }
388 return true;
389}
390
Adam Lesinski39c353a2015-05-14 17:58:14 -0700391/**
392 * Determines if a layout should be auto generated based on SDK level. We do not
393 * generate a layout if there is already a layout defined whose SDK version is greater than
394 * the one we want to generate.
395 */
396bool shouldGenerateVersionedResource(const std::shared_ptr<const ResourceTable>& table,
397 const ResourceName& name, const ConfigDescription& config,
398 int sdkVersionToGenerate) {
399 assert(sdkVersionToGenerate > config.sdkVersion);
400 const ResourceTableType* type;
401 const ResourceEntry* entry;
402 std::tie(type, entry) = table->findResource(name);
403 assert(type && entry);
404
405 auto iter = std::lower_bound(entry->values.begin(), entry->values.end(), config,
406 [](const ResourceConfigValue& lhs, const ConfigDescription& config) -> bool {
407 return lhs.config < config;
408 });
409
410 assert(iter != entry->values.end());
411 ++iter;
412
413 if (iter == entry->values.end()) {
414 return true;
415 }
416
417 ConfigDescription newConfig = config;
418 newConfig.sdkVersion = sdkVersionToGenerate;
419 return newConfig < iter->config;
420}
421
422bool linkXml(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table,
423 const std::shared_ptr<IResolver>& resolver, const LinkItem& item,
Adam Lesinskia1ad4a82015-06-08 11:41:09 -0700424 const void* data, size_t dataLen, ZipFile* outApk, std::queue<LinkItem>* outQueue,
425 proguard::KeepSet* keepSet) {
Adam Lesinski75f3a552015-06-03 14:54:23 -0700426 SourceLogger logger(item.source);
427 std::unique_ptr<xml::Node> root = xml::inflate(data, dataLen, &logger);
428 if (!root) {
Adam Lesinski330edcd2015-05-04 17:40:56 -0700429 return false;
430 }
431
Adam Lesinski75f3a552015-06-03 14:54:23 -0700432 xml::FlattenOptions xmlOptions;
Adam Lesinski330edcd2015-05-04 17:40:56 -0700433 if (options.packageType == AaptOptions::PackageType::StaticLibrary) {
434 xmlOptions.keepRawValues = true;
435 }
Adam Lesinski24aad162015-04-24 19:19:30 -0700436
Adam Lesinski5886a922015-04-15 20:29:22 -0700437 if (options.versionStylesAndLayouts) {
438 // We strip attributes that do not belong in this version of the resource.
439 // Non-version qualified resources have an implicit version 1 requirement.
440 xmlOptions.maxSdkAttribute = item.config.sdkVersion ? item.config.sdkVersion : 1;
441 }
Adam Lesinski769de982015-04-10 19:43:55 -0700442
Adam Lesinskia1ad4a82015-06-08 11:41:09 -0700443 if (options.generateProguardRules) {
444 proguard::collectProguardRules(item.name.type, item.source, root.get(), keepSet);
445 }
446
Adam Lesinski75f3a552015-06-03 14:54:23 -0700447 BigBuffer outBuffer(1024);
448 Maybe<size_t> minStrippedSdk = xml::flattenAndLink(item.source, root.get(),
449 item.originalPackage, resolver,
450 xmlOptions, &outBuffer);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800451 if (!minStrippedSdk) {
Adam Lesinski75f3a552015-06-03 14:54:23 -0700452 logger.error() << "failed to encode XML." << std::endl;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800453 return false;
454 }
455
456 if (minStrippedSdk.value() > 0) {
457 // Something was stripped, so let's generate a new file
458 // with the version of the smallest SDK version stripped.
Adam Lesinski39c353a2015-05-14 17:58:14 -0700459 // We can only generate a versioned layout if there doesn't exist a layout
460 // with sdk version greater than the current one but less than the one we
461 // want to generate.
462 if (shouldGenerateVersionedResource(table, item.name, item.config,
463 minStrippedSdk.value())) {
464 LinkItem newWork = item;
465 newWork.config.sdkVersion = minStrippedSdk.value();
466 outQueue->push(newWork);
467
468 if (!addFileReference(table, newWork)) {
469 Logger::error(options.output) << "failed to add auto-versioned resource '"
470 << newWork.name << "'." << std::endl;
471 return false;
472 }
473 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800474 }
475
Adam Lesinski330edcd2015-05-04 17:40:56 -0700476 if (outApk->add(outBuffer, buildFileReference(item).data(), ZipEntry::kCompressDeflated,
Adam Lesinski769de982015-04-10 19:43:55 -0700477 nullptr) != android::NO_ERROR) {
Adam Lesinski330edcd2015-05-04 17:40:56 -0700478 Logger::error(options.output) << "failed to write linked file '"
479 << buildFileReference(item) << "' to apk." << std::endl;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800480 return false;
481 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800482 return true;
483}
484
Adam Lesinski769de982015-04-10 19:43:55 -0700485bool compilePng(const AaptOptions& options, const CompileItem& item, ZipFile* outApk) {
486 std::ifstream in(item.source.path, std::ifstream::binary);
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700487 if (!in) {
Adam Lesinski769de982015-04-10 19:43:55 -0700488 Logger::error(item.source) << strerror(errno) << std::endl;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700489 return false;
490 }
491
Adam Lesinski769de982015-04-10 19:43:55 -0700492 BigBuffer outBuffer(4096);
493 std::string err;
494 Png png;
495 if (!png.process(item.source, in, &outBuffer, {}, &err)) {
496 Logger::error(item.source) << err << std::endl;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700497 return false;
498 }
499
Adam Lesinski769de982015-04-10 19:43:55 -0700500 if (outApk->add(outBuffer, buildFileReference(item).data(), ZipEntry::kCompressStored,
501 nullptr) != android::NO_ERROR) {
502 Logger::error(options.output) << "failed to write compiled '" << item.source
503 << "' to apk." << std::endl;
504 return false;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700505 }
Adam Lesinski769de982015-04-10 19:43:55 -0700506 return true;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700507}
508
Adam Lesinski769de982015-04-10 19:43:55 -0700509bool copyFile(const AaptOptions& options, const CompileItem& item, ZipFile* outApk) {
510 if (outApk->add(item.source.path.data(), buildFileReference(item).data(),
511 ZipEntry::kCompressStored, nullptr) != android::NO_ERROR) {
512 Logger::error(options.output) << "failed to copy file '" << item.source << "' to apk."
513 << std::endl;
514 return false;
515 }
516 return true;
517}
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800518
Adam Lesinski330edcd2015-05-04 17:40:56 -0700519bool compileManifest(const AaptOptions& options, const std::shared_ptr<IResolver>& resolver,
Adam Lesinski8c831ca2015-05-20 15:24:01 -0700520 const std::map<std::shared_ptr<ResourceTable>, StaticLibraryData>& libApks,
Adam Lesinskia1ad4a82015-06-08 11:41:09 -0700521 const android::ResTable& table, ZipFile* outApk, proguard::KeepSet* keepSet) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800522 if (options.verbose) {
Adam Lesinski769de982015-04-10 19:43:55 -0700523 Logger::note(options.manifest) << "compiling AndroidManifest.xml." << std::endl;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800524 }
525
526 std::ifstream in(options.manifest.path, std::ifstream::binary);
527 if (!in) {
528 Logger::error(options.manifest) << strerror(errno) << std::endl;
529 return false;
530 }
531
Adam Lesinski75f3a552015-06-03 14:54:23 -0700532 SourceLogger logger(options.manifest);
533 std::unique_ptr<xml::Node> root = xml::inflate(&in, &logger);
534 if (!root) {
535 return false;
536 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800537
Adam Lesinski8c831ca2015-05-20 15:24:01 -0700538 ManifestMerger merger({});
539 if (!merger.setAppManifest(options.manifest, options.appInfo.package, std::move(root))) {
540 return false;
541 }
542
543 for (const auto& entry : libApks) {
544 ZipFile* libApk = entry.second.apk.get();
545 const std::u16string& libPackage = entry.first->getPackage();
546 const Source& libSource = entry.second.source;
547
548 ZipEntry* zipEntry = libApk->getEntryByName("AndroidManifest.xml");
549 if (!zipEntry) {
550 continue;
551 }
552
553 std::unique_ptr<void, DeleteMalloc> uncompressedData = std::unique_ptr<void, DeleteMalloc>(
554 libApk->uncompress(zipEntry));
555 assert(uncompressedData);
556
557 SourceLogger logger(libSource);
558 std::unique_ptr<xml::Node> libRoot = xml::inflate(uncompressedData.get(),
559 zipEntry->getUncompressedLen(), &logger);
560 if (!libRoot) {
561 return false;
562 }
563
564 if (!merger.mergeLibraryManifest(libSource, libPackage, std::move(libRoot))) {
565 return false;
566 }
567 }
568
Adam Lesinskia1ad4a82015-06-08 11:41:09 -0700569 if (options.generateProguardRules) {
570 proguard::collectProguardRulesForManifest(options.manifest, merger.getMergedXml(),
571 keepSet);
572 }
573
Adam Lesinski75f3a552015-06-03 14:54:23 -0700574 BigBuffer outBuffer(1024);
Adam Lesinski8c831ca2015-05-20 15:24:01 -0700575 if (!xml::flattenAndLink(options.manifest, merger.getMergedXml(), options.appInfo.package,
576 resolver, {}, &outBuffer)) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800577 return false;
578 }
579
Adam Lesinski769de982015-04-10 19:43:55 -0700580 std::unique_ptr<uint8_t[]> data = util::copy(outBuffer);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800581
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700582 android::ResXMLTree tree;
Adam Lesinski769de982015-04-10 19:43:55 -0700583 if (tree.setTo(data.get(), outBuffer.size(), false) != android::NO_ERROR) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800584 return false;
585 }
586
Adam Lesinski330edcd2015-05-04 17:40:56 -0700587 ManifestValidator validator(table);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800588 if (!validator.validate(options.manifest, &tree)) {
589 return false;
590 }
591
Adam Lesinski769de982015-04-10 19:43:55 -0700592 if (outApk->add(data.get(), outBuffer.size(), "AndroidManifest.xml",
593 ZipEntry::kCompressStored, nullptr) != android::NO_ERROR) {
594 Logger::error(options.output) << "failed to write 'AndroidManifest.xml' to apk."
595 << std::endl;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800596 return false;
597 }
598 return true;
599}
600
Adam Lesinski769de982015-04-10 19:43:55 -0700601static bool compileValues(const std::shared_ptr<ResourceTable>& table, const Source& source,
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700602 const ConfigDescription& config) {
603 std::ifstream in(source.path, std::ifstream::binary);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800604 if (!in) {
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700605 Logger::error(source) << strerror(errno) << std::endl;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800606 return false;
607 }
608
609 std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<SourceXmlPullParser>(in);
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700610 ResourceParser parser(table, source, config, xmlParser);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800611 return parser.parse();
612}
613
614struct ResourcePathData {
615 std::u16string resourceDir;
616 std::u16string name;
617 std::string extension;
618 ConfigDescription config;
619};
620
621/**
622 * Resource file paths are expected to look like:
623 * [--/res/]type[-config]/name
624 */
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700625static Maybe<ResourcePathData> extractResourcePathData(const Source& source) {
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700626 // TODO(adamlesinski): Use Windows path separator on windows.
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800627 std::vector<std::string> parts = util::splitAndLowercase(source.path, '/');
628 if (parts.size() < 2) {
629 Logger::error(source) << "bad resource path." << std::endl;
630 return {};
631 }
632
633 std::string& dir = parts[parts.size() - 2];
634 StringPiece dirStr = dir;
635
636 ConfigDescription config;
637 size_t dashPos = dir.find('-');
638 if (dashPos != std::string::npos) {
639 StringPiece configStr = dirStr.substr(dashPos + 1, dir.size() - (dashPos + 1));
640 if (!ConfigDescription::parse(configStr, &config)) {
641 Logger::error(source)
642 << "invalid configuration '"
643 << configStr
644 << "'."
645 << std::endl;
646 return {};
647 }
648 dirStr = dirStr.substr(0, dashPos);
649 }
650
651 std::string& filename = parts[parts.size() - 1];
652 StringPiece name = filename;
653 StringPiece extension;
654 size_t dotPos = filename.find('.');
655 if (dotPos != std::string::npos) {
656 extension = name.substr(dotPos + 1, filename.size() - (dotPos + 1));
657 name = name.substr(0, dotPos);
658 }
659
660 return ResourcePathData{
661 util::utf8ToUtf16(dirStr),
662 util::utf8ToUtf16(name),
663 extension.toString(),
664 config
665 };
666}
667
Adam Lesinski769de982015-04-10 19:43:55 -0700668bool writeResourceTable(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table,
669 const TableFlattener::Options& flattenerOptions, ZipFile* outApk) {
670 if (table->begin() != table->end()) {
671 BigBuffer buffer(1024);
672 TableFlattener flattener(flattenerOptions);
673 if (!flattener.flatten(&buffer, *table)) {
674 Logger::error() << "failed to flatten resource table." << std::endl;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700675 return false;
676 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800677
Adam Lesinski769de982015-04-10 19:43:55 -0700678 if (options.verbose) {
679 Logger::note() << "Final resource table size=" << util::formatSize(buffer.size())
680 << std::endl;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700681 }
682
Adam Lesinski769de982015-04-10 19:43:55 -0700683 if (outApk->add(buffer, "resources.arsc", ZipEntry::kCompressStored, nullptr) !=
684 android::NO_ERROR) {
685 Logger::note(options.output) << "failed to store resource table." << std::endl;
686 return false;
687 }
688 }
689 return true;
690}
691
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700692/**
693 * For each FileReference in the table, adds a LinkItem to the link queue for processing.
694 */
695static void addApkFilesToLinkQueue(const std::u16string& package, const Source& source,
696 const std::shared_ptr<ResourceTable>& table,
697 const std::unique_ptr<ZipFile>& apk,
698 std::queue<LinkItem>* outLinkQueue) {
699 bool mangle = package != table->getPackage();
700 for (auto& type : *table) {
701 for (auto& entry : type->entries) {
702 ResourceName name = { package, type->type, entry->name };
703 if (mangle) {
704 NameMangler::mangle(table->getPackage(), &name.entry);
705 }
706
707 for (auto& value : entry->values) {
708 visitFunc<FileReference>(*value.value, [&](FileReference& ref) {
709 std::string pathUtf8 = util::utf16ToUtf8(*ref.path);
Adam Lesinski24aad162015-04-24 19:19:30 -0700710 Source newSource = source;
711 newSource.path += "/";
712 newSource.path += pathUtf8;
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700713 outLinkQueue->push(LinkItem{
Adam Lesinski39c353a2015-05-14 17:58:14 -0700714 name, value.config, newSource, pathUtf8, apk.get(),
Adam Lesinski24aad162015-04-24 19:19:30 -0700715 table->getPackage() });
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700716 // Now rewrite the file path.
717 if (mangle) {
718 ref.path = table->getValueStringPool().makeRef(util::utf8ToUtf16(
719 buildFileReference(name, value.config,
720 getExtension<char>(pathUtf8))));
721 }
722 });
723 }
724 }
725 }
726}
727
Adam Lesinski769de982015-04-10 19:43:55 -0700728static constexpr int kOpenFlags = ZipFile::kOpenCreate | ZipFile::kOpenTruncate |
729 ZipFile::kOpenReadWrite;
730
731bool link(const AaptOptions& options, const std::shared_ptr<ResourceTable>& outTable,
Adam Lesinski330edcd2015-05-04 17:40:56 -0700732 const std::shared_ptr<IResolver>& resolver) {
Adam Lesinski24aad162015-04-24 19:19:30 -0700733 std::map<std::shared_ptr<ResourceTable>, StaticLibraryData> apkFiles;
Adam Lesinski769de982015-04-10 19:43:55 -0700734 std::unordered_set<std::u16string> linkedPackages;
735
736 // Populate the linkedPackages with our own.
737 linkedPackages.insert(options.appInfo.package);
738
739 // Load all APK files.
740 for (const Source& source : options.input) {
741 std::unique_ptr<ZipFile> zipFile = util::make_unique<ZipFile>();
742 if (zipFile->open(source.path.data(), ZipFile::kOpenReadOnly) != android::NO_ERROR) {
743 Logger::error(source) << "failed to open: " << strerror(errno) << std::endl;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700744 return false;
745 }
746
Adam Lesinski769de982015-04-10 19:43:55 -0700747 std::shared_ptr<ResourceTable> table = std::make_shared<ResourceTable>();
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700748
Adam Lesinski769de982015-04-10 19:43:55 -0700749 ZipEntry* entry = zipFile->getEntryByName("resources.arsc");
750 if (!entry) {
751 Logger::error(source) << "missing 'resources.arsc'." << std::endl;
752 return false;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700753 }
754
Adam Lesinski24aad162015-04-24 19:19:30 -0700755 std::unique_ptr<void, DeleteMalloc> uncompressedData = std::unique_ptr<void, DeleteMalloc>(
756 zipFile->uncompress(entry));
Adam Lesinski769de982015-04-10 19:43:55 -0700757 assert(uncompressedData);
758
Adam Lesinski6cc479b2015-06-12 15:45:48 -0700759 BinaryResourceParser parser(table, resolver, source, options.appInfo.package,
760 uncompressedData.get(), entry->getUncompressedLen());
Adam Lesinski769de982015-04-10 19:43:55 -0700761 if (!parser.parse()) {
Adam Lesinski769de982015-04-10 19:43:55 -0700762 return false;
763 }
Adam Lesinski769de982015-04-10 19:43:55 -0700764
765 // Keep track of where this table came from.
Adam Lesinski24aad162015-04-24 19:19:30 -0700766 apkFiles[table] = StaticLibraryData{ source, std::move(zipFile) };
Adam Lesinski769de982015-04-10 19:43:55 -0700767
768 // Add the package to the set of linked packages.
769 linkedPackages.insert(table->getPackage());
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700770 }
771
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700772 std::queue<LinkItem> linkQueue;
Adam Lesinski769de982015-04-10 19:43:55 -0700773 for (auto& p : apkFiles) {
774 const std::shared_ptr<ResourceTable>& inTable = p.first;
775
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700776 // Collect all FileReferences and add them to the queue for processing.
Adam Lesinski24aad162015-04-24 19:19:30 -0700777 addApkFilesToLinkQueue(options.appInfo.package, p.second.source, inTable, p.second.apk,
778 &linkQueue);
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700779
780 // Merge the tables.
Adam Lesinski769de982015-04-10 19:43:55 -0700781 if (!outTable->merge(std::move(*inTable))) {
782 return false;
783 }
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700784 }
785
Adam Lesinski330edcd2015-05-04 17:40:56 -0700786 // Version all styles referencing attributes outside of their specified SDK version.
787 if (options.versionStylesAndLayouts) {
788 versionStylesForCompat(outTable);
789 }
790
Adam Lesinski769de982015-04-10 19:43:55 -0700791 {
792 // Now that everything is merged, let's link it.
Adam Lesinski330edcd2015-05-04 17:40:56 -0700793 Linker::Options linkerOptions;
794 if (options.packageType == AaptOptions::PackageType::StaticLibrary) {
795 linkerOptions.linkResourceIds = false;
796 }
797 Linker linker(outTable, resolver, linkerOptions);
Adam Lesinski769de982015-04-10 19:43:55 -0700798 if (!linker.linkAndValidate()) {
799 return false;
800 }
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700801
Adam Lesinski769de982015-04-10 19:43:55 -0700802 // Verify that all symbols exist.
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700803 const auto& unresolvedRefs = linker.getUnresolvedReferences();
804 if (!unresolvedRefs.empty()) {
805 for (const auto& entry : unresolvedRefs) {
806 for (const auto& source : entry.second) {
807 Logger::error(source) << "unresolved symbol '" << entry.first << "'."
808 << std::endl;
809 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800810 }
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700811 return false;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800812 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800813 }
814
Adam Lesinski769de982015-04-10 19:43:55 -0700815 // Open the output APK file for writing.
816 ZipFile outApk;
817 if (outApk.open(options.output.path.data(), kOpenFlags) != android::NO_ERROR) {
818 Logger::error(options.output) << "failed to open: " << strerror(errno) << std::endl;
819 return false;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700820 }
821
Adam Lesinskia1ad4a82015-06-08 11:41:09 -0700822 proguard::KeepSet keepSet;
823
Adam Lesinski330edcd2015-05-04 17:40:56 -0700824 android::ResTable binTable;
Adam Lesinskia1ad4a82015-06-08 11:41:09 -0700825 if (!compileManifest(options, resolver, apkFiles, binTable, &outApk, &keepSet)) {
Adam Lesinski769de982015-04-10 19:43:55 -0700826 return false;
827 }
828
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700829 for (; !linkQueue.empty(); linkQueue.pop()) {
830 const LinkItem& item = linkQueue.front();
Adam Lesinski769de982015-04-10 19:43:55 -0700831
Adam Lesinski24aad162015-04-24 19:19:30 -0700832 assert(!item.originalPackage.empty());
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700833 ZipEntry* entry = item.apk->getEntryByName(item.originalPath.data());
834 if (!entry) {
835 Logger::error(item.source) << "failed to find '" << item.originalPath << "'."
836 << std::endl;
837 return false;
838 }
Adam Lesinski769de982015-04-10 19:43:55 -0700839
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700840 if (util::stringEndsWith<char>(item.originalPath, ".xml")) {
841 void* uncompressedData = item.apk->uncompress(entry);
842 assert(uncompressedData);
Adam Lesinski769de982015-04-10 19:43:55 -0700843
Adam Lesinski39c353a2015-05-14 17:58:14 -0700844 if (!linkXml(options, outTable, resolver, item, uncompressedData,
Adam Lesinskia1ad4a82015-06-08 11:41:09 -0700845 entry->getUncompressedLen(), &outApk, &linkQueue, &keepSet)) {
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700846 Logger::error(options.output) << "failed to link '" << item.originalPath << "'."
847 << std::endl;
848 return false;
Adam Lesinski769de982015-04-10 19:43:55 -0700849 }
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700850 } else {
851 if (outApk.add(item.apk, entry, buildFileReference(item).data(), 0, nullptr) !=
852 android::NO_ERROR) {
853 Logger::error(options.output) << "failed to copy '" << item.originalPath << "'."
854 << std::endl;
855 return false;
Adam Lesinski769de982015-04-10 19:43:55 -0700856 }
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700857 }
858 }
859
860 // Generate the Java class file.
Adam Lesinski769de982015-04-10 19:43:55 -0700861 if (options.generateJavaClass) {
Adam Lesinski6d8e4c42015-05-01 14:47:28 -0700862 JavaClassGenerator::Options javaOptions;
863 if (options.packageType == AaptOptions::PackageType::StaticLibrary) {
864 javaOptions.useFinal = false;
865 }
866 JavaClassGenerator generator(outTable, javaOptions);
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700867
Adam Lesinski769de982015-04-10 19:43:55 -0700868 for (const std::u16string& package : linkedPackages) {
869 Source outPath = options.generateJavaClass.value();
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800870
Adam Lesinski769de982015-04-10 19:43:55 -0700871 // Build the output directory from the package name.
872 // Eg. com.android.app -> com/android/app
873 const std::string packageUtf8 = util::utf16ToUtf8(package);
874 for (StringPiece part : util::tokenize<char>(packageUtf8, '.')) {
875 appendPath(&outPath.path, part);
876 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800877
Adam Lesinski769de982015-04-10 19:43:55 -0700878 if (!mkdirs(outPath.path)) {
879 Logger::error(outPath) << strerror(errno) << std::endl;
880 return false;
881 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800882
Adam Lesinski769de982015-04-10 19:43:55 -0700883 appendPath(&outPath.path, "R.java");
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800884
Adam Lesinski769de982015-04-10 19:43:55 -0700885 if (options.verbose) {
886 Logger::note(outPath) << "writing Java symbols." << std::endl;
887 }
888
889 std::ofstream fout(outPath.path);
890 if (!fout) {
891 Logger::error(outPath) << strerror(errno) << std::endl;
892 return false;
893 }
894
895 if (!generator.generate(package, fout)) {
896 Logger::error(outPath) << generator.getError() << "." << std::endl;
897 return false;
898 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800899 }
900 }
901
Adam Lesinskia1ad4a82015-06-08 11:41:09 -0700902 // Generate the Proguard rules file.
903 if (options.generateProguardRules) {
904 const Source& outPath = options.generateProguardRules.value();
905
906 if (options.verbose) {
907 Logger::note(outPath) << "writing proguard rules." << std::endl;
908 }
909
910 std::ofstream fout(outPath.path);
911 if (!fout) {
912 Logger::error(outPath) << strerror(errno) << std::endl;
913 return false;
914 }
915
916 if (!proguard::writeKeepSet(&fout, keepSet)) {
917 Logger::error(outPath) << "failed to write proguard rules." << std::endl;
918 return false;
919 }
920 }
921
Adam Lesinski24aad162015-04-24 19:19:30 -0700922 outTable->getValueStringPool().prune();
923 outTable->getValueStringPool().sort(
924 [](const StringPool::Entry& a, const StringPool::Entry& b) -> bool {
925 if (a.context.priority < b.context.priority) {
926 return true;
927 }
928
929 if (a.context.priority > b.context.priority) {
930 return false;
931 }
932 return a.value < b.value;
933 });
934
935
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700936 // Flatten the resource table.
Adam Lesinski769de982015-04-10 19:43:55 -0700937 TableFlattener::Options flattenerOptions;
Adam Lesinski330edcd2015-05-04 17:40:56 -0700938 if (options.packageType != AaptOptions::PackageType::StaticLibrary) {
939 flattenerOptions.useExtendedChunks = false;
Adam Lesinski6d8e4c42015-05-01 14:47:28 -0700940 }
941
Adam Lesinski769de982015-04-10 19:43:55 -0700942 if (!writeResourceTable(options, outTable, flattenerOptions, &outApk)) {
943 return false;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800944 }
Adam Lesinski769de982015-04-10 19:43:55 -0700945
946 outApk.flush();
947 return true;
948}
949
950bool compile(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table,
Adam Lesinski24aad162015-04-24 19:19:30 -0700951 const std::shared_ptr<IResolver>& resolver) {
Adam Lesinski769de982015-04-10 19:43:55 -0700952 std::queue<CompileItem> compileQueue;
953 bool error = false;
954
955 // Compile all the resource files passed in on the command line.
956 for (const Source& source : options.input) {
957 // Need to parse the resource type/config/filename.
958 Maybe<ResourcePathData> maybePathData = extractResourcePathData(source);
959 if (!maybePathData) {
960 return false;
961 }
962
963 const ResourcePathData& pathData = maybePathData.value();
964 if (pathData.resourceDir == u"values") {
965 // The file is in the values directory, which means its contents will
966 // go into the resource table.
967 if (options.verbose) {
968 Logger::note(source) << "compiling values." << std::endl;
969 }
970
971 error |= !compileValues(table, source, pathData.config);
972 } else {
973 // The file is in a directory like 'layout' or 'drawable'. Find out
974 // the type.
975 const ResourceType* type = parseResourceType(pathData.resourceDir);
976 if (!type) {
977 Logger::error(source) << "invalid resource type '" << pathData.resourceDir << "'."
978 << std::endl;
979 return false;
980 }
981
982 compileQueue.push(CompileItem{
Adam Lesinski769de982015-04-10 19:43:55 -0700983 ResourceName{ table->getPackage(), *type, pathData.name },
984 pathData.config,
Adam Lesinski39c353a2015-05-14 17:58:14 -0700985 source,
Adam Lesinski769de982015-04-10 19:43:55 -0700986 pathData.extension
987 });
988 }
989 }
990
991 if (error) {
992 return false;
993 }
Adam Lesinski769de982015-04-10 19:43:55 -0700994 // Open the output APK file for writing.
995 ZipFile outApk;
996 if (outApk.open(options.output.path.data(), kOpenFlags) != android::NO_ERROR) {
997 Logger::error(options.output) << "failed to open: " << strerror(errno) << std::endl;
998 return false;
999 }
1000
1001 // Compile each file.
1002 for (; !compileQueue.empty(); compileQueue.pop()) {
1003 const CompileItem& item = compileQueue.front();
1004
1005 // Add the file name to the resource table.
1006 error |= !addFileReference(table, item);
1007
1008 if (item.extension == "xml") {
Adam Lesinski330edcd2015-05-04 17:40:56 -07001009 error |= !compileXml(options, table, item, &outApk);
Adam Lesinski769de982015-04-10 19:43:55 -07001010 } else if (item.extension == "png" || item.extension == "9.png") {
1011 error |= !compilePng(options, item, &outApk);
1012 } else {
1013 error |= !copyFile(options, item, &outApk);
1014 }
1015 }
1016
1017 if (error) {
1018 return false;
1019 }
1020
1021 // Link and assign resource IDs.
Adam Lesinski330edcd2015-05-04 17:40:56 -07001022 Linker linker(table, resolver, {});
Adam Lesinski769de982015-04-10 19:43:55 -07001023 if (!linker.linkAndValidate()) {
1024 return false;
1025 }
1026
1027 // Flatten the resource table.
1028 if (!writeResourceTable(options, table, {}, &outApk)) {
1029 return false;
1030 }
1031
1032 outApk.flush();
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001033 return true;
1034}
1035
Adam Lesinskid5c4f872015-04-21 13:56:10 -07001036bool loadAppInfo(const Source& source, AppInfo* outInfo) {
1037 std::ifstream ifs(source.path, std::ifstream::in | std::ifstream::binary);
1038 if (!ifs) {
1039 Logger::error(source) << strerror(errno) << std::endl;
1040 return false;
1041 }
1042
1043 ManifestParser parser;
1044 std::shared_ptr<XmlPullParser> pullParser = std::make_shared<SourceXmlPullParser>(ifs);
1045 return parser.parse(source, pullParser, outInfo);
1046}
1047
1048static void printCommandsAndDie() {
1049 std::cerr << "The following commands are supported:" << std::endl << std::endl;
1050 std::cerr << "compile compiles a subset of resources" << std::endl;
1051 std::cerr << "link links together compiled resources and libraries" << std::endl;
Adam Lesinski330edcd2015-05-04 17:40:56 -07001052 std::cerr << "dump dumps resource contents to to standard out" << std::endl;
Adam Lesinskid5c4f872015-04-21 13:56:10 -07001053 std::cerr << std::endl;
1054 std::cerr << "run aapt2 with one of the commands and the -h flag for extra details."
1055 << std::endl;
1056 exit(1);
1057}
1058
1059static AaptOptions prepareArgs(int argc, char** argv) {
1060 if (argc < 2) {
1061 std::cerr << "no command specified." << std::endl << std::endl;
1062 printCommandsAndDie();
1063 }
1064
1065 const StringPiece command(argv[1]);
1066 argc -= 2;
1067 argv += 2;
1068
1069 AaptOptions options;
1070
1071 if (command == "--version" || command == "version") {
1072 std::cout << kAaptVersionStr << std::endl;
1073 exit(0);
1074 } else if (command == "link") {
1075 options.phase = AaptOptions::Phase::Link;
1076 } else if (command == "compile") {
1077 options.phase = AaptOptions::Phase::Compile;
Adam Lesinski330edcd2015-05-04 17:40:56 -07001078 } else if (command == "dump") {
1079 options.phase = AaptOptions::Phase::Dump;
1080 } else if (command == "dump-style-graph") {
1081 options.phase = AaptOptions::Phase::DumpStyleGraph;
Adam Lesinskid5c4f872015-04-21 13:56:10 -07001082 } else {
1083 std::cerr << "invalid command '" << command << "'." << std::endl << std::endl;
1084 printCommandsAndDie();
1085 }
1086
Adam Lesinski6d8e4c42015-05-01 14:47:28 -07001087 bool isStaticLib = false;
Adam Lesinski6cc479b2015-06-12 15:45:48 -07001088 if (options.phase == AaptOptions::Phase::Link) {
1089 flag::requiredFlag("--manifest", "AndroidManifest.xml of your app",
1090 [&options](const StringPiece& arg) {
1091 options.manifest = Source{ arg.toString() };
1092 });
1093
1094 flag::optionalFlag("-I", "add an Android APK to link against",
1095 [&options](const StringPiece& arg) {
1096 options.libraries.push_back(Source{ arg.toString() });
1097 });
1098
1099 flag::optionalFlag("--java", "directory in which to generate R.java",
1100 [&options](const StringPiece& arg) {
1101 options.generateJavaClass = Source{ arg.toString() };
1102 });
1103
1104 flag::optionalFlag("--proguard", "file in which to output proguard rules",
1105 [&options](const StringPiece& arg) {
1106 options.generateProguardRules = Source{ arg.toString() };
1107 });
1108
1109 flag::optionalSwitch("--static-lib", "generate a static Android library", true,
1110 &isStaticLib);
1111
1112 flag::optionalFlag("--binding", "Output directory for binding XML files",
1113 [&options](const StringPiece& arg) {
1114 options.bindingOutput = Source{ arg.toString() };
1115 });
1116 flag::optionalSwitch("--no-version", "Disables automatic style and layout versioning",
1117 false, &options.versionStylesAndLayouts);
1118 }
1119
Adam Lesinski330edcd2015-05-04 17:40:56 -07001120 if (options.phase == AaptOptions::Phase::Compile ||
1121 options.phase == AaptOptions::Phase::Link) {
Adam Lesinski330edcd2015-05-04 17:40:56 -07001122 // Common flags for all steps.
1123 flag::requiredFlag("-o", "Output path", [&options](const StringPiece& arg) {
1124 options.output = Source{ arg.toString() };
1125 });
Adam Lesinski6cc479b2015-06-12 15:45:48 -07001126 }
1127
1128 if (options.phase == AaptOptions::Phase::DumpStyleGraph) {
Adam Lesinskid13fb242015-05-12 20:40:48 -07001129 flag::requiredFlag("--style", "Name of the style to dump",
1130 [&options](const StringPiece& arg, std::string* outError) -> bool {
1131 Reference styleReference;
1132 if (!ResourceParser::parseStyleParentReference(util::utf8ToUtf16(arg),
1133 &styleReference, outError)) {
1134 return false;
1135 }
1136 options.dumpStyleTarget = styleReference.name;
1137 return true;
1138 });
Adam Lesinskid5c4f872015-04-21 13:56:10 -07001139 }
1140
Adam Lesinskid5c4f872015-04-21 13:56:10 -07001141 bool help = false;
1142 flag::optionalSwitch("-v", "enables verbose logging", true, &options.verbose);
1143 flag::optionalSwitch("-h", "displays this help menu", true, &help);
1144
1145 // Build the command string for output (eg. "aapt2 compile").
1146 std::string fullCommand = "aapt2";
1147 fullCommand += " ";
1148 fullCommand += command.toString();
1149
1150 // Actually read the command line flags.
1151 flag::parse(argc, argv, fullCommand);
1152
1153 if (help) {
1154 flag::usageAndDie(fullCommand);
1155 }
1156
Adam Lesinski6d8e4c42015-05-01 14:47:28 -07001157 if (isStaticLib) {
1158 options.packageType = AaptOptions::PackageType::StaticLibrary;
1159 }
1160
Adam Lesinskid5c4f872015-04-21 13:56:10 -07001161 // Copy all the remaining arguments.
1162 for (const std::string& arg : flag::getArgs()) {
1163 options.input.push_back(Source{ arg });
1164 }
1165 return options;
1166}
1167
Adam Lesinski330edcd2015-05-04 17:40:56 -07001168static bool doDump(const AaptOptions& options) {
1169 for (const Source& source : options.input) {
1170 std::unique_ptr<ZipFile> zipFile = util::make_unique<ZipFile>();
1171 if (zipFile->open(source.path.data(), ZipFile::kOpenReadOnly) != android::NO_ERROR) {
1172 Logger::error(source) << "failed to open: " << strerror(errno) << std::endl;
1173 return false;
1174 }
1175
1176 std::shared_ptr<ResourceTable> table = std::make_shared<ResourceTable>();
1177 std::shared_ptr<ResourceTableResolver> resolver =
1178 std::make_shared<ResourceTableResolver>(
1179 table, std::vector<std::shared_ptr<const android::AssetManager>>());
1180
1181 ZipEntry* entry = zipFile->getEntryByName("resources.arsc");
1182 if (!entry) {
1183 Logger::error(source) << "missing 'resources.arsc'." << std::endl;
1184 return false;
1185 }
1186
1187 std::unique_ptr<void, DeleteMalloc> uncompressedData = std::unique_ptr<void, DeleteMalloc>(
1188 zipFile->uncompress(entry));
1189 assert(uncompressedData);
1190
Adam Lesinski6cc479b2015-06-12 15:45:48 -07001191 BinaryResourceParser parser(table, resolver, source, {}, uncompressedData.get(),
Adam Lesinski330edcd2015-05-04 17:40:56 -07001192 entry->getUncompressedLen());
1193 if (!parser.parse()) {
1194 return false;
1195 }
1196
1197 if (options.phase == AaptOptions::Phase::Dump) {
1198 Debug::printTable(table);
1199 } else if (options.phase == AaptOptions::Phase::DumpStyleGraph) {
Adam Lesinskid13fb242015-05-12 20:40:48 -07001200 Debug::printStyleGraph(table, options.dumpStyleTarget);
Adam Lesinski330edcd2015-05-04 17:40:56 -07001201 }
1202 }
1203 return true;
1204}
1205
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001206int main(int argc, char** argv) {
1207 Logger::setLog(std::make_shared<Log>(std::cerr, std::cerr));
Adam Lesinski98aa3ad2015-04-06 11:46:52 -07001208 AaptOptions options = prepareArgs(argc, argv);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001209
Adam Lesinski330edcd2015-05-04 17:40:56 -07001210 if (options.phase == AaptOptions::Phase::Dump ||
1211 options.phase == AaptOptions::Phase::DumpStyleGraph) {
1212 if (!doDump(options)) {
1213 return 1;
1214 }
1215 return 0;
1216 }
1217
Adam Lesinski98aa3ad2015-04-06 11:46:52 -07001218 // If we specified a manifest, go ahead and load the package name from the manifest.
1219 if (!options.manifest.path.empty()) {
1220 if (!loadAppInfo(options.manifest, &options.appInfo)) {
1221 return false;
1222 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001223
Adam Lesinski6cc479b2015-06-12 15:45:48 -07001224 if (options.appInfo.package.empty()) {
1225 Logger::error() << "no package name specified." << std::endl;
1226 return false;
1227 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001228 }
1229
Adam Lesinski98aa3ad2015-04-06 11:46:52 -07001230 // Every phase needs a resource table.
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001231 std::shared_ptr<ResourceTable> table = std::make_shared<ResourceTable>();
Adam Lesinski6cc479b2015-06-12 15:45:48 -07001232
1233 // The package name is empty when in the compile phase.
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001234 table->setPackage(options.appInfo.package);
1235 if (options.appInfo.package == u"android") {
1236 table->setPackageId(0x01);
1237 } else {
1238 table->setPackageId(0x7f);
1239 }
1240
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001241 // Load the included libraries.
Adam Lesinski330edcd2015-05-04 17:40:56 -07001242 std::vector<std::shared_ptr<const android::AssetManager>> sources;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001243 for (const Source& source : options.libraries) {
Adam Lesinski330edcd2015-05-04 17:40:56 -07001244 std::shared_ptr<android::AssetManager> assetManager =
1245 std::make_shared<android::AssetManager>();
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001246 int32_t cookie;
Adam Lesinski330edcd2015-05-04 17:40:56 -07001247 if (!assetManager->addAssetPath(android::String8(source.path.data()), &cookie)) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001248 Logger::error(source) << "failed to load library." << std::endl;
1249 return false;
1250 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001251
Adam Lesinski330edcd2015-05-04 17:40:56 -07001252 if (cookie == 0) {
1253 Logger::error(source) << "failed to load library." << std::endl;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001254 return false;
1255 }
Adam Lesinski330edcd2015-05-04 17:40:56 -07001256 sources.push_back(assetManager);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001257 }
1258
1259 // Make the resolver that will cache IDs for us.
Adam Lesinski24aad162015-04-24 19:19:30 -07001260 std::shared_ptr<ResourceTableResolver> resolver = std::make_shared<ResourceTableResolver>(
Adam Lesinski330edcd2015-05-04 17:40:56 -07001261 table, sources);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001262
Adam Lesinski769de982015-04-10 19:43:55 -07001263 if (options.phase == AaptOptions::Phase::Compile) {
1264 if (!compile(options, table, resolver)) {
1265 Logger::error() << "aapt exiting with failures." << std::endl;
1266 return 1;
1267 }
1268 } else if (options.phase == AaptOptions::Phase::Link) {
1269 if (!link(options, table, resolver)) {
1270 Logger::error() << "aapt exiting with failures." << std::endl;
1271 return 1;
1272 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001273 }
1274 return 0;
1275}