diff options
-rw-r--r-- | tools/aapt2/Android.bp | 1 | ||||
-rw-r--r-- | tools/aapt2/LoadedApk.cpp | 25 | ||||
-rw-r--r-- | tools/aapt2/Main.cpp | 2 | ||||
-rw-r--r-- | tools/aapt2/compile/Compile.cpp | 103 | ||||
-rw-r--r-- | tools/aapt2/compile/Png.cpp | 1 | ||||
-rw-r--r-- | tools/aapt2/compile/Png.h | 25 | ||||
-rw-r--r-- | tools/aapt2/compile/PngChunkFilter.cpp | 34 | ||||
-rw-r--r-- | tools/aapt2/compile/PngCrunch.cpp | 155 | ||||
-rw-r--r-- | tools/aapt2/flatten/Archive.cpp | 134 | ||||
-rw-r--r-- | tools/aapt2/flatten/Archive.h | 27 | ||||
-rw-r--r-- | tools/aapt2/io/BigBufferInputStream.h | 56 | ||||
-rw-r--r-- | tools/aapt2/io/BigBufferOutputStream.h | 48 | ||||
-rw-r--r-- | tools/aapt2/io/BigBufferStreams.cpp | 86 | ||||
-rw-r--r-- | tools/aapt2/io/Data.h | 144 | ||||
-rw-r--r-- | tools/aapt2/io/File.h | 47 | ||||
-rw-r--r-- | tools/aapt2/io/Io.cpp | 7 | ||||
-rw-r--r-- | tools/aapt2/io/Io.h | 76 | ||||
-rw-r--r-- | tools/aapt2/link/Link.cpp | 113 | ||||
-rw-r--r-- | tools/aapt2/readme.md | 7 |
19 files changed, 678 insertions, 413 deletions
diff --git a/tools/aapt2/Android.bp b/tools/aapt2/Android.bp index 57036aad611b..ef3797c4a277 100644 --- a/tools/aapt2/Android.bp +++ b/tools/aapt2/Android.bp @@ -87,6 +87,7 @@ cc_library_host_static { "flatten/Archive.cpp", "flatten/TableFlattener.cpp", "flatten/XmlFlattener.cpp", + "io/BigBufferStreams.cpp", "io/File.cpp", "io/FileSystem.cpp", "io/Io.cpp", diff --git a/tools/aapt2/LoadedApk.cpp b/tools/aapt2/LoadedApk.cpp index 1d04b357131a..b855f8f80c58 100644 --- a/tools/aapt2/LoadedApk.cpp +++ b/tools/aapt2/LoadedApk.cpp @@ -20,6 +20,7 @@ #include "ValueVisitor.h" #include "flatten/Archive.h" #include "flatten/TableFlattener.h" +#include "io/BigBufferInputStream.h" namespace aapt { @@ -27,8 +28,7 @@ std::unique_ptr<LoadedApk> LoadedApk::LoadApkFromPath(IAaptContext* context, const android::StringPiece& path) { Source source(path); std::string error; - std::unique_ptr<io::ZipFileCollection> apk = - io::ZipFileCollection::Create(path, &error); + std::unique_ptr<io::ZipFileCollection> apk = io::ZipFileCollection::Create(path, &error); if (!apk) { context->GetDiagnostics()->Error(DiagMessage(source) << error); return {}; @@ -36,21 +36,18 @@ std::unique_ptr<LoadedApk> LoadedApk::LoadApkFromPath(IAaptContext* context, io::IFile* file = apk->FindFile("resources.arsc"); if (!file) { - context->GetDiagnostics()->Error(DiagMessage(source) - << "no resources.arsc found"); + context->GetDiagnostics()->Error(DiagMessage(source) << "no resources.arsc found"); return {}; } std::unique_ptr<io::IData> data = file->OpenAsData(); if (!data) { - context->GetDiagnostics()->Error(DiagMessage(source) - << "could not open resources.arsc"); + context->GetDiagnostics()->Error(DiagMessage(source) << "could not open resources.arsc"); return {}; } std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>(); - BinaryResourceParser parser(context, table.get(), source, data->data(), - data->size()); + BinaryResourceParser parser(context, table.get(), source, data->data(), data->size()); if (!parser.Parse()) { return {}; } @@ -92,9 +89,9 @@ bool LoadedApk::WriteToArchive(IAaptContext* context, const TableFlattenerOption continue; } - // The resource table needs to be reserialized since it might have changed. + // The resource table needs to be re-serialized since it might have changed. if (path == "resources.arsc") { - BigBuffer buffer = BigBuffer(1024); + BigBuffer buffer(4096); // TODO(adamlesinski): How to determine if there were sparse entries (and if to encode // with sparse entries) b/35389232. TableFlattener flattener(options, &buffer); @@ -102,8 +99,8 @@ bool LoadedApk::WriteToArchive(IAaptContext* context, const TableFlattenerOption return false; } - if (!writer->StartEntry(path, ArchiveEntry::kAlign) || !writer->WriteEntry(buffer) || - !writer->FinishEntry()) { + io::BigBufferInputStream input_stream(&buffer); + if (!writer->WriteFile(path, ArchiveEntry::kAlign, &input_stream)) { context->GetDiagnostics()->Error(DiagMessage() << "Error when writing file '" << path << "' in APK."); return false; @@ -113,14 +110,12 @@ bool LoadedApk::WriteToArchive(IAaptContext* context, const TableFlattenerOption std::unique_ptr<io::IData> data = file->OpenAsData(); uint32_t compression_flags = file->WasCompressed() ? ArchiveEntry::kCompress : 0u; - if (!writer->StartEntry(path, compression_flags) || - !writer->WriteEntry(data->data(), data->size()) || !writer->FinishEntry()) { + if (!writer->WriteFile(path, compression_flags, data.get())) { context->GetDiagnostics()->Error(DiagMessage() << "Error when writing file '" << path << "' in APK."); return false; } } - return true; } diff --git a/tools/aapt2/Main.cpp b/tools/aapt2/Main.cpp index 456f68635705..5e9b81a29af9 100644 --- a/tools/aapt2/Main.cpp +++ b/tools/aapt2/Main.cpp @@ -25,7 +25,7 @@ namespace aapt { static const char* sMajorVersion = "2"; // Update minor version whenever a feature or flag is added. -static const char* sMinorVersion = "10"; +static const char* sMinorVersion = "11"; int PrintVersion() { std::cerr << "Android Asset Packaging Tool (aapt) " << sMajorVersion << "." diff --git a/tools/aapt2/compile/Compile.cpp b/tools/aapt2/compile/Compile.cpp index 8027f4289169..1fe30f0b1478 100644 --- a/tools/aapt2/compile/Compile.cpp +++ b/tools/aapt2/compile/Compile.cpp @@ -37,6 +37,7 @@ #include "compile/XmlIdCollector.h" #include "flatten/Archive.h" #include "flatten/XmlFlattener.h" +#include "io/BigBufferOutputStream.h" #include "proto/ProtoSerialize.h" #include "util/Files.h" #include "util/Maybe.h" @@ -46,7 +47,6 @@ using android::StringPiece; using google::protobuf::io::CopyingOutputStreamAdaptor; -using google::protobuf::io::ZeroCopyOutputStream; namespace aapt { @@ -142,10 +142,10 @@ static bool LoadInputFilesFromDir( IAaptContext* context, const CompileOptions& options, std::vector<ResourcePathData>* out_path_data) { const std::string& root_dir = options.res_dir.value(); - std::unique_ptr<DIR, decltype(closedir)*> d(opendir(root_dir.data()), - closedir); + std::unique_ptr<DIR, decltype(closedir)*> d(opendir(root_dir.data()), closedir); if (!d) { - context->GetDiagnostics()->Error(DiagMessage() << strerror(errno)); + context->GetDiagnostics()->Error(DiagMessage() + << android::base::SystemErrorCodeToString(errno)); return false; } @@ -161,10 +161,10 @@ static bool LoadInputFilesFromDir( continue; } - std::unique_ptr<DIR, decltype(closedir)*> subdir( - opendir(prefix_path.data()), closedir); + std::unique_ptr<DIR, decltype(closedir)*> subdir(opendir(prefix_path.data()), closedir); if (!subdir) { - context->GetDiagnostics()->Error(DiagMessage() << strerror(errno)); + context->GetDiagnostics()->Error(DiagMessage() + << android::base::SystemErrorCodeToString(errno)); return false; } @@ -177,8 +177,7 @@ static bool LoadInputFilesFromDir( file::AppendPath(&full_path, leaf_entry->d_name); std::string err_str; - Maybe<ResourcePathData> path_data = - ExtractResourcePathData(full_path, &err_str); + Maybe<ResourcePathData> path_data = ExtractResourcePathData(full_path, &err_str); if (!path_data) { context->GetDiagnostics()->Error(DiagMessage() << err_str); return false; @@ -199,7 +198,7 @@ static bool CompileTable(IAaptContext* context, const CompileOptions& options, std::ifstream fin(path_data.source.path, std::ifstream::binary); if (!fin) { context->GetDiagnostics()->Error(DiagMessage(path_data.source) - << strerror(errno)); + << android::base::SystemErrorCodeToString(errno)); return false; } @@ -249,8 +248,7 @@ static bool CompileTable(IAaptContext* context, const CompileOptions& options, // Create the file/zip entry. if (!writer->StartEntry(output_path, 0)) { - context->GetDiagnostics()->Error(DiagMessage(output_path) - << "failed to open"); + context->GetDiagnostics()->Error(DiagMessage(output_path) << "failed to open"); return false; } @@ -258,21 +256,18 @@ static bool CompileTable(IAaptContext* context, const CompileOptions& options, // writer->FinishEntry(). { // Wrap our IArchiveWriter with an adaptor that implements the - // ZeroCopyOutputStream - // interface. + // ZeroCopyOutputStream interface. CopyingOutputStreamAdaptor copying_adaptor(writer); std::unique_ptr<pb::ResourceTable> pb_table = SerializeTableToPb(&table); if (!pb_table->SerializeToZeroCopyStream(©ing_adaptor)) { - context->GetDiagnostics()->Error(DiagMessage(output_path) - << "failed to write"); + context->GetDiagnostics()->Error(DiagMessage(output_path) << "failed to write"); return false; } } if (!writer->FinishEntry()) { - context->GetDiagnostics()->Error(DiagMessage(output_path) - << "failed to finish entry"); + context->GetDiagnostics()->Error(DiagMessage(output_path) << "failed to finish entry"); return false; } return true; @@ -293,16 +288,14 @@ static bool WriteHeaderAndBufferToWriter(const StringPiece& output_path, // writer->FinishEntry(). { // Wrap our IArchiveWriter with an adaptor that implements the - // ZeroCopyOutputStream - // interface. + // ZeroCopyOutputStream interface. CopyingOutputStreamAdaptor copying_adaptor(writer); CompiledFileOutputStream output_stream(©ing_adaptor); // Number of CompiledFiles. output_stream.WriteLittleEndian32(1); - std::unique_ptr<pb::CompiledFile> compiled_file = - SerializeCompiledFileToPb(file); + std::unique_ptr<pb::CompiledFile> compiled_file = SerializeCompiledFileToPb(file); output_stream.WriteCompiledFile(compiled_file.get()); output_stream.WriteData(&buffer); @@ -371,14 +364,12 @@ static bool FlattenXmlToOutStream(IAaptContext* context, return false; } - std::unique_ptr<pb::CompiledFile> pb_compiled_file = - SerializeCompiledFileToPb(xmlres->file); + std::unique_ptr<pb::CompiledFile> pb_compiled_file = SerializeCompiledFileToPb(xmlres->file); out->WriteCompiledFile(pb_compiled_file.get()); out->WriteData(&buffer); if (out->HadError()) { - context->GetDiagnostics()->Error(DiagMessage(output_path) - << "failed to write data"); + context->GetDiagnostics()->Error(DiagMessage(output_path) << "failed to write data"); return false; } return true; @@ -388,8 +379,7 @@ static bool CompileXml(IAaptContext* context, const CompileOptions& options, const ResourcePathData& path_data, IArchiveWriter* writer, const std::string& output_path) { if (context->IsVerbose()) { - context->GetDiagnostics()->Note(DiagMessage(path_data.source) - << "compiling XML"); + context->GetDiagnostics()->Note(DiagMessage(path_data.source) << "compiling XML"); } std::unique_ptr<xml::XmlResource> xmlres; @@ -397,7 +387,7 @@ static bool CompileXml(IAaptContext* context, const CompileOptions& options, std::ifstream fin(path_data.source.path, std::ifstream::binary); if (!fin) { context->GetDiagnostics()->Error(DiagMessage(path_data.source) - << strerror(errno)); + << android::base::SystemErrorCodeToString(errno)); return false; } @@ -470,31 +460,6 @@ static bool CompileXml(IAaptContext* context, const CompileOptions& options, return true; } -class BigBufferOutputStream : public io::OutputStream { - public: - explicit BigBufferOutputStream(BigBuffer* buffer) : buffer_(buffer) {} - - bool Next(void** data, int* len) override { - size_t count; - *data = buffer_->NextBlock(&count); - *len = static_cast<int>(count); - return true; - } - - void BackUp(int count) override { buffer_->BackUp(count); } - - google::protobuf::int64 ByteCount() const override { - return buffer_->size(); - } - - bool HadError() const override { return false; } - - private: - BigBuffer* buffer_; - - DISALLOW_COPY_AND_ASSIGN(BigBufferOutputStream); -}; - static bool CompilePng(IAaptContext* context, const CompileOptions& options, const ResourcePathData& path_data, IArchiveWriter* writer, const std::string& output_path) { @@ -520,7 +485,7 @@ static bool CompilePng(IAaptContext* context, const CompileOptions& options, } BigBuffer crunched_png_buffer(4096); - BigBufferOutputStream crunched_png_buffer_out(&crunched_png_buffer); + io::BigBufferOutputStream crunched_png_buffer_out(&crunched_png_buffer); // Ensure that we only keep the chunks we care about if we end up // using the original PNG instead of the crunched one. @@ -533,8 +498,7 @@ static bool CompilePng(IAaptContext* context, const CompileOptions& options, std::unique_ptr<NinePatch> nine_patch; if (path_data.extension == "9.png") { std::string err; - nine_patch = NinePatch::Create(image->rows.get(), image->width, - image->height, &err); + nine_patch = NinePatch::Create(image->rows.get(), image->width, image->height, &err); if (!nine_patch) { context->GetDiagnostics()->Error(DiagMessage() << err); return false; @@ -547,8 +511,7 @@ static bool CompilePng(IAaptContext* context, const CompileOptions& options, // width - 2. image->width -= 2; image->height -= 2; - memmove(image->rows.get(), image->rows.get() + 1, - image->height * sizeof(uint8_t**)); + memmove(image->rows.get(), image->rows.get() + 1, image->height * sizeof(uint8_t**)); for (int32_t h = 0; h < image->height; h++) { memmove(image->rows[h], image->rows[h] + 4, image->width * 4); } @@ -560,8 +523,7 @@ static bool CompilePng(IAaptContext* context, const CompileOptions& options, } // Write the crunched PNG. - if (!WritePng(context, image.get(), nine_patch.get(), - &crunched_png_buffer_out, {})) { + if (!WritePng(context, image.get(), nine_patch.get(), &crunched_png_buffer_out, {})) { return false; } @@ -574,24 +536,21 @@ static bool CompilePng(IAaptContext* context, const CompileOptions& options, // The re-encoded PNG is larger than the original, and there is // no mandatory transformation. Use the original. if (context->IsVerbose()) { - context->GetDiagnostics()->Note( - DiagMessage(path_data.source) - << "original PNG is smaller than crunched PNG" - << ", using original"); + context->GetDiagnostics()->Note(DiagMessage(path_data.source) + << "original PNG is smaller than crunched PNG" + << ", using original"); } - PngChunkFilter png_chunk_filter_again(content); + png_chunk_filter.Rewind(); BigBuffer filtered_png_buffer(4096); - BigBufferOutputStream filtered_png_buffer_out(&filtered_png_buffer); - io::Copy(&filtered_png_buffer_out, &png_chunk_filter_again); + io::BigBufferOutputStream filtered_png_buffer_out(&filtered_png_buffer); + io::Copy(&filtered_png_buffer_out, &png_chunk_filter); buffer.AppendBuffer(std::move(filtered_png_buffer)); } if (context->IsVerbose()) { - // For debugging only, use the legacy PNG cruncher and compare the - // resulting file sizes. - // This will help catch exotic cases where the new code may generate - // larger PNGs. + // For debugging only, use the legacy PNG cruncher and compare the resulting file sizes. + // This will help catch exotic cases where the new code may generate larger PNGs. std::stringstream legacy_stream(content); BigBuffer legacy_buffer(4096); Png png(context->GetDiagnostics()); diff --git a/tools/aapt2/compile/Png.cpp b/tools/aapt2/compile/Png.cpp index 5e15c88410ee..6d6147d8c97a 100644 --- a/tools/aapt2/compile/Png.cpp +++ b/tools/aapt2/compile/Png.cpp @@ -33,7 +33,6 @@ namespace aapt { constexpr bool kDebug = false; -constexpr size_t kPngSignatureSize = 8u; struct PngInfo { ~PngInfo() { diff --git a/tools/aapt2/compile/Png.h b/tools/aapt2/compile/Png.h index a82005115850..e4255e714744 100644 --- a/tools/aapt2/compile/Png.h +++ b/tools/aapt2/compile/Png.h @@ -31,6 +31,9 @@ namespace aapt { +// Size in bytes of the PNG signature. +constexpr size_t kPngSignatureSize = 8u; + struct PngOptions { int grayscale_tolerance = 0; }; @@ -46,9 +49,9 @@ class Png { const PngOptions& options); private: - IDiagnostics* mDiag; - DISALLOW_COPY_AND_ASSIGN(Png); + + IDiagnostics* mDiag; }; /** @@ -57,26 +60,26 @@ class Png { class PngChunkFilter : public io::InputStream { public: explicit PngChunkFilter(const android::StringPiece& data); + virtual ~PngChunkFilter() = default; - bool Next(const void** buffer, int* len) override; - void BackUp(int count) override; - bool Skip(int count) override; + bool Next(const void** buffer, size_t* len) override; + void BackUp(size_t count) override; - google::protobuf::int64 ByteCount() const override { - return static_cast<google::protobuf::int64>(window_start_); - } + bool CanRewind() const override { return true; } + bool Rewind() override; + size_t ByteCount() const override { return window_start_; } bool HadError() const override { return error_; } private: - bool ConsumeWindow(const void** buffer, int* len); + DISALLOW_COPY_AND_ASSIGN(PngChunkFilter); + + bool ConsumeWindow(const void** buffer, size_t* len); android::StringPiece data_; size_t window_start_ = 0; size_t window_end_ = 0; bool error_ = false; - - DISALLOW_COPY_AND_ASSIGN(PngChunkFilter); }; /** diff --git a/tools/aapt2/compile/PngChunkFilter.cpp b/tools/aapt2/compile/PngChunkFilter.cpp index edec12317fdd..f9043b5a109a 100644 --- a/tools/aapt2/compile/PngChunkFilter.cpp +++ b/tools/aapt2/compile/PngChunkFilter.cpp @@ -71,16 +71,16 @@ static bool IsPngChunkWhitelisted(uint32_t type) { PngChunkFilter::PngChunkFilter(const StringPiece& data) : data_(data) { if (util::StartsWith(data_, kPngSignature)) { window_start_ = 0; - window_end_ = strlen(kPngSignature); + window_end_ = kPngSignatureSize; } else { error_ = true; } } -bool PngChunkFilter::ConsumeWindow(const void** buffer, int* len) { +bool PngChunkFilter::ConsumeWindow(const void** buffer, size_t* len) { if (window_start_ != window_end_) { // We have bytes to give from our window. - const int bytes_read = (int)(window_end_ - window_start_); + const size_t bytes_read = window_end_ - window_start_; *buffer = data_.data() + window_start_; *len = bytes_read; window_start_ = window_end_; @@ -89,7 +89,7 @@ bool PngChunkFilter::ConsumeWindow(const void** buffer, int* len) { return false; } -bool PngChunkFilter::Next(const void** buffer, int* len) { +bool PngChunkFilter::Next(const void** buffer, size_t* len) { if (error_) { return false; } @@ -113,16 +113,14 @@ bool PngChunkFilter::Next(const void** buffer, int* len) { // Verify the chunk length. const uint32_t chunk_len = Peek32LE(data_.data() + window_end_); - if (((uint64_t)chunk_len) + ((uint64_t)window_end_) + sizeof(uint32_t) > - data_.size()) { + if (((uint64_t)chunk_len) + ((uint64_t)window_end_) + sizeof(uint32_t) > data_.size()) { // Overflow. error_ = true; return false; } // Do we strip this chunk? - const uint32_t chunk_type = - Peek32LE(data_.data() + window_end_ + sizeof(uint32_t)); + const uint32_t chunk_type = Peek32LE(data_.data() + window_end_ + sizeof(uint32_t)); if (IsPngChunkWhitelisted(chunk_type)) { // Advance the window to include this chunk. window_end_ += kMinChunkHeaderSize + chunk_len; @@ -146,31 +144,19 @@ bool PngChunkFilter::Next(const void** buffer, int* len) { return false; } -void PngChunkFilter::BackUp(int count) { +void PngChunkFilter::BackUp(size_t count) { if (error_) { return; } window_start_ -= count; } -bool PngChunkFilter::Skip(int count) { +bool PngChunkFilter::Rewind() { if (error_) { return false; } - - const void* buffer; - int len; - while (count > 0) { - if (!Next(&buffer, &len)) { - return false; - } - if (len > count) { - BackUp(len - count); - count = 0; - } else { - count -= len; - } - } + window_start_ = 0; + window_end_ = kPngSignatureSize; return true; } diff --git a/tools/aapt2/compile/PngCrunch.cpp b/tools/aapt2/compile/PngCrunch.cpp index 3b46d8b4c782..ae98afcd3cc3 100644 --- a/tools/aapt2/compile/PngCrunch.cpp +++ b/tools/aapt2/compile/PngCrunch.cpp @@ -29,12 +29,7 @@ namespace aapt { -// Size in bytes of the PNG signature. -constexpr size_t kPngSignatureSize = 8u; - -/** - * Custom deleter that destroys libpng read and info structs. - */ +// Custom deleter that destroys libpng read and info structs. class PngReadStructDeleter { public: PngReadStructDeleter(png_structp read_ptr, png_infop info_ptr) @@ -51,9 +46,7 @@ class PngReadStructDeleter { DISALLOW_COPY_AND_ASSIGN(PngReadStructDeleter); }; -/** - * Custom deleter that destroys libpng write and info structs. - */ +// Custom deleter that destroys libpng write and info structs. class PngWriteStructDeleter { public: PngWriteStructDeleter(png_structp write_ptr, png_infop info_ptr) @@ -82,12 +75,11 @@ static void LogError(png_structp png_ptr, png_const_charp error_msg) { diag->Error(DiagMessage() << error_msg); } -static void ReadDataFromStream(png_structp png_ptr, png_bytep buffer, - png_size_t len) { +static void ReadDataFromStream(png_structp png_ptr, png_bytep buffer, png_size_t len) { io::InputStream* in = (io::InputStream*)png_get_io_ptr(png_ptr); const void* in_buffer; - int in_len; + size_t in_len; if (!in->Next(&in_buffer, &in_len)) { if (in->HadError()) { std::string err = in->GetError(); @@ -96,19 +88,18 @@ static void ReadDataFromStream(png_structp png_ptr, png_bytep buffer, return; } - const size_t bytes_read = std::min(static_cast<size_t>(in_len), len); + const size_t bytes_read = std::min(in_len, len); memcpy(buffer, in_buffer, bytes_read); - if (bytes_read != static_cast<size_t>(in_len)) { - in->BackUp(in_len - static_cast<int>(bytes_read)); + if (bytes_read != in_len) { + in->BackUp(in_len - bytes_read); } } -static void WriteDataToStream(png_structp png_ptr, png_bytep buffer, - png_size_t len) { +static void WriteDataToStream(png_structp png_ptr, png_bytep buffer, png_size_t len) { io::OutputStream* out = (io::OutputStream*)png_get_io_ptr(png_ptr); void* out_buffer; - int out_len; + size_t out_len; while (len > 0) { if (!out->Next(&out_buffer, &out_len)) { if (out->HadError()) { @@ -118,7 +109,7 @@ static void WriteDataToStream(png_structp png_ptr, png_bytep buffer, return; } - const size_t bytes_written = std::min(static_cast<size_t>(out_len), len); + const size_t bytes_written = std::min(out_len, len); memcpy(out_buffer, buffer, bytes_written); // Advance the input buffer. @@ -126,7 +117,7 @@ static void WriteDataToStream(png_structp png_ptr, png_bytep buffer, len -= bytes_written; // Advance the output buffer. - out_len -= static_cast<int>(bytes_written); + out_len -= bytes_written; } // If the entire output buffer wasn't used, backup. @@ -139,41 +130,35 @@ std::unique_ptr<Image> ReadPng(IAaptContext* context, io::InputStream* in) { // Read the first 8 bytes of the file looking for the PNG signature. // Bail early if it does not match. const png_byte* signature; - int buffer_size; + size_t buffer_size; if (!in->Next((const void**)&signature, &buffer_size)) { - context->GetDiagnostics()->Error( - DiagMessage() << android::base::SystemErrorCodeToString(errno)); + context->GetDiagnostics()->Error(DiagMessage() + << android::base::SystemErrorCodeToString(errno)); return {}; } - if (static_cast<size_t>(buffer_size) < kPngSignatureSize || - png_sig_cmp(signature, 0, kPngSignatureSize) != 0) { - context->GetDiagnostics()->Error( - DiagMessage() << "file signature does not match PNG signature"); + if (buffer_size < kPngSignatureSize || png_sig_cmp(signature, 0, kPngSignatureSize) != 0) { + context->GetDiagnostics()->Error(DiagMessage() + << "file signature does not match PNG signature"); return {}; } // Start at the beginning of the first chunk. - in->BackUp(buffer_size - static_cast<int>(kPngSignatureSize)); + in->BackUp(buffer_size - kPngSignatureSize); - // Create and initialize the png_struct with the default error and warning - // handlers. - // The header version is also passed in to ensure that this was built against - // the same + // Create and initialize the png_struct with the default error and warning handlers. + // The header version is also passed in to ensure that this was built against the same // version of libpng. - png_structp read_ptr = - png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); + png_structp read_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); if (read_ptr == nullptr) { - context->GetDiagnostics()->Error( - DiagMessage() << "failed to create libpng read png_struct"); + context->GetDiagnostics()->Error(DiagMessage() << "failed to create libpng read png_struct"); return {}; } // Create and initialize the memory for image header and data. png_infop info_ptr = png_create_info_struct(read_ptr); if (info_ptr == nullptr) { - context->GetDiagnostics()->Error( - DiagMessage() << "failed to create libpng read png_info"); + context->GetDiagnostics()->Error(DiagMessage() << "failed to create libpng read png_info"); png_destroy_read_struct(&read_ptr, nullptr, nullptr); return {}; } @@ -189,8 +174,7 @@ std::unique_ptr<Image> ReadPng(IAaptContext* context, io::InputStream* in) { } // Handle warnings ourselves via IDiagnostics. - png_set_error_fn(read_ptr, (png_voidp)context->GetDiagnostics(), LogError, - LogWarning); + png_set_error_fn(read_ptr, (png_voidp)context->GetDiagnostics(), LogError, LogWarning); // Set up the read functions which read from our custom data sources. png_set_read_fn(read_ptr, (png_voidp)in, ReadDataFromStream); @@ -203,8 +187,7 @@ std::unique_ptr<Image> ReadPng(IAaptContext* context, io::InputStream* in) { // Extract image meta-data from the various chunk headers. uint32_t width, height; - int bit_depth, color_type, interlace_method, compression_method, - filter_method; + int bit_depth, color_type, interlace_method, compression_method, filter_method; png_get_IHDR(read_ptr, info_ptr, &width, &height, &bit_depth, &color_type, &interlace_method, &compression_method, &filter_method); @@ -247,11 +230,9 @@ std::unique_ptr<Image> ReadPng(IAaptContext* context, io::InputStream* in) { // 9-patch uses int32_t to index images, so we cap the image dimensions to // something // that can always be represented by 9-patch. - if (width > std::numeric_limits<int32_t>::max() || - height > std::numeric_limits<int32_t>::max()) { - context->GetDiagnostics()->Error(DiagMessage() - << "PNG image dimensions are too large: " - << width << "x" << height); + if (width > std::numeric_limits<int32_t>::max() || height > std::numeric_limits<int32_t>::max()) { + context->GetDiagnostics()->Error( + DiagMessage() << "PNG image dimensions are too large: " << width << "x" << height); return {}; } @@ -263,8 +244,7 @@ std::unique_ptr<Image> ReadPng(IAaptContext* context, io::InputStream* in) { CHECK(row_bytes == 4 * width); // RGBA // Allocate one large block to hold the image. - output_image->data = - std::unique_ptr<uint8_t[]>(new uint8_t[height * row_bytes]); + output_image->data = std::unique_ptr<uint8_t[]>(new uint8_t[height * row_bytes]); // Create an array of rows that index into the data block. output_image->rows = std::unique_ptr<uint8_t* []>(new uint8_t*[height]); @@ -281,19 +261,13 @@ std::unique_ptr<Image> ReadPng(IAaptContext* context, io::InputStream* in) { return output_image; } -/** - * Experimentally chosen constant to be added to the overhead of using color - * type - * PNG_COLOR_TYPE_PALETTE to account for the uncompressability of the palette - * chunk. - * Without this, many small PNGs encoded with palettes are larger after - * compression than - * the same PNGs encoded as RGBA. - */ +// Experimentally chosen constant to be added to the overhead of using color type +// PNG_COLOR_TYPE_PALETTE to account for the uncompressability of the palette chunk. +// Without this, many small PNGs encoded with palettes are larger after compression than +// the same PNGs encoded as RGBA. constexpr static const size_t kPaletteOverheadConstant = 1024u * 10u; -// Pick a color type by which to encode the image, based on which color type -// will take +// Pick a color type by which to encode the image, based on which color type will take // the least amount of disk space. // // 9-patch images traditionally have not been encoded with palettes. @@ -372,20 +346,17 @@ static int PickColorType(int32_t width, int32_t height, bool grayscale, return PNG_COLOR_TYPE_RGBA; } -// Assigns indices to the color and alpha palettes, encodes them, and then -// invokes +// Assigns indices to the color and alpha palettes, encodes them, and then invokes // png_set_PLTE/png_set_tRNS. // This must be done before writing image data. -// Image data must be transformed to use the indices assigned within the -// palette. +// Image data must be transformed to use the indices assigned within the palette. static void WritePalette(png_structp write_ptr, png_infop write_info_ptr, std::unordered_map<uint32_t, int>* color_palette, std::unordered_set<uint32_t>* alpha_palette) { CHECK(color_palette->size() <= 256); CHECK(alpha_palette->size() <= 256); - // Populate the PNG palette struct and assign indices to the color - // palette. + // Populate the PNG palette struct and assign indices to the color palette. // Colors in the alpha palette should have smaller indices. // This will ensure that we can truncate the alpha palette if it is @@ -403,13 +374,11 @@ static void WritePalette(png_structp write_ptr, png_infop write_info_ptr, } // Create the PNG color palette struct. - auto color_palette_bytes = - std::unique_ptr<png_color[]>(new png_color[color_palette->size()]); + auto color_palette_bytes = std::unique_ptr<png_color[]>(new png_color[color_palette->size()]); std::unique_ptr<png_byte[]> alpha_palette_bytes; if (!alpha_palette->empty()) { - alpha_palette_bytes = - std::unique_ptr<png_byte[]>(new png_byte[alpha_palette->size()]); + alpha_palette_bytes = std::unique_ptr<png_byte[]>(new png_byte[alpha_palette->size()]); } for (const auto& entry : *color_palette) { @@ -433,23 +402,20 @@ static void WritePalette(png_structp write_ptr, png_infop write_info_ptr, // The bytes get copied here, so it is safe to release color_palette_bytes at // the end of function // scope. - png_set_PLTE(write_ptr, write_info_ptr, color_palette_bytes.get(), - color_palette->size()); + png_set_PLTE(write_ptr, write_info_ptr, color_palette_bytes.get(), color_palette->size()); if (alpha_palette_bytes) { - png_set_tRNS(write_ptr, write_info_ptr, alpha_palette_bytes.get(), - alpha_palette->size(), nullptr); + png_set_tRNS(write_ptr, write_info_ptr, alpha_palette_bytes.get(), alpha_palette->size(), + nullptr); } } // Write the 9-patch custom PNG chunks to write_info_ptr. This must be done -// before -// writing image data. +// before writing image data. static void WriteNinePatch(png_structp write_ptr, png_infop write_info_ptr, const NinePatch* nine_patch) { // The order of the chunks is important. - // 9-patch code in older platforms expects the 9-patch chunk to - // be last. + // 9-patch code in older platforms expects the 9-patch chunk to be last. png_unknown_chunk unknown_chunks[3]; memset(unknown_chunks, 0, sizeof(unknown_chunks)); @@ -475,8 +441,7 @@ static void WriteNinePatch(png_structp write_ptr, png_infop write_info_ptr, index++; } - std::unique_ptr<uint8_t[]> serialized_nine_patch = - nine_patch->SerializeBase(&chunk_len); + std::unique_ptr<uint8_t[]> serialized_nine_patch = nine_patch->SerializeBase(&chunk_len); strcpy((char*)unknown_chunks[index].name, "npTc"); unknown_chunks[index].size = chunk_len; unknown_chunks[index].data = (png_bytep)serialized_nine_patch.get(); @@ -497,22 +462,18 @@ bool WritePng(IAaptContext* context, const Image* image, const PngOptions& options) { // Create and initialize the write png_struct with the default error and // warning handlers. - // The header version is also passed in to ensure that this was built against - // the same + // The header version is also passed in to ensure that this was built against the same // version of libpng. - png_structp write_ptr = - png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); + png_structp write_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); if (write_ptr == nullptr) { - context->GetDiagnostics()->Error( - DiagMessage() << "failed to create libpng write png_struct"); + context->GetDiagnostics()->Error(DiagMessage() << "failed to create libpng write png_struct"); return false; } // Allocate memory to store image header data. png_infop write_info_ptr = png_create_info_struct(write_ptr); if (write_info_ptr == nullptr) { - context->GetDiagnostics()->Error( - DiagMessage() << "failed to create libpng write png_info"); + context->GetDiagnostics()->Error(DiagMessage() << "failed to create libpng write png_info"); png_destroy_write_struct(&write_ptr, nullptr); return false; } @@ -527,8 +488,7 @@ bool WritePng(IAaptContext* context, const Image* image, } // Handle warnings with our IDiagnostics. - png_set_error_fn(write_ptr, (png_voidp)context->GetDiagnostics(), LogError, - LogWarning); + png_set_error_fn(write_ptr, (png_voidp)context->GetDiagnostics(), LogError, LogWarning); // Set up the write functions which write to our custom data sources. png_set_write_fn(write_ptr, (png_voidp)out, WriteDataToStream, nullptr); @@ -599,8 +559,7 @@ bool WritePng(IAaptContext* context, const Image* image, context->GetDiagnostics()->Note(msg); } - const bool convertible_to_grayscale = - max_gray_deviation <= options.grayscale_tolerance; + const bool convertible_to_grayscale = max_gray_deviation <= options.grayscale_tolerance; const int new_color_type = PickColorType( image->width, image->height, grayscale, convertible_to_grayscale, @@ -715,15 +674,12 @@ bool WritePng(IAaptContext* context, const Image* image, } png_write_row(write_ptr, out_row.get()); } - } else if (new_color_type == PNG_COLOR_TYPE_RGB || - new_color_type == PNG_COLOR_TYPE_RGBA) { + } else if (new_color_type == PNG_COLOR_TYPE_RGB || new_color_type == PNG_COLOR_TYPE_RGBA) { const size_t bpp = new_color_type == PNG_COLOR_TYPE_RGB ? 3 : 4; if (needs_to_zero_rgb_channels_of_transparent_pixels) { // The source RGBA data can't be used as-is, because we need to zero out - // the RGB - // values of transparent pixels. - auto out_row = - std::unique_ptr<png_byte[]>(new png_byte[image->width * bpp]); + // the RGB values of transparent pixels. + auto out_row = std::unique_ptr<png_byte[]>(new png_byte[image->width * bpp]); for (int32_t y = 0; y < image->height; y++) { png_const_bytep in_row = image->rows[y]; @@ -747,8 +703,7 @@ bool WritePng(IAaptContext* context, const Image* image, } } else { // The source image can be used as-is, just tell libpng whether or not to - // ignore - // the alpha channel. + // ignore the alpha channel. if (new_color_type == PNG_COLOR_TYPE_RGB) { // Delete the extraneous alpha values that we appended to our buffer // when reading the original values. diff --git a/tools/aapt2/flatten/Archive.cpp b/tools/aapt2/flatten/Archive.cpp index 5c96a4dbd4e2..826f91b4a2fd 100644 --- a/tools/aapt2/flatten/Archive.cpp +++ b/tools/aapt2/flatten/Archive.cpp @@ -21,6 +21,7 @@ #include <string> #include <vector> +#include "android-base/errors.h" #include "android-base/macros.h" #include "androidfw/StringPiece.h" #include "ziparchive/zip_writer.h" @@ -37,14 +38,14 @@ class DirectoryWriter : public IArchiveWriter { public: DirectoryWriter() = default; - bool Open(IDiagnostics* diag, const StringPiece& out_dir) { + bool Open(const StringPiece& out_dir) { dir_ = out_dir.to_string(); file::FileType type = file::GetFileType(dir_); if (type == file::FileType::kNonexistant) { - diag->Error(DiagMessage() << "directory " << dir_ << " does not exist"); + error_ = "directory does not exist"; return false; } else if (type != file::FileType::kDirectory) { - diag->Error(DiagMessage() << dir_ << " is not a directory"); + error_ = "not a directory"; return false; } return true; @@ -61,27 +62,19 @@ class DirectoryWriter : public IArchiveWriter { file_ = {fopen(full_path.data(), "wb"), fclose}; if (!file_) { + error_ = android::base::SystemErrorCodeToString(errno); return false; } return true; } - bool WriteEntry(const BigBuffer& buffer) override { + bool Write(const void* data, int len) override { if (!file_) { return false; } - for (const BigBuffer::Block& b : buffer) { - if (fwrite(b.buffer.get(), 1, b.size, file_.get()) != b.size) { - file_.reset(nullptr); - return false; - } - } - return true; - } - - bool WriteEntry(const void* data, size_t len) override { - if (fwrite(data, 1, len, file_.get()) != len) { + if (fwrite(data, 1, len, file_.get()) != static_cast<size_t>(len)) { + error_ = android::base::SystemErrorCodeToString(errno); file_.reset(nullptr); return false; } @@ -96,22 +89,41 @@ class DirectoryWriter : public IArchiveWriter { return true; } + bool WriteFile(const StringPiece& path, uint32_t flags, io::InputStream* in) override { + if (!StartEntry(path, flags)) { + return false; + } + + const void* data = nullptr; + size_t len = 0; + while (in->Next(&data, &len)) { + if (!Write(data, static_cast<int>(len))) { + return false; + } + } + return !in->HadError(); + } + + bool HadError() const override { return !error_.empty(); } + + std::string GetError() const override { return error_; } + private: DISALLOW_COPY_AND_ASSIGN(DirectoryWriter); std::string dir_; std::unique_ptr<FILE, decltype(fclose)*> file_ = {nullptr, fclose}; + std::string error_; }; class ZipFileWriter : public IArchiveWriter { public: ZipFileWriter() = default; - bool Open(IDiagnostics* diag, const StringPiece& path) { + bool Open(const StringPiece& path) { file_ = {fopen(path.data(), "w+b"), fclose}; if (!file_) { - diag->Error(DiagMessage() << "failed to Open " << path << ": " - << strerror(errno)); + error_ = android::base::SystemErrorCodeToString(errno); return false; } writer_ = util::make_unique<ZipWriter>(file_.get()); @@ -134,37 +146,83 @@ class ZipFileWriter : public IArchiveWriter { int32_t result = writer_->StartEntry(path.data(), zip_flags); if (result != 0) { + error_ = ZipWriter::ErrorCodeString(result); return false; } return true; } - bool WriteEntry(const void* data, size_t len) override { + bool Write(const void* data, int len) override { int32_t result = writer_->WriteBytes(data, len); if (result != 0) { + error_ = ZipWriter::ErrorCodeString(result); return false; } return true; } - bool WriteEntry(const BigBuffer& buffer) override { - for (const BigBuffer::Block& b : buffer) { - int32_t result = writer_->WriteBytes(b.buffer.get(), b.size); - if (result != 0) { - return false; - } - } - return true; - } - bool FinishEntry() override { int32_t result = writer_->FinishEntry(); if (result != 0) { + error_ = ZipWriter::ErrorCodeString(result); return false; } return true; } + bool WriteFile(const StringPiece& path, uint32_t flags, io::InputStream* in) override { + while (true) { + if (!StartEntry(path, flags)) { + return false; + } + + const void* data = nullptr; + size_t len = 0; + while (in->Next(&data, &len)) { + if (!Write(data, static_cast<int>(len))) { + return false; + } + } + + if (in->HadError()) { + return false; + } + + if (!FinishEntry()) { + return false; + } + + // Check to see if the file was compressed enough. This is preserving behavior of AAPT. + if ((flags & ArchiveEntry::kCompress) != 0 && in->CanRewind()) { + ZipWriter::FileEntry last_entry; + int32_t result = writer_->GetLastEntry(&last_entry); + CHECK(result == 0); + if (last_entry.compressed_size + (last_entry.compressed_size / 10) > + last_entry.uncompressed_size) { + // The file was not compressed enough, rewind and store it uncompressed. + if (!in->Rewind()) { + // Well we tried, may as well keep what we had. + return true; + } + + int32_t result = writer_->DiscardLastEntry(); + if (result != 0) { + error_ = ZipWriter::ErrorCodeString(result); + return false; + } + flags &= ~ArchiveEntry::kCompress; + + continue; + } + } + return true; + } + } + + bool HadError() const override { return !error_.empty(); } + + std::string GetError() const override { return error_; } + virtual ~ZipFileWriter() { if (writer_) { writer_->Finish(); @@ -176,24 +234,26 @@ class ZipFileWriter : public IArchiveWriter { std::unique_ptr<FILE, decltype(fclose)*> file_ = {nullptr, fclose}; std::unique_ptr<ZipWriter> writer_; + std::string error_; }; } // namespace -std::unique_ptr<IArchiveWriter> CreateDirectoryArchiveWriter( - IDiagnostics* diag, const StringPiece& path) { - std::unique_ptr<DirectoryWriter> writer = - util::make_unique<DirectoryWriter>(); - if (!writer->Open(diag, path)) { +std::unique_ptr<IArchiveWriter> CreateDirectoryArchiveWriter(IDiagnostics* diag, + const StringPiece& path) { + std::unique_ptr<DirectoryWriter> writer = util::make_unique<DirectoryWriter>(); + if (!writer->Open(path)) { + diag->Error(DiagMessage(path) << writer->GetError()); return {}; } return std::move(writer); } -std::unique_ptr<IArchiveWriter> CreateZipFileArchiveWriter( - IDiagnostics* diag, const StringPiece& path) { +std::unique_ptr<IArchiveWriter> CreateZipFileArchiveWriter(IDiagnostics* diag, + const StringPiece& path) { std::unique_ptr<ZipFileWriter> writer = util::make_unique<ZipFileWriter>(); - if (!writer->Open(diag, path)) { + if (!writer->Open(path)) { + diag->Error(DiagMessage(path) << writer->GetError()); return {}; } return std::move(writer); diff --git a/tools/aapt2/flatten/Archive.h b/tools/aapt2/flatten/Archive.h index f0681bdd1167..4ee4ce71a5c5 100644 --- a/tools/aapt2/flatten/Archive.h +++ b/tools/aapt2/flatten/Archive.h @@ -26,6 +26,7 @@ #include "google/protobuf/io/zero_copy_stream_impl_lite.h" #include "Diagnostics.h" +#include "io/Io.h" #include "util/BigBuffer.h" #include "util/Files.h" @@ -42,19 +43,31 @@ struct ArchiveEntry { size_t uncompressed_size; }; -class IArchiveWriter : public google::protobuf::io::CopyingOutputStream { +class IArchiveWriter : public ::google::protobuf::io::CopyingOutputStream { public: virtual ~IArchiveWriter() = default; + virtual bool WriteFile(const android::StringPiece& path, uint32_t flags, io::InputStream* in) = 0; + + // Starts a new entry and allows caller to write bytes to it sequentially. + // Only use StartEntry if code you do not control needs to write to a CopyingOutputStream. + // Prefer WriteFile instead of manually calling StartEntry/FinishEntry. virtual bool StartEntry(const android::StringPiece& path, uint32_t flags) = 0; - virtual bool WriteEntry(const BigBuffer& buffer) = 0; - virtual bool WriteEntry(const void* data, size_t len) = 0; + + // Called to finish writing an entry previously started by StartEntry. + // Prefer WriteFile instead of manually calling StartEntry/FinishEntry. virtual bool FinishEntry() = 0; - // CopyingOutputStream implementations. - bool Write(const void* buffer, int size) override { - return WriteEntry(buffer, size); - } + // CopyingOutputStream implementation that allows sequential writes to this archive. Only + // valid between calls to StartEntry and FinishEntry. + virtual bool Write(const void* buffer, int size) = 0; + + // Returns true if there was an error writing to the archive. + // The resulting error message can be retrieved from GetError(). + virtual bool HadError() const = 0; + + // Returns the error message if HadError() returns true. + virtual std::string GetError() const = 0; }; std::unique_ptr<IArchiveWriter> CreateDirectoryArchiveWriter(IDiagnostics* diag, diff --git a/tools/aapt2/io/BigBufferInputStream.h b/tools/aapt2/io/BigBufferInputStream.h new file mode 100644 index 000000000000..92612c744aae --- /dev/null +++ b/tools/aapt2/io/BigBufferInputStream.h @@ -0,0 +1,56 @@ +/* + * 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_BIGBUFFERINPUTSTREAM_H +#define AAPT_IO_BIGBUFFERINPUTSTREAM_H + +#include "io/Io.h" +#include "util/BigBuffer.h" + +namespace aapt { +namespace io { + +class BigBufferInputStream : public InputStream { + public: + inline explicit BigBufferInputStream(const BigBuffer* buffer) + : buffer_(buffer), iter_(buffer->begin()) {} + virtual ~BigBufferInputStream() = default; + + bool Next(const void** data, size_t* size) override; + + void BackUp(size_t count) override; + + bool CanRewind() const override; + + bool Rewind() override; + + size_t ByteCount() const override; + + bool HadError() const override; + + private: + DISALLOW_COPY_AND_ASSIGN(BigBufferInputStream); + + const BigBuffer* buffer_; + BigBuffer::const_iterator iter_; + size_t offset_ = 0; + size_t bytes_read_ = 0; +}; + +} // namespace io +} // namespace aapt + +#endif // AAPT_IO_BIGBUFFERINPUTSTREAM_H diff --git a/tools/aapt2/io/BigBufferOutputStream.h b/tools/aapt2/io/BigBufferOutputStream.h new file mode 100644 index 000000000000..95113bc2132c --- /dev/null +++ b/tools/aapt2/io/BigBufferOutputStream.h @@ -0,0 +1,48 @@ +/* + * 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/BigBufferStreams.cpp new file mode 100644 index 000000000000..eb99033e1cbe --- /dev/null +++ b/tools/aapt2/io/BigBufferStreams.cpp @@ -0,0 +1,86 @@ +/* + * 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 "io/BigBufferInputStream.h" +#include "io/BigBufferOutputStream.h" + +namespace aapt { +namespace io { + +// +// BigBufferInputStream +// + +bool BigBufferInputStream::Next(const void** data, size_t* size) { + if (iter_ == buffer_->end()) { + return false; + } + + if (offset_ == iter_->size) { + ++iter_; + if (iter_ == buffer_->end()) { + return false; + } + offset_ = 0; + } + + *data = iter_->buffer.get() + offset_; + *size = iter_->size - offset_; + bytes_read_ += iter_->size - offset_; + offset_ = iter_->size; + return true; +} + +void BigBufferInputStream::BackUp(size_t count) { + if (count > offset_) { + bytes_read_ -= offset_; + offset_ = 0; + } else { + offset_ -= count; + bytes_read_ -= count; + } +} + +bool BigBufferInputStream::CanRewind() const { return true; } + +bool BigBufferInputStream::Rewind() { + iter_ = buffer_->begin(); + offset_ = 0; + bytes_read_ = 0; + return true; +} + +size_t BigBufferInputStream::ByteCount() const { return bytes_read_; } + +bool BigBufferInputStream::HadError() const { return false; } + +// +// BigBufferOutputStream +// + +bool BigBufferOutputStream::Next(void** data, size_t* size) { + *data = buffer_->NextBlock(size); + return true; +} + +void BigBufferOutputStream::BackUp(size_t count) { buffer_->BackUp(count); } + +size_t BigBufferOutputStream::ByteCount() const { return buffer_->size(); } + +bool BigBufferOutputStream::HadError() const { return false; } + +} // namespace io +} // namespace aapt diff --git a/tools/aapt2/io/Data.h b/tools/aapt2/io/Data.h index fdc044d86e5a..09dc7ea5b1c4 100644 --- a/tools/aapt2/io/Data.h +++ b/tools/aapt2/io/Data.h @@ -22,14 +22,13 @@ #include "android-base/macros.h" #include "utils/FileMap.h" +#include "io/Io.h" + namespace aapt { namespace io { -/** - * Interface for a block of contiguous memory. An instance of this interface - * owns the data. - */ -class IData { +// Interface for a block of contiguous memory. An instance of this interface owns the data. +class IData : public InputStream { public: virtual ~IData() = default; @@ -40,7 +39,8 @@ class IData { class DataSegment : public IData { public: explicit DataSegment(std::unique_ptr<IData> data, size_t offset, size_t len) - : data_(std::move(data)), offset_(offset), len_(len) {} + : data_(std::move(data)), offset_(offset), len_(len), next_read_(offset) {} + virtual ~DataSegment() = default; const void* data() const override { return static_cast<const uint8_t*>(data_->data()) + offset_; @@ -48,63 +48,163 @@ class DataSegment : public IData { size_t size() const override { return len_; } + bool Next(const void** data, size_t* size) override { + if (next_read_ == offset_ + len_) { + return false; + } + *data = static_cast<const uint8_t*>(data_->data()) + next_read_; + *size = len_ - (next_read_ - offset_); + next_read_ = offset_ + len_; + return true; + } + + void BackUp(size_t count) override { + if (count > next_read_ - offset_) { + next_read_ = offset_; + } else { + next_read_ -= count; + } + } + + bool CanRewind() const override { return true; } + + bool Rewind() override { + next_read_ = offset_; + return true; + } + + size_t ByteCount() const override { return next_read_ - offset_; } + + bool HadError() const override { return false; } + private: DISALLOW_COPY_AND_ASSIGN(DataSegment); std::unique_ptr<IData> data_; size_t offset_; size_t len_; + size_t next_read_; }; -/** - * Implementation of IData that exposes a memory mapped file. The mmapped file - * is owned by this - * object. - */ +// Implementation of IData that exposes a memory mapped file. +// The mmapped file is owned by this object. class MmappedData : public IData { public: - explicit MmappedData(android::FileMap&& map) - : map_(std::forward<android::FileMap>(map)) {} + explicit MmappedData(android::FileMap&& map) : map_(std::forward<android::FileMap>(map)) {} + virtual ~MmappedData() = default; const void* data() const override { return map_.getDataPtr(); } size_t size() const override { return map_.getDataLength(); } + bool Next(const void** data, size_t* size) override { + if (next_read_ == map_.getDataLength()) { + return false; + } + *data = reinterpret_cast<const uint8_t*>(map_.getDataPtr()) + next_read_; + *size = map_.getDataLength() - next_read_; + next_read_ = map_.getDataLength(); + return true; + } + + void BackUp(size_t count) override { + if (count > next_read_) { + next_read_ = 0; + } else { + next_read_ -= count; + } + } + + bool CanRewind() const override { return true; } + + bool Rewind() override { + next_read_ = 0; + return true; + } + + size_t ByteCount() const override { return next_read_; } + + bool HadError() const override { return false; } + private: + DISALLOW_COPY_AND_ASSIGN(MmappedData); + android::FileMap map_; + size_t next_read_ = 0; }; -/** - * Implementation of IData that exposes a block of memory that was malloc'ed - * (new'ed). The - * memory is owned by this object. - */ +// Implementation of IData that exposes a block of memory that was malloc'ed (new'ed). +// The memory is owned by this object. class MallocData : public IData { public: MallocData(std::unique_ptr<const uint8_t[]> data, size_t size) : data_(std::move(data)), size_(size) {} + virtual ~MallocData() = default; const void* data() const override { return data_.get(); } size_t size() const override { return size_; } + bool Next(const void** data, size_t* size) override { + if (next_read_ == size_) { + return false; + } + *data = data_.get() + next_read_; + *size = size_ - next_read_; + next_read_ = size_; + return true; + } + + void BackUp(size_t count) override { + if (count > next_read_) { + next_read_ = 0; + } else { + next_read_ -= count; + } + } + + bool CanRewind() const override { return true; } + + bool Rewind() override { + next_read_ = 0; + return true; + } + + size_t ByteCount() const override { return next_read_; } + + bool HadError() const override { return false; } + private: + DISALLOW_COPY_AND_ASSIGN(MallocData); + std::unique_ptr<const uint8_t[]> data_; size_t size_; + size_t next_read_ = 0; }; -/** - * When mmap fails because the file has length 0, we use the EmptyData to - * simulate data of length 0. - */ +// When mmap fails because the file has length 0, we use the EmptyData to simulate data of length 0. class EmptyData : public IData { public: + virtual ~EmptyData() = default; + const void* data() const override { static const uint8_t d = 0; return &d; } size_t size() const override { return 0u; } + + bool Next(const void** /*data*/, size_t* /*size*/) override { return false; } + + void BackUp(size_t /*count*/) override {} + + bool CanRewind() const override { return true; } + + bool Rewind() override { return true; } + + size_t ByteCount() const override { return 0u; } + + bool HadError() const override { return false; } }; } // namespace io diff --git a/tools/aapt2/io/File.h b/tools/aapt2/io/File.h index 1ef9743f65cf..7ef6d88c1e3b 100644 --- a/tools/aapt2/io/File.h +++ b/tools/aapt2/io/File.h @@ -30,40 +30,27 @@ namespace aapt { namespace io { -/** - * Interface for a file, which could be a real file on the file system, or a - * file inside - * a ZIP archive. - */ +// Interface for a file, which could be a real file on the file system, or a +// file inside a ZIP archive. class IFile { public: virtual ~IFile() = default; - /** - * Open the file and return it as a block of contiguous memory. How this - * occurs is - * implementation dependent. For example, if this is a file on the file - * system, it may - * simply mmap the contents. If this file represents a compressed file in a - * ZIP archive, - * it may need to inflate it to memory, incurring a copy. - * - * Returns nullptr on failure. - */ + // Open the file and return it as a block of contiguous memory. How this + // occurs is implementation dependent. For example, if this is a file on the file + // system, it may simply mmap the contents. If this file represents a compressed file in a + // ZIP archive, it may need to inflate it to memory, incurring a copy. + // Returns nullptr on failure. virtual std::unique_ptr<IData> OpenAsData() = 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. - */ + // 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. virtual const Source& GetSource() const = 0; IFile* CreateFileSegment(size_t offset, size_t len); - /** Returns whether the file was compressed before it was stored in memory. */ + // Returns whether the file was compressed before it was stored in memory. virtual bool WasCompressed() { return false; } @@ -77,10 +64,7 @@ class IFile { std::list<std::unique_ptr<IFile>> segments_; }; -/** - * An IFile that wraps an underlying IFile but limits it to a subsection of that - * file. - */ +// An IFile that wraps an underlying IFile but limits it to a subsection of that file. class FileSegment : public IFile { public: explicit FileSegment(IFile* file, size_t offset, size_t len) @@ -106,11 +90,8 @@ class IFileCollectionIterator { virtual IFile* Next() = 0; }; -/** - * Interface for a collection of files, all of which share a common source. That - * source may - * simply be the filesystem, or a ZIP archive. - */ +// Interface for a collection of files, all of which share a common source. That source may +// simply be the filesystem, or a ZIP archive. class IFileCollection { public: virtual ~IFileCollection() = default; diff --git a/tools/aapt2/io/Io.cpp b/tools/aapt2/io/Io.cpp index cab4b65f2f5a..f5c5737cb149 100644 --- a/tools/aapt2/io/Io.cpp +++ b/tools/aapt2/io/Io.cpp @@ -16,7 +16,6 @@ #include "io/Io.h" -#include <algorithm> #include <cstring> namespace aapt { @@ -24,15 +23,15 @@ namespace io { bool Copy(OutputStream* out, InputStream* in) { const void* in_buffer; - int in_len; + size_t in_len; while (in->Next(&in_buffer, &in_len)) { void* out_buffer; - int out_len; + size_t out_len; if (!out->Next(&out_buffer, &out_len)) { return !out->HadError(); } - const int bytes_to_copy = std::min(in_len, out_len); + const size_t bytes_to_copy = in_len < out_len ? in_len : out_len; memcpy(out_buffer, in_buffer, bytes_to_copy); out->BackUp(out_len - bytes_to_copy); in->BackUp(in_len - bytes_to_copy); diff --git a/tools/aapt2/io/Io.h b/tools/aapt2/io/Io.h index 33cdc7bbe498..2a34d4dd442d 100644 --- a/tools/aapt2/io/Io.h +++ b/tools/aapt2/io/Io.h @@ -19,42 +19,76 @@ #include <string> -#include "google/protobuf/io/zero_copy_stream_impl_lite.h" - namespace aapt { namespace io { -/** - * InputStream interface that inherits from protobuf's ZeroCopyInputStream, - * but adds error handling methods to better report issues. - * - * The code style here matches the protobuf style. - */ -class InputStream : public ::google::protobuf::io::ZeroCopyInputStream { +// InputStream interface that mimics protobuf's ZeroCopyInputStream, +// with added error handling methods to better report issues. +class InputStream { public: + virtual ~InputStream() = default; + + // Returns a chunk of data for reading. data and size must not be nullptr. + // Returns true so long as there is more data to read, returns false if an error occurred + // or no data remains. If an error occurred, check HadError(). + // The stream owns the buffer returned from this method and the buffer is invalidated + // anytime another mutable method is called. + virtual bool Next(const void** data, size_t* size) = 0; + + // Backup count bytes, where count is smaller or equal to the size of the last buffer returned + // from Next(). + // Useful when the last block returned from Next() wasn't fully read. + virtual void BackUp(size_t count) = 0; + + // Returns true if this InputStream can rewind. If so, Rewind() can be called. + virtual bool CanRewind() const { return false; }; + + // Rewinds the stream to the beginning so it can be read again. + // Returns true if the rewind succeeded. + // This does nothing if CanRewind() returns false. + virtual bool Rewind() { return false; } + + // Returns the number of bytes that have been read from the stream. + virtual size_t ByteCount() const = 0; + + // Returns an error message if HadError() returned true. virtual std::string GetError() const { return {}; } + // Returns true if an error occurred. Errors are permanent. virtual bool HadError() const = 0; }; -/** - * OutputStream interface that inherits from protobuf's ZeroCopyOutputStream, - * but adds error handling methods to better report issues. - * - * The code style here matches the protobuf style. - */ -class OutputStream : public ::google::protobuf::io::ZeroCopyOutputStream { +// OutputStream interface that mimics protobuf's ZeroCopyOutputStream, +// with added error handling methods to better report issues. +class OutputStream { public: + virtual ~OutputStream() = default; + + // Returns a buffer to which data can be written to. The data written to this buffer will + // eventually be written to the stream. Call BackUp() if the data written doesn't occupy the + // entire buffer. + // Return false if there was an error. + // The stream owns the buffer returned from this method and the buffer is invalidated + // anytime another mutable method is called. + virtual bool Next(void** data, size_t* size) = 0; + + // Backup count bytes, where count is smaller or equal to the size of the last buffer returned + // from Next(). + // Useful for when the last block returned from Next() wasn't fully written to. + virtual void BackUp(size_t count) = 0; + + // Returns the number of bytes that have been written to the stream. + virtual size_t ByteCount() const = 0; + + // Returns an error message if HadError() returned true. virtual std::string GetError() const { return {}; } + // Returns true if an error occurred. Errors are permanent. virtual bool HadError() const = 0; }; -/** - * Copies the data from in to out. Returns true if there was no error. - * If there was an error, check the individual streams' HadError/GetError - * methods. - */ +// 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); } // namespace io diff --git a/tools/aapt2/link/Link.cpp b/tools/aapt2/link/Link.cpp index 1b4d5bb4f6e1..7f715895e282 100644 --- a/tools/aapt2/link/Link.cpp +++ b/tools/aapt2/link/Link.cpp @@ -38,6 +38,7 @@ #include "flatten/Archive.h" #include "flatten/TableFlattener.h" #include "flatten/XmlFlattener.h" +#include "io/BigBufferInputStream.h" #include "io/FileSystem.h" #include "io/ZipArchive.h" #include "java/JavaClassGenerator.h" @@ -168,34 +169,57 @@ class LinkContext : public IAaptContext { int min_sdk_version_ = 0; }; +static bool CopyInputStreamToArchive(io::InputStream* in, const std::string& out_path, + uint32_t compression_flags, IArchiveWriter* writer, + IAaptContext* context) { + if (context->IsVerbose()) { + context->GetDiagnostics()->Note(DiagMessage() << "writing " << out_path << " to archive"); + } + + if (!writer->WriteFile(out_path, compression_flags, in)) { + context->GetDiagnostics()->Error(DiagMessage() << "failed to write " << out_path + << " to archive: " << writer->GetError()); + return false; + } + return true; +} + static bool CopyFileToArchive(io::IFile* file, const std::string& out_path, uint32_t compression_flags, IArchiveWriter* writer, IAaptContext* context) { std::unique_ptr<io::IData> data = file->OpenAsData(); if (!data) { - context->GetDiagnostics()->Error(DiagMessage(file->GetSource()) - << "failed to open file"); + context->GetDiagnostics()->Error(DiagMessage(file->GetSource()) << "failed to open file"); return false; } + return CopyInputStreamToArchive(data.get(), out_path, compression_flags, writer, context); +} - const uint8_t* buffer = reinterpret_cast<const uint8_t*>(data->data()); - const size_t buffer_size = data->size(); - +static bool CopyProtoToArchive(::google::protobuf::MessageLite* proto_msg, + const std::string& out_path, uint32_t compression_flags, + IArchiveWriter* writer, IAaptContext* context) { if (context->IsVerbose()) { - context->GetDiagnostics()->Note(DiagMessage() << "writing " << out_path - << " to archive"); + context->GetDiagnostics()->Note(DiagMessage() << "writing " << out_path << " to archive"); } if (writer->StartEntry(out_path, compression_flags)) { - if (writer->WriteEntry(buffer, buffer_size)) { - if (writer->FinishEntry()) { - return true; + // Make sure CopyingOutputStreamAdaptor is deleted before we call writer->FinishEntry(). + { + // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream interface. + ::google::protobuf::io::CopyingOutputStreamAdaptor adaptor(writer); + if (!proto_msg->SerializeToZeroCopyStream(&adaptor)) { + context->GetDiagnostics()->Error(DiagMessage() + << "failed to write " << out_path << " to archive"); + return false; } } - } - context->GetDiagnostics()->Error(DiagMessage() << "failed to write file " - << out_path); + if (writer->FinishEntry()) { + return true; + } + } + context->GetDiagnostics()->Error(DiagMessage() << "failed to write " << out_path + << " to archive: " << writer->GetError()); return false; } @@ -221,16 +245,9 @@ static bool FlattenXml(xml::XmlResource* xml_res, const StringPiece& path, context->GetDiagnostics()->Note(msg); } - if (writer->StartEntry(path, ArchiveEntry::kCompress)) { - if (writer->WriteEntry(buffer)) { - if (writer->FinishEntry()) { - return true; - } - } - } - context->GetDiagnostics()->Error(DiagMessage() << "failed to write " << path - << " to archive"); - return false; + io::BigBufferInputStream input_stream(&buffer); + return CopyInputStreamToArchive(&input_stream, path.to_string(), ArchiveEntry::kCompress, writer, + context); } static std::unique_ptr<ResourceTable> LoadTableFromPb(const Source& source, @@ -243,8 +260,7 @@ static std::unique_ptr<ResourceTable> LoadTableFromPb(const Source& source, return {}; } - std::unique_ptr<ResourceTable> table = - DeserializeTableFromPb(pb_table, source, diag); + std::unique_ptr<ResourceTable> table = DeserializeTableFromPb(pb_table, source, diag); if (!table) { return {}; } @@ -898,49 +914,18 @@ class LinkCommand { 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; } - if (writer->StartEntry("resources.arsc", ArchiveEntry::kAlign)) { - if (writer->WriteEntry(buffer)) { - if (writer->FinishEntry()) { - return true; - } - } - } - - context_->GetDiagnostics()->Error( - DiagMessage() << "failed to write resources.arsc to archive"); - return false; + io::BigBufferInputStream input_stream(&buffer); + return CopyInputStreamToArchive(&input_stream, "resources.arsc", ArchiveEntry::kAlign, writer, + context_); } bool FlattenTableToPb(ResourceTable* table, IArchiveWriter* writer) { - // Create the file/zip entry. - if (!writer->StartEntry("resources.arsc.flat", 0)) { - context_->GetDiagnostics()->Error(DiagMessage() << "failed to open"); - return false; - } - - // Make sure CopyingOutputStreamAdaptor is deleted before we call - // writer->FinishEntry(). - { - // Wrap our IArchiveWriter with an adaptor that implements the - // ZeroCopyOutputStream interface. - CopyingOutputStreamAdaptor adaptor(writer); - - std::unique_ptr<pb::ResourceTable> pb_table = SerializeTableToPb(table); - if (!pb_table->SerializeToZeroCopyStream(&adaptor)) { - context_->GetDiagnostics()->Error(DiagMessage() << "failed to write"); - return false; - } - } - - if (!writer->FinishEntry()) { - context_->GetDiagnostics()->Error(DiagMessage() - << "failed to finish entry"); - return false; - } - return true; + std::unique_ptr<pb::ResourceTable> pb_table = SerializeTableToPb(table); + return CopyProtoToArchive(pb_table.get(), "resources.arsc.flat", 0, writer, context_); } bool WriteJavaFile(ResourceTable* table, @@ -971,8 +956,7 @@ class LinkCommand { JavaClassGenerator generator(context_, table, java_options); if (!generator.Generate(package_name_to_generate, out_package, &fout)) { - context_->GetDiagnostics()->Error(DiagMessage(out_path) - << generator.getError()); + context_->GetDiagnostics()->Error(DiagMessage(out_path) << generator.getError()); return false; } @@ -1484,7 +1468,6 @@ class LinkCommand { if (options_.package_type == PackageType::kStaticLib) { if (!FlattenTableToPb(table, writer)) { - context_->GetDiagnostics()->Error(DiagMessage() << "failed to write resources.arsc.flat"); return false; } } else { diff --git a/tools/aapt2/readme.md b/tools/aapt2/readme.md index 1c9a75d0547d..9899f803b339 100644 --- a/tools/aapt2/readme.md +++ b/tools/aapt2/readme.md @@ -1,5 +1,12 @@ # Android Asset Packaging Tool 2.0 (AAPT2) release notes +## Version 2.11 +### `aapt2 link ...` +- Adds the ability to specify assets directories with the -A parameter. Assets work just like + assets in the original AAPT. It is not recommended to package assets with aapt2, however, + since the resulting APK is post-processed by other tools anyways. Assets do not get processed + by AAPT2, just copied, so incremental building gets slower if they are included early on. + ## Version 2.10 ### `aapt2 link ...` - Add ability to specify package ID to compile with for regular apps (not shared or static libs). |