blob: e0f37ec37b92dc4d6fd32e3a211f044f27f202ce [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 Lesinskia40e9722015-11-24 19:11:46 -080039#include <dirent.h>
Adam Lesinski1ab598f2015-08-14 14:26:04 -070040#include <fstream>
41#include <string>
42
Adam Lesinski5eeaadd2016-08-25 12:26:56 -070043using google::protobuf::io::CopyingOutputStreamAdaptor;
44using google::protobuf::io::ZeroCopyOutputStream;
45
Adam Lesinski1ab598f2015-08-14 14:26:04 -070046namespace aapt {
47
48struct ResourcePathData {
49 Source source;
Adam Lesinskid0f116b2016-07-08 15:00:32 -070050 std::string resourceDir;
51 std::string name;
Adam Lesinski1ab598f2015-08-14 14:26:04 -070052 std::string extension;
53
54 // Original config str. We keep this because when we parse the config, we may add on
55 // version qualifiers. We want to preserve the original input so the output is easily
56 // computed before hand.
57 std::string configStr;
58 ConfigDescription config;
59};
60
61/**
62 * Resource file paths are expected to look like:
63 * [--/res/]type[-config]/name
64 */
65static Maybe<ResourcePathData> extractResourcePathData(const std::string& path,
66 std::string* outError) {
67 std::vector<std::string> parts = util::split(path, file::sDirSep);
68 if (parts.size() < 2) {
69 if (outError) *outError = "bad resource path";
70 return {};
71 }
72
73 std::string& dir = parts[parts.size() - 2];
74 StringPiece dirStr = dir;
75
76 StringPiece configStr;
77 ConfigDescription config;
78 size_t dashPos = dir.find('-');
79 if (dashPos != std::string::npos) {
80 configStr = dirStr.substr(dashPos + 1, dir.size() - (dashPos + 1));
81 if (!ConfigDescription::parse(configStr, &config)) {
82 if (outError) {
83 std::stringstream errStr;
84 errStr << "invalid configuration '" << configStr << "'";
85 *outError = errStr.str();
86 }
87 return {};
88 }
89 dirStr = dirStr.substr(0, dashPos);
90 }
91
92 std::string& filename = parts[parts.size() - 1];
93 StringPiece name = filename;
94 StringPiece extension;
95 size_t dotPos = filename.find('.');
96 if (dotPos != std::string::npos) {
97 extension = name.substr(dotPos + 1, filename.size() - (dotPos + 1));
98 name = name.substr(0, dotPos);
99 }
100
101 return ResourcePathData{
Adam Lesinskia40e9722015-11-24 19:11:46 -0800102 Source(path),
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700103 dirStr.toString(),
104 name.toString(),
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700105 extension.toString(),
106 configStr.toString(),
107 config
108 };
109}
110
111struct CompileOptions {
112 std::string outputPath;
Adam Lesinskia40e9722015-11-24 19:11:46 -0800113 Maybe<std::string> resDir;
Adam Lesinski393b5f02015-12-17 13:03:11 -0800114 bool pseudolocalize = false;
Adam Lesinski979ccb22016-01-11 10:42:19 -0800115 bool legacyMode = false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700116 bool verbose = false;
117};
118
Adam Lesinskia40e9722015-11-24 19:11:46 -0800119static std::string buildIntermediateFilename(const ResourcePathData& data) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700120 std::stringstream name;
121 name << data.resourceDir;
122 if (!data.configStr.empty()) {
123 name << "-" << data.configStr;
124 }
Adam Lesinski52364f72016-01-11 13:10:24 -0800125 name << "_" << data.name;
126 if (!data.extension.empty()) {
127 name << "." << data.extension;
128 }
129 name << ".flat";
Adam Lesinskia40e9722015-11-24 19:11:46 -0800130 return name.str();
131}
132
133static bool isHidden(const StringPiece& filename) {
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700134 return util::stringStartsWith(filename, ".");
Adam Lesinskia40e9722015-11-24 19:11:46 -0800135}
136
137/**
138 * Walks the res directory structure, looking for resource files.
139 */
140static bool loadInputFilesFromDir(IAaptContext* context, const CompileOptions& options,
141 std::vector<ResourcePathData>* outPathData) {
142 const std::string& rootDir = options.resDir.value();
143 std::unique_ptr<DIR, decltype(closedir)*> d(opendir(rootDir.data()), closedir);
144 if (!d) {
145 context->getDiagnostics()->error(DiagMessage() << strerror(errno));
146 return false;
147 }
148
149 while (struct dirent* entry = readdir(d.get())) {
150 if (isHidden(entry->d_name)) {
151 continue;
152 }
153
154 std::string prefixPath = rootDir;
155 file::appendPath(&prefixPath, entry->d_name);
156
157 if (file::getFileType(prefixPath) != file::FileType::kDirectory) {
158 continue;
159 }
160
161 std::unique_ptr<DIR, decltype(closedir)*> subDir(opendir(prefixPath.data()), closedir);
162 if (!subDir) {
163 context->getDiagnostics()->error(DiagMessage() << strerror(errno));
164 return false;
165 }
166
167 while (struct dirent* leafEntry = readdir(subDir.get())) {
168 if (isHidden(leafEntry->d_name)) {
169 continue;
170 }
171
172 std::string fullPath = prefixPath;
173 file::appendPath(&fullPath, leafEntry->d_name);
174
175 std::string errStr;
176 Maybe<ResourcePathData> pathData = extractResourcePathData(fullPath, &errStr);
177 if (!pathData) {
178 context->getDiagnostics()->error(DiagMessage() << errStr);
179 return false;
180 }
181
182 outPathData->push_back(std::move(pathData.value()));
183 }
184 }
185 return true;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700186}
187
188static bool compileTable(IAaptContext* context, const CompileOptions& options,
Adam Lesinskia40e9722015-11-24 19:11:46 -0800189 const ResourcePathData& pathData, IArchiveWriter* writer,
190 const std::string& outputPath) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700191 ResourceTable table;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700192 {
193 std::ifstream fin(pathData.source.path, std::ifstream::binary);
194 if (!fin) {
195 context->getDiagnostics()->error(DiagMessage(pathData.source) << strerror(errno));
196 return false;
197 }
198
199
200 // Parse the values file from XML.
Adam Lesinski467f1712015-11-16 17:35:44 -0800201 xml::XmlPullParser xmlParser(fin);
Adam Lesinski9f222042015-11-04 13:51:45 -0800202
203 ResourceParserOptions parserOptions;
Adam Lesinski979ccb22016-01-11 10:42:19 -0800204 parserOptions.errorOnPositionalArguments = !options.legacyMode;
Adam Lesinski9f222042015-11-04 13:51:45 -0800205
206 // If the filename includes donottranslate, then the default translatable is false.
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700207 parserOptions.translatable = pathData.name.find("donottranslate") == std::string::npos;
Adam Lesinski9f222042015-11-04 13:51:45 -0800208
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700209 ResourceParser resParser(context->getDiagnostics(), &table, pathData.source,
Adam Lesinski9f222042015-11-04 13:51:45 -0800210 pathData.config, parserOptions);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700211 if (!resParser.parse(&xmlParser)) {
212 return false;
213 }
214
215 fin.close();
216 }
217
Adam Lesinski393b5f02015-12-17 13:03:11 -0800218 if (options.pseudolocalize) {
219 // Generate pseudo-localized strings (en-XA and ar-XB).
220 // These are created as weak symbols, and are only generated from default configuration
221 // strings and plurals.
222 PseudolocaleGenerator pseudolocaleGenerator;
223 if (!pseudolocaleGenerator.consume(context, &table)) {
224 return false;
225 }
226 }
227
Adam Lesinski83f22552015-11-07 11:51:23 -0800228 // Ensure we have the compilation package at least.
229 table.createPackage(context->getCompilationPackage());
230
Adam Lesinskia40e9722015-11-24 19:11:46 -0800231 // Assign an ID to any package that has resources.
Adam Lesinski83f22552015-11-07 11:51:23 -0800232 for (auto& pkg : table.packages) {
233 if (!pkg->id) {
234 // If no package ID was set while parsing (public identifiers), auto assign an ID.
235 pkg->id = context->getPackageId();
236 }
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700237 }
238
Adam Lesinski59e04c62016-02-04 15:59:23 -0800239 // Create the file/zip entry.
Adam Lesinskia40e9722015-11-24 19:11:46 -0800240 if (!writer->startEntry(outputPath, 0)) {
241 context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to open");
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700242 return false;
243 }
244
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700245 // Make sure CopyingOutputStreamAdaptor is deleted before we call writer->finishEntry().
Adam Lesinski59e04c62016-02-04 15:59:23 -0800246 {
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700247 // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream
248 // interface.
249 CopyingOutputStreamAdaptor copyingAdaptor(writer);
Adam Lesinski59e04c62016-02-04 15:59:23 -0800250
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700251 std::unique_ptr<pb::ResourceTable> pbTable = serializeTableToPb(&table);
252 if (!pbTable->SerializeToZeroCopyStream(&copyingAdaptor)) {
Adam Lesinski59e04c62016-02-04 15:59:23 -0800253 context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to write");
254 return false;
Adam Lesinskia40e9722015-11-24 19:11:46 -0800255 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700256 }
Adam Lesinskia40e9722015-11-24 19:11:46 -0800257
Adam Lesinski59e04c62016-02-04 15:59:23 -0800258 if (!writer->finishEntry()) {
259 context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to finish entry");
260 return false;
261 }
262 return true;
263}
264
265static bool writeHeaderAndBufferToWriter(const StringPiece& outputPath, const ResourceFile& file,
266 const BigBuffer& buffer, IArchiveWriter* writer,
267 IDiagnostics* diag) {
268 // Start the entry so we can write the header.
269 if (!writer->startEntry(outputPath, 0)) {
270 diag->error(DiagMessage(outputPath) << "failed to open file");
271 return false;
272 }
273
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700274 // Make sure CopyingOutputStreamAdaptor is deleted before we call writer->finishEntry().
Adam Lesinski59e04c62016-02-04 15:59:23 -0800275 {
Adam Lesinski59e04c62016-02-04 15:59:23 -0800276 // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream
277 // interface.
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700278 CopyingOutputStreamAdaptor copyingAdaptor(writer);
279 CompiledFileOutputStream outputStream(&copyingAdaptor);
280
281 // Number of CompiledFiles.
282 outputStream.WriteLittleEndian32(1);
283
284 std::unique_ptr<pb::CompiledFile> compiledFile = serializeCompiledFileToPb(file);
285 outputStream.WriteCompiledFile(compiledFile.get());
286 outputStream.WriteData(&buffer);
287
288 if (outputStream.HadError()) {
289 diag->error(DiagMessage(outputPath) << "failed to write data");
290 return false;
Adam Lesinski59e04c62016-02-04 15:59:23 -0800291 }
292 }
293
294 if (!writer->finishEntry()) {
295 diag->error(DiagMessage(outputPath) << "failed to finish writing data");
296 return false;
297 }
298 return true;
299}
300
301static bool writeHeaderAndMmapToWriter(const StringPiece& outputPath, const ResourceFile& file,
302 const android::FileMap& map, IArchiveWriter* writer,
303 IDiagnostics* diag) {
304 // Start the entry so we can write the header.
305 if (!writer->startEntry(outputPath, 0)) {
306 diag->error(DiagMessage(outputPath) << "failed to open file");
307 return false;
308 }
309
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700310 // Make sure CopyingOutputStreamAdaptor is deleted before we call writer->finishEntry().
Adam Lesinski59e04c62016-02-04 15:59:23 -0800311 {
Adam Lesinski59e04c62016-02-04 15:59:23 -0800312 // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream
313 // interface.
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700314 CopyingOutputStreamAdaptor copyingAdaptor(writer);
315 CompiledFileOutputStream outputStream(&copyingAdaptor);
316
317 // Number of CompiledFiles.
318 outputStream.WriteLittleEndian32(1);
319
320 std::unique_ptr<pb::CompiledFile> compiledFile = serializeCompiledFileToPb(file);
321 outputStream.WriteCompiledFile(compiledFile.get());
322 outputStream.WriteData(map.getDataPtr(), map.getDataLength());
323
324 if (outputStream.HadError()) {
Adam Lesinski59e04c62016-02-04 15:59:23 -0800325 diag->error(DiagMessage(outputPath) << "failed to write data");
326 return false;
327 }
328 }
329
330 if (!writer->finishEntry()) {
331 diag->error(DiagMessage(outputPath) << "failed to finish writing data");
332 return false;
333 }
334 return true;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700335}
336
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700337static bool flattenXmlToOutStream(IAaptContext* context, const StringPiece& outputPath,
338 xml::XmlResource* xmlRes,
339 CompiledFileOutputStream* out) {
340 BigBuffer buffer(1024);
341 XmlFlattenerOptions xmlFlattenerOptions;
342 xmlFlattenerOptions.keepRawValues = true;
343 XmlFlattener flattener(&buffer, xmlFlattenerOptions);
344 if (!flattener.consume(context, xmlRes)) {
345 return false;
346 }
347
348 std::unique_ptr<pb::CompiledFile> pbCompiledFile = serializeCompiledFileToPb(xmlRes->file);
349 out->WriteCompiledFile(pbCompiledFile.get());
350 out->WriteData(&buffer);
351
352 if (out->HadError()) {
353 context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to write data");
354 return false;
355 }
356 return true;
357}
358
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700359static bool compileXml(IAaptContext* context, const CompileOptions& options,
Adam Lesinskia40e9722015-11-24 19:11:46 -0800360 const ResourcePathData& pathData, IArchiveWriter* writer,
361 const std::string& outputPath) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700362
Adam Lesinski467f1712015-11-16 17:35:44 -0800363 std::unique_ptr<xml::XmlResource> xmlRes;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700364 {
365 std::ifstream fin(pathData.source.path, std::ifstream::binary);
366 if (!fin) {
367 context->getDiagnostics()->error(DiagMessage(pathData.source) << strerror(errno));
368 return false;
369 }
370
371 xmlRes = xml::inflate(&fin, context->getDiagnostics(), pathData.source);
372
373 fin.close();
374 }
375
376 if (!xmlRes) {
377 return false;
378 }
379
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700380 xmlRes->file.name = ResourceName({}, *parseResourceType(pathData.resourceDir), pathData.name);
381 xmlRes->file.config = pathData.config;
382 xmlRes->file.source = pathData.source;
383
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700384 // Collect IDs that are defined here.
385 XmlIdCollector collector;
386 if (!collector.consume(context, xmlRes.get())) {
387 return false;
388 }
389
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700390 // Look for and process any <aapt:attr> tags and create sub-documents.
391 InlineXmlFormatParser inlineXmlFormatParser;
392 if (!inlineXmlFormatParser.consume(context, xmlRes.get())) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700393 return false;
394 }
395
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700396 // Start the entry so we can write the header.
397 if (!writer->startEntry(outputPath, 0)) {
398 context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to open file");
399 return false;
400 }
401
402 // Make sure CopyingOutputStreamAdaptor is deleted before we call writer->finishEntry().
403 {
404 // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream
405 // interface.
406 CopyingOutputStreamAdaptor copyingAdaptor(writer);
407 CompiledFileOutputStream outputStream(&copyingAdaptor);
408
409 std::vector<std::unique_ptr<xml::XmlResource>>& inlineDocuments =
410 inlineXmlFormatParser.getExtractedInlineXmlDocuments();
411
412 // Number of CompiledFiles.
413 outputStream.WriteLittleEndian32(1 + inlineDocuments.size());
414
415 if (!flattenXmlToOutStream(context, outputPath, xmlRes.get(), &outputStream)) {
416 return false;
417 }
418
419 for (auto& inlineXmlDoc : inlineDocuments) {
420 if (!flattenXmlToOutStream(context, outputPath, inlineXmlDoc.get(), &outputStream)) {
421 return false;
422 }
423 }
424 }
425
426 if (!writer->finishEntry()) {
427 context->getDiagnostics()->error(DiagMessage(outputPath)
428 << "failed to finish writing data");
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700429 return false;
430 }
Adam Lesinski59e04c62016-02-04 15:59:23 -0800431 return true;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700432}
433
434static bool compilePng(IAaptContext* context, const CompileOptions& options,
Adam Lesinskia40e9722015-11-24 19:11:46 -0800435 const ResourcePathData& pathData, IArchiveWriter* writer,
436 const std::string& outputPath) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700437 BigBuffer buffer(4096);
438 ResourceFile resFile;
Adam Lesinskia40e9722015-11-24 19:11:46 -0800439 resFile.name = ResourceName({}, *parseResourceType(pathData.resourceDir), pathData.name);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700440 resFile.config = pathData.config;
441 resFile.source = pathData.source;
442
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700443 {
444 std::ifstream fin(pathData.source.path, std::ifstream::binary);
445 if (!fin) {
446 context->getDiagnostics()->error(DiagMessage(pathData.source) << strerror(errno));
447 return false;
448 }
449
450 Png png(context->getDiagnostics());
Adam Lesinski59e04c62016-02-04 15:59:23 -0800451 if (!png.process(pathData.source, &fin, &buffer, {})) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700452 return false;
453 }
454 }
455
Adam Lesinski59e04c62016-02-04 15:59:23 -0800456 if (!writeHeaderAndBufferToWriter(outputPath, resFile, buffer, writer,
457 context->getDiagnostics())) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700458 return false;
459 }
Adam Lesinski59e04c62016-02-04 15:59:23 -0800460 return true;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700461}
462
463static bool compileFile(IAaptContext* context, const CompileOptions& options,
Adam Lesinskia40e9722015-11-24 19:11:46 -0800464 const ResourcePathData& pathData, IArchiveWriter* writer,
465 const std::string& outputPath) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700466 BigBuffer buffer(256);
467 ResourceFile resFile;
Adam Lesinskia40e9722015-11-24 19:11:46 -0800468 resFile.name = ResourceName({}, *parseResourceType(pathData.resourceDir), pathData.name);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700469 resFile.config = pathData.config;
470 resFile.source = pathData.source;
471
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700472 std::string errorStr;
473 Maybe<android::FileMap> f = file::mmapPath(pathData.source.path, &errorStr);
474 if (!f) {
475 context->getDiagnostics()->error(DiagMessage(pathData.source) << errorStr);
476 return false;
477 }
478
Adam Lesinski59e04c62016-02-04 15:59:23 -0800479 if (!writeHeaderAndMmapToWriter(outputPath, resFile, f.value(), writer,
480 context->getDiagnostics())) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700481 return false;
482 }
Adam Lesinski52364f72016-01-11 13:10:24 -0800483 return true;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700484}
485
486class CompileContext : public IAaptContext {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700487public:
Adam Lesinski355f2852016-02-13 20:26:45 -0800488 void setVerbose(bool val) {
489 mVerbose = val;
490 }
491
492 bool verbose() override {
493 return mVerbose;
494 }
495
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700496 IDiagnostics* getDiagnostics() override {
497 return &mDiagnostics;
498 }
499
500 NameMangler* getNameMangler() override {
501 abort();
502 return nullptr;
503 }
504
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700505 const std::string& getCompilationPackage() override {
506 static std::string empty;
Adam Lesinski64587af2016-02-18 18:33:06 -0800507 return empty;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700508 }
509
510 uint8_t getPackageId() override {
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700511 return 0x0;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700512 }
513
Adam Lesinski64587af2016-02-18 18:33:06 -0800514 SymbolTable* getExternalSymbols() override {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700515 abort();
516 return nullptr;
517 }
Adam Lesinski64587af2016-02-18 18:33:06 -0800518
Adam Lesinskifb6312f2016-06-28 14:40:32 -0700519 int getMinSdkVersion() override {
520 return 0;
521 }
522
Adam Lesinski64587af2016-02-18 18:33:06 -0800523private:
524 StdErrDiagnostics mDiagnostics;
525 bool mVerbose = false;
526
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700527};
528
529/**
530 * Entry point for compilation phase. Parses arguments and dispatches to the correct steps.
531 */
532int compile(const std::vector<StringPiece>& args) {
Adam Lesinski355f2852016-02-13 20:26:45 -0800533 CompileContext context;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700534 CompileOptions options;
535
Adam Lesinski355f2852016-02-13 20:26:45 -0800536 bool verbose = false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700537 Flags flags = Flags()
538 .requiredFlag("-o", "Output path", &options.outputPath)
Adam Lesinskia40e9722015-11-24 19:11:46 -0800539 .optionalFlag("--dir", "Directory to scan for resources", &options.resDir)
Adam Lesinski393b5f02015-12-17 13:03:11 -0800540 .optionalSwitch("--pseudo-localize", "Generate resources for pseudo-locales "
541 "(en-XA and ar-XB)", &options.pseudolocalize)
Adam Lesinski979ccb22016-01-11 10:42:19 -0800542 .optionalSwitch("--legacy", "Treat errors that used to be valid in AAPT as warnings",
543 &options.legacyMode)
Adam Lesinski355f2852016-02-13 20:26:45 -0800544 .optionalSwitch("-v", "Enables verbose logging", &verbose);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700545 if (!flags.parse("aapt2 compile", args, &std::cerr)) {
546 return 1;
547 }
548
Adam Lesinski355f2852016-02-13 20:26:45 -0800549 context.setVerbose(verbose);
550
Adam Lesinskia40e9722015-11-24 19:11:46 -0800551 std::unique_ptr<IArchiveWriter> archiveWriter;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700552
553 std::vector<ResourcePathData> inputData;
Adam Lesinskia40e9722015-11-24 19:11:46 -0800554 if (options.resDir) {
555 if (!flags.getArgs().empty()) {
556 // Can't have both files and a resource directory.
557 context.getDiagnostics()->error(DiagMessage() << "files given but --dir specified");
558 flags.usage("aapt2 compile", &std::cerr);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700559 return 1;
560 }
Adam Lesinskia40e9722015-11-24 19:11:46 -0800561
562 if (!loadInputFilesFromDir(&context, options, &inputData)) {
563 return 1;
564 }
565
566 archiveWriter = createZipFileArchiveWriter(context.getDiagnostics(), options.outputPath);
567
568 } else {
569 inputData.reserve(flags.getArgs().size());
570
571 // Collect data from the path for each input file.
572 for (const std::string& arg : flags.getArgs()) {
573 std::string errorStr;
574 if (Maybe<ResourcePathData> pathData = extractResourcePathData(arg, &errorStr)) {
575 inputData.push_back(std::move(pathData.value()));
576 } else {
577 context.getDiagnostics()->error(DiagMessage() << errorStr << " (" << arg << ")");
578 return 1;
579 }
580 }
581
582 archiveWriter = createDirectoryArchiveWriter(context.getDiagnostics(), options.outputPath);
583 }
584
585 if (!archiveWriter) {
586 return false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700587 }
588
589 bool error = false;
590 for (ResourcePathData& pathData : inputData) {
591 if (options.verbose) {
592 context.getDiagnostics()->note(DiagMessage(pathData.source) << "processing");
593 }
594
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700595 if (pathData.resourceDir == "values") {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700596 // Overwrite the extension.
597 pathData.extension = "arsc";
598
Adam Lesinskia40e9722015-11-24 19:11:46 -0800599 const std::string outputFilename = buildIntermediateFilename(pathData);
600 if (!compileTable(&context, options, pathData, archiveWriter.get(), outputFilename)) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700601 error = true;
602 }
603
604 } else {
Adam Lesinskia40e9722015-11-24 19:11:46 -0800605 const std::string outputFilename = buildIntermediateFilename(pathData);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700606 if (const ResourceType* type = parseResourceType(pathData.resourceDir)) {
607 if (*type != ResourceType::kRaw) {
608 if (pathData.extension == "xml") {
Adam Lesinskia40e9722015-11-24 19:11:46 -0800609 if (!compileXml(&context, options, pathData, archiveWriter.get(),
610 outputFilename)) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700611 error = true;
612 }
613 } else if (pathData.extension == "png" || pathData.extension == "9.png") {
Adam Lesinskia40e9722015-11-24 19:11:46 -0800614 if (!compilePng(&context, options, pathData, archiveWriter.get(),
615 outputFilename)) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700616 error = true;
617 }
618 } else {
Adam Lesinskia40e9722015-11-24 19:11:46 -0800619 if (!compileFile(&context, options, pathData, archiveWriter.get(),
620 outputFilename)) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700621 error = true;
622 }
623 }
624 } else {
Adam Lesinskia40e9722015-11-24 19:11:46 -0800625 if (!compileFile(&context, options, pathData, archiveWriter.get(),
626 outputFilename)) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700627 error = true;
628 }
629 }
630 } else {
631 context.getDiagnostics()->error(
632 DiagMessage() << "invalid file path '" << pathData.source << "'");
633 error = true;
634 }
635 }
636 }
637
638 if (error) {
639 return 1;
640 }
641 return 0;
642}
643
644} // namespace aapt