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