diff --git a/tools/aapt2/Android.bp b/tools/aapt2/Android.bp index f964dfed17104..058504d7520d0 100644 --- a/tools/aapt2/Android.bp +++ b/tools/aapt2/Android.bp @@ -84,17 +84,18 @@ cc_library_host_static { "filter/AbiFilter.cpp", "filter/ConfigFilter.cpp", "format/Archive.cpp", + "format/Container.cpp", "format/binary/BinaryResourceParser.cpp", "format/binary/ResChunkPullParser.cpp", "format/binary/TableFlattener.cpp", "format/binary/XmlFlattener.cpp", "format/proto/ProtoDeserialize.cpp", "format/proto/ProtoSerialize.cpp", - "io/BigBufferStreams.cpp", + "io/BigBufferStream.cpp", "io/File.cpp", - "io/FileInputStream.cpp", + "io/FileStream.cpp", "io/FileSystem.cpp", - "io/StringInputStream.cpp", + "io/StringStream.cpp", "io/Util.cpp", "io/ZipArchive.cpp", "link/AutoVersioner.cpp", diff --git a/tools/aapt2/LoadedApk.cpp b/tools/aapt2/LoadedApk.cpp index c1815c82b5b5c..ae32ee965b480 100644 --- a/tools/aapt2/LoadedApk.cpp +++ b/tools/aapt2/LoadedApk.cpp @@ -21,7 +21,7 @@ #include "format/Archive.h" #include "format/binary/TableFlattener.h" #include "format/binary/XmlFlattener.h" -#include "io/BigBufferInputStream.h" +#include "io/BigBufferStream.h" #include "io/Util.h" #include "xml/XmlDom.h" diff --git a/tools/aapt2/Resource.h b/tools/aapt2/Resource.h index cbcc8fb805aa1..87b986784961a 100644 --- a/tools/aapt2/Resource.h +++ b/tools/aapt2/Resource.h @@ -157,12 +157,22 @@ struct SourcedResourceName { }; struct ResourceFile { + enum class Type { + kUnknown, + kPng, + kBinaryXml, + kProtoXml, + }; + // Name ResourceName name; // Configuration ConfigDescription config; + // Type + Type type; + // Source Source source; diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp index f08b03e7b8af3..9a5cd3edb47fe 100644 --- a/tools/aapt2/ResourceParser_test.cpp +++ b/tools/aapt2/ResourceParser_test.cpp @@ -22,7 +22,7 @@ #include "ResourceTable.h" #include "ResourceUtils.h" #include "ResourceValues.h" -#include "io/StringInputStream.h" +#include "io/StringStream.h" #include "test/Test.h" #include "xml/XmlPullParser.h" diff --git a/tools/aapt2/ResourceValues.cpp b/tools/aapt2/ResourceValues.cpp index e0137290f5ee7..4e84a476a3004 100644 --- a/tools/aapt2/ResourceValues.cpp +++ b/tools/aapt2/ResourceValues.cpp @@ -292,6 +292,7 @@ bool FileReference::Flatten(android::Res_value* out_value) const { FileReference* FileReference::Clone(StringPool* new_pool) const { FileReference* fr = new FileReference(new_pool->MakeRef(*path)); fr->file = file; + fr->type = type; fr->comment_ = comment_; fr->source_ = source_; return fr; diff --git a/tools/aapt2/ResourceValues.h b/tools/aapt2/ResourceValues.h index 742765d166daa..fd242a109006b 100644 --- a/tools/aapt2/ResourceValues.h +++ b/tools/aapt2/ResourceValues.h @@ -249,6 +249,10 @@ struct FileReference : public BaseItem { // This field is NOT persisted in any format. It is transient. io::IFile* file = nullptr; + // FileType of the file pointed to by `file`. This is used to know how to inflate the file, + // or if to inflate at all (just copy). + ResourceFile::Type type = ResourceFile::Type::kUnknown; + FileReference() = default; explicit FileReference(const StringPool::Ref& path); diff --git a/tools/aapt2/ResourcesInternal.proto b/tools/aapt2/ResourcesInternal.proto index 0b0a252a34529..c16cf6515de1f 100644 --- a/tools/aapt2/ResourcesInternal.proto +++ b/tools/aapt2/ResourcesInternal.proto @@ -41,13 +41,22 @@ message CompiledFile { // The configuration for which the resource is defined. aapt.pb.Configuration config = 2; + enum Type { + UNKNOWN = 0; + PNG = 1; + BINARY_XML = 2; + PROTO_XML = 3; + } + + Type type = 3; + // The filesystem path to where the source file originated. // Mainly used to display helpful error messages. - string source_path = 3; + string source_path = 4; // Any symbols this file auto-generates/exports (eg. @+id/foo in an XML file). - repeated Symbol exported_symbol = 4; + repeated Symbol exported_symbol = 5; // If this is a compiled XML file, this is the root node. - aapt.pb.XmlNode xml_root = 5; + aapt.pb.XmlNode xml_root = 6; } diff --git a/tools/aapt2/cmd/Compile.cpp b/tools/aapt2/cmd/Compile.cpp index a5e6aefd1e0f6..53910afff5938 100644 --- a/tools/aapt2/cmd/Compile.cpp +++ b/tools/aapt2/cmd/Compile.cpp @@ -36,10 +36,11 @@ #include "compile/PseudolocaleGenerator.h" #include "compile/XmlIdCollector.h" #include "format/Archive.h" -#include "format/binary/XmlFlattener.h" +#include "format/Container.h" #include "format/proto/ProtoSerialize.h" -#include "io/BigBufferOutputStream.h" -#include "io/FileInputStream.h" +#include "io/BigBufferStream.h" +#include "io/FileStream.h" +#include "io/StringStream.h" #include "io/Util.h" #include "util/Files.h" #include "util/Maybe.h" @@ -49,6 +50,7 @@ using ::aapt::io::FileInputStream; using ::android::StringPiece; +using ::android::base::SystemErrorCodeToString; using ::google::protobuf::io::CopyingOutputStreamAdaptor; namespace aapt { @@ -116,7 +118,7 @@ struct CompileOptions { bool verbose = false; }; -static std::string BuildIntermediateFilename(const ResourcePathData& data) { +static std::string BuildIntermediateContainerFilename(const ResourcePathData& data) { std::stringstream name; name << data.resource_dir; if (!data.config_str.empty()) { @@ -141,7 +143,7 @@ static bool LoadInputFilesFromDir(IAaptContext* context, const CompileOptions& o std::unique_ptr d(opendir(root_dir.data()), closedir); if (!d) { context->GetDiagnostics()->Error(DiagMessage(root_dir) << "failed to open directory: " - << android::base::SystemErrorCodeToString(errno)); + << SystemErrorCodeToString(errno)); return false; } @@ -160,7 +162,7 @@ static bool LoadInputFilesFromDir(IAaptContext* context, const CompileOptions& o std::unique_ptr subdir(opendir(prefix_path.data()), closedir); if (!subdir) { context->GetDiagnostics()->Error(DiagMessage(prefix_path) << "failed to open directory: " - << android::base::SystemErrorCodeToString(errno)); + << SystemErrorCodeToString(errno)); return false; } @@ -241,16 +243,15 @@ static bool CompileTable(IAaptContext* context, const CompileOptions& options, return false; } - // Make sure CopyingOutputStreamAdaptor is deleted before we call - // writer->FinishEntry(). + // Make sure CopyingOutputStreamAdaptor is deleted before we call writer->FinishEntry(). { - // Wrap our IArchiveWriter with an adaptor that implements the - // ZeroCopyOutputStream interface. + // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream interface. CopyingOutputStreamAdaptor copying_adaptor(writer); + ContainerWriter container_writer(©ing_adaptor, 1u); pb::ResourceTable pb_table; SerializeTableToPb(table, &pb_table); - if (!pb_table.SerializeToZeroCopyStream(©ing_adaptor)) { + if (!container_writer.AddResTableEntry(pb_table)) { context->GetDiagnostics()->Error(DiagMessage(output_path) << "failed to write"); return false; } @@ -263,46 +264,8 @@ static bool CompileTable(IAaptContext* context, const CompileOptions& options, return true; } -static bool WriteHeaderAndBufferToWriter(const StringPiece& output_path, const ResourceFile& file, - const BigBuffer& buffer, IArchiveWriter* writer, - IDiagnostics* diag) { - // Start the entry so we can write the header. - if (!writer->StartEntry(output_path, 0)) { - diag->Error(DiagMessage(output_path) << "failed to open file"); - return false; - } - - // Make sure CopyingOutputStreamAdaptor is deleted before we call - // writer->FinishEntry(). - { - // Wrap our IArchiveWriter with an adaptor that implements the - // ZeroCopyOutputStream interface. - CopyingOutputStreamAdaptor copying_adaptor(writer); - CompiledFileOutputStream output_stream(©ing_adaptor); - - // Number of CompiledFiles. - output_stream.WriteLittleEndian32(1); - - pb::internal::CompiledFile pb_compiled_file; - SerializeCompiledFileToPb(file, &pb_compiled_file); - output_stream.WriteCompiledFile(pb_compiled_file); - output_stream.WriteData(buffer); - - if (output_stream.HadError()) { - diag->Error(DiagMessage(output_path) << "failed to write data"); - return false; - } - } - - if (!writer->FinishEntry()) { - diag->Error(DiagMessage(output_path) << "failed to finish writing data"); - return false; - } - return true; -} - -static bool WriteHeaderAndMmapToWriter(const StringPiece& output_path, const ResourceFile& file, - const android::FileMap& map, IArchiveWriter* writer, +static bool WriteHeaderAndDataToWriter(const StringPiece& output_path, const ResourceFile& file, + io::KnownSizeInputStream* in, IArchiveWriter* writer, IDiagnostics* diag) { // Start the entry so we can write the header. if (!writer->StartEntry(output_path, 0)) { @@ -310,24 +273,17 @@ static bool WriteHeaderAndMmapToWriter(const StringPiece& output_path, const Res return false; } - // Make sure CopyingOutputStreamAdaptor is deleted before we call - // writer->FinishEntry(). + // Make sure CopyingOutputStreamAdaptor is deleted before we call writer->FinishEntry(). { - // Wrap our IArchiveWriter with an adaptor that implements the - // ZeroCopyOutputStream interface. + // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream interface. CopyingOutputStreamAdaptor copying_adaptor(writer); - CompiledFileOutputStream output_stream(©ing_adaptor); - - // Number of CompiledFiles. - output_stream.WriteLittleEndian32(1); + ContainerWriter container_writer(©ing_adaptor, 1u); pb::internal::CompiledFile pb_compiled_file; SerializeCompiledFileToPb(file, &pb_compiled_file); - output_stream.WriteCompiledFile(pb_compiled_file); - output_stream.WriteData(map.getDataPtr(), map.getDataLength()); - if (output_stream.HadError()) { - diag->Error(DiagMessage(output_path) << "failed to write data"); + if (!container_writer.AddResFileEntry(pb_compiled_file, in)) { + diag->Error(DiagMessage(output_path) << "failed to write entry data"); return false; } } @@ -339,23 +295,19 @@ static bool WriteHeaderAndMmapToWriter(const StringPiece& output_path, const Res return true; } -static bool FlattenXmlToOutStream(IAaptContext* context, const StringPiece& output_path, - xml::XmlResource* xmlres, CompiledFileOutputStream* out) { - BigBuffer buffer(1024); - XmlFlattenerOptions xml_flattener_options; - xml_flattener_options.keep_raw_values = true; - XmlFlattener flattener(&buffer, xml_flattener_options); - if (!flattener.Consume(context, xmlres)) { - return false; - } - +static bool FlattenXmlToOutStream(const StringPiece& output_path, const xml::XmlResource& xmlres, + ContainerWriter* container_writer, IDiagnostics* diag) { pb::internal::CompiledFile pb_compiled_file; - SerializeCompiledFileToPb(xmlres->file, &pb_compiled_file); - out->WriteCompiledFile(pb_compiled_file); - out->WriteData(buffer); + SerializeCompiledFileToPb(xmlres.file, &pb_compiled_file); - if (out->HadError()) { - context->GetDiagnostics()->Error(DiagMessage(output_path) << "failed to write XML data"); + pb::XmlNode pb_xml_node; + SerializeXmlToPb(*xmlres.root, &pb_xml_node); + + std::string serialized_xml = pb_xml_node.SerializeAsString(); + io::StringInputStream serialized_in(serialized_xml); + + if (!container_writer->AddResFileEntry(pb_compiled_file, &serialized_in)) { + diag->Error(DiagMessage(output_path) << "failed to write entry data"); return false; } return true; @@ -404,6 +356,7 @@ static bool CompileXml(IAaptContext* context, const CompileOptions& options, xmlres->file.name = ResourceName({}, *ParseResourceType(path_data.resource_dir), path_data.name); xmlres->file.config = path_data.config; xmlres->file.source = path_data.source; + xmlres->file.type = ResourceFile::Type::kProtoXml; // Collect IDs that are defined here. XmlIdCollector collector; @@ -423,24 +376,23 @@ static bool CompileXml(IAaptContext* context, const CompileOptions& options, return false; } + std::vector>& inline_documents = + inline_xml_format_parser.GetExtractedInlineXmlDocuments(); + // Make sure CopyingOutputStreamAdaptor is deleted before we call writer->FinishEntry(). { // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream interface. CopyingOutputStreamAdaptor copying_adaptor(writer); - CompiledFileOutputStream output_stream(©ing_adaptor); + ContainerWriter container_writer(©ing_adaptor, 1u + inline_documents.size()); - std::vector>& inline_documents = - inline_xml_format_parser.GetExtractedInlineXmlDocuments(); - - // Number of CompiledFiles. - output_stream.WriteLittleEndian32(1 + inline_documents.size()); - - if (!FlattenXmlToOutStream(context, output_path, xmlres.get(), &output_stream)) { + if (!FlattenXmlToOutStream(output_path, *xmlres, &container_writer, + context->GetDiagnostics())) { return false; } - for (auto& inline_xml_doc : inline_documents) { - if (!FlattenXmlToOutStream(context, output_path, inline_xml_doc.get(), &output_stream)) { + for (const std::unique_ptr& inline_xml_doc : inline_documents) { + if (!FlattenXmlToOutStream(output_path, *inline_xml_doc, &container_writer, + context->GetDiagnostics())) { return false; } } @@ -465,6 +417,7 @@ static bool CompilePng(IAaptContext* context, const CompileOptions& options, res_file.name = ResourceName({}, *ParseResourceType(path_data.resource_dir), path_data.name); res_file.config = path_data.config; res_file.source = path_data.source; + res_file.type = ResourceFile::Type::kPng; { std::string content; @@ -472,7 +425,7 @@ static bool CompilePng(IAaptContext* context, const CompileOptions& options, true /*follow_symlinks*/)) { context->GetDiagnostics()->Error(DiagMessage(path_data.source) << "failed to open file: " - << android::base::SystemErrorCodeToString(errno)); + << SystemErrorCodeToString(errno)); return false; } @@ -556,8 +509,9 @@ static bool CompilePng(IAaptContext* context, const CompileOptions& options, } } - if (!WriteHeaderAndBufferToWriter(output_path, res_file, buffer, writer, - context->GetDiagnostics())) { + io::BigBufferInputStream buffer_in(&buffer); + if (!WriteHeaderAndDataToWriter(output_path, res_file, &buffer_in, writer, + context->GetDiagnostics())) { return false; } return true; @@ -575,6 +529,7 @@ static bool CompileFile(IAaptContext* context, const CompileOptions& options, res_file.name = ResourceName({}, *ParseResourceType(path_data.resource_dir), path_data.name); res_file.config = path_data.config; res_file.source = path_data.source; + res_file.type = ResourceFile::Type::kUnknown; std::string error_str; Maybe f = file::MmapPath(path_data.source.path, &error_str); @@ -584,7 +539,8 @@ static bool CompileFile(IAaptContext* context, const CompileOptions& options, return false; } - if (!WriteHeaderAndMmapToWriter(output_path, res_file, f.value(), writer, + io::MmappedData mmapped_in(std::move(f.value())); + if (!WriteHeaderAndDataToWriter(output_path, res_file, &mmapped_in, writer, context->GetDiagnostics())) { return false; } @@ -614,7 +570,7 @@ class CompileContext : public IAaptContext { } NameMangler* GetNameMangler() override { - abort(); + UNIMPLEMENTED(FATAL) << "No name mangling should be needed in compile phase"; return nullptr; } @@ -628,7 +584,7 @@ class CompileContext : public IAaptContext { } SymbolTable* GetExternalSymbols() override { - abort(); + UNIMPLEMENTED(FATAL) << "No symbols should be needed in compile phase"; return nullptr; } @@ -637,14 +593,13 @@ class CompileContext : public IAaptContext { } private: + DISALLOW_COPY_AND_ASSIGN(CompileContext); + IDiagnostics* diagnostics_; bool verbose_ = false; }; -/** - * Entry point for compilation phase. Parses arguments and dispatches to the - * correct steps. - */ +// Entry point for compilation phase. Parses arguments and dispatches to the correct steps. int Compile(const std::vector& args, IDiagnostics* diagnostics) { CompileContext context(diagnostics); CompileOptions options; @@ -717,50 +672,34 @@ int Compile(const std::vector& args, IDiagnostics* diagnostics) { continue; } + // Determine how to compile the file based on its type. + auto compile_func = &CompileFile; if (path_data.resource_dir == "values") { - // Overwrite the extension. + compile_func = &CompileTable; + // We use a different extension (not necessary anymore, but avoids altering the existing + // build system logic). path_data.extension = "arsc"; - - const std::string output_filename = BuildIntermediateFilename(path_data); - if (!CompileTable(&context, options, path_data, archive_writer.get(), output_filename)) { - error = true; - } - - } else { - const std::string output_filename = BuildIntermediateFilename(path_data); - if (const ResourceType* type = ParseResourceType(path_data.resource_dir)) { - if (*type != ResourceType::kRaw) { - if (path_data.extension == "xml") { - if (!CompileXml(&context, options, path_data, archive_writer.get(), output_filename)) { - error = true; - } - } else if (!options.no_png_crunch && - (path_data.extension == "png" || path_data.extension == "9.png")) { - if (!CompilePng(&context, options, path_data, archive_writer.get(), output_filename)) { - error = true; - } - } else { - if (!CompileFile(&context, options, path_data, archive_writer.get(), output_filename)) { - error = true; - } - } - } else { - if (!CompileFile(&context, options, path_data, archive_writer.get(), output_filename)) { - error = true; - } + } else if (const ResourceType* type = ParseResourceType(path_data.resource_dir)) { + if (*type != ResourceType::kRaw) { + if (path_data.extension == "xml") { + compile_func = &CompileXml; + } else if (!options.no_png_crunch && + (path_data.extension == "png" || path_data.extension == "9.png")) { + compile_func = &CompilePng; } - } else { - context.GetDiagnostics()->Error(DiagMessage() << "invalid file path '" << path_data.source - << "'"); - error = true; } + } else { + context.GetDiagnostics()->Error(DiagMessage() + << "invalid file path '" << path_data.source << "'"); + error = true; + continue; } - } - if (error) { - return 1; + // Compile the file. + const std::string out_path = BuildIntermediateContainerFilename(path_data); + error |= !compile_func(&context, options, path_data, archive_writer.get(), out_path); } - return 0; + return error ? 1 : 0; } } // namespace aapt diff --git a/tools/aapt2/cmd/Dump.cpp b/tools/aapt2/cmd/Dump.cpp index 44032f6a730db..7be6dc018ad5a 100644 --- a/tools/aapt2/cmd/Dump.cpp +++ b/tools/aapt2/cmd/Dump.cpp @@ -21,8 +21,10 @@ #include "Debug.h" #include "Diagnostics.h" #include "Flags.h" +#include "format/Container.h" #include "format/binary/BinaryResourceParser.h" #include "format/proto/ProtoDeserialize.h" +#include "io/FileStream.h" #include "io/ZipArchive.h" #include "process/IResourceTableConsumer.h" #include "util/Files.h" @@ -31,23 +33,34 @@ using ::android::StringPiece; namespace aapt { -bool DumpCompiledFile(const pb::internal::CompiledFile& pb_file, const void* data, size_t len, - const Source& source, IAaptContext* context) { - ResourceFile file; - std::string error; - if (!DeserializeCompiledFileFromPb(pb_file, &file, &error)) { - context->GetDiagnostics()->Warn(DiagMessage(source) - << "failed to read compiled file: " << error); - return false; +static const char* ResourceFileTypeToString(const ResourceFile::Type& type) { + switch (type) { + case ResourceFile::Type::kPng: + return "PNG"; + case ResourceFile::Type::kBinaryXml: + return "BINARY_XML"; + case ResourceFile::Type::kProtoXml: + return "PROTO_XML"; + default: + break; } - - std::cout << "Resource: " << file.name << "\n" - << "Config: " << file.config << "\n" - << "Source: " << file.source << "\n"; - return true; + return "UNKNOWN"; } -bool TryDumpFile(IAaptContext* context, const std::string& file_path) { +static void DumpCompiledFile(const ResourceFile& file, const Source& source, off64_t offset, + size_t len) { + std::cout << "Resource: " << file.name << "\n" + << "Config: " << file.config << "\n" + << "Source: " << file.source << "\n" + << "Type: " << ResourceFileTypeToString(file.type) << "\n" + << "DataOff: " << offset << "\n" + << "DataLen: " << len << "\n"; +} + +static bool TryDumpFile(IAaptContext* context, const std::string& file_path) { + DebugPrintTableOptions print_options; + print_options.show_sources = true; + std::string err; std::unique_ptr zip = io::ZipFileCollection::Create(file_path, &err); if (zip) { @@ -85,62 +98,72 @@ bool TryDumpFile(IAaptContext* context, const std::string& file_path) { } } - DebugPrintTableOptions options; - options.show_sources = true; - Debug::PrintTable(&table, options); + Debug::PrintTable(&table, print_options); return true; } err.clear(); - Maybe file = file::MmapPath(file_path, &err); - if (!file) { - context->GetDiagnostics()->Error(DiagMessage(file_path) << err); + io::FileInputStream input(file_path); + if (input.HadError()) { + context->GetDiagnostics()->Error(DiagMessage(file_path) + << "failed to open file: " << input.GetError()); return false; } - android::FileMap* file_map = &file.value(); - - // Check to see if this is a loose ResourceTable. - pb::ResourceTable pb_table; - if (pb_table.ParseFromArray(file_map->getDataPtr(), file_map->getDataLength())) { - ResourceTable table; - if (DeserializeTableFromPb(pb_table, &table, &err)) { - DebugPrintTableOptions options; - options.show_sources = true; - Debug::PrintTable(&table, options); - return true; - } - } - // Try as a compiled file. - CompiledFileInputStream input(file_map->getDataPtr(), file_map->getDataLength()); - uint32_t num_files = 0; - if (!input.ReadLittleEndian32(&num_files)) { + ContainerReader reader(&input); + if (reader.HadError()) { + context->GetDiagnostics()->Error(DiagMessage(file_path) + << "failed to read container: " << reader.GetError()); return false; } - for (uint32_t i = 0; i < num_files; i++) { - pb::internal::CompiledFile compiled_file; - if (!input.ReadCompiledFile(&compiled_file)) { - context->GetDiagnostics()->Warn(DiagMessage() << "failed to read compiled file"); - return false; - } + ContainerReaderEntry* entry; + while ((entry = reader.Next()) != nullptr) { + if (entry->Type() == ContainerEntryType::kResTable) { + pb::ResourceTable pb_table; + if (!entry->GetResTable(&pb_table)) { + context->GetDiagnostics()->Error(DiagMessage(file_path) + << "failed to parse proto table: " << entry->GetError()); + continue; + } - uint64_t offset, len; - if (!input.ReadDataMetaData(&offset, &len)) { - context->GetDiagnostics()->Warn(DiagMessage() << "failed to read meta data"); - return false; - } + ResourceTable table; + err.clear(); + if (!DeserializeTableFromPb(pb_table, &table, &err)) { + context->GetDiagnostics()->Error(DiagMessage(file_path) + << "failed to parse table: " << err); + continue; + } - const void* data = static_cast(file_map->getDataPtr()) + offset; - if (!DumpCompiledFile(compiled_file, data, len, Source(file_path), context)) { - return false; + Debug::PrintTable(&table, print_options); + } else if (entry->Type() == ContainerEntryType::kResFile) { + pb::internal::CompiledFile pb_compiled_file; + off64_t offset; + size_t length; + if (!entry->GetResFileOffsets(&pb_compiled_file, &offset, &length)) { + context->GetDiagnostics()->Error( + DiagMessage(file_path) << "failed to parse compiled proto file: " << entry->GetError()); + continue; + } + + ResourceFile file; + std::string error; + if (!DeserializeCompiledFileFromPb(pb_compiled_file, &file, &error)) { + context->GetDiagnostics()->Warn(DiagMessage(file_path) + << "failed to parse compiled file: " << error); + continue; + } + + DumpCompiledFile(file, Source(file_path), offset, length); } } return true; } +namespace { + class DumpContext : public IAaptContext { public: PackageType GetPackageType() override { @@ -153,7 +176,7 @@ class DumpContext : public IAaptContext { } NameMangler* GetNameMangler() override { - abort(); + UNIMPLEMENTED(FATAL); return nullptr; } @@ -167,7 +190,7 @@ class DumpContext : public IAaptContext { } SymbolTable* GetExternalSymbols() override { - abort(); + UNIMPLEMENTED(FATAL); return nullptr; } @@ -188,9 +211,9 @@ class DumpContext : public IAaptContext { bool verbose_ = false; }; -/** - * Entry point for dump command. - */ +} // namespace + +// Entry point for dump command. int Dump(const std::vector& args) { bool verbose = false; Flags flags = Flags().OptionalSwitch("-v", "increase verbosity of output", &verbose); @@ -206,7 +229,6 @@ int Dump(const std::vector& args) { return 1; } } - return 0; } diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp index 40d71a3429d0b..baee9c98ecd7c 100644 --- a/tools/aapt2/cmd/Link.cpp +++ b/tools/aapt2/cmd/Link.cpp @@ -25,7 +25,6 @@ #include "android-base/file.h" #include "android-base/stringprintf.h" #include "androidfw/StringPiece.h" -#include "google/protobuf/io/coded_stream.h" #include "AppInfo.h" #include "Debug.h" @@ -39,13 +38,14 @@ #include "compile/IdAssigner.h" #include "filter/ConfigFilter.h" #include "format/Archive.h" +#include "format/Container.h" #include "format/binary/BinaryResourceParser.h" #include "format/binary/TableFlattener.h" #include "format/binary/XmlFlattener.h" #include "format/proto/ProtoDeserialize.h" #include "format/proto/ProtoSerialize.h" -#include "io/BigBufferInputStream.h" -#include "io/FileInputStream.h" +#include "io/BigBufferStream.h" +#include "io/FileStream.h" #include "io/FileSystem.h" #include "io/Util.h" #include "io/ZipArchive.h" @@ -467,6 +467,10 @@ std::vector> ResourceFileFlattener::LinkAndVer << "linking " << src.path << " (" << doc->file.name << ")"); } + // First, strip out any tools namespace attributes. AAPT stripped them out early, which means + // that existing projects have out-of-date references which pass compilation. + xml::StripAndroidStudioAttributes(doc->root.get()); + XmlReferenceLinker xml_linker; if (!xml_linker.Consume(context_, doc)) { return {}; @@ -543,9 +547,9 @@ bool ResourceFileFlattener::Flatten(ResourceTable* table, IArchiveWriter* archiv file_op.config = config_value->config; file_op.file_to_copy = file; - const StringPiece src_path = file->GetSource().path; if (type->type != ResourceType::kRaw && - (util::EndsWith(src_path, ".xml.flat") || util::EndsWith(src_path, ".xml"))) { + (file_ref->type == ResourceFile::Type::kBinaryXml || + file_ref->type == ResourceFile::Type::kProtoXml)) { std::unique_ptr data = file->OpenAsData(); if (!data) { context_->GetDiagnostics()->Error(DiagMessage(file->GetSource()) @@ -553,11 +557,27 @@ bool ResourceFileFlattener::Flatten(ResourceTable* table, IArchiveWriter* archiv return false; } - file_op.xml_to_flatten = xml::Inflate(data->data(), data->size(), - context_->GetDiagnostics(), file->GetSource()); + if (file_ref->type == ResourceFile::Type::kProtoXml) { + pb::XmlNode pb_xml_node; + if (!pb_xml_node.ParseFromArray(data->data(), static_cast(data->size()))) { + context_->GetDiagnostics()->Error(DiagMessage(file->GetSource()) + << "failed to parse proto xml"); + return false; + } - if (!file_op.xml_to_flatten) { - return false; + std::string error; + file_op.xml_to_flatten = DeserializeXmlResourceFromPb(pb_xml_node, &error); + if (file_op.xml_to_flatten == nullptr) { + context_->GetDiagnostics()->Error(DiagMessage(file->GetSource()) + << "failed to deserialize proto xml: " << error); + return false; + } + } else { + file_op.xml_to_flatten = xml::Inflate(data->data(), data->size(), + context_->GetDiagnostics(), file->GetSource()); + if (file_op.xml_to_flatten == nullptr) { + return false; + } } file_op.xml_to_flatten->file.config = config_value->config; @@ -1201,11 +1221,7 @@ class LinkCommand { // Clear the package name, so as to make the resources look like they are coming from the // local package. pkg->name = ""; - if (override) { - result = table_merger_->MergeOverlay(Source(input), table.get(), collection.get()); - } else { - result = table_merger_->Merge(Source(input), table.get(), collection.get()); - } + result = table_merger_->Merge(Source(input), table.get(), override, collection.get()); } else { // This is the proper way to merge libraries, where the package name is @@ -1241,49 +1257,34 @@ class LinkCommand { return false; } - bool result = false; - if (override) { - result = table_merger_->MergeOverlay(file->GetSource(), table.get()); - } else { - result = table_merger_->Merge(file->GetSource(), table.get()); - } - return result; + return table_merger_->Merge(file->GetSource(), table.get(), override); } - bool MergeCompiledFile(io::IFile* file, ResourceFile* file_desc, bool override) { + bool MergeCompiledFile(const ResourceFile& compiled_file, io::IFile* file, bool override) { if (context_->IsVerbose()) { - context_->GetDiagnostics()->Note(DiagMessage() << "merging '" << file_desc->name - << "' from compiled file " - << file->GetSource()); + context_->GetDiagnostics()->Note(DiagMessage() + << "merging '" << compiled_file.name + << "' from compiled file " << compiled_file.source); } - bool result = false; - if (override) { - result = table_merger_->MergeFileOverlay(*file_desc, file); - } else { - result = table_merger_->MergeFile(*file_desc, file); - } - - if (!result) { + if (!table_merger_->MergeFile(compiled_file, override, file)) { return false; } // Add the exports of this file to the table. - for (SourcedResourceName& exported_symbol : file_desc->exported_symbols) { - if (exported_symbol.name.package.empty()) { - exported_symbol.name.package = context_->GetCompilationPackage(); + for (const SourcedResourceName& exported_symbol : compiled_file.exported_symbols) { + ResourceName res_name = exported_symbol.name; + if (res_name.package.empty()) { + res_name.package = context_->GetCompilationPackage(); } - ResourceNameRef res_name = exported_symbol.name; - - Maybe mangled_name = - context_->GetNameMangler()->MangleName(exported_symbol.name); + Maybe mangled_name = context_->GetNameMangler()->MangleName(res_name); if (mangled_name) { res_name = mangled_name.value(); } std::unique_ptr id = util::make_unique(); - id->SetSource(file_desc->source.WithLine(exported_symbol.line)); + id->SetSource(compiled_file.source.WithLine(exported_symbol.line)); bool result = final_table_.AddResourceAllowMangled( res_name, ConfigDescription::DefaultConfig(), std::string(), std::move(id), context_->GetDiagnostics()); @@ -1294,15 +1295,11 @@ class LinkCommand { return true; } - /** - * Takes a path to load as a ZIP file and merges the files within into the - * master ResourceTable. - * If override is true, conflicting resources are allowed to override each - * other, in order of last seen. - * - * An io::IFileCollection is created from the ZIP file and added to the set of - * io::IFileCollections that are open. - */ + // Takes a path to load as a ZIP file and merges the files within into the master ResourceTable. + // If override is true, conflicting resources are allowed to override each other, in order of last + // seen. + // An io::IFileCollection is created from the ZIP file and added to the set of + // io::IFileCollections that are open. bool MergeArchive(const std::string& input, bool override) { if (context_->IsVerbose()) { context_->GetDiagnostics()->Note(DiagMessage() << "merging archive " << input); @@ -1328,18 +1325,11 @@ class LinkCommand { return !error; } - /** - * Takes a path to load and merge into the master ResourceTable. If override - * is true, - * conflicting resources are allowed to override each other, in order of last - * seen. - * - * If the file path ends with .flata, .jar, .jack, or .zip the file is treated - * as ZIP archive - * and the files within are merged individually. - * - * Otherwise the files is processed on its own. - */ + // Takes a path to load and merge into the master ResourceTable. If override is true, + // conflicting resources are allowed to override each other, in order of last seen. + // If the file path ends with .flata, .jar, .jack, or .zip the file is treated + // as ZIP archive and the files within are merged individually. + // Otherwise the file is processed on its own. bool MergePath(const std::string& path, bool override) { if (util::EndsWith(path, ".flata") || util::EndsWith(path, ".jar") || util::EndsWith(path, ".jack") || util::EndsWith(path, ".zip")) { @@ -1352,70 +1342,15 @@ class LinkCommand { return MergeFile(file, override); } - /** - * Takes a file to load and merge into the master ResourceTable. If override - * is true, - * conflicting resources are allowed to override each other, in order of last - * seen. - * - * If the file ends with .arsc.flat, then it is loaded as a ResourceTable and - * merged into the - * master ResourceTable. If the file ends with .flat, then it is treated like - * a compiled file - * and the header data is read and merged into the final ResourceTable. - * - * All other file types are ignored. This is because these files could be - * coming from a zip, - * where we could have other files like classes.dex. - */ + // Takes an AAPT Container file (.apc/.flat) to load and merge into the master ResourceTable. + // If override is true, conflicting resources are allowed to override each other, in order of last + // seen. + // All other file types are ignored. This is because these files could be coming from a zip, + // where we could have other files like classes.dex. bool MergeFile(io::IFile* file, bool override) { const Source& src = file->GetSource(); - if (util::EndsWith(src.path, ".arsc.flat")) { - return MergeResourceTable(file, override); - } else if (util::EndsWith(src.path, ".flat")) { - // Try opening the file and looking for an Export header. - std::unique_ptr data = file->OpenAsData(); - if (!data) { - context_->GetDiagnostics()->Error(DiagMessage(src) << "failed to open"); - return false; - } - - CompiledFileInputStream input_stream(data->data(), data->size()); - uint32_t num_files = 0; - if (!input_stream.ReadLittleEndian32(&num_files)) { - context_->GetDiagnostics()->Error(DiagMessage(src) << "failed read num files"); - return false; - } - - for (uint32_t i = 0; i < num_files; i++) { - pb::internal::CompiledFile compiled_file; - if (!input_stream.ReadCompiledFile(&compiled_file)) { - context_->GetDiagnostics()->Error(DiagMessage(src) - << "failed to read compiled file header"); - return false; - } - - uint64_t offset, len; - if (!input_stream.ReadDataMetaData(&offset, &len)) { - context_->GetDiagnostics()->Error(DiagMessage(src) << "failed to read data meta data"); - return false; - } - - ResourceFile resource_file; - std::string error; - if (!DeserializeCompiledFileFromPb(compiled_file, &resource_file, &error)) { - context_->GetDiagnostics()->Error(DiagMessage(src) - << "failed to read compiled header: " << error); - return false; - } - - if (!MergeCompiledFile(file->CreateFileSegment(offset, len), &resource_file, override)) { - return false; - } - } - return true; - } else if (util::EndsWith(src.path, ".xml") || util::EndsWith(src.path, ".png")) { + if (util::EndsWith(src.path, ".xml") || util::EndsWith(src.path, ".png")) { // Since AAPT compiles these file types and appends .flat to them, seeing // their raw extensions is a sign that they weren't compiled. const StringPiece file_type = util::EndsWith(src.path, ".xml") ? "XML" : "PNG"; @@ -1423,11 +1358,71 @@ class LinkCommand { << " file passed as argument. Must be " "compiled first into .flat file."); return false; + } else if (!util::EndsWith(src.path, ".apc") && !util::EndsWith(src.path, ".flat")) { + if (context_->IsVerbose()) { + context_->GetDiagnostics()->Warn(DiagMessage(src) << "ignoring unrecognized file"); + return true; + } } - // Ignore non .flat files. This could be classes.dex or something else that - // happens - // to be in an archive. + std::unique_ptr input_stream = file->OpenInputStream(); + if (input_stream == nullptr) { + context_->GetDiagnostics()->Error(DiagMessage(src) << "failed to open file"); + return false; + } + + if (input_stream->HadError()) { + context_->GetDiagnostics()->Error(DiagMessage(src) + << "failed to open file: " << input_stream->GetError()); + return false; + } + + ContainerReaderEntry* entry; + ContainerReader reader(input_stream.get()); + while ((entry = reader.Next()) != nullptr) { + if (entry->Type() == ContainerEntryType::kResTable) { + pb::ResourceTable pb_table; + if (!entry->GetResTable(&pb_table)) { + context_->GetDiagnostics()->Error(DiagMessage(src) << "failed to read resource table: " + << entry->GetError()); + return false; + } + + ResourceTable table; + std::string error; + if (!DeserializeTableFromPb(pb_table, &table, &error)) { + context_->GetDiagnostics()->Error(DiagMessage(src) + << "failed to deserialize resource table: " << error); + return false; + } + + if (!table_merger_->Merge(src, &table, override)) { + context_->GetDiagnostics()->Error(DiagMessage(src) << "failed to merge resource table"); + return false; + } + } else if (entry->Type() == ContainerEntryType::kResFile) { + pb::internal::CompiledFile pb_compiled_file; + off64_t offset; + size_t len; + if (!entry->GetResFileOffsets(&pb_compiled_file, &offset, &len)) { + context_->GetDiagnostics()->Error(DiagMessage(src) << "failed to get resource file: " + << entry->GetError()); + return false; + } + + ResourceFile resource_file; + std::string error; + if (!DeserializeCompiledFileFromPb(pb_compiled_file, &resource_file, &error)) { + context_->GetDiagnostics()->Error(DiagMessage(src) + << "failed to read compiled header: " << error); + return false; + } + + if (!MergeCompiledFile(resource_file, file->CreateFileSegment(offset, len), override)) { + return false; + } + } + } return true; } diff --git a/tools/aapt2/cmd/Optimize.cpp b/tools/aapt2/cmd/Optimize.cpp index 67ac67a9367e8..44e148ee9732e 100644 --- a/tools/aapt2/cmd/Optimize.cpp +++ b/tools/aapt2/cmd/Optimize.cpp @@ -33,7 +33,7 @@ #include "filter/AbiFilter.h" #include "format/binary/TableFlattener.h" #include "format/binary/XmlFlattener.h" -#include "io/BigBufferInputStream.h" +#include "io/BigBufferStream.h" #include "io/Util.h" #include "optimize/MultiApkGenerator.h" #include "optimize/ResourceDeduper.h" diff --git a/tools/aapt2/configuration/ConfigurationParser.cpp b/tools/aapt2/configuration/ConfigurationParser.cpp index a79a577c663b8..b99240f0a40a1 100644 --- a/tools/aapt2/configuration/ConfigurationParser.cpp +++ b/tools/aapt2/configuration/ConfigurationParser.cpp @@ -30,7 +30,7 @@ #include "ResourceUtils.h" #include "io/File.h" #include "io/FileSystem.h" -#include "io/StringInputStream.h" +#include "io/StringStream.h" #include "util/Files.h" #include "util/Maybe.h" #include "util/Util.h" diff --git a/tools/aapt2/format/Container.cpp b/tools/aapt2/format/Container.cpp new file mode 100644 index 0000000000000..739555c5b15dc --- /dev/null +++ b/tools/aapt2/format/Container.cpp @@ -0,0 +1,353 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "format/Container.h" + +#include "android-base/scopeguard.h" +#include "android-base/stringprintf.h" + +using ::android::base::StringPrintf; +using ::google::protobuf::io::CodedInputStream; +using ::google::protobuf::io::CodedOutputStream; +using ::google::protobuf::io::ZeroCopyOutputStream; + +namespace aapt { + +constexpr const static uint32_t kContainerFormatMagic = 0x54504141u; +constexpr const static uint32_t kContainerFormatVersion = 1u; + +ContainerWriter::ContainerWriter(ZeroCopyOutputStream* out, size_t entry_count) + : out_(out), total_entry_count_(entry_count), current_entry_count_(0u) { + CodedOutputStream coded_out(out_); + + // Write the magic. + coded_out.WriteLittleEndian32(kContainerFormatMagic); + + // Write the version. + coded_out.WriteLittleEndian32(kContainerFormatVersion); + + // Write the total number of entries. + coded_out.WriteLittleEndian32(static_cast(total_entry_count_)); + + if (coded_out.HadError()) { + error_ = "failed writing container format header"; + } +} + +inline static void WritePadding(int padding, CodedOutputStream* out) { + if (padding < 4) { + const uint32_t zero = 0u; + out->WriteRaw(&zero, padding); + } +} + +bool ContainerWriter::AddResTableEntry(const pb::ResourceTable& table) { + if (current_entry_count_ >= total_entry_count_) { + error_ = "too many entries being serialized"; + return false; + } + current_entry_count_++; + + CodedOutputStream coded_out(out_); + + // Write the type. + coded_out.WriteLittleEndian32(kResTable); + + // Write the aligned size. + const ::google::protobuf::uint64 size = table.ByteSize(); + const int padding = 4 - (size % 4); + coded_out.WriteLittleEndian64(size); + + // Write the table. + table.SerializeWithCachedSizes(&coded_out); + + // Write the padding. + WritePadding(padding, &coded_out); + + if (coded_out.HadError()) { + error_ = "failed writing to output"; + return false; + } + return true; +} + +bool ContainerWriter::AddResFileEntry(const pb::internal::CompiledFile& file, + io::KnownSizeInputStream* in) { + if (current_entry_count_ >= total_entry_count_) { + error_ = "too many entries being serialized"; + return false; + } + current_entry_count_++; + + constexpr const static int kResFileEntryHeaderSize = 12; + + CodedOutputStream coded_out(out_); + + // Write the type. + coded_out.WriteLittleEndian32(kResFile); + + // Write the aligned size. + const ::google::protobuf::uint32 header_size = file.ByteSize(); + const int header_padding = 4 - (header_size % 4); + const ::google::protobuf::uint64 data_size = in->TotalSize(); + const int data_padding = 4 - (data_size % 4); + coded_out.WriteLittleEndian64(kResFileEntryHeaderSize + header_size + header_padding + data_size + + data_padding); + + // Write the res file header size. + coded_out.WriteLittleEndian32(header_size); + + // Write the data payload size. + coded_out.WriteLittleEndian64(data_size); + + // Write the header. + file.SerializeToCodedStream(&coded_out); + + WritePadding(header_padding, &coded_out); + + // Write the data payload. We need to call Trim() since we are going to write to the underlying + // ZeroCopyOutputStream. + coded_out.Trim(); + + // Check at this point if there were any errors. + if (coded_out.HadError()) { + error_ = "failed writing to output"; + return false; + } + + if (!io::Copy(out_, in)) { + if (in->HadError()) { + std::ostringstream error; + error << "failed reading from input: " << in->GetError(); + error_ = error.str(); + } else { + error_ = "failed writing to output"; + } + return false; + } + WritePadding(data_padding, &coded_out); + + if (coded_out.HadError()) { + error_ = "failed writing to output"; + return false; + } + return true; +} + +bool ContainerWriter::HadError() const { + return !error_.empty(); +} + +std::string ContainerWriter::GetError() const { + return error_; +} + +static bool AlignRead(CodedInputStream* in) { + const int padding = 4 - (in->CurrentPosition() % 4); + if (padding < 4) { + return in->Skip(padding); + } + return true; +} + +ContainerReaderEntry::ContainerReaderEntry(ContainerReader* reader) : reader_(reader) { +} + +ContainerEntryType ContainerReaderEntry::Type() const { + return type_; +} + +bool ContainerReaderEntry::GetResTable(pb::ResourceTable* out_table) { + CHECK(type_ == ContainerEntryType::kResTable) << "reading a kResTable when the type is kResFile"; + if (length_ > std::numeric_limits::max()) { + reader_->error_ = StringPrintf("entry length %zu is too large", length_); + return false; + } + + CodedInputStream& coded_in = reader_->coded_in_; + + const CodedInputStream::Limit limit = coded_in.PushLimit(static_cast(length_)); + auto guard = ::android::base::make_scope_guard([&]() { coded_in.PopLimit(limit); }); + + if (!out_table->ParseFromCodedStream(&coded_in)) { + reader_->error_ = "failed to parse ResourceTable"; + return false; + } + return true; +} + +bool ContainerReaderEntry::GetResFileOffsets(pb::internal::CompiledFile* out_file, + off64_t* out_offset, size_t* out_len) { + CHECK(type_ == ContainerEntryType::kResFile) << "reading a kResFile when the type is kResTable"; + + CodedInputStream& coded_in = reader_->coded_in_; + + // Read the ResFile header. + ::google::protobuf::uint32 header_length; + if (!coded_in.ReadLittleEndian32(&header_length)) { + std::ostringstream error; + error << "failed to read header length from input: " << reader_->in_->GetError(); + reader_->error_ = error.str(); + return false; + } + + ::google::protobuf::uint64 data_length; + if (!coded_in.ReadLittleEndian64(&data_length)) { + std::ostringstream error; + error << "failed to read data length from input: " << reader_->in_->GetError(); + reader_->error_ = error.str(); + return false; + } + + if (header_length > std::numeric_limits::max()) { + std::ostringstream error; + error << "header length " << header_length << " is too large"; + reader_->error_ = error.str(); + return false; + } + + if (data_length > std::numeric_limits::max()) { + std::ostringstream error; + error << "data length " << data_length << " is too large"; + reader_->error_ = error.str(); + return false; + } + + { + const CodedInputStream::Limit limit = coded_in.PushLimit(static_cast(header_length)); + auto guard = ::android::base::make_scope_guard([&]() { coded_in.PopLimit(limit); }); + + if (!out_file->ParseFromCodedStream(&coded_in)) { + reader_->error_ = "failed to parse CompiledFile header"; + return false; + } + } + + AlignRead(&coded_in); + + *out_offset = coded_in.CurrentPosition(); + *out_len = data_length; + + coded_in.Skip(static_cast(data_length)); + AlignRead(&coded_in); + return true; +} + +bool ContainerReaderEntry::HadError() const { + return reader_->HadError(); +} + +std::string ContainerReaderEntry::GetError() const { + return reader_->GetError(); +} + +ContainerReader::ContainerReader(io::InputStream* in) + : in_(in), + adaptor_(in), + coded_in_(&adaptor_), + total_entry_count_(0u), + current_entry_count_(0u), + entry_(this) { + ::google::protobuf::uint32 magic; + if (!coded_in_.ReadLittleEndian32(&magic)) { + std::ostringstream error; + error << "failed to read magic from input: " << in_->GetError(); + error_ = error.str(); + return; + } + + if (magic != kContainerFormatMagic) { + error_ = "magic value doesn't match AAPT"; + return; + } + + ::google::protobuf::uint32 version; + if (!coded_in_.ReadLittleEndian32(&version)) { + std::ostringstream error; + error << "failed to read version from input: " << in_->GetError(); + error_ = error.str(); + return; + } + + if (version != kContainerFormatVersion) { + error_ = StringPrintf("container version is 0x%08x but AAPT expects version 0x%08x", version, + kContainerFormatVersion); + return; + } + + ::google::protobuf::uint32 total_entry_count; + if (!coded_in_.ReadLittleEndian32(&total_entry_count)) { + std::ostringstream error; + error << "failed to read entry count from input: " << in_->GetError(); + error_ = error.str(); + return; + } + + total_entry_count_ = total_entry_count; +} + +ContainerReaderEntry* ContainerReader::Next() { + if (current_entry_count_ >= total_entry_count_) { + return nullptr; + } + current_entry_count_++; + + // Ensure the next read is aligned. + AlignRead(&coded_in_); + + ::google::protobuf::uint32 entry_type; + if (!coded_in_.ReadLittleEndian32(&entry_type)) { + std::ostringstream error; + error << "failed reading entry type from input: " << in_->GetError(); + error_ = error.str(); + return nullptr; + } + + ::google::protobuf::uint64 entry_length; + if (!coded_in_.ReadLittleEndian64(&entry_length)) { + std::ostringstream error; + error << "failed reading entry length from input: " << in_->GetError(); + error_ = error.str(); + return nullptr; + } + + if (entry_type == ContainerEntryType::kResFile || entry_type == ContainerEntryType::kResTable) { + entry_.type_ = static_cast(entry_type); + } else { + error_ = StringPrintf("entry type 0x%08x is invalid", entry_type); + return nullptr; + } + + if (entry_length > std::numeric_limits::max()) { + std::ostringstream error; + error << "entry length " << entry_length << " is too large"; + error_ = error.str(); + return nullptr; + } + + entry_.length_ = entry_length; + return &entry_; +} + +bool ContainerReader::HadError() const { + return !error_.empty(); +} + +std::string ContainerReader::GetError() const { + return error_; +} + +} // namespace aapt diff --git a/tools/aapt2/format/Container.h b/tools/aapt2/format/Container.h new file mode 100644 index 0000000000000..aa5c82cd322ca --- /dev/null +++ b/tools/aapt2/format/Container.h @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AAPT_FORMAT_CONTAINER_H +#define AAPT_FORMAT_CONTAINER_H + +#include + +#include "google/protobuf/io/coded_stream.h" +#include "google/protobuf/io/zero_copy_stream.h" + +#include "Resources.pb.h" +#include "ResourcesInternal.pb.h" +#include "io/Io.h" +#include "io/Util.h" +#include "util/BigBuffer.h" + +namespace aapt { + +enum ContainerEntryType : uint8_t { + kResTable = 0x00u, + kResFile = 0x01u, +}; + +class ContainerWriter { + public: + explicit ContainerWriter(::google::protobuf::io::ZeroCopyOutputStream* out, size_t entry_count); + + bool AddResTableEntry(const pb::ResourceTable& table); + bool AddResFileEntry(const pb::internal::CompiledFile& file, io::KnownSizeInputStream* in); + bool HadError() const; + std::string GetError() const; + + private: + DISALLOW_COPY_AND_ASSIGN(ContainerWriter); + + ::google::protobuf::io::ZeroCopyOutputStream* out_; + size_t total_entry_count_; + size_t current_entry_count_; + std::string error_; +}; + +class ContainerReader; + +class ContainerReaderEntry { + public: + ContainerEntryType Type() const; + + bool GetResTable(pb::ResourceTable* out_table); + bool GetResFileOffsets(pb::internal::CompiledFile* out_file, off64_t* out_offset, + size_t* out_len); + + bool HadError() const; + std::string GetError() const; + + private: + DISALLOW_COPY_AND_ASSIGN(ContainerReaderEntry); + + friend class ContainerReader; + + explicit ContainerReaderEntry(ContainerReader* reader); + + ContainerReader* reader_; + ContainerEntryType type_ = ContainerEntryType::kResTable; + size_t length_ = 0u; +}; + +class ContainerReader { + public: + explicit ContainerReader(io::InputStream* in); + + ContainerReaderEntry* Next(); + + bool HadError() const; + std::string GetError() const; + + private: + DISALLOW_COPY_AND_ASSIGN(ContainerReader); + + friend class ContainerReaderEntry; + + io::InputStream* in_; + io::ZeroCopyInputAdaptor adaptor_; + ::google::protobuf::io::CodedInputStream coded_in_; + size_t total_entry_count_; + size_t current_entry_count_; + ContainerReaderEntry entry_; + std::string error_; +}; + +} // namespace aapt + +#endif /* AAPT_FORMAT_CONTAINER_H */ diff --git a/tools/aapt2/format/Container_test.cpp b/tools/aapt2/format/Container_test.cpp new file mode 100644 index 0000000000000..3d064d0169fb6 --- /dev/null +++ b/tools/aapt2/format/Container_test.cpp @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "format/Container.h" + +#include "google/protobuf/io/zero_copy_stream_impl_lite.h" + +#include "io/StringStream.h" +#include "test/Test.h" + +using ::google::protobuf::io::StringOutputStream; +using ::testing::Eq; +using ::testing::IsEmpty; +using ::testing::IsNull; +using ::testing::NotNull; +using ::testing::StrEq; + +namespace aapt { + +TEST(ContainerTest, SerializeCompiledFile) { + std::unique_ptr context = test::ContextBuilder().Build(); + + const std::string expected_data = "123"; + + std::string output_str; + { + StringOutputStream out_stream(&output_str); + ContainerWriter writer(&out_stream, 2u); + ASSERT_FALSE(writer.HadError()); + + pb::internal::CompiledFile pb_compiled_file; + pb_compiled_file.set_resource_name("android:layout/main.xml"); + pb_compiled_file.set_type(pb::internal::CompiledFile::Type::CompiledFile_Type_PROTO_XML); + pb_compiled_file.set_source_path("res/layout/main.xml"); + io::StringInputStream data(expected_data); + ASSERT_TRUE(writer.AddResFileEntry(pb_compiled_file, &data)); + + pb::ResourceTable pb_table; + pb::Package* pb_pkg = pb_table.add_package(); + pb_pkg->set_package_name("android"); + pb_pkg->mutable_package_id()->set_id(0x01u); + ASSERT_TRUE(writer.AddResTableEntry(pb_table)); + + ASSERT_FALSE(writer.HadError()); + } + + io::StringInputStream input(output_str); + ContainerReader reader(&input); + ASSERT_FALSE(reader.HadError()); + + ContainerReaderEntry* entry = reader.Next(); + ASSERT_THAT(entry, NotNull()); + ASSERT_THAT(entry->Type(), Eq(ContainerEntryType::kResFile)); + + pb::internal::CompiledFile pb_new_file; + off64_t offset; + size_t len; + ASSERT_TRUE(entry->GetResFileOffsets(&pb_new_file, &offset, &len)) << entry->GetError(); + EXPECT_THAT(offset & 0x03, Eq(0u)); + EXPECT_THAT(output_str.substr(static_cast(offset), len), StrEq(expected_data)); + + entry = reader.Next(); + ASSERT_THAT(entry, NotNull()); + ASSERT_THAT(entry->Type(), Eq(ContainerEntryType::kResTable)); + + pb::ResourceTable pb_new_table; + ASSERT_TRUE(entry->GetResTable(&pb_new_table)); + ASSERT_THAT(pb_new_table.package_size(), Eq(1)); + EXPECT_THAT(pb_new_table.package(0).package_name(), StrEq("android")); + EXPECT_THAT(pb_new_table.package(0).package_id().id(), Eq(0x01u)); + + EXPECT_THAT(reader.Next(), IsNull()); + EXPECT_FALSE(reader.HadError()); + EXPECT_THAT(reader.GetError(), IsEmpty()); +} + +} // namespace aapt diff --git a/tools/aapt2/format/proto/ProtoDeserialize.cpp b/tools/aapt2/format/proto/ProtoDeserialize.cpp index c14f09a3b103e..c722307240c15 100644 --- a/tools/aapt2/format/proto/ProtoDeserialize.cpp +++ b/tools/aapt2/format/proto/ProtoDeserialize.cpp @@ -26,7 +26,6 @@ #include "ValueVisitor.h" using ::android::ResStringPool; -using ::google::protobuf::io::CodedInputStream; namespace aapt { @@ -485,6 +484,20 @@ bool DeserializeTableFromPb(const pb::ResourceTable& pb_table, ResourceTable* ou return true; } +static ResourceFile::Type DeserializeCompiledFileType( + const pb::internal::CompiledFile::Type& pb_type) { + switch (pb_type) { + case pb::internal::CompiledFile::Type::CompiledFile_Type_PNG: + return ResourceFile::Type::kPng; + case pb::internal::CompiledFile::Type::CompiledFile_Type_BINARY_XML: + return ResourceFile::Type::kBinaryXml; + case pb::internal::CompiledFile::Type::CompiledFile_Type_PROTO_XML: + return ResourceFile::Type::kProtoXml; + default: + return ResourceFile::Type::kUnknown; + } +} + bool DeserializeCompiledFileFromPb(const pb::internal::CompiledFile& pb_file, ResourceFile* out_file, std::string* out_error) { ResourceNameRef name_ref; @@ -497,6 +510,7 @@ bool DeserializeCompiledFileFromPb(const pb::internal::CompiledFile& pb_file, out_file->name = name_ref.ToResourceName(); out_file->source.path = pb_file.source_path(); + out_file->type = DeserializeCompiledFileType(pb_file.type()); std::string config_error; if (!DeserializeConfigFromPb(pb_file.config(), &out_file->config, &config_error)) { @@ -847,72 +861,4 @@ bool DeserializeXmlFromPb(const pb::XmlNode& pb_node, xml::Element* out_el, Stri return true; } -CompiledFileInputStream::CompiledFileInputStream(const void* data, size_t size) - : in_(static_cast(data), size) { -} - -void CompiledFileInputStream::EnsureAlignedRead() { - const int overflow = in_.CurrentPosition() % 4; - if (overflow > 0) { - // Reads are always 4 byte aligned. - in_.Skip(4 - overflow); - } -} - -bool CompiledFileInputStream::ReadLittleEndian32(uint32_t* out_val) { - EnsureAlignedRead(); - return in_.ReadLittleEndian32(out_val); -} - -bool CompiledFileInputStream::ReadCompiledFile(pb::internal::CompiledFile* out_val) { - EnsureAlignedRead(); - - google::protobuf::uint64 pb_size = 0u; - if (!in_.ReadLittleEndian64(&pb_size)) { - return false; - } - - CodedInputStream::Limit l = in_.PushLimit(static_cast(pb_size)); - - // Check that we haven't tried to read past the end. - if (static_cast(in_.BytesUntilLimit()) != pb_size) { - in_.PopLimit(l); - in_.PushLimit(0); - return false; - } - - if (!out_val->ParsePartialFromCodedStream(&in_)) { - in_.PopLimit(l); - in_.PushLimit(0); - return false; - } - - in_.PopLimit(l); - return true; -} - -bool CompiledFileInputStream::ReadDataMetaData(uint64_t* out_offset, uint64_t* out_len) { - EnsureAlignedRead(); - - google::protobuf::uint64 pb_size = 0u; - if (!in_.ReadLittleEndian64(&pb_size)) { - return false; - } - - // Check that we aren't trying to read past the end. - if (pb_size > static_cast(in_.BytesUntilLimit())) { - in_.PushLimit(0); - return false; - } - - uint64_t offset = static_cast(in_.CurrentPosition()); - if (!in_.Skip(pb_size)) { - return false; - } - - *out_offset = offset; - *out_len = pb_size; - return true; -} - } // namespace aapt diff --git a/tools/aapt2/format/proto/ProtoDeserialize.h b/tools/aapt2/format/proto/ProtoDeserialize.h index c8a7199eb8b58..7dc54f242d947 100644 --- a/tools/aapt2/format/proto/ProtoDeserialize.h +++ b/tools/aapt2/format/proto/ProtoDeserialize.h @@ -19,7 +19,6 @@ #include "android-base/macros.h" #include "androidfw/ResourceTypes.h" -#include "google/protobuf/io/coded_stream.h" #include "ConfigDescription.h" #include "Configuration.pb.h" @@ -57,22 +56,6 @@ bool DeserializeTableFromPb(const pb::ResourceTable& pb_table, ResourceTable* ou bool DeserializeCompiledFileFromPb(const pb::internal::CompiledFile& pb_file, ResourceFile* out_file, std::string* out_error); -class CompiledFileInputStream { - public: - explicit CompiledFileInputStream(const void* data, size_t size); - - bool ReadLittleEndian32(uint32_t* outVal); - bool ReadCompiledFile(pb::internal::CompiledFile* outVal); - bool ReadDataMetaData(uint64_t* outOffset, uint64_t* outLen); - - private: - DISALLOW_COPY_AND_ASSIGN(CompiledFileInputStream); - - void EnsureAlignedRead(); - - ::google::protobuf::io::CodedInputStream in_; -}; - } // namespace aapt #endif /* AAPT_FORMAT_PROTO_PROTODESERIALIZE_H */ diff --git a/tools/aapt2/format/proto/ProtoSerialize.cpp b/tools/aapt2/format/proto/ProtoSerialize.cpp index c0d36141d457e..a75bbc73568bb 100644 --- a/tools/aapt2/format/proto/ProtoSerialize.cpp +++ b/tools/aapt2/format/proto/ProtoSerialize.cpp @@ -16,14 +16,9 @@ #include "format/proto/ProtoSerialize.h" -#include "google/protobuf/io/zero_copy_stream_impl_lite.h" - #include "ValueVisitor.h" #include "util/BigBuffer.h" -using ::google::protobuf::io::CodedOutputStream; -using ::google::protobuf::io::ZeroCopyOutputStream; - namespace aapt { void SerializeStringPoolToPb(const StringPool& pool, pb::StringPool* out_pb_pool) { @@ -512,9 +507,23 @@ void SerializeItemToPb(const Item& item, pb::Item* out_item) { out_item->MergeFrom(value.item()); } +static pb::internal::CompiledFile::Type SerializeCompiledFileType(const ResourceFile::Type& type) { + switch (type) { + case ResourceFile::Type::kPng: + return pb::internal::CompiledFile::Type::CompiledFile_Type_PNG; + case ResourceFile::Type::kBinaryXml: + return pb::internal::CompiledFile::Type::CompiledFile_Type_BINARY_XML; + case ResourceFile::Type::kProtoXml: + return pb::internal::CompiledFile::Type::CompiledFile_Type_PROTO_XML; + default: + return pb::internal::CompiledFile::Type::CompiledFile_Type_UNKNOWN; + } +} + void SerializeCompiledFileToPb(const ResourceFile& file, pb::internal::CompiledFile* out_file) { out_file->set_resource_name(file.name.ToString()); out_file->set_source_path(file.source.path); + out_file->set_type(SerializeCompiledFileType(file.type)); SerializeConfig(file.config, out_file->mutable_config()); for (const SourcedResourceName& exported : file.exported_symbols) { @@ -579,44 +588,4 @@ void SerializeXmlResourceToPb(const xml::XmlResource& resource, pb::XmlNode* out SerializeXmlToPb(*resource.root, out_node); } -CompiledFileOutputStream::CompiledFileOutputStream(ZeroCopyOutputStream* out) : out_(out) { -} - -void CompiledFileOutputStream::EnsureAlignedWrite() { - const int overflow = out_.ByteCount() % 4; - if (overflow > 0) { - uint32_t zero = 0u; - out_.WriteRaw(&zero, 4 - overflow); - } -} - -void CompiledFileOutputStream::WriteLittleEndian32(uint32_t val) { - EnsureAlignedWrite(); - out_.WriteLittleEndian32(val); -} - -void CompiledFileOutputStream::WriteCompiledFile(const pb::internal::CompiledFile& compiled_file) { - EnsureAlignedWrite(); - out_.WriteLittleEndian64(static_cast(compiled_file.ByteSize())); - compiled_file.SerializeWithCachedSizes(&out_); -} - -void CompiledFileOutputStream::WriteData(const BigBuffer& buffer) { - EnsureAlignedWrite(); - out_.WriteLittleEndian64(static_cast(buffer.size())); - for (const BigBuffer::Block& block : buffer) { - out_.WriteRaw(block.buffer.get(), block.size); - } -} - -void CompiledFileOutputStream::WriteData(const void* data, size_t len) { - EnsureAlignedWrite(); - out_.WriteLittleEndian64(static_cast(len)); - out_.WriteRaw(data, len); -} - -bool CompiledFileOutputStream::HadError() { - return out_.HadError(); -} - } // namespace aapt diff --git a/tools/aapt2/format/proto/ProtoSerialize.h b/tools/aapt2/format/proto/ProtoSerialize.h index 1694b16f32778..95dd413c1713c 100644 --- a/tools/aapt2/format/proto/ProtoSerialize.h +++ b/tools/aapt2/format/proto/ProtoSerialize.h @@ -18,7 +18,6 @@ #define AAPT_FORMAT_PROTO_PROTOSERIALIZE_H #include "android-base/macros.h" -#include "google/protobuf/io/coded_stream.h" #include "ConfigDescription.h" #include "Configuration.pb.h" @@ -29,14 +28,6 @@ #include "StringPool.h" #include "xml/XmlDom.h" -namespace google { -namespace protobuf { -namespace io { -class ZeroCopyOutputStream; -} // namespace io -} // namespace protobuf -} // namespace google - namespace aapt { // Serializes a Value to its protobuf representation. An optional StringPool will hold the @@ -66,24 +57,6 @@ void SerializeTableToPb(const ResourceTable& table, pb::ResourceTable* out_table // Serializes a ResourceFile into its protobuf representation. void SerializeCompiledFileToPb(const ResourceFile& file, pb::internal::CompiledFile* out_file); -class CompiledFileOutputStream { - public: - explicit CompiledFileOutputStream(::google::protobuf::io::ZeroCopyOutputStream* out); - - void WriteLittleEndian32(uint32_t value); - void WriteCompiledFile(const pb::internal::CompiledFile& compiledFile); - void WriteData(const BigBuffer& buffer); - void WriteData(const void* data, size_t len); - bool HadError(); - - private: - DISALLOW_COPY_AND_ASSIGN(CompiledFileOutputStream); - - void EnsureAlignedWrite(); - - ::google::protobuf::io::CodedOutputStream out_; -}; - } // namespace aapt #endif /* AAPT_FORMAT_PROTO_PROTOSERIALIZE_H */ diff --git a/tools/aapt2/format/proto/ProtoSerialize_test.cpp b/tools/aapt2/format/proto/ProtoSerialize_test.cpp index 2154d5a9ab2c3..8efac8ae6ffd7 100644 --- a/tools/aapt2/format/proto/ProtoSerialize_test.cpp +++ b/tools/aapt2/format/proto/ProtoSerialize_test.cpp @@ -16,14 +16,11 @@ #include "format/proto/ProtoSerialize.h" -#include "google/protobuf/io/zero_copy_stream_impl_lite.h" - #include "ResourceUtils.h" #include "format/proto/ProtoDeserialize.h" #include "test/Test.h" using ::android::StringPiece; -using ::google::protobuf::io::StringOutputStream; using ::testing::Eq; using ::testing::IsEmpty; using ::testing::NotNull; @@ -137,113 +134,6 @@ TEST(ProtoSerializeTest, SerializeSinglePackage) { EXPECT_THAT(actual_styled_str->value->spans[0].last_char, Eq(4u)); } -TEST(ProtoSerializeTest, SerializeFileHeader) { - std::unique_ptr context = test::ContextBuilder().Build(); - - ResourceFile f; - f.config = test::ParseConfigOrDie("hdpi-v9"); - f.name = test::ParseNameOrDie("com.app.a:layout/main"); - f.source.path = "res/layout-hdpi-v9/main.xml"; - f.exported_symbols.push_back(SourcedResourceName{test::ParseNameOrDie("id/unchecked"), 23u}); - - const std::string expected_data1 = "123"; - const std::string expected_data2 = "1234"; - - std::string output_str; - { - pb::internal::CompiledFile pb_f1, pb_f2; - SerializeCompiledFileToPb(f, &pb_f1); - - f.name.entry = "__" + f.name.entry + "$0"; - SerializeCompiledFileToPb(f, &pb_f2); - - StringOutputStream out_stream(&output_str); - CompiledFileOutputStream out_file_stream(&out_stream); - out_file_stream.WriteLittleEndian32(2); - out_file_stream.WriteCompiledFile(pb_f1); - out_file_stream.WriteData(expected_data1.data(), expected_data1.size()); - out_file_stream.WriteCompiledFile(pb_f2); - out_file_stream.WriteData(expected_data2.data(), expected_data2.size()); - ASSERT_FALSE(out_file_stream.HadError()); - } - - CompiledFileInputStream in_file_stream(output_str.data(), output_str.size()); - uint32_t num_files = 0; - ASSERT_TRUE(in_file_stream.ReadLittleEndian32(&num_files)); - ASSERT_EQ(2u, num_files); - - // Read the first compiled file. - - pb::internal::CompiledFile new_pb_f1; - ASSERT_TRUE(in_file_stream.ReadCompiledFile(&new_pb_f1)); - - ResourceFile new_f1; - std::string error; - ASSERT_TRUE(DeserializeCompiledFileFromPb(new_pb_f1, &new_f1, &error)); - EXPECT_THAT(error, IsEmpty()); - - uint64_t offset, len; - ASSERT_TRUE(in_file_stream.ReadDataMetaData(&offset, &len)); - - std::string actual_data(output_str.data() + offset, len); - EXPECT_EQ(expected_data1, actual_data); - - // Expect the data to be aligned. - EXPECT_EQ(0u, offset & 0x03); - - ASSERT_EQ(1u, new_f1.exported_symbols.size()); - EXPECT_EQ(test::ParseNameOrDie("id/unchecked"), new_f1.exported_symbols[0].name); - - // Read the second compiled file. - - pb::internal::CompiledFile new_pb_f2; - ASSERT_TRUE(in_file_stream.ReadCompiledFile(&new_pb_f2)); - - ResourceFile new_f2; - ASSERT_TRUE(DeserializeCompiledFileFromPb(new_pb_f2, &new_f2, &error)); - EXPECT_THAT(error, IsEmpty()); - - ASSERT_TRUE(in_file_stream.ReadDataMetaData(&offset, &len)); - - actual_data = std::string(output_str.data() + offset, len); - EXPECT_EQ(expected_data2, actual_data); - - // Expect the data to be aligned. - EXPECT_EQ(0u, offset & 0x03); -} - -TEST(ProtoSerializeTest, DeserializeCorruptHeaderSafely) { - ResourceFile f; - pb::internal::CompiledFile pb_file; - SerializeCompiledFileToPb(f, &pb_file); - - const std::string expected_data = "1234"; - - std::string output_str; - { - StringOutputStream out_stream(&output_str); - CompiledFileOutputStream out_file_stream(&out_stream); - out_file_stream.WriteLittleEndian32(1); - out_file_stream.WriteCompiledFile(pb_file); - out_file_stream.WriteData(expected_data.data(), expected_data.size()); - ASSERT_FALSE(out_file_stream.HadError()); - } - - output_str[4] = 0xff; - - CompiledFileInputStream in_file_stream(output_str.data(), output_str.size()); - - uint32_t num_files = 0; - EXPECT_TRUE(in_file_stream.ReadLittleEndian32(&num_files)); - EXPECT_EQ(1u, num_files); - - pb::internal::CompiledFile new_pb_file; - EXPECT_FALSE(in_file_stream.ReadCompiledFile(&new_pb_file)); - - uint64_t offset, len; - EXPECT_FALSE(in_file_stream.ReadDataMetaData(&offset, &len)); -} - TEST(ProtoSerializeTest, SerializeAndDeserializeXml) { xml::Element element; element.line_number = 22; diff --git a/tools/aapt2/formats.md b/tools/aapt2/formats.md new file mode 100644 index 0000000000000..bb31a005ef42f --- /dev/null +++ b/tools/aapt2/formats.md @@ -0,0 +1,44 @@ +# AAPT2 On-Disk Formats +- AAPT2 Container Format (extension `.apc`) +- AAPT2 Static Library Format (extension `.sapk`) + +## AAPT2 Container Format (extension `.apc`) +The APC format (AAPT2 Container Format) is generated by AAPT2 during the compile phase and +consumed by the AAPT2 link phase. It is a simple container format for storing compiled PNGs, +binary and protobuf XML, and intermediate protobuf resource tables. It also stores all associated +meta-data from the compile phase. + +### Format +The file starts with a simple header. All multi-byte fields are little-endian. + +| Size (in bytes) | Field | Description | +|:----------------|:--------------|:-----------------------------------------------------| +| `4` | `magic` | The magic bytes must equal `'AAPT'` or `0x54504141`. | +| `4` | `version` | The version of the container format. | +| `4` | `entry_count` | The number of entries in this container. | + +This is followed by `entry_count` of the following data structure. It must be aligned on a 32-bit +boundary, so if a previous entry ends unaligned, padding must be inserted. + +| Size (in bytes) | Field | Description | +|:----------------|:---------------|:----------------------------------------------------------------------------------------------------------| +| `4` | `entry_type` | The type of the entry. This can be one of two types: `RES_TABLE (0x00000000)` or `RES_FILE (0x00000001)`. | +| `8` | `entry_length` | The length of the data that follows. | +| `entry_length` | `data` | The payload. The contents of this varies based on the `entry_type`. | + +If the `entry_type` is equal to `RES_TABLE (0x00000000)`, the `data` field contains a serialized +[aapt.pb.ResourceTable](Resources.proto). + +If the `entry_type` is equal to `RES_FILE (0x00000001)`, the `data` field contains the following: + + +| Size (in bytes) | Field | Description | +|:----------------|:---------------|:----------------------------------------------------------------------------------------------------------| +| `4` | `header_size` | The size of the `header` field. | +| `8` | `data_size` | The size of the `data` field. | +| `header_size` | `header` | The serialized Protobuf message [aapt.pb.internal.CompiledFile](ResourcesInternal.proto). | +| `x` | `padding` | Up to 4 bytes of zeros, if padding is necessary to align the `data` field on a 32-bit boundary. | +| `data_size` | `data` | The payload, which is determined by the `type` field in the `aapt.pb.internal.CompiledFile`. This can be a PNG file, binary XML, or [aapt.pb.XmlNode](Resources.proto). | + +## AAPT2 Static Library Format (extension `.sapk`) + diff --git a/tools/aapt2/io/BigBufferOutputStream.h b/tools/aapt2/io/BigBufferOutputStream.h deleted file mode 100644 index 95113bc2132ce..0000000000000 --- a/tools/aapt2/io/BigBufferOutputStream.h +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (C) 2017 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef AAPT_IO_BIGBUFFEROUTPUTSTREAM_H -#define AAPT_IO_BIGBUFFEROUTPUTSTREAM_H - -#include "io/Io.h" -#include "util/BigBuffer.h" - -namespace aapt { -namespace io { - -class BigBufferOutputStream : public OutputStream { - public: - inline explicit BigBufferOutputStream(BigBuffer* buffer) : buffer_(buffer) {} - virtual ~BigBufferOutputStream() = default; - - bool Next(void** data, size_t* size) override; - - void BackUp(size_t count) override; - - size_t ByteCount() const override; - - bool HadError() const override; - - private: - DISALLOW_COPY_AND_ASSIGN(BigBufferOutputStream); - - BigBuffer* buffer_; -}; - -} // namespace io -} // namespace aapt - -#endif // AAPT_IO_BIGBUFFEROUTPUTSTREAM_H diff --git a/tools/aapt2/io/BigBufferStreams.cpp b/tools/aapt2/io/BigBufferStream.cpp similarity index 74% rename from tools/aapt2/io/BigBufferStreams.cpp rename to tools/aapt2/io/BigBufferStream.cpp index eb99033e1cbef..9704caae4719f 100644 --- a/tools/aapt2/io/BigBufferStreams.cpp +++ b/tools/aapt2/io/BigBufferStream.cpp @@ -14,8 +14,7 @@ * limitations under the License. */ -#include "io/BigBufferInputStream.h" -#include "io/BigBufferOutputStream.h" +#include "io/BigBufferStream.h" namespace aapt { namespace io { @@ -54,7 +53,9 @@ void BigBufferInputStream::BackUp(size_t count) { } } -bool BigBufferInputStream::CanRewind() const { return true; } +bool BigBufferInputStream::CanRewind() const { + return true; +} bool BigBufferInputStream::Rewind() { iter_ = buffer_->begin(); @@ -63,9 +64,17 @@ bool BigBufferInputStream::Rewind() { return true; } -size_t BigBufferInputStream::ByteCount() const { return bytes_read_; } +size_t BigBufferInputStream::ByteCount() const { + return bytes_read_; +} -bool BigBufferInputStream::HadError() const { return false; } +bool BigBufferInputStream::HadError() const { + return false; +} + +size_t BigBufferInputStream::TotalSize() const { + return buffer_->size(); +} // // BigBufferOutputStream @@ -76,11 +85,17 @@ bool BigBufferOutputStream::Next(void** data, size_t* size) { return true; } -void BigBufferOutputStream::BackUp(size_t count) { buffer_->BackUp(count); } +void BigBufferOutputStream::BackUp(size_t count) { + buffer_->BackUp(count); +} -size_t BigBufferOutputStream::ByteCount() const { return buffer_->size(); } +size_t BigBufferOutputStream::ByteCount() const { + return buffer_->size(); +} -bool BigBufferOutputStream::HadError() const { return false; } +bool BigBufferOutputStream::HadError() const { + return false; +} } // namespace io } // namespace aapt diff --git a/tools/aapt2/io/BigBufferInputStream.h b/tools/aapt2/io/BigBufferStream.h similarity index 64% rename from tools/aapt2/io/BigBufferInputStream.h rename to tools/aapt2/io/BigBufferStream.h index 92612c744aae1..8b5c8b84cd3cf 100644 --- a/tools/aapt2/io/BigBufferInputStream.h +++ b/tools/aapt2/io/BigBufferStream.h @@ -14,8 +14,8 @@ * limitations under the License. */ -#ifndef AAPT_IO_BIGBUFFERINPUTSTREAM_H -#define AAPT_IO_BIGBUFFERINPUTSTREAM_H +#ifndef AAPT_IO_BIGBUFFERSTREAM_H +#define AAPT_IO_BIGBUFFERSTREAM_H #include "io/Io.h" #include "util/BigBuffer.h" @@ -23,10 +23,11 @@ namespace aapt { namespace io { -class BigBufferInputStream : public InputStream { +class BigBufferInputStream : public KnownSizeInputStream { public: inline explicit BigBufferInputStream(const BigBuffer* buffer) - : buffer_(buffer), iter_(buffer->begin()) {} + : buffer_(buffer), iter_(buffer->begin()) { + } virtual ~BigBufferInputStream() = default; bool Next(const void** data, size_t* size) override; @@ -41,6 +42,8 @@ class BigBufferInputStream : public InputStream { bool HadError() const override; + size_t TotalSize() const override; + private: DISALLOW_COPY_AND_ASSIGN(BigBufferInputStream); @@ -50,7 +53,27 @@ class BigBufferInputStream : public InputStream { size_t bytes_read_ = 0; }; +class BigBufferOutputStream : public OutputStream { + public: + inline explicit BigBufferOutputStream(BigBuffer* buffer) : buffer_(buffer) { + } + virtual ~BigBufferOutputStream() = default; + + bool Next(void** data, size_t* size) override; + + void BackUp(size_t count) override; + + size_t ByteCount() const override; + + bool HadError() const override; + + private: + DISALLOW_COPY_AND_ASSIGN(BigBufferOutputStream); + + BigBuffer* buffer_; +}; + } // namespace io } // namespace aapt -#endif // AAPT_IO_BIGBUFFERINPUTSTREAM_H +#endif // AAPT_IO_BIGBUFFERSTREAM_H diff --git a/tools/aapt2/io/Data.h b/tools/aapt2/io/Data.h index 09dc7ea5b1c48..db91a77a5ae6d 100644 --- a/tools/aapt2/io/Data.h +++ b/tools/aapt2/io/Data.h @@ -28,12 +28,16 @@ namespace aapt { namespace io { // Interface for a block of contiguous memory. An instance of this interface owns the data. -class IData : public InputStream { +class IData : public KnownSizeInputStream { public: virtual ~IData() = default; virtual const void* data() const = 0; virtual size_t size() const = 0; + + virtual size_t TotalSize() const override { + return size(); + } }; class DataSegment : public IData { diff --git a/tools/aapt2/io/File.cpp b/tools/aapt2/io/File.cpp index ee737280ec81b..b4f1ff3a5b49e 100644 --- a/tools/aapt2/io/File.cpp +++ b/tools/aapt2/io/File.cpp @@ -39,5 +39,9 @@ std::unique_ptr FileSegment::OpenAsData() { return {}; } +std::unique_ptr FileSegment::OpenInputStream() { + return OpenAsData(); +} + } // namespace io } // namespace aapt diff --git a/tools/aapt2/io/File.h b/tools/aapt2/io/File.h index 7ef6d88c1e3b3..f06e28c79c7bc 100644 --- a/tools/aapt2/io/File.h +++ b/tools/aapt2/io/File.h @@ -43,6 +43,8 @@ class IFile { // Returns nullptr on failure. virtual std::unique_ptr OpenAsData() = 0; + virtual std::unique_ptr OpenInputStream() = 0; + // Returns the source of this file. This is for presentation to the user and // may not be a valid file system path (for example, it may contain a '@' sign to separate // the files within a ZIP archive from the path to the containing ZIP archive. @@ -71,8 +73,11 @@ class FileSegment : public IFile { : file_(file), offset_(offset), len_(len) {} std::unique_ptr OpenAsData() override; + std::unique_ptr OpenInputStream() override; - const Source& GetSource() const override { return file_->GetSource(); } + const Source& GetSource() const override { + return file_->GetSource(); + } private: DISALLOW_COPY_AND_ASSIGN(FileSegment); diff --git a/tools/aapt2/io/FileInputStream.cpp b/tools/aapt2/io/FileStream.cpp similarity index 59% rename from tools/aapt2/io/FileInputStream.cpp rename to tools/aapt2/io/FileStream.cpp index 07dbb5a98add7..2f7a4b3479caa 100644 --- a/tools/aapt2/io/FileInputStream.cpp +++ b/tools/aapt2/io/FileStream.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#include "io/FileInputStream.h" +#include "io/FileStream.h" #include // for errno #include // for O_RDONLY @@ -66,6 +66,7 @@ bool FileInputStream::Next(const void** data, size_t* size) { if (n < 0) { error_ = SystemErrorCodeToString(errno); fd_.reset(); + buffer_.reset(); return false; } @@ -91,12 +92,89 @@ size_t FileInputStream::ByteCount() const { } bool FileInputStream::HadError() const { - return !error_.empty(); + return fd_ == -1; } std::string FileInputStream::GetError() const { return error_; } +FileOutputStream::FileOutputStream(const std::string& path, int mode, size_t buffer_capacity) + : FileOutputStream(::android::base::utf8::open(path.c_str(), mode), buffer_capacity) { +} + +FileOutputStream::FileOutputStream(int fd, size_t buffer_capacity) + : fd_(fd), buffer_capacity_(buffer_capacity), buffer_offset_(0u), total_byte_count_(0u) { + if (fd_ == -1) { + error_ = SystemErrorCodeToString(errno); + } else { + buffer_.reset(new uint8_t[buffer_capacity_]); + } +} + +FileOutputStream::~FileOutputStream() { + // Flush the buffer. + Flush(); +} + +bool FileOutputStream::Next(void** data, size_t* size) { + if (fd_ == -1 || HadError()) { + return false; + } + + if (buffer_offset_ == buffer_capacity_) { + if (!FlushImpl()) { + return false; + } + } + + const size_t buffer_size = buffer_capacity_ - buffer_offset_; + *data = buffer_.get() + buffer_offset_; + *size = buffer_size; + total_byte_count_ += buffer_size; + buffer_offset_ = buffer_capacity_; + return true; +} + +void FileOutputStream::BackUp(size_t count) { + if (count > buffer_offset_) { + count = buffer_offset_; + } + buffer_offset_ -= count; + total_byte_count_ -= count; +} + +size_t FileOutputStream::ByteCount() const { + return total_byte_count_; +} + +bool FileOutputStream::Flush() { + if (!HadError()) { + return FlushImpl(); + } + return false; +} + +bool FileOutputStream::FlushImpl() { + ssize_t n = TEMP_FAILURE_RETRY(write(fd_, buffer_.get(), buffer_offset_)); + if (n < 0) { + error_ = SystemErrorCodeToString(errno); + fd_.reset(); + buffer_.reset(); + return false; + } + + buffer_offset_ = 0u; + return true; +} + +bool FileOutputStream::HadError() const { + return fd_ == -1; +} + +std::string FileOutputStream::GetError() const { + return error_; +} + } // namespace io } // namespace aapt diff --git a/tools/aapt2/io/FileInputStream.h b/tools/aapt2/io/FileStream.h similarity index 60% rename from tools/aapt2/io/FileInputStream.h rename to tools/aapt2/io/FileStream.h index 6beb9a186ce5e..3b0766750eae8 100644 --- a/tools/aapt2/io/FileInputStream.h +++ b/tools/aapt2/io/FileStream.h @@ -14,14 +14,15 @@ * limitations under the License. */ -#ifndef AAPT_IO_FILEINPUTSTREAM_H -#define AAPT_IO_FILEINPUTSTREAM_H +#ifndef AAPT_IO_FILESTREAM_H +#define AAPT_IO_FILESTREAM_H #include "io/Io.h" #include #include +#include "android-base/file.h" // for O_BINARY #include "android-base/macros.h" #include "android-base/unique_fd.h" @@ -57,7 +58,43 @@ class FileInputStream : public InputStream { size_t total_byte_count_; }; +class FileOutputStream : public OutputStream { + public: + explicit FileOutputStream(const std::string& path, int mode = O_RDWR | O_CREAT | O_BINARY, + size_t buffer_capacity = 4096); + + // Takes ownership of `fd`. + explicit FileOutputStream(int fd, size_t buffer_capacity = 4096); + + ~FileOutputStream(); + + bool Next(void** data, size_t* size) override; + + // Immediately flushes out the contents of the buffer to disk. + bool Flush(); + + void BackUp(size_t count) override; + + size_t ByteCount() const override; + + bool HadError() const override; + + std::string GetError() const override; + + private: + DISALLOW_COPY_AND_ASSIGN(FileOutputStream); + + bool FlushImpl(); + + android::base::unique_fd fd_; + std::string error_; + std::unique_ptr buffer_; + size_t buffer_capacity_; + size_t buffer_offset_; + size_t total_byte_count_; +}; + } // namespace io } // namespace aapt -#endif // AAPT_IO_FILEINPUTSTREAM_H +#endif // AAPT_IO_FILESTREAM_H diff --git a/tools/aapt2/io/FileInputStream_test.cpp b/tools/aapt2/io/FileStream_test.cpp similarity index 68% rename from tools/aapt2/io/FileInputStream_test.cpp rename to tools/aapt2/io/FileStream_test.cpp index 7314ab7beebae..68c3cb1f41565 100644 --- a/tools/aapt2/io/FileInputStream_test.cpp +++ b/tools/aapt2/io/FileStream_test.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#include "io/FileInputStream.h" +#include "io/FileStream.h" #include "android-base/macros.h" #include "android-base/test_utils.h" @@ -36,7 +36,7 @@ TEST(FileInputStreamTest, NextAndBackup) { lseek64(file.fd, 0, SEEK_SET); // Use a small buffer size so that we can call Next() a few times. - FileInputStream in(file.fd, 10u); + FileInputStream in(file.release(), 10u); ASSERT_FALSE(in.HadError()); EXPECT_THAT(in.ByteCount(), Eq(0u)); @@ -83,5 +83,47 @@ TEST(FileInputStreamTest, NextAndBackup) { EXPECT_FALSE(in.HadError()); } +TEST(FileOutputStreamTest, NextAndBackup) { + const std::string input = "this is a cool string"; + + TemporaryFile file; + int fd = file.release(); + + // FileOutputStream takes ownership. + FileOutputStream out(fd, 10u); + ASSERT_FALSE(out.HadError()); + EXPECT_THAT(out.ByteCount(), Eq(0u)); + + char* buffer; + size_t size; + ASSERT_TRUE(out.Next(reinterpret_cast(&buffer), &size)); + ASSERT_THAT(size, Eq(10u)); + ASSERT_THAT(buffer, NotNull()); + EXPECT_THAT(out.ByteCount(), Eq(10u)); + memcpy(buffer, input.c_str(), size); + + ASSERT_TRUE(out.Next(reinterpret_cast(&buffer), &size)); + ASSERT_THAT(size, Eq(10u)); + ASSERT_THAT(buffer, NotNull()); + EXPECT_THAT(out.ByteCount(), Eq(20u)); + memcpy(buffer, input.c_str() + 10u, size); + + ASSERT_TRUE(out.Next(reinterpret_cast(&buffer), &size)); + ASSERT_THAT(size, Eq(10u)); + ASSERT_THAT(buffer, NotNull()); + EXPECT_THAT(out.ByteCount(), Eq(30u)); + buffer[0] = input[20u]; + out.BackUp(size - 1); + EXPECT_THAT(out.ByteCount(), Eq(21u)); + + ASSERT_TRUE(out.Flush()); + + lseek64(fd, 0, SEEK_SET); + + std::string actual; + ASSERT_TRUE(android::base::ReadFdToString(fd, &actual)); + EXPECT_THAT(actual, StrEq(input)); +} + } // namespace io } // namespace aapt diff --git a/tools/aapt2/io/FileSystem.cpp b/tools/aapt2/io/FileSystem.cpp index 027cbd041fa64..1387d2218ed4c 100644 --- a/tools/aapt2/io/FileSystem.cpp +++ b/tools/aapt2/io/FileSystem.cpp @@ -20,11 +20,12 @@ #include "utils/FileMap.h" #include "Source.h" +#include "io/FileStream.h" #include "util/Files.h" #include "util/Maybe.h" #include "util/Util.h" -using android::StringPiece; +using ::android::StringPiece; namespace aapt { namespace io { @@ -42,12 +43,20 @@ std::unique_ptr RegularFile::OpenAsData() { return {}; } -const Source& RegularFile::GetSource() const { return source_; } +std::unique_ptr RegularFile::OpenInputStream() { + return util::make_unique(source_.path); +} + +const Source& RegularFile::GetSource() const { + return source_; +} FileCollectionIterator::FileCollectionIterator(FileCollection* collection) : current_(collection->files_.begin()), end_(collection->files_.end()) {} -bool FileCollectionIterator::HasNext() { return current_ != end_; } +bool FileCollectionIterator::HasNext() { + return current_ != end_; +} IFile* FileCollectionIterator::Next() { IFile* result = current_->second.get(); diff --git a/tools/aapt2/io/FileSystem.h b/tools/aapt2/io/FileSystem.h index dfd37172004b1..6be8807735f16 100644 --- a/tools/aapt2/io/FileSystem.h +++ b/tools/aapt2/io/FileSystem.h @@ -24,17 +24,18 @@ namespace aapt { namespace io { -/** - * A regular file from the file system. Uses mmap to open the data. - */ +// A regular file from the file system. Uses mmap to open the data. class RegularFile : public IFile { public: explicit RegularFile(const Source& source); std::unique_ptr OpenAsData() override; + std::unique_ptr OpenInputStream() override; const Source& GetSource() const override; private: + DISALLOW_COPY_AND_ASSIGN(RegularFile); + Source source_; }; @@ -48,23 +49,26 @@ class FileCollectionIterator : public IFileCollectionIterator { io::IFile* Next() override; private: + DISALLOW_COPY_AND_ASSIGN(FileCollectionIterator); + std::map>::const_iterator current_, end_; }; -/** - * An IFileCollection representing the file system. - */ +// An IFileCollection representing the file system. class FileCollection : public IFileCollection { public: - /** - * Adds a file located at path. Returns the IFile representation of that file. - */ + FileCollection() = default; + + // Adds a file located at path. Returns the IFile representation of that file. IFile* InsertFile(const android::StringPiece& path); IFile* FindFile(const android::StringPiece& path) override; std::unique_ptr Iterator() override; private: + DISALLOW_COPY_AND_ASSIGN(FileCollection); + friend class FileCollectionIterator; + std::map> files_; }; diff --git a/tools/aapt2/io/Io.h b/tools/aapt2/io/Io.h index a656740b35da5..e1df23a698f5e 100644 --- a/tools/aapt2/io/Io.h +++ b/tools/aapt2/io/Io.h @@ -58,6 +58,12 @@ class InputStream { virtual bool HadError() const = 0; }; +// A sub-InputStream interface that knows the total size of its stream. +class KnownSizeInputStream : public InputStream { + public: + virtual size_t TotalSize() const = 0; +}; + // OutputStream interface that mimics protobuf's ZeroCopyOutputStream, // with added error handling methods to better report issues. class OutputStream { diff --git a/tools/aapt2/io/StringInputStream.cpp b/tools/aapt2/io/StringStream.cpp similarity index 51% rename from tools/aapt2/io/StringInputStream.cpp rename to tools/aapt2/io/StringStream.cpp index 51a18a7d8a9f8..4ca04a8c74772 100644 --- a/tools/aapt2/io/StringInputStream.cpp +++ b/tools/aapt2/io/StringStream.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#include "io/StringInputStream.h" +#include "io/StringStream.h" using ::android::StringPiece; @@ -37,14 +37,64 @@ bool StringInputStream::Next(const void** data, size_t* size) { void StringInputStream::BackUp(size_t count) { if (count > offset_) { - count = offset_; + offset_ = 0u; + } else { + offset_ -= count; } - offset_ -= count; } size_t StringInputStream::ByteCount() const { return offset_; } +size_t StringInputStream::TotalSize() const { + return str_.size(); +} + +StringOutputStream::StringOutputStream(std::string* str, size_t buffer_capacity) + : str_(str), + buffer_capacity_(buffer_capacity), + buffer_offset_(0u), + buffer_(new char[buffer_capacity]) { +} + +StringOutputStream::~StringOutputStream() { + Flush(); +} + +bool StringOutputStream::Next(void** data, size_t* size) { + if (buffer_offset_ == buffer_capacity_) { + FlushImpl(); + } + + *data = buffer_.get() + buffer_offset_; + *size = buffer_capacity_ - buffer_offset_; + buffer_offset_ = buffer_capacity_; + return true; +} + +void StringOutputStream::BackUp(size_t count) { + if (count > buffer_offset_) { + buffer_offset_ = 0u; + } else { + buffer_offset_ -= count; + } +} + +size_t StringOutputStream::ByteCount() const { + return str_->size() + buffer_offset_; +} + +void StringOutputStream::Flush() { + if (buffer_offset_ != 0u) { + FlushImpl(); + } +} + +void StringOutputStream::FlushImpl() { + str_->append(buffer_.get(), buffer_offset_); + buffer_offset_ = 0u; +} + } // namespace io } // namespace aapt diff --git a/tools/aapt2/io/StringInputStream.h b/tools/aapt2/io/StringStream.h similarity index 59% rename from tools/aapt2/io/StringInputStream.h rename to tools/aapt2/io/StringStream.h index ff5b112ef274a..f29890ab7ee54 100644 --- a/tools/aapt2/io/StringInputStream.h +++ b/tools/aapt2/io/StringStream.h @@ -14,18 +14,20 @@ * limitations under the License. */ -#ifndef AAPT_IO_STRINGINPUTSTREAM_H -#define AAPT_IO_STRINGINPUTSTREAM_H +#ifndef AAPT_IO_STRINGSTREAM_H +#define AAPT_IO_STRINGSTREAM_H #include "io/Io.h" +#include + #include "android-base/macros.h" #include "androidfw/StringPiece.h" namespace aapt { namespace io { -class StringInputStream : public InputStream { +class StringInputStream : public KnownSizeInputStream { public: explicit StringInputStream(const android::StringPiece& str); @@ -43,6 +45,8 @@ class StringInputStream : public InputStream { return {}; } + size_t TotalSize() const override; + private: DISALLOW_COPY_AND_ASSIGN(StringInputStream); @@ -50,7 +54,40 @@ class StringInputStream : public InputStream { size_t offset_; }; +class StringOutputStream : public OutputStream { + public: + explicit StringOutputStream(std::string* str, size_t buffer_capacity = 4096u); + + ~StringOutputStream(); + + bool Next(void** data, size_t* size) override; + + void BackUp(size_t count) override; + + void Flush(); + + size_t ByteCount() const override; + + inline bool HadError() const override { + return false; + } + + inline std::string GetError() const override { + return {}; + } + + private: + DISALLOW_COPY_AND_ASSIGN(StringOutputStream); + + void FlushImpl(); + + std::string* str_; + size_t buffer_capacity_; + size_t buffer_offset_; + std::unique_ptr buffer_; +}; + } // namespace io } // namespace aapt -#endif // AAPT_IO_STRINGINPUTSTREAM_H +#endif // AAPT_IO_STRINGSTREAM_H diff --git a/tools/aapt2/io/StringInputStream_test.cpp b/tools/aapt2/io/StringStream_test.cpp similarity index 86% rename from tools/aapt2/io/StringInputStream_test.cpp rename to tools/aapt2/io/StringStream_test.cpp index cc57bc498313b..fb43fb773dd41 100644 --- a/tools/aapt2/io/StringInputStream_test.cpp +++ b/tools/aapt2/io/StringStream_test.cpp @@ -14,7 +14,9 @@ * limitations under the License. */ -#include "io/StringInputStream.h" +#include "io/StringStream.h" + +#include "io/Util.h" #include "test/Test.h" @@ -68,5 +70,16 @@ TEST(StringInputStreamTest, BackUp) { EXPECT_THAT(in.ByteCount(), Eq(input.size())); } +TEST(StringOutputStreamTest, NextAndBackUp) { + std::string input = "hello this is a string"; + std::string output; + + StringInputStream in(input); + StringOutputStream out(&output, 10u); + ASSERT_TRUE(Copy(&out, &in)); + out.Flush(); + EXPECT_THAT(output, StrEq(input)); +} + } // namespace io } // namespace aapt diff --git a/tools/aapt2/io/Util.cpp b/tools/aapt2/io/Util.cpp index 15114e8dbf543..d270340a180c7 100644 --- a/tools/aapt2/io/Util.cpp +++ b/tools/aapt2/io/Util.cpp @@ -18,6 +18,8 @@ #include "google/protobuf/io/zero_copy_stream_impl_lite.h" +using ::google::protobuf::io::ZeroCopyOutputStream; + namespace aapt { namespace io { @@ -91,5 +93,10 @@ bool Copy(OutputStream* out, InputStream* in) { return !in->HadError(); } +bool Copy(ZeroCopyOutputStream* out, InputStream* in) { + OutputStreamAdaptor adaptor(out); + return Copy(&adaptor, in); +} + } // namespace io } // namespace aapt diff --git a/tools/aapt2/io/Util.h b/tools/aapt2/io/Util.h index 02ee876014f7b..1e48508bb4b24 100644 --- a/tools/aapt2/io/Util.h +++ b/tools/aapt2/io/Util.h @@ -42,6 +42,81 @@ bool CopyProtoToArchive(IAaptContext* context, ::google::protobuf::MessageLite* // Copies the data from in to out. Returns false if there was an error. // If there was an error, check the individual streams' HadError/GetError methods. bool Copy(OutputStream* out, InputStream* in); +bool Copy(::google::protobuf::io::ZeroCopyOutputStream* out, InputStream* in); + +class OutputStreamAdaptor : public io::OutputStream { + public: + explicit OutputStreamAdaptor(::google::protobuf::io::ZeroCopyOutputStream* out) : out_(out) { + } + + bool Next(void** data, size_t* size) override { + int out_size; + bool result = out_->Next(data, &out_size); + *size = static_cast(out_size); + if (!result) { + error_ocurred_ = true; + } + return result; + } + + void BackUp(size_t count) override { + out_->BackUp(static_cast(count)); + } + + size_t ByteCount() const override { + return static_cast(out_->ByteCount()); + } + + bool HadError() const override { + return error_ocurred_; + } + + private: + DISALLOW_COPY_AND_ASSIGN(OutputStreamAdaptor); + + ::google::protobuf::io::ZeroCopyOutputStream* out_; + bool error_ocurred_ = false; +}; + +class ZeroCopyInputAdaptor : public ::google::protobuf::io::ZeroCopyInputStream { + public: + explicit ZeroCopyInputAdaptor(io::InputStream* in) : in_(in) { + } + + bool Next(const void** data, int* size) override { + size_t out_size; + bool result = in_->Next(data, &out_size); + *size = static_cast(out_size); + return result; + } + + void BackUp(int count) override { + in_->BackUp(static_cast(count)); + } + + bool Skip(int count) override { + const void* data; + int size; + while (Next(&data, &size)) { + if (size > count) { + BackUp(size - count); + return true; + } else { + count -= size; + } + } + return false; + } + + ::google::protobuf::int64 ByteCount() const override { + return static_cast<::google::protobuf::int64>(in_->ByteCount()); + } + + private: + DISALLOW_COPY_AND_ASSIGN(ZeroCopyInputAdaptor); + + io::InputStream* in_; +}; } // namespace io } // namespace aapt diff --git a/tools/aapt2/io/ZipArchive.cpp b/tools/aapt2/io/ZipArchive.cpp index 6494d2def748c..269b6c5a12e16 100644 --- a/tools/aapt2/io/ZipArchive.cpp +++ b/tools/aapt2/io/ZipArchive.cpp @@ -22,7 +22,7 @@ #include "Source.h" #include "util/Util.h" -using android::StringPiece; +using ::android::StringPiece; namespace aapt { namespace io { @@ -57,7 +57,13 @@ std::unique_ptr ZipFile::OpenAsData() { } } -const Source& ZipFile::GetSource() const { return source_; } +std::unique_ptr ZipFile::OpenInputStream() { + return OpenAsData(); +} + +const Source& ZipFile::GetSource() const { + return source_; +} bool ZipFile::WasCompressed() { return zip_entry_.method != kCompressStored; @@ -67,7 +73,9 @@ ZipFileCollectionIterator::ZipFileCollectionIterator( ZipFileCollection* collection) : current_(collection->files_.begin()), end_(collection->files_.end()) {} -bool ZipFileCollectionIterator::HasNext() { return current_ != end_; } +bool ZipFileCollectionIterator::HasNext() { + return current_ != end_; +} IFile* ZipFileCollectionIterator::Next() { IFile* result = current_->get(); diff --git a/tools/aapt2/io/ZipArchive.h b/tools/aapt2/io/ZipArchive.h index 56c74e326e6c8..8381259d77c5c 100644 --- a/tools/aapt2/io/ZipArchive.h +++ b/tools/aapt2/io/ZipArchive.h @@ -28,23 +28,20 @@ namespace aapt { namespace io { -/** - * An IFile representing a file within a ZIP archive. If the file is compressed, - * it is uncompressed - * and copied into memory when opened. Otherwise it is mmapped from the ZIP - * archive. - */ +// An IFile representing a file within a ZIP archive. If the file is compressed, it is uncompressed +// and copied into memory when opened. Otherwise it is mmapped from the ZIP archive. class ZipFile : public IFile { public: - ZipFile(ZipArchiveHandle handle, const ZipEntry& entry, const Source& source); + ZipFile(::ZipArchiveHandle handle, const ::ZipEntry& entry, const Source& source); std::unique_ptr OpenAsData() override; + std::unique_ptr OpenInputStream() override; const Source& GetSource() const override; bool WasCompressed() override; private: - ZipArchiveHandle zip_handle_; - ZipEntry zip_entry_; + ::ZipArchiveHandle zip_handle_; + ::ZipEntry zip_entry_; Source source_; }; @@ -61,9 +58,7 @@ class ZipFileCollectionIterator : public IFileCollectionIterator { std::vector>::const_iterator current_, end_; }; -/** - * An IFileCollection that represents a ZIP archive and the entries within it. - */ +// An IFileCollection that represents a ZIP archive and the entries within it. class ZipFileCollection : public IFileCollection { public: static std::unique_ptr Create(const android::StringPiece& path, diff --git a/tools/aapt2/link/TableMerger.cpp b/tools/aapt2/link/TableMerger.cpp index 93c904f1a7432..21c6b111b8552 100644 --- a/tools/aapt2/link/TableMerger.cpp +++ b/tools/aapt2/link/TableMerger.cpp @@ -37,13 +37,12 @@ TableMerger::TableMerger(IAaptContext* context, ResourceTable* out_table, CHECK(master_package_ != nullptr) << "package name or ID already taken"; } -bool TableMerger::Merge(const Source& src, ResourceTable* table, io::IFileCollection* collection) { - return MergeImpl(src, table, collection, false /*overlay*/, true /*allow_new*/); -} - -bool TableMerger::MergeOverlay(const Source& src, ResourceTable* table, - io::IFileCollection* collection) { - return MergeImpl(src, table, collection, true /*overlay*/, options_.auto_add_overlay); +bool TableMerger::Merge(const Source& src, ResourceTable* table, bool overlay, + io::IFileCollection* collection) { + // We allow adding new resources if this is not an overlay, or if the options allow overlays + // to add new resources. + return MergeImpl(src, table, collection, overlay, + options_.auto_add_overlay || !overlay /*allow_new*/); } // This will merge packages with the same package name (or no package name). @@ -322,17 +321,20 @@ std::unique_ptr TableMerger::CloneAndMangleFile( util::make_unique(master_table_->string_pool.MakeRef(newPath)); new_file_ref->SetComment(file_ref.GetComment()); new_file_ref->SetSource(file_ref.GetSource()); + new_file_ref->type = file_ref.type; + new_file_ref->file = file_ref.file; return new_file_ref; } return std::unique_ptr(file_ref.Clone(&master_table_->string_pool)); } -bool TableMerger::MergeFileImpl(const ResourceFile& file_desc, io::IFile* file, bool overlay) { +bool TableMerger::MergeFile(const ResourceFile& file_desc, bool overlay, io::IFile* file) { ResourceTable table; std::string path = ResourceUtils::BuildResourceFileName(file_desc); std::unique_ptr file_ref = util::make_unique(table.string_pool.MakeRef(path)); file_ref->SetSource(file_desc.source); + file_ref->type = file_desc.type; file_ref->file = file; ResourceTablePackage* pkg = table.CreatePackage(file_desc.name.package, 0x0); @@ -341,17 +343,8 @@ bool TableMerger::MergeFileImpl(const ResourceFile& file_desc, io::IFile* file, ->FindOrCreateValue(file_desc.config, {}) ->value = std::move(file_ref); - return DoMerge(file->GetSource(), &table, pkg, false /* mangle */, - overlay /* overlay */, true /* allow_new */, {}); -} - -bool TableMerger::MergeFile(const ResourceFile& file_desc, io::IFile* file) { - return MergeFileImpl(file_desc, file, false /* overlay */); -} - -bool TableMerger::MergeFileOverlay(const ResourceFile& file_desc, - io::IFile* file) { - return MergeFileImpl(file_desc, file, true /* overlay */); + return DoMerge(file->GetSource(), &table, pkg, false /* mangle */, overlay /* overlay */, + true /* allow_new */, {}); } } // namespace aapt diff --git a/tools/aapt2/link/TableMerger.h b/tools/aapt2/link/TableMerger.h index 81518ffb24416..d024aa47d9526 100644 --- a/tools/aapt2/link/TableMerger.h +++ b/tools/aapt2/link/TableMerger.h @@ -59,24 +59,18 @@ class TableMerger { } // Merges resources from the same or empty package. This is for local sources. + // If overlay is true, the resources are treated as overlays. // An io::IFileCollection is optional and used to find the referenced Files and process them. - bool Merge(const Source& src, ResourceTable* table, io::IFileCollection* collection = nullptr); - - // Merges resources from an overlay ResourceTable. - // An io::IFileCollection is optional and used to find the referenced Files and process them. - bool MergeOverlay(const Source& src, ResourceTable* table, - io::IFileCollection* collection = nullptr); + bool Merge(const Source& src, ResourceTable* table, bool overlay, + io::IFileCollection* collection = nullptr); // Merges resources from the given package, mangling the name. This is for static libraries. // An io::IFileCollection is needed in order to find the referenced Files and process them. bool MergeAndMangle(const Source& src, const android::StringPiece& package, ResourceTable* table, io::IFileCollection* collection); - // Merges a compiled file that belongs to this same or empty package. This is for local sources. - bool MergeFile(const ResourceFile& fileDesc, io::IFile* file); - - // Merges a compiled file from an overlay, overriding an existing definition. - bool MergeFileOverlay(const ResourceFile& fileDesc, io::IFile* file); + // Merges a compiled file that belongs to this same or empty package. + bool MergeFile(const ResourceFile& fileDesc, bool overlay, io::IFile* file); private: DISALLOW_COPY_AND_ASSIGN(TableMerger); @@ -91,9 +85,6 @@ class TableMerger { ResourceTablePackage* master_package_; std::set merged_packages_; - bool MergeFileImpl(const ResourceFile& file_desc, io::IFile* file, - bool overlay); - bool MergeImpl(const Source& src, ResourceTable* src_table, io::IFileCollection* collection, bool overlay, bool allow_new); diff --git a/tools/aapt2/link/TableMerger_test.cpp b/tools/aapt2/link/TableMerger_test.cpp index 45b01a494164c..3499809e5a838 100644 --- a/tools/aapt2/link/TableMerger_test.cpp +++ b/tools/aapt2/link/TableMerger_test.cpp @@ -69,7 +69,7 @@ TEST_F(TableMergerTest, SimpleMerge) { TableMerger merger(context_.get(), &final_table, TableMergerOptions{}); io::FileCollection collection; - ASSERT_TRUE(merger.Merge({}, table_a.get())); + ASSERT_TRUE(merger.Merge({}, table_a.get(), false /*overlay*/)); ASSERT_TRUE(merger.MergeAndMangle({}, "com.app.b", table_b.get(), &collection)); EXPECT_TRUE(merger.merged_packages().count("com.app.b") != 0); @@ -98,7 +98,7 @@ TEST_F(TableMergerTest, MergeFile) { file_desc.source = Source("res/layout-hdpi/main.xml"); test::TestFile test_file("path/to/res/layout-hdpi/main.xml.flat"); - ASSERT_TRUE(merger.MergeFile(file_desc, &test_file)); + ASSERT_TRUE(merger.MergeFile(file_desc, false /*overlay*/, &test_file)); FileReference* file = test::GetValueForConfig( &final_table, "com.app.a:layout/main", test::ParseConfigOrDie("hdpi-v4")); @@ -117,8 +117,8 @@ TEST_F(TableMergerTest, MergeFileOverlay) { test::TestFile file_a("path/to/fileA.xml.flat"); test::TestFile file_b("path/to/fileB.xml.flat"); - ASSERT_TRUE(merger.MergeFile(file_desc, &file_a)); - ASSERT_TRUE(merger.MergeFileOverlay(file_desc, &file_b)); + ASSERT_TRUE(merger.MergeFile(file_desc, false /*overlay*/, &file_a)); + ASSERT_TRUE(merger.MergeFile(file_desc, true /*overlay*/, &file_b)); } TEST_F(TableMergerTest, MergeFileReferences) { @@ -138,7 +138,7 @@ TEST_F(TableMergerTest, MergeFileReferences) { io::FileCollection collection; collection.InsertFile("res/xml/file.xml"); - ASSERT_TRUE(merger.Merge({}, table_a.get())); + ASSERT_TRUE(merger.Merge({}, table_a.get(), false /*overlay*/)); ASSERT_TRUE(merger.MergeAndMangle({}, "com.app.b", table_b.get(), &collection)); FileReference* f = test::GetValue(&final_table, "com.app.a:xml/file"); @@ -167,8 +167,8 @@ TEST_F(TableMergerTest, OverrideResourceWithOverlay) { options.auto_add_overlay = false; TableMerger merger(context_.get(), &final_table, options); - ASSERT_TRUE(merger.Merge({}, base.get())); - ASSERT_TRUE(merger.MergeOverlay({}, overlay.get())); + ASSERT_TRUE(merger.Merge({}, base.get(), false /*overlay*/)); + ASSERT_TRUE(merger.Merge({}, overlay.get(), true /*overlay*/)); BinaryPrimitive* foo = test::GetValue(&final_table, "com.app.a:bool/foo"); ASSERT_THAT(foo, @@ -194,8 +194,8 @@ TEST_F(TableMergerTest, OverrideSameResourceIdsWithOverlay) { options.auto_add_overlay = false; TableMerger merger(context_.get(), &final_table, options); - ASSERT_TRUE(merger.Merge({}, base.get())); - ASSERT_TRUE(merger.MergeOverlay({}, overlay.get())); + ASSERT_TRUE(merger.Merge({}, base.get(), false /*overlay*/)); + ASSERT_TRUE(merger.Merge({}, overlay.get(), true /*overlay*/)); } TEST_F(TableMergerTest, FailToOverrideConflictingTypeIdsWithOverlay) { @@ -217,8 +217,8 @@ TEST_F(TableMergerTest, FailToOverrideConflictingTypeIdsWithOverlay) { options.auto_add_overlay = false; TableMerger merger(context_.get(), &final_table, options); - ASSERT_TRUE(merger.Merge({}, base.get())); - ASSERT_FALSE(merger.MergeOverlay({}, overlay.get())); + ASSERT_TRUE(merger.Merge({}, base.get(), false /*overlay*/)); + ASSERT_FALSE(merger.Merge({}, overlay.get(), true /*overlay*/)); } TEST_F(TableMergerTest, FailToOverrideConflictingEntryIdsWithOverlay) { @@ -240,8 +240,8 @@ TEST_F(TableMergerTest, FailToOverrideConflictingEntryIdsWithOverlay) { options.auto_add_overlay = false; TableMerger merger(context_.get(), &final_table, options); - ASSERT_TRUE(merger.Merge({}, base.get())); - ASSERT_FALSE(merger.MergeOverlay({}, overlay.get())); + ASSERT_TRUE(merger.Merge({}, base.get(), false /*overlay*/)); + ASSERT_FALSE(merger.Merge({}, overlay.get(), true /*overlay*/)); } TEST_F(TableMergerTest, MergeAddResourceFromOverlay) { @@ -259,8 +259,8 @@ TEST_F(TableMergerTest, MergeAddResourceFromOverlay) { options.auto_add_overlay = false; TableMerger merger(context_.get(), &final_table, options); - ASSERT_TRUE(merger.Merge({}, table_a.get())); - ASSERT_TRUE(merger.MergeOverlay({}, table_b.get())); + ASSERT_TRUE(merger.Merge({}, table_a.get(), false /*overlay*/)); + ASSERT_TRUE(merger.Merge({}, table_b.get(), false /*overlay*/)); } TEST_F(TableMergerTest, MergeAddResourceFromOverlayWithAutoAddOverlay) { @@ -277,8 +277,8 @@ TEST_F(TableMergerTest, MergeAddResourceFromOverlayWithAutoAddOverlay) { options.auto_add_overlay = true; TableMerger merger(context_.get(), &final_table, options); - ASSERT_TRUE(merger.Merge({}, table_a.get())); - ASSERT_TRUE(merger.MergeOverlay({}, table_b.get())); + ASSERT_TRUE(merger.Merge({}, table_a.get(), false /*overlay*/)); + ASSERT_TRUE(merger.Merge({}, table_b.get(), false /*overlay*/)); } TEST_F(TableMergerTest, FailToMergeNewResourceWithoutAutoAddOverlay) { @@ -295,8 +295,8 @@ TEST_F(TableMergerTest, FailToMergeNewResourceWithoutAutoAddOverlay) { options.auto_add_overlay = false; TableMerger merger(context_.get(), &final_table, options); - ASSERT_TRUE(merger.Merge({}, table_a.get())); - ASSERT_FALSE(merger.MergeOverlay({}, table_b.get())); + ASSERT_TRUE(merger.Merge({}, table_a.get(), false /*overlay*/)); + ASSERT_FALSE(merger.Merge({}, table_b.get(), true /*overlay*/)); } TEST_F(TableMergerTest, OverlaidStyleablesAndStylesShouldBeMerged) { @@ -337,8 +337,8 @@ TEST_F(TableMergerTest, OverlaidStyleablesAndStylesShouldBeMerged) { options.auto_add_overlay = true; TableMerger merger(context_.get(), &final_table, options); - ASSERT_TRUE(merger.Merge({}, table_a.get())); - ASSERT_TRUE(merger.MergeOverlay({}, table_b.get())); + ASSERT_TRUE(merger.Merge({}, table_a.get(), false /*overlay*/)); + ASSERT_TRUE(merger.Merge({}, table_b.get(), true /*overlay*/)); Styleable* styleable = test::GetValue(&final_table, "com.app.a:styleable/Foo"); ASSERT_THAT(styleable, NotNull()); diff --git a/tools/aapt2/test/Builders.cpp b/tools/aapt2/test/Builders.cpp index e658664d8653d..5a62e97770650 100644 --- a/tools/aapt2/test/Builders.cpp +++ b/tools/aapt2/test/Builders.cpp @@ -19,7 +19,7 @@ #include "android-base/logging.h" #include "androidfw/StringPiece.h" -#include "io/StringInputStream.h" +#include "io/StringStream.h" #include "test/Common.h" #include "util/Util.h" diff --git a/tools/aapt2/test/Common.h b/tools/aapt2/test/Common.h index 61d056309a696..4e318a92f3fa0 100644 --- a/tools/aapt2/test/Common.h +++ b/tools/aapt2/test/Common.h @@ -90,6 +90,10 @@ class TestFile : public io::IFile { return {}; } + std::unique_ptr OpenInputStream() override { + return OpenAsData(); + } + const Source& GetSource() const override { return source_; } diff --git a/tools/aapt2/xml/XmlDom.cpp b/tools/aapt2/xml/XmlDom.cpp index b3e0a926b4bd3..35225066633c6 100644 --- a/tools/aapt2/xml/XmlDom.cpp +++ b/tools/aapt2/xml/XmlDom.cpp @@ -215,8 +215,8 @@ std::unique_ptr Inflate(InputStream* in, IDiagnostics* diag, const return {}; } } - return util::make_unique(ResourceFile{{}, {}, source}, StringPool{}, - std::move(stack.root)); + return util::make_unique(ResourceFile{{}, {}, ResourceFile::Type::kUnknown, source}, + StringPool{}, std::move(stack.root)); } static void CopyAttributes(Element* el, android::ResXMLParser* parser, StringPool* out_pool) { diff --git a/tools/aapt2/xml/XmlDom_test.cpp b/tools/aapt2/xml/XmlDom_test.cpp index 4ba04430ced39..34e6d3f54f56f 100644 --- a/tools/aapt2/xml/XmlDom_test.cpp +++ b/tools/aapt2/xml/XmlDom_test.cpp @@ -19,7 +19,7 @@ #include #include "format/binary/XmlFlattener.h" -#include "io/StringInputStream.h" +#include "io/StringStream.h" #include "test/Test.h" using ::aapt::io::StringInputStream; diff --git a/tools/aapt2/xml/XmlPullParser_test.cpp b/tools/aapt2/xml/XmlPullParser_test.cpp index 681d9d48173f0..5304bded85a50 100644 --- a/tools/aapt2/xml/XmlPullParser_test.cpp +++ b/tools/aapt2/xml/XmlPullParser_test.cpp @@ -18,43 +18,49 @@ #include "androidfw/StringPiece.h" -#include "io/StringInputStream.h" +#include "io/StringStream.h" #include "test/Test.h" using ::aapt::io::StringInputStream; using ::android::StringPiece; +using ::testing::Eq; +using ::testing::StrEq; + +using Event = ::aapt::xml::XmlPullParser::Event; namespace aapt { +namespace xml { TEST(XmlPullParserTest, NextChildNodeTraversesCorrectly) { std::string str = R"( )"; StringInputStream input(str); - xml::XmlPullParser parser(&input); + XmlPullParser parser(&input); const size_t depth_outer = parser.depth(); - ASSERT_TRUE(xml::XmlPullParser::NextChildNode(&parser, depth_outer)); + ASSERT_TRUE(XmlPullParser::NextChildNode(&parser, depth_outer)); - EXPECT_EQ(xml::XmlPullParser::Event::kStartElement, parser.event()); - EXPECT_EQ(StringPiece("a"), StringPiece(parser.element_name())); + EXPECT_THAT(parser.event(), Eq(XmlPullParser::Event::kStartElement)); + EXPECT_THAT(parser.element_name(), StrEq("a")); const size_t depth_a = parser.depth(); - ASSERT_TRUE(xml::XmlPullParser::NextChildNode(&parser, depth_a)); - EXPECT_EQ(xml::XmlPullParser::Event::kStartElement, parser.event()); - EXPECT_EQ(StringPiece("b"), StringPiece(parser.element_name())); + ASSERT_TRUE(XmlPullParser::NextChildNode(&parser, depth_a)); + EXPECT_THAT(parser.event(), Eq(XmlPullParser::Event::kStartElement)); + EXPECT_THAT(parser.element_name(), StrEq("b")); const size_t depth_b = parser.depth(); - ASSERT_TRUE(xml::XmlPullParser::NextChildNode(&parser, depth_b)); - EXPECT_EQ(xml::XmlPullParser::Event::kStartElement, parser.event()); - EXPECT_EQ(StringPiece("c"), StringPiece(parser.element_name())); + ASSERT_TRUE(XmlPullParser::NextChildNode(&parser, depth_b)); + EXPECT_THAT(parser.event(), Eq(XmlPullParser::Event::kStartElement)); + EXPECT_THAT(parser.element_name(), StrEq("c")); - ASSERT_TRUE(xml::XmlPullParser::NextChildNode(&parser, depth_b)); - EXPECT_EQ(xml::XmlPullParser::Event::kStartElement, parser.event()); - EXPECT_EQ(StringPiece("e"), StringPiece(parser.element_name())); + ASSERT_TRUE(XmlPullParser::NextChildNode(&parser, depth_b)); + EXPECT_THAT(parser.event(), Eq(XmlPullParser::Event::kStartElement)); + EXPECT_THAT(parser.element_name(), StrEq("e")); - ASSERT_FALSE(xml::XmlPullParser::NextChildNode(&parser, depth_outer)); - EXPECT_EQ(xml::XmlPullParser::Event::kEndDocument, parser.event()); + ASSERT_FALSE(XmlPullParser::NextChildNode(&parser, depth_outer)); + EXPECT_THAT(parser.event(), Eq(XmlPullParser::Event::kEndDocument)); } +} // namespace xml } // namespace aapt diff --git a/tools/aapt2/xml/XmlUtil.cpp b/tools/aapt2/xml/XmlUtil.cpp index c1186e83369c3..0a622b2bd336d 100644 --- a/tools/aapt2/xml/XmlUtil.cpp +++ b/tools/aapt2/xml/XmlUtil.cpp @@ -16,20 +16,20 @@ #include "xml/XmlUtil.h" +#include #include #include "util/Maybe.h" #include "util/Util.h" +#include "xml/XmlDom.h" -using android::StringPiece; +using ::android::StringPiece; namespace aapt { namespace xml { -std::string BuildPackageNamespace(const StringPiece& package, - bool private_reference) { - std::string result = - private_reference ? kSchemaPrivatePrefix : kSchemaPublicPrefix; +std::string BuildPackageNamespace(const StringPiece& package, bool private_reference) { + std::string result = private_reference ? kSchemaPrivatePrefix : kSchemaPublicPrefix; result.append(package.data(), package.size()); return result; } @@ -39,8 +39,7 @@ Maybe ExtractPackageFromNamespace( if (util::StartsWith(namespace_uri, kSchemaPublicPrefix)) { StringPiece schema_prefix = kSchemaPublicPrefix; StringPiece package = namespace_uri; - package = package.substr(schema_prefix.size(), - package.size() - schema_prefix.size()); + package = package.substr(schema_prefix.size(), package.size() - schema_prefix.size()); if (package.empty()) { return {}; } @@ -49,8 +48,7 @@ Maybe ExtractPackageFromNamespace( } else if (util::StartsWith(namespace_uri, kSchemaPrivatePrefix)) { StringPiece schema_prefix = kSchemaPrivatePrefix; StringPiece package = namespace_uri; - package = package.substr(schema_prefix.size(), - package.size() - schema_prefix.size()); + package = package.substr(schema_prefix.size(), package.size() - schema_prefix.size()); if (package.empty()) { return {}; } @@ -76,5 +74,33 @@ void ResolvePackage(const IPackageDeclStack* decl_stack, Reference* in_ref) { } } +namespace { + +class ToolsNamespaceRemover : public Visitor { + public: + using Visitor::Visit; + + void Visit(Element* el) override { + auto new_end = + std::remove_if(el->namespace_decls.begin(), el->namespace_decls.end(), + [](const NamespaceDecl& decl) -> bool { return decl.uri == kSchemaTools; }); + el->namespace_decls.erase(new_end, el->namespace_decls.end()); + + auto new_attr_end = std::remove_if( + el->attributes.begin(), el->attributes.end(), + [](const Attribute& attr) -> bool { return attr.namespace_uri == kSchemaTools; }); + el->attributes.erase(new_attr_end, el->attributes.end()); + + Visitor::Visit(el); + } +}; + +} // namespace + +void StripAndroidStudioAttributes(Element* el) { + ToolsNamespaceRemover remover; + el->Accept(&remover); +} + } // namespace xml } // namespace aapt diff --git a/tools/aapt2/xml/XmlUtil.h b/tools/aapt2/xml/XmlUtil.h index 4eb359a9eed4b..592a604f87a6f 100644 --- a/tools/aapt2/xml/XmlUtil.h +++ b/tools/aapt2/xml/XmlUtil.h @@ -78,6 +78,12 @@ struct IPackageDeclStack { // package declaration was private. void ResolvePackage(const IPackageDeclStack* decl_stack, Reference* in_ref); +class Element; + +// Strips out any attributes in the http://schemas.android.com/tools namespace, which is owned by +// Android Studio and should not make it to the final APK. +void StripAndroidStudioAttributes(Element* el); + } // namespace xml } // namespace aapt