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(©ing_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(©ing_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(©ing_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(©ing_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