summaryrefslogtreecommitdiff
path: root/tools/aapt2/compile/Compile.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'tools/aapt2/compile/Compile.cpp')
-rw-r--r--tools/aapt2/compile/Compile.cpp281
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(&copyingAdaptor)) {
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(&copyingAdaptor);
+
+ // 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(&copyingAdaptor);
+
+ // 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(&copyingAdaptor);
+
+ 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";