1 /*
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 <dirent.h>
18
19 #include <fstream>
20 #include <string>
21
22 #include "android-base/errors.h"
23 #include "android-base/file.h"
24 #include "androidfw/StringPiece.h"
25 #include "google/protobuf/io/coded_stream.h"
26 #include "google/protobuf/io/zero_copy_stream_impl_lite.h"
27
28 #include "ConfigDescription.h"
29 #include "Diagnostics.h"
30 #include "Flags.h"
31 #include "ResourceParser.h"
32 #include "ResourceTable.h"
33 #include "compile/IdAssigner.h"
34 #include "compile/InlineXmlFormatParser.h"
35 #include "compile/Png.h"
36 #include "compile/PseudolocaleGenerator.h"
37 #include "compile/XmlIdCollector.h"
38 #include "flatten/Archive.h"
39 #include "flatten/XmlFlattener.h"
40 #include "io/BigBufferOutputStream.h"
41 #include "io/Util.h"
42 #include "proto/ProtoSerialize.h"
43 #include "util/Files.h"
44 #include "util/Maybe.h"
45 #include "util/Util.h"
46 #include "xml/XmlDom.h"
47 #include "xml/XmlPullParser.h"
48
49 using android::StringPiece;
50 using google::protobuf::io::CopyingOutputStreamAdaptor;
51
52 namespace aapt {
53
54 struct ResourcePathData {
55 Source source;
56 std::string resource_dir;
57 std::string name;
58 std::string extension;
59
60 // Original config str. We keep this because when we parse the config, we may
61 // add on
62 // version qualifiers. We want to preserve the original input so the output is
63 // easily
64 // computed before hand.
65 std::string config_str;
66 ConfigDescription config;
67 };
68
69 /**
70 * Resource file paths are expected to look like:
71 * [--/res/]type[-config]/name
72 */
ExtractResourcePathData(const std::string & path,std::string * out_error)73 static Maybe<ResourcePathData> ExtractResourcePathData(const std::string& path,
74 std::string* out_error) {
75 std::vector<std::string> parts = util::Split(path, file::sDirSep);
76 if (parts.size() < 2) {
77 if (out_error) *out_error = "bad resource path";
78 return {};
79 }
80
81 std::string& dir = parts[parts.size() - 2];
82 StringPiece dir_str = dir;
83
84 StringPiece config_str;
85 ConfigDescription config;
86 size_t dash_pos = dir.find('-');
87 if (dash_pos != std::string::npos) {
88 config_str = dir_str.substr(dash_pos + 1, dir.size() - (dash_pos + 1));
89 if (!ConfigDescription::Parse(config_str, &config)) {
90 if (out_error) {
91 std::stringstream err_str;
92 err_str << "invalid configuration '" << config_str << "'";
93 *out_error = err_str.str();
94 }
95 return {};
96 }
97 dir_str = dir_str.substr(0, dash_pos);
98 }
99
100 std::string& filename = parts[parts.size() - 1];
101 StringPiece name = filename;
102 StringPiece extension;
103 size_t dot_pos = filename.find('.');
104 if (dot_pos != std::string::npos) {
105 extension = name.substr(dot_pos + 1, filename.size() - (dot_pos + 1));
106 name = name.substr(0, dot_pos);
107 }
108
109 return ResourcePathData{Source(path), dir_str.to_string(), name.to_string(),
110 extension.to_string(), config_str.to_string(), config};
111 }
112
113 struct CompileOptions {
114 std::string output_path;
115 Maybe<std::string> res_dir;
116 bool pseudolocalize = false;
117 bool no_png_crunch = false;
118 bool legacy_mode = false;
119 bool verbose = false;
120 };
121
BuildIntermediateFilename(const ResourcePathData & data)122 static std::string BuildIntermediateFilename(const ResourcePathData& data) {
123 std::stringstream name;
124 name << data.resource_dir;
125 if (!data.config_str.empty()) {
126 name << "-" << data.config_str;
127 }
128 name << "_" << data.name;
129 if (!data.extension.empty()) {
130 name << "." << data.extension;
131 }
132 name << ".flat";
133 return name.str();
134 }
135
IsHidden(const StringPiece & filename)136 static bool IsHidden(const StringPiece& filename) {
137 return util::StartsWith(filename, ".");
138 }
139
140 /**
141 * Walks the res directory structure, looking for resource files.
142 */
LoadInputFilesFromDir(IAaptContext * context,const CompileOptions & options,std::vector<ResourcePathData> * out_path_data)143 static bool LoadInputFilesFromDir(IAaptContext* context, const CompileOptions& options,
144 std::vector<ResourcePathData>* out_path_data) {
145 const std::string& root_dir = options.res_dir.value();
146 std::unique_ptr<DIR, decltype(closedir)*> d(opendir(root_dir.data()), closedir);
147 if (!d) {
148 context->GetDiagnostics()->Error(DiagMessage()
149 << android::base::SystemErrorCodeToString(errno));
150 return false;
151 }
152
153 while (struct dirent* entry = readdir(d.get())) {
154 if (IsHidden(entry->d_name)) {
155 continue;
156 }
157
158 std::string prefix_path = root_dir;
159 file::AppendPath(&prefix_path, entry->d_name);
160
161 if (file::GetFileType(prefix_path) != file::FileType::kDirectory) {
162 continue;
163 }
164
165 std::unique_ptr<DIR, decltype(closedir)*> subdir(opendir(prefix_path.data()), closedir);
166 if (!subdir) {
167 context->GetDiagnostics()->Error(DiagMessage()
168 << android::base::SystemErrorCodeToString(errno));
169 return false;
170 }
171
172 while (struct dirent* leaf_entry = readdir(subdir.get())) {
173 if (IsHidden(leaf_entry->d_name)) {
174 continue;
175 }
176
177 std::string full_path = prefix_path;
178 file::AppendPath(&full_path, leaf_entry->d_name);
179
180 std::string err_str;
181 Maybe<ResourcePathData> path_data = ExtractResourcePathData(full_path, &err_str);
182 if (!path_data) {
183 context->GetDiagnostics()->Error(DiagMessage() << err_str);
184 return false;
185 }
186
187 out_path_data->push_back(std::move(path_data.value()));
188 }
189 }
190 return true;
191 }
192
CompileTable(IAaptContext * context,const CompileOptions & options,const ResourcePathData & path_data,IArchiveWriter * writer,const std::string & output_path)193 static bool CompileTable(IAaptContext* context, const CompileOptions& options,
194 const ResourcePathData& path_data, IArchiveWriter* writer,
195 const std::string& output_path) {
196 ResourceTable table;
197 {
198 std::ifstream fin(path_data.source.path, std::ifstream::binary);
199 if (!fin) {
200 context->GetDiagnostics()->Error(DiagMessage(path_data.source)
201 << android::base::SystemErrorCodeToString(errno));
202 return false;
203 }
204
205 // Parse the values file from XML.
206 xml::XmlPullParser xml_parser(fin);
207
208 ResourceParserOptions parser_options;
209 parser_options.error_on_positional_arguments = !options.legacy_mode;
210
211 // If the filename includes donottranslate, then the default translatable is
212 // false.
213 parser_options.translatable = path_data.name.find("donottranslate") == std::string::npos;
214
215 ResourceParser res_parser(context->GetDiagnostics(), &table, path_data.source, path_data.config,
216 parser_options);
217 if (!res_parser.Parse(&xml_parser)) {
218 return false;
219 }
220
221 fin.close();
222 }
223
224 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 pseudolocale_generator;
230 if (!pseudolocale_generator.Consume(context, &table)) {
231 return false;
232 }
233 }
234
235 // 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();
244 }
245 }
246
247 // Create the file/zip entry.
248 if (!writer->StartEntry(output_path, 0)) {
249 context->GetDiagnostics()->Error(DiagMessage(output_path) << "failed to open");
250 return false;
251 }
252
253 // Make sure CopyingOutputStreamAdaptor is deleted before we call
254 // writer->FinishEntry().
255 {
256 // Wrap our IArchiveWriter with an adaptor that implements the
257 // ZeroCopyOutputStream interface.
258 CopyingOutputStreamAdaptor copying_adaptor(writer);
259
260 std::unique_ptr<pb::ResourceTable> pb_table = SerializeTableToPb(&table);
261 if (!pb_table->SerializeToZeroCopyStream(©ing_adaptor)) {
262 context->GetDiagnostics()->Error(DiagMessage(output_path) << "failed to write");
263 return false;
264 }
265 }
266
267 if (!writer->FinishEntry()) {
268 context->GetDiagnostics()->Error(DiagMessage(output_path) << "failed to finish entry");
269 return false;
270 }
271 return true;
272 }
273
WriteHeaderAndBufferToWriter(const StringPiece & output_path,const ResourceFile & file,const BigBuffer & buffer,IArchiveWriter * writer,IDiagnostics * diag)274 static bool WriteHeaderAndBufferToWriter(const StringPiece& output_path, const ResourceFile& file,
275 const BigBuffer& buffer, IArchiveWriter* writer,
276 IDiagnostics* diag) {
277 // Start the entry so we can write the header.
278 if (!writer->StartEntry(output_path, 0)) {
279 diag->Error(DiagMessage(output_path) << "failed to open file");
280 return false;
281 }
282
283 // Make sure CopyingOutputStreamAdaptor is deleted before we call
284 // writer->FinishEntry().
285 {
286 // Wrap our IArchiveWriter with an adaptor that implements the
287 // ZeroCopyOutputStream interface.
288 CopyingOutputStreamAdaptor copying_adaptor(writer);
289 CompiledFileOutputStream output_stream(©ing_adaptor);
290
291 // Number of CompiledFiles.
292 output_stream.WriteLittleEndian32(1);
293
294 std::unique_ptr<pb::CompiledFile> compiled_file = SerializeCompiledFileToPb(file);
295 output_stream.WriteCompiledFile(compiled_file.get());
296 output_stream.WriteData(&buffer);
297
298 if (output_stream.HadError()) {
299 diag->Error(DiagMessage(output_path) << "failed to write data");
300 return false;
301 }
302 }
303
304 if (!writer->FinishEntry()) {
305 diag->Error(DiagMessage(output_path) << "failed to finish writing data");
306 return false;
307 }
308 return true;
309 }
310
WriteHeaderAndMmapToWriter(const StringPiece & output_path,const ResourceFile & file,const android::FileMap & map,IArchiveWriter * writer,IDiagnostics * diag)311 static bool WriteHeaderAndMmapToWriter(const StringPiece& output_path, const ResourceFile& file,
312 const android::FileMap& map, IArchiveWriter* writer,
313 IDiagnostics* diag) {
314 // Start the entry so we can write the header.
315 if (!writer->StartEntry(output_path, 0)) {
316 diag->Error(DiagMessage(output_path) << "failed to open file");
317 return false;
318 }
319
320 // Make sure CopyingOutputStreamAdaptor is deleted before we call
321 // writer->FinishEntry().
322 {
323 // Wrap our IArchiveWriter with an adaptor that implements the
324 // ZeroCopyOutputStream interface.
325 CopyingOutputStreamAdaptor copying_adaptor(writer);
326 CompiledFileOutputStream output_stream(©ing_adaptor);
327
328 // Number of CompiledFiles.
329 output_stream.WriteLittleEndian32(1);
330
331 std::unique_ptr<pb::CompiledFile> compiled_file = SerializeCompiledFileToPb(file);
332 output_stream.WriteCompiledFile(compiled_file.get());
333 output_stream.WriteData(map.getDataPtr(), map.getDataLength());
334
335 if (output_stream.HadError()) {
336 diag->Error(DiagMessage(output_path) << "failed to write data");
337 return false;
338 }
339 }
340
341 if (!writer->FinishEntry()) {
342 diag->Error(DiagMessage(output_path) << "failed to finish writing data");
343 return false;
344 }
345 return true;
346 }
347
FlattenXmlToOutStream(IAaptContext * context,const StringPiece & output_path,xml::XmlResource * xmlres,CompiledFileOutputStream * out)348 static bool FlattenXmlToOutStream(IAaptContext* context, const StringPiece& output_path,
349 xml::XmlResource* xmlres, CompiledFileOutputStream* out) {
350 BigBuffer buffer(1024);
351 XmlFlattenerOptions xml_flattener_options;
352 xml_flattener_options.keep_raw_values = true;
353 XmlFlattener flattener(&buffer, xml_flattener_options);
354 if (!flattener.Consume(context, xmlres)) {
355 return false;
356 }
357
358 std::unique_ptr<pb::CompiledFile> pb_compiled_file = SerializeCompiledFileToPb(xmlres->file);
359 out->WriteCompiledFile(pb_compiled_file.get());
360 out->WriteData(&buffer);
361
362 if (out->HadError()) {
363 context->GetDiagnostics()->Error(DiagMessage(output_path) << "failed to write data");
364 return false;
365 }
366 return true;
367 }
368
IsValidFile(IAaptContext * context,const StringPiece & input_path)369 static bool IsValidFile(IAaptContext* context, const StringPiece& input_path) {
370 const file::FileType file_type = file::GetFileType(input_path);
371 if (file_type != file::FileType::kRegular && file_type != file::FileType::kSymlink) {
372 if (file_type == file::FileType::kDirectory) {
373 context->GetDiagnostics()->Error(DiagMessage(input_path)
374 << "resource file cannot be a directory");
375 } else if (file_type == file::FileType::kNonexistant) {
376 context->GetDiagnostics()->Error(DiagMessage(input_path) << "file not found");
377 } else {
378 context->GetDiagnostics()->Error(DiagMessage(input_path)
379 << "not a valid resource file");
380 }
381 return false;
382 }
383 return true;
384 }
385
CompileXml(IAaptContext * context,const CompileOptions & options,const ResourcePathData & path_data,IArchiveWriter * writer,const std::string & output_path)386 static bool CompileXml(IAaptContext* context, const CompileOptions& options,
387 const ResourcePathData& path_data, IArchiveWriter* writer,
388 const std::string& output_path) {
389 if (context->IsVerbose()) {
390 context->GetDiagnostics()->Note(DiagMessage(path_data.source) << "compiling XML");
391 }
392
393 std::unique_ptr<xml::XmlResource> xmlres;
394 {
395 std::ifstream fin(path_data.source.path, std::ifstream::binary);
396 if (!fin) {
397 context->GetDiagnostics()->Error(DiagMessage(path_data.source)
398 << android::base::SystemErrorCodeToString(errno));
399 return false;
400 }
401
402 xmlres = xml::Inflate(&fin, context->GetDiagnostics(), path_data.source);
403
404 fin.close();
405 }
406
407 if (!xmlres) {
408 return false;
409 }
410
411 xmlres->file.name = ResourceName({}, *ParseResourceType(path_data.resource_dir), path_data.name);
412 xmlres->file.config = path_data.config;
413 xmlres->file.source = path_data.source;
414
415 // Collect IDs that are defined here.
416 XmlIdCollector collector;
417 if (!collector.Consume(context, xmlres.get())) {
418 return false;
419 }
420
421 // Look for and process any <aapt:attr> tags and create sub-documents.
422 InlineXmlFormatParser inline_xml_format_parser;
423 if (!inline_xml_format_parser.Consume(context, xmlres.get())) {
424 return false;
425 }
426
427 // Start the entry so we can write the header.
428 if (!writer->StartEntry(output_path, 0)) {
429 context->GetDiagnostics()->Error(DiagMessage(output_path) << "failed to open file");
430 return false;
431 }
432
433 // Make sure CopyingOutputStreamAdaptor is deleted before we call
434 // writer->FinishEntry().
435 {
436 // Wrap our IArchiveWriter with an adaptor that implements the
437 // ZeroCopyOutputStream
438 // interface.
439 CopyingOutputStreamAdaptor copying_adaptor(writer);
440 CompiledFileOutputStream output_stream(©ing_adaptor);
441
442 std::vector<std::unique_ptr<xml::XmlResource>>& inline_documents =
443 inline_xml_format_parser.GetExtractedInlineXmlDocuments();
444
445 // Number of CompiledFiles.
446 output_stream.WriteLittleEndian32(1 + inline_documents.size());
447
448 if (!FlattenXmlToOutStream(context, output_path, xmlres.get(), &output_stream)) {
449 return false;
450 }
451
452 for (auto& inline_xml_doc : inline_documents) {
453 if (!FlattenXmlToOutStream(context, output_path, inline_xml_doc.get(), &output_stream)) {
454 return false;
455 }
456 }
457 }
458
459 if (!writer->FinishEntry()) {
460 context->GetDiagnostics()->Error(DiagMessage(output_path) << "failed to finish writing data");
461 return false;
462 }
463 return true;
464 }
465
CompilePng(IAaptContext * context,const CompileOptions & options,const ResourcePathData & path_data,IArchiveWriter * writer,const std::string & output_path)466 static bool CompilePng(IAaptContext* context, const CompileOptions& options,
467 const ResourcePathData& path_data, IArchiveWriter* writer,
468 const std::string& output_path) {
469 if (context->IsVerbose()) {
470 context->GetDiagnostics()->Note(DiagMessage(path_data.source) << "compiling PNG");
471 }
472
473 BigBuffer buffer(4096);
474 ResourceFile res_file;
475 res_file.name = ResourceName({}, *ParseResourceType(path_data.resource_dir), path_data.name);
476 res_file.config = path_data.config;
477 res_file.source = path_data.source;
478
479 {
480 std::string content;
481 if (!android::base::ReadFileToString(path_data.source.path, &content,
482 true /*follow_symlinks*/)) {
483 context->GetDiagnostics()->Error(DiagMessage(path_data.source)
484 << android::base::SystemErrorCodeToString(errno));
485 return false;
486 }
487
488 BigBuffer crunched_png_buffer(4096);
489 io::BigBufferOutputStream crunched_png_buffer_out(&crunched_png_buffer);
490
491 // Ensure that we only keep the chunks we care about if we end up
492 // using the original PNG instead of the crunched one.
493 PngChunkFilter png_chunk_filter(content);
494 std::unique_ptr<Image> image = ReadPng(context, path_data.source, &png_chunk_filter);
495 if (!image) {
496 return false;
497 }
498
499 std::unique_ptr<NinePatch> nine_patch;
500 if (path_data.extension == "9.png") {
501 std::string err;
502 nine_patch = NinePatch::Create(image->rows.get(), image->width, image->height, &err);
503 if (!nine_patch) {
504 context->GetDiagnostics()->Error(DiagMessage() << err);
505 return false;
506 }
507
508 // Remove the 1px border around the NinePatch.
509 // Basically the row array is shifted up by 1, and the length is treated
510 // as height - 2.
511 // For each row, shift the array to the left by 1, and treat the length as
512 // width - 2.
513 image->width -= 2;
514 image->height -= 2;
515 memmove(image->rows.get(), image->rows.get() + 1, image->height * sizeof(uint8_t**));
516 for (int32_t h = 0; h < image->height; h++) {
517 memmove(image->rows[h], image->rows[h] + 4, image->width * 4);
518 }
519
520 if (context->IsVerbose()) {
521 context->GetDiagnostics()->Note(DiagMessage(path_data.source) << "9-patch: "
522 << *nine_patch);
523 }
524 }
525
526 // Write the crunched PNG.
527 if (!WritePng(context, image.get(), nine_patch.get(), &crunched_png_buffer_out, {})) {
528 return false;
529 }
530
531 if (nine_patch != nullptr ||
532 crunched_png_buffer_out.ByteCount() <= png_chunk_filter.ByteCount()) {
533 // No matter what, we must use the re-encoded PNG, even if it is larger.
534 // 9-patch images must be re-encoded since their borders are stripped.
535 buffer.AppendBuffer(std::move(crunched_png_buffer));
536 } else {
537 // The re-encoded PNG is larger than the original, and there is
538 // no mandatory transformation. Use the original.
539 if (context->IsVerbose()) {
540 context->GetDiagnostics()->Note(DiagMessage(path_data.source)
541 << "original PNG is smaller than crunched PNG"
542 << ", using original");
543 }
544
545 png_chunk_filter.Rewind();
546 BigBuffer filtered_png_buffer(4096);
547 io::BigBufferOutputStream filtered_png_buffer_out(&filtered_png_buffer);
548 io::Copy(&filtered_png_buffer_out, &png_chunk_filter);
549 buffer.AppendBuffer(std::move(filtered_png_buffer));
550 }
551
552 if (context->IsVerbose()) {
553 // For debugging only, use the legacy PNG cruncher and compare the resulting file sizes.
554 // This will help catch exotic cases where the new code may generate larger PNGs.
555 std::stringstream legacy_stream(content);
556 BigBuffer legacy_buffer(4096);
557 Png png(context->GetDiagnostics());
558 if (!png.process(path_data.source, &legacy_stream, &legacy_buffer, {})) {
559 return false;
560 }
561
562 context->GetDiagnostics()->Note(DiagMessage(path_data.source)
563 << "legacy=" << legacy_buffer.size()
564 << " new=" << buffer.size());
565 }
566 }
567
568 if (!WriteHeaderAndBufferToWriter(output_path, res_file, buffer, writer,
569 context->GetDiagnostics())) {
570 return false;
571 }
572 return true;
573 }
574
CompileFile(IAaptContext * context,const CompileOptions & options,const ResourcePathData & path_data,IArchiveWriter * writer,const std::string & output_path)575 static bool CompileFile(IAaptContext* context, const CompileOptions& options,
576 const ResourcePathData& path_data, IArchiveWriter* writer,
577 const std::string& output_path) {
578 if (context->IsVerbose()) {
579 context->GetDiagnostics()->Note(DiagMessage(path_data.source) << "compiling file");
580 }
581
582 BigBuffer buffer(256);
583 ResourceFile res_file;
584 res_file.name = ResourceName({}, *ParseResourceType(path_data.resource_dir), path_data.name);
585 res_file.config = path_data.config;
586 res_file.source = path_data.source;
587
588 std::string error_str;
589 Maybe<android::FileMap> f = file::MmapPath(path_data.source.path, &error_str);
590 if (!f) {
591 context->GetDiagnostics()->Error(DiagMessage(path_data.source) << "failed to mmap file: "
592 << error_str);
593 return false;
594 }
595
596 if (!WriteHeaderAndMmapToWriter(output_path, res_file, f.value(), writer,
597 context->GetDiagnostics())) {
598 return false;
599 }
600 return true;
601 }
602
603 class CompileContext : public IAaptContext {
604 public:
CompileContext(IDiagnostics * diagnostics)605 CompileContext(IDiagnostics* diagnostics) : diagnostics_(diagnostics) {
606 }
607
GetPackageType()608 PackageType GetPackageType() override {
609 // Every compilation unit starts as an app and then gets linked as potentially something else.
610 return PackageType::kApp;
611 }
612
SetVerbose(bool val)613 void SetVerbose(bool val) {
614 verbose_ = val;
615 }
616
IsVerbose()617 bool IsVerbose() override {
618 return verbose_;
619 }
620
GetDiagnostics()621 IDiagnostics* GetDiagnostics() override {
622 return diagnostics_;
623 }
624
GetNameMangler()625 NameMangler* GetNameMangler() override {
626 abort();
627 return nullptr;
628 }
629
GetCompilationPackage()630 const std::string& GetCompilationPackage() override {
631 static std::string empty;
632 return empty;
633 }
634
GetPackageId()635 uint8_t GetPackageId() override {
636 return 0x0;
637 }
638
GetExternalSymbols()639 SymbolTable* GetExternalSymbols() override {
640 abort();
641 return nullptr;
642 }
643
GetMinSdkVersion()644 int GetMinSdkVersion() override {
645 return 0;
646 }
647
648 private:
649 IDiagnostics* diagnostics_;
650 bool verbose_ = false;
651 };
652
653 /**
654 * Entry point for compilation phase. Parses arguments and dispatches to the
655 * correct steps.
656 */
Compile(const std::vector<StringPiece> & args,IDiagnostics * diagnostics)657 int Compile(const std::vector<StringPiece>& args, IDiagnostics* diagnostics) {
658 CompileContext context(diagnostics);
659 CompileOptions options;
660
661 bool verbose = false;
662 Flags flags =
663 Flags()
664 .RequiredFlag("-o", "Output path", &options.output_path)
665 .OptionalFlag("--dir", "Directory to scan for resources", &options.res_dir)
666 .OptionalSwitch("--pseudo-localize",
667 "Generate resources for pseudo-locales "
668 "(en-XA and ar-XB)",
669 &options.pseudolocalize)
670 .OptionalSwitch("--no-crunch", "Disables PNG processing", &options.no_png_crunch)
671 .OptionalSwitch("--legacy", "Treat errors that used to be valid in AAPT as warnings",
672 &options.legacy_mode)
673 .OptionalSwitch("-v", "Enables verbose logging", &verbose);
674 if (!flags.Parse("aapt2 compile", args, &std::cerr)) {
675 return 1;
676 }
677
678 context.SetVerbose(verbose);
679
680 std::unique_ptr<IArchiveWriter> archive_writer;
681
682 std::vector<ResourcePathData> input_data;
683 if (options.res_dir) {
684 if (!flags.GetArgs().empty()) {
685 // Can't have both files and a resource directory.
686 context.GetDiagnostics()->Error(DiagMessage() << "files given but --dir specified");
687 flags.Usage("aapt2 compile", &std::cerr);
688 return 1;
689 }
690
691 if (!LoadInputFilesFromDir(&context, options, &input_data)) {
692 return 1;
693 }
694
695 archive_writer = CreateZipFileArchiveWriter(context.GetDiagnostics(), options.output_path);
696
697 } else {
698 input_data.reserve(flags.GetArgs().size());
699
700 // Collect data from the path for each input file.
701 for (const std::string& arg : flags.GetArgs()) {
702 std::string error_str;
703 if (Maybe<ResourcePathData> path_data = ExtractResourcePathData(arg, &error_str)) {
704 input_data.push_back(std::move(path_data.value()));
705 } else {
706 context.GetDiagnostics()->Error(DiagMessage() << error_str << " (" << arg << ")");
707 return 1;
708 }
709 }
710
711 archive_writer = CreateDirectoryArchiveWriter(context.GetDiagnostics(), options.output_path);
712 }
713
714 if (!archive_writer) {
715 return 1;
716 }
717
718 bool error = false;
719 for (ResourcePathData& path_data : input_data) {
720 if (options.verbose) {
721 context.GetDiagnostics()->Note(DiagMessage(path_data.source) << "processing");
722 }
723
724 if (!IsValidFile(&context, path_data.source.path)) {
725 error = true;
726 continue;
727 }
728
729 if (path_data.resource_dir == "values") {
730 // Overwrite the extension.
731 path_data.extension = "arsc";
732
733 const std::string output_filename = BuildIntermediateFilename(path_data);
734 if (!CompileTable(&context, options, path_data, archive_writer.get(), output_filename)) {
735 error = true;
736 }
737
738 } else {
739 const std::string output_filename = BuildIntermediateFilename(path_data);
740 if (const ResourceType* type = ParseResourceType(path_data.resource_dir)) {
741 if (*type != ResourceType::kRaw) {
742 if (path_data.extension == "xml") {
743 if (!CompileXml(&context, options, path_data, archive_writer.get(), output_filename)) {
744 error = true;
745 }
746 } else if (!options.no_png_crunch &&
747 (path_data.extension == "png" || path_data.extension == "9.png")) {
748 if (!CompilePng(&context, options, path_data, archive_writer.get(), output_filename)) {
749 error = true;
750 }
751 } else {
752 if (!CompileFile(&context, options, path_data, archive_writer.get(), output_filename)) {
753 error = true;
754 }
755 }
756 } else {
757 if (!CompileFile(&context, options, path_data, archive_writer.get(), output_filename)) {
758 error = true;
759 }
760 }
761 } else {
762 context.GetDiagnostics()->Error(DiagMessage() << "invalid file path '" << path_data.source
763 << "'");
764 error = true;
765 }
766 }
767 }
768
769 if (error) {
770 return 1;
771 }
772 return 0;
773 }
774
775 } // namespace aapt
776