blob: dbd8062e8b3618b02b004b88f3bc68abd8165dc5 [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"
Adam Lesinski5eeaadd2016-08-25 12:26:56 -070023#include "compile/InlineXmlFormatParser.h"
Adam Lesinski1ab598f2015-08-14 14:26:04 -070024#include "compile/Png.h"
Adam Lesinski393b5f02015-12-17 13:03:11 -080025#include "compile/PseudolocaleGenerator.h"
Adam Lesinski1ab598f2015-08-14 14:26:04 -070026#include "compile/XmlIdCollector.h"
Adam Lesinskia40e9722015-11-24 19:11:46 -080027#include "flatten/Archive.h"
Adam Lesinski1ab598f2015-08-14 14:26:04 -070028#include "flatten/XmlFlattener.h"
Adam Lesinski59e04c62016-02-04 15:59:23 -080029#include "proto/ProtoSerialize.h"
Adam Lesinski1ab598f2015-08-14 14:26:04 -070030#include "util/Files.h"
31#include "util/Maybe.h"
32#include "util/Util.h"
Adam Lesinski467f1712015-11-16 17:35:44 -080033#include "xml/XmlDom.h"
34#include "xml/XmlPullParser.h"
Adam Lesinski1ab598f2015-08-14 14:26:04 -070035
Adam Lesinski59e04c62016-02-04 15:59:23 -080036#include <google/protobuf/io/zero_copy_stream_impl_lite.h>
37#include <google/protobuf/io/coded_stream.h>
38
Adam Lesinski21efb682016-09-14 17:35:43 -070039#include <android-base/errors.h>
40#include <android-base/file.h>
Adam Lesinskia40e9722015-11-24 19:11:46 -080041#include <dirent.h>
Adam Lesinski1ab598f2015-08-14 14:26:04 -070042#include <fstream>
43#include <string>
44
Adam Lesinski5eeaadd2016-08-25 12:26:56 -070045using google::protobuf::io::CopyingOutputStreamAdaptor;
46using google::protobuf::io::ZeroCopyOutputStream;
47
Adam Lesinski1ab598f2015-08-14 14:26:04 -070048namespace aapt {
49
50struct ResourcePathData {
51 Source source;
Adam Lesinskid0f116b2016-07-08 15:00:32 -070052 std::string resourceDir;
53 std::string name;
Adam Lesinski1ab598f2015-08-14 14:26:04 -070054 std::string extension;
55
56 // Original config str. We keep this because when we parse the config, we may add on
57 // version qualifiers. We want to preserve the original input so the output is easily
58 // computed before hand.
59 std::string configStr;
60 ConfigDescription config;
61};
62
63/**
64 * Resource file paths are expected to look like:
65 * [--/res/]type[-config]/name
66 */
67static Maybe<ResourcePathData> extractResourcePathData(const std::string& path,
68 std::string* outError) {
69 std::vector<std::string> parts = util::split(path, file::sDirSep);
70 if (parts.size() < 2) {
71 if (outError) *outError = "bad resource path";
72 return {};
73 }
74
75 std::string& dir = parts[parts.size() - 2];
76 StringPiece dirStr = dir;
77
78 StringPiece configStr;
79 ConfigDescription config;
80 size_t dashPos = dir.find('-');
81 if (dashPos != std::string::npos) {
82 configStr = dirStr.substr(dashPos + 1, dir.size() - (dashPos + 1));
83 if (!ConfigDescription::parse(configStr, &config)) {
84 if (outError) {
85 std::stringstream errStr;
86 errStr << "invalid configuration '" << configStr << "'";
87 *outError = errStr.str();
88 }
89 return {};
90 }
91 dirStr = dirStr.substr(0, dashPos);
92 }
93
94 std::string& filename = parts[parts.size() - 1];
95 StringPiece name = filename;
96 StringPiece extension;
97 size_t dotPos = filename.find('.');
98 if (dotPos != std::string::npos) {
99 extension = name.substr(dotPos + 1, filename.size() - (dotPos + 1));
100 name = name.substr(0, dotPos);
101 }
102
103 return ResourcePathData{
Adam Lesinskia40e9722015-11-24 19:11:46 -0800104 Source(path),
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700105 dirStr.toString(),
106 name.toString(),
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700107 extension.toString(),
108 configStr.toString(),
109 config
110 };
111}
112
113struct CompileOptions {
114 std::string outputPath;
Adam Lesinskia40e9722015-11-24 19:11:46 -0800115 Maybe<std::string> resDir;
Adam Lesinski393b5f02015-12-17 13:03:11 -0800116 bool pseudolocalize = false;
Adam Lesinski979ccb22016-01-11 10:42:19 -0800117 bool legacyMode = false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700118 bool verbose = false;
119};
120
Adam Lesinskia40e9722015-11-24 19:11:46 -0800121static std::string buildIntermediateFilename(const ResourcePathData& data) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700122 std::stringstream name;
123 name << data.resourceDir;
124 if (!data.configStr.empty()) {
125 name << "-" << data.configStr;
126 }
Adam Lesinski52364f72016-01-11 13:10:24 -0800127 name << "_" << data.name;
128 if (!data.extension.empty()) {
129 name << "." << data.extension;
130 }
131 name << ".flat";
Adam Lesinskia40e9722015-11-24 19:11:46 -0800132 return name.str();
133}
134
135static bool isHidden(const StringPiece& filename) {
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700136 return util::stringStartsWith(filename, ".");
Adam Lesinskia40e9722015-11-24 19:11:46 -0800137}
138
139/**
140 * Walks the res directory structure, looking for resource files.
141 */
142static bool loadInputFilesFromDir(IAaptContext* context, const CompileOptions& options,
143 std::vector<ResourcePathData>* outPathData) {
144 const std::string& rootDir = options.resDir.value();
145 std::unique_ptr<DIR, decltype(closedir)*> d(opendir(rootDir.data()), closedir);
146 if (!d) {
147 context->getDiagnostics()->error(DiagMessage() << strerror(errno));
148 return false;
149 }
150
151 while (struct dirent* entry = readdir(d.get())) {
152 if (isHidden(entry->d_name)) {
153 continue;
154 }
155
156 std::string prefixPath = rootDir;
157 file::appendPath(&prefixPath, entry->d_name);
158
159 if (file::getFileType(prefixPath) != file::FileType::kDirectory) {
160 continue;
161 }
162
163 std::unique_ptr<DIR, decltype(closedir)*> subDir(opendir(prefixPath.data()), closedir);
164 if (!subDir) {
165 context->getDiagnostics()->error(DiagMessage() << strerror(errno));
166 return false;
167 }
168
169 while (struct dirent* leafEntry = readdir(subDir.get())) {
170 if (isHidden(leafEntry->d_name)) {
171 continue;
172 }
173
174 std::string fullPath = prefixPath;
175 file::appendPath(&fullPath, leafEntry->d_name);
176
177 std::string errStr;
178 Maybe<ResourcePathData> pathData = extractResourcePathData(fullPath, &errStr);
179 if (!pathData) {
180 context->getDiagnostics()->error(DiagMessage() << errStr);
181 return false;
182 }
183
184 outPathData->push_back(std::move(pathData.value()));
185 }
186 }
187 return true;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700188}
189
190static bool compileTable(IAaptContext* context, const CompileOptions& options,
Adam Lesinskia40e9722015-11-24 19:11:46 -0800191 const ResourcePathData& pathData, IArchiveWriter* writer,
192 const std::string& outputPath) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700193 ResourceTable table;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700194 {
195 std::ifstream fin(pathData.source.path, std::ifstream::binary);
196 if (!fin) {
197 context->getDiagnostics()->error(DiagMessage(pathData.source) << strerror(errno));
198 return false;
199 }
200
201
202 // Parse the values file from XML.
Adam Lesinski467f1712015-11-16 17:35:44 -0800203 xml::XmlPullParser xmlParser(fin);
Adam Lesinski9f222042015-11-04 13:51:45 -0800204
205 ResourceParserOptions parserOptions;
Adam Lesinski979ccb22016-01-11 10:42:19 -0800206 parserOptions.errorOnPositionalArguments = !options.legacyMode;
Adam Lesinski9f222042015-11-04 13:51:45 -0800207
208 // If the filename includes donottranslate, then the default translatable is false.
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700209 parserOptions.translatable = pathData.name.find("donottranslate") == std::string::npos;
Adam Lesinski9f222042015-11-04 13:51:45 -0800210
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700211 ResourceParser resParser(context->getDiagnostics(), &table, pathData.source,
Adam Lesinski9f222042015-11-04 13:51:45 -0800212 pathData.config, parserOptions);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700213 if (!resParser.parse(&xmlParser)) {
214 return false;
215 }
216
217 fin.close();
218 }
219
Adam Lesinski393b5f02015-12-17 13:03:11 -0800220 if (options.pseudolocalize) {
221 // Generate pseudo-localized strings (en-XA and ar-XB).
222 // These are created as weak symbols, and are only generated from default configuration
223 // strings and plurals.
224 PseudolocaleGenerator pseudolocaleGenerator;
225 if (!pseudolocaleGenerator.consume(context, &table)) {
226 return false;
227 }
228 }
229
Adam Lesinski83f22552015-11-07 11:51:23 -0800230 // Ensure we have the compilation package at least.
231 table.createPackage(context->getCompilationPackage());
232
Adam Lesinskia40e9722015-11-24 19:11:46 -0800233 // Assign an ID to any package that has resources.
Adam Lesinski83f22552015-11-07 11:51:23 -0800234 for (auto& pkg : table.packages) {
235 if (!pkg->id) {
236 // If no package ID was set while parsing (public identifiers), auto assign an ID.
237 pkg->id = context->getPackageId();
238 }
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700239 }
240
Adam Lesinski59e04c62016-02-04 15:59:23 -0800241 // Create the file/zip entry.
Adam Lesinskia40e9722015-11-24 19:11:46 -0800242 if (!writer->startEntry(outputPath, 0)) {
243 context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to open");
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700244 return false;
245 }
246
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700247 // Make sure CopyingOutputStreamAdaptor is deleted before we call writer->finishEntry().
Adam Lesinski59e04c62016-02-04 15:59:23 -0800248 {
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700249 // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream
250 // interface.
251 CopyingOutputStreamAdaptor copyingAdaptor(writer);
Adam Lesinski59e04c62016-02-04 15:59:23 -0800252
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700253 std::unique_ptr<pb::ResourceTable> pbTable = serializeTableToPb(&table);
254 if (!pbTable->SerializeToZeroCopyStream(&copyingAdaptor)) {
Adam Lesinski59e04c62016-02-04 15:59:23 -0800255 context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to write");
256 return false;
Adam Lesinskia40e9722015-11-24 19:11:46 -0800257 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700258 }
Adam Lesinskia40e9722015-11-24 19:11:46 -0800259
Adam Lesinski59e04c62016-02-04 15:59:23 -0800260 if (!writer->finishEntry()) {
261 context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to finish entry");
262 return false;
263 }
264 return true;
265}
266
267static bool writeHeaderAndBufferToWriter(const StringPiece& outputPath, const ResourceFile& file,
268 const BigBuffer& buffer, IArchiveWriter* writer,
269 IDiagnostics* diag) {
270 // Start the entry so we can write the header.
271 if (!writer->startEntry(outputPath, 0)) {
272 diag->error(DiagMessage(outputPath) << "failed to open file");
273 return false;
274 }
275
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700276 // Make sure CopyingOutputStreamAdaptor is deleted before we call writer->finishEntry().
Adam Lesinski59e04c62016-02-04 15:59:23 -0800277 {
Adam Lesinski59e04c62016-02-04 15:59:23 -0800278 // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream
279 // interface.
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700280 CopyingOutputStreamAdaptor copyingAdaptor(writer);
281 CompiledFileOutputStream outputStream(&copyingAdaptor);
282
283 // Number of CompiledFiles.
284 outputStream.WriteLittleEndian32(1);
285
286 std::unique_ptr<pb::CompiledFile> compiledFile = serializeCompiledFileToPb(file);
287 outputStream.WriteCompiledFile(compiledFile.get());
288 outputStream.WriteData(&buffer);
289
290 if (outputStream.HadError()) {
291 diag->error(DiagMessage(outputPath) << "failed to write data");
292 return false;
Adam Lesinski59e04c62016-02-04 15:59:23 -0800293 }
294 }
295
296 if (!writer->finishEntry()) {
297 diag->error(DiagMessage(outputPath) << "failed to finish writing data");
298 return false;
299 }
300 return true;
301}
302
303static bool writeHeaderAndMmapToWriter(const StringPiece& outputPath, const ResourceFile& file,
304 const android::FileMap& map, IArchiveWriter* writer,
305 IDiagnostics* diag) {
306 // Start the entry so we can write the header.
307 if (!writer->startEntry(outputPath, 0)) {
308 diag->error(DiagMessage(outputPath) << "failed to open file");
309 return false;
310 }
311
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700312 // Make sure CopyingOutputStreamAdaptor is deleted before we call writer->finishEntry().
Adam Lesinski59e04c62016-02-04 15:59:23 -0800313 {
Adam Lesinski59e04c62016-02-04 15:59:23 -0800314 // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream
315 // interface.
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700316 CopyingOutputStreamAdaptor copyingAdaptor(writer);
317 CompiledFileOutputStream outputStream(&copyingAdaptor);
318
319 // Number of CompiledFiles.
320 outputStream.WriteLittleEndian32(1);
321
322 std::unique_ptr<pb::CompiledFile> compiledFile = serializeCompiledFileToPb(file);
323 outputStream.WriteCompiledFile(compiledFile.get());
324 outputStream.WriteData(map.getDataPtr(), map.getDataLength());
325
326 if (outputStream.HadError()) {
Adam Lesinski59e04c62016-02-04 15:59:23 -0800327 diag->error(DiagMessage(outputPath) << "failed to write data");
328 return false;
329 }
330 }
331
332 if (!writer->finishEntry()) {
333 diag->error(DiagMessage(outputPath) << "failed to finish writing data");
334 return false;
335 }
336 return true;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700337}
338
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700339static bool flattenXmlToOutStream(IAaptContext* context, const StringPiece& outputPath,
340 xml::XmlResource* xmlRes,
341 CompiledFileOutputStream* out) {
342 BigBuffer buffer(1024);
343 XmlFlattenerOptions xmlFlattenerOptions;
344 xmlFlattenerOptions.keepRawValues = true;
345 XmlFlattener flattener(&buffer, xmlFlattenerOptions);
346 if (!flattener.consume(context, xmlRes)) {
347 return false;
348 }
349
350 std::unique_ptr<pb::CompiledFile> pbCompiledFile = serializeCompiledFileToPb(xmlRes->file);
351 out->WriteCompiledFile(pbCompiledFile.get());
352 out->WriteData(&buffer);
353
354 if (out->HadError()) {
355 context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to write data");
356 return false;
357 }
358 return true;
359}
360
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700361static bool compileXml(IAaptContext* context, const CompileOptions& options,
Adam Lesinskia40e9722015-11-24 19:11:46 -0800362 const ResourcePathData& pathData, IArchiveWriter* writer,
363 const std::string& outputPath) {
Adam Lesinski21efb682016-09-14 17:35:43 -0700364 if (context->verbose()) {
365 context->getDiagnostics()->note(DiagMessage(pathData.source) << "compiling XML");
366 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700367
Adam Lesinski467f1712015-11-16 17:35:44 -0800368 std::unique_ptr<xml::XmlResource> xmlRes;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700369 {
370 std::ifstream fin(pathData.source.path, std::ifstream::binary);
371 if (!fin) {
372 context->getDiagnostics()->error(DiagMessage(pathData.source) << strerror(errno));
373 return false;
374 }
375
376 xmlRes = xml::inflate(&fin, context->getDiagnostics(), pathData.source);
377
378 fin.close();
379 }
380
381 if (!xmlRes) {
382 return false;
383 }
384
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700385 xmlRes->file.name = ResourceName({}, *parseResourceType(pathData.resourceDir), pathData.name);
386 xmlRes->file.config = pathData.config;
387 xmlRes->file.source = pathData.source;
388
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700389 // Collect IDs that are defined here.
390 XmlIdCollector collector;
391 if (!collector.consume(context, xmlRes.get())) {
392 return false;
393 }
394
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700395 // Look for and process any <aapt:attr> tags and create sub-documents.
396 InlineXmlFormatParser inlineXmlFormatParser;
397 if (!inlineXmlFormatParser.consume(context, xmlRes.get())) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700398 return false;
399 }
400
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700401 // Start the entry so we can write the header.
402 if (!writer->startEntry(outputPath, 0)) {
403 context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to open file");
404 return false;
405 }
406
407 // Make sure CopyingOutputStreamAdaptor is deleted before we call writer->finishEntry().
408 {
409 // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream
410 // interface.
411 CopyingOutputStreamAdaptor copyingAdaptor(writer);
412 CompiledFileOutputStream outputStream(&copyingAdaptor);
413
414 std::vector<std::unique_ptr<xml::XmlResource>>& inlineDocuments =
415 inlineXmlFormatParser.getExtractedInlineXmlDocuments();
416
417 // Number of CompiledFiles.
418 outputStream.WriteLittleEndian32(1 + inlineDocuments.size());
419
420 if (!flattenXmlToOutStream(context, outputPath, xmlRes.get(), &outputStream)) {
421 return false;
422 }
423
424 for (auto& inlineXmlDoc : inlineDocuments) {
425 if (!flattenXmlToOutStream(context, outputPath, inlineXmlDoc.get(), &outputStream)) {
426 return false;
427 }
428 }
429 }
430
431 if (!writer->finishEntry()) {
432 context->getDiagnostics()->error(DiagMessage(outputPath)
433 << "failed to finish writing data");
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700434 return false;
435 }
Adam Lesinski59e04c62016-02-04 15:59:23 -0800436 return true;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700437}
438
Adam Lesinski21efb682016-09-14 17:35:43 -0700439class BigBufferOutputStream : public io::OutputStream {
440public:
441 explicit BigBufferOutputStream(BigBuffer* buffer) : mBuffer(buffer) {
442 }
443
444 bool Next(void** data, int* len) override {
445 size_t count;
446 *data = mBuffer->nextBlock(&count);
447 *len = static_cast<int>(count);
448 return true;
449 }
450
451 void BackUp(int count) override {
452 mBuffer->backUp(count);
453 }
454
455 int64_t ByteCount() const override {
456 return mBuffer->size();
457 }
458
459 bool HadError() const override {
460 return false;
461 }
462
463private:
464 BigBuffer* mBuffer;
465
466 DISALLOW_COPY_AND_ASSIGN(BigBufferOutputStream);
467};
468
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700469static bool compilePng(IAaptContext* context, const CompileOptions& options,
Adam Lesinskia40e9722015-11-24 19:11:46 -0800470 const ResourcePathData& pathData, IArchiveWriter* writer,
471 const std::string& outputPath) {
Adam Lesinski21efb682016-09-14 17:35:43 -0700472 if (context->verbose()) {
473 context->getDiagnostics()->note(DiagMessage(pathData.source) << "compiling PNG");
474 }
475
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700476 BigBuffer buffer(4096);
477 ResourceFile resFile;
Adam Lesinskia40e9722015-11-24 19:11:46 -0800478 resFile.name = ResourceName({}, *parseResourceType(pathData.resourceDir), pathData.name);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700479 resFile.config = pathData.config;
480 resFile.source = pathData.source;
481
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700482 {
Adam Lesinski21efb682016-09-14 17:35:43 -0700483 std::string content;
484 if (!android::base::ReadFileToString(pathData.source.path, &content)) {
485 context->getDiagnostics()->error(DiagMessage(pathData.source)
486 << android::base::SystemErrorCodeToString(errno));
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700487 return false;
488 }
489
Adam Lesinski21efb682016-09-14 17:35:43 -0700490 BigBuffer crunchedPngBuffer(4096);
491 BigBufferOutputStream crunchedPngBufferOut(&crunchedPngBuffer);
492
493 // Ensure that we only keep the chunks we care about if we end up
494 // using the original PNG instead of the crunched one.
495 PngChunkFilter pngChunkFilter(content);
496 std::unique_ptr<Image> image = readPng(context, &pngChunkFilter);
497 if (!image) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700498 return false;
499 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700500
501 std::unique_ptr<NinePatch> ninePatch;
502 if (pathData.extension == "9.png") {
503 std::string err;
504 ninePatch = NinePatch::create(image->rows.get(), image->width, image->height, &err);
505 if (!ninePatch) {
506 context->getDiagnostics()->error(DiagMessage() << err);
507 return false;
508 }
509
510 // Remove the 1px border around the NinePatch.
511 // Basically the row array is shifted up by 1, and the length is treated
512 // as height - 2.
513 // For each row, shift the array to the left by 1, and treat the length as width - 2.
514 image->width -= 2;
515 image->height -= 2;
516 memmove(image->rows.get(), image->rows.get() + 1, image->height * sizeof(uint8_t**));
517 for (int32_t h = 0; h < image->height; h++) {
518 memmove(image->rows[h], image->rows[h] + 4, image->width * 4);
519 }
520
521 if (context->verbose()) {
522 context->getDiagnostics()->note(DiagMessage(pathData.source)
523 << "9-patch: " << *ninePatch);
524 }
525 }
526
527 // Write the crunched PNG.
528 if (!writePng(context, image.get(), ninePatch.get(), &crunchedPngBufferOut, {})) {
529 return false;
530 }
531
532 if (ninePatch != nullptr
533 || crunchedPngBufferOut.ByteCount() <= pngChunkFilter.ByteCount()) {
534 // No matter what, we must use the re-encoded PNG, even if it is larger.
535 // 9-patch images must be re-encoded since their borders are stripped.
536 buffer.appendBuffer(std::move(crunchedPngBuffer));
537 } else {
538 // The re-encoded PNG is larger than the original, and there is
539 // no mandatory transformation. Use the original.
540 if (context->verbose()) {
541 context->getDiagnostics()->note(DiagMessage(pathData.source)
542 << "original PNG is smaller than crunched PNG"
543 << ", using original");
544 }
545
546 PngChunkFilter pngChunkFilterAgain(content);
547 BigBuffer filteredPngBuffer(4096);
548 BigBufferOutputStream filteredPngBufferOut(&filteredPngBuffer);
549 io::copy(&filteredPngBufferOut, &pngChunkFilterAgain);
550 buffer.appendBuffer(std::move(filteredPngBuffer));
551 }
552
553 if (context->verbose()) {
554 // For debugging only, use the legacy PNG cruncher and compare the resulting file sizes.
555 // This will help catch exotic cases where the new code may generate larger PNGs.
556 std::stringstream legacyStream(content);
557 BigBuffer legacyBuffer(4096);
558 Png png(context->getDiagnostics());
559 if (!png.process(pathData.source, &legacyStream, &legacyBuffer, {})) {
560 return false;
561 }
562
563 context->getDiagnostics()->note(DiagMessage(pathData.source)
564 << "legacy=" << legacyBuffer.size()
565 << " new=" << buffer.size());
566 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700567 }
568
Adam Lesinski59e04c62016-02-04 15:59:23 -0800569 if (!writeHeaderAndBufferToWriter(outputPath, resFile, buffer, writer,
570 context->getDiagnostics())) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700571 return false;
572 }
Adam Lesinski59e04c62016-02-04 15:59:23 -0800573 return true;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700574}
575
576static bool compileFile(IAaptContext* context, const CompileOptions& options,
Adam Lesinskia40e9722015-11-24 19:11:46 -0800577 const ResourcePathData& pathData, IArchiveWriter* writer,
578 const std::string& outputPath) {
Adam Lesinski21efb682016-09-14 17:35:43 -0700579 if (context->verbose()) {
580 context->getDiagnostics()->note(DiagMessage(pathData.source) << "compiling file");
581 }
582
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700583 BigBuffer buffer(256);
584 ResourceFile resFile;
Adam Lesinskia40e9722015-11-24 19:11:46 -0800585 resFile.name = ResourceName({}, *parseResourceType(pathData.resourceDir), pathData.name);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700586 resFile.config = pathData.config;
587 resFile.source = pathData.source;
588
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700589 std::string errorStr;
590 Maybe<android::FileMap> f = file::mmapPath(pathData.source.path, &errorStr);
591 if (!f) {
592 context->getDiagnostics()->error(DiagMessage(pathData.source) << errorStr);
593 return false;
594 }
595
Adam Lesinski59e04c62016-02-04 15:59:23 -0800596 if (!writeHeaderAndMmapToWriter(outputPath, resFile, f.value(), writer,
597 context->getDiagnostics())) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700598 return false;
599 }
Adam Lesinski52364f72016-01-11 13:10:24 -0800600 return true;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700601}
602
603class CompileContext : public IAaptContext {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700604public:
Adam Lesinski355f2852016-02-13 20:26:45 -0800605 void setVerbose(bool val) {
606 mVerbose = val;
607 }
608
609 bool verbose() override {
610 return mVerbose;
611 }
612
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700613 IDiagnostics* getDiagnostics() override {
614 return &mDiagnostics;
615 }
616
617 NameMangler* getNameMangler() override {
618 abort();
619 return nullptr;
620 }
621
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700622 const std::string& getCompilationPackage() override {
623 static std::string empty;
Adam Lesinski64587af2016-02-18 18:33:06 -0800624 return empty;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700625 }
626
627 uint8_t getPackageId() override {
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700628 return 0x0;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700629 }
630
Adam Lesinski64587af2016-02-18 18:33:06 -0800631 SymbolTable* getExternalSymbols() override {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700632 abort();
633 return nullptr;
634 }
Adam Lesinski64587af2016-02-18 18:33:06 -0800635
Adam Lesinskifb6312f2016-06-28 14:40:32 -0700636 int getMinSdkVersion() override {
637 return 0;
638 }
639
Adam Lesinski64587af2016-02-18 18:33:06 -0800640private:
641 StdErrDiagnostics mDiagnostics;
642 bool mVerbose = false;
643
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700644};
645
646/**
647 * Entry point for compilation phase. Parses arguments and dispatches to the correct steps.
648 */
649int compile(const std::vector<StringPiece>& args) {
Adam Lesinski355f2852016-02-13 20:26:45 -0800650 CompileContext context;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700651 CompileOptions options;
652
Adam Lesinski355f2852016-02-13 20:26:45 -0800653 bool verbose = false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700654 Flags flags = Flags()
655 .requiredFlag("-o", "Output path", &options.outputPath)
Adam Lesinskia40e9722015-11-24 19:11:46 -0800656 .optionalFlag("--dir", "Directory to scan for resources", &options.resDir)
Adam Lesinski393b5f02015-12-17 13:03:11 -0800657 .optionalSwitch("--pseudo-localize", "Generate resources for pseudo-locales "
658 "(en-XA and ar-XB)", &options.pseudolocalize)
Adam Lesinski979ccb22016-01-11 10:42:19 -0800659 .optionalSwitch("--legacy", "Treat errors that used to be valid in AAPT as warnings",
660 &options.legacyMode)
Adam Lesinski355f2852016-02-13 20:26:45 -0800661 .optionalSwitch("-v", "Enables verbose logging", &verbose);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700662 if (!flags.parse("aapt2 compile", args, &std::cerr)) {
663 return 1;
664 }
665
Adam Lesinski355f2852016-02-13 20:26:45 -0800666 context.setVerbose(verbose);
667
Adam Lesinskia40e9722015-11-24 19:11:46 -0800668 std::unique_ptr<IArchiveWriter> archiveWriter;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700669
670 std::vector<ResourcePathData> inputData;
Adam Lesinskia40e9722015-11-24 19:11:46 -0800671 if (options.resDir) {
672 if (!flags.getArgs().empty()) {
673 // Can't have both files and a resource directory.
674 context.getDiagnostics()->error(DiagMessage() << "files given but --dir specified");
675 flags.usage("aapt2 compile", &std::cerr);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700676 return 1;
677 }
Adam Lesinskia40e9722015-11-24 19:11:46 -0800678
679 if (!loadInputFilesFromDir(&context, options, &inputData)) {
680 return 1;
681 }
682
683 archiveWriter = createZipFileArchiveWriter(context.getDiagnostics(), options.outputPath);
684
685 } else {
686 inputData.reserve(flags.getArgs().size());
687
688 // Collect data from the path for each input file.
689 for (const std::string& arg : flags.getArgs()) {
690 std::string errorStr;
691 if (Maybe<ResourcePathData> pathData = extractResourcePathData(arg, &errorStr)) {
692 inputData.push_back(std::move(pathData.value()));
693 } else {
694 context.getDiagnostics()->error(DiagMessage() << errorStr << " (" << arg << ")");
695 return 1;
696 }
697 }
698
699 archiveWriter = createDirectoryArchiveWriter(context.getDiagnostics(), options.outputPath);
700 }
701
702 if (!archiveWriter) {
703 return false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700704 }
705
706 bool error = false;
707 for (ResourcePathData& pathData : inputData) {
708 if (options.verbose) {
709 context.getDiagnostics()->note(DiagMessage(pathData.source) << "processing");
710 }
711
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700712 if (pathData.resourceDir == "values") {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700713 // Overwrite the extension.
714 pathData.extension = "arsc";
715
Adam Lesinskia40e9722015-11-24 19:11:46 -0800716 const std::string outputFilename = buildIntermediateFilename(pathData);
717 if (!compileTable(&context, options, pathData, archiveWriter.get(), outputFilename)) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700718 error = true;
719 }
720
721 } else {
Adam Lesinskia40e9722015-11-24 19:11:46 -0800722 const std::string outputFilename = buildIntermediateFilename(pathData);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700723 if (const ResourceType* type = parseResourceType(pathData.resourceDir)) {
724 if (*type != ResourceType::kRaw) {
725 if (pathData.extension == "xml") {
Adam Lesinskia40e9722015-11-24 19:11:46 -0800726 if (!compileXml(&context, options, pathData, archiveWriter.get(),
727 outputFilename)) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700728 error = true;
729 }
730 } else if (pathData.extension == "png" || pathData.extension == "9.png") {
Adam Lesinskia40e9722015-11-24 19:11:46 -0800731 if (!compilePng(&context, options, pathData, archiveWriter.get(),
732 outputFilename)) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700733 error = true;
734 }
735 } else {
Adam Lesinskia40e9722015-11-24 19:11:46 -0800736 if (!compileFile(&context, options, pathData, archiveWriter.get(),
737 outputFilename)) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700738 error = true;
739 }
740 }
741 } else {
Adam Lesinskia40e9722015-11-24 19:11:46 -0800742 if (!compileFile(&context, options, pathData, archiveWriter.get(),
743 outputFilename)) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700744 error = true;
745 }
746 }
747 } else {
748 context.getDiagnostics()->error(
749 DiagMessage() << "invalid file path '" << pathData.source << "'");
750 error = true;
751 }
752 }
753 }
754
755 if (error) {
756 return 1;
757 }
758 return 0;
759}
760
761} // namespace aapt