• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 "Compile.h"
18 
19 #include <dirent.h>
20 #include <string>
21 
22 #include "android-base/errors.h"
23 #include "android-base/file.h"
24 #include "android-base/utf8.h"
25 #include "androidfw/ConfigDescription.h"
26 #include "androidfw/StringPiece.h"
27 #include "google/protobuf/io/coded_stream.h"
28 #include "google/protobuf/io/zero_copy_stream_impl_lite.h"
29 
30 #include "Diagnostics.h"
31 #include "ResourceParser.h"
32 #include "ResourceTable.h"
33 #include "cmd/Util.h"
34 #include "compile/IdAssigner.h"
35 #include "compile/InlineXmlFormatParser.h"
36 #include "compile/Png.h"
37 #include "compile/PseudolocaleGenerator.h"
38 #include "compile/XmlIdCollector.h"
39 #include "format/Archive.h"
40 #include "format/Container.h"
41 #include "format/proto/ProtoSerialize.h"
42 #include "io/BigBufferStream.h"
43 #include "io/FileStream.h"
44 #include "io/FileSystem.h"
45 #include "io/StringStream.h"
46 #include "io/Util.h"
47 #include "io/ZipArchive.h"
48 #include "trace/TraceBuffer.h"
49 #include "util/Files.h"
50 #include "util/Util.h"
51 #include "xml/XmlDom.h"
52 #include "xml/XmlPullParser.h"
53 
54 using ::aapt::io::FileInputStream;
55 using ::aapt::text::Printer;
56 using ::android::ConfigDescription;
57 using ::android::StringPiece;
58 using ::android::base::SystemErrorCodeToString;
59 using ::google::protobuf::io::CopyingOutputStreamAdaptor;
60 
61 namespace aapt {
62 
63 struct ResourcePathData {
64   Source source;
65   std::string resource_dir;
66   std::string name;
67   std::string extension;
68 
69   // Original config str. We keep this because when we parse the config, we may add on
70   // version qualifiers. We want to preserve the original input so the output is easily
71   // computed before hand.
72   std::string config_str;
73   ConfigDescription config;
74 };
75 
76 // Resource file paths are expected to look like: [--/res/]type[-config]/name
ExtractResourcePathData(const std::string & path,const char dir_sep,std::string * out_error,const CompileOptions & options)77 static std::optional<ResourcePathData> ExtractResourcePathData(const std::string& path,
78                                                                const char dir_sep,
79                                                                std::string* out_error,
80                                                                const CompileOptions& options) {
81   std::vector<std::string> parts = util::Split(path, dir_sep);
82   if (parts.size() < 2) {
83     if (out_error) *out_error = "bad resource path";
84     return {};
85   }
86 
87   std::string& dir = parts[parts.size() - 2];
88   StringPiece dir_str = dir;
89 
90   StringPiece config_str;
91   ConfigDescription config;
92   size_t dash_pos = dir.find('-');
93   if (dash_pos != std::string::npos) {
94     config_str = dir_str.substr(dash_pos + 1, dir.size() - (dash_pos + 1));
95     if (!ConfigDescription::Parse(config_str, &config)) {
96       if (out_error) {
97         std::stringstream err_str;
98         err_str << "invalid configuration '" << config_str << "'";
99         *out_error = err_str.str();
100       }
101       return {};
102     }
103     dir_str = dir_str.substr(0, dash_pos);
104   }
105 
106   std::string& filename = parts[parts.size() - 1];
107   StringPiece name = filename;
108   StringPiece extension;
109 
110   const std::string kNinePng = ".9.png";
111   if (filename.size() > kNinePng.size()
112       && std::equal(kNinePng.rbegin(), kNinePng.rend(), filename.rbegin())) {
113     // Split on .9.png if this extension is present at the end of the file path
114     name = name.substr(0, filename.size() - kNinePng.size());
115     extension = "9.png";
116   } else {
117     // Split on the last period occurrence
118     size_t dot_pos = filename.rfind('.');
119     if (dot_pos != std::string::npos) {
120       extension = name.substr(dot_pos + 1, filename.size() - (dot_pos + 1));
121       name = name.substr(0, dot_pos);
122     }
123   }
124 
125   const Source res_path = options.source_path
126       ? StringPiece(options.source_path.value())
127       : StringPiece(path);
128 
129   return ResourcePathData{res_path, dir_str.to_string(), name.to_string(),
130                           extension.to_string(), config_str.to_string(), config};
131 }
132 
BuildIntermediateContainerFilename(const ResourcePathData & data)133 static std::string BuildIntermediateContainerFilename(const ResourcePathData& data) {
134   std::stringstream name;
135   name << data.resource_dir;
136   if (!data.config_str.empty()) {
137     name << "-" << data.config_str;
138   }
139   name << "_" << data.name;
140   if (!data.extension.empty()) {
141     name << "." << data.extension;
142   }
143   name << ".flat";
144   return name.str();
145 }
146 
CompileTable(IAaptContext * context,const CompileOptions & options,const ResourcePathData & path_data,io::IFile * file,IArchiveWriter * writer,const std::string & output_path)147 static bool CompileTable(IAaptContext* context, const CompileOptions& options,
148                          const ResourcePathData& path_data, io::IFile* file, IArchiveWriter* writer,
149                          const std::string& output_path) {
150   TRACE_CALL();
151   // Filenames starting with "donottranslate" are not localizable
152   bool translatable_file = path_data.name.find("donottranslate") != 0;
153   ResourceTable table;
154   {
155     auto fin = file->OpenInputStream();
156     if (fin->HadError()) {
157       context->GetDiagnostics()->Error(DiagMessage(path_data.source)
158           << "failed to open file: " << fin->GetError());
159       return false;
160     }
161 
162     // Parse the values file from XML.
163     xml::XmlPullParser xml_parser(fin.get());
164 
165     ResourceParserOptions parser_options;
166     parser_options.error_on_positional_arguments = !options.legacy_mode;
167     parser_options.preserve_visibility_of_styleables = options.preserve_visibility_of_styleables;
168     parser_options.translatable = translatable_file;
169 
170     // If visibility was forced, we need to use it when creating a new resource and also error if
171     // we try to parse the <public>, <public-group>, <java-symbol> or <symbol> tags.
172     parser_options.visibility = options.visibility;
173 
174     ResourceParser res_parser(context->GetDiagnostics(), &table, path_data.source, path_data.config,
175         parser_options);
176     if (!res_parser.Parse(&xml_parser)) {
177       return false;
178     }
179   }
180 
181   if (options.pseudolocalize && translatable_file) {
182     // Generate pseudo-localized strings (en-XA and ar-XB).
183     // These are created as weak symbols, and are only generated from default
184     // configuration
185     // strings and plurals.
186     PseudolocaleGenerator pseudolocale_generator;
187     if (!pseudolocale_generator.Consume(context, &table)) {
188       return false;
189     }
190   }
191 
192   // Create the file/zip entry.
193   if (!writer->StartEntry(output_path, 0)) {
194     context->GetDiagnostics()->Error(DiagMessage(output_path) << "failed to open");
195     return false;
196   }
197 
198   // Make sure CopyingOutputStreamAdaptor is deleted before we call writer->FinishEntry().
199   {
200     // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream interface.
201     CopyingOutputStreamAdaptor copying_adaptor(writer);
202     ContainerWriter container_writer(&copying_adaptor, 1u);
203 
204     pb::ResourceTable pb_table;
205     SerializeTableToPb(table, &pb_table, context->GetDiagnostics());
206     if (!container_writer.AddResTableEntry(pb_table)) {
207       context->GetDiagnostics()->Error(DiagMessage(output_path) << "failed to write");
208       return false;
209     }
210   }
211 
212   if (!writer->FinishEntry()) {
213     context->GetDiagnostics()->Error(DiagMessage(output_path) << "failed to finish entry");
214     return false;
215   }
216 
217   if (options.generate_text_symbols_path) {
218     io::FileOutputStream fout_text(options.generate_text_symbols_path.value());
219 
220     if (fout_text.HadError()) {
221       context->GetDiagnostics()->Error(DiagMessage()
222                                        << "failed writing to'"
223                                        << options.generate_text_symbols_path.value()
224                                        << "': " << fout_text.GetError());
225       return false;
226     }
227 
228     Printer r_txt_printer(&fout_text);
229     for (const auto& package : table.packages) {
230       // Only print resources defined locally, e.g. don't write android attributes.
231       if (package->name.empty()) {
232         for (const auto& type : package->types) {
233           for (const auto& entry : type->entries) {
234             // Check access modifiers.
235             switch (entry->visibility.level) {
236               case Visibility::Level::kUndefined :
237                 r_txt_printer.Print("default ");
238                 break;
239               case Visibility::Level::kPublic :
240                 r_txt_printer.Print("public ");
241                 break;
242               case Visibility::Level::kPrivate :
243                 r_txt_printer.Print("private ");
244             }
245 
246             if (type->type != ResourceType::kStyleable) {
247               r_txt_printer.Print("int ");
248               r_txt_printer.Print(to_string(type->type));
249               r_txt_printer.Print(" ");
250               r_txt_printer.Println(entry->name);
251             } else {
252               r_txt_printer.Print("int[] styleable ");
253               r_txt_printer.Println(entry->name);
254 
255               if (!entry->values.empty()) {
256                 auto styleable =
257                     static_cast<const Styleable*>(entry->values.front()->value.get());
258                 for (const auto& attr : styleable->entries) {
259                   // The visibility of the children under the styleable does not matter as they are
260                   // nested under their parent and use its visibility.
261                   r_txt_printer.Print("default int styleable ");
262                   r_txt_printer.Print(entry->name);
263                   // If the package name is present, also include it in the mangled name (e.g.
264                   // "android")
265                   if (!attr.name.value().package.empty()) {
266                     r_txt_printer.Print("_");
267                     r_txt_printer.Print(MakePackageSafeName(attr.name.value().package));
268                   }
269                   r_txt_printer.Print("_");
270                   r_txt_printer.Println(attr.name.value().entry);
271                 }
272               }
273             }
274           }
275         }
276       }
277     }
278   }
279 
280   return true;
281 }
282 
WriteHeaderAndDataToWriter(const StringPiece & output_path,const ResourceFile & file,io::KnownSizeInputStream * in,IArchiveWriter * writer,IDiagnostics * diag)283 static bool WriteHeaderAndDataToWriter(const StringPiece& output_path, const ResourceFile& file,
284                                        io::KnownSizeInputStream* in, IArchiveWriter* writer,
285                                        IDiagnostics* diag) {
286   TRACE_CALL();
287   // Start the entry so we can write the header.
288   if (!writer->StartEntry(output_path, 0)) {
289     diag->Error(DiagMessage(output_path) << "failed to open file");
290     return false;
291   }
292 
293   // Make sure CopyingOutputStreamAdaptor is deleted before we call writer->FinishEntry().
294   {
295     // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream interface.
296     CopyingOutputStreamAdaptor copying_adaptor(writer);
297     ContainerWriter container_writer(&copying_adaptor, 1u);
298 
299     pb::internal::CompiledFile pb_compiled_file;
300     SerializeCompiledFileToPb(file, &pb_compiled_file);
301 
302     if (!container_writer.AddResFileEntry(pb_compiled_file, in)) {
303       diag->Error(DiagMessage(output_path) << "failed to write entry data");
304       return false;
305     }
306   }
307 
308   if (!writer->FinishEntry()) {
309     diag->Error(DiagMessage(output_path) << "failed to finish writing data");
310     return false;
311   }
312   return true;
313 }
314 
FlattenXmlToOutStream(const StringPiece & output_path,const xml::XmlResource & xmlres,ContainerWriter * container_writer,IDiagnostics * diag)315 static bool FlattenXmlToOutStream(const StringPiece& output_path, const xml::XmlResource& xmlres,
316                                   ContainerWriter* container_writer, IDiagnostics* diag) {
317   pb::internal::CompiledFile pb_compiled_file;
318   SerializeCompiledFileToPb(xmlres.file, &pb_compiled_file);
319 
320   pb::XmlNode pb_xml_node;
321   SerializeXmlToPb(*xmlres.root, &pb_xml_node);
322 
323   std::string serialized_xml = pb_xml_node.SerializeAsString();
324   io::StringInputStream serialized_in(serialized_xml);
325 
326   if (!container_writer->AddResFileEntry(pb_compiled_file, &serialized_in)) {
327     diag->Error(DiagMessage(output_path) << "failed to write entry data");
328     return false;
329   }
330   return true;
331 }
332 
IsValidFile(IAaptContext * context,const std::string & input_path)333 static bool IsValidFile(IAaptContext* context, const std::string& input_path) {
334   const file::FileType file_type = file::GetFileType(input_path);
335   if (file_type != file::FileType::kRegular && file_type != file::FileType::kSymlink) {
336     if (file_type == file::FileType::kDirectory) {
337       context->GetDiagnostics()->Error(DiagMessage(input_path)
338                                        << "resource file cannot be a directory");
339     } else if (file_type == file::FileType::kNonExistant) {
340       context->GetDiagnostics()->Error(DiagMessage(input_path) << "file not found");
341     } else {
342       context->GetDiagnostics()->Error(DiagMessage(input_path)
343                                        << "not a valid resource file");
344     }
345     return false;
346   }
347   return true;
348 }
349 
CompileXml(IAaptContext * context,const CompileOptions & options,const ResourcePathData & path_data,io::IFile * file,IArchiveWriter * writer,const std::string & output_path)350 static bool CompileXml(IAaptContext* context, const CompileOptions& options,
351                        const ResourcePathData& path_data, io::IFile* file, IArchiveWriter* writer,
352                        const std::string& output_path) {
353   TRACE_CALL();
354   if (context->IsVerbose()) {
355     context->GetDiagnostics()->Note(DiagMessage(path_data.source) << "compiling XML");
356   }
357 
358   std::unique_ptr<xml::XmlResource> xmlres;
359   {
360     auto fin = file->OpenInputStream();
361     if (fin->HadError()) {
362       context->GetDiagnostics()->Error(DiagMessage(path_data.source)
363                                        << "failed to open file: " << fin->GetError());
364       return false;
365     }
366 
367     xmlres = xml::Inflate(fin.get(), context->GetDiagnostics(), path_data.source);
368     if (!xmlres) {
369       return false;
370     }
371   }
372 
373   xmlres->file.name = ResourceName({}, *ParseResourceType(path_data.resource_dir), path_data.name);
374   xmlres->file.config = path_data.config;
375   xmlres->file.source = path_data.source;
376   xmlres->file.type = ResourceFile::Type::kProtoXml;
377 
378   // Collect IDs that are defined here.
379   XmlIdCollector collector;
380   if (!collector.Consume(context, xmlres.get())) {
381     return false;
382   }
383 
384   // Look for and process any <aapt:attr> tags and create sub-documents.
385   InlineXmlFormatParser inline_xml_format_parser;
386   if (!inline_xml_format_parser.Consume(context, xmlres.get())) {
387     return false;
388   }
389 
390   // Start the entry so we can write the header.
391   if (!writer->StartEntry(output_path, 0)) {
392     context->GetDiagnostics()->Error(DiagMessage(output_path) << "failed to open file");
393     return false;
394   }
395 
396   std::vector<std::unique_ptr<xml::XmlResource>>& inline_documents =
397       inline_xml_format_parser.GetExtractedInlineXmlDocuments();
398 
399   // Make sure CopyingOutputStreamAdaptor is deleted before we call writer->FinishEntry().
400   {
401     // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream interface.
402     CopyingOutputStreamAdaptor copying_adaptor(writer);
403     ContainerWriter container_writer(&copying_adaptor, 1u + inline_documents.size());
404 
405     if (!FlattenXmlToOutStream(output_path, *xmlres, &container_writer,
406                                context->GetDiagnostics())) {
407       return false;
408     }
409 
410     for (const std::unique_ptr<xml::XmlResource>& inline_xml_doc : inline_documents) {
411       if (!FlattenXmlToOutStream(output_path, *inline_xml_doc, &container_writer,
412                                  context->GetDiagnostics())) {
413         return false;
414       }
415     }
416   }
417 
418   if (!writer->FinishEntry()) {
419     context->GetDiagnostics()->Error(DiagMessage(output_path) << "failed to finish writing data");
420     return false;
421   }
422 
423   if (options.generate_text_symbols_path) {
424     io::FileOutputStream fout_text(options.generate_text_symbols_path.value());
425 
426     if (fout_text.HadError()) {
427       context->GetDiagnostics()->Error(DiagMessage()
428                                        << "failed writing to'"
429                                        << options.generate_text_symbols_path.value()
430                                        << "': " << fout_text.GetError());
431       return false;
432     }
433 
434     Printer r_txt_printer(&fout_text);
435     for (const auto& res : xmlres->file.exported_symbols) {
436       r_txt_printer.Print("default int id ");
437       r_txt_printer.Println(res.name.entry);
438     }
439 
440     // And print ourselves.
441     r_txt_printer.Print("default int ");
442     r_txt_printer.Print(path_data.resource_dir);
443     r_txt_printer.Print(" ");
444     r_txt_printer.Println(path_data.name);
445   }
446 
447   return true;
448 }
449 
CompilePng(IAaptContext * context,const CompileOptions & options,const ResourcePathData & path_data,io::IFile * file,IArchiveWriter * writer,const std::string & output_path)450 static bool CompilePng(IAaptContext* context, const CompileOptions& options,
451                        const ResourcePathData& path_data, io::IFile* file, IArchiveWriter* writer,
452                        const std::string& output_path) {
453   TRACE_CALL();
454   if (context->IsVerbose()) {
455     context->GetDiagnostics()->Note(DiagMessage(path_data.source) << "compiling PNG");
456   }
457 
458   BigBuffer buffer(4096);
459   ResourceFile res_file;
460   res_file.name = ResourceName({}, *ParseResourceType(path_data.resource_dir), path_data.name);
461   res_file.config = path_data.config;
462   res_file.source = path_data.source;
463   res_file.type = ResourceFile::Type::kPng;
464 
465   {
466     auto data = file->OpenAsData();
467     if (!data) {
468       context->GetDiagnostics()->Error(DiagMessage(path_data.source) << "failed to open file ");
469       return false;
470     }
471 
472     BigBuffer crunched_png_buffer(4096);
473     io::BigBufferOutputStream crunched_png_buffer_out(&crunched_png_buffer);
474 
475     // Ensure that we only keep the chunks we care about if we end up
476     // using the original PNG instead of the crunched one.
477     const StringPiece content(reinterpret_cast<const char*>(data->data()), data->size());
478     PngChunkFilter png_chunk_filter(content);
479     std::unique_ptr<Image> image = ReadPng(context, path_data.source, &png_chunk_filter);
480     if (!image) {
481       return false;
482     }
483 
484     std::unique_ptr<NinePatch> nine_patch;
485     if (path_data.extension == "9.png") {
486       std::string err;
487       nine_patch = NinePatch::Create(image->rows.get(), image->width, image->height, &err);
488       if (!nine_patch) {
489         context->GetDiagnostics()->Error(DiagMessage() << err);
490         return false;
491       }
492 
493       // Remove the 1px border around the NinePatch.
494       // Basically the row array is shifted up by 1, and the length is treated
495       // as height - 2.
496       // For each row, shift the array to the left by 1, and treat the length as
497       // width - 2.
498       image->width -= 2;
499       image->height -= 2;
500       memmove(image->rows.get(), image->rows.get() + 1, image->height * sizeof(uint8_t**));
501       for (int32_t h = 0; h < image->height; h++) {
502         memmove(image->rows[h], image->rows[h] + 4, image->width * 4);
503       }
504 
505       if (context->IsVerbose()) {
506         context->GetDiagnostics()->Note(DiagMessage(path_data.source) << "9-patch: "
507                                                                       << *nine_patch);
508       }
509     }
510 
511     // Write the crunched PNG.
512     if (!WritePng(context, image.get(), nine_patch.get(), &crunched_png_buffer_out, {})) {
513       return false;
514     }
515 
516     if (nine_patch != nullptr ||
517         crunched_png_buffer_out.ByteCount() <= png_chunk_filter.ByteCount()) {
518       // No matter what, we must use the re-encoded PNG, even if it is larger.
519       // 9-patch images must be re-encoded since their borders are stripped.
520       buffer.AppendBuffer(std::move(crunched_png_buffer));
521     } else {
522       // The re-encoded PNG is larger than the original, and there is
523       // no mandatory transformation. Use the original.
524       if (context->IsVerbose()) {
525         context->GetDiagnostics()->Note(DiagMessage(path_data.source)
526                                         << "original PNG is smaller than crunched PNG"
527                                         << ", using original");
528       }
529 
530       png_chunk_filter.Rewind();
531       BigBuffer filtered_png_buffer(4096);
532       io::BigBufferOutputStream filtered_png_buffer_out(&filtered_png_buffer);
533       io::Copy(&filtered_png_buffer_out, &png_chunk_filter);
534       buffer.AppendBuffer(std::move(filtered_png_buffer));
535     }
536 
537     if (context->IsVerbose()) {
538       // For debugging only, use the legacy PNG cruncher and compare the resulting file sizes.
539       // This will help catch exotic cases where the new code may generate larger PNGs.
540       std::stringstream legacy_stream(content.to_string());
541       BigBuffer legacy_buffer(4096);
542       Png png(context->GetDiagnostics());
543       if (!png.process(path_data.source, &legacy_stream, &legacy_buffer, {})) {
544         return false;
545       }
546 
547       context->GetDiagnostics()->Note(DiagMessage(path_data.source)
548                                       << "legacy=" << legacy_buffer.size()
549                                       << " new=" << buffer.size());
550     }
551   }
552 
553   io::BigBufferInputStream buffer_in(&buffer);
554   return WriteHeaderAndDataToWriter(output_path, res_file, &buffer_in, writer,
555       context->GetDiagnostics());
556 }
557 
CompileFile(IAaptContext * context,const CompileOptions & options,const ResourcePathData & path_data,io::IFile * file,IArchiveWriter * writer,const std::string & output_path)558 static bool CompileFile(IAaptContext* context, const CompileOptions& options,
559                         const ResourcePathData& path_data, io::IFile* file, IArchiveWriter* writer,
560                         const std::string& output_path) {
561   TRACE_CALL();
562   if (context->IsVerbose()) {
563     context->GetDiagnostics()->Note(DiagMessage(path_data.source) << "compiling file");
564   }
565 
566   ResourceFile res_file;
567   res_file.name = ResourceName({}, *ParseResourceType(path_data.resource_dir), path_data.name);
568   res_file.config = path_data.config;
569   res_file.source = path_data.source;
570   res_file.type = ResourceFile::Type::kUnknown;
571 
572   auto data = file->OpenAsData();
573   if (!data) {
574     context->GetDiagnostics()->Error(DiagMessage(path_data.source) << "failed to open file ");
575     return false;
576   }
577 
578   return WriteHeaderAndDataToWriter(output_path, res_file, data.get(), writer,
579       context->GetDiagnostics());
580 }
581 
582 class CompileContext : public IAaptContext {
583  public:
CompileContext(IDiagnostics * diagnostics)584   explicit CompileContext(IDiagnostics* diagnostics) : diagnostics_(diagnostics) {
585   }
586 
GetPackageType()587   PackageType GetPackageType() override {
588     // Every compilation unit starts as an app and then gets linked as potentially something else.
589     return PackageType::kApp;
590   }
591 
SetVerbose(bool val)592   void SetVerbose(bool val) {
593     verbose_ = val;
594   }
595 
IsVerbose()596   bool IsVerbose() override {
597     return verbose_;
598   }
599 
GetDiagnostics()600   IDiagnostics* GetDiagnostics() override {
601     return diagnostics_;
602   }
603 
GetNameMangler()604   NameMangler* GetNameMangler() override {
605     UNIMPLEMENTED(FATAL) << "No name mangling should be needed in compile phase";
606     return nullptr;
607   }
608 
GetCompilationPackage()609   const std::string& GetCompilationPackage() override {
610     static std::string empty;
611     return empty;
612   }
613 
GetPackageId()614   uint8_t GetPackageId() override {
615     return 0x0;
616   }
617 
GetExternalSymbols()618   SymbolTable* GetExternalSymbols() override {
619     UNIMPLEMENTED(FATAL) << "No symbols should be needed in compile phase";
620     return nullptr;
621   }
622 
GetMinSdkVersion()623   int GetMinSdkVersion() override {
624     return 0;
625   }
626 
GetSplitNameDependencies()627   const std::set<std::string>& GetSplitNameDependencies() override {
628     UNIMPLEMENTED(FATAL) << "No Split Name Dependencies be needed in compile phase";
629     static std::set<std::string> empty;
630     return empty;
631   }
632 
633  private:
634   DISALLOW_COPY_AND_ASSIGN(CompileContext);
635 
636   IDiagnostics* diagnostics_;
637   bool verbose_ = false;
638 };
639 
Compile(IAaptContext * context,io::IFileCollection * inputs,IArchiveWriter * output_writer,CompileOptions & options)640 int Compile(IAaptContext* context, io::IFileCollection* inputs, IArchiveWriter* output_writer,
641              CompileOptions& options) {
642   TRACE_CALL();
643   bool error = false;
644 
645   // Iterate over the input files in a stable, platform-independent manner
646   auto file_iterator  = inputs->Iterator();
647   while (file_iterator->HasNext()) {
648     auto file = file_iterator->Next();
649     std::string path = file->GetSource().path;
650 
651     // Skip hidden input files
652     if (file::IsHidden(path)) {
653       continue;
654     }
655 
656     if (!options.res_zip && !IsValidFile(context, path)) {
657       error = true;
658       continue;
659     }
660 
661     // Extract resource type information from the full path
662     std::string err_str;
663     ResourcePathData path_data;
664     if (auto maybe_path_data = ExtractResourcePathData(
665         path, inputs->GetDirSeparator(), &err_str, options)) {
666       path_data = maybe_path_data.value();
667     } else {
668       context->GetDiagnostics()->Error(DiagMessage(file->GetSource()) << err_str);
669       error = true;
670       continue;
671     }
672 
673     // Determine how to compile the file based on its type.
674     auto compile_func = &CompileFile;
675     if (path_data.resource_dir == "values" && path_data.extension == "xml") {
676       compile_func = &CompileTable;
677       // We use a different extension (not necessary anymore, but avoids altering the existing
678       // build system logic).
679       path_data.extension = "arsc";
680 
681     } else if (const ResourceType* type = ParseResourceType(path_data.resource_dir)) {
682       if (*type != ResourceType::kRaw) {
683         if (*type == ResourceType::kXml || path_data.extension == "xml") {
684           compile_func = &CompileXml;
685         } else if ((!options.no_png_crunch && path_data.extension == "png")
686                    || path_data.extension == "9.png") {
687           compile_func = &CompilePng;
688         }
689       }
690     } else {
691       context->GetDiagnostics()->Error(DiagMessage()
692           << "invalid file path '" << path_data.source << "'");
693       error = true;
694       continue;
695     }
696 
697     // Treat periods as a reserved character that should not be present in a file name
698     // Legacy support for AAPT which did not reserve periods
699     if (compile_func != &CompileFile && !options.legacy_mode
700         && std::count(path_data.name.begin(), path_data.name.end(), '.') != 0) {
701       error = true;
702       context->GetDiagnostics()->Error(DiagMessage(file->GetSource())
703                                                     << "file name cannot contain '.' other than for"
704                                                     << " specifying the extension");
705       continue;
706     }
707 
708     const std::string out_path = BuildIntermediateContainerFilename(path_data);
709     if (!compile_func(context, options, path_data, file, output_writer, out_path)) {
710       context->GetDiagnostics()->Error(DiagMessage(file->GetSource()) << "file failed to compile");
711       error = true;
712     }
713   }
714 
715   return error ? 1 : 0;
716 }
717 
Action(const std::vector<std::string> & args)718 int CompileCommand::Action(const std::vector<std::string>& args) {
719   TRACE_FLUSH(trace_folder_? trace_folder_.value() : "", "CompileCommand::Action");
720   CompileContext context(diagnostic_);
721   context.SetVerbose(options_.verbose);
722 
723   if (visibility_) {
724     if (visibility_.value() == "public") {
725       options_.visibility = Visibility::Level::kPublic;
726     } else if (visibility_.value() == "private") {
727       options_.visibility = Visibility::Level::kPrivate;
728     } else if (visibility_.value() == "default") {
729       options_.visibility = Visibility::Level::kUndefined;
730     } else {
731       context.GetDiagnostics()->Error(
732           DiagMessage() << "Unrecognized visibility level passes to --visibility: '"
733                         << visibility_.value() << "'. Accepted levels: public, private, default");
734       return 1;
735     }
736   }
737 
738   std::unique_ptr<io::IFileCollection> file_collection;
739 
740   // Collect the resources files to compile
741   if (options_.res_dir && options_.res_zip) {
742     context.GetDiagnostics()->Error(DiagMessage()
743                                       << "only one of --dir and --zip can be specified");
744     return 1;
745   } else if ((options_.res_dir || options_.res_zip) &&
746               options_.source_path && args.size() > 1) {
747       context.GetDiagnostics()->Error(DiagMessage(kPath)
748       << "Cannot use an overriding source path with multiple files.");
749       return 1;
750   } else if (options_.res_dir) {
751     if (!args.empty()) {
752       context.GetDiagnostics()->Error(DiagMessage() << "files given but --dir specified");
753       Usage(&std::cerr);
754       return 1;
755     }
756 
757     // Load the files from the res directory
758     std::string err;
759     file_collection = io::FileCollection::Create(options_.res_dir.value(), &err);
760     if (!file_collection) {
761       context.GetDiagnostics()->Error(DiagMessage(options_.res_dir.value()) << err);
762       return 1;
763     }
764   } else if (options_.res_zip) {
765     if (!args.empty()) {
766       context.GetDiagnostics()->Error(DiagMessage() << "files given but --zip specified");
767       Usage(&std::cerr);
768       return 1;
769     }
770 
771     // Load a zip file containing a res directory
772     std::string err;
773     file_collection = io::ZipFileCollection::Create(options_.res_zip.value(), &err);
774     if (!file_collection) {
775       context.GetDiagnostics()->Error(DiagMessage(options_.res_zip.value()) << err);
776       return 1;
777     }
778   } else {
779     auto collection = util::make_unique<io::FileCollection>();
780 
781     // Collect data from the path for each input file.
782     std::vector<std::string> sorted_args = args;
783     std::sort(sorted_args.begin(), sorted_args.end());
784 
785     for (const std::string& arg : sorted_args) {
786       collection->InsertFile(arg);
787     }
788 
789     file_collection = std::move(collection);
790   }
791 
792   std::unique_ptr<IArchiveWriter> archive_writer;
793   file::FileType output_file_type = file::GetFileType(options_.output_path);
794   if (output_file_type == file::FileType::kDirectory) {
795     archive_writer = CreateDirectoryArchiveWriter(context.GetDiagnostics(), options_.output_path);
796   } else {
797     archive_writer = CreateZipFileArchiveWriter(context.GetDiagnostics(), options_.output_path);
798   }
799 
800   if (!archive_writer) {
801     return 1;
802   }
803 
804   return Compile(&context, file_collection.get(), archive_writer.get(), options_);
805 }
806 
807 }  // namespace aapt
808