blob: b3e276839c308182007d85d6d09d2a5b579cf891 [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
52using namespace aapt;
53
54void printTable(const ResourceTable& table) {
55 std::cout << "ResourceTable package=" << table.getPackage();
56 if (table.getPackageId() != ResourceTable::kUnsetPackageId) {
57 std::cout << " id=" << std::hex << table.getPackageId() << std::dec;
58 }
59 std::cout << std::endl
60 << "---------------------------------------------------------" << std::endl;
61
62 for (const auto& type : table) {
63 std::cout << "Type " << type->type;
64 if (type->typeId != ResourceTableType::kUnsetTypeId) {
65 std::cout << " [" << type->typeId << "]";
66 }
67 std::cout << " (" << type->entries.size() << " entries)" << std::endl;
68 for (const auto& entry : type->entries) {
69 std::cout << " " << entry->name;
70 if (entry->entryId != ResourceEntry::kUnsetEntryId) {
71 std::cout << " [" << entry->entryId << "]";
72 }
73 std::cout << " (" << entry->values.size() << " configurations)";
74 if (entry->publicStatus.isPublic) {
75 std::cout << " PUBLIC";
76 }
77 std::cout << std::endl;
78 for (const auto& value : entry->values) {
79 std::cout << " " << value.config << " (" << value.source << ") : ";
80 value.value->print(std::cout);
81 std::cout << std::endl;
82 }
83 }
84 }
85}
86
87void printStringPool(const StringPool& pool) {
88 std::cout << "String pool of length " << pool.size() << std::endl
89 << "---------------------------------------------------------" << std::endl;
90
91 size_t i = 0;
92 for (const auto& entry : pool) {
93 std::cout << "[" << i << "]: "
94 << entry->value
95 << " (Priority " << entry->context.priority
96 << ", Config '" << entry->context.config << "')"
97 << std::endl;
98 i++;
99 }
100}
101
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800102/**
103 * Collect files from 'root', filtering out any files that do not
104 * match the FileFilter 'filter'.
105 */
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700106bool walkTree(const Source& root, const FileFilter& filter,
107 std::vector<Source>* outEntries) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800108 bool error = false;
109
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700110 for (const std::string& dirName : listFiles(root.path)) {
111 std::string dir = root.path;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800112 appendPath(&dir, dirName);
113
114 FileType ft = getFileType(dir);
115 if (!filter(dirName, ft)) {
116 continue;
117 }
118
119 if (ft != FileType::kDirectory) {
120 continue;
121 }
122
123 for (const std::string& fileName : listFiles(dir)) {
124 std::string file(dir);
125 appendPath(&file, fileName);
126
127 FileType ft = getFileType(file);
128 if (!filter(fileName, ft)) {
129 continue;
130 }
131
132 if (ft != FileType::kRegular) {
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700133 Logger::error(Source{ file }) << "not a regular file." << std::endl;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800134 error = true;
135 continue;
136 }
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700137 outEntries->push_back(Source{ file });
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800138 }
139 }
140 return !error;
141}
142
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800143bool loadResTable(android::ResTable* table, const Source& source) {
144 std::ifstream ifs(source.path, std::ifstream::in | std::ifstream::binary);
145 if (!ifs) {
146 Logger::error(source) << strerror(errno) << std::endl;
147 return false;
148 }
149
150 std::streampos fsize = ifs.tellg();
151 ifs.seekg(0, std::ios::end);
152 fsize = ifs.tellg() - fsize;
153 ifs.seekg(0, std::ios::beg);
154
155 assert(fsize >= 0);
156 size_t dataSize = static_cast<size_t>(fsize);
157 char* buf = new char[dataSize];
158 ifs.read(buf, dataSize);
159
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700160 bool result = table->add(buf, dataSize, -1, true) == android::NO_ERROR;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800161
162 delete [] buf;
163 return result;
164}
165
Adam Lesinski769de982015-04-10 19:43:55 -0700166void versionStylesForCompat(const std::shared_ptr<ResourceTable>& table) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800167 for (auto& type : *table) {
168 if (type->type != ResourceType::kStyle) {
169 continue;
170 }
171
172 for (auto& entry : type->entries) {
173 // Add the versioned styles we want to create
174 // here. They are added to the table after
175 // iterating over the original set of styles.
176 //
177 // A stack is used since auto-generated styles
178 // from later versions should override
179 // auto-generated styles from earlier versions.
180 // Iterating over the styles is done in order,
181 // so we will always visit sdkVersions from smallest
182 // to largest.
183 std::stack<ResourceConfigValue> addStack;
184
185 for (ResourceConfigValue& configValue : entry->values) {
186 visitFunc<Style>(*configValue.value, [&](Style& style) {
187 // Collect which entries we've stripped and the smallest
188 // SDK level which was stripped.
189 size_t minSdkStripped = std::numeric_limits<size_t>::max();
190 std::vector<Style::Entry> stripped;
191
192 // Iterate over the style's entries and erase/record the
193 // attributes whose SDK level exceeds the config's sdkVersion.
194 auto iter = style.entries.begin();
195 while (iter != style.entries.end()) {
196 if (iter->key.name.package == u"android") {
197 size_t sdkLevel = findAttributeSdkLevel(iter->key.name.entry);
198 if (sdkLevel > 1 && sdkLevel > configValue.config.sdkVersion) {
199 // Record that we are about to strip this.
200 stripped.emplace_back(std::move(*iter));
201 minSdkStripped = std::min(minSdkStripped, sdkLevel);
202
203 // Erase this from this style.
204 iter = style.entries.erase(iter);
205 continue;
206 }
207 }
208 ++iter;
209 }
210
211 if (!stripped.empty()) {
212 // We have stripped attributes, so let's create a new style to hold them.
213 ConfigDescription versionConfig(configValue.config);
214 versionConfig.sdkVersion = minSdkStripped;
215
216 ResourceConfigValue value = {
217 versionConfig,
218 configValue.source,
219 {},
220
221 // Create a copy of the original style.
Adam Lesinski769de982015-04-10 19:43:55 -0700222 std::unique_ptr<Value>(configValue.value->clone(
223 &table->getValueStringPool()))
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800224 };
225
226 Style& newStyle = static_cast<Style&>(*value.value);
Adam Lesinski769de982015-04-10 19:43:55 -0700227 newStyle.weak = true;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800228
229 // Move the recorded stripped attributes into this new style.
230 std::move(stripped.begin(), stripped.end(),
231 std::back_inserter(newStyle.entries));
232
233 // We will add this style to the table later. If we do it now, we will
234 // mess up iteration.
235 addStack.push(std::move(value));
236 }
237 });
238 }
239
240 auto comparator =
241 [](const ResourceConfigValue& lhs, const ConfigDescription& rhs) -> bool {
242 return lhs.config < rhs;
243 };
244
245 while (!addStack.empty()) {
246 ResourceConfigValue& value = addStack.top();
247 auto iter = std::lower_bound(entry->values.begin(), entry->values.end(),
248 value.config, comparator);
249 if (iter == entry->values.end() || iter->config != value.config) {
250 entry->values.insert(iter, std::move(value));
251 }
252 addStack.pop();
253 }
254 }
255 }
256}
257
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700258struct CompileItem {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800259 Source source;
260 ResourceName name;
261 ConfigDescription config;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700262 std::string extension;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800263};
264
Adam Lesinski769de982015-04-10 19:43:55 -0700265struct LinkItem {
266 Source source;
267 std::string apkPath;
268};
269
270std::string buildFileReference(const CompileItem& item) {
271 std::stringstream path;
272 path << "res/" << item.name.type;
273 if (item.config != ConfigDescription{}) {
274 path << "-" << item.config;
275 }
276 path << "/" << util::utf16ToUtf8(item.name.entry) + "." + item.extension;
277 return path.str();
278}
279
280bool addFileReference(const std::shared_ptr<ResourceTable>& table, const CompileItem& item) {
281 StringPool& pool = table->getValueStringPool();
282 StringPool::Ref ref = pool.makeRef(util::utf8ToUtf16(buildFileReference(item)));
283 return table->addResource(item.name, item.config, item.source.line(0),
284 util::make_unique<FileReference>(ref));
285}
286
287struct AaptOptions {
288 enum class Phase {
289 Link,
290 Compile,
291 };
292
293 // The phase to process.
294 Phase phase;
295
296 // Details about the app.
297 AppInfo appInfo;
298
299 // The location of the manifest file.
300 Source manifest;
301
302 // The APK files to link.
303 std::vector<Source> input;
304
305 // The libraries these files may reference.
306 std::vector<Source> libraries;
307
308 // Output path. This can be a directory or file
309 // depending on the phase.
310 Source output;
311
312 // Directory in which to write binding xml files.
313 Source bindingOutput;
314
315 // Directory to in which to generate R.java.
316 Maybe<Source> generateJavaClass;
317
318 // Whether to output verbose details about
319 // compilation.
320 bool verbose = false;
321};
322
323bool compileXml(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table,
324 const CompileItem& item, std::queue<CompileItem>* outQueue, ZipFile* outApk) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800325 std::ifstream in(item.source.path, std::ifstream::binary);
326 if (!in) {
327 Logger::error(item.source) << strerror(errno) << std::endl;
328 return false;
329 }
330
Adam Lesinski4d3a9872015-04-09 19:53:22 -0700331 BigBuffer outBuffer(1024);
Adam Lesinski769de982015-04-10 19:43:55 -0700332
333 // No resolver, since we are not compiling attributes here.
334 XmlFlattener flattener(table, {});
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800335
336 // We strip attributes that do not belong in this version of the resource.
337 // Non-version qualified resources have an implicit version 1 requirement.
Adam Lesinski769de982015-04-10 19:43:55 -0700338 XmlFlattener::Options xmlOptions;
339 xmlOptions.maxSdkAttribute = item.config.sdkVersion ? item.config.sdkVersion : 1;
340
341 std::shared_ptr<BindingXmlPullParser> binding;
342 std::shared_ptr<XmlPullParser> parser = std::make_shared<SourceXmlPullParser>(in);
343 if (item.name.type == ResourceType::kLayout) {
344 // Layouts may have defined bindings, so we need to make sure they get processed.
345 binding = std::make_shared<BindingXmlPullParser>(parser);
346 parser = binding;
347 }
348
349 Maybe<size_t> minStrippedSdk = flattener.flatten(item.source, parser, &outBuffer, xmlOptions);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800350 if (!minStrippedSdk) {
351 return false;
352 }
353
354 if (minStrippedSdk.value() > 0) {
355 // Something was stripped, so let's generate a new file
356 // with the version of the smallest SDK version stripped.
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700357 CompileItem newWork = item;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800358 newWork.config.sdkVersion = minStrippedSdk.value();
Adam Lesinski769de982015-04-10 19:43:55 -0700359 outQueue->push(newWork);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800360 }
361
Adam Lesinski769de982015-04-10 19:43:55 -0700362 // Write the resulting compiled XML file to the output APK.
363 if (outApk->add(outBuffer, buildFileReference(item).data(), ZipEntry::kCompressStored,
364 nullptr) != android::NO_ERROR) {
365 Logger::error(options.output) << "failed to write compiled '" << item.source << "' to apk."
366 << std::endl;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800367 return false;
368 }
369
Adam Lesinski769de982015-04-10 19:43:55 -0700370 if (binding && !options.bindingOutput.path.empty()) {
371 // We generated a binding xml file, write it out.
372 Source bindingOutput = options.bindingOutput;
373 appendPath(&bindingOutput.path, buildFileReference(item));
Adam Lesinski4d3a9872015-04-09 19:53:22 -0700374
Adam Lesinski769de982015-04-10 19:43:55 -0700375 if (!mkdirs(bindingOutput.path)) {
376 Logger::error(bindingOutput) << strerror(errno) << std::endl;
377 return false;
378 }
379
380 appendPath(&bindingOutput.path, "bind.xml");
381
Adam Lesinski4d3a9872015-04-09 19:53:22 -0700382 std::ofstream bout(bindingOutput.path);
383 if (!bout) {
384 Logger::error(bindingOutput) << strerror(errno) << std::endl;
385 return false;
386 }
387
388 if (!binding->writeToFile(bout)) {
389 Logger::error(bindingOutput) << strerror(errno) << std::endl;
390 return false;
391 }
392 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800393 return true;
394}
395
Adam Lesinski769de982015-04-10 19:43:55 -0700396bool linkXml(const AaptOptions& options, const std::shared_ptr<Resolver>& resolver,
397 const LinkItem& item, const void* data, size_t dataLen, ZipFile* outApk) {
398 std::shared_ptr<android::ResXMLTree> tree = std::make_shared<android::ResXMLTree>();
399 if (tree->setTo(data, dataLen, false) != android::NO_ERROR) {
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700400 return false;
401 }
402
Adam Lesinski769de982015-04-10 19:43:55 -0700403 std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<BinaryXmlPullParser>(tree);
404
405 BigBuffer outBuffer(1024);
406 XmlFlattener flattener({}, resolver);
407 if (!flattener.flatten(item.source, xmlParser, &outBuffer, {})) {
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700408 return false;
409 }
410
Adam Lesinski769de982015-04-10 19:43:55 -0700411 if (outApk->add(outBuffer, item.apkPath.data(), ZipEntry::kCompressDeflated, nullptr) !=
412 android::NO_ERROR) {
413 Logger::error(options.output) << "failed to write linked file '" << item.source
414 << "' to apk." << std::endl;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700415 return false;
416 }
417 return true;
418}
419
Adam Lesinski769de982015-04-10 19:43:55 -0700420bool compilePng(const AaptOptions& options, const CompileItem& item, ZipFile* outApk) {
421 std::ifstream in(item.source.path, std::ifstream::binary);
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700422 if (!in) {
Adam Lesinski769de982015-04-10 19:43:55 -0700423 Logger::error(item.source) << strerror(errno) << std::endl;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700424 return false;
425 }
426
Adam Lesinski769de982015-04-10 19:43:55 -0700427 BigBuffer outBuffer(4096);
428 std::string err;
429 Png png;
430 if (!png.process(item.source, in, &outBuffer, {}, &err)) {
431 Logger::error(item.source) << err << std::endl;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700432 return false;
433 }
434
Adam Lesinski769de982015-04-10 19:43:55 -0700435 if (outApk->add(outBuffer, buildFileReference(item).data(), ZipEntry::kCompressStored,
436 nullptr) != android::NO_ERROR) {
437 Logger::error(options.output) << "failed to write compiled '" << item.source
438 << "' to apk." << std::endl;
439 return false;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700440 }
Adam Lesinski769de982015-04-10 19:43:55 -0700441 return true;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700442}
443
Adam Lesinski769de982015-04-10 19:43:55 -0700444bool copyFile(const AaptOptions& options, const CompileItem& item, ZipFile* outApk) {
445 if (outApk->add(item.source.path.data(), buildFileReference(item).data(),
446 ZipEntry::kCompressStored, nullptr) != android::NO_ERROR) {
447 Logger::error(options.output) << "failed to copy file '" << item.source << "' to apk."
448 << std::endl;
449 return false;
450 }
451 return true;
452}
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800453
Adam Lesinski769de982015-04-10 19:43:55 -0700454bool compileManifest(const AaptOptions& options, const std::shared_ptr<Resolver>& resolver,
455 ZipFile* outApk) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800456 if (options.verbose) {
Adam Lesinski769de982015-04-10 19:43:55 -0700457 Logger::note(options.manifest) << "compiling AndroidManifest.xml." << std::endl;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800458 }
459
460 std::ifstream in(options.manifest.path, std::ifstream::binary);
461 if (!in) {
462 Logger::error(options.manifest) << strerror(errno) << std::endl;
463 return false;
464 }
465
466 BigBuffer outBuffer(1024);
467 std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<SourceXmlPullParser>(in);
Adam Lesinski769de982015-04-10 19:43:55 -0700468 XmlFlattener flattener({}, resolver);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800469
Adam Lesinski769de982015-04-10 19:43:55 -0700470 if (!flattener.flatten(options.manifest, xmlParser, &outBuffer, {})) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800471 return false;
472 }
473
Adam Lesinski769de982015-04-10 19:43:55 -0700474 std::unique_ptr<uint8_t[]> data = util::copy(outBuffer);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800475
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700476 android::ResXMLTree tree;
Adam Lesinski769de982015-04-10 19:43:55 -0700477 if (tree.setTo(data.get(), outBuffer.size(), false) != android::NO_ERROR) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800478 return false;
479 }
480
481 ManifestValidator validator(resolver->getResTable());
482 if (!validator.validate(options.manifest, &tree)) {
483 return false;
484 }
485
Adam Lesinski769de982015-04-10 19:43:55 -0700486 if (outApk->add(data.get(), outBuffer.size(), "AndroidManifest.xml",
487 ZipEntry::kCompressStored, nullptr) != android::NO_ERROR) {
488 Logger::error(options.output) << "failed to write 'AndroidManifest.xml' to apk."
489 << std::endl;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800490 return false;
491 }
492 return true;
493}
494
495bool loadAppInfo(const Source& source, AppInfo* outInfo) {
496 std::ifstream ifs(source.path, std::ifstream::in | std::ifstream::binary);
497 if (!ifs) {
498 Logger::error(source) << strerror(errno) << std::endl;
499 return false;
500 }
501
502 ManifestParser parser;
503 std::shared_ptr<XmlPullParser> pullParser = std::make_shared<SourceXmlPullParser>(ifs);
504 return parser.parse(source, pullParser, outInfo);
505}
506
Adam Lesinski769de982015-04-10 19:43:55 -0700507static void printCommandsAndDie() {
508 std::cerr << "The following commands are supported:" << std::endl << std::endl;
509 std::cerr << "compile compiles a subset of resources" << std::endl;
510 std::cerr << "link links together compiled resources and libraries" << std::endl;
511 std::cerr << std::endl;
512 std::cerr << "run aapt2 with one of the commands and the -h flag for extra details."
513 << std::endl;
514 exit(1);
515}
516
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700517static AaptOptions prepareArgs(int argc, char** argv) {
518 if (argc < 2) {
Adam Lesinski769de982015-04-10 19:43:55 -0700519 std::cerr << "no command specified." << std::endl << std::endl;
520 printCommandsAndDie();
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800521 }
522
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700523 const StringPiece command(argv[1]);
524 argc -= 2;
525 argv += 2;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800526
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700527 AaptOptions options;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800528
Adam Lesinski769de982015-04-10 19:43:55 -0700529 if (command == "link") {
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700530 options.phase = AaptOptions::Phase::Link;
531 } else if (command == "compile") {
532 options.phase = AaptOptions::Phase::Compile;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800533 } else {
Adam Lesinski769de982015-04-10 19:43:55 -0700534 std::cerr << "invalid command '" << command << "'." << std::endl << std::endl;
535 printCommandsAndDie();
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800536 }
537
Adam Lesinski769de982015-04-10 19:43:55 -0700538 if (options.phase == AaptOptions::Phase::Compile) {
539 flag::requiredFlag("--package", "Android package name",
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700540 [&options](const StringPiece& arg) {
Adam Lesinski769de982015-04-10 19:43:55 -0700541 options.appInfo.package = util::utf8ToUtf16(arg);
542 });
543 flag::optionalFlag("--binding", "Output directory for binding XML files",
544 [&options](const StringPiece& arg) {
545 options.bindingOutput = Source{ arg.toString() };
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700546 });
547
Adam Lesinski769de982015-04-10 19:43:55 -0700548 } else if (options.phase == AaptOptions::Phase::Link) {
549 flag::requiredFlag("--manifest", "AndroidManifest.xml of your app",
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700550 [&options](const StringPiece& arg) {
551 options.manifest = Source{ arg.toString() };
552 });
553
554 flag::optionalFlag("-I", "add an Android APK to link against",
555 [&options](const StringPiece& arg) {
556 options.libraries.push_back(Source{ arg.toString() });
557 });
558
559 flag::optionalFlag("--java", "directory in which to generate R.java",
560 [&options](const StringPiece& arg) {
561 options.generateJavaClass = Source{ arg.toString() };
562 });
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800563 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800564
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700565 // Common flags for all steps.
Adam Lesinski769de982015-04-10 19:43:55 -0700566 flag::requiredFlag("-o", "Output path", [&options](const StringPiece& arg) {
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700567 options.output = Source{ arg.toString() };
568 });
Adam Lesinski769de982015-04-10 19:43:55 -0700569
570 bool help = false;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700571 flag::optionalSwitch("-v", "enables verbose logging", &options.verbose);
Adam Lesinski769de982015-04-10 19:43:55 -0700572 flag::optionalSwitch("-h", "displays this help menu", &help);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800573
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700574 // Build the command string for output (eg. "aapt2 compile").
575 std::string fullCommand = "aapt2";
576 fullCommand += " ";
577 fullCommand += command.toString();
578
579 // Actually read the command line flags.
580 flag::parse(argc, argv, fullCommand);
581
Adam Lesinski769de982015-04-10 19:43:55 -0700582 if (help) {
583 flag::usageAndDie(fullCommand);
584 }
585
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700586 // Copy all the remaining arguments.
Adam Lesinski769de982015-04-10 19:43:55 -0700587 for (const std::string& arg : flag::getArgs()) {
588 options.input.push_back(Source{ arg });
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800589 }
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700590 return options;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800591}
592
Adam Lesinski769de982015-04-10 19:43:55 -0700593static bool compileValues(const std::shared_ptr<ResourceTable>& table, const Source& source,
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700594 const ConfigDescription& config) {
595 std::ifstream in(source.path, std::ifstream::binary);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800596 if (!in) {
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700597 Logger::error(source) << strerror(errno) << std::endl;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800598 return false;
599 }
600
601 std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<SourceXmlPullParser>(in);
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700602 ResourceParser parser(table, source, config, xmlParser);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800603 return parser.parse();
604}
605
606struct ResourcePathData {
607 std::u16string resourceDir;
608 std::u16string name;
609 std::string extension;
610 ConfigDescription config;
611};
612
613/**
614 * Resource file paths are expected to look like:
615 * [--/res/]type[-config]/name
616 */
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700617static Maybe<ResourcePathData> extractResourcePathData(const Source& source) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800618 std::vector<std::string> parts = util::splitAndLowercase(source.path, '/');
619 if (parts.size() < 2) {
620 Logger::error(source) << "bad resource path." << std::endl;
621 return {};
622 }
623
624 std::string& dir = parts[parts.size() - 2];
625 StringPiece dirStr = dir;
626
627 ConfigDescription config;
628 size_t dashPos = dir.find('-');
629 if (dashPos != std::string::npos) {
630 StringPiece configStr = dirStr.substr(dashPos + 1, dir.size() - (dashPos + 1));
631 if (!ConfigDescription::parse(configStr, &config)) {
632 Logger::error(source)
633 << "invalid configuration '"
634 << configStr
635 << "'."
636 << std::endl;
637 return {};
638 }
639 dirStr = dirStr.substr(0, dashPos);
640 }
641
642 std::string& filename = parts[parts.size() - 1];
643 StringPiece name = filename;
644 StringPiece extension;
645 size_t dotPos = filename.find('.');
646 if (dotPos != std::string::npos) {
647 extension = name.substr(dotPos + 1, filename.size() - (dotPos + 1));
648 name = name.substr(0, dotPos);
649 }
650
651 return ResourcePathData{
652 util::utf8ToUtf16(dirStr),
653 util::utf8ToUtf16(name),
654 extension.toString(),
655 config
656 };
657}
658
Adam Lesinski769de982015-04-10 19:43:55 -0700659bool writeResourceTable(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table,
660 const TableFlattener::Options& flattenerOptions, ZipFile* outApk) {
661 if (table->begin() != table->end()) {
662 BigBuffer buffer(1024);
663 TableFlattener flattener(flattenerOptions);
664 if (!flattener.flatten(&buffer, *table)) {
665 Logger::error() << "failed to flatten resource table." << std::endl;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700666 return false;
667 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800668
Adam Lesinski769de982015-04-10 19:43:55 -0700669 if (options.verbose) {
670 Logger::note() << "Final resource table size=" << util::formatSize(buffer.size())
671 << std::endl;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700672 }
673
Adam Lesinski769de982015-04-10 19:43:55 -0700674 if (outApk->add(buffer, "resources.arsc", ZipEntry::kCompressStored, nullptr) !=
675 android::NO_ERROR) {
676 Logger::note(options.output) << "failed to store resource table." << std::endl;
677 return false;
678 }
679 }
680 return true;
681}
682
683static constexpr int kOpenFlags = ZipFile::kOpenCreate | ZipFile::kOpenTruncate |
684 ZipFile::kOpenReadWrite;
685
686bool link(const AaptOptions& options, const std::shared_ptr<ResourceTable>& outTable,
687 const std::shared_ptr<Resolver>& resolver) {
688 std::map<std::shared_ptr<ResourceTable>, std::unique_ptr<ZipFile>> apkFiles;
689 std::unordered_set<std::u16string> linkedPackages;
690
691 // Populate the linkedPackages with our own.
692 linkedPackages.insert(options.appInfo.package);
693
694 // Load all APK files.
695 for (const Source& source : options.input) {
696 std::unique_ptr<ZipFile> zipFile = util::make_unique<ZipFile>();
697 if (zipFile->open(source.path.data(), ZipFile::kOpenReadOnly) != android::NO_ERROR) {
698 Logger::error(source) << "failed to open: " << strerror(errno) << std::endl;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700699 return false;
700 }
701
Adam Lesinski769de982015-04-10 19:43:55 -0700702 std::shared_ptr<ResourceTable> table = std::make_shared<ResourceTable>();
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700703
Adam Lesinski769de982015-04-10 19:43:55 -0700704 ZipEntry* entry = zipFile->getEntryByName("resources.arsc");
705 if (!entry) {
706 Logger::error(source) << "missing 'resources.arsc'." << std::endl;
707 return false;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700708 }
709
Adam Lesinski769de982015-04-10 19:43:55 -0700710 void* uncompressedData = zipFile->uncompress(entry);
711 assert(uncompressedData);
712
713 BinaryResourceParser parser(table, resolver, source, uncompressedData,
714 entry->getUncompressedLen());
715 if (!parser.parse()) {
716 free(uncompressedData);
717 return false;
718 }
719 free(uncompressedData);
720
721 // Keep track of where this table came from.
722 apkFiles[table] = std::move(zipFile);
723
724 // Add the package to the set of linked packages.
725 linkedPackages.insert(table->getPackage());
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700726 }
727
Adam Lesinski769de982015-04-10 19:43:55 -0700728 for (auto& p : apkFiles) {
729 const std::shared_ptr<ResourceTable>& inTable = p.first;
730
731 if (!outTable->merge(std::move(*inTable))) {
732 return false;
733 }
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700734 }
735
Adam Lesinski769de982015-04-10 19:43:55 -0700736 {
737 // Now that everything is merged, let's link it.
738 Linker linker(outTable, resolver);
739 if (!linker.linkAndValidate()) {
740 return false;
741 }
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700742
Adam Lesinski769de982015-04-10 19:43:55 -0700743 // Verify that all symbols exist.
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700744 const auto& unresolvedRefs = linker.getUnresolvedReferences();
745 if (!unresolvedRefs.empty()) {
746 for (const auto& entry : unresolvedRefs) {
747 for (const auto& source : entry.second) {
748 Logger::error(source) << "unresolved symbol '" << entry.first << "'."
749 << std::endl;
750 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800751 }
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700752 return false;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800753 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800754 }
755
Adam Lesinski769de982015-04-10 19:43:55 -0700756 // Open the output APK file for writing.
757 ZipFile outApk;
758 if (outApk.open(options.output.path.data(), kOpenFlags) != android::NO_ERROR) {
759 Logger::error(options.output) << "failed to open: " << strerror(errno) << std::endl;
760 return false;
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700761 }
762
Adam Lesinski769de982015-04-10 19:43:55 -0700763 if (!compileManifest(options, resolver, &outApk)) {
764 return false;
765 }
766
767 for (auto& p : apkFiles) {
768 std::unique_ptr<ZipFile>& zipFile = p.second;
769
770 // TODO(adamlesinski): Get list of files to read when processing config filter.
771
772 const int numEntries = zipFile->getNumEntries();
773 for (int i = 0; i < numEntries; i++) {
774 ZipEntry* entry = zipFile->getEntryByIndex(i);
775 assert(entry);
776
777 StringPiece filename = entry->getFileName();
778 if (!util::stringStartsWith<char>(filename, "res/")) {
779 continue;
780 }
781
782 if (util::stringEndsWith<char>(filename, ".xml")) {
783 void* uncompressedData = zipFile->uncompress(entry);
784 assert(uncompressedData);
785
786 LinkItem item = { Source{ filename.toString() }, filename.toString() };
787
788 if (!linkXml(options, resolver, item, uncompressedData,
789 entry->getUncompressedLen(), &outApk)) {
790 Logger::error(options.output) << "failed to link '" << filename << "'."
791 << std::endl;
792 return false;
793 }
794 } else {
795 if (outApk.add(zipFile.get(), entry, 0, nullptr) != android::NO_ERROR) {
796 Logger::error(options.output) << "failed to copy '" << filename << "'."
797 << std::endl;
798 return false;
799 }
800 }
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700801 }
802 }
803
804 // Generate the Java class file.
Adam Lesinski769de982015-04-10 19:43:55 -0700805 if (options.generateJavaClass) {
806 JavaClassGenerator generator(outTable, {});
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700807
Adam Lesinski769de982015-04-10 19:43:55 -0700808 for (const std::u16string& package : linkedPackages) {
809 Source outPath = options.generateJavaClass.value();
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800810
Adam Lesinski769de982015-04-10 19:43:55 -0700811 // Build the output directory from the package name.
812 // Eg. com.android.app -> com/android/app
813 const std::string packageUtf8 = util::utf16ToUtf8(package);
814 for (StringPiece part : util::tokenize<char>(packageUtf8, '.')) {
815 appendPath(&outPath.path, part);
816 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800817
Adam Lesinski769de982015-04-10 19:43:55 -0700818 if (!mkdirs(outPath.path)) {
819 Logger::error(outPath) << strerror(errno) << std::endl;
820 return false;
821 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800822
Adam Lesinski769de982015-04-10 19:43:55 -0700823 appendPath(&outPath.path, "R.java");
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800824
Adam Lesinski769de982015-04-10 19:43:55 -0700825 if (options.verbose) {
826 Logger::note(outPath) << "writing Java symbols." << std::endl;
827 }
828
829 std::ofstream fout(outPath.path);
830 if (!fout) {
831 Logger::error(outPath) << strerror(errno) << std::endl;
832 return false;
833 }
834
835 if (!generator.generate(package, fout)) {
836 Logger::error(outPath) << generator.getError() << "." << std::endl;
837 return false;
838 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800839 }
840 }
841
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700842 // Flatten the resource table.
Adam Lesinski769de982015-04-10 19:43:55 -0700843 TableFlattener::Options flattenerOptions;
844 flattenerOptions.useExtendedChunks = false;
845 if (!writeResourceTable(options, outTable, flattenerOptions, &outApk)) {
846 return false;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800847 }
Adam Lesinski769de982015-04-10 19:43:55 -0700848
849 outApk.flush();
850 return true;
851}
852
853bool compile(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table,
854 const std::shared_ptr<Resolver>& resolver) {
855 std::queue<CompileItem> compileQueue;
856 bool error = false;
857
858 // Compile all the resource files passed in on the command line.
859 for (const Source& source : options.input) {
860 // Need to parse the resource type/config/filename.
861 Maybe<ResourcePathData> maybePathData = extractResourcePathData(source);
862 if (!maybePathData) {
863 return false;
864 }
865
866 const ResourcePathData& pathData = maybePathData.value();
867 if (pathData.resourceDir == u"values") {
868 // The file is in the values directory, which means its contents will
869 // go into the resource table.
870 if (options.verbose) {
871 Logger::note(source) << "compiling values." << std::endl;
872 }
873
874 error |= !compileValues(table, source, pathData.config);
875 } else {
876 // The file is in a directory like 'layout' or 'drawable'. Find out
877 // the type.
878 const ResourceType* type = parseResourceType(pathData.resourceDir);
879 if (!type) {
880 Logger::error(source) << "invalid resource type '" << pathData.resourceDir << "'."
881 << std::endl;
882 return false;
883 }
884
885 compileQueue.push(CompileItem{
886 source,
887 ResourceName{ table->getPackage(), *type, pathData.name },
888 pathData.config,
889 pathData.extension
890 });
891 }
892 }
893
894 if (error) {
895 return false;
896 }
897
898 // Version all styles referencing attributes outside of their specified SDK version.
899 versionStylesForCompat(table);
900
901 // Open the output APK file for writing.
902 ZipFile outApk;
903 if (outApk.open(options.output.path.data(), kOpenFlags) != android::NO_ERROR) {
904 Logger::error(options.output) << "failed to open: " << strerror(errno) << std::endl;
905 return false;
906 }
907
908 // Compile each file.
909 for (; !compileQueue.empty(); compileQueue.pop()) {
910 const CompileItem& item = compileQueue.front();
911
912 // Add the file name to the resource table.
913 error |= !addFileReference(table, item);
914
915 if (item.extension == "xml") {
916 error |= !compileXml(options, table, item, &compileQueue, &outApk);
917 } else if (item.extension == "png" || item.extension == "9.png") {
918 error |= !compilePng(options, item, &outApk);
919 } else {
920 error |= !copyFile(options, item, &outApk);
921 }
922 }
923
924 if (error) {
925 return false;
926 }
927
928 // Link and assign resource IDs.
929 Linker linker(table, resolver);
930 if (!linker.linkAndValidate()) {
931 return false;
932 }
933
934 // Flatten the resource table.
935 if (!writeResourceTable(options, table, {}, &outApk)) {
936 return false;
937 }
938
939 outApk.flush();
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800940 return true;
941}
942
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800943int main(int argc, char** argv) {
944 Logger::setLog(std::make_shared<Log>(std::cerr, std::cerr));
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700945 AaptOptions options = prepareArgs(argc, argv);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800946
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700947 // If we specified a manifest, go ahead and load the package name from the manifest.
948 if (!options.manifest.path.empty()) {
949 if (!loadAppInfo(options.manifest, &options.appInfo)) {
950 return false;
951 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800952 }
953
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800954 // Verify we have some common options set.
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800955 if (options.appInfo.package.empty()) {
956 Logger::error() << "no package name specified." << std::endl;
957 return false;
958 }
959
Adam Lesinski98aa3ad2015-04-06 11:46:52 -0700960 // Every phase needs a resource table.
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800961 std::shared_ptr<ResourceTable> table = std::make_shared<ResourceTable>();
962 table->setPackage(options.appInfo.package);
963 if (options.appInfo.package == u"android") {
964 table->setPackageId(0x01);
965 } else {
966 table->setPackageId(0x7f);
967 }
968
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800969 // Load the included libraries.
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800970 std::shared_ptr<android::AssetManager> libraries = std::make_shared<android::AssetManager>();
971 for (const Source& source : options.libraries) {
Adam Lesinski4d3a9872015-04-09 19:53:22 -0700972 if (util::stringEndsWith<char>(source.path, ".arsc")) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800973 // We'll process these last so as to avoid a cookie issue.
974 continue;
975 }
976
977 int32_t cookie;
978 if (!libraries->addAssetPath(android::String8(source.path.data()), &cookie)) {
979 Logger::error(source) << "failed to load library." << std::endl;
980 return false;
981 }
982 }
983
984 for (const Source& source : options.libraries) {
Adam Lesinski4d3a9872015-04-09 19:53:22 -0700985 if (!util::stringEndsWith<char>(source.path, ".arsc")) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800986 // We've already processed this.
987 continue;
988 }
989
990 // Dirty hack but there is no other way to get a
991 // writeable ResTable.
992 if (!loadResTable(const_cast<android::ResTable*>(&libraries->getResources(false)),
993 source)) {
994 return false;
995 }
996 }
997
998 // Make the resolver that will cache IDs for us.
999 std::shared_ptr<Resolver> resolver = std::make_shared<Resolver>(table, libraries);
1000
Adam Lesinski769de982015-04-10 19:43:55 -07001001 if (options.phase == AaptOptions::Phase::Compile) {
1002 if (!compile(options, table, resolver)) {
1003 Logger::error() << "aapt exiting with failures." << std::endl;
1004 return 1;
1005 }
1006 } else if (options.phase == AaptOptions::Phase::Link) {
1007 if (!link(options, table, resolver)) {
1008 Logger::error() << "aapt exiting with failures." << std::endl;
1009 return 1;
1010 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001011 }
1012 return 0;
1013}