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