diff options
author | Adam Lesinski <adamlesinski@google.com> | 2017-10-20 19:15:54 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2017-10-20 19:15:54 +0000 |
commit | 7fd74b5ad1df53b92f08a9aa2f3d445bf7b786fe (patch) | |
tree | 03bbf941ec0fead1ad64ba2acd06113486a612c1 | |
parent | 4bf7416a062af25574f548846e60e88df332cc55 (diff) | |
parent | e59f0d80ec19249f72c07ae191ad673d040443e3 (diff) |
Merge changes I1a4b3ce5,Id7216e5b
* changes:
AAPT2: Enable building proto artifacts
AAPT2: Define and Implement AAPT Container Format
56 files changed, 1649 insertions, 884 deletions
diff --git a/tools/aapt2/Android.bp b/tools/aapt2/Android.bp index f964dfed1710..058504d7520d 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/Debug.cpp b/tools/aapt2/Debug.cpp index 1555d6126493..61c304b2c048 100644 --- a/tools/aapt2/Debug.cpp +++ b/tools/aapt2/Debug.cpp @@ -35,11 +35,11 @@ namespace aapt { namespace { -class PrintVisitor : public DescendingValueVisitor { +class PrintVisitor : public ConstValueVisitor { public: - using DescendingValueVisitor::Visit; + using ConstValueVisitor::Visit; - void Visit(Attribute* attr) override { + void Visit(const Attribute* attr) override { std::cout << "(attr) type="; attr->PrintMask(&std::cout); static constexpr uint32_t kMask = @@ -55,7 +55,7 @@ class PrintVisitor : public DescendingValueVisitor { } } - void Visit(Style* style) override { + void Visit(const Style* style) override { std::cout << "(style)"; if (style->parent) { const Reference& parent_ref = style->parent.value(); @@ -90,15 +90,15 @@ class PrintVisitor : public DescendingValueVisitor { } } - void Visit(Array* array) override { + void Visit(const Array* array) override { array->Print(&std::cout); } - void Visit(Plural* plural) override { + void Visit(const Plural* plural) override { plural->Print(&std::cout); } - void Visit(Styleable* styleable) override { + void Visit(const Styleable* styleable) override { std::cout << "(styleable)"; for (const auto& attr : styleable->entries) { std::cout << "\n "; @@ -116,17 +116,17 @@ class PrintVisitor : public DescendingValueVisitor { } } - void VisitItem(Item* item) override { + void VisitItem(const Item* item) override { item->Print(&std::cout); } }; } // namespace -void Debug::PrintTable(ResourceTable* table, const DebugPrintTableOptions& options) { +void Debug::PrintTable(const ResourceTable& table, const DebugPrintTableOptions& options) { PrintVisitor visitor; - for (auto& package : table->packages) { + for (const auto& package : table.packages) { std::cout << "Package name=" << package->name; if (package->id) { std::cout << " id=" << std::hex << (int)package->id.value() << std::dec; @@ -261,11 +261,11 @@ void Debug::DumpHex(const void* data, size_t len) { namespace { -class XmlPrinter : public xml::Visitor { +class XmlPrinter : public xml::ConstVisitor { public: - using xml::Visitor::Visit; + using xml::ConstVisitor::Visit; - void Visit(xml::Element* el) override { + void Visit(const xml::Element* el) override { const size_t previous_size = prefix_.size(); for (const xml::NamespaceDecl& decl : el->namespace_decls) { @@ -301,11 +301,11 @@ class XmlPrinter : public xml::Visitor { } prefix_ += " "; - xml::Visitor::Visit(el); + xml::ConstVisitor::Visit(el); prefix_.resize(previous_size); } - void Visit(xml::Text* text) override { + void Visit(const xml::Text* text) override { std::cerr << prefix_ << "T: '" << text->text << "'\n"; } @@ -315,9 +315,9 @@ class XmlPrinter : public xml::Visitor { } // namespace -void Debug::DumpXml(xml::XmlResource* doc) { +void Debug::DumpXml(const xml::XmlResource& doc) { XmlPrinter printer; - doc->root->Accept(&printer); + doc.root->Accept(&printer); } } // namespace aapt diff --git a/tools/aapt2/Debug.h b/tools/aapt2/Debug.h index e2456c7f98b2..296d04bd49bd 100644 --- a/tools/aapt2/Debug.h +++ b/tools/aapt2/Debug.h @@ -31,13 +31,11 @@ struct DebugPrintTableOptions { }; struct Debug { - static void PrintTable(ResourceTable* table, - const DebugPrintTableOptions& options = {}); + static void PrintTable(const ResourceTable& table, const DebugPrintTableOptions& options = {}); static void PrintStyleGraph(ResourceTable* table, const ResourceName& target_style); static void DumpHex(const void* data, size_t len); - static void DumpXml(xml::XmlResource* doc); - static std::string ToString(xml::XmlResource* doc); + static void DumpXml(const xml::XmlResource& doc); }; } // namespace aapt diff --git a/tools/aapt2/LoadedApk.cpp b/tools/aapt2/LoadedApk.cpp index c1815c82b5b5..ae32ee965b48 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 cbcc8fb805aa..87b986784961 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 f08b03e7b8af..9a5cd3edb47f 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/ResourceUtils.cpp b/tools/aapt2/ResourceUtils.cpp index 6fac6e9dfefe..24187d96fec5 100644 --- a/tools/aapt2/ResourceUtils.cpp +++ b/tools/aapt2/ResourceUtils.cpp @@ -704,8 +704,15 @@ std::unique_ptr<Item> ParseBinaryResValue(const ResourceType& type, const Config } else { if (type != ResourceType::kString && util::StartsWith(str, "res/")) { // This must be a FileReference. - return util::make_unique<FileReference>(dst_pool->MakeRef( - str, StringPool::Context(StringPool::Context::kHighPriority, config))); + std::unique_ptr<FileReference> file_ref = + util::make_unique<FileReference>(dst_pool->MakeRef( + str, StringPool::Context(StringPool::Context::kHighPriority, config))); + if (util::EndsWith(*file_ref->path, ".xml")) { + file_ref->type = ResourceFile::Type::kBinaryXml; + } else if (util::EndsWith(*file_ref->path, ".png")) { + file_ref->type = ResourceFile::Type::kPng; + } + return std::move(file_ref); } // There are no styles associated with this string, so treat it as a simple string. diff --git a/tools/aapt2/ResourceValues.cpp b/tools/aapt2/ResourceValues.cpp index 608316083f41..082fd86604da 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 742765d166da..fd242a109006 100644 --- a/tools/aapt2/ResourceValues.h +++ b/tools/aapt2/ResourceValues.h @@ -249,6 +249,10 @@ struct FileReference : public BaseItem<FileReference> { // 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/Resources.proto b/tools/aapt2/Resources.proto index 174b7f6b7c8c..7e7c86d53d24 100644 --- a/tools/aapt2/Resources.proto +++ b/tools/aapt2/Resources.proto @@ -269,8 +269,19 @@ message StyledString { // A value that is a reference to an external entity, like an XML file or a PNG. message FileReference { + enum Type { + UNKNOWN = 0; + PNG = 1; + BINARY_XML = 2; + PROTO_XML = 3; + } + // Path to a file within the APK (typically res/type-config/entry.ext). string path = 1; + + // The type of file this path points to. For UAM bundle, this cannot be + // BINARY_XML. + Type type = 2; } // A value that represents a primitive data type (float, int, boolean, etc.). diff --git a/tools/aapt2/ResourcesInternal.proto b/tools/aapt2/ResourcesInternal.proto index 0b0a252a3452..520b242ee509 100644 --- a/tools/aapt2/ResourcesInternal.proto +++ b/tools/aapt2/ResourcesInternal.proto @@ -41,13 +41,13 @@ message CompiledFile { // The configuration for which the resource is defined. aapt.pb.Configuration config = 2; + // The type of the file. + aapt.pb.FileReference.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; - - // If this is a compiled XML file, this is the root node. - aapt.pb.XmlNode xml_root = 5; + repeated Symbol exported_symbol = 5; } diff --git a/tools/aapt2/cmd/Compile.cpp b/tools/aapt2/cmd/Compile.cpp index a5e6aefd1e0f..53910afff593 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<DIR, decltype(closedir)*> 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<DIR, decltype(closedir)*> 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<std::unique_ptr<xml::XmlResource>>& 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<std::unique_ptr<xml::XmlResource>>& 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<xml::XmlResource>& 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<android::FileMap> 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<StringPiece>& args, IDiagnostics* diagnostics) { CompileContext context(diagnostics); CompileOptions options; @@ -717,50 +672,34 @@ int Compile(const std::vector<StringPiece>& 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 44032f6a730d..090c3fbc5731 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,42 +33,51 @@ 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; + } + return "UNKNOWN"; +} +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"; - return true; + << "Source: " << file.source << "\n" + << "Type: " << ResourceFileTypeToString(file.type) << "\n" + << "DataOff: " << offset << "\n" + << "DataLen: " << len << "\n"; } -bool TryDumpFile(IAaptContext* context, const std::string& file_path) { +static bool TryDumpFile(IAaptContext* context, const std::string& file_path) { + DebugPrintTableOptions print_options; + print_options.show_sources = true; + std::string err; std::unique_ptr<io::ZipFileCollection> zip = io::ZipFileCollection::Create(file_path, &err); if (zip) { ResourceTable table; - if (io::IFile* file = zip->FindFile("resources.arsc.flat")) { + if (io::IFile* file = zip->FindFile("resources.pb")) { std::unique_ptr<io::IData> data = file->OpenAsData(); if (data == nullptr) { - context->GetDiagnostics()->Error(DiagMessage(file_path) - << "failed to open resources.arsc.flat"); + context->GetDiagnostics()->Error(DiagMessage(file_path) << "failed to open resources.pb"); return false; } pb::ResourceTable pb_table; if (!pb_table.ParseFromArray(data->data(), data->size())) { - context->GetDiagnostics()->Error(DiagMessage(file_path) << "invalid resources.arsc.flat"); + context->GetDiagnostics()->Error(DiagMessage(file_path) << "invalid resources.pb"); return false; } - ResourceTable table; if (!DeserializeTableFromPb(pb_table, &table, &err)) { context->GetDiagnostics()->Error(DiagMessage(file_path) << "failed to parse table: " << err); @@ -85,62 +96,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<android::FileMap> 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<const uint8_t*>(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 +174,7 @@ class DumpContext : public IAaptContext { } NameMangler* GetNameMangler() override { - abort(); + UNIMPLEMENTED(FATAL); return nullptr; } @@ -167,7 +188,7 @@ class DumpContext : public IAaptContext { } SymbolTable* GetExternalSymbols() override { - abort(); + UNIMPLEMENTED(FATAL); return nullptr; } @@ -188,9 +209,9 @@ class DumpContext : public IAaptContext { bool verbose_ = false; }; -/** - * Entry point for dump command. - */ +} // namespace + +// Entry point for dump command. int Dump(const std::vector<StringPiece>& args) { bool verbose = false; Flags flags = Flags().OptionalSwitch("-v", "increase verbosity of output", &verbose); @@ -206,7 +227,6 @@ int Dump(const std::vector<StringPiece>& args) { return 1; } } - return 0; } diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp index 40d71a3429d0..55a4c438755c 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" @@ -71,6 +71,14 @@ using ::android::base::StringPrintf; namespace aapt { +constexpr static const char kApkResourceTablePath[] = "resources.arsc"; +constexpr static const char kProtoResourceTablePath[] = "resources.pb"; + +enum class OutputFormat { + kApk, + kProto, +}; + struct LinkOptions { std::string output_path; std::string manifest_path; @@ -79,6 +87,7 @@ struct LinkOptions { std::vector<std::string> assets_dirs; bool output_to_directory = false; bool auto_add_overlay = false; + OutputFormat output_format = OutputFormat::kApk; // Java/Proguard options. Maybe<std::string> generate_java_class_path; @@ -253,26 +262,39 @@ class FeatureSplitSymbolTableDelegate : public DefaultSymbolTableDelegate { IAaptContext* context_; }; -static bool FlattenXml(IAaptContext* context, xml::XmlResource* xml_res, const StringPiece& path, - bool keep_raw_values, bool utf16, IArchiveWriter* writer) { - BigBuffer buffer(1024); - XmlFlattenerOptions options = {}; - options.keep_raw_values = keep_raw_values; - options.use_utf16 = utf16; - XmlFlattener flattener(&buffer, options); - if (!flattener.Consume(context, xml_res)) { - return false; - } - +static bool FlattenXml(IAaptContext* context, const xml::XmlResource& xml_res, + const StringPiece& path, bool keep_raw_values, bool utf16, + OutputFormat format, IArchiveWriter* writer) { if (context->IsVerbose()) { context->GetDiagnostics()->Note(DiagMessage(path) << "writing to archive (keep_raw_values=" << (keep_raw_values ? "true" : "false") << ")"); } - io::BigBufferInputStream input_stream(&buffer); - return io::CopyInputStreamToArchive(context, &input_stream, path.to_string(), - ArchiveEntry::kCompress, writer); + switch (format) { + case OutputFormat::kApk: { + BigBuffer buffer(1024); + XmlFlattenerOptions options = {}; + options.keep_raw_values = keep_raw_values; + options.use_utf16 = utf16; + XmlFlattener flattener(&buffer, options); + if (!flattener.Consume(context, &xml_res)) { + return false; + } + + io::BigBufferInputStream input_stream(&buffer); + return io::CopyInputStreamToArchive(context, &input_stream, path.to_string(), + ArchiveEntry::kCompress, writer); + } break; + + case OutputFormat::kProto: { + pb::XmlNode pb_node; + SerializeXmlResourceToPb(xml_res, &pb_node); + return io::CopyProtoToArchive(context, &pb_node, path.to_string(), ArchiveEntry::kCompress, + writer); + } break; + } + return false; } static std::unique_ptr<ResourceTable> LoadTableFromPb(const Source& source, const void* data, @@ -310,6 +332,7 @@ struct ResourceFileFlattenerOptions { bool keep_raw_values = false; bool do_not_compress_anything = false; bool update_proguard_spec = false; + OutputFormat output_format = OutputFormat::kApk; std::unordered_set<std::string> extensions_to_not_compress; }; @@ -467,6 +490,10 @@ std::vector<std::unique_ptr<xml::XmlResource>> 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 +570,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<io::IData> data = file->OpenAsData(); if (!data) { context_->GetDiagnostics()->Error(DiagMessage(file->GetSource()) @@ -553,11 +580,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<int>(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; @@ -607,8 +650,9 @@ bool ResourceFileFlattener::Flatten(ResourceTable* table, IArchiveWriter* archiv return false; } } - error |= !FlattenXml(context_, doc.get(), dst_path, options_.keep_raw_values, - false /*utf16*/, archive_writer); + + error |= !FlattenXml(context_, *doc, dst_path, options_.keep_raw_values, + false /*utf16*/, options_.output_format, archive_writer); } } else { error |= !io::CopyFileToArchive(context_, file_op.file_to_copy, file_op.dst_path, @@ -907,23 +951,29 @@ class LinkCommand { } } - bool FlattenTable(ResourceTable* table, IArchiveWriter* writer) { - BigBuffer buffer(1024); - TableFlattener flattener(options_.table_flattener_options, &buffer); - if (!flattener.Consume(context_, table)) { - context_->GetDiagnostics()->Error(DiagMessage() << "failed to flatten resource table"); - return false; - } + bool FlattenTable(ResourceTable* table, OutputFormat format, IArchiveWriter* writer) { + switch (format) { + case OutputFormat::kApk: { + BigBuffer buffer(1024); + TableFlattener flattener(options_.table_flattener_options, &buffer); + if (!flattener.Consume(context_, table)) { + context_->GetDiagnostics()->Error(DiagMessage() << "failed to flatten resource table"); + return false; + } - io::BigBufferInputStream input_stream(&buffer); - return io::CopyInputStreamToArchive(context_, &input_stream, "resources.arsc", - ArchiveEntry::kAlign, writer); - } + io::BigBufferInputStream input_stream(&buffer); + return io::CopyInputStreamToArchive(context_, &input_stream, kApkResourceTablePath, + ArchiveEntry::kAlign, writer); + } break; - bool FlattenTableToPb(ResourceTable* table, IArchiveWriter* writer) { - pb::ResourceTable pb_table; - SerializeTableToPb(*table, &pb_table); - return io::CopyProtoToArchive(context_, &pb_table, "resources.arsc.flat", 0, writer); + case OutputFormat::kProto: { + pb::ResourceTable pb_table; + SerializeTableToPb(*table, &pb_table); + return io::CopyProtoToArchive(context_, &pb_table, kProtoResourceTablePath, + ArchiveEntry::kCompress, writer); + } break; + } + return false; } bool WriteJavaFile(ResourceTable* table, const StringPiece& package_name_to_generate, @@ -1152,7 +1202,7 @@ class LinkCommand { } std::unique_ptr<ResourceTable> LoadTablePbFromCollection(io::IFileCollection* collection) { - io::IFile* file = collection->FindFile("resources.arsc.flat"); + io::IFile* file = collection->FindFile(kProtoResourceTablePath); if (!file) { return {}; } @@ -1201,11 +1251,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 +1287,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()); - } - - bool result = false; - if (override) { - result = table_merger_->MergeFileOverlay(*file_desc, file); - } else { - result = table_merger_->MergeFile(*file_desc, file); + context_->GetDiagnostics()->Note(DiagMessage() + << "merging '" << compiled_file.name + << "' from compiled file " << compiled_file.source); } - 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<ResourceName> mangled_name = - context_->GetNameMangler()->MangleName(exported_symbol.name); + Maybe<ResourceName> mangled_name = context_->GetNameMangler()->MangleName(res_name); if (mangled_name) { res_name = mangled_name.value(); } std::unique_ptr<Id> id = util::make_unique<Id>(); - 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 +1325,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 +1355,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,82 +1372,87 @@ 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<io::IData> 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; + 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"; + context_->GetDiagnostics()->Error(DiagMessage(src) << "uncompiled " << file_type + << " 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; } + } + + std::unique_ptr<io::InputStream> 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; + } - for (uint32_t i = 0; i < num_files; i++) { - pb::internal::CompiledFile compiled_file; - if (!input_stream.ReadCompiledFile(&compiled_file)) { + ResourceTable table; + std::string error; + if (!DeserializeTableFromPb(pb_table, &table, &error)) { context_->GetDiagnostics()->Error(DiagMessage(src) - << "failed to read compiled file header"); + << "failed to deserialize resource table: " << error); return false; } - uint64_t offset, len; - if (!input_stream.ReadDataMetaData(&offset, &len)) { - context_->GetDiagnostics()->Error(DiagMessage(src) << "failed to read data meta data"); + 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(compiled_file, &resource_file, &error)) { + if (!DeserializeCompiledFileFromPb(pb_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)) { + if (!MergeCompiledFile(resource_file, file->CreateFileSegment(offset, len), override)) { return false; } } - return true; - } else 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"; - context_->GetDiagnostics()->Error(DiagMessage(src) << "uncompiled " << file_type - << " file passed as argument. Must be " - "compiled first into .flat file."); - return false; } - - // Ignore non .flat files. This could be classes.dex or something else that - // happens - // to be in an archive. return true; } @@ -1471,15 +1496,13 @@ class LinkCommand { return true; } - /** - * Writes the AndroidManifest, ResourceTable, and all XML files referenced by - * the ResourceTable to the IArchiveWriter. - */ + // Writes the AndroidManifest, ResourceTable, and all XML files referenced by the ResourceTable + // to the IArchiveWriter. bool WriteApk(IArchiveWriter* writer, proguard::KeepSet* keep_set, xml::XmlResource* manifest, ResourceTable* table) { const bool keep_raw_values = context_->GetPackageType() == PackageType::kStaticLib; - bool result = FlattenXml(context_, manifest, "AndroidManifest.xml", keep_raw_values, - true /*utf16*/, writer); + bool result = FlattenXml(context_, *manifest, "AndroidManifest.xml", keep_raw_values, + true /*utf16*/, options_.output_format, writer); if (!result) { return false; } @@ -1494,6 +1517,7 @@ class LinkCommand { file_flattener_options.no_xml_namespaces = options_.no_xml_namespaces; file_flattener_options.update_proguard_spec = static_cast<bool>(options_.generate_proguard_rules_path); + file_flattener_options.output_format = options_.output_format; ResourceFileFlattener file_flattener(file_flattener_options, context_, keep_set); @@ -1502,15 +1526,9 @@ class LinkCommand { return false; } - if (context_->GetPackageType() == PackageType::kStaticLib) { - if (!FlattenTableToPb(table, writer)) { - return false; - } - } else { - if (!FlattenTable(table, writer)) { - context_->GetDiagnostics()->Error(DiagMessage() << "failed to write resources.arsc"); - return false; - } + if (!FlattenTable(table, options_.output_format, writer)) { + context_->GetDiagnostics()->Error(DiagMessage() << "failed to write resource table"); + return false; } return true; } @@ -1874,6 +1892,7 @@ int Link(const std::vector<StringPiece>& args, IDiagnostics* diagnostics) { bool verbose = false; bool shared_lib = false; bool static_lib = false; + bool proto_format = false; Maybe<std::string> stable_id_file_path; std::vector<std::string> split_args; Flags flags = @@ -1954,6 +1973,10 @@ int Link(const std::vector<StringPiece>& args, IDiagnostics* diagnostics) { .OptionalSwitch("--shared-lib", "Generates a shared Android runtime library.", &shared_lib) .OptionalSwitch("--static-lib", "Generate a static Android library.", &static_lib) + .OptionalSwitch("--proto-format", + "Generates compiled resources in Protobuf format.\n" + "Suitable as input to the bundle tool for generating an App Bundle.", + &proto_format) .OptionalSwitch("--no-static-lib-packages", "Merge all library resources under the app's package.", &options.no_static_lib_packages) @@ -2040,21 +2063,25 @@ int Link(const std::vector<StringPiece>& args, IDiagnostics* diagnostics) { context.SetVerbose(verbose); } - if (shared_lib && static_lib) { - context.GetDiagnostics()->Error(DiagMessage() - << "only one of --shared-lib and --static-lib can be defined"); + if (int{shared_lib} + int{static_lib} + int{proto_format} > 1) { + context.GetDiagnostics()->Error( + DiagMessage() + << "only one of --shared-lib, --static-lib, or --proto_format can be defined"); return 1; } + // The default build type. + context.SetPackageType(PackageType::kApp); + context.SetPackageId(kAppPackageId); + if (shared_lib) { context.SetPackageType(PackageType::kSharedLib); context.SetPackageId(0x00); } else if (static_lib) { context.SetPackageType(PackageType::kStaticLib); - context.SetPackageId(kAppPackageId); - } else { - context.SetPackageType(PackageType::kApp); - context.SetPackageId(kAppPackageId); + options.output_format = OutputFormat::kProto; + } else if (proto_format) { + options.output_format = OutputFormat::kProto; } if (package_id) { diff --git a/tools/aapt2/cmd/Optimize.cpp b/tools/aapt2/cmd/Optimize.cpp index 67ac67a9367e..44e148ee9732 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 a79a577c663b..b99240f0a40a 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 000000000000..739555c5b15d --- /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<uint32_t>(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<int>::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<int>(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<int>::max()) { + std::ostringstream error; + error << "header length " << header_length << " is too large"; + reader_->error_ = error.str(); + return false; + } + + if (data_length > std::numeric_limits<size_t>::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<int>(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<int>(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<ContainerEntryType>(entry_type); + } else { + error_ = StringPrintf("entry type 0x%08x is invalid", entry_type); + return nullptr; + } + + if (entry_length > std::numeric_limits<size_t>::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 000000000000..aa5c82cd322c --- /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 <inttypes.h> + +#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 000000000000..dc81a3ae2219 --- /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<IAaptContext> 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::FileReference::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<size_t>(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/binary/XmlFlattener.cpp b/tools/aapt2/format/binary/XmlFlattener.cpp index 2456c3dfd5e9..345cc95cfb29 100644 --- a/tools/aapt2/format/binary/XmlFlattener.cpp +++ b/tools/aapt2/format/binary/XmlFlattener.cpp @@ -56,9 +56,9 @@ static bool cmp_xml_attribute_by_id(const xml::Attribute* a, const xml::Attribut return false; } -class XmlFlattenerVisitor : public xml::Visitor { +class XmlFlattenerVisitor : public xml::ConstVisitor { public: - using xml::Visitor::Visit; + using xml::ConstVisitor::Visit; StringPool pool; std::map<uint8_t, StringPool> package_pools; @@ -74,7 +74,7 @@ class XmlFlattenerVisitor : public xml::Visitor { : buffer_(buffer), options_(options) { } - void Visit(xml::Text* node) override { + void Visit(const xml::Text* node) override { if (util::TrimWhitespace(node->text).empty()) { // Skip whitespace only text nodes. return; @@ -95,7 +95,7 @@ class XmlFlattenerVisitor : public xml::Visitor { writer.Finish(); } - void Visit(xml::Element* node) override { + void Visit(const xml::Element* node) override { for (const xml::NamespaceDecl& decl : node->namespace_decls) { // Skip dedicated tools namespace. if (decl.uri != xml::kSchemaTools) { @@ -125,7 +125,7 @@ class XmlFlattenerVisitor : public xml::Visitor { start_writer.Finish(); } - xml::Visitor::Visit(node); + xml::ConstVisitor::Visit(node); { ChunkWriter end_writer(buffer_); @@ -182,12 +182,13 @@ class XmlFlattenerVisitor : public xml::Visitor { writer.Finish(); } - void WriteAttributes(xml::Element* node, ResXMLTree_attrExt* flat_elem, ChunkWriter* writer) { + void WriteAttributes(const xml::Element* node, ResXMLTree_attrExt* flat_elem, + ChunkWriter* writer) { filtered_attrs_.clear(); filtered_attrs_.reserve(node->attributes.size()); // Filter the attributes. - for (xml::Attribute& attr : node->attributes) { + for (const xml::Attribute& attr : node->attributes) { if (attr.namespace_uri != xml::kSchemaTools) { filtered_attrs_.push_back(&attr); } @@ -282,12 +283,12 @@ class XmlFlattenerVisitor : public xml::Visitor { XmlFlattenerOptions options_; // Scratch vector to filter attributes. We avoid allocations making this a member. - std::vector<xml::Attribute*> filtered_attrs_; + std::vector<const xml::Attribute*> filtered_attrs_; }; } // namespace -bool XmlFlattener::Flatten(IAaptContext* context, xml::Node* node) { +bool XmlFlattener::Flatten(IAaptContext* context, const xml::Node* node) { BigBuffer node_buffer(1024); XmlFlattenerVisitor visitor(&node_buffer, options_); node->Accept(&visitor); @@ -341,7 +342,7 @@ bool XmlFlattener::Flatten(IAaptContext* context, xml::Node* node) { return true; } -bool XmlFlattener::Consume(IAaptContext* context, xml::XmlResource* resource) { +bool XmlFlattener::Consume(IAaptContext* context, const xml::XmlResource* resource) { if (!resource->root) { return false; } diff --git a/tools/aapt2/format/binary/XmlFlattener.h b/tools/aapt2/format/binary/XmlFlattener.h index 8db2281cd74a..1f9e777f7a1a 100644 --- a/tools/aapt2/format/binary/XmlFlattener.h +++ b/tools/aapt2/format/binary/XmlFlattener.h @@ -34,18 +34,18 @@ struct XmlFlattenerOptions { bool use_utf16 = false; }; -class XmlFlattener : public IXmlResourceConsumer { +class XmlFlattener { public: XmlFlattener(BigBuffer* buffer, XmlFlattenerOptions options) : buffer_(buffer), options_(options) { } - bool Consume(IAaptContext* context, xml::XmlResource* resource) override; + bool Consume(IAaptContext* context, const xml::XmlResource* resource); private: DISALLOW_COPY_AND_ASSIGN(XmlFlattener); - bool Flatten(IAaptContext* context, xml::Node* node); + bool Flatten(IAaptContext* context, const xml::Node* node); BigBuffer* buffer_; XmlFlattenerOptions options_; diff --git a/tools/aapt2/format/proto/ProtoDeserialize.cpp b/tools/aapt2/format/proto/ProtoDeserialize.cpp index c14f09a3b103..86bd86536cc4 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 { @@ -391,8 +390,15 @@ static bool DeserializePackageFromPb(const pb::Package& pb_package, const ResStr } ResourceTableType* type = pkg->FindOrCreateType(*res_type); + if (pb_type.has_type_id()) { + type->id = static_cast<uint8_t>(pb_type.type_id().id()); + } + for (const pb::Entry& pb_entry : pb_type.entry()) { ResourceEntry* entry = type->FindOrCreateEntry(pb_entry.name()); + if (pb_entry.has_entry_id()) { + entry->id = static_cast<uint16_t>(pb_entry.entry_id().id()); + } // Deserialize the symbol status (public/private with source and comments). if (pb_entry.has_symbol_status()) { @@ -406,21 +412,11 @@ static bool DeserializePackageFromPb(const pb::Package& pb_package, const ResStr const SymbolState visibility = DeserializeVisibilityFromPb(pb_status.visibility()); entry->symbol_status.state = visibility; - if (visibility == SymbolState::kPublic) { - // This is a public symbol, we must encode the ID now if there is one. - if (pb_entry.has_entry_id()) { - entry->id = static_cast<uint16_t>(pb_entry.entry_id().id()); - } - - if (type->symbol_status.state != SymbolState::kPublic) { - // If the type has not been made public, do so now. - type->symbol_status.state = SymbolState::kPublic; - if (pb_type.has_type_id()) { - type->id = static_cast<uint8_t>(pb_type.type_id().id()); - } - } + // Propagate the public visibility up to the Type. + type->symbol_status.state = SymbolState::kPublic; } else if (visibility == SymbolState::kPrivate) { + // Only propagate if no previous state was assigned. if (type->symbol_status.state == SymbolState::kUndefined) { type->symbol_status.state = SymbolState::kPrivate; } @@ -485,6 +481,19 @@ bool DeserializeTableFromPb(const pb::ResourceTable& pb_table, ResourceTable* ou return true; } +static ResourceFile::Type DeserializeFileReferenceTypeFromPb(const pb::FileReference::Type& type) { + switch (type) { + case pb::FileReference::BINARY_XML: + return ResourceFile::Type::kBinaryXml; + case pb::FileReference::PROTO_XML: + return ResourceFile::Type::kProtoXml; + case pb::FileReference::PNG: + return ResourceFile::Type::kPng; + 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 +506,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 = DeserializeFileReferenceTypeFromPb(pb_file.type()); std::string config_error; if (!DeserializeConfigFromPb(pb_file.config(), &out_file->config, &config_error)) { @@ -759,8 +769,12 @@ std::unique_ptr<Item> DeserializeItemFromPb(const pb::Item& pb_item, } break; case pb::Item::kFile: { - return util::make_unique<FileReference>(value_pool->MakeRef( - pb_item.file().path(), StringPool::Context(StringPool::Context::kHighPriority, config))); + const pb::FileReference& pb_file = pb_item.file(); + std::unique_ptr<FileReference> file_ref = + util::make_unique<FileReference>(value_pool->MakeRef( + pb_file.path(), StringPool::Context(StringPool::Context::kHighPriority, config))); + file_ref->type = DeserializeFileReferenceTypeFromPb(pb_file.type()); + return std::move(file_ref); } break; default: @@ -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<const uint8_t*>(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<int>(pb_size)); - - // Check that we haven't tried to read past the end. - if (static_cast<uint64_t>(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<uint64_t>(in_.BytesUntilLimit())) { - in_.PushLimit(0); - return false; - } - - uint64_t offset = static_cast<uint64_t>(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 c8a7199eb8b5..7dc54f242d94 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 c0d36141d457..1d184fe5a8d2 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) { @@ -366,6 +361,19 @@ static pb::Plural_Arity SerializePluralEnumToPb(size_t plural_idx) { return pb::Plural_Arity_OTHER; } +static pb::FileReference::Type SerializeFileReferenceTypeToPb(const ResourceFile::Type& type) { + switch (type) { + case ResourceFile::Type::kBinaryXml: + return pb::FileReference::BINARY_XML; + case ResourceFile::Type::kProtoXml: + return pb::FileReference::PROTO_XML; + case ResourceFile::Type::kPng: + return pb::FileReference::PNG; + default: + return pb::FileReference::UNKNOWN; + } +} + namespace { class ValueSerializer : public ConstValueVisitor { @@ -400,7 +408,9 @@ class ValueSerializer : public ConstValueVisitor { } void Visit(const FileReference* file) override { - out_value_->mutable_item()->mutable_file()->set_path(*file->path); + pb::FileReference* pb_file = out_value_->mutable_item()->mutable_file(); + pb_file->set_path(*file->path); + pb_file->set_type(SerializeFileReferenceTypeToPb(file->type)); } void Visit(const Id* /*id*/) override { @@ -515,6 +525,7 @@ void SerializeItemToPb(const Item& item, pb::Item* out_item) { 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(SerializeFileReferenceTypeToPb(file.type)); SerializeConfig(file.config, out_file->mutable_config()); for (const SourcedResourceName& exported : file.exported_symbols) { @@ -579,44 +590,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<uint64_t>(compiled_file.ByteSize())); - compiled_file.SerializeWithCachedSizes(&out_); -} - -void CompiledFileOutputStream::WriteData(const BigBuffer& buffer) { - EnsureAlignedWrite(); - out_.WriteLittleEndian64(static_cast<uint64_t>(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<uint64_t>(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 1694b16f3277..95dd413c1713 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 2154d5a9ab2c..8efac8ae6ffd 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<IAaptContext> 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 000000000000..bb31a005ef42 --- /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 95113bc2132c..000000000000 --- 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 index eb99033e1cbe..9704caae4719 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 index 92612c744aae..8b5c8b84cd3c 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 09dc7ea5b1c4..db91a77a5ae6 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 ee737280ec81..b4f1ff3a5b49 100644 --- a/tools/aapt2/io/File.cpp +++ b/tools/aapt2/io/File.cpp @@ -39,5 +39,9 @@ std::unique_ptr<IData> FileSegment::OpenAsData() { return {}; } +std::unique_ptr<io::InputStream> FileSegment::OpenInputStream() { + return OpenAsData(); +} + } // namespace io } // namespace aapt diff --git a/tools/aapt2/io/File.h b/tools/aapt2/io/File.h index 7ef6d88c1e3b..f06e28c79c7b 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<IData> OpenAsData() = 0; + virtual std::unique_ptr<io::InputStream> 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<IData> OpenAsData() override; + std::unique_ptr<io::InputStream> 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 index 07dbb5a98add..2f7a4b3479ca 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 <errno.h> // for errno #include <fcntl.h> // 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 index 6beb9a186ce5..3b0766750eae 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 <memory> #include <string> +#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<uint8_t[]> 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 index 7314ab7beeba..68c3cb1f4156 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<void**>(&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<void**>(&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<void**>(&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 027cbd041fa6..1387d2218ed4 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<IData> RegularFile::OpenAsData() { return {}; } -const Source& RegularFile::GetSource() const { return source_; } +std::unique_ptr<io::InputStream> RegularFile::OpenInputStream() { + return util::make_unique<FileInputStream>(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 dfd37172004b..6be8807735f1 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<IData> OpenAsData() override; + std::unique_ptr<io::InputStream> 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<std::string, std::unique_ptr<IFile>>::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<IFileCollectionIterator> Iterator() override; private: + DISALLOW_COPY_AND_ASSIGN(FileCollection); + friend class FileCollectionIterator; + std::map<std::string, std::unique_ptr<IFile>> files_; }; diff --git a/tools/aapt2/io/Io.h b/tools/aapt2/io/Io.h index a656740b35da..e1df23a698f5 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 index 51a18a7d8a9f..4ca04a8c7477 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 index ff5b112ef274..f29890ab7ee5 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 <memory> + #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<char[]> 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 index cc57bc498313..fb43fb773dd4 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 15114e8dbf54..d270340a180c 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 02ee876014f7..1e48508bb4b2 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<size_t>(out_size); + if (!result) { + error_ocurred_ = true; + } + return result; + } + + void BackUp(size_t count) override { + out_->BackUp(static_cast<int>(count)); + } + + size_t ByteCount() const override { + return static_cast<size_t>(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<int>(out_size); + return result; + } + + void BackUp(int count) override { + in_->BackUp(static_cast<size_t>(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 6494d2def748..269b6c5a12e1 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<IData> ZipFile::OpenAsData() { } } -const Source& ZipFile::GetSource() const { return source_; } +std::unique_ptr<io::InputStream> 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 56c74e326e6c..8381259d77c5 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<IData> OpenAsData() override; + std::unique_ptr<io::InputStream> 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<std::unique_ptr<IFile>>::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<ZipFileCollection> Create(const android::StringPiece& path, diff --git a/tools/aapt2/link/TableMerger.cpp b/tools/aapt2/link/TableMerger.cpp index 93c904f1a743..21c6b111b855 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<FileReference> TableMerger::CloneAndMangleFile( util::make_unique<FileReference>(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<FileReference>(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<FileReference> file_ref = util::make_unique<FileReference>(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 81518ffb2441..d024aa47d952 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<std::string> 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 45b01a494164..3499809e5a83 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<FileReference>( &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<FileReference>(&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<BinaryPrimitive>(&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<Styleable>(&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 e658664d8653..5a62e9777065 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 61d056309a69..4e318a92f3fa 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<io::InputStream> 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 b3e0a926b4bd..35225066633c 100644 --- a/tools/aapt2/xml/XmlDom.cpp +++ b/tools/aapt2/xml/XmlDom.cpp @@ -215,8 +215,8 @@ std::unique_ptr<XmlResource> Inflate(InputStream* in, IDiagnostics* diag, const return {}; } } - return util::make_unique<XmlResource>(ResourceFile{{}, {}, source}, StringPool{}, - std::move(stack.root)); + return util::make_unique<XmlResource>(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 4ba04430ced3..34e6d3f54f56 100644 --- a/tools/aapt2/xml/XmlDom_test.cpp +++ b/tools/aapt2/xml/XmlDom_test.cpp @@ -19,7 +19,7 @@ #include <string> #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 681d9d48173f..5304bded85a5 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"(<?xml version="1.0" encoding="utf-8"?> <a><b><c xmlns:a="http://schema.org"><d/></c><e/></b></a>)"; 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 c1186e83369c..0a622b2bd336 100644 --- a/tools/aapt2/xml/XmlUtil.cpp +++ b/tools/aapt2/xml/XmlUtil.cpp @@ -16,20 +16,20 @@ #include "xml/XmlUtil.h" +#include <algorithm> #include <string> #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<ExtractedPackage> 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<ExtractedPackage> 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 4eb359a9eed4..592a604f87a6 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 |