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(©ing_adaptor)) { 262 context->GetDiagnostics()->Error(DiagMessage(output_path) << "failed to write"); 263 return false; 264 } 265 } 266 267 if (!writer->FinishEntry()) { 268 context->GetDiagnostics()->Error(DiagMessage(output_path) << "failed to finish entry"); 269 return false; 270 } 271 return true; 272 } 273 274 static bool WriteHeaderAndBufferToWriter(const StringPiece& output_path, const ResourceFile& file, 275 const BigBuffer& buffer, IArchiveWriter* writer, 276 IDiagnostics* diag) { 277 // Start the entry so we can write the header. 278 if (!writer->StartEntry(output_path, 0)) { 279 diag->Error(DiagMessage(output_path) << "failed to open file"); 280 return false; 281 } 282 283 // Make sure CopyingOutputStreamAdaptor is deleted before we call 284 // writer->FinishEntry(). 285 { 286 // Wrap our IArchiveWriter with an adaptor that implements the 287 // ZeroCopyOutputStream interface. 288 CopyingOutputStreamAdaptor copying_adaptor(writer); 289 CompiledFileOutputStream output_stream(©ing_adaptor); 290 291 // Number of CompiledFiles. 292 output_stream.WriteLittleEndian32(1); 293 294 std::unique_ptr<pb::CompiledFile> compiled_file = SerializeCompiledFileToPb(file); 295 output_stream.WriteCompiledFile(compiled_file.get()); 296 output_stream.WriteData(&buffer); 297 298 if (output_stream.HadError()) { 299 diag->Error(DiagMessage(output_path) << "failed to write data"); 300 return false; 301 } 302 } 303 304 if (!writer->FinishEntry()) { 305 diag->Error(DiagMessage(output_path) << "failed to finish writing data"); 306 return false; 307 } 308 return true; 309 } 310 311 static bool WriteHeaderAndMmapToWriter(const StringPiece& output_path, const ResourceFile& file, 312 const android::FileMap& map, IArchiveWriter* writer, 313 IDiagnostics* diag) { 314 // Start the entry so we can write the header. 315 if (!writer->StartEntry(output_path, 0)) { 316 diag->Error(DiagMessage(output_path) << "failed to open file"); 317 return false; 318 } 319 320 // Make sure CopyingOutputStreamAdaptor is deleted before we call 321 // writer->FinishEntry(). 322 { 323 // Wrap our IArchiveWriter with an adaptor that implements the 324 // ZeroCopyOutputStream interface. 325 CopyingOutputStreamAdaptor copying_adaptor(writer); 326 CompiledFileOutputStream output_stream(©ing_adaptor); 327 328 // Number of CompiledFiles. 329 output_stream.WriteLittleEndian32(1); 330 331 std::unique_ptr<pb::CompiledFile> compiled_file = SerializeCompiledFileToPb(file); 332 output_stream.WriteCompiledFile(compiled_file.get()); 333 output_stream.WriteData(map.getDataPtr(), map.getDataLength()); 334 335 if (output_stream.HadError()) { 336 diag->Error(DiagMessage(output_path) << "failed to write data"); 337 return false; 338 } 339 } 340 341 if (!writer->FinishEntry()) { 342 diag->Error(DiagMessage(output_path) << "failed to finish writing data"); 343 return false; 344 } 345 return true; 346 } 347 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(©ing_adaptor); 441 442 std::vector<std::unique_ptr<xml::XmlResource>>& inline_documents = 443 inline_xml_format_parser.GetExtractedInlineXmlDocuments(); 444 445 // Number of CompiledFiles. 446 output_stream.WriteLittleEndian32(1 + inline_documents.size()); 447 448 if (!FlattenXmlToOutStream(context, output_path, xmlres.get(), &output_stream)) { 449 return false; 450 } 451 452 for (auto& inline_xml_doc : inline_documents) { 453 if (!FlattenXmlToOutStream(context, output_path, inline_xml_doc.get(), &output_stream)) { 454 return false; 455 } 456 } 457 } 458 459 if (!writer->FinishEntry()) { 460 context->GetDiagnostics()->Error(DiagMessage(output_path) << "failed to finish writing data"); 461 return false; 462 } 463 return true; 464 } 465 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