blob: 90e35d52788c4a53abd827173cf1efc8b4ae8a48 [file] [log] [blame]
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001/*
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 "ConfigDescription.h"
18#include "Diagnostics.h"
19#include "Flags.h"
20#include "ResourceParser.h"
21#include "ResourceTable.h"
Adam Lesinski1ab598f2015-08-14 14:26:04 -070022#include "compile/IdAssigner.h"
23#include "compile/Png.h"
24#include "compile/XmlIdCollector.h"
Adam Lesinskia40e9722015-11-24 19:11:46 -080025#include "flatten/Archive.h"
Adam Lesinski1ab598f2015-08-14 14:26:04 -070026#include "flatten/FileExportWriter.h"
27#include "flatten/TableFlattener.h"
28#include "flatten/XmlFlattener.h"
29#include "util/Files.h"
30#include "util/Maybe.h"
31#include "util/Util.h"
Adam Lesinski467f1712015-11-16 17:35:44 -080032#include "xml/XmlDom.h"
33#include "xml/XmlPullParser.h"
Adam Lesinski1ab598f2015-08-14 14:26:04 -070034
Adam Lesinskia40e9722015-11-24 19:11:46 -080035#include <dirent.h>
Adam Lesinski1ab598f2015-08-14 14:26:04 -070036#include <fstream>
37#include <string>
38
39namespace aapt {
40
41struct ResourcePathData {
42 Source source;
43 std::u16string resourceDir;
44 std::u16string name;
45 std::string extension;
46
47 // Original config str. We keep this because when we parse the config, we may add on
48 // version qualifiers. We want to preserve the original input so the output is easily
49 // computed before hand.
50 std::string configStr;
51 ConfigDescription config;
52};
53
54/**
55 * Resource file paths are expected to look like:
56 * [--/res/]type[-config]/name
57 */
58static Maybe<ResourcePathData> extractResourcePathData(const std::string& path,
59 std::string* outError) {
60 std::vector<std::string> parts = util::split(path, file::sDirSep);
61 if (parts.size() < 2) {
62 if (outError) *outError = "bad resource path";
63 return {};
64 }
65
66 std::string& dir = parts[parts.size() - 2];
67 StringPiece dirStr = dir;
68
69 StringPiece configStr;
70 ConfigDescription config;
71 size_t dashPos = dir.find('-');
72 if (dashPos != std::string::npos) {
73 configStr = dirStr.substr(dashPos + 1, dir.size() - (dashPos + 1));
74 if (!ConfigDescription::parse(configStr, &config)) {
75 if (outError) {
76 std::stringstream errStr;
77 errStr << "invalid configuration '" << configStr << "'";
78 *outError = errStr.str();
79 }
80 return {};
81 }
82 dirStr = dirStr.substr(0, dashPos);
83 }
84
85 std::string& filename = parts[parts.size() - 1];
86 StringPiece name = filename;
87 StringPiece extension;
88 size_t dotPos = filename.find('.');
89 if (dotPos != std::string::npos) {
90 extension = name.substr(dotPos + 1, filename.size() - (dotPos + 1));
91 name = name.substr(0, dotPos);
92 }
93
94 return ResourcePathData{
Adam Lesinskia40e9722015-11-24 19:11:46 -080095 Source(path),
Adam Lesinski1ab598f2015-08-14 14:26:04 -070096 util::utf8ToUtf16(dirStr),
97 util::utf8ToUtf16(name),
98 extension.toString(),
99 configStr.toString(),
100 config
101 };
102}
103
104struct CompileOptions {
105 std::string outputPath;
Adam Lesinskia40e9722015-11-24 19:11:46 -0800106 Maybe<std::string> resDir;
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700107 Maybe<std::u16string> product;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700108 bool verbose = false;
109};
110
Adam Lesinskia40e9722015-11-24 19:11:46 -0800111static std::string buildIntermediateFilename(const ResourcePathData& data) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700112 std::stringstream name;
113 name << data.resourceDir;
114 if (!data.configStr.empty()) {
115 name << "-" << data.configStr;
116 }
117 name << "_" << data.name << "." << data.extension << ".flat";
Adam Lesinskia40e9722015-11-24 19:11:46 -0800118 return name.str();
119}
120
121static bool isHidden(const StringPiece& filename) {
122 return util::stringStartsWith<char>(filename, ".");
123}
124
125/**
126 * Walks the res directory structure, looking for resource files.
127 */
128static bool loadInputFilesFromDir(IAaptContext* context, const CompileOptions& options,
129 std::vector<ResourcePathData>* outPathData) {
130 const std::string& rootDir = options.resDir.value();
131 std::unique_ptr<DIR, decltype(closedir)*> d(opendir(rootDir.data()), closedir);
132 if (!d) {
133 context->getDiagnostics()->error(DiagMessage() << strerror(errno));
134 return false;
135 }
136
137 while (struct dirent* entry = readdir(d.get())) {
138 if (isHidden(entry->d_name)) {
139 continue;
140 }
141
142 std::string prefixPath = rootDir;
143 file::appendPath(&prefixPath, entry->d_name);
144
145 if (file::getFileType(prefixPath) != file::FileType::kDirectory) {
146 continue;
147 }
148
149 std::unique_ptr<DIR, decltype(closedir)*> subDir(opendir(prefixPath.data()), closedir);
150 if (!subDir) {
151 context->getDiagnostics()->error(DiagMessage() << strerror(errno));
152 return false;
153 }
154
155 while (struct dirent* leafEntry = readdir(subDir.get())) {
156 if (isHidden(leafEntry->d_name)) {
157 continue;
158 }
159
160 std::string fullPath = prefixPath;
161 file::appendPath(&fullPath, leafEntry->d_name);
162
163 std::string errStr;
164 Maybe<ResourcePathData> pathData = extractResourcePathData(fullPath, &errStr);
165 if (!pathData) {
166 context->getDiagnostics()->error(DiagMessage() << errStr);
167 return false;
168 }
169
170 outPathData->push_back(std::move(pathData.value()));
171 }
172 }
173 return true;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700174}
175
176static bool compileTable(IAaptContext* context, const CompileOptions& options,
Adam Lesinskia40e9722015-11-24 19:11:46 -0800177 const ResourcePathData& pathData, IArchiveWriter* writer,
178 const std::string& outputPath) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700179 ResourceTable table;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700180 {
181 std::ifstream fin(pathData.source.path, std::ifstream::binary);
182 if (!fin) {
183 context->getDiagnostics()->error(DiagMessage(pathData.source) << strerror(errno));
184 return false;
185 }
186
187
188 // Parse the values file from XML.
Adam Lesinski467f1712015-11-16 17:35:44 -0800189 xml::XmlPullParser xmlParser(fin);
Adam Lesinski9f222042015-11-04 13:51:45 -0800190
191 ResourceParserOptions parserOptions;
192 parserOptions.product = options.product;
193
194 // If the filename includes donottranslate, then the default translatable is false.
195 parserOptions.translatable = pathData.name.find(u"donottranslate") == std::string::npos;
196
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700197 ResourceParser resParser(context->getDiagnostics(), &table, pathData.source,
Adam Lesinski9f222042015-11-04 13:51:45 -0800198 pathData.config, parserOptions);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700199 if (!resParser.parse(&xmlParser)) {
200 return false;
201 }
202
203 fin.close();
204 }
205
Adam Lesinski83f22552015-11-07 11:51:23 -0800206 // Ensure we have the compilation package at least.
207 table.createPackage(context->getCompilationPackage());
208
Adam Lesinskia40e9722015-11-24 19:11:46 -0800209 // Assign an ID to any package that has resources.
Adam Lesinski83f22552015-11-07 11:51:23 -0800210 for (auto& pkg : table.packages) {
211 if (!pkg->id) {
212 // If no package ID was set while parsing (public identifiers), auto assign an ID.
213 pkg->id = context->getPackageId();
214 }
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700215 }
216
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700217 // Assign IDs to prepare the table for flattening.
218 IdAssigner idAssigner;
219 if (!idAssigner.consume(context, &table)) {
220 return false;
221 }
222
223 // Flatten the table.
224 BigBuffer buffer(1024);
225 TableFlattenerOptions tableFlattenerOptions;
226 tableFlattenerOptions.useExtendedChunks = true;
227 TableFlattener flattener(&buffer, tableFlattenerOptions);
228 if (!flattener.consume(context, &table)) {
229 return false;
230 }
231
Adam Lesinskia40e9722015-11-24 19:11:46 -0800232 if (!writer->startEntry(outputPath, 0)) {
233 context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to open");
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700234 return false;
235 }
236
Adam Lesinskia40e9722015-11-24 19:11:46 -0800237 if (writer->writeEntry(buffer)) {
238 if (writer->finishEntry()) {
239 return true;
240 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700241 }
Adam Lesinskia40e9722015-11-24 19:11:46 -0800242
243 context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to write");
244 return false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700245}
246
247static bool compileXml(IAaptContext* context, const CompileOptions& options,
Adam Lesinskia40e9722015-11-24 19:11:46 -0800248 const ResourcePathData& pathData, IArchiveWriter* writer,
249 const std::string& outputPath) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700250
Adam Lesinski467f1712015-11-16 17:35:44 -0800251 std::unique_ptr<xml::XmlResource> xmlRes;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700252
253 {
254 std::ifstream fin(pathData.source.path, std::ifstream::binary);
255 if (!fin) {
256 context->getDiagnostics()->error(DiagMessage(pathData.source) << strerror(errno));
257 return false;
258 }
259
260 xmlRes = xml::inflate(&fin, context->getDiagnostics(), pathData.source);
261
262 fin.close();
263 }
264
265 if (!xmlRes) {
266 return false;
267 }
268
269 // Collect IDs that are defined here.
270 XmlIdCollector collector;
271 if (!collector.consume(context, xmlRes.get())) {
272 return false;
273 }
274
Adam Lesinskia40e9722015-11-24 19:11:46 -0800275 xmlRes->file.name = ResourceName({}, *parseResourceType(pathData.resourceDir), pathData.name);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700276 xmlRes->file.config = pathData.config;
277 xmlRes->file.source = pathData.source;
278
279 BigBuffer buffer(1024);
280 ChunkWriter fileExportWriter = wrapBufferWithFileExportHeader(&buffer, &xmlRes->file);
281
282 XmlFlattenerOptions xmlFlattenerOptions;
283 xmlFlattenerOptions.keepRawValues = true;
284 XmlFlattener flattener(fileExportWriter.getBuffer(), xmlFlattenerOptions);
285 if (!flattener.consume(context, xmlRes.get())) {
286 return false;
287 }
288
289 fileExportWriter.finish();
290
Adam Lesinskia40e9722015-11-24 19:11:46 -0800291 if (!writer->startEntry(outputPath, 0)) {
292 context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to open");
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700293 return false;
294 }
295
Adam Lesinskia40e9722015-11-24 19:11:46 -0800296 if (writer->writeEntry(buffer)) {
297 if (writer->finishEntry()) {
298 return true;
299 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700300 }
Adam Lesinskia40e9722015-11-24 19:11:46 -0800301
302 context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to write");
303 return false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700304}
305
306static bool compilePng(IAaptContext* context, const CompileOptions& options,
Adam Lesinskia40e9722015-11-24 19:11:46 -0800307 const ResourcePathData& pathData, IArchiveWriter* writer,
308 const std::string& outputPath) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700309 BigBuffer buffer(4096);
310 ResourceFile resFile;
Adam Lesinskia40e9722015-11-24 19:11:46 -0800311 resFile.name = ResourceName({}, *parseResourceType(pathData.resourceDir), pathData.name);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700312 resFile.config = pathData.config;
313 resFile.source = pathData.source;
314
315 ChunkWriter fileExportWriter = wrapBufferWithFileExportHeader(&buffer, &resFile);
316
317 {
318 std::ifstream fin(pathData.source.path, std::ifstream::binary);
319 if (!fin) {
320 context->getDiagnostics()->error(DiagMessage(pathData.source) << strerror(errno));
321 return false;
322 }
323
324 Png png(context->getDiagnostics());
325 if (!png.process(pathData.source, &fin, fileExportWriter.getBuffer(), {})) {
326 return false;
327 }
328 }
329
330 fileExportWriter.finish();
331
Adam Lesinskia40e9722015-11-24 19:11:46 -0800332 if (!writer->startEntry(outputPath, 0)) {
333 context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to open");
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700334 return false;
335 }
336
Adam Lesinskia40e9722015-11-24 19:11:46 -0800337 if (writer->writeEntry(buffer)) {
338 if (writer->finishEntry()) {
339 return true;
340 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700341 }
Adam Lesinskia40e9722015-11-24 19:11:46 -0800342
343 context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to write");
344 return false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700345}
346
347static bool compileFile(IAaptContext* context, const CompileOptions& options,
Adam Lesinskia40e9722015-11-24 19:11:46 -0800348 const ResourcePathData& pathData, IArchiveWriter* writer,
349 const std::string& outputPath) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700350 BigBuffer buffer(256);
351 ResourceFile resFile;
Adam Lesinskia40e9722015-11-24 19:11:46 -0800352 resFile.name = ResourceName({}, *parseResourceType(pathData.resourceDir), pathData.name);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700353 resFile.config = pathData.config;
354 resFile.source = pathData.source;
355
356 ChunkWriter fileExportWriter = wrapBufferWithFileExportHeader(&buffer, &resFile);
357
358 std::string errorStr;
359 Maybe<android::FileMap> f = file::mmapPath(pathData.source.path, &errorStr);
360 if (!f) {
361 context->getDiagnostics()->error(DiagMessage(pathData.source) << errorStr);
362 return false;
363 }
364
Adam Lesinskia40e9722015-11-24 19:11:46 -0800365 if (!writer->startEntry(outputPath, 0)) {
366 context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to open");
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700367 return false;
368 }
369
370 // Manually set the size and don't call finish(). This is because we are not copying from
371 // the buffer the entire file.
372 fileExportWriter.getChunkHeader()->size =
373 util::hostToDevice32(buffer.size() + f.value().getDataLength());
Adam Lesinskia40e9722015-11-24 19:11:46 -0800374
375 if (writer->writeEntry(buffer)) {
376 if (writer->writeEntry(f.value().getDataPtr(), f.value().getDataLength())) {
377 if (writer->finishEntry()) {
378 return true;
379 }
380 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700381 }
382
Adam Lesinskia40e9722015-11-24 19:11:46 -0800383 context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to write");
384 return false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700385}
386
387class CompileContext : public IAaptContext {
388private:
389 StdErrDiagnostics mDiagnostics;
390
391public:
392 IDiagnostics* getDiagnostics() override {
393 return &mDiagnostics;
394 }
395
396 NameMangler* getNameMangler() override {
397 abort();
398 return nullptr;
399 }
400
401 StringPiece16 getCompilationPackage() override {
402 return {};
403 }
404
405 uint8_t getPackageId() override {
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700406 return 0x0;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700407 }
408
409 ISymbolTable* getExternalSymbols() override {
410 abort();
411 return nullptr;
412 }
413};
414
415/**
416 * Entry point for compilation phase. Parses arguments and dispatches to the correct steps.
417 */
418int compile(const std::vector<StringPiece>& args) {
419 CompileOptions options;
420
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700421 Maybe<std::string> product;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700422 Flags flags = Flags()
423 .requiredFlag("-o", "Output path", &options.outputPath)
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700424 .optionalFlag("--product", "Product type to compile", &product)
Adam Lesinskia40e9722015-11-24 19:11:46 -0800425 .optionalFlag("--dir", "Directory to scan for resources", &options.resDir)
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700426 .optionalSwitch("-v", "Enables verbose logging", &options.verbose);
427 if (!flags.parse("aapt2 compile", args, &std::cerr)) {
428 return 1;
429 }
430
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700431 if (product) {
432 options.product = util::utf8ToUtf16(product.value());
433 }
434
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700435 CompileContext context;
Adam Lesinskia40e9722015-11-24 19:11:46 -0800436 std::unique_ptr<IArchiveWriter> archiveWriter;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700437
438 std::vector<ResourcePathData> inputData;
Adam Lesinskia40e9722015-11-24 19:11:46 -0800439 if (options.resDir) {
440 if (!flags.getArgs().empty()) {
441 // Can't have both files and a resource directory.
442 context.getDiagnostics()->error(DiagMessage() << "files given but --dir specified");
443 flags.usage("aapt2 compile", &std::cerr);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700444 return 1;
445 }
Adam Lesinskia40e9722015-11-24 19:11:46 -0800446
447 if (!loadInputFilesFromDir(&context, options, &inputData)) {
448 return 1;
449 }
450
451 archiveWriter = createZipFileArchiveWriter(context.getDiagnostics(), options.outputPath);
452
453 } else {
454 inputData.reserve(flags.getArgs().size());
455
456 // Collect data from the path for each input file.
457 for (const std::string& arg : flags.getArgs()) {
458 std::string errorStr;
459 if (Maybe<ResourcePathData> pathData = extractResourcePathData(arg, &errorStr)) {
460 inputData.push_back(std::move(pathData.value()));
461 } else {
462 context.getDiagnostics()->error(DiagMessage() << errorStr << " (" << arg << ")");
463 return 1;
464 }
465 }
466
467 archiveWriter = createDirectoryArchiveWriter(context.getDiagnostics(), options.outputPath);
468 }
469
470 if (!archiveWriter) {
471 return false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700472 }
473
474 bool error = false;
475 for (ResourcePathData& pathData : inputData) {
476 if (options.verbose) {
477 context.getDiagnostics()->note(DiagMessage(pathData.source) << "processing");
478 }
479
480 if (pathData.resourceDir == u"values") {
481 // Overwrite the extension.
482 pathData.extension = "arsc";
483
Adam Lesinskia40e9722015-11-24 19:11:46 -0800484 const std::string outputFilename = buildIntermediateFilename(pathData);
485 if (!compileTable(&context, options, pathData, archiveWriter.get(), outputFilename)) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700486 error = true;
487 }
488
489 } else {
Adam Lesinskia40e9722015-11-24 19:11:46 -0800490 const std::string outputFilename = buildIntermediateFilename(pathData);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700491 if (const ResourceType* type = parseResourceType(pathData.resourceDir)) {
492 if (*type != ResourceType::kRaw) {
493 if (pathData.extension == "xml") {
Adam Lesinskia40e9722015-11-24 19:11:46 -0800494 if (!compileXml(&context, options, pathData, archiveWriter.get(),
495 outputFilename)) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700496 error = true;
497 }
498 } else if (pathData.extension == "png" || pathData.extension == "9.png") {
Adam Lesinskia40e9722015-11-24 19:11:46 -0800499 if (!compilePng(&context, options, pathData, archiveWriter.get(),
500 outputFilename)) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700501 error = true;
502 }
503 } else {
Adam Lesinskia40e9722015-11-24 19:11:46 -0800504 if (!compileFile(&context, options, pathData, archiveWriter.get(),
505 outputFilename)) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700506 error = true;
507 }
508 }
509 } else {
Adam Lesinskia40e9722015-11-24 19:11:46 -0800510 if (!compileFile(&context, options, pathData, archiveWriter.get(),
511 outputFilename)) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700512 error = true;
513 }
514 }
515 } else {
516 context.getDiagnostics()->error(
517 DiagMessage() << "invalid file path '" << pathData.source << "'");
518 error = true;
519 }
520 }
521 }
522
523 if (error) {
524 return 1;
525 }
526 return 0;
527}
528
529} // namespace aapt