diff options
Diffstat (limited to 'tools/aapt2/compile/Compile.cpp')
-rw-r--r-- | tools/aapt2/compile/Compile.cpp | 281 |
1 files changed, 232 insertions, 49 deletions
diff --git a/tools/aapt2/compile/Compile.cpp b/tools/aapt2/compile/Compile.cpp index 2452a1d29410..dbd8062e8b36 100644 --- a/tools/aapt2/compile/Compile.cpp +++ b/tools/aapt2/compile/Compile.cpp @@ -20,6 +20,7 @@ #include "ResourceParser.h" #include "ResourceTable.h" #include "compile/IdAssigner.h" +#include "compile/InlineXmlFormatParser.h" #include "compile/Png.h" #include "compile/PseudolocaleGenerator.h" #include "compile/XmlIdCollector.h" @@ -35,16 +36,21 @@ #include <google/protobuf/io/zero_copy_stream_impl_lite.h> #include <google/protobuf/io/coded_stream.h> +#include <android-base/errors.h> +#include <android-base/file.h> #include <dirent.h> #include <fstream> #include <string> +using google::protobuf::io::CopyingOutputStreamAdaptor; +using google::protobuf::io::ZeroCopyOutputStream; + namespace aapt { struct ResourcePathData { Source source; - std::u16string resourceDir; - std::u16string name; + std::string resourceDir; + std::string name; std::string extension; // Original config str. We keep this because when we parse the config, we may add on @@ -96,8 +102,8 @@ static Maybe<ResourcePathData> extractResourcePathData(const std::string& path, return ResourcePathData{ Source(path), - util::utf8ToUtf16(dirStr), - util::utf8ToUtf16(name), + dirStr.toString(), + name.toString(), extension.toString(), configStr.toString(), config @@ -127,7 +133,7 @@ static std::string buildIntermediateFilename(const ResourcePathData& data) { } static bool isHidden(const StringPiece& filename) { - return util::stringStartsWith<char>(filename, "."); + return util::stringStartsWith(filename, "."); } /** @@ -200,7 +206,7 @@ static bool compileTable(IAaptContext* context, const CompileOptions& options, parserOptions.errorOnPositionalArguments = !options.legacyMode; // If the filename includes donottranslate, then the default translatable is false. - parserOptions.translatable = pathData.name.find(u"donottranslate") == std::string::npos; + parserOptions.translatable = pathData.name.find("donottranslate") == std::string::npos; ResourceParser resParser(context->getDiagnostics(), &table, pathData.source, pathData.config, parserOptions); @@ -238,13 +244,14 @@ static bool compileTable(IAaptContext* context, const CompileOptions& options, return false; } - std::unique_ptr<pb::ResourceTable> pbTable = serializeTableToPb(&table); - - // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream interface. + // Make sure CopyingOutputStreamAdaptor is deleted before we call writer->finishEntry(). { - google::protobuf::io::CopyingOutputStreamAdaptor adaptor(writer); + // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream + // interface. + CopyingOutputStreamAdaptor copyingAdaptor(writer); - if (!pbTable->SerializeToZeroCopyStream(&adaptor)) { + std::unique_ptr<pb::ResourceTable> pbTable = serializeTableToPb(&table); + if (!pbTable->SerializeToZeroCopyStream(©ingAdaptor)) { context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to write"); return false; } @@ -266,21 +273,23 @@ static bool writeHeaderAndBufferToWriter(const StringPiece& outputPath, const Re return false; } - // Create the header. - std::unique_ptr<pb::CompiledFile> pbCompiledFile = serializeCompiledFileToPb(file); - + // Make sure CopyingOutputStreamAdaptor is deleted before we call writer->finishEntry(). { - // The stream must be destroyed before we finish the entry, or else - // some data won't be flushed. // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream // interface. - google::protobuf::io::CopyingOutputStreamAdaptor adaptor(writer); - CompiledFileOutputStream outputStream(&adaptor, pbCompiledFile.get()); - for (const BigBuffer::Block& block : buffer) { - if (!outputStream.Write(block.buffer.get(), block.size)) { - diag->error(DiagMessage(outputPath) << "failed to write data"); - return false; - } + CopyingOutputStreamAdaptor copyingAdaptor(writer); + CompiledFileOutputStream outputStream(©ingAdaptor); + + // Number of CompiledFiles. + outputStream.WriteLittleEndian32(1); + + std::unique_ptr<pb::CompiledFile> compiledFile = serializeCompiledFileToPb(file); + outputStream.WriteCompiledFile(compiledFile.get()); + outputStream.WriteData(&buffer); + + if (outputStream.HadError()) { + diag->error(DiagMessage(outputPath) << "failed to write data"); + return false; } } @@ -300,17 +309,21 @@ static bool writeHeaderAndMmapToWriter(const StringPiece& outputPath, const Reso return false; } - // Create the header. - std::unique_ptr<pb::CompiledFile> pbCompiledFile = serializeCompiledFileToPb(file); - + // Make sure CopyingOutputStreamAdaptor is deleted before we call writer->finishEntry(). { - // The stream must be destroyed before we finish the entry, or else - // some data won't be flushed. // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream // interface. - google::protobuf::io::CopyingOutputStreamAdaptor adaptor(writer); - CompiledFileOutputStream outputStream(&adaptor, pbCompiledFile.get()); - if (!outputStream.Write(map.getDataPtr(), map.getDataLength())) { + CopyingOutputStreamAdaptor copyingAdaptor(writer); + CompiledFileOutputStream outputStream(©ingAdaptor); + + // Number of CompiledFiles. + outputStream.WriteLittleEndian32(1); + + std::unique_ptr<pb::CompiledFile> compiledFile = serializeCompiledFileToPb(file); + outputStream.WriteCompiledFile(compiledFile.get()); + outputStream.WriteData(map.getDataPtr(), map.getDataLength()); + + if (outputStream.HadError()) { diag->error(DiagMessage(outputPath) << "failed to write data"); return false; } @@ -323,9 +336,34 @@ static bool writeHeaderAndMmapToWriter(const StringPiece& outputPath, const Reso return true; } +static bool flattenXmlToOutStream(IAaptContext* context, const StringPiece& outputPath, + xml::XmlResource* xmlRes, + CompiledFileOutputStream* out) { + BigBuffer buffer(1024); + XmlFlattenerOptions xmlFlattenerOptions; + xmlFlattenerOptions.keepRawValues = true; + XmlFlattener flattener(&buffer, xmlFlattenerOptions); + if (!flattener.consume(context, xmlRes)) { + return false; + } + + std::unique_ptr<pb::CompiledFile> pbCompiledFile = serializeCompiledFileToPb(xmlRes->file); + out->WriteCompiledFile(pbCompiledFile.get()); + out->WriteData(&buffer); + + if (out->HadError()) { + context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to write data"); + return false; + } + return true; +} + static bool compileXml(IAaptContext* context, const CompileOptions& options, const ResourcePathData& pathData, IArchiveWriter* writer, const std::string& outputPath) { + if (context->verbose()) { + context->getDiagnostics()->note(DiagMessage(pathData.source) << "compiling XML"); + } std::unique_ptr<xml::XmlResource> xmlRes; { @@ -344,34 +382,97 @@ static bool compileXml(IAaptContext* context, const CompileOptions& options, return false; } + xmlRes->file.name = ResourceName({}, *parseResourceType(pathData.resourceDir), pathData.name); + xmlRes->file.config = pathData.config; + xmlRes->file.source = pathData.source; + // Collect IDs that are defined here. XmlIdCollector collector; if (!collector.consume(context, xmlRes.get())) { return false; } - xmlRes->file.name = ResourceName({}, *parseResourceType(pathData.resourceDir), pathData.name); - xmlRes->file.config = pathData.config; - xmlRes->file.source = pathData.source; + // Look for and process any <aapt:attr> tags and create sub-documents. + InlineXmlFormatParser inlineXmlFormatParser; + if (!inlineXmlFormatParser.consume(context, xmlRes.get())) { + return false; + } - BigBuffer buffer(1024); - XmlFlattenerOptions xmlFlattenerOptions; - xmlFlattenerOptions.keepRawValues = true; - XmlFlattener flattener(&buffer, xmlFlattenerOptions); - if (!flattener.consume(context, xmlRes.get())) { + // Start the entry so we can write the header. + if (!writer->startEntry(outputPath, 0)) { + context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to open file"); return false; } - if (!writeHeaderAndBufferToWriter(outputPath, xmlRes->file, buffer, writer, - context->getDiagnostics())) { + // Make sure CopyingOutputStreamAdaptor is deleted before we call writer->finishEntry(). + { + // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream + // interface. + CopyingOutputStreamAdaptor copyingAdaptor(writer); + CompiledFileOutputStream outputStream(©ingAdaptor); + + std::vector<std::unique_ptr<xml::XmlResource>>& inlineDocuments = + inlineXmlFormatParser.getExtractedInlineXmlDocuments(); + + // Number of CompiledFiles. + outputStream.WriteLittleEndian32(1 + inlineDocuments.size()); + + if (!flattenXmlToOutStream(context, outputPath, xmlRes.get(), &outputStream)) { + return false; + } + + for (auto& inlineXmlDoc : inlineDocuments) { + if (!flattenXmlToOutStream(context, outputPath, inlineXmlDoc.get(), &outputStream)) { + return false; + } + } + } + + if (!writer->finishEntry()) { + context->getDiagnostics()->error(DiagMessage(outputPath) + << "failed to finish writing data"); return false; } return true; } +class BigBufferOutputStream : public io::OutputStream { +public: + explicit BigBufferOutputStream(BigBuffer* buffer) : mBuffer(buffer) { + } + + bool Next(void** data, int* len) override { + size_t count; + *data = mBuffer->nextBlock(&count); + *len = static_cast<int>(count); + return true; + } + + void BackUp(int count) override { + mBuffer->backUp(count); + } + + int64_t ByteCount() const override { + return mBuffer->size(); + } + + bool HadError() const override { + return false; + } + +private: + BigBuffer* mBuffer; + + DISALLOW_COPY_AND_ASSIGN(BigBufferOutputStream); +}; + static bool compilePng(IAaptContext* context, const CompileOptions& options, const ResourcePathData& pathData, IArchiveWriter* writer, const std::string& outputPath) { + if (context->verbose()) { + context->getDiagnostics()->note(DiagMessage(pathData.source) << "compiling PNG"); + } + BigBuffer buffer(4096); ResourceFile resFile; resFile.name = ResourceName({}, *parseResourceType(pathData.resourceDir), pathData.name); @@ -379,16 +480,90 @@ static bool compilePng(IAaptContext* context, const CompileOptions& options, resFile.source = pathData.source; { - std::ifstream fin(pathData.source.path, std::ifstream::binary); - if (!fin) { - context->getDiagnostics()->error(DiagMessage(pathData.source) << strerror(errno)); + std::string content; + if (!android::base::ReadFileToString(pathData.source.path, &content)) { + context->getDiagnostics()->error(DiagMessage(pathData.source) + << android::base::SystemErrorCodeToString(errno)); return false; } - Png png(context->getDiagnostics()); - if (!png.process(pathData.source, &fin, &buffer, {})) { + BigBuffer crunchedPngBuffer(4096); + BigBufferOutputStream crunchedPngBufferOut(&crunchedPngBuffer); + + // Ensure that we only keep the chunks we care about if we end up + // using the original PNG instead of the crunched one. + PngChunkFilter pngChunkFilter(content); + std::unique_ptr<Image> image = readPng(context, &pngChunkFilter); + if (!image) { + return false; + } + + std::unique_ptr<NinePatch> ninePatch; + if (pathData.extension == "9.png") { + std::string err; + ninePatch = NinePatch::create(image->rows.get(), image->width, image->height, &err); + if (!ninePatch) { + context->getDiagnostics()->error(DiagMessage() << err); + return false; + } + + // Remove the 1px border around the NinePatch. + // Basically the row array is shifted up by 1, and the length is treated + // as height - 2. + // For each row, shift the array to the left by 1, and treat the length as width - 2. + image->width -= 2; + image->height -= 2; + 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); + } + + if (context->verbose()) { + context->getDiagnostics()->note(DiagMessage(pathData.source) + << "9-patch: " << *ninePatch); + } + } + + // Write the crunched PNG. + if (!writePng(context, image.get(), ninePatch.get(), &crunchedPngBufferOut, {})) { return false; } + + if (ninePatch != nullptr + || crunchedPngBufferOut.ByteCount() <= pngChunkFilter.ByteCount()) { + // No matter what, we must use the re-encoded PNG, even if it is larger. + // 9-patch images must be re-encoded since their borders are stripped. + buffer.appendBuffer(std::move(crunchedPngBuffer)); + } else { + // The re-encoded PNG is larger than the original, and there is + // no mandatory transformation. Use the original. + if (context->verbose()) { + context->getDiagnostics()->note(DiagMessage(pathData.source) + << "original PNG is smaller than crunched PNG" + << ", using original"); + } + + PngChunkFilter pngChunkFilterAgain(content); + BigBuffer filteredPngBuffer(4096); + BigBufferOutputStream filteredPngBufferOut(&filteredPngBuffer); + io::copy(&filteredPngBufferOut, &pngChunkFilterAgain); + buffer.appendBuffer(std::move(filteredPngBuffer)); + } + + if (context->verbose()) { + // 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 legacyStream(content); + BigBuffer legacyBuffer(4096); + Png png(context->getDiagnostics()); + if (!png.process(pathData.source, &legacyStream, &legacyBuffer, {})) { + return false; + } + + context->getDiagnostics()->note(DiagMessage(pathData.source) + << "legacy=" << legacyBuffer.size() + << " new=" << buffer.size()); + } } if (!writeHeaderAndBufferToWriter(outputPath, resFile, buffer, writer, @@ -401,6 +576,10 @@ static bool compilePng(IAaptContext* context, const CompileOptions& options, static bool compileFile(IAaptContext* context, const CompileOptions& options, const ResourcePathData& pathData, IArchiveWriter* writer, const std::string& outputPath) { + if (context->verbose()) { + context->getDiagnostics()->note(DiagMessage(pathData.source) << "compiling file"); + } + BigBuffer buffer(256); ResourceFile resFile; resFile.name = ResourceName({}, *parseResourceType(pathData.resourceDir), pathData.name); @@ -440,8 +619,8 @@ public: return nullptr; } - const std::u16string& getCompilationPackage() override { - static std::u16string empty; + const std::string& getCompilationPackage() override { + static std::string empty; return empty; } @@ -454,6 +633,10 @@ public: return nullptr; } + int getMinSdkVersion() override { + return 0; + } + private: StdErrDiagnostics mDiagnostics; bool mVerbose = false; @@ -526,7 +709,7 @@ int compile(const std::vector<StringPiece>& args) { context.getDiagnostics()->note(DiagMessage(pathData.source) << "processing"); } - if (pathData.resourceDir == u"values") { + if (pathData.resourceDir == "values") { // Overwrite the extension. pathData.extension = "arsc"; |