blob: de2dafce33525cd5b5e524a42d112c5423c5f532 [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 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/**
Adam Lesinski8c831ca2015-05-20 15:24:01 -070060 * Used with smart pointers to free malloc'ed memory.
61 */
62struct DeleteMalloc {
63 void operator()(void* ptr) {
64 free(ptr);
65 }
66};
67
68struct StaticLibraryData {
69 Source source;
70 std::unique_ptr<ZipFile> apk;
71};
72
73/**
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080074 * Collect files from 'root', filtering out any files that do not
75 * match the FileFilter 'filter'.
76 */
Adam Lesinski98aa3ad2015-04-06 11:46:52 -070077bool walkTree(const Source& root, const FileFilter& filter,
78 std::vector<Source>* outEntries) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080079 bool error = false;
80
Adam Lesinski98aa3ad2015-04-06 11:46:52 -070081 for (const std::string& dirName : listFiles(root.path)) {
82 std::string dir = root.path;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080083 appendPath(&dir, dirName);
84
85 FileType ft = getFileType(dir);
86 if (!filter(dirName, ft)) {
87 continue;
88 }
89
90 if (ft != FileType::kDirectory) {
91 continue;
92 }
93
94 for (const std::string& fileName : listFiles(dir)) {
95 std::string file(dir);
96 appendPath(&file, fileName);
97
98 FileType ft = getFileType(file);
99 if (!filter(fileName, ft)) {
100 continue;
101 }
102
103 if (ft != FileType::kRegular) {
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700104 Logger::error(Source{ file }) << "not a regular file." << std::endl;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800105 error = true;
106 continue;
107 }
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700108 outEntries->push_back(Source{ file });
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800109 }
110 }
111 return !error;
112}
113
Adam Lesinski769de982015-04-10 19:43:55 -0700114void versionStylesForCompat(const std::shared_ptr<ResourceTable>& table) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800115 for (auto& type : *table) {
116 if (type->type != ResourceType::kStyle) {
117 continue;
118 }
119
120 for (auto& entry : type->entries) {
121 // Add the versioned styles we want to create
122 // here. They are added to the table after
123 // iterating over the original set of styles.
124 //
125 // A stack is used since auto-generated styles
126 // from later versions should override
127 // auto-generated styles from earlier versions.
128 // Iterating over the styles is done in order,
129 // so we will always visit sdkVersions from smallest
130 // to largest.
131 std::stack<ResourceConfigValue> addStack;
132
133 for (ResourceConfigValue& configValue : entry->values) {
134 visitFunc<Style>(*configValue.value, [&](Style& style) {
135 // Collect which entries we've stripped and the smallest
136 // SDK level which was stripped.
137 size_t minSdkStripped = std::numeric_limits<size_t>::max();
138 std::vector<Style::Entry> stripped;
139
140 // Iterate over the style's entries and erase/record the
141 // attributes whose SDK level exceeds the config's sdkVersion.
142 auto iter = style.entries.begin();
143 while (iter != style.entries.end()) {
144 if (iter->key.name.package == u"android") {
Adam Lesinski75f3a552015-06-03 14:54:23 -0700145 size_t sdkLevel = findAttributeSdkLevel(iter->key.name);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800146 if (sdkLevel > 1 && sdkLevel > configValue.config.sdkVersion) {
147 // Record that we are about to strip this.
148 stripped.emplace_back(std::move(*iter));
149 minSdkStripped = std::min(minSdkStripped, sdkLevel);
150
151 // Erase this from this style.
152 iter = style.entries.erase(iter);
153 continue;
154 }
155 }
156 ++iter;
157 }
158
159 if (!stripped.empty()) {
160 // We have stripped attributes, so let's create a new style to hold them.
161 ConfigDescription versionConfig(configValue.config);
162 versionConfig.sdkVersion = minSdkStripped;
163
164 ResourceConfigValue value = {
165 versionConfig,
166 configValue.source,
167 {},
168
169 // Create a copy of the original style.
Adam Lesinski769de982015-04-10 19:43:55 -0700170 std::unique_ptr<Value>(configValue.value->clone(
171 &table->getValueStringPool()))
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800172 };
173
174 Style& newStyle = static_cast<Style&>(*value.value);
175
176 // Move the recorded stripped attributes into this new style.
177 std::move(stripped.begin(), stripped.end(),
178 std::back_inserter(newStyle.entries));
179
180 // We will add this style to the table later. If we do it now, we will
181 // mess up iteration.
182 addStack.push(std::move(value));
183 }
184 });
185 }
186
187 auto comparator =
188 [](const ResourceConfigValue& lhs, const ConfigDescription& rhs) -> bool {
189 return lhs.config < rhs;
190 };
191
192 while (!addStack.empty()) {
193 ResourceConfigValue& value = addStack.top();
194 auto iter = std::lower_bound(entry->values.begin(), entry->values.end(),
195 value.config, comparator);
196 if (iter == entry->values.end() || iter->config != value.config) {
197 entry->values.insert(iter, std::move(value));
198 }
199 addStack.pop();
200 }
201 }
202 }
203}
204
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700205struct CompileItem {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800206 ResourceName name;
207 ConfigDescription config;
Adam Lesinski39c353a2015-05-14 17:58:14 -0700208 Source source;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700209 std::string extension;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800210};
211
Adam Lesinski769de982015-04-10 19:43:55 -0700212struct LinkItem {
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700213 ResourceName name;
214 ConfigDescription config;
Adam Lesinski39c353a2015-05-14 17:58:14 -0700215 Source source;
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700216 std::string originalPath;
217 ZipFile* apk;
Adam Lesinski24aad162015-04-24 19:19:30 -0700218 std::u16string originalPackage;
Adam Lesinski769de982015-04-10 19:43:55 -0700219};
220
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700221template <typename TChar>
222static BasicStringPiece<TChar> getExtension(const BasicStringPiece<TChar>& str) {
223 auto iter = std::find(str.begin(), str.end(), static_cast<TChar>('.'));
224 if (iter == str.end()) {
225 return BasicStringPiece<TChar>();
Adam Lesinski769de982015-04-10 19:43:55 -0700226 }
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700227 size_t offset = (iter - str.begin()) + 1;
228 return str.substr(offset, str.size() - offset);
229}
230
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700231std::string buildFileReference(const ResourceNameRef& name, const ConfigDescription& config,
232 const StringPiece& extension) {
233 std::stringstream path;
234 path << "res/" << name.type;
235 if (config != ConfigDescription{}) {
236 path << "-" << config;
237 }
238 path << "/" << util::utf16ToUtf8(name.entry);
239 if (!extension.empty()) {
240 path << "." << extension;
241 }
Adam Lesinski769de982015-04-10 19:43:55 -0700242 return path.str();
243}
244
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700245std::string buildFileReference(const CompileItem& item) {
246 return buildFileReference(item.name, item.config, item.extension);
247}
248
249std::string buildFileReference(const LinkItem& item) {
250 return buildFileReference(item.name, item.config, getExtension<char>(item.originalPath));
251}
252
Adam Lesinski39c353a2015-05-14 17:58:14 -0700253template <typename T>
254bool addFileReference(const std::shared_ptr<ResourceTable>& table, const T& item) {
Adam Lesinski769de982015-04-10 19:43:55 -0700255 StringPool& pool = table->getValueStringPool();
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700256 StringPool::Ref ref = pool.makeRef(util::utf8ToUtf16(buildFileReference(item)),
257 StringPool::Context{ 0, item.config });
Adam Lesinski769de982015-04-10 19:43:55 -0700258 return table->addResource(item.name, item.config, item.source.line(0),
259 util::make_unique<FileReference>(ref));
260}
261
262struct AaptOptions {
263 enum class Phase {
264 Link,
265 Compile,
Adam Lesinski330edcd2015-05-04 17:40:56 -0700266 Dump,
267 DumpStyleGraph,
Adam Lesinski769de982015-04-10 19:43:55 -0700268 };
269
Adam Lesinski6d8e4c42015-05-01 14:47:28 -0700270 enum class PackageType {
271 StandardApp,
272 StaticLibrary,
273 };
274
Adam Lesinski769de982015-04-10 19:43:55 -0700275 // The phase to process.
276 Phase phase;
277
Adam Lesinski6d8e4c42015-05-01 14:47:28 -0700278 // The type of package to produce.
279 PackageType packageType = PackageType::StandardApp;
280
Adam Lesinski769de982015-04-10 19:43:55 -0700281 // Details about the app.
282 AppInfo appInfo;
283
284 // The location of the manifest file.
285 Source manifest;
286
287 // The APK files to link.
288 std::vector<Source> input;
289
290 // The libraries these files may reference.
291 std::vector<Source> libraries;
292
293 // Output path. This can be a directory or file
294 // depending on the phase.
295 Source output;
296
297 // Directory in which to write binding xml files.
298 Source bindingOutput;
299
300 // Directory to in which to generate R.java.
301 Maybe<Source> generateJavaClass;
302
303 // Whether to output verbose details about
304 // compilation.
305 bool verbose = false;
Adam Lesinski5886a922015-04-15 20:29:22 -0700306
307 // Whether or not to auto-version styles or layouts
308 // referencing attributes defined in a newer SDK
309 // level than the style or layout is defined for.
310 bool versionStylesAndLayouts = true;
Adam Lesinskid13fb242015-05-12 20:40:48 -0700311
312 // The target style that will have it's style hierarchy dumped
313 // when the phase is DumpStyleGraph.
314 ResourceName dumpStyleTarget;
Adam Lesinski769de982015-04-10 19:43:55 -0700315};
316
Adam Lesinski75f3a552015-06-03 14:54:23 -0700317struct IdCollector : public xml::Visitor {
318 IdCollector(const Source& source, const std::shared_ptr<ResourceTable>& table) :
319 mSource(source), mTable(table) {
320 }
321
322 virtual void visit(xml::Text* node) override {}
323
324 virtual void visit(xml::Namespace* node) override {
325 for (const auto& child : node->children) {
326 child->accept(this);
327 }
328 }
329
330 virtual void visit(xml::Element* node) override {
331 for (const xml::Attribute& attr : node->attributes) {
332 bool create = false;
333 bool priv = false;
334 ResourceNameRef nameRef;
335 if (ResourceParser::tryParseReference(attr.value, &nameRef, &create, &priv)) {
336 if (create) {
337 mTable->addResource(nameRef, {}, mSource.line(node->lineNumber),
338 util::make_unique<Id>());
339 }
340 }
341 }
342
343 for (const auto& child : node->children) {
344 child->accept(this);
345 }
346 }
347
348private:
349 Source mSource;
350 std::shared_ptr<ResourceTable> mTable;
351};
352
Adam Lesinski769de982015-04-10 19:43:55 -0700353bool compileXml(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table,
Adam Lesinski330edcd2015-05-04 17:40:56 -0700354 const CompileItem& item, ZipFile* outApk) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800355 std::ifstream in(item.source.path, std::ifstream::binary);
356 if (!in) {
357 Logger::error(item.source) << strerror(errno) << std::endl;
358 return false;
359 }
360
Adam Lesinski75f3a552015-06-03 14:54:23 -0700361 SourceLogger logger(item.source);
362 std::unique_ptr<xml::Node> root = xml::inflate(&in, &logger);
363 if (!root) {
364 return false;
365 }
366
367 // Collect any resource ID's declared here.
368 IdCollector idCollector(item.source, table);
369 root->accept(&idCollector);
370
Adam Lesinski4d3a9872015-04-09 19:53:22 -0700371 BigBuffer outBuffer(1024);
Adam Lesinski75f3a552015-06-03 14:54:23 -0700372 if (!xml::flatten(root.get(), options.appInfo.package, &outBuffer)) {
373 logger.error() << "failed to encode XML." << std::endl;
Adam Lesinski330edcd2015-05-04 17:40:56 -0700374 return false;
375 }
376
377 // Write the resulting compiled XML file to the output APK.
378 if (outApk->add(outBuffer, buildFileReference(item).data(), ZipEntry::kCompressStored,
379 nullptr) != android::NO_ERROR) {
380 Logger::error(options.output) << "failed to write compiled '" << item.source
381 << "' to apk." << std::endl;
382 return false;
383 }
384 return true;
385}
386
Adam Lesinski39c353a2015-05-14 17:58:14 -0700387/**
388 * Determines if a layout should be auto generated based on SDK level. We do not
389 * generate a layout if there is already a layout defined whose SDK version is greater than
390 * the one we want to generate.
391 */
392bool shouldGenerateVersionedResource(const std::shared_ptr<const ResourceTable>& table,
393 const ResourceName& name, const ConfigDescription& config,
394 int sdkVersionToGenerate) {
395 assert(sdkVersionToGenerate > config.sdkVersion);
396 const ResourceTableType* type;
397 const ResourceEntry* entry;
398 std::tie(type, entry) = table->findResource(name);
399 assert(type && entry);
400
401 auto iter = std::lower_bound(entry->values.begin(), entry->values.end(), config,
402 [](const ResourceConfigValue& lhs, const ConfigDescription& config) -> bool {
403 return lhs.config < config;
404 });
405
406 assert(iter != entry->values.end());
407 ++iter;
408
409 if (iter == entry->values.end()) {
410 return true;
411 }
412
413 ConfigDescription newConfig = config;
414 newConfig.sdkVersion = sdkVersionToGenerate;
415 return newConfig < iter->config;
416}
417
418bool linkXml(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table,
419 const std::shared_ptr<IResolver>& resolver, const LinkItem& item,
420 const void* data, size_t dataLen, ZipFile* outApk, std::queue<LinkItem>* outQueue) {
Adam Lesinski75f3a552015-06-03 14:54:23 -0700421 SourceLogger logger(item.source);
422 std::unique_ptr<xml::Node> root = xml::inflate(data, dataLen, &logger);
423 if (!root) {
Adam Lesinski330edcd2015-05-04 17:40:56 -0700424 return false;
425 }
426
Adam Lesinski75f3a552015-06-03 14:54:23 -0700427 xml::FlattenOptions xmlOptions;
Adam Lesinski330edcd2015-05-04 17:40:56 -0700428 if (options.packageType == AaptOptions::PackageType::StaticLibrary) {
429 xmlOptions.keepRawValues = true;
430 }
Adam Lesinski24aad162015-04-24 19:19:30 -0700431
Adam Lesinski5886a922015-04-15 20:29:22 -0700432 if (options.versionStylesAndLayouts) {
433 // We strip attributes that do not belong in this version of the resource.
434 // Non-version qualified resources have an implicit version 1 requirement.
435 xmlOptions.maxSdkAttribute = item.config.sdkVersion ? item.config.sdkVersion : 1;
436 }
Adam Lesinski769de982015-04-10 19:43:55 -0700437
Adam Lesinski75f3a552015-06-03 14:54:23 -0700438 BigBuffer outBuffer(1024);
439 Maybe<size_t> minStrippedSdk = xml::flattenAndLink(item.source, root.get(),
440 item.originalPackage, resolver,
441 xmlOptions, &outBuffer);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800442 if (!minStrippedSdk) {
Adam Lesinski75f3a552015-06-03 14:54:23 -0700443 logger.error() << "failed to encode XML." << std::endl;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800444 return false;
445 }
446
447 if (minStrippedSdk.value() > 0) {
448 // Something was stripped, so let's generate a new file
449 // with the version of the smallest SDK version stripped.
Adam Lesinski39c353a2015-05-14 17:58:14 -0700450 // We can only generate a versioned layout if there doesn't exist a layout
451 // with sdk version greater than the current one but less than the one we
452 // want to generate.
453 if (shouldGenerateVersionedResource(table, item.name, item.config,
454 minStrippedSdk.value())) {
455 LinkItem newWork = item;
456 newWork.config.sdkVersion = minStrippedSdk.value();
457 outQueue->push(newWork);
458
459 if (!addFileReference(table, newWork)) {
460 Logger::error(options.output) << "failed to add auto-versioned resource '"
461 << newWork.name << "'." << std::endl;
462 return false;
463 }
464 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800465 }
466
Adam Lesinski330edcd2015-05-04 17:40:56 -0700467 if (outApk->add(outBuffer, buildFileReference(item).data(), ZipEntry::kCompressDeflated,
Adam Lesinski769de982015-04-10 19:43:55 -0700468 nullptr) != android::NO_ERROR) {
Adam Lesinski330edcd2015-05-04 17:40:56 -0700469 Logger::error(options.output) << "failed to write linked file '"
470 << buildFileReference(item) << "' to apk." << std::endl;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800471 return false;
472 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800473 return true;
474}
475
Adam Lesinski769de982015-04-10 19:43:55 -0700476bool compilePng(const AaptOptions& options, const CompileItem& item, ZipFile* outApk) {
477 std::ifstream in(item.source.path, std::ifstream::binary);
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700478 if (!in) {
Adam Lesinski769de982015-04-10 19:43:55 -0700479 Logger::error(item.source) << strerror(errno) << std::endl;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700480 return false;
481 }
482
Adam Lesinski769de982015-04-10 19:43:55 -0700483 BigBuffer outBuffer(4096);
484 std::string err;
485 Png png;
486 if (!png.process(item.source, in, &outBuffer, {}, &err)) {
487 Logger::error(item.source) << err << std::endl;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700488 return false;
489 }
490
Adam Lesinski769de982015-04-10 19:43:55 -0700491 if (outApk->add(outBuffer, buildFileReference(item).data(), ZipEntry::kCompressStored,
492 nullptr) != android::NO_ERROR) {
493 Logger::error(options.output) << "failed to write compiled '" << item.source
494 << "' to apk." << std::endl;
495 return false;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700496 }
Adam Lesinski769de982015-04-10 19:43:55 -0700497 return true;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700498}
499
Adam Lesinski769de982015-04-10 19:43:55 -0700500bool copyFile(const AaptOptions& options, const CompileItem& item, ZipFile* outApk) {
501 if (outApk->add(item.source.path.data(), buildFileReference(item).data(),
502 ZipEntry::kCompressStored, nullptr) != android::NO_ERROR) {
503 Logger::error(options.output) << "failed to copy file '" << item.source << "' to apk."
504 << std::endl;
505 return false;
506 }
507 return true;
508}
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800509
Adam Lesinski330edcd2015-05-04 17:40:56 -0700510bool compileManifest(const AaptOptions& options, const std::shared_ptr<IResolver>& resolver,
Adam Lesinski8c831ca2015-05-20 15:24:01 -0700511 const std::map<std::shared_ptr<ResourceTable>, StaticLibraryData>& libApks,
Adam Lesinski330edcd2015-05-04 17:40:56 -0700512 const android::ResTable& table, ZipFile* outApk) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800513 if (options.verbose) {
Adam Lesinski769de982015-04-10 19:43:55 -0700514 Logger::note(options.manifest) << "compiling AndroidManifest.xml." << std::endl;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800515 }
516
517 std::ifstream in(options.manifest.path, std::ifstream::binary);
518 if (!in) {
519 Logger::error(options.manifest) << strerror(errno) << std::endl;
520 return false;
521 }
522
Adam Lesinski75f3a552015-06-03 14:54:23 -0700523 SourceLogger logger(options.manifest);
524 std::unique_ptr<xml::Node> root = xml::inflate(&in, &logger);
525 if (!root) {
526 return false;
527 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800528
Adam Lesinski8c831ca2015-05-20 15:24:01 -0700529 ManifestMerger merger({});
530 if (!merger.setAppManifest(options.manifest, options.appInfo.package, std::move(root))) {
531 return false;
532 }
533
534 for (const auto& entry : libApks) {
535 ZipFile* libApk = entry.second.apk.get();
536 const std::u16string& libPackage = entry.first->getPackage();
537 const Source& libSource = entry.second.source;
538
539 ZipEntry* zipEntry = libApk->getEntryByName("AndroidManifest.xml");
540 if (!zipEntry) {
541 continue;
542 }
543
544 std::unique_ptr<void, DeleteMalloc> uncompressedData = std::unique_ptr<void, DeleteMalloc>(
545 libApk->uncompress(zipEntry));
546 assert(uncompressedData);
547
548 SourceLogger logger(libSource);
549 std::unique_ptr<xml::Node> libRoot = xml::inflate(uncompressedData.get(),
550 zipEntry->getUncompressedLen(), &logger);
551 if (!libRoot) {
552 return false;
553 }
554
555 if (!merger.mergeLibraryManifest(libSource, libPackage, std::move(libRoot))) {
556 return false;
557 }
558 }
559
Adam Lesinski75f3a552015-06-03 14:54:23 -0700560 BigBuffer outBuffer(1024);
Adam Lesinski8c831ca2015-05-20 15:24:01 -0700561 if (!xml::flattenAndLink(options.manifest, merger.getMergedXml(), options.appInfo.package,
562 resolver, {}, &outBuffer)) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800563 return false;
564 }
565
Adam Lesinski769de982015-04-10 19:43:55 -0700566 std::unique_ptr<uint8_t[]> data = util::copy(outBuffer);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800567
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700568 android::ResXMLTree tree;
Adam Lesinski769de982015-04-10 19:43:55 -0700569 if (tree.setTo(data.get(), outBuffer.size(), false) != android::NO_ERROR) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800570 return false;
571 }
572
Adam Lesinski330edcd2015-05-04 17:40:56 -0700573 ManifestValidator validator(table);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800574 if (!validator.validate(options.manifest, &tree)) {
575 return false;
576 }
577
Adam Lesinski769de982015-04-10 19:43:55 -0700578 if (outApk->add(data.get(), outBuffer.size(), "AndroidManifest.xml",
579 ZipEntry::kCompressStored, nullptr) != android::NO_ERROR) {
580 Logger::error(options.output) << "failed to write 'AndroidManifest.xml' to apk."
581 << std::endl;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800582 return false;
583 }
584 return true;
585}
586
Adam Lesinski769de982015-04-10 19:43:55 -0700587static bool compileValues(const std::shared_ptr<ResourceTable>& table, const Source& source,
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700588 const ConfigDescription& config) {
589 std::ifstream in(source.path, std::ifstream::binary);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800590 if (!in) {
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700591 Logger::error(source) << strerror(errno) << std::endl;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800592 return false;
593 }
594
595 std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<SourceXmlPullParser>(in);
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700596 ResourceParser parser(table, source, config, xmlParser);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800597 return parser.parse();
598}
599
600struct ResourcePathData {
601 std::u16string resourceDir;
602 std::u16string name;
603 std::string extension;
604 ConfigDescription config;
605};
606
607/**
608 * Resource file paths are expected to look like:
609 * [--/res/]type[-config]/name
610 */
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700611static Maybe<ResourcePathData> extractResourcePathData(const Source& source) {
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700612 // TODO(adamlesinski): Use Windows path separator on windows.
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800613 std::vector<std::string> parts = util::splitAndLowercase(source.path, '/');
614 if (parts.size() < 2) {
615 Logger::error(source) << "bad resource path." << std::endl;
616 return {};
617 }
618
619 std::string& dir = parts[parts.size() - 2];
620 StringPiece dirStr = dir;
621
622 ConfigDescription config;
623 size_t dashPos = dir.find('-');
624 if (dashPos != std::string::npos) {
625 StringPiece configStr = dirStr.substr(dashPos + 1, dir.size() - (dashPos + 1));
626 if (!ConfigDescription::parse(configStr, &config)) {
627 Logger::error(source)
628 << "invalid configuration '"
629 << configStr
630 << "'."
631 << std::endl;
632 return {};
633 }
634 dirStr = dirStr.substr(0, dashPos);
635 }
636
637 std::string& filename = parts[parts.size() - 1];
638 StringPiece name = filename;
639 StringPiece extension;
640 size_t dotPos = filename.find('.');
641 if (dotPos != std::string::npos) {
642 extension = name.substr(dotPos + 1, filename.size() - (dotPos + 1));
643 name = name.substr(0, dotPos);
644 }
645
646 return ResourcePathData{
647 util::utf8ToUtf16(dirStr),
648 util::utf8ToUtf16(name),
649 extension.toString(),
650 config
651 };
652}
653
Adam Lesinski769de982015-04-10 19:43:55 -0700654bool writeResourceTable(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table,
655 const TableFlattener::Options& flattenerOptions, ZipFile* outApk) {
656 if (table->begin() != table->end()) {
657 BigBuffer buffer(1024);
658 TableFlattener flattener(flattenerOptions);
659 if (!flattener.flatten(&buffer, *table)) {
660 Logger::error() << "failed to flatten resource table." << std::endl;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700661 return false;
662 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800663
Adam Lesinski769de982015-04-10 19:43:55 -0700664 if (options.verbose) {
665 Logger::note() << "Final resource table size=" << util::formatSize(buffer.size())
666 << std::endl;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700667 }
668
Adam Lesinski769de982015-04-10 19:43:55 -0700669 if (outApk->add(buffer, "resources.arsc", ZipEntry::kCompressStored, nullptr) !=
670 android::NO_ERROR) {
671 Logger::note(options.output) << "failed to store resource table." << std::endl;
672 return false;
673 }
674 }
675 return true;
676}
677
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700678/**
679 * For each FileReference in the table, adds a LinkItem to the link queue for processing.
680 */
681static void addApkFilesToLinkQueue(const std::u16string& package, const Source& source,
682 const std::shared_ptr<ResourceTable>& table,
683 const std::unique_ptr<ZipFile>& apk,
684 std::queue<LinkItem>* outLinkQueue) {
685 bool mangle = package != table->getPackage();
686 for (auto& type : *table) {
687 for (auto& entry : type->entries) {
688 ResourceName name = { package, type->type, entry->name };
689 if (mangle) {
690 NameMangler::mangle(table->getPackage(), &name.entry);
691 }
692
693 for (auto& value : entry->values) {
694 visitFunc<FileReference>(*value.value, [&](FileReference& ref) {
695 std::string pathUtf8 = util::utf16ToUtf8(*ref.path);
Adam Lesinski24aad162015-04-24 19:19:30 -0700696 Source newSource = source;
697 newSource.path += "/";
698 newSource.path += pathUtf8;
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700699 outLinkQueue->push(LinkItem{
Adam Lesinski39c353a2015-05-14 17:58:14 -0700700 name, value.config, newSource, pathUtf8, apk.get(),
Adam Lesinski24aad162015-04-24 19:19:30 -0700701 table->getPackage() });
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700702 // Now rewrite the file path.
703 if (mangle) {
704 ref.path = table->getValueStringPool().makeRef(util::utf8ToUtf16(
705 buildFileReference(name, value.config,
706 getExtension<char>(pathUtf8))));
707 }
708 });
709 }
710 }
711 }
712}
713
Adam Lesinski769de982015-04-10 19:43:55 -0700714static constexpr int kOpenFlags = ZipFile::kOpenCreate | ZipFile::kOpenTruncate |
715 ZipFile::kOpenReadWrite;
716
717bool link(const AaptOptions& options, const std::shared_ptr<ResourceTable>& outTable,
Adam Lesinski330edcd2015-05-04 17:40:56 -0700718 const std::shared_ptr<IResolver>& resolver) {
Adam Lesinski24aad162015-04-24 19:19:30 -0700719 std::map<std::shared_ptr<ResourceTable>, StaticLibraryData> apkFiles;
Adam Lesinski769de982015-04-10 19:43:55 -0700720 std::unordered_set<std::u16string> linkedPackages;
721
722 // Populate the linkedPackages with our own.
723 linkedPackages.insert(options.appInfo.package);
724
725 // Load all APK files.
726 for (const Source& source : options.input) {
727 std::unique_ptr<ZipFile> zipFile = util::make_unique<ZipFile>();
728 if (zipFile->open(source.path.data(), ZipFile::kOpenReadOnly) != android::NO_ERROR) {
729 Logger::error(source) << "failed to open: " << strerror(errno) << std::endl;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700730 return false;
731 }
732
Adam Lesinski769de982015-04-10 19:43:55 -0700733 std::shared_ptr<ResourceTable> table = std::make_shared<ResourceTable>();
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700734
Adam Lesinski769de982015-04-10 19:43:55 -0700735 ZipEntry* entry = zipFile->getEntryByName("resources.arsc");
736 if (!entry) {
737 Logger::error(source) << "missing 'resources.arsc'." << std::endl;
738 return false;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700739 }
740
Adam Lesinski24aad162015-04-24 19:19:30 -0700741 std::unique_ptr<void, DeleteMalloc> uncompressedData = std::unique_ptr<void, DeleteMalloc>(
742 zipFile->uncompress(entry));
Adam Lesinski769de982015-04-10 19:43:55 -0700743 assert(uncompressedData);
744
Adam Lesinski24aad162015-04-24 19:19:30 -0700745 BinaryResourceParser parser(table, resolver, source, uncompressedData.get(),
Adam Lesinski769de982015-04-10 19:43:55 -0700746 entry->getUncompressedLen());
747 if (!parser.parse()) {
Adam Lesinski769de982015-04-10 19:43:55 -0700748 return false;
749 }
Adam Lesinski769de982015-04-10 19:43:55 -0700750
751 // Keep track of where this table came from.
Adam Lesinski24aad162015-04-24 19:19:30 -0700752 apkFiles[table] = StaticLibraryData{ source, std::move(zipFile) };
Adam Lesinski769de982015-04-10 19:43:55 -0700753
754 // Add the package to the set of linked packages.
755 linkedPackages.insert(table->getPackage());
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700756 }
757
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700758 std::queue<LinkItem> linkQueue;
Adam Lesinski769de982015-04-10 19:43:55 -0700759 for (auto& p : apkFiles) {
760 const std::shared_ptr<ResourceTable>& inTable = p.first;
761
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700762 // Collect all FileReferences and add them to the queue for processing.
Adam Lesinski24aad162015-04-24 19:19:30 -0700763 addApkFilesToLinkQueue(options.appInfo.package, p.second.source, inTable, p.second.apk,
764 &linkQueue);
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700765
766 // Merge the tables.
Adam Lesinski769de982015-04-10 19:43:55 -0700767 if (!outTable->merge(std::move(*inTable))) {
768 return false;
769 }
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700770 }
771
Adam Lesinski330edcd2015-05-04 17:40:56 -0700772 // Version all styles referencing attributes outside of their specified SDK version.
773 if (options.versionStylesAndLayouts) {
774 versionStylesForCompat(outTable);
775 }
776
Adam Lesinski769de982015-04-10 19:43:55 -0700777 {
778 // Now that everything is merged, let's link it.
Adam Lesinski330edcd2015-05-04 17:40:56 -0700779 Linker::Options linkerOptions;
780 if (options.packageType == AaptOptions::PackageType::StaticLibrary) {
781 linkerOptions.linkResourceIds = false;
782 }
783 Linker linker(outTable, resolver, linkerOptions);
Adam Lesinski769de982015-04-10 19:43:55 -0700784 if (!linker.linkAndValidate()) {
785 return false;
786 }
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700787
Adam Lesinski769de982015-04-10 19:43:55 -0700788 // Verify that all symbols exist.
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700789 const auto& unresolvedRefs = linker.getUnresolvedReferences();
790 if (!unresolvedRefs.empty()) {
791 for (const auto& entry : unresolvedRefs) {
792 for (const auto& source : entry.second) {
793 Logger::error(source) << "unresolved symbol '" << entry.first << "'."
794 << std::endl;
795 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800796 }
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700797 return false;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800798 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800799 }
800
Adam Lesinski769de982015-04-10 19:43:55 -0700801 // Open the output APK file for writing.
802 ZipFile outApk;
803 if (outApk.open(options.output.path.data(), kOpenFlags) != android::NO_ERROR) {
804 Logger::error(options.output) << "failed to open: " << strerror(errno) << std::endl;
805 return false;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700806 }
807
Adam Lesinski330edcd2015-05-04 17:40:56 -0700808 android::ResTable binTable;
Adam Lesinski8c831ca2015-05-20 15:24:01 -0700809 if (!compileManifest(options, resolver, apkFiles, binTable, &outApk)) {
Adam Lesinski769de982015-04-10 19:43:55 -0700810 return false;
811 }
812
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700813 for (; !linkQueue.empty(); linkQueue.pop()) {
814 const LinkItem& item = linkQueue.front();
Adam Lesinski769de982015-04-10 19:43:55 -0700815
Adam Lesinski24aad162015-04-24 19:19:30 -0700816 assert(!item.originalPackage.empty());
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700817 ZipEntry* entry = item.apk->getEntryByName(item.originalPath.data());
818 if (!entry) {
819 Logger::error(item.source) << "failed to find '" << item.originalPath << "'."
820 << std::endl;
821 return false;
822 }
Adam Lesinski769de982015-04-10 19:43:55 -0700823
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700824 if (util::stringEndsWith<char>(item.originalPath, ".xml")) {
825 void* uncompressedData = item.apk->uncompress(entry);
826 assert(uncompressedData);
Adam Lesinski769de982015-04-10 19:43:55 -0700827
Adam Lesinski39c353a2015-05-14 17:58:14 -0700828 if (!linkXml(options, outTable, resolver, item, uncompressedData,
829 entry->getUncompressedLen(), &outApk, &linkQueue)) {
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700830 Logger::error(options.output) << "failed to link '" << item.originalPath << "'."
831 << std::endl;
832 return false;
Adam Lesinski769de982015-04-10 19:43:55 -0700833 }
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700834 } else {
835 if (outApk.add(item.apk, entry, buildFileReference(item).data(), 0, nullptr) !=
836 android::NO_ERROR) {
837 Logger::error(options.output) << "failed to copy '" << item.originalPath << "'."
838 << std::endl;
839 return false;
Adam Lesinski769de982015-04-10 19:43:55 -0700840 }
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700841 }
842 }
843
844 // Generate the Java class file.
Adam Lesinski769de982015-04-10 19:43:55 -0700845 if (options.generateJavaClass) {
Adam Lesinski6d8e4c42015-05-01 14:47:28 -0700846 JavaClassGenerator::Options javaOptions;
847 if (options.packageType == AaptOptions::PackageType::StaticLibrary) {
848 javaOptions.useFinal = false;
849 }
850 JavaClassGenerator generator(outTable, javaOptions);
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700851
Adam Lesinski769de982015-04-10 19:43:55 -0700852 for (const std::u16string& package : linkedPackages) {
853 Source outPath = options.generateJavaClass.value();
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800854
Adam Lesinski769de982015-04-10 19:43:55 -0700855 // Build the output directory from the package name.
856 // Eg. com.android.app -> com/android/app
857 const std::string packageUtf8 = util::utf16ToUtf8(package);
858 for (StringPiece part : util::tokenize<char>(packageUtf8, '.')) {
859 appendPath(&outPath.path, part);
860 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800861
Adam Lesinski769de982015-04-10 19:43:55 -0700862 if (!mkdirs(outPath.path)) {
863 Logger::error(outPath) << strerror(errno) << std::endl;
864 return false;
865 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800866
Adam Lesinski769de982015-04-10 19:43:55 -0700867 appendPath(&outPath.path, "R.java");
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800868
Adam Lesinski769de982015-04-10 19:43:55 -0700869 if (options.verbose) {
870 Logger::note(outPath) << "writing Java symbols." << std::endl;
871 }
872
873 std::ofstream fout(outPath.path);
874 if (!fout) {
875 Logger::error(outPath) << strerror(errno) << std::endl;
876 return false;
877 }
878
879 if (!generator.generate(package, fout)) {
880 Logger::error(outPath) << generator.getError() << "." << std::endl;
881 return false;
882 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800883 }
884 }
885
Adam Lesinski24aad162015-04-24 19:19:30 -0700886 outTable->getValueStringPool().prune();
887 outTable->getValueStringPool().sort(
888 [](const StringPool::Entry& a, const StringPool::Entry& b) -> bool {
889 if (a.context.priority < b.context.priority) {
890 return true;
891 }
892
893 if (a.context.priority > b.context.priority) {
894 return false;
895 }
896 return a.value < b.value;
897 });
898
899
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700900 // Flatten the resource table.
Adam Lesinski769de982015-04-10 19:43:55 -0700901 TableFlattener::Options flattenerOptions;
Adam Lesinski330edcd2015-05-04 17:40:56 -0700902 if (options.packageType != AaptOptions::PackageType::StaticLibrary) {
903 flattenerOptions.useExtendedChunks = false;
Adam Lesinski6d8e4c42015-05-01 14:47:28 -0700904 }
905
Adam Lesinski769de982015-04-10 19:43:55 -0700906 if (!writeResourceTable(options, outTable, flattenerOptions, &outApk)) {
907 return false;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800908 }
Adam Lesinski769de982015-04-10 19:43:55 -0700909
910 outApk.flush();
911 return true;
912}
913
914bool compile(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table,
Adam Lesinski24aad162015-04-24 19:19:30 -0700915 const std::shared_ptr<IResolver>& resolver) {
Adam Lesinski769de982015-04-10 19:43:55 -0700916 std::queue<CompileItem> compileQueue;
917 bool error = false;
918
919 // Compile all the resource files passed in on the command line.
920 for (const Source& source : options.input) {
921 // Need to parse the resource type/config/filename.
922 Maybe<ResourcePathData> maybePathData = extractResourcePathData(source);
923 if (!maybePathData) {
924 return false;
925 }
926
927 const ResourcePathData& pathData = maybePathData.value();
928 if (pathData.resourceDir == u"values") {
929 // The file is in the values directory, which means its contents will
930 // go into the resource table.
931 if (options.verbose) {
932 Logger::note(source) << "compiling values." << std::endl;
933 }
934
935 error |= !compileValues(table, source, pathData.config);
936 } else {
937 // The file is in a directory like 'layout' or 'drawable'. Find out
938 // the type.
939 const ResourceType* type = parseResourceType(pathData.resourceDir);
940 if (!type) {
941 Logger::error(source) << "invalid resource type '" << pathData.resourceDir << "'."
942 << std::endl;
943 return false;
944 }
945
946 compileQueue.push(CompileItem{
Adam Lesinski769de982015-04-10 19:43:55 -0700947 ResourceName{ table->getPackage(), *type, pathData.name },
948 pathData.config,
Adam Lesinski39c353a2015-05-14 17:58:14 -0700949 source,
Adam Lesinski769de982015-04-10 19:43:55 -0700950 pathData.extension
951 });
952 }
953 }
954
955 if (error) {
956 return false;
957 }
Adam Lesinski769de982015-04-10 19:43:55 -0700958 // Open the output APK file for writing.
959 ZipFile outApk;
960 if (outApk.open(options.output.path.data(), kOpenFlags) != android::NO_ERROR) {
961 Logger::error(options.output) << "failed to open: " << strerror(errno) << std::endl;
962 return false;
963 }
964
965 // Compile each file.
966 for (; !compileQueue.empty(); compileQueue.pop()) {
967 const CompileItem& item = compileQueue.front();
968
969 // Add the file name to the resource table.
970 error |= !addFileReference(table, item);
971
972 if (item.extension == "xml") {
Adam Lesinski330edcd2015-05-04 17:40:56 -0700973 error |= !compileXml(options, table, item, &outApk);
Adam Lesinski769de982015-04-10 19:43:55 -0700974 } else if (item.extension == "png" || item.extension == "9.png") {
975 error |= !compilePng(options, item, &outApk);
976 } else {
977 error |= !copyFile(options, item, &outApk);
978 }
979 }
980
981 if (error) {
982 return false;
983 }
984
985 // Link and assign resource IDs.
Adam Lesinski330edcd2015-05-04 17:40:56 -0700986 Linker linker(table, resolver, {});
Adam Lesinski769de982015-04-10 19:43:55 -0700987 if (!linker.linkAndValidate()) {
988 return false;
989 }
990
991 // Flatten the resource table.
992 if (!writeResourceTable(options, table, {}, &outApk)) {
993 return false;
994 }
995
996 outApk.flush();
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800997 return true;
998}
999
Adam Lesinskid5c4f872015-04-21 13:56:10 -07001000bool loadAppInfo(const Source& source, AppInfo* outInfo) {
1001 std::ifstream ifs(source.path, std::ifstream::in | std::ifstream::binary);
1002 if (!ifs) {
1003 Logger::error(source) << strerror(errno) << std::endl;
1004 return false;
1005 }
1006
1007 ManifestParser parser;
1008 std::shared_ptr<XmlPullParser> pullParser = std::make_shared<SourceXmlPullParser>(ifs);
1009 return parser.parse(source, pullParser, outInfo);
1010}
1011
1012static void printCommandsAndDie() {
1013 std::cerr << "The following commands are supported:" << std::endl << std::endl;
1014 std::cerr << "compile compiles a subset of resources" << std::endl;
1015 std::cerr << "link links together compiled resources and libraries" << std::endl;
Adam Lesinski330edcd2015-05-04 17:40:56 -07001016 std::cerr << "dump dumps resource contents to to standard out" << std::endl;
Adam Lesinskid5c4f872015-04-21 13:56:10 -07001017 std::cerr << std::endl;
1018 std::cerr << "run aapt2 with one of the commands and the -h flag for extra details."
1019 << std::endl;
1020 exit(1);
1021}
1022
1023static AaptOptions prepareArgs(int argc, char** argv) {
1024 if (argc < 2) {
1025 std::cerr << "no command specified." << std::endl << std::endl;
1026 printCommandsAndDie();
1027 }
1028
1029 const StringPiece command(argv[1]);
1030 argc -= 2;
1031 argv += 2;
1032
1033 AaptOptions options;
1034
1035 if (command == "--version" || command == "version") {
1036 std::cout << kAaptVersionStr << std::endl;
1037 exit(0);
1038 } else if (command == "link") {
1039 options.phase = AaptOptions::Phase::Link;
1040 } else if (command == "compile") {
1041 options.phase = AaptOptions::Phase::Compile;
Adam Lesinski330edcd2015-05-04 17:40:56 -07001042 } else if (command == "dump") {
1043 options.phase = AaptOptions::Phase::Dump;
1044 } else if (command == "dump-style-graph") {
1045 options.phase = AaptOptions::Phase::DumpStyleGraph;
Adam Lesinskid5c4f872015-04-21 13:56:10 -07001046 } else {
1047 std::cerr << "invalid command '" << command << "'." << std::endl << std::endl;
1048 printCommandsAndDie();
1049 }
1050
Adam Lesinski6d8e4c42015-05-01 14:47:28 -07001051 bool isStaticLib = false;
Adam Lesinski330edcd2015-05-04 17:40:56 -07001052 if (options.phase == AaptOptions::Phase::Compile ||
1053 options.phase == AaptOptions::Phase::Link) {
1054 if (options.phase == AaptOptions::Phase::Compile) {
1055 flag::requiredFlag("--package", "Android package name",
1056 [&options](const StringPiece& arg) {
1057 options.appInfo.package = util::utf8ToUtf16(arg);
1058 });
1059 } else if (options.phase == AaptOptions::Phase::Link) {
1060 flag::requiredFlag("--manifest", "AndroidManifest.xml of your app",
1061 [&options](const StringPiece& arg) {
1062 options.manifest = Source{ arg.toString() };
1063 });
Adam Lesinskid5c4f872015-04-21 13:56:10 -07001064
Adam Lesinski330edcd2015-05-04 17:40:56 -07001065 flag::optionalFlag("-I", "add an Android APK to link against",
1066 [&options](const StringPiece& arg) {
1067 options.libraries.push_back(Source{ arg.toString() });
1068 });
Adam Lesinskid5c4f872015-04-21 13:56:10 -07001069
Adam Lesinski330edcd2015-05-04 17:40:56 -07001070 flag::optionalFlag("--java", "directory in which to generate R.java",
1071 [&options](const StringPiece& arg) {
1072 options.generateJavaClass = Source{ arg.toString() };
1073 });
Adam Lesinskid5c4f872015-04-21 13:56:10 -07001074
Adam Lesinski330edcd2015-05-04 17:40:56 -07001075 flag::optionalSwitch("--static-lib", "generate a static Android library", true,
1076 &isStaticLib);
1077
1078 flag::optionalFlag("--binding", "Output directory for binding XML files",
1079 [&options](const StringPiece& arg) {
1080 options.bindingOutput = Source{ arg.toString() };
1081 });
1082 flag::optionalSwitch("--no-version", "Disables automatic style and layout versioning",
1083 false, &options.versionStylesAndLayouts);
1084 }
1085
1086 // Common flags for all steps.
1087 flag::requiredFlag("-o", "Output path", [&options](const StringPiece& arg) {
1088 options.output = Source{ arg.toString() };
1089 });
Adam Lesinskid13fb242015-05-12 20:40:48 -07001090 } else if (options.phase == AaptOptions::Phase::DumpStyleGraph) {
1091 flag::requiredFlag("--style", "Name of the style to dump",
1092 [&options](const StringPiece& arg, std::string* outError) -> bool {
1093 Reference styleReference;
1094 if (!ResourceParser::parseStyleParentReference(util::utf8ToUtf16(arg),
1095 &styleReference, outError)) {
1096 return false;
1097 }
1098 options.dumpStyleTarget = styleReference.name;
1099 return true;
1100 });
Adam Lesinskid5c4f872015-04-21 13:56:10 -07001101 }
1102
Adam Lesinskid5c4f872015-04-21 13:56:10 -07001103 bool help = false;
1104 flag::optionalSwitch("-v", "enables verbose logging", true, &options.verbose);
1105 flag::optionalSwitch("-h", "displays this help menu", true, &help);
1106
1107 // Build the command string for output (eg. "aapt2 compile").
1108 std::string fullCommand = "aapt2";
1109 fullCommand += " ";
1110 fullCommand += command.toString();
1111
1112 // Actually read the command line flags.
1113 flag::parse(argc, argv, fullCommand);
1114
1115 if (help) {
1116 flag::usageAndDie(fullCommand);
1117 }
1118
Adam Lesinski6d8e4c42015-05-01 14:47:28 -07001119 if (isStaticLib) {
1120 options.packageType = AaptOptions::PackageType::StaticLibrary;
1121 }
1122
Adam Lesinskid5c4f872015-04-21 13:56:10 -07001123 // Copy all the remaining arguments.
1124 for (const std::string& arg : flag::getArgs()) {
1125 options.input.push_back(Source{ arg });
1126 }
1127 return options;
1128}
1129
Adam Lesinski330edcd2015-05-04 17:40:56 -07001130static bool doDump(const AaptOptions& options) {
1131 for (const Source& source : options.input) {
1132 std::unique_ptr<ZipFile> zipFile = util::make_unique<ZipFile>();
1133 if (zipFile->open(source.path.data(), ZipFile::kOpenReadOnly) != android::NO_ERROR) {
1134 Logger::error(source) << "failed to open: " << strerror(errno) << std::endl;
1135 return false;
1136 }
1137
1138 std::shared_ptr<ResourceTable> table = std::make_shared<ResourceTable>();
1139 std::shared_ptr<ResourceTableResolver> resolver =
1140 std::make_shared<ResourceTableResolver>(
1141 table, std::vector<std::shared_ptr<const android::AssetManager>>());
1142
1143 ZipEntry* entry = zipFile->getEntryByName("resources.arsc");
1144 if (!entry) {
1145 Logger::error(source) << "missing 'resources.arsc'." << std::endl;
1146 return false;
1147 }
1148
1149 std::unique_ptr<void, DeleteMalloc> uncompressedData = std::unique_ptr<void, DeleteMalloc>(
1150 zipFile->uncompress(entry));
1151 assert(uncompressedData);
1152
1153 BinaryResourceParser parser(table, resolver, source, uncompressedData.get(),
1154 entry->getUncompressedLen());
1155 if (!parser.parse()) {
1156 return false;
1157 }
1158
1159 if (options.phase == AaptOptions::Phase::Dump) {
1160 Debug::printTable(table);
1161 } else if (options.phase == AaptOptions::Phase::DumpStyleGraph) {
Adam Lesinskid13fb242015-05-12 20:40:48 -07001162 Debug::printStyleGraph(table, options.dumpStyleTarget);
Adam Lesinski330edcd2015-05-04 17:40:56 -07001163 }
1164 }
1165 return true;
1166}
1167
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001168int main(int argc, char** argv) {
1169 Logger::setLog(std::make_shared<Log>(std::cerr, std::cerr));
Adam Lesinski98aa3ad2015-04-06 11:46:52 -07001170 AaptOptions options = prepareArgs(argc, argv);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001171
Adam Lesinski330edcd2015-05-04 17:40:56 -07001172 if (options.phase == AaptOptions::Phase::Dump ||
1173 options.phase == AaptOptions::Phase::DumpStyleGraph) {
1174 if (!doDump(options)) {
1175 return 1;
1176 }
1177 return 0;
1178 }
1179
Adam Lesinski98aa3ad2015-04-06 11:46:52 -07001180 // If we specified a manifest, go ahead and load the package name from the manifest.
1181 if (!options.manifest.path.empty()) {
1182 if (!loadAppInfo(options.manifest, &options.appInfo)) {
1183 return false;
1184 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001185 }
1186
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001187 // Verify we have some common options set.
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001188 if (options.appInfo.package.empty()) {
1189 Logger::error() << "no package name specified." << std::endl;
1190 return false;
1191 }
1192
Adam Lesinski98aa3ad2015-04-06 11:46:52 -07001193 // Every phase needs a resource table.
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001194 std::shared_ptr<ResourceTable> table = std::make_shared<ResourceTable>();
1195 table->setPackage(options.appInfo.package);
1196 if (options.appInfo.package == u"android") {
1197 table->setPackageId(0x01);
1198 } else {
1199 table->setPackageId(0x7f);
1200 }
1201
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001202 // Load the included libraries.
Adam Lesinski330edcd2015-05-04 17:40:56 -07001203 std::vector<std::shared_ptr<const android::AssetManager>> sources;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001204 for (const Source& source : options.libraries) {
Adam Lesinski330edcd2015-05-04 17:40:56 -07001205 std::shared_ptr<android::AssetManager> assetManager =
1206 std::make_shared<android::AssetManager>();
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001207 int32_t cookie;
Adam Lesinski330edcd2015-05-04 17:40:56 -07001208 if (!assetManager->addAssetPath(android::String8(source.path.data()), &cookie)) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001209 Logger::error(source) << "failed to load library." << std::endl;
1210 return false;
1211 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001212
Adam Lesinski330edcd2015-05-04 17:40:56 -07001213 if (cookie == 0) {
1214 Logger::error(source) << "failed to load library." << std::endl;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001215 return false;
1216 }
Adam Lesinski330edcd2015-05-04 17:40:56 -07001217 sources.push_back(assetManager);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001218 }
1219
1220 // Make the resolver that will cache IDs for us.
Adam Lesinski24aad162015-04-24 19:19:30 -07001221 std::shared_ptr<ResourceTableResolver> resolver = std::make_shared<ResourceTableResolver>(
Adam Lesinski330edcd2015-05-04 17:40:56 -07001222 table, sources);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001223
Adam Lesinski769de982015-04-10 19:43:55 -07001224 if (options.phase == AaptOptions::Phase::Compile) {
1225 if (!compile(options, table, resolver)) {
1226 Logger::error() << "aapt exiting with failures." << std::endl;
1227 return 1;
1228 }
1229 } else if (options.phase == AaptOptions::Phase::Link) {
1230 if (!link(options, table, resolver)) {
1231 Logger::error() << "aapt exiting with failures." << std::endl;
1232 return 1;
1233 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001234 }
1235 return 0;
1236}