blob: 2ea63d02b7548b0b0a2b35a888077b0aa5ed7b8b [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/coded_stream.h>
Adam Lesinskicacb28f2016-10-19 12:18:14 -070037#include <google/protobuf/io/zero_copy_stream_impl_lite.h>
Adam Lesinski59e04c62016-02-04 15:59:23 -080038
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 {
Adam Lesinskicacb28f2016-10-19 12:18:14 -070051 Source source;
52 std::string resourceDir;
53 std::string name;
54 std::string extension;
Adam Lesinski1ab598f2015-08-14 14:26:04 -070055
Adam Lesinskicacb28f2016-10-19 12:18:14 -070056 // Original config str. We keep this because when we parse the config, we may
57 // add on
58 // version qualifiers. We want to preserve the original input so the output is
59 // easily
60 // computed before hand.
61 std::string configStr;
62 ConfigDescription config;
Adam Lesinski1ab598f2015-08-14 14:26:04 -070063};
64
65/**
66 * Resource file paths are expected to look like:
67 * [--/res/]type[-config]/name
68 */
69static Maybe<ResourcePathData> extractResourcePathData(const std::string& path,
70 std::string* outError) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -070071 std::vector<std::string> parts = util::split(path, file::sDirSep);
72 if (parts.size() < 2) {
73 if (outError) *outError = "bad resource path";
74 return {};
75 }
76
77 std::string& dir = parts[parts.size() - 2];
78 StringPiece dirStr = dir;
79
80 StringPiece configStr;
81 ConfigDescription config;
82 size_t dashPos = dir.find('-');
83 if (dashPos != std::string::npos) {
84 configStr = dirStr.substr(dashPos + 1, dir.size() - (dashPos + 1));
85 if (!ConfigDescription::parse(configStr, &config)) {
86 if (outError) {
87 std::stringstream errStr;
88 errStr << "invalid configuration '" << configStr << "'";
89 *outError = errStr.str();
90 }
91 return {};
Adam Lesinski1ab598f2015-08-14 14:26:04 -070092 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -070093 dirStr = dirStr.substr(0, dashPos);
94 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -070095
Adam Lesinskicacb28f2016-10-19 12:18:14 -070096 std::string& filename = parts[parts.size() - 1];
97 StringPiece name = filename;
98 StringPiece extension;
99 size_t dotPos = filename.find('.');
100 if (dotPos != std::string::npos) {
101 extension = name.substr(dotPos + 1, filename.size() - (dotPos + 1));
102 name = name.substr(0, dotPos);
103 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700104
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700105 return ResourcePathData{Source(path), dirStr.toString(),
106 name.toString(), extension.toString(),
107 configStr.toString(), config};
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700108}
109
110struct CompileOptions {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700111 std::string outputPath;
112 Maybe<std::string> resDir;
113 bool pseudolocalize = false;
114 bool legacyMode = false;
115 bool verbose = false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700116};
117
Adam Lesinskia40e9722015-11-24 19:11:46 -0800118static std::string buildIntermediateFilename(const ResourcePathData& data) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700119 std::stringstream name;
120 name << data.resourceDir;
121 if (!data.configStr.empty()) {
122 name << "-" << data.configStr;
123 }
124 name << "_" << data.name;
125 if (!data.extension.empty()) {
126 name << "." << data.extension;
127 }
128 name << ".flat";
129 return name.str();
Adam Lesinskia40e9722015-11-24 19:11:46 -0800130}
131
132static bool isHidden(const StringPiece& filename) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700133 return util::stringStartsWith(filename, ".");
Adam Lesinskia40e9722015-11-24 19:11:46 -0800134}
135
136/**
137 * Walks the res directory structure, looking for resource files.
138 */
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700139static bool loadInputFilesFromDir(IAaptContext* context,
140 const CompileOptions& options,
Adam Lesinskia40e9722015-11-24 19:11:46 -0800141 std::vector<ResourcePathData>* outPathData) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700142 const std::string& rootDir = options.resDir.value();
143 std::unique_ptr<DIR, decltype(closedir)*> d(opendir(rootDir.data()),
144 closedir);
145 if (!d) {
146 context->getDiagnostics()->error(DiagMessage() << strerror(errno));
147 return false;
148 }
149
150 while (struct dirent* entry = readdir(d.get())) {
151 if (isHidden(entry->d_name)) {
152 continue;
153 }
154
155 std::string prefixPath = rootDir;
156 file::appendPath(&prefixPath, entry->d_name);
157
158 if (file::getFileType(prefixPath) != file::FileType::kDirectory) {
159 continue;
160 }
161
162 std::unique_ptr<DIR, decltype(closedir)*> subDir(opendir(prefixPath.data()),
163 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 =
179 extractResourcePathData(fullPath, &errStr);
180 if (!pathData) {
181 context->getDiagnostics()->error(DiagMessage() << errStr);
Adam Lesinskia40e9722015-11-24 19:11:46 -0800182 return false;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700183 }
184
185 outPathData->push_back(std::move(pathData.value()));
Adam Lesinskia40e9722015-11-24 19:11:46 -0800186 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700187 }
188 return true;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700189}
190
191static bool compileTable(IAaptContext* context, const CompileOptions& options,
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700192 const ResourcePathData& pathData,
193 IArchiveWriter* writer,
Adam Lesinskia40e9722015-11-24 19:11:46 -0800194 const std::string& outputPath) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700195 ResourceTable table;
196 {
197 std::ifstream fin(pathData.source.path, std::ifstream::binary);
198 if (!fin) {
199 context->getDiagnostics()->error(DiagMessage(pathData.source)
200 << strerror(errno));
201 return false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700202 }
203
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700204 // Parse the values file from XML.
205 xml::XmlPullParser xmlParser(fin);
206
207 ResourceParserOptions parserOptions;
208 parserOptions.errorOnPositionalArguments = !options.legacyMode;
209
210 // If the filename includes donottranslate, then the default translatable is
211 // false.
212 parserOptions.translatable =
213 pathData.name.find("donottranslate") == std::string::npos;
214
215 ResourceParser resParser(context->getDiagnostics(), &table, pathData.source,
216 pathData.config, parserOptions);
217 if (!resParser.parse(&xmlParser)) {
218 return false;
Adam Lesinski393b5f02015-12-17 13:03:11 -0800219 }
220
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700221 fin.close();
222 }
Adam Lesinski83f22552015-11-07 11:51:23 -0800223
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700224 if (options.pseudolocalize) {
225 // Generate pseudo-localized strings (en-XA and ar-XB).
226 // These are created as weak symbols, and are only generated from default
227 // configuration
228 // strings and plurals.
229 PseudolocaleGenerator pseudolocaleGenerator;
230 if (!pseudolocaleGenerator.consume(context, &table)) {
231 return false;
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700232 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700233 }
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700234
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700235 // Ensure we have the compilation package at least.
236 table.createPackage(context->getCompilationPackage());
237
238 // Assign an ID to any package that has resources.
239 for (auto& pkg : table.packages) {
240 if (!pkg->id) {
241 // If no package ID was set while parsing (public identifiers), auto
242 // assign an ID.
243 pkg->id = context->getPackageId();
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700244 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700245 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700246
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700247 // Create the file/zip entry.
248 if (!writer->startEntry(outputPath, 0)) {
249 context->getDiagnostics()->error(DiagMessage(outputPath)
250 << "failed to open");
251 return false;
252 }
Adam Lesinski59e04c62016-02-04 15:59:23 -0800253
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700254 // Make sure CopyingOutputStreamAdaptor is deleted before we call
255 // writer->finishEntry().
256 {
257 // Wrap our IArchiveWriter with an adaptor that implements the
258 // ZeroCopyOutputStream
259 // interface.
260 CopyingOutputStreamAdaptor copyingAdaptor(writer);
261
262 std::unique_ptr<pb::ResourceTable> pbTable = serializeTableToPb(&table);
263 if (!pbTable->SerializeToZeroCopyStream(&copyingAdaptor)) {
264 context->getDiagnostics()->error(DiagMessage(outputPath)
265 << "failed to write");
266 return false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700267 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700268 }
Adam Lesinskia40e9722015-11-24 19:11:46 -0800269
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700270 if (!writer->finishEntry()) {
271 context->getDiagnostics()->error(DiagMessage(outputPath)
272 << "failed to finish entry");
273 return false;
274 }
275 return true;
Adam Lesinski59e04c62016-02-04 15:59:23 -0800276}
277
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700278static bool writeHeaderAndBufferToWriter(const StringPiece& outputPath,
279 const ResourceFile& file,
280 const BigBuffer& buffer,
281 IArchiveWriter* writer,
Adam Lesinski59e04c62016-02-04 15:59:23 -0800282 IDiagnostics* diag) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700283 // Start the entry so we can write the header.
284 if (!writer->startEntry(outputPath, 0)) {
285 diag->error(DiagMessage(outputPath) << "failed to open file");
286 return false;
287 }
288
289 // Make sure CopyingOutputStreamAdaptor is deleted before we call
290 // writer->finishEntry().
291 {
292 // Wrap our IArchiveWriter with an adaptor that implements the
293 // ZeroCopyOutputStream
294 // interface.
295 CopyingOutputStreamAdaptor copyingAdaptor(writer);
296 CompiledFileOutputStream outputStream(&copyingAdaptor);
297
298 // Number of CompiledFiles.
299 outputStream.WriteLittleEndian32(1);
300
301 std::unique_ptr<pb::CompiledFile> compiledFile =
302 serializeCompiledFileToPb(file);
303 outputStream.WriteCompiledFile(compiledFile.get());
304 outputStream.WriteData(&buffer);
305
306 if (outputStream.HadError()) {
307 diag->error(DiagMessage(outputPath) << "failed to write data");
308 return false;
Adam Lesinski59e04c62016-02-04 15:59:23 -0800309 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700310 }
Adam Lesinski59e04c62016-02-04 15:59:23 -0800311
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700312 if (!writer->finishEntry()) {
313 diag->error(DiagMessage(outputPath) << "failed to finish writing data");
314 return false;
315 }
316 return true;
Adam Lesinski59e04c62016-02-04 15:59:23 -0800317}
318
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700319static bool writeHeaderAndMmapToWriter(const StringPiece& outputPath,
320 const ResourceFile& file,
321 const android::FileMap& map,
322 IArchiveWriter* writer,
Adam Lesinski59e04c62016-02-04 15:59:23 -0800323 IDiagnostics* diag) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700324 // Start the entry so we can write the header.
325 if (!writer->startEntry(outputPath, 0)) {
326 diag->error(DiagMessage(outputPath) << "failed to open file");
327 return false;
328 }
329
330 // Make sure CopyingOutputStreamAdaptor is deleted before we call
331 // writer->finishEntry().
332 {
333 // Wrap our IArchiveWriter with an adaptor that implements the
334 // ZeroCopyOutputStream
335 // interface.
336 CopyingOutputStreamAdaptor copyingAdaptor(writer);
337 CompiledFileOutputStream outputStream(&copyingAdaptor);
338
339 // Number of CompiledFiles.
340 outputStream.WriteLittleEndian32(1);
341
342 std::unique_ptr<pb::CompiledFile> compiledFile =
343 serializeCompiledFileToPb(file);
344 outputStream.WriteCompiledFile(compiledFile.get());
345 outputStream.WriteData(map.getDataPtr(), map.getDataLength());
346
347 if (outputStream.HadError()) {
348 diag->error(DiagMessage(outputPath) << "failed to write data");
349 return false;
Adam Lesinski59e04c62016-02-04 15:59:23 -0800350 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700351 }
Adam Lesinski59e04c62016-02-04 15:59:23 -0800352
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700353 if (!writer->finishEntry()) {
354 diag->error(DiagMessage(outputPath) << "failed to finish writing data");
355 return false;
356 }
357 return true;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700358}
359
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700360static bool flattenXmlToOutStream(IAaptContext* context,
361 const StringPiece& outputPath,
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700362 xml::XmlResource* xmlRes,
363 CompiledFileOutputStream* out) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700364 BigBuffer buffer(1024);
365 XmlFlattenerOptions xmlFlattenerOptions;
366 xmlFlattenerOptions.keepRawValues = true;
367 XmlFlattener flattener(&buffer, xmlFlattenerOptions);
368 if (!flattener.consume(context, xmlRes)) {
369 return false;
370 }
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700371
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700372 std::unique_ptr<pb::CompiledFile> pbCompiledFile =
373 serializeCompiledFileToPb(xmlRes->file);
374 out->WriteCompiledFile(pbCompiledFile.get());
375 out->WriteData(&buffer);
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700376
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700377 if (out->HadError()) {
378 context->getDiagnostics()->error(DiagMessage(outputPath)
379 << "failed to write data");
380 return false;
381 }
382 return true;
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700383}
384
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700385static bool compileXml(IAaptContext* context, const CompileOptions& options,
Adam Lesinskia40e9722015-11-24 19:11:46 -0800386 const ResourcePathData& pathData, IArchiveWriter* writer,
387 const std::string& outputPath) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700388 if (context->verbose()) {
389 context->getDiagnostics()->note(DiagMessage(pathData.source)
390 << "compiling XML");
391 }
392
393 std::unique_ptr<xml::XmlResource> xmlRes;
394 {
395 std::ifstream fin(pathData.source.path, std::ifstream::binary);
396 if (!fin) {
397 context->getDiagnostics()->error(DiagMessage(pathData.source)
398 << strerror(errno));
399 return false;
Adam Lesinski21efb682016-09-14 17:35:43 -0700400 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700401
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700402 xmlRes = xml::inflate(&fin, context->getDiagnostics(), pathData.source);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700403
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700404 fin.close();
405 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700406
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700407 if (!xmlRes) {
408 return false;
409 }
410
411 xmlRes->file.name =
412 ResourceName({}, *parseResourceType(pathData.resourceDir), pathData.name);
413 xmlRes->file.config = pathData.config;
414 xmlRes->file.source = pathData.source;
415
416 // Collect IDs that are defined here.
417 XmlIdCollector collector;
418 if (!collector.consume(context, xmlRes.get())) {
419 return false;
420 }
421
422 // Look for and process any <aapt:attr> tags and create sub-documents.
423 InlineXmlFormatParser inlineXmlFormatParser;
424 if (!inlineXmlFormatParser.consume(context, xmlRes.get())) {
425 return false;
426 }
427
428 // Start the entry so we can write the header.
429 if (!writer->startEntry(outputPath, 0)) {
430 context->getDiagnostics()->error(DiagMessage(outputPath)
431 << "failed to open file");
432 return false;
433 }
434
435 // Make sure CopyingOutputStreamAdaptor is deleted before we call
436 // writer->finishEntry().
437 {
438 // Wrap our IArchiveWriter with an adaptor that implements the
439 // ZeroCopyOutputStream
440 // interface.
441 CopyingOutputStreamAdaptor copyingAdaptor(writer);
442 CompiledFileOutputStream outputStream(&copyingAdaptor);
443
444 std::vector<std::unique_ptr<xml::XmlResource>>& inlineDocuments =
445 inlineXmlFormatParser.getExtractedInlineXmlDocuments();
446
447 // Number of CompiledFiles.
448 outputStream.WriteLittleEndian32(1 + inlineDocuments.size());
449
450 if (!flattenXmlToOutStream(context, outputPath, xmlRes.get(),
451 &outputStream)) {
452 return false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700453 }
454
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700455 for (auto& inlineXmlDoc : inlineDocuments) {
456 if (!flattenXmlToOutStream(context, outputPath, inlineXmlDoc.get(),
457 &outputStream)) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700458 return false;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700459 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700460 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700461 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700462
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700463 if (!writer->finishEntry()) {
464 context->getDiagnostics()->error(DiagMessage(outputPath)
465 << "failed to finish writing data");
466 return false;
467 }
468 return true;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700469}
470
Adam Lesinski21efb682016-09-14 17:35:43 -0700471class BigBufferOutputStream : public io::OutputStream {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700472 public:
473 explicit BigBufferOutputStream(BigBuffer* buffer) : mBuffer(buffer) {}
Adam Lesinski21efb682016-09-14 17:35:43 -0700474
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700475 bool Next(void** data, int* len) override {
476 size_t count;
477 *data = mBuffer->nextBlock(&count);
478 *len = static_cast<int>(count);
479 return true;
480 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700481
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700482 void BackUp(int count) override { mBuffer->backUp(count); }
Adam Lesinski21efb682016-09-14 17:35:43 -0700483
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700484 int64_t ByteCount() const override { return mBuffer->size(); }
Adam Lesinski21efb682016-09-14 17:35:43 -0700485
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700486 bool HadError() const override { return false; }
Adam Lesinski21efb682016-09-14 17:35:43 -0700487
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700488 private:
489 BigBuffer* mBuffer;
Adam Lesinski21efb682016-09-14 17:35:43 -0700490
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700491 DISALLOW_COPY_AND_ASSIGN(BigBufferOutputStream);
Adam Lesinski21efb682016-09-14 17:35:43 -0700492};
493
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700494static bool compilePng(IAaptContext* context, const CompileOptions& options,
Adam Lesinskia40e9722015-11-24 19:11:46 -0800495 const ResourcePathData& pathData, IArchiveWriter* writer,
496 const std::string& outputPath) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700497 if (context->verbose()) {
498 context->getDiagnostics()->note(DiagMessage(pathData.source)
499 << "compiling PNG");
500 }
501
502 BigBuffer buffer(4096);
503 ResourceFile resFile;
504 resFile.name =
505 ResourceName({}, *parseResourceType(pathData.resourceDir), pathData.name);
506 resFile.config = pathData.config;
507 resFile.source = pathData.source;
508
509 {
510 std::string content;
511 if (!android::base::ReadFileToString(pathData.source.path, &content)) {
512 context->getDiagnostics()->error(
513 DiagMessage(pathData.source)
514 << android::base::SystemErrorCodeToString(errno));
515 return false;
Adam Lesinski21efb682016-09-14 17:35:43 -0700516 }
517
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700518 BigBuffer crunchedPngBuffer(4096);
519 BigBufferOutputStream crunchedPngBufferOut(&crunchedPngBuffer);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700520
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700521 // Ensure that we only keep the chunks we care about if we end up
522 // using the original PNG instead of the crunched one.
523 PngChunkFilter pngChunkFilter(content);
524 std::unique_ptr<Image> image = readPng(context, &pngChunkFilter);
525 if (!image) {
526 return false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700527 }
528
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700529 std::unique_ptr<NinePatch> ninePatch;
530 if (pathData.extension == "9.png") {
531 std::string err;
532 ninePatch = NinePatch::create(image->rows.get(), image->width,
533 image->height, &err);
534 if (!ninePatch) {
535 context->getDiagnostics()->error(DiagMessage() << err);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700536 return false;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700537 }
538
539 // Remove the 1px border around the NinePatch.
540 // Basically the row array is shifted up by 1, and the length is treated
541 // as height - 2.
542 // For each row, shift the array to the left by 1, and treat the length as
543 // width - 2.
544 image->width -= 2;
545 image->height -= 2;
546 memmove(image->rows.get(), image->rows.get() + 1,
547 image->height * sizeof(uint8_t**));
548 for (int32_t h = 0; h < image->height; h++) {
549 memmove(image->rows[h], image->rows[h] + 4, image->width * 4);
550 }
551
552 if (context->verbose()) {
553 context->getDiagnostics()->note(DiagMessage(pathData.source)
554 << "9-patch: " << *ninePatch);
555 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700556 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700557
558 // Write the crunched PNG.
559 if (!writePng(context, image.get(), ninePatch.get(), &crunchedPngBufferOut,
560 {})) {
561 return false;
562 }
563
564 if (ninePatch != nullptr ||
565 crunchedPngBufferOut.ByteCount() <= pngChunkFilter.ByteCount()) {
566 // No matter what, we must use the re-encoded PNG, even if it is larger.
567 // 9-patch images must be re-encoded since their borders are stripped.
568 buffer.appendBuffer(std::move(crunchedPngBuffer));
569 } else {
570 // The re-encoded PNG is larger than the original, and there is
571 // no mandatory transformation. Use the original.
572 if (context->verbose()) {
573 context->getDiagnostics()->note(
574 DiagMessage(pathData.source)
575 << "original PNG is smaller than crunched PNG"
576 << ", using original");
577 }
578
579 PngChunkFilter pngChunkFilterAgain(content);
580 BigBuffer filteredPngBuffer(4096);
581 BigBufferOutputStream filteredPngBufferOut(&filteredPngBuffer);
582 io::copy(&filteredPngBufferOut, &pngChunkFilterAgain);
583 buffer.appendBuffer(std::move(filteredPngBuffer));
584 }
585
586 if (context->verbose()) {
587 // For debugging only, use the legacy PNG cruncher and compare the
588 // resulting file sizes.
589 // This will help catch exotic cases where the new code may generate
590 // larger PNGs.
591 std::stringstream legacyStream(content);
592 BigBuffer legacyBuffer(4096);
593 Png png(context->getDiagnostics());
594 if (!png.process(pathData.source, &legacyStream, &legacyBuffer, {})) {
595 return false;
596 }
597
598 context->getDiagnostics()->note(DiagMessage(pathData.source)
599 << "legacy=" << legacyBuffer.size()
600 << " new=" << buffer.size());
601 }
602 }
603
604 if (!writeHeaderAndBufferToWriter(outputPath, resFile, buffer, writer,
605 context->getDiagnostics())) {
606 return false;
607 }
608 return true;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700609}
610
611static bool compileFile(IAaptContext* context, const CompileOptions& options,
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700612 const ResourcePathData& pathData,
613 IArchiveWriter* writer, const std::string& outputPath) {
614 if (context->verbose()) {
615 context->getDiagnostics()->note(DiagMessage(pathData.source)
616 << "compiling file");
617 }
Adam Lesinski21efb682016-09-14 17:35:43 -0700618
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700619 BigBuffer buffer(256);
620 ResourceFile resFile;
621 resFile.name =
622 ResourceName({}, *parseResourceType(pathData.resourceDir), pathData.name);
623 resFile.config = pathData.config;
624 resFile.source = pathData.source;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700625
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700626 std::string errorStr;
627 Maybe<android::FileMap> f = file::mmapPath(pathData.source.path, &errorStr);
628 if (!f) {
629 context->getDiagnostics()->error(DiagMessage(pathData.source) << errorStr);
630 return false;
631 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700632
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700633 if (!writeHeaderAndMmapToWriter(outputPath, resFile, f.value(), writer,
634 context->getDiagnostics())) {
635 return false;
636 }
637 return true;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700638}
639
640class CompileContext : public IAaptContext {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700641 public:
642 void setVerbose(bool val) { mVerbose = val; }
Adam Lesinski355f2852016-02-13 20:26:45 -0800643
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700644 bool verbose() override { return mVerbose; }
Adam Lesinski355f2852016-02-13 20:26:45 -0800645
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700646 IDiagnostics* getDiagnostics() override { return &mDiagnostics; }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700647
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700648 NameMangler* getNameMangler() override {
649 abort();
650 return nullptr;
651 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700652
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700653 const std::string& getCompilationPackage() override {
654 static std::string empty;
655 return empty;
656 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700657
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700658 uint8_t getPackageId() override { return 0x0; }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700659
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700660 SymbolTable* getExternalSymbols() override {
661 abort();
662 return nullptr;
663 }
Adam Lesinski64587af2016-02-18 18:33:06 -0800664
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700665 int getMinSdkVersion() override { return 0; }
Adam Lesinskifb6312f2016-06-28 14:40:32 -0700666
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700667 private:
668 StdErrDiagnostics mDiagnostics;
669 bool mVerbose = false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700670};
671
672/**
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700673 * Entry point for compilation phase. Parses arguments and dispatches to the
674 * correct steps.
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700675 */
676int compile(const std::vector<StringPiece>& args) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700677 CompileContext context;
678 CompileOptions options;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700679
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700680 bool verbose = false;
681 Flags flags =
682 Flags()
683 .requiredFlag("-o", "Output path", &options.outputPath)
684 .optionalFlag("--dir", "Directory to scan for resources",
685 &options.resDir)
686 .optionalSwitch("--pseudo-localize",
687 "Generate resources for pseudo-locales "
688 "(en-XA and ar-XB)",
689 &options.pseudolocalize)
690 .optionalSwitch(
691 "--legacy",
692 "Treat errors that used to be valid in AAPT as warnings",
693 &options.legacyMode)
694 .optionalSwitch("-v", "Enables verbose logging", &verbose);
695 if (!flags.parse("aapt2 compile", args, &std::cerr)) {
696 return 1;
697 }
698
699 context.setVerbose(verbose);
700
701 std::unique_ptr<IArchiveWriter> archiveWriter;
702
703 std::vector<ResourcePathData> inputData;
704 if (options.resDir) {
705 if (!flags.getArgs().empty()) {
706 // Can't have both files and a resource directory.
707 context.getDiagnostics()->error(DiagMessage()
708 << "files given but --dir specified");
709 flags.usage("aapt2 compile", &std::cerr);
710 return 1;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700711 }
712
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700713 if (!loadInputFilesFromDir(&context, options, &inputData)) {
714 return 1;
715 }
Adam Lesinski355f2852016-02-13 20:26:45 -0800716
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700717 archiveWriter = createZipFileArchiveWriter(context.getDiagnostics(),
718 options.outputPath);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700719
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700720 } else {
721 inputData.reserve(flags.getArgs().size());
Adam Lesinskia40e9722015-11-24 19:11:46 -0800722
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700723 // Collect data from the path for each input file.
724 for (const std::string& arg : flags.getArgs()) {
725 std::string errorStr;
726 if (Maybe<ResourcePathData> pathData =
727 extractResourcePathData(arg, &errorStr)) {
728 inputData.push_back(std::move(pathData.value()));
729 } else {
730 context.getDiagnostics()->error(DiagMessage() << errorStr << " (" << arg
731 << ")");
732 return 1;
733 }
734 }
Adam Lesinskia40e9722015-11-24 19:11:46 -0800735
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700736 archiveWriter = createDirectoryArchiveWriter(context.getDiagnostics(),
737 options.outputPath);
738 }
739
740 if (!archiveWriter) {
Adam Lesinskidfaecaf2016-10-20 17:08:51 -0700741 return 1;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700742 }
743
744 bool error = false;
745 for (ResourcePathData& pathData : inputData) {
746 if (options.verbose) {
747 context.getDiagnostics()->note(DiagMessage(pathData.source)
748 << "processing");
749 }
750
751 if (pathData.resourceDir == "values") {
752 // Overwrite the extension.
753 pathData.extension = "arsc";
754
755 const std::string outputFilename = buildIntermediateFilename(pathData);
756 if (!compileTable(&context, options, pathData, archiveWriter.get(),
757 outputFilename)) {
758 error = true;
759 }
Adam Lesinskia40e9722015-11-24 19:11:46 -0800760
761 } else {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700762 const std::string outputFilename = buildIntermediateFilename(pathData);
763 if (const ResourceType* type = parseResourceType(pathData.resourceDir)) {
764 if (*type != ResourceType::kRaw) {
765 if (pathData.extension == "xml") {
766 if (!compileXml(&context, options, pathData, archiveWriter.get(),
767 outputFilename)) {
768 error = true;
Adam Lesinskia40e9722015-11-24 19:11:46 -0800769 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700770 } else if (pathData.extension == "png" ||
771 pathData.extension == "9.png") {
772 if (!compilePng(&context, options, pathData, archiveWriter.get(),
773 outputFilename)) {
774 error = true;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700775 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700776 } else {
777 if (!compileFile(&context, options, pathData, archiveWriter.get(),
778 outputFilename)) {
779 error = true;
780 }
781 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700782 } else {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700783 if (!compileFile(&context, options, pathData, archiveWriter.get(),
784 outputFilename)) {
785 error = true;
786 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700787 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700788 } else {
789 context.getDiagnostics()->error(
790 DiagMessage() << "invalid file path '" << pathData.source << "'");
791 error = true;
792 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700793 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700794 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700795
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700796 if (error) {
797 return 1;
798 }
799 return 0;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700800}
801
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700802} // namespace aapt