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 <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  */
     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 
    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 
    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  */
    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 
    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(&copying_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 
    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(&copying_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 
    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(&copying_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 
    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 
    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 
    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(&copying_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 
    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 
    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:
    605   CompileContext(IDiagnostics* diagnostics) : diagnostics_(diagnostics) {
    606   }
    607 
    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 
    613   void SetVerbose(bool val) {
    614     verbose_ = val;
    615   }
    616 
    617   bool IsVerbose() override {
    618     return verbose_;
    619   }
    620 
    621   IDiagnostics* GetDiagnostics() override {
    622     return diagnostics_;
    623   }
    624 
    625   NameMangler* GetNameMangler() override {
    626     abort();
    627     return nullptr;
    628   }
    629 
    630   const std::string& GetCompilationPackage() override {
    631     static std::string empty;
    632     return empty;
    633   }
    634 
    635   uint8_t GetPackageId() override {
    636     return 0x0;
    637   }
    638 
    639   SymbolTable* GetExternalSymbols() override {
    640     abort();
    641     return nullptr;
    642   }
    643 
    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  */
    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