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