blob: 03b9ba4f86ce2211f916b6741801ffc00719e36d [file] [log] [blame]
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include "AppInfo.h"
18#include "BigBuffer.h"
19#include "BinaryResourceParser.h"
Adam Lesinski769de982015-04-10 19:43:55 -070020#include "BinaryXmlPullParser.h"
Adam Lesinski4d3a9872015-04-09 19:53:22 -070021#include "BindingXmlPullParser.h"
Adam 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"
26#include "ManifestParser.h"
27#include "ManifestValidator.h"
Adam Lesinski98aa3ad2015-04-06 11:46:52 -070028#include "Png.h"
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080029#include "ResourceParser.h"
30#include "ResourceTable.h"
31#include "ResourceValues.h"
32#include "SdkConstants.h"
33#include "SourceXmlPullParser.h"
34#include "StringPiece.h"
35#include "TableFlattener.h"
36#include "Util.h"
37#include "XmlFlattener.h"
Adam Lesinski769de982015-04-10 19:43:55 -070038#include "ZipFile.h"
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080039
40#include <algorithm>
41#include <androidfw/AssetManager.h>
42#include <cstdlib>
43#include <dirent.h>
44#include <errno.h>
45#include <fstream>
46#include <iostream>
47#include <sstream>
48#include <sys/stat.h>
Adam Lesinski769de982015-04-10 19:43:55 -070049#include <unordered_set>
Adam Lesinski98aa3ad2015-04-06 11:46:52 -070050#include <utils/Errors.h>
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080051
Adam Lesinski5886a922015-04-15 20:29:22 -070052constexpr const char* kAaptVersionStr = "2.0-alpha";
53
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080054using namespace aapt;
55
56void printTable(const ResourceTable& table) {
57 std::cout << "ResourceTable package=" << table.getPackage();
58 if (table.getPackageId() != ResourceTable::kUnsetPackageId) {
59 std::cout << " id=" << std::hex << table.getPackageId() << std::dec;
60 }
61 std::cout << std::endl
62 << "---------------------------------------------------------" << std::endl;
63
64 for (const auto& type : table) {
65 std::cout << "Type " << type->type;
66 if (type->typeId != ResourceTableType::kUnsetTypeId) {
67 std::cout << " [" << type->typeId << "]";
68 }
69 std::cout << " (" << type->entries.size() << " entries)" << std::endl;
70 for (const auto& entry : type->entries) {
71 std::cout << " " << entry->name;
72 if (entry->entryId != ResourceEntry::kUnsetEntryId) {
73 std::cout << " [" << entry->entryId << "]";
74 }
75 std::cout << " (" << entry->values.size() << " configurations)";
76 if (entry->publicStatus.isPublic) {
77 std::cout << " PUBLIC";
78 }
79 std::cout << std::endl;
80 for (const auto& value : entry->values) {
81 std::cout << " " << value.config << " (" << value.source << ") : ";
82 value.value->print(std::cout);
83 std::cout << std::endl;
84 }
85 }
86 }
87}
88
89void printStringPool(const StringPool& pool) {
90 std::cout << "String pool of length " << pool.size() << std::endl
91 << "---------------------------------------------------------" << std::endl;
92
93 size_t i = 0;
94 for (const auto& entry : pool) {
95 std::cout << "[" << i << "]: "
96 << entry->value
97 << " (Priority " << entry->context.priority
98 << ", Config '" << entry->context.config << "')"
99 << std::endl;
100 i++;
101 }
102}
103
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800104/**
105 * Collect files from 'root', filtering out any files that do not
106 * match the FileFilter 'filter'.
107 */
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700108bool walkTree(const Source& root, const FileFilter& filter,
109 std::vector<Source>* outEntries) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800110 bool error = false;
111
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700112 for (const std::string& dirName : listFiles(root.path)) {
113 std::string dir = root.path;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800114 appendPath(&dir, dirName);
115
116 FileType ft = getFileType(dir);
117 if (!filter(dirName, ft)) {
118 continue;
119 }
120
121 if (ft != FileType::kDirectory) {
122 continue;
123 }
124
125 for (const std::string& fileName : listFiles(dir)) {
126 std::string file(dir);
127 appendPath(&file, fileName);
128
129 FileType ft = getFileType(file);
130 if (!filter(fileName, ft)) {
131 continue;
132 }
133
134 if (ft != FileType::kRegular) {
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700135 Logger::error(Source{ file }) << "not a regular file." << std::endl;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800136 error = true;
137 continue;
138 }
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700139 outEntries->push_back(Source{ file });
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800140 }
141 }
142 return !error;
143}
144
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800145bool loadResTable(android::ResTable* table, const Source& source) {
146 std::ifstream ifs(source.path, std::ifstream::in | std::ifstream::binary);
147 if (!ifs) {
148 Logger::error(source) << strerror(errno) << std::endl;
149 return false;
150 }
151
152 std::streampos fsize = ifs.tellg();
153 ifs.seekg(0, std::ios::end);
154 fsize = ifs.tellg() - fsize;
155 ifs.seekg(0, std::ios::beg);
156
157 assert(fsize >= 0);
158 size_t dataSize = static_cast<size_t>(fsize);
159 char* buf = new char[dataSize];
160 ifs.read(buf, dataSize);
161
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700162 bool result = table->add(buf, dataSize, -1, true) == android::NO_ERROR;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800163
164 delete [] buf;
165 return result;
166}
167
Adam Lesinski769de982015-04-10 19:43:55 -0700168void versionStylesForCompat(const std::shared_ptr<ResourceTable>& table) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800169 for (auto& type : *table) {
170 if (type->type != ResourceType::kStyle) {
171 continue;
172 }
173
174 for (auto& entry : type->entries) {
175 // Add the versioned styles we want to create
176 // here. They are added to the table after
177 // iterating over the original set of styles.
178 //
179 // A stack is used since auto-generated styles
180 // from later versions should override
181 // auto-generated styles from earlier versions.
182 // Iterating over the styles is done in order,
183 // so we will always visit sdkVersions from smallest
184 // to largest.
185 std::stack<ResourceConfigValue> addStack;
186
187 for (ResourceConfigValue& configValue : entry->values) {
188 visitFunc<Style>(*configValue.value, [&](Style& style) {
189 // Collect which entries we've stripped and the smallest
190 // SDK level which was stripped.
191 size_t minSdkStripped = std::numeric_limits<size_t>::max();
192 std::vector<Style::Entry> stripped;
193
194 // Iterate over the style's entries and erase/record the
195 // attributes whose SDK level exceeds the config's sdkVersion.
196 auto iter = style.entries.begin();
197 while (iter != style.entries.end()) {
198 if (iter->key.name.package == u"android") {
199 size_t sdkLevel = findAttributeSdkLevel(iter->key.name.entry);
200 if (sdkLevel > 1 && sdkLevel > configValue.config.sdkVersion) {
201 // Record that we are about to strip this.
202 stripped.emplace_back(std::move(*iter));
203 minSdkStripped = std::min(minSdkStripped, sdkLevel);
204
205 // Erase this from this style.
206 iter = style.entries.erase(iter);
207 continue;
208 }
209 }
210 ++iter;
211 }
212
213 if (!stripped.empty()) {
214 // We have stripped attributes, so let's create a new style to hold them.
215 ConfigDescription versionConfig(configValue.config);
216 versionConfig.sdkVersion = minSdkStripped;
217
218 ResourceConfigValue value = {
219 versionConfig,
220 configValue.source,
221 {},
222
223 // Create a copy of the original style.
Adam Lesinski769de982015-04-10 19:43:55 -0700224 std::unique_ptr<Value>(configValue.value->clone(
225 &table->getValueStringPool()))
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800226 };
227
228 Style& newStyle = static_cast<Style&>(*value.value);
Adam Lesinski769de982015-04-10 19:43:55 -0700229 newStyle.weak = true;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800230
231 // Move the recorded stripped attributes into this new style.
232 std::move(stripped.begin(), stripped.end(),
233 std::back_inserter(newStyle.entries));
234
235 // We will add this style to the table later. If we do it now, we will
236 // mess up iteration.
237 addStack.push(std::move(value));
238 }
239 });
240 }
241
242 auto comparator =
243 [](const ResourceConfigValue& lhs, const ConfigDescription& rhs) -> bool {
244 return lhs.config < rhs;
245 };
246
247 while (!addStack.empty()) {
248 ResourceConfigValue& value = addStack.top();
249 auto iter = std::lower_bound(entry->values.begin(), entry->values.end(),
250 value.config, comparator);
251 if (iter == entry->values.end() || iter->config != value.config) {
252 entry->values.insert(iter, std::move(value));
253 }
254 addStack.pop();
255 }
256 }
257 }
258}
259
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700260struct CompileItem {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800261 Source source;
262 ResourceName name;
263 ConfigDescription config;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700264 std::string extension;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800265};
266
Adam Lesinski769de982015-04-10 19:43:55 -0700267struct LinkItem {
268 Source source;
269 std::string apkPath;
270};
271
272std::string buildFileReference(const CompileItem& item) {
273 std::stringstream path;
274 path << "res/" << item.name.type;
275 if (item.config != ConfigDescription{}) {
276 path << "-" << item.config;
277 }
278 path << "/" << util::utf16ToUtf8(item.name.entry) + "." + item.extension;
279 return path.str();
280}
281
282bool addFileReference(const std::shared_ptr<ResourceTable>& table, const CompileItem& item) {
283 StringPool& pool = table->getValueStringPool();
284 StringPool::Ref ref = pool.makeRef(util::utf8ToUtf16(buildFileReference(item)));
285 return table->addResource(item.name, item.config, item.source.line(0),
286 util::make_unique<FileReference>(ref));
287}
288
289struct AaptOptions {
290 enum class Phase {
291 Link,
292 Compile,
293 };
294
295 // The phase to process.
296 Phase phase;
297
298 // Details about the app.
299 AppInfo appInfo;
300
301 // The location of the manifest file.
302 Source manifest;
303
304 // The APK files to link.
305 std::vector<Source> input;
306
307 // The libraries these files may reference.
308 std::vector<Source> libraries;
309
310 // Output path. This can be a directory or file
311 // depending on the phase.
312 Source output;
313
314 // Directory in which to write binding xml files.
315 Source bindingOutput;
316
317 // Directory to in which to generate R.java.
318 Maybe<Source> generateJavaClass;
319
320 // Whether to output verbose details about
321 // compilation.
322 bool verbose = false;
Adam Lesinski5886a922015-04-15 20:29:22 -0700323
324 // Whether or not to auto-version styles or layouts
325 // referencing attributes defined in a newer SDK
326 // level than the style or layout is defined for.
327 bool versionStylesAndLayouts = true;
Adam Lesinski769de982015-04-10 19:43:55 -0700328};
329
Adam Lesinski5886a922015-04-15 20:29:22 -0700330
Adam Lesinski769de982015-04-10 19:43:55 -0700331bool compileXml(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table,
332 const CompileItem& item, std::queue<CompileItem>* outQueue, ZipFile* outApk) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800333 std::ifstream in(item.source.path, std::ifstream::binary);
334 if (!in) {
335 Logger::error(item.source) << strerror(errno) << std::endl;
336 return false;
337 }
338
Adam Lesinski4d3a9872015-04-09 19:53:22 -0700339 BigBuffer outBuffer(1024);
Adam Lesinski769de982015-04-10 19:43:55 -0700340
341 // No resolver, since we are not compiling attributes here.
342 XmlFlattener flattener(table, {});
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800343
Adam Lesinski769de982015-04-10 19:43:55 -0700344 XmlFlattener::Options xmlOptions;
Adam Lesinski5886a922015-04-15 20:29:22 -0700345 if (options.versionStylesAndLayouts) {
346 // We strip attributes that do not belong in this version of the resource.
347 // Non-version qualified resources have an implicit version 1 requirement.
348 xmlOptions.maxSdkAttribute = item.config.sdkVersion ? item.config.sdkVersion : 1;
349 }
Adam Lesinski769de982015-04-10 19:43:55 -0700350
351 std::shared_ptr<BindingXmlPullParser> binding;
352 std::shared_ptr<XmlPullParser> parser = std::make_shared<SourceXmlPullParser>(in);
353 if (item.name.type == ResourceType::kLayout) {
354 // Layouts may have defined bindings, so we need to make sure they get processed.
355 binding = std::make_shared<BindingXmlPullParser>(parser);
356 parser = binding;
357 }
358
359 Maybe<size_t> minStrippedSdk = flattener.flatten(item.source, parser, &outBuffer, xmlOptions);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800360 if (!minStrippedSdk) {
361 return false;
362 }
363
364 if (minStrippedSdk.value() > 0) {
365 // Something was stripped, so let's generate a new file
366 // with the version of the smallest SDK version stripped.
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700367 CompileItem newWork = item;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800368 newWork.config.sdkVersion = minStrippedSdk.value();
Adam Lesinski769de982015-04-10 19:43:55 -0700369 outQueue->push(newWork);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800370 }
371
Adam Lesinski769de982015-04-10 19:43:55 -0700372 // Write the resulting compiled XML file to the output APK.
373 if (outApk->add(outBuffer, buildFileReference(item).data(), ZipEntry::kCompressStored,
374 nullptr) != android::NO_ERROR) {
375 Logger::error(options.output) << "failed to write compiled '" << item.source << "' to apk."
376 << std::endl;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800377 return false;
378 }
379
Adam Lesinski769de982015-04-10 19:43:55 -0700380 if (binding && !options.bindingOutput.path.empty()) {
381 // We generated a binding xml file, write it out.
382 Source bindingOutput = options.bindingOutput;
383 appendPath(&bindingOutput.path, buildFileReference(item));
Adam Lesinski4d3a9872015-04-09 19:53:22 -0700384
Adam Lesinski769de982015-04-10 19:43:55 -0700385 if (!mkdirs(bindingOutput.path)) {
386 Logger::error(bindingOutput) << strerror(errno) << std::endl;
387 return false;
388 }
389
390 appendPath(&bindingOutput.path, "bind.xml");
391
Adam Lesinski4d3a9872015-04-09 19:53:22 -0700392 std::ofstream bout(bindingOutput.path);
393 if (!bout) {
394 Logger::error(bindingOutput) << strerror(errno) << std::endl;
395 return false;
396 }
397
398 if (!binding->writeToFile(bout)) {
399 Logger::error(bindingOutput) << strerror(errno) << std::endl;
400 return false;
401 }
402 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800403 return true;
404}
405
Adam Lesinski769de982015-04-10 19:43:55 -0700406bool linkXml(const AaptOptions& options, const std::shared_ptr<Resolver>& resolver,
407 const LinkItem& item, const void* data, size_t dataLen, ZipFile* outApk) {
408 std::shared_ptr<android::ResXMLTree> tree = std::make_shared<android::ResXMLTree>();
409 if (tree->setTo(data, dataLen, false) != android::NO_ERROR) {
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700410 return false;
411 }
412
Adam Lesinski769de982015-04-10 19:43:55 -0700413 std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<BinaryXmlPullParser>(tree);
414
415 BigBuffer outBuffer(1024);
416 XmlFlattener flattener({}, resolver);
417 if (!flattener.flatten(item.source, xmlParser, &outBuffer, {})) {
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700418 return false;
419 }
420
Adam Lesinski769de982015-04-10 19:43:55 -0700421 if (outApk->add(outBuffer, item.apkPath.data(), ZipEntry::kCompressDeflated, nullptr) !=
422 android::NO_ERROR) {
423 Logger::error(options.output) << "failed to write linked file '" << item.source
424 << "' to apk." << std::endl;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700425 return false;
426 }
427 return true;
428}
429
Adam Lesinski769de982015-04-10 19:43:55 -0700430bool compilePng(const AaptOptions& options, const CompileItem& item, ZipFile* outApk) {
431 std::ifstream in(item.source.path, std::ifstream::binary);
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700432 if (!in) {
Adam Lesinski769de982015-04-10 19:43:55 -0700433 Logger::error(item.source) << strerror(errno) << std::endl;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700434 return false;
435 }
436
Adam Lesinski769de982015-04-10 19:43:55 -0700437 BigBuffer outBuffer(4096);
438 std::string err;
439 Png png;
440 if (!png.process(item.source, in, &outBuffer, {}, &err)) {
441 Logger::error(item.source) << err << std::endl;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700442 return false;
443 }
444
Adam Lesinski769de982015-04-10 19:43:55 -0700445 if (outApk->add(outBuffer, buildFileReference(item).data(), ZipEntry::kCompressStored,
446 nullptr) != android::NO_ERROR) {
447 Logger::error(options.output) << "failed to write compiled '" << item.source
448 << "' to apk." << std::endl;
449 return false;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700450 }
Adam Lesinski769de982015-04-10 19:43:55 -0700451 return true;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700452}
453
Adam Lesinski769de982015-04-10 19:43:55 -0700454bool copyFile(const AaptOptions& options, const CompileItem& item, ZipFile* outApk) {
455 if (outApk->add(item.source.path.data(), buildFileReference(item).data(),
456 ZipEntry::kCompressStored, nullptr) != android::NO_ERROR) {
457 Logger::error(options.output) << "failed to copy file '" << item.source << "' to apk."
458 << std::endl;
459 return false;
460 }
461 return true;
462}
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800463
Adam Lesinski769de982015-04-10 19:43:55 -0700464bool compileManifest(const AaptOptions& options, const std::shared_ptr<Resolver>& resolver,
465 ZipFile* outApk) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800466 if (options.verbose) {
Adam Lesinski769de982015-04-10 19:43:55 -0700467 Logger::note(options.manifest) << "compiling AndroidManifest.xml." << std::endl;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800468 }
469
470 std::ifstream in(options.manifest.path, std::ifstream::binary);
471 if (!in) {
472 Logger::error(options.manifest) << strerror(errno) << std::endl;
473 return false;
474 }
475
476 BigBuffer outBuffer(1024);
477 std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<SourceXmlPullParser>(in);
Adam Lesinski769de982015-04-10 19:43:55 -0700478 XmlFlattener flattener({}, resolver);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800479
Adam Lesinski769de982015-04-10 19:43:55 -0700480 if (!flattener.flatten(options.manifest, xmlParser, &outBuffer, {})) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800481 return false;
482 }
483
Adam Lesinski769de982015-04-10 19:43:55 -0700484 std::unique_ptr<uint8_t[]> data = util::copy(outBuffer);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800485
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700486 android::ResXMLTree tree;
Adam Lesinski769de982015-04-10 19:43:55 -0700487 if (tree.setTo(data.get(), outBuffer.size(), false) != android::NO_ERROR) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800488 return false;
489 }
490
491 ManifestValidator validator(resolver->getResTable());
492 if (!validator.validate(options.manifest, &tree)) {
493 return false;
494 }
495
Adam Lesinski769de982015-04-10 19:43:55 -0700496 if (outApk->add(data.get(), outBuffer.size(), "AndroidManifest.xml",
497 ZipEntry::kCompressStored, nullptr) != android::NO_ERROR) {
498 Logger::error(options.output) << "failed to write 'AndroidManifest.xml' to apk."
499 << std::endl;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800500 return false;
501 }
502 return true;
503}
504
505bool loadAppInfo(const Source& source, AppInfo* outInfo) {
506 std::ifstream ifs(source.path, std::ifstream::in | std::ifstream::binary);
507 if (!ifs) {
508 Logger::error(source) << strerror(errno) << std::endl;
509 return false;
510 }
511
512 ManifestParser parser;
513 std::shared_ptr<XmlPullParser> pullParser = std::make_shared<SourceXmlPullParser>(ifs);
514 return parser.parse(source, pullParser, outInfo);
515}
516
Adam Lesinski769de982015-04-10 19:43:55 -0700517static void printCommandsAndDie() {
518 std::cerr << "The following commands are supported:" << std::endl << std::endl;
519 std::cerr << "compile compiles a subset of resources" << std::endl;
520 std::cerr << "link links together compiled resources and libraries" << std::endl;
521 std::cerr << std::endl;
522 std::cerr << "run aapt2 with one of the commands and the -h flag for extra details."
523 << std::endl;
524 exit(1);
525}
526
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700527static AaptOptions prepareArgs(int argc, char** argv) {
528 if (argc < 2) {
Adam Lesinski769de982015-04-10 19:43:55 -0700529 std::cerr << "no command specified." << std::endl << std::endl;
530 printCommandsAndDie();
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800531 }
532
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700533 const StringPiece command(argv[1]);
534 argc -= 2;
535 argv += 2;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800536
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700537 AaptOptions options;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800538
Adam Lesinski5886a922015-04-15 20:29:22 -0700539 if (command == "--version" || command == "version") {
540 std::cout << kAaptVersionStr << std::endl;
541 exit(0);
542 } else if (command == "link") {
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700543 options.phase = AaptOptions::Phase::Link;
544 } else if (command == "compile") {
545 options.phase = AaptOptions::Phase::Compile;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800546 } else {
Adam Lesinski769de982015-04-10 19:43:55 -0700547 std::cerr << "invalid command '" << command << "'." << std::endl << std::endl;
548 printCommandsAndDie();
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800549 }
550
Adam Lesinski769de982015-04-10 19:43:55 -0700551 if (options.phase == AaptOptions::Phase::Compile) {
552 flag::requiredFlag("--package", "Android package name",
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700553 [&options](const StringPiece& arg) {
Adam Lesinski769de982015-04-10 19:43:55 -0700554 options.appInfo.package = util::utf8ToUtf16(arg);
555 });
556 flag::optionalFlag("--binding", "Output directory for binding XML files",
557 [&options](const StringPiece& arg) {
558 options.bindingOutput = Source{ arg.toString() };
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700559 });
Adam Lesinski5886a922015-04-15 20:29:22 -0700560 flag::optionalSwitch("--no-version", "Disables automatic style and layout versioning",
561 false, &options.versionStylesAndLayouts);
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700562
Adam Lesinski769de982015-04-10 19:43:55 -0700563 } else if (options.phase == AaptOptions::Phase::Link) {
564 flag::requiredFlag("--manifest", "AndroidManifest.xml of your app",
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700565 [&options](const StringPiece& arg) {
566 options.manifest = Source{ arg.toString() };
567 });
568
569 flag::optionalFlag("-I", "add an Android APK to link against",
570 [&options](const StringPiece& arg) {
571 options.libraries.push_back(Source{ arg.toString() });
572 });
573
574 flag::optionalFlag("--java", "directory in which to generate R.java",
575 [&options](const StringPiece& arg) {
576 options.generateJavaClass = Source{ arg.toString() };
577 });
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800578 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800579
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700580 // Common flags for all steps.
Adam Lesinski769de982015-04-10 19:43:55 -0700581 flag::requiredFlag("-o", "Output path", [&options](const StringPiece& arg) {
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700582 options.output = Source{ arg.toString() };
583 });
Adam Lesinski769de982015-04-10 19:43:55 -0700584
585 bool help = false;
Adam Lesinski5886a922015-04-15 20:29:22 -0700586 flag::optionalSwitch("-v", "enables verbose logging", true, &options.verbose);
587 flag::optionalSwitch("-h", "displays this help menu", true, &help);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800588
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700589 // Build the command string for output (eg. "aapt2 compile").
590 std::string fullCommand = "aapt2";
591 fullCommand += " ";
592 fullCommand += command.toString();
593
594 // Actually read the command line flags.
595 flag::parse(argc, argv, fullCommand);
596
Adam Lesinski769de982015-04-10 19:43:55 -0700597 if (help) {
598 flag::usageAndDie(fullCommand);
599 }
600
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700601 // Copy all the remaining arguments.
Adam Lesinski769de982015-04-10 19:43:55 -0700602 for (const std::string& arg : flag::getArgs()) {
603 options.input.push_back(Source{ arg });
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800604 }
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700605 return options;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800606}
607
Adam Lesinski769de982015-04-10 19:43:55 -0700608static bool compileValues(const std::shared_ptr<ResourceTable>& table, const Source& source,
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700609 const ConfigDescription& config) {
610 std::ifstream in(source.path, std::ifstream::binary);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800611 if (!in) {
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700612 Logger::error(source) << strerror(errno) << std::endl;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800613 return false;
614 }
615
616 std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<SourceXmlPullParser>(in);
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700617 ResourceParser parser(table, source, config, xmlParser);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800618 return parser.parse();
619}
620
621struct ResourcePathData {
622 std::u16string resourceDir;
623 std::u16string name;
624 std::string extension;
625 ConfigDescription config;
626};
627
628/**
629 * Resource file paths are expected to look like:
630 * [--/res/]type[-config]/name
631 */
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700632static Maybe<ResourcePathData> extractResourcePathData(const Source& source) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800633 std::vector<std::string> parts = util::splitAndLowercase(source.path, '/');
634 if (parts.size() < 2) {
635 Logger::error(source) << "bad resource path." << std::endl;
636 return {};
637 }
638
639 std::string& dir = parts[parts.size() - 2];
640 StringPiece dirStr = dir;
641
642 ConfigDescription config;
643 size_t dashPos = dir.find('-');
644 if (dashPos != std::string::npos) {
645 StringPiece configStr = dirStr.substr(dashPos + 1, dir.size() - (dashPos + 1));
646 if (!ConfigDescription::parse(configStr, &config)) {
647 Logger::error(source)
648 << "invalid configuration '"
649 << configStr
650 << "'."
651 << std::endl;
652 return {};
653 }
654 dirStr = dirStr.substr(0, dashPos);
655 }
656
657 std::string& filename = parts[parts.size() - 1];
658 StringPiece name = filename;
659 StringPiece extension;
660 size_t dotPos = filename.find('.');
661 if (dotPos != std::string::npos) {
662 extension = name.substr(dotPos + 1, filename.size() - (dotPos + 1));
663 name = name.substr(0, dotPos);
664 }
665
666 return ResourcePathData{
667 util::utf8ToUtf16(dirStr),
668 util::utf8ToUtf16(name),
669 extension.toString(),
670 config
671 };
672}
673
Adam Lesinski769de982015-04-10 19:43:55 -0700674bool writeResourceTable(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table,
675 const TableFlattener::Options& flattenerOptions, ZipFile* outApk) {
676 if (table->begin() != table->end()) {
677 BigBuffer buffer(1024);
678 TableFlattener flattener(flattenerOptions);
679 if (!flattener.flatten(&buffer, *table)) {
680 Logger::error() << "failed to flatten resource table." << std::endl;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700681 return false;
682 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800683
Adam Lesinski769de982015-04-10 19:43:55 -0700684 if (options.verbose) {
685 Logger::note() << "Final resource table size=" << util::formatSize(buffer.size())
686 << std::endl;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700687 }
688
Adam Lesinski769de982015-04-10 19:43:55 -0700689 if (outApk->add(buffer, "resources.arsc", ZipEntry::kCompressStored, nullptr) !=
690 android::NO_ERROR) {
691 Logger::note(options.output) << "failed to store resource table." << std::endl;
692 return false;
693 }
694 }
695 return true;
696}
697
698static constexpr int kOpenFlags = ZipFile::kOpenCreate | ZipFile::kOpenTruncate |
699 ZipFile::kOpenReadWrite;
700
701bool link(const AaptOptions& options, const std::shared_ptr<ResourceTable>& outTable,
702 const std::shared_ptr<Resolver>& resolver) {
703 std::map<std::shared_ptr<ResourceTable>, std::unique_ptr<ZipFile>> apkFiles;
704 std::unordered_set<std::u16string> linkedPackages;
705
706 // Populate the linkedPackages with our own.
707 linkedPackages.insert(options.appInfo.package);
708
709 // Load all APK files.
710 for (const Source& source : options.input) {
711 std::unique_ptr<ZipFile> zipFile = util::make_unique<ZipFile>();
712 if (zipFile->open(source.path.data(), ZipFile::kOpenReadOnly) != android::NO_ERROR) {
713 Logger::error(source) << "failed to open: " << strerror(errno) << std::endl;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700714 return false;
715 }
716
Adam Lesinski769de982015-04-10 19:43:55 -0700717 std::shared_ptr<ResourceTable> table = std::make_shared<ResourceTable>();
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700718
Adam Lesinski769de982015-04-10 19:43:55 -0700719 ZipEntry* entry = zipFile->getEntryByName("resources.arsc");
720 if (!entry) {
721 Logger::error(source) << "missing 'resources.arsc'." << std::endl;
722 return false;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700723 }
724
Adam Lesinski769de982015-04-10 19:43:55 -0700725 void* uncompressedData = zipFile->uncompress(entry);
726 assert(uncompressedData);
727
728 BinaryResourceParser parser(table, resolver, source, uncompressedData,
729 entry->getUncompressedLen());
730 if (!parser.parse()) {
731 free(uncompressedData);
732 return false;
733 }
734 free(uncompressedData);
735
736 // Keep track of where this table came from.
737 apkFiles[table] = std::move(zipFile);
738
739 // Add the package to the set of linked packages.
740 linkedPackages.insert(table->getPackage());
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700741 }
742
Adam Lesinski769de982015-04-10 19:43:55 -0700743 for (auto& p : apkFiles) {
744 const std::shared_ptr<ResourceTable>& inTable = p.first;
745
746 if (!outTable->merge(std::move(*inTable))) {
747 return false;
748 }
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700749 }
750
Adam Lesinski769de982015-04-10 19:43:55 -0700751 {
752 // Now that everything is merged, let's link it.
753 Linker linker(outTable, resolver);
754 if (!linker.linkAndValidate()) {
755 return false;
756 }
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700757
Adam Lesinski769de982015-04-10 19:43:55 -0700758 // Verify that all symbols exist.
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700759 const auto& unresolvedRefs = linker.getUnresolvedReferences();
760 if (!unresolvedRefs.empty()) {
761 for (const auto& entry : unresolvedRefs) {
762 for (const auto& source : entry.second) {
763 Logger::error(source) << "unresolved symbol '" << entry.first << "'."
764 << std::endl;
765 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800766 }
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700767 return false;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800768 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800769 }
770
Adam Lesinski769de982015-04-10 19:43:55 -0700771 // Open the output APK file for writing.
772 ZipFile outApk;
773 if (outApk.open(options.output.path.data(), kOpenFlags) != android::NO_ERROR) {
774 Logger::error(options.output) << "failed to open: " << strerror(errno) << std::endl;
775 return false;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700776 }
777
Adam Lesinski769de982015-04-10 19:43:55 -0700778 if (!compileManifest(options, resolver, &outApk)) {
779 return false;
780 }
781
782 for (auto& p : apkFiles) {
783 std::unique_ptr<ZipFile>& zipFile = p.second;
784
785 // TODO(adamlesinski): Get list of files to read when processing config filter.
786
787 const int numEntries = zipFile->getNumEntries();
788 for (int i = 0; i < numEntries; i++) {
789 ZipEntry* entry = zipFile->getEntryByIndex(i);
790 assert(entry);
791
792 StringPiece filename = entry->getFileName();
793 if (!util::stringStartsWith<char>(filename, "res/")) {
794 continue;
795 }
796
797 if (util::stringEndsWith<char>(filename, ".xml")) {
798 void* uncompressedData = zipFile->uncompress(entry);
799 assert(uncompressedData);
800
801 LinkItem item = { Source{ filename.toString() }, filename.toString() };
802
803 if (!linkXml(options, resolver, item, uncompressedData,
804 entry->getUncompressedLen(), &outApk)) {
805 Logger::error(options.output) << "failed to link '" << filename << "'."
806 << std::endl;
807 return false;
808 }
809 } else {
810 if (outApk.add(zipFile.get(), entry, 0, nullptr) != android::NO_ERROR) {
811 Logger::error(options.output) << "failed to copy '" << filename << "'."
812 << std::endl;
813 return false;
814 }
815 }
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700816 }
817 }
818
819 // Generate the Java class file.
Adam Lesinski769de982015-04-10 19:43:55 -0700820 if (options.generateJavaClass) {
821 JavaClassGenerator generator(outTable, {});
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700822
Adam Lesinski769de982015-04-10 19:43:55 -0700823 for (const std::u16string& package : linkedPackages) {
824 Source outPath = options.generateJavaClass.value();
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800825
Adam Lesinski769de982015-04-10 19:43:55 -0700826 // Build the output directory from the package name.
827 // Eg. com.android.app -> com/android/app
828 const std::string packageUtf8 = util::utf16ToUtf8(package);
829 for (StringPiece part : util::tokenize<char>(packageUtf8, '.')) {
830 appendPath(&outPath.path, part);
831 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800832
Adam Lesinski769de982015-04-10 19:43:55 -0700833 if (!mkdirs(outPath.path)) {
834 Logger::error(outPath) << strerror(errno) << std::endl;
835 return false;
836 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800837
Adam Lesinski769de982015-04-10 19:43:55 -0700838 appendPath(&outPath.path, "R.java");
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800839
Adam Lesinski769de982015-04-10 19:43:55 -0700840 if (options.verbose) {
841 Logger::note(outPath) << "writing Java symbols." << std::endl;
842 }
843
844 std::ofstream fout(outPath.path);
845 if (!fout) {
846 Logger::error(outPath) << strerror(errno) << std::endl;
847 return false;
848 }
849
850 if (!generator.generate(package, fout)) {
851 Logger::error(outPath) << generator.getError() << "." << std::endl;
852 return false;
853 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800854 }
855 }
856
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700857 // Flatten the resource table.
Adam Lesinski769de982015-04-10 19:43:55 -0700858 TableFlattener::Options flattenerOptions;
859 flattenerOptions.useExtendedChunks = false;
860 if (!writeResourceTable(options, outTable, flattenerOptions, &outApk)) {
861 return false;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800862 }
Adam Lesinski769de982015-04-10 19:43:55 -0700863
864 outApk.flush();
865 return true;
866}
867
868bool compile(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table,
869 const std::shared_ptr<Resolver>& resolver) {
870 std::queue<CompileItem> compileQueue;
871 bool error = false;
872
873 // Compile all the resource files passed in on the command line.
874 for (const Source& source : options.input) {
875 // Need to parse the resource type/config/filename.
876 Maybe<ResourcePathData> maybePathData = extractResourcePathData(source);
877 if (!maybePathData) {
878 return false;
879 }
880
881 const ResourcePathData& pathData = maybePathData.value();
882 if (pathData.resourceDir == u"values") {
883 // The file is in the values directory, which means its contents will
884 // go into the resource table.
885 if (options.verbose) {
886 Logger::note(source) << "compiling values." << std::endl;
887 }
888
889 error |= !compileValues(table, source, pathData.config);
890 } else {
891 // The file is in a directory like 'layout' or 'drawable'. Find out
892 // the type.
893 const ResourceType* type = parseResourceType(pathData.resourceDir);
894 if (!type) {
895 Logger::error(source) << "invalid resource type '" << pathData.resourceDir << "'."
896 << std::endl;
897 return false;
898 }
899
900 compileQueue.push(CompileItem{
901 source,
902 ResourceName{ table->getPackage(), *type, pathData.name },
903 pathData.config,
904 pathData.extension
905 });
906 }
907 }
908
909 if (error) {
910 return false;
911 }
912
913 // Version all styles referencing attributes outside of their specified SDK version.
Adam Lesinski5886a922015-04-15 20:29:22 -0700914 if (options.versionStylesAndLayouts) {
915 versionStylesForCompat(table);
916 }
Adam Lesinski769de982015-04-10 19:43:55 -0700917
918 // Open the output APK file for writing.
919 ZipFile outApk;
920 if (outApk.open(options.output.path.data(), kOpenFlags) != android::NO_ERROR) {
921 Logger::error(options.output) << "failed to open: " << strerror(errno) << std::endl;
922 return false;
923 }
924
925 // Compile each file.
926 for (; !compileQueue.empty(); compileQueue.pop()) {
927 const CompileItem& item = compileQueue.front();
928
929 // Add the file name to the resource table.
930 error |= !addFileReference(table, item);
931
932 if (item.extension == "xml") {
933 error |= !compileXml(options, table, item, &compileQueue, &outApk);
934 } else if (item.extension == "png" || item.extension == "9.png") {
935 error |= !compilePng(options, item, &outApk);
936 } else {
937 error |= !copyFile(options, item, &outApk);
938 }
939 }
940
941 if (error) {
942 return false;
943 }
944
945 // Link and assign resource IDs.
946 Linker linker(table, resolver);
947 if (!linker.linkAndValidate()) {
948 return false;
949 }
950
951 // Flatten the resource table.
952 if (!writeResourceTable(options, table, {}, &outApk)) {
953 return false;
954 }
955
956 outApk.flush();
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800957 return true;
958}
959
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800960int main(int argc, char** argv) {
961 Logger::setLog(std::make_shared<Log>(std::cerr, std::cerr));
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700962 AaptOptions options = prepareArgs(argc, argv);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800963
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700964 // If we specified a manifest, go ahead and load the package name from the manifest.
965 if (!options.manifest.path.empty()) {
966 if (!loadAppInfo(options.manifest, &options.appInfo)) {
967 return false;
968 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800969 }
970
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800971 // Verify we have some common options set.
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800972 if (options.appInfo.package.empty()) {
973 Logger::error() << "no package name specified." << std::endl;
974 return false;
975 }
976
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700977 // Every phase needs a resource table.
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800978 std::shared_ptr<ResourceTable> table = std::make_shared<ResourceTable>();
979 table->setPackage(options.appInfo.package);
980 if (options.appInfo.package == u"android") {
981 table->setPackageId(0x01);
982 } else {
983 table->setPackageId(0x7f);
984 }
985
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800986 // Load the included libraries.
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800987 std::shared_ptr<android::AssetManager> libraries = std::make_shared<android::AssetManager>();
988 for (const Source& source : options.libraries) {
Adam Lesinski4d3a9872015-04-09 19:53:22 -0700989 if (util::stringEndsWith<char>(source.path, ".arsc")) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800990 // We'll process these last so as to avoid a cookie issue.
991 continue;
992 }
993
994 int32_t cookie;
995 if (!libraries->addAssetPath(android::String8(source.path.data()), &cookie)) {
996 Logger::error(source) << "failed to load library." << std::endl;
997 return false;
998 }
999 }
1000
1001 for (const Source& source : options.libraries) {
Adam Lesinski4d3a9872015-04-09 19:53:22 -07001002 if (!util::stringEndsWith<char>(source.path, ".arsc")) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001003 // We've already processed this.
1004 continue;
1005 }
1006
1007 // Dirty hack but there is no other way to get a
1008 // writeable ResTable.
1009 if (!loadResTable(const_cast<android::ResTable*>(&libraries->getResources(false)),
1010 source)) {
1011 return false;
1012 }
1013 }
1014
1015 // Make the resolver that will cache IDs for us.
1016 std::shared_ptr<Resolver> resolver = std::make_shared<Resolver>(table, libraries);
1017
Adam Lesinski769de982015-04-10 19:43:55 -07001018 if (options.phase == AaptOptions::Phase::Compile) {
1019 if (!compile(options, table, resolver)) {
1020 Logger::error() << "aapt exiting with failures." << std::endl;
1021 return 1;
1022 }
1023 } else if (options.phase == AaptOptions::Phase::Link) {
1024 if (!link(options, table, resolver)) {
1025 Logger::error() << "aapt exiting with failures." << std::endl;
1026 return 1;
1027 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001028 }
1029 return 0;
1030}