Merge "AAPT2: Share split functionality between link and optimize" into oc-dev

This commit is contained in:
TreeHugger Robot
2017-04-10 23:22:43 +00:00
committed by Android (Google) Code Review
25 changed files with 1513 additions and 1071 deletions

View File

@@ -15,11 +15,12 @@
//
toolSources = [
"compile/Compile.cpp",
"diff/Diff.cpp",
"dump/Dump.cpp",
"link/Link.cpp",
"optimize/Optimize.cpp",
"cmd/Compile.cpp",
"cmd/Diff.cpp",
"cmd/Dump.cpp",
"cmd/Link.cpp",
"cmd/Optimize.cpp",
"cmd/Util.cpp",
]
cc_defaults {
@@ -90,7 +91,7 @@ cc_library_host_static {
"io/BigBufferStreams.cpp",
"io/File.cpp",
"io/FileSystem.cpp",
"io/Io.cpp",
"io/Util.cpp",
"io/ZipArchive.cpp",
"link/AutoVersioner.cpp",
"link/ManifestFixer.cpp",

View File

@@ -29,7 +29,7 @@ struct AppInfo {
std::string package;
// The app's minimum SDK version, if it is defined.
Maybe<std::string> min_sdk_version;
Maybe<int> min_sdk_version;
// The app's version code, if it is defined.
Maybe<uint32_t> version_code;

View File

@@ -21,6 +21,7 @@
#include "flatten/Archive.h"
#include "flatten/TableFlattener.h"
#include "io/BigBufferInputStream.h"
#include "io/Util.h"
namespace aapt {
@@ -47,11 +48,10 @@ std::unique_ptr<LoadedApk> LoadedApk::LoadApkFromPath(IAaptContext* context,
}
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(), apk.get());
if (!parser.Parse()) {
return {};
}
return util::make_unique<LoadedApk>(source, std::move(apk), std::move(table));
}
@@ -100,20 +100,16 @@ bool LoadedApk::WriteToArchive(IAaptContext* context, const TableFlattenerOption
}
io::BigBufferInputStream input_stream(&buffer);
if (!writer->WriteFile(path, ArchiveEntry::kAlign, &input_stream)) {
context->GetDiagnostics()->Error(DiagMessage()
<< "Error when writing file '" << path << "' in APK.");
if (!io::CopyInputStreamToArchive(context, &input_stream, path, ArchiveEntry::kAlign,
writer)) {
return false;
}
continue;
}
std::unique_ptr<io::IData> data = file->OpenAsData();
uint32_t compression_flags = file->WasCompressed() ? ArchiveEntry::kCompress : 0u;
if (!writer->WriteFile(path, compression_flags, data.get())) {
context->GetDiagnostics()->Error(DiagMessage()
<< "Error when writing file '" << path << "' in APK.");
return false;
} else {
uint32_t compression_flags = file->WasCompressed() ? ArchiveEntry::kCompress : 0u;
if (!io::CopyFileToArchive(context, file, path, compression_flags, writer)) {
return false;
}
}
}
return true;

View File

@@ -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 = "12";
static const char* sMinorVersion = "13";
int PrintVersion() {
std::cerr << "Android Asset Packaging Tool (aapt) " << sMajorVersion << "."

View File

@@ -675,5 +675,65 @@ std::string BuildResourceFileName(const ResourceFile& res_file,
return out.str();
}
std::unique_ptr<Item> ParseBinaryResValue(const ResourceType& type, const ConfigDescription& config,
const android::ResStringPool& src_pool,
const android::Res_value& res_value,
StringPool* dst_pool) {
if (type == ResourceType::kId) {
return util::make_unique<Id>();
}
const uint32_t data = util::DeviceToHost32(res_value.data);
switch (res_value.dataType) {
case android::Res_value::TYPE_STRING: {
const std::string str = util::GetString(src_pool, data);
const android::ResStringPool_span* spans = src_pool.styleAt(data);
// Check if the string has a valid style associated with it.
if (spans != nullptr && spans->name.index != android::ResStringPool_span::END) {
StyleString style_str = {str};
while (spans->name.index != android::ResStringPool_span::END) {
style_str.spans.push_back(Span{util::GetString(src_pool, spans->name.index),
spans->firstChar, spans->lastChar});
spans++;
}
return util::make_unique<StyledString>(dst_pool->MakeRef(
style_str, StringPool::Context(StringPool::Context::kStylePriority, config)));
} else {
if (type != ResourceType::kString && util::StartsWith(str, "res/")) {
// This must be a FileReference.
return util::make_unique<FileReference>(dst_pool->MakeRef(
str, StringPool::Context(StringPool::Context::kHighPriority, config)));
}
// There are no styles associated with this string, so treat it as a simple string.
return util::make_unique<String>(dst_pool->MakeRef(str, StringPool::Context(config)));
}
} break;
case android::Res_value::TYPE_REFERENCE:
case android::Res_value::TYPE_ATTRIBUTE:
case android::Res_value::TYPE_DYNAMIC_REFERENCE:
case android::Res_value::TYPE_DYNAMIC_ATTRIBUTE: {
Reference::Type ref_type = Reference::Type::kResource;
if (res_value.dataType == android::Res_value::TYPE_ATTRIBUTE ||
res_value.dataType == android::Res_value::TYPE_DYNAMIC_ATTRIBUTE) {
ref_type = Reference::Type::kAttribute;
}
if (data == 0) {
// A reference of 0, must be the magic @null reference.
return util::make_unique<BinaryPrimitive>(android::Res_value::TYPE_REFERENCE, 0u);
}
// This is a normal reference.
return util::make_unique<Reference>(data, ref_type);
} break;
}
// Treat this as a raw binary primitive.
return util::make_unique<BinaryPrimitive>(res_value);
}
} // namespace ResourceUtils
} // namespace aapt

View File

@@ -20,11 +20,13 @@
#include <functional>
#include <memory>
#include "androidfw/ResourceTypes.h"
#include "androidfw/StringPiece.h"
#include "NameMangler.h"
#include "Resource.h"
#include "ResourceValues.h"
#include "StringPool.h"
namespace aapt {
namespace ResourceUtils {
@@ -200,6 +202,13 @@ uint32_t AndroidTypeToAttributeTypeMask(uint16_t type);
std::string BuildResourceFileName(const ResourceFile& res_file,
const NameMangler* mangler = nullptr);
// Parses the binary form of a resource value. `type` is used as a hint to know when a value is
// an ID versus a False boolean value, etc. `config` is for sorting strings in the string pool.
std::unique_ptr<Item> ParseBinaryResValue(const ResourceType& type, const ConfigDescription& config,
const android::ResStringPool& src_pool,
const android::Res_value& res_value,
StringPool* dst_pool);
} // namespace ResourceUtils
} // namespace aapt

View File

@@ -23,6 +23,7 @@
#include <unordered_map>
#include <vector>
#include "android-base/macros.h"
#include "androidfw/StringPiece.h"
#include "ConfigDescription.h"
@@ -148,7 +149,8 @@ class StringPool {
static bool FlattenUtf16(BigBuffer* out, const StringPool& pool);
StringPool() = default;
StringPool(const StringPool&) = delete;
StringPool(StringPool&&) = default;
StringPool& operator=(StringPool&&) = default;
/**
* Adds a string to the pool, unless it already exists. Returns
@@ -208,6 +210,8 @@ class StringPool {
void Prune();
private:
DISALLOW_COPY_AND_ASSIGN(StringPool);
friend const_iterator begin(const StringPool& pool);
friend const_iterator end(const StringPool& pool);

View File

@@ -38,6 +38,7 @@
#include "flatten/Archive.h"
#include "flatten/XmlFlattener.h"
#include "io/BigBufferOutputStream.h"
#include "io/Util.h"
#include "proto/ProtoSerialize.h"
#include "util/Files.h"
#include "util/Maybe.h"
@@ -138,9 +139,8 @@ static bool IsHidden(const StringPiece& filename) {
/**
* Walks the res directory structure, looking for resource files.
*/
static bool LoadInputFilesFromDir(
IAaptContext* context, const CompileOptions& options,
std::vector<ResourcePathData>* out_path_data) {
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);
if (!d) {
@@ -190,8 +190,7 @@ static bool LoadInputFilesFromDir(
}
static bool CompileTable(IAaptContext* context, const CompileOptions& options,
const ResourcePathData& path_data,
IArchiveWriter* writer,
const ResourcePathData& path_data, IArchiveWriter* writer,
const std::string& output_path) {
ResourceTable table;
{
@@ -210,11 +209,9 @@ static bool CompileTable(IAaptContext* context, const CompileOptions& options,
// If the filename includes donottranslate, then the default translatable is
// false.
parser_options.translatable =
path_data.name.find("donottranslate") == std::string::npos;
parser_options.translatable = path_data.name.find("donottranslate") == std::string::npos;
ResourceParser res_parser(context->GetDiagnostics(), &table,
path_data.source, path_data.config,
ResourceParser res_parser(context->GetDiagnostics(), &table, path_data.source, path_data.config,
parser_options);
if (!res_parser.Parse(&xml_parser)) {
return false;
@@ -273,10 +270,8 @@ static bool CompileTable(IAaptContext* context, const CompileOptions& options,
return true;
}
static bool WriteHeaderAndBufferToWriter(const StringPiece& output_path,
const ResourceFile& file,
const BigBuffer& buffer,
IArchiveWriter* writer,
static bool WriteHeaderAndBufferToWriter(const StringPiece& output_path, const ResourceFile& file,
const BigBuffer& buffer, IArchiveWriter* writer,
IDiagnostics* diag) {
// Start the entry so we can write the header.
if (!writer->StartEntry(output_path, 0)) {
@@ -312,10 +307,8 @@ static bool WriteHeaderAndBufferToWriter(const StringPiece& output_path,
return true;
}
static bool WriteHeaderAndMmapToWriter(const StringPiece& output_path,
const ResourceFile& file,
const android::FileMap& map,
IArchiveWriter* writer,
static bool WriteHeaderAndMmapToWriter(const StringPiece& output_path, const ResourceFile& file,
const android::FileMap& map, IArchiveWriter* writer,
IDiagnostics* diag) {
// Start the entry so we can write the header.
if (!writer->StartEntry(output_path, 0)) {
@@ -334,8 +327,7 @@ static bool WriteHeaderAndMmapToWriter(const StringPiece& output_path,
// 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(map.getDataPtr(), map.getDataLength());
@@ -352,10 +344,8 @@ static bool WriteHeaderAndMmapToWriter(const StringPiece& output_path,
return true;
}
static bool FlattenXmlToOutStream(IAaptContext* context,
const StringPiece& output_path,
xml::XmlResource* xmlres,
CompiledFileOutputStream* out) {
static bool FlattenXmlToOutStream(IAaptContext* context, const StringPiece& output_path,
xml::XmlResource* xmlres, CompiledFileOutputStream* out) {
BigBuffer buffer(1024);
XmlFlattenerOptions xml_flattener_options;
xml_flattener_options.keep_raw_values = true;
@@ -376,8 +366,8 @@ static bool FlattenXmlToOutStream(IAaptContext* context,
}
static bool CompileXml(IAaptContext* context, const CompileOptions& options,
const ResourcePathData& path_data,
IArchiveWriter* writer, const std::string& output_path) {
const ResourcePathData& path_data, IArchiveWriter* writer,
const std::string& output_path) {
if (context->IsVerbose()) {
context->GetDiagnostics()->Note(DiagMessage(path_data.source) << "compiling XML");
}
@@ -400,8 +390,7 @@ static bool CompileXml(IAaptContext* context, const CompileOptions& options,
return false;
}
xmlres->file.name = ResourceName(
{}, *ParseResourceType(path_data.resource_dir), path_data.name);
xmlres->file.name = ResourceName({}, *ParseResourceType(path_data.resource_dir), path_data.name);
xmlres->file.config = path_data.config;
xmlres->file.source = path_data.source;
@@ -419,8 +408,7 @@ static bool CompileXml(IAaptContext* context, const CompileOptions& options,
// Start the entry so we can write the header.
if (!writer->StartEntry(output_path, 0)) {
context->GetDiagnostics()->Error(DiagMessage(output_path)
<< "failed to open file");
context->GetDiagnostics()->Error(DiagMessage(output_path) << "failed to open file");
return false;
}
@@ -439,48 +427,42 @@ static bool CompileXml(IAaptContext* context, const CompileOptions& options,
// Number of CompiledFiles.
output_stream.WriteLittleEndian32(1 + inline_documents.size());
if (!FlattenXmlToOutStream(context, output_path, xmlres.get(),
&output_stream)) {
if (!FlattenXmlToOutStream(context, output_path, xmlres.get(), &output_stream)) {
return false;
}
for (auto& inline_xml_doc : inline_documents) {
if (!FlattenXmlToOutStream(context, output_path, inline_xml_doc.get(),
&output_stream)) {
if (!FlattenXmlToOutStream(context, output_path, inline_xml_doc.get(), &output_stream)) {
return false;
}
}
}
if (!writer->FinishEntry()) {
context->GetDiagnostics()->Error(DiagMessage(output_path)
<< "failed to finish writing data");
context->GetDiagnostics()->Error(DiagMessage(output_path) << "failed to finish writing data");
return false;
}
return true;
}
static bool CompilePng(IAaptContext* context, const CompileOptions& options,
const ResourcePathData& path_data,
IArchiveWriter* writer, const std::string& output_path) {
const ResourcePathData& path_data, IArchiveWriter* writer,
const std::string& output_path) {
if (context->IsVerbose()) {
context->GetDiagnostics()->Note(DiagMessage(path_data.source)
<< "compiling PNG");
context->GetDiagnostics()->Note(DiagMessage(path_data.source) << "compiling PNG");
}
BigBuffer buffer(4096);
ResourceFile res_file;
res_file.name = ResourceName({}, *ParseResourceType(path_data.resource_dir),
path_data.name);
res_file.name = ResourceName({}, *ParseResourceType(path_data.resource_dir), path_data.name);
res_file.config = path_data.config;
res_file.source = path_data.source;
{
std::string content;
if (!android::base::ReadFileToString(path_data.source.path, &content)) {
context->GetDiagnostics()->Error(
DiagMessage(path_data.source)
<< android::base::SystemErrorCodeToString(errno));
context->GetDiagnostics()->Error(DiagMessage(path_data.source)
<< android::base::SystemErrorCodeToString(errno));
return false;
}
@@ -517,8 +499,8 @@ static bool CompilePng(IAaptContext* context, const CompileOptions& options,
}
if (context->IsVerbose()) {
context->GetDiagnostics()->Note(DiagMessage(path_data.source)
<< "9-patch: " << *nine_patch);
context->GetDiagnostics()->Note(DiagMessage(path_data.source) << "9-patch: "
<< *nine_patch);
}
}
@@ -572,26 +554,22 @@ static bool CompilePng(IAaptContext* context, const CompileOptions& options,
}
static bool CompileFile(IAaptContext* context, const CompileOptions& options,
const ResourcePathData& path_data,
IArchiveWriter* writer,
const ResourcePathData& path_data, IArchiveWriter* writer,
const std::string& output_path) {
if (context->IsVerbose()) {
context->GetDiagnostics()->Note(DiagMessage(path_data.source)
<< "compiling file");
context->GetDiagnostics()->Note(DiagMessage(path_data.source) << "compiling file");
}
BigBuffer buffer(256);
ResourceFile res_file;
res_file.name = ResourceName({}, *ParseResourceType(path_data.resource_dir),
path_data.name);
res_file.name = ResourceName({}, *ParseResourceType(path_data.resource_dir), path_data.name);
res_file.config = path_data.config;
res_file.source = path_data.source;
std::string error_str;
Maybe<android::FileMap> f = file::MmapPath(path_data.source.path, &error_str);
if (!f) {
context->GetDiagnostics()->Error(DiagMessage(path_data.source)
<< error_str);
context->GetDiagnostics()->Error(DiagMessage(path_data.source) << error_str);
return false;
}
@@ -604,11 +582,17 @@ static bool CompileFile(IAaptContext* context, const CompileOptions& options,
class CompileContext : public IAaptContext {
public:
void SetVerbose(bool val) { verbose_ = val; }
void SetVerbose(bool val) {
verbose_ = val;
}
bool IsVerbose() override { return verbose_; }
bool IsVerbose() override {
return verbose_;
}
IDiagnostics* GetDiagnostics() override { return &diagnostics_; }
IDiagnostics* GetDiagnostics() override {
return &diagnostics_;
}
NameMangler* GetNameMangler() override {
abort();
@@ -620,14 +604,18 @@ class CompileContext : public IAaptContext {
return empty;
}
uint8_t GetPackageId() override { return 0x0; }
uint8_t GetPackageId() override {
return 0x0;
}
SymbolTable* GetExternalSymbols() override {
abort();
return nullptr;
}
int GetMinSdkVersion() override { return 0; }
int GetMinSdkVersion() override {
return 0;
}
private:
StdErrDiagnostics diagnostics_;
@@ -646,16 +634,13 @@ int Compile(const std::vector<StringPiece>& args) {
Flags flags =
Flags()
.RequiredFlag("-o", "Output path", &options.output_path)
.OptionalFlag("--dir", "Directory to scan for resources",
&options.res_dir)
.OptionalFlag("--dir", "Directory to scan for resources", &options.res_dir)
.OptionalSwitch("--pseudo-localize",
"Generate resources for pseudo-locales "
"(en-XA and ar-XB)",
&options.pseudolocalize)
.OptionalSwitch(
"--legacy",
"Treat errors that used to be valid in AAPT as warnings",
&options.legacy_mode)
.OptionalSwitch("--legacy", "Treat errors that used to be valid in AAPT as warnings",
&options.legacy_mode)
.OptionalSwitch("-v", "Enables verbose logging", &verbose);
if (!flags.Parse("aapt2 compile", args, &std::cerr)) {
return 1;
@@ -669,8 +654,7 @@ int Compile(const std::vector<StringPiece>& args) {
if (options.res_dir) {
if (!flags.GetArgs().empty()) {
// Can't have both files and a resource directory.
context.GetDiagnostics()->Error(DiagMessage()
<< "files given but --dir specified");
context.GetDiagnostics()->Error(DiagMessage() << "files given but --dir specified");
flags.Usage("aapt2 compile", &std::cerr);
return 1;
}
@@ -679,8 +663,7 @@ int Compile(const std::vector<StringPiece>& args) {
return 1;
}
archive_writer = CreateZipFileArchiveWriter(context.GetDiagnostics(),
options.output_path);
archive_writer = CreateZipFileArchiveWriter(context.GetDiagnostics(), options.output_path);
} else {
input_data.reserve(flags.GetArgs().size());
@@ -688,18 +671,15 @@ int Compile(const std::vector<StringPiece>& args) {
// Collect data from the path for each input file.
for (const std::string& arg : flags.GetArgs()) {
std::string error_str;
if (Maybe<ResourcePathData> path_data =
ExtractResourcePathData(arg, &error_str)) {
if (Maybe<ResourcePathData> path_data = ExtractResourcePathData(arg, &error_str)) {
input_data.push_back(std::move(path_data.value()));
} else {
context.GetDiagnostics()->Error(DiagMessage() << error_str << " ("
<< arg << ")");
context.GetDiagnostics()->Error(DiagMessage() << error_str << " (" << arg << ")");
return 1;
}
}
archive_writer = CreateDirectoryArchiveWriter(context.GetDiagnostics(),
options.output_path);
archive_writer = CreateDirectoryArchiveWriter(context.GetDiagnostics(), options.output_path);
}
if (!archive_writer) {
@@ -709,8 +689,7 @@ int Compile(const std::vector<StringPiece>& args) {
bool error = false;
for (ResourcePathData& path_data : input_data) {
if (options.verbose) {
context.GetDiagnostics()->Note(DiagMessage(path_data.source)
<< "processing");
context.GetDiagnostics()->Note(DiagMessage(path_data.source) << "processing");
}
if (path_data.resource_dir == "values") {
@@ -718,42 +697,35 @@ int Compile(const std::vector<StringPiece>& args) {
path_data.extension = "arsc";
const std::string output_filename = BuildIntermediateFilename(path_data);
if (!CompileTable(&context, options, path_data, archive_writer.get(),
output_filename)) {
if (!CompileTable(&context, options, path_data, archive_writer.get(), output_filename)) {
error = true;
}
} else {
const std::string output_filename = BuildIntermediateFilename(path_data);
if (const ResourceType* type =
ParseResourceType(path_data.resource_dir)) {
if (const ResourceType* type = ParseResourceType(path_data.resource_dir)) {
if (*type != ResourceType::kRaw) {
if (path_data.extension == "xml") {
if (!CompileXml(&context, options, path_data, archive_writer.get(),
output_filename)) {
if (!CompileXml(&context, options, path_data, archive_writer.get(), output_filename)) {
error = true;
}
} else if (path_data.extension == "png" ||
path_data.extension == "9.png") {
if (!CompilePng(&context, options, path_data, archive_writer.get(),
output_filename)) {
} else if (path_data.extension == "png" || path_data.extension == "9.png") {
if (!CompilePng(&context, options, path_data, archive_writer.get(), output_filename)) {
error = true;
}
} else {
if (!CompileFile(&context, options, path_data, archive_writer.get(),
output_filename)) {
if (!CompileFile(&context, options, path_data, archive_writer.get(), output_filename)) {
error = true;
}
}
} else {
if (!CompileFile(&context, options, path_data, archive_writer.get(),
output_filename)) {
if (!CompileFile(&context, options, path_data, archive_writer.get(), output_filename)) {
error = true;
}
}
} else {
context.GetDiagnostics()->Error(
DiagMessage() << "invalid file path '" << path_data.source << "'");
context.GetDiagnostics()->Error(DiagMessage() << "invalid file path '" << path_data.source
<< "'");
error = true;
}
}

View File

@@ -28,21 +28,36 @@ namespace aapt {
class DiffContext : public IAaptContext {
public:
DiffContext() : name_mangler_({}), symbol_table_(&name_mangler_) {}
DiffContext() : name_mangler_({}), symbol_table_(&name_mangler_) {
}
const std::string& GetCompilationPackage() override { return empty_; }
const std::string& GetCompilationPackage() override {
return empty_;
}
uint8_t GetPackageId() override { return 0x0; }
uint8_t GetPackageId() override {
return 0x0;
}
IDiagnostics* GetDiagnostics() override { return &diagnostics_; }
IDiagnostics* GetDiagnostics() override {
return &diagnostics_;
}
NameMangler* GetNameMangler() override { return &name_mangler_; }
NameMangler* GetNameMangler() override {
return &name_mangler_;
}
SymbolTable* GetExternalSymbols() override { return &symbol_table_; }
SymbolTable* GetExternalSymbols() override {
return &symbol_table_;
}
bool IsVerbose() override { return false; }
bool IsVerbose() override {
return false;
}
int GetMinSdkVersion() override { return 0; }
int GetMinSdkVersion() override {
return 0;
}
private:
std::string empty_;
@@ -55,34 +70,31 @@ static void EmitDiffLine(const Source& source, const StringPiece& message) {
std::cerr << source << ": " << message << "\n";
}
static bool IsSymbolVisibilityDifferent(const Symbol& symbol_a,
const Symbol& symbol_b) {
static bool IsSymbolVisibilityDifferent(const Symbol& symbol_a, const Symbol& symbol_b) {
return symbol_a.state != symbol_b.state;
}
template <typename Id>
static bool IsIdDiff(const Symbol& symbol_a, const Maybe<Id>& id_a,
const Symbol& symbol_b, const Maybe<Id>& id_b) {
if (symbol_a.state == SymbolState::kPublic ||
symbol_b.state == SymbolState::kPublic) {
static bool IsIdDiff(const Symbol& symbol_a, const Maybe<Id>& id_a, const Symbol& symbol_b,
const Maybe<Id>& id_b) {
if (symbol_a.state == SymbolState::kPublic || symbol_b.state == SymbolState::kPublic) {
return id_a != id_b;
}
return false;
}
static bool EmitResourceConfigValueDiff(
IAaptContext* context, LoadedApk* apk_a, ResourceTablePackage* pkg_a,
ResourceTableType* type_a, ResourceEntry* entry_a,
ResourceConfigValue* config_value_a, LoadedApk* apk_b,
ResourceTablePackage* pkg_b, ResourceTableType* type_b,
ResourceEntry* entry_b, ResourceConfigValue* config_value_b) {
static bool EmitResourceConfigValueDiff(IAaptContext* context, LoadedApk* apk_a,
ResourceTablePackage* pkg_a, ResourceTableType* type_a,
ResourceEntry* entry_a, ResourceConfigValue* config_value_a,
LoadedApk* apk_b, ResourceTablePackage* pkg_b,
ResourceTableType* type_b, ResourceEntry* entry_b,
ResourceConfigValue* config_value_b) {
Value* value_a = config_value_a->value.get();
Value* value_b = config_value_b->value.get();
if (!value_a->Equals(value_b)) {
std::stringstream str_stream;
str_stream << "value " << pkg_a->name << ":" << type_a->type << "/"
<< entry_a->name << " config=" << config_value_a->config
<< " does not match:\n";
str_stream << "value " << pkg_a->name << ":" << type_a->type << "/" << entry_a->name
<< " config=" << config_value_a->config << " does not match:\n";
value_a->Print(&str_stream);
str_stream << "\n vs \n";
value_b->Print(&str_stream);
@@ -93,37 +105,33 @@ static bool EmitResourceConfigValueDiff(
}
static bool EmitResourceEntryDiff(IAaptContext* context, LoadedApk* apk_a,
ResourceTablePackage* pkg_a,
ResourceTableType* type_a,
ResourceTablePackage* pkg_a, ResourceTableType* type_a,
ResourceEntry* entry_a, LoadedApk* apk_b,
ResourceTablePackage* pkg_b,
ResourceTableType* type_b,
ResourceTablePackage* pkg_b, ResourceTableType* type_b,
ResourceEntry* entry_b) {
bool diff = false;
for (std::unique_ptr<ResourceConfigValue>& config_value_a : entry_a->values) {
ResourceConfigValue* config_value_b =
entry_b->FindValue(config_value_a->config);
ResourceConfigValue* config_value_b = entry_b->FindValue(config_value_a->config);
if (!config_value_b) {
std::stringstream str_stream;
str_stream << "missing " << pkg_a->name << ":" << type_a->type << "/"
<< entry_a->name << " config=" << config_value_a->config;
str_stream << "missing " << pkg_a->name << ":" << type_a->type << "/" << entry_a->name
<< " config=" << config_value_a->config;
EmitDiffLine(apk_b->GetSource(), str_stream.str());
diff = true;
} else {
diff |= EmitResourceConfigValueDiff(
context, apk_a, pkg_a, type_a, entry_a, config_value_a.get(), apk_b,
pkg_b, type_b, entry_b, config_value_b);
diff |=
EmitResourceConfigValueDiff(context, apk_a, pkg_a, type_a, entry_a, config_value_a.get(),
apk_b, pkg_b, type_b, entry_b, config_value_b);
}
}
// Check for any newly added config values.
for (std::unique_ptr<ResourceConfigValue>& config_value_b : entry_b->values) {
ResourceConfigValue* config_value_a =
entry_a->FindValue(config_value_b->config);
ResourceConfigValue* config_value_a = entry_a->FindValue(config_value_b->config);
if (!config_value_a) {
std::stringstream str_stream;
str_stream << "new config " << pkg_b->name << ":" << type_b->type << "/"
<< entry_b->name << " config=" << config_value_b->config;
str_stream << "new config " << pkg_b->name << ":" << type_b->type << "/" << entry_b->name
<< " config=" << config_value_b->config;
EmitDiffLine(apk_b->GetSource(), str_stream.str());
diff = true;
}
@@ -132,22 +140,19 @@ static bool EmitResourceEntryDiff(IAaptContext* context, LoadedApk* apk_a,
}
static bool EmitResourceTypeDiff(IAaptContext* context, LoadedApk* apk_a,
ResourceTablePackage* pkg_a,
ResourceTableType* type_a, LoadedApk* apk_b,
ResourceTablePackage* pkg_b,
ResourceTablePackage* pkg_a, ResourceTableType* type_a,
LoadedApk* apk_b, ResourceTablePackage* pkg_b,
ResourceTableType* type_b) {
bool diff = false;
for (std::unique_ptr<ResourceEntry>& entry_a : type_a->entries) {
ResourceEntry* entry_b = type_b->FindEntry(entry_a->name);
if (!entry_b) {
std::stringstream str_stream;
str_stream << "missing " << pkg_a->name << ":" << type_a->type << "/"
<< entry_a->name;
str_stream << "missing " << pkg_a->name << ":" << type_a->type << "/" << entry_a->name;
EmitDiffLine(apk_b->GetSource(), str_stream.str());
diff = true;
} else {
if (IsSymbolVisibilityDifferent(entry_a->symbol_status,
entry_b->symbol_status)) {
if (IsSymbolVisibilityDifferent(entry_a->symbol_status, entry_b->symbol_status)) {
std::stringstream str_stream;
str_stream << pkg_a->name << ":" << type_a->type << "/" << entry_a->name
<< " has different visibility (";
@@ -165,8 +170,8 @@ static bool EmitResourceTypeDiff(IAaptContext* context, LoadedApk* apk_a,
str_stream << ")";
EmitDiffLine(apk_b->GetSource(), str_stream.str());
diff = true;
} else if (IsIdDiff(entry_a->symbol_status, entry_a->id,
entry_b->symbol_status, entry_b->id)) {
} else if (IsIdDiff(entry_a->symbol_status, entry_a->id, entry_b->symbol_status,
entry_b->id)) {
std::stringstream str_stream;
str_stream << pkg_a->name << ":" << type_a->type << "/" << entry_a->name
<< " has different public ID (";
@@ -185,9 +190,8 @@ static bool EmitResourceTypeDiff(IAaptContext* context, LoadedApk* apk_a,
EmitDiffLine(apk_b->GetSource(), str_stream.str());
diff = true;
}
diff |=
EmitResourceEntryDiff(context, apk_a, pkg_a, type_a, entry_a.get(),
apk_b, pkg_b, type_b, entry_b);
diff |= EmitResourceEntryDiff(context, apk_a, pkg_a, type_a, entry_a.get(), apk_b, pkg_b,
type_b, entry_b);
}
}
@@ -196,8 +200,7 @@ static bool EmitResourceTypeDiff(IAaptContext* context, LoadedApk* apk_a,
ResourceEntry* entry_a = type_a->FindEntry(entry_b->name);
if (!entry_a) {
std::stringstream str_stream;
str_stream << "new entry " << pkg_b->name << ":" << type_b->type << "/"
<< entry_b->name;
str_stream << "new entry " << pkg_b->name << ":" << type_b->type << "/" << entry_b->name;
EmitDiffLine(apk_b->GetSource(), str_stream.str());
diff = true;
}
@@ -206,8 +209,7 @@ static bool EmitResourceTypeDiff(IAaptContext* context, LoadedApk* apk_a,
}
static bool EmitResourcePackageDiff(IAaptContext* context, LoadedApk* apk_a,
ResourceTablePackage* pkg_a,
LoadedApk* apk_b,
ResourceTablePackage* pkg_a, LoadedApk* apk_b,
ResourceTablePackage* pkg_b) {
bool diff = false;
for (std::unique_ptr<ResourceTableType>& type_a : pkg_a->types) {
@@ -218,11 +220,9 @@ static bool EmitResourcePackageDiff(IAaptContext* context, LoadedApk* apk_a,
EmitDiffLine(apk_a->GetSource(), str_stream.str());
diff = true;
} else {
if (IsSymbolVisibilityDifferent(type_a->symbol_status,
type_b->symbol_status)) {
if (IsSymbolVisibilityDifferent(type_a->symbol_status, type_b->symbol_status)) {
std::stringstream str_stream;
str_stream << pkg_a->name << ":" << type_a->type
<< " has different visibility (";
str_stream << pkg_a->name << ":" << type_a->type << " has different visibility (";
if (type_b->symbol_status.state == SymbolState::kPublic) {
str_stream << "PUBLIC";
} else {
@@ -237,11 +237,9 @@ static bool EmitResourcePackageDiff(IAaptContext* context, LoadedApk* apk_a,
str_stream << ")";
EmitDiffLine(apk_b->GetSource(), str_stream.str());
diff = true;
} else if (IsIdDiff(type_a->symbol_status, type_a->id,
type_b->symbol_status, type_b->id)) {
} else if (IsIdDiff(type_a->symbol_status, type_a->id, type_b->symbol_status, type_b->id)) {
std::stringstream str_stream;
str_stream << pkg_a->name << ":" << type_a->type
<< " has different public ID (";
str_stream << pkg_a->name << ":" << type_a->type << " has different public ID (";
if (type_b->id) {
str_stream << "0x" << std::hex << type_b->id.value();
} else {
@@ -257,8 +255,7 @@ static bool EmitResourcePackageDiff(IAaptContext* context, LoadedApk* apk_a,
EmitDiffLine(apk_b->GetSource(), str_stream.str());
diff = true;
}
diff |= EmitResourceTypeDiff(context, apk_a, pkg_a, type_a.get(), apk_b,
pkg_b, type_b);
diff |= EmitResourceTypeDiff(context, apk_a, pkg_a, type_a.get(), apk_b, pkg_b, type_b);
}
}
@@ -275,8 +272,7 @@ static bool EmitResourcePackageDiff(IAaptContext* context, LoadedApk* apk_a,
return diff;
}
static bool EmitResourceTableDiff(IAaptContext* context, LoadedApk* apk_a,
LoadedApk* apk_b) {
static bool EmitResourceTableDiff(IAaptContext* context, LoadedApk* apk_a, LoadedApk* apk_b) {
ResourceTable* table_a = apk_a->GetResourceTable();
ResourceTable* table_b = apk_b->GetResourceTable();
@@ -307,8 +303,7 @@ static bool EmitResourceTableDiff(IAaptContext* context, LoadedApk* apk_a,
EmitDiffLine(apk_b->GetSource(), str_stream.str());
diff = true;
}
diff |=
EmitResourcePackageDiff(context, apk_a, pkg_a.get(), apk_b, pkg_b);
diff |= EmitResourcePackageDiff(context, apk_a, pkg_a.get(), apk_b, pkg_b);
}
}
@@ -357,10 +352,8 @@ int Diff(const std::vector<StringPiece>& args) {
return 1;
}
std::unique_ptr<LoadedApk> apk_a =
LoadedApk::LoadApkFromPath(&context, flags.GetArgs()[0]);
std::unique_ptr<LoadedApk> apk_b =
LoadedApk::LoadApkFromPath(&context, flags.GetArgs()[1]);
std::unique_ptr<LoadedApk> apk_a = LoadedApk::LoadApkFromPath(&context, flags.GetArgs()[0]);
std::unique_ptr<LoadedApk> apk_b = LoadedApk::LoadApkFromPath(&context, flags.GetArgs()[1]);
if (!apk_a || !apk_b) {
return 1;
}

View File

@@ -31,13 +31,12 @@ using android::StringPiece;
namespace aapt {
void DumpCompiledFile(const pb::CompiledFile& pb_file, const void* data,
size_t len, const Source& source, IAaptContext* context) {
void DumpCompiledFile(const pb::CompiledFile& pb_file, const void* data, size_t len,
const Source& source, IAaptContext* context) {
std::unique_ptr<ResourceFile> file =
DeserializeCompiledFileFromPb(pb_file, source, context->GetDiagnostics());
if (!file) {
context->GetDiagnostics()->Warn(DiagMessage()
<< "failed to read compiled file");
context->GetDiagnostics()->Warn(DiagMessage() << "failed to read compiled file");
return;
}
@@ -50,27 +49,24 @@ void TryDumpFile(IAaptContext* context, const std::string& file_path) {
std::unique_ptr<ResourceTable> table;
std::string err;
std::unique_ptr<io::ZipFileCollection> zip =
io::ZipFileCollection::Create(file_path, &err);
std::unique_ptr<io::ZipFileCollection> zip = io::ZipFileCollection::Create(file_path, &err);
if (zip) {
io::IFile* file = zip->FindFile("resources.arsc.flat");
if (file) {
std::unique_ptr<io::IData> data = file->OpenAsData();
if (!data) {
context->GetDiagnostics()->Error(
DiagMessage(file_path) << "failed to open resources.arsc.flat");
context->GetDiagnostics()->Error(DiagMessage(file_path)
<< "failed to open resources.arsc.flat");
return;
}
pb::ResourceTable pb_table;
if (!pb_table.ParseFromArray(data->data(), data->size())) {
context->GetDiagnostics()->Error(DiagMessage(file_path)
<< "invalid resources.arsc.flat");
context->GetDiagnostics()->Error(DiagMessage(file_path) << "invalid resources.arsc.flat");
return;
}
table = DeserializeTableFromPb(pb_table, Source(file_path),
context->GetDiagnostics());
table = DeserializeTableFromPb(pb_table, Source(file_path), context->GetDiagnostics());
if (!table) {
return;
}
@@ -87,8 +83,8 @@ void TryDumpFile(IAaptContext* context, const std::string& file_path) {
}
table = util::make_unique<ResourceTable>();
BinaryResourceParser parser(context, table.get(), Source(file_path),
data->data(), data->size());
BinaryResourceParser parser(context, table.get(), Source(file_path), data->data(),
data->size());
if (!parser.Parse()) {
return;
}
@@ -107,16 +103,13 @@ void TryDumpFile(IAaptContext* context, const std::string& file_path) {
// Try as a compiled table.
pb::ResourceTable pb_table;
if (pb_table.ParseFromArray(file_map->getDataPtr(),
file_map->getDataLength())) {
table = DeserializeTableFromPb(pb_table, Source(file_path),
context->GetDiagnostics());
if (pb_table.ParseFromArray(file_map->getDataPtr(), file_map->getDataLength())) {
table = DeserializeTableFromPb(pb_table, Source(file_path), context->GetDiagnostics());
}
if (!table) {
// Try as a compiled file.
CompiledFileInputStream input(file_map->getDataPtr(),
file_map->getDataLength());
CompiledFileInputStream input(file_map->getDataPtr(), file_map->getDataLength());
uint32_t num_files = 0;
if (!input.ReadLittleEndian32(&num_files)) {
@@ -126,20 +119,17 @@ void TryDumpFile(IAaptContext* context, const std::string& file_path) {
for (uint32_t i = 0; i < num_files; i++) {
pb::CompiledFile compiled_file;
if (!input.ReadCompiledFile(&compiled_file)) {
context->GetDiagnostics()->Warn(DiagMessage()
<< "failed to read compiled file");
context->GetDiagnostics()->Warn(DiagMessage() << "failed to read compiled file");
return;
}
uint64_t offset, len;
if (!input.ReadDataMetaData(&offset, &len)) {
context->GetDiagnostics()->Warn(DiagMessage()
<< "failed to read meta data");
context->GetDiagnostics()->Warn(DiagMessage() << "failed to read meta data");
return;
}
const void* data =
static_cast<const uint8_t*>(file_map->getDataPtr()) + offset;
const void* data = static_cast<const uint8_t*>(file_map->getDataPtr()) + offset;
DumpCompiledFile(compiled_file, data, len, Source(file_path), context);
}
}
@@ -154,7 +144,9 @@ void TryDumpFile(IAaptContext* context, const std::string& file_path) {
class DumpContext : public IAaptContext {
public:
IDiagnostics* GetDiagnostics() override { return &diagnostics_; }
IDiagnostics* GetDiagnostics() override {
return &diagnostics_;
}
NameMangler* GetNameMangler() override {
abort();
@@ -166,18 +158,26 @@ class DumpContext : public IAaptContext {
return empty;
}
uint8_t GetPackageId() override { return 0; }
uint8_t GetPackageId() override {
return 0;
}
SymbolTable* GetExternalSymbols() override {
abort();
return nullptr;
}
bool IsVerbose() override { return verbose_; }
bool IsVerbose() override {
return verbose_;
}
void SetVerbose(bool val) { verbose_ = val; }
void SetVerbose(bool val) {
verbose_ = val;
}
int GetMinSdkVersion() override { return 0; }
int GetMinSdkVersion() override {
return 0;
}
private:
StdErrDiagnostics diagnostics_;
@@ -189,8 +189,7 @@ class DumpContext : public IAaptContext {
*/
int Dump(const std::vector<StringPiece>& args) {
bool verbose = false;
Flags flags =
Flags().OptionalSwitch("-v", "increase verbosity of output", &verbose);
Flags flags = Flags().OptionalSwitch("-v", "increase verbosity of output", &verbose);
if (!flags.Parse("aapt2 dump", args, &std::cerr)) {
return 1;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,369 @@
/*
* 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 <memory>
#include <vector>
#include "androidfw/StringPiece.h"
#include "Diagnostics.h"
#include "Flags.h"
#include "LoadedApk.h"
#include "ResourceUtils.h"
#include "SdkConstants.h"
#include "ValueVisitor.h"
#include "cmd/Util.h"
#include "flatten/TableFlattener.h"
#include "flatten/XmlFlattener.h"
#include "io/BigBufferInputStream.h"
#include "io/Util.h"
#include "optimize/ResourceDeduper.h"
#include "optimize/VersionCollapser.h"
#include "split/TableSplitter.h"
using android::StringPiece;
namespace aapt {
struct OptimizeOptions {
// Path to the output APK.
std::string output_path;
// Details of the app extracted from the AndroidManifest.xml
AppInfo app_info;
// Split APK options.
TableSplitterOptions table_splitter_options;
// List of output split paths. These are in the same order as `split_constraints`.
std::vector<std::string> split_paths;
// List of SplitConstraints governing what resources go into each split. Ordered by `split_paths`.
std::vector<SplitConstraints> split_constraints;
TableFlattenerOptions table_flattener_options;
};
class OptimizeContext : public IAaptContext {
public:
IDiagnostics* GetDiagnostics() override {
return &diagnostics_;
}
NameMangler* GetNameMangler() override {
UNIMPLEMENTED(FATAL);
return nullptr;
}
const std::string& GetCompilationPackage() override {
static std::string empty;
return empty;
}
uint8_t GetPackageId() override {
return 0;
}
SymbolTable* GetExternalSymbols() override {
UNIMPLEMENTED(FATAL);
return nullptr;
}
bool IsVerbose() override {
return verbose_;
}
void SetVerbose(bool val) {
verbose_ = val;
}
void SetMinSdkVersion(int sdk_version) {
sdk_version_ = sdk_version;
}
int GetMinSdkVersion() override {
return sdk_version_;
}
private:
StdErrDiagnostics diagnostics_;
bool verbose_ = false;
int sdk_version_ = 0;
};
class OptimizeCommand {
public:
OptimizeCommand(OptimizeContext* context, const OptimizeOptions& options)
: options_(options), context_(context) {
}
int Run(std::unique_ptr<LoadedApk> apk) {
if (context_->IsVerbose()) {
context_->GetDiagnostics()->Note(DiagMessage() << "Optimizing APK...");
}
VersionCollapser collapser;
if (!collapser.Consume(context_, apk->GetResourceTable())) {
return 1;
}
ResourceDeduper deduper;
if (!deduper.Consume(context_, apk->GetResourceTable())) {
context_->GetDiagnostics()->Error(DiagMessage() << "failed deduping resources");
return 1;
}
// Adjust the SplitConstraints so that their SDK version is stripped if it is less than or
// equal to the minSdk.
options_.split_constraints =
AdjustSplitConstraintsForMinSdk(context_->GetMinSdkVersion(), options_.split_constraints);
// Stripping the APK using the TableSplitter. The resource table is modified in place in the
// LoadedApk.
TableSplitter splitter(options_.split_constraints, options_.table_splitter_options);
if (!splitter.VerifySplitConstraints(context_)) {
return 1;
}
splitter.SplitTable(apk->GetResourceTable());
auto path_iter = options_.split_paths.begin();
auto split_constraints_iter = options_.split_constraints.begin();
for (std::unique_ptr<ResourceTable>& split_table : splitter.splits()) {
if (context_->IsVerbose()) {
context_->GetDiagnostics()->Note(
DiagMessage(*path_iter) << "generating split with configurations '"
<< util::Joiner(split_constraints_iter->configs, ", ") << "'");
}
// Generate an AndroidManifest.xml for each split.
std::unique_ptr<xml::XmlResource> split_manifest =
GenerateSplitManifest(options_.app_info, *split_constraints_iter);
std::unique_ptr<IArchiveWriter> split_writer =
CreateZipFileArchiveWriter(context_->GetDiagnostics(), *path_iter);
if (!split_writer) {
return 1;
}
if (!WriteSplitApk(split_table.get(), split_manifest.get(), split_writer.get())) {
return 1;
}
++path_iter;
++split_constraints_iter;
}
std::unique_ptr<IArchiveWriter> writer =
CreateZipFileArchiveWriter(context_->GetDiagnostics(), options_.output_path);
if (!apk->WriteToArchive(context_, options_.table_flattener_options, writer.get())) {
return 1;
}
return 0;
}
private:
bool WriteSplitApk(ResourceTable* table, xml::XmlResource* manifest, IArchiveWriter* writer) {
BigBuffer manifest_buffer(4096);
XmlFlattener xml_flattener(&manifest_buffer, {});
if (!xml_flattener.Consume(context_, manifest)) {
return false;
}
io::BigBufferInputStream manifest_buffer_in(&manifest_buffer);
if (!io::CopyInputStreamToArchive(context_, &manifest_buffer_in, "AndroidManifest.xml",
ArchiveEntry::kCompress, writer)) {
return false;
}
std::map<std::pair<ConfigDescription, StringPiece>, FileReference*> config_sorted_files;
for (auto& pkg : table->packages) {
for (auto& type : pkg->types) {
// Sort by config and name, so that we get better locality in the zip file.
config_sorted_files.clear();
for (auto& entry : type->entries) {
for (auto& config_value : entry->values) {
FileReference* file_ref = ValueCast<FileReference>(config_value->value.get());
if (file_ref == nullptr) {
continue;
}
if (file_ref->file == nullptr) {
ResourceNameRef name(pkg->name, type->type, entry->name);
context_->GetDiagnostics()->Error(DiagMessage(file_ref->GetSource())
<< "file for resource " << name << " with config '"
<< config_value->config << "' not found");
return false;
}
const StringPiece entry_name = entry->name;
config_sorted_files[std::make_pair(config_value->config, entry_name)] = file_ref;
}
}
for (auto& entry : config_sorted_files) {
FileReference* file_ref = entry.second;
uint32_t compression_flags =
file_ref->file->WasCompressed() ? ArchiveEntry::kCompress : 0u;
if (!io::CopyFileToArchive(context_, file_ref->file, *file_ref->path, compression_flags,
writer)) {
return false;
}
}
}
}
BigBuffer table_buffer(4096);
TableFlattener table_flattener(options_.table_flattener_options, &table_buffer);
if (!table_flattener.Consume(context_, table)) {
return false;
}
io::BigBufferInputStream table_buffer_in(&table_buffer);
if (!io::CopyInputStreamToArchive(context_, &table_buffer_in, "resources.arsc",
ArchiveEntry::kAlign, writer)) {
return false;
}
return true;
}
OptimizeOptions options_;
OptimizeContext* context_;
};
bool ExtractAppDataFromManifest(OptimizeContext* context, LoadedApk* apk,
OptimizeOptions* out_options) {
io::IFile* manifest_file = apk->GetFileCollection()->FindFile("AndroidManifest.xml");
if (manifest_file == nullptr) {
context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
<< "missing AndroidManifest.xml");
return false;
}
std::unique_ptr<io::IData> data = manifest_file->OpenAsData();
if (data == nullptr) {
context->GetDiagnostics()->Error(DiagMessage(manifest_file->GetSource())
<< "failed to open file");
return false;
}
std::unique_ptr<xml::XmlResource> manifest = xml::Inflate(
data->data(), data->size(), context->GetDiagnostics(), manifest_file->GetSource());
if (manifest == nullptr) {
context->GetDiagnostics()->Error(DiagMessage() << "failed to read binary AndroidManifest.xml");
return false;
}
Maybe<AppInfo> app_info =
ExtractAppInfoFromBinaryManifest(manifest.get(), context->GetDiagnostics());
if (!app_info) {
context->GetDiagnostics()->Error(DiagMessage()
<< "failed to extract data from AndroidManifest.xml");
return false;
}
out_options->app_info = std::move(app_info.value());
context->SetMinSdkVersion(out_options->app_info.min_sdk_version.value_or_default(0));
return true;
}
int Optimize(const std::vector<StringPiece>& args) {
OptimizeContext context;
OptimizeOptions options;
Maybe<std::string> target_densities;
std::vector<std::string> configs;
std::vector<std::string> split_args;
bool verbose = false;
Flags flags =
Flags()
.RequiredFlag("-o", "Path to the output APK.", &options.output_path)
.OptionalFlag(
"--target-densities",
"Comma separated list of the screen densities that the APK will be optimized for.\n"
"All the resources that would be unused on devices of the given densities will be \n"
"removed from the APK.",
&target_densities)
.OptionalFlagList("-c",
"Comma separated list of configurations to include. The default\n"
"is all configurations.",
&configs)
.OptionalFlagList("--split",
"Split resources matching a set of configs out to a "
"Split APK.\nSyntax: path/to/output.apk:<config>[,<config>[...]].",
&split_args)
.OptionalSwitch("--enable-sparse-encoding",
"Enables encoding sparse entries using a binary search tree.\n"
"This decreases APK size at the cost of resource retrieval performance.",
&options.table_flattener_options.use_sparse_entries)
.OptionalSwitch("-v", "Enables verbose logging", &verbose);
if (!flags.Parse("aapt2 optimize", args, &std::cerr)) {
return 1;
}
if (flags.GetArgs().size() != 1u) {
std::cerr << "must have one APK as argument.\n\n";
flags.Usage("aapt2 optimize", &std::cerr);
return 1;
}
std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(&context, flags.GetArgs()[0]);
if (!apk) {
return 1;
}
context.SetVerbose(verbose);
if (target_densities) {
// Parse the target screen densities.
for (const StringPiece& config_str : util::Tokenize(target_densities.value(), ',')) {
Maybe<uint16_t> target_density =
ParseTargetDensityParameter(config_str, context.GetDiagnostics());
if (!target_density) {
return 1;
}
options.table_splitter_options.preferred_densities.push_back(target_density.value());
}
}
std::unique_ptr<IConfigFilter> filter;
if (!configs.empty()) {
filter = ParseConfigFilterParameters(configs, context.GetDiagnostics());
if (filter == nullptr) {
return 1;
}
options.table_splitter_options.config_filter = filter.get();
}
// Parse the split parameters.
for (const std::string& split_arg : split_args) {
options.split_paths.push_back({});
options.split_constraints.push_back({});
if (!ParseSplitParameter(split_arg, context.GetDiagnostics(), &options.split_paths.back(),
&options.split_constraints.back())) {
return 1;
}
}
if (!ExtractAppDataFromManifest(&context, apk.get(), &options)) {
return 1;
}
OptimizeCommand cmd(&context, options);
return cmd.Run(std::move(apk));
}
} // namespace aapt

347
tools/aapt2/cmd/Util.cpp Normal file
View File

@@ -0,0 +1,347 @@
/*
* 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 "cmd/Util.h"
#include <vector>
#include "android-base/logging.h"
#include "ConfigDescription.h"
#include "Locale.h"
#include "ResourceUtils.h"
#include "ValueVisitor.h"
#include "split/TableSplitter.h"
#include "util/Maybe.h"
#include "util/Util.h"
using android::StringPiece;
namespace aapt {
Maybe<uint16_t> ParseTargetDensityParameter(const StringPiece& arg, IDiagnostics* diag) {
ConfigDescription preferred_density_config;
if (!ConfigDescription::Parse(arg, &preferred_density_config)) {
diag->Error(DiagMessage() << "invalid density '" << arg << "' for --preferred-density option");
return {};
}
// Clear the version that can be automatically added.
preferred_density_config.sdkVersion = 0;
if (preferred_density_config.diff(ConfigDescription::DefaultConfig()) !=
ConfigDescription::CONFIG_DENSITY) {
diag->Error(DiagMessage() << "invalid preferred density '" << arg << "'. "
<< "Preferred density must only be a density value");
return {};
}
return preferred_density_config.density;
}
bool ParseSplitParameter(const StringPiece& arg, IDiagnostics* diag, std::string* out_path,
SplitConstraints* out_split) {
CHECK(diag != nullptr);
CHECK(out_path != nullptr);
CHECK(out_split != nullptr);
std::vector<std::string> parts = util::Split(arg, ':');
if (parts.size() != 2) {
diag->Error(DiagMessage() << "invalid split parameter '" << arg << "'");
diag->Note(DiagMessage() << "should be --split path/to/output.apk:<config>[,<config>...]");
return false;
}
*out_path = parts[0];
std::vector<ConfigDescription> configs;
for (const StringPiece& config_str : util::Tokenize(parts[1], ',')) {
ConfigDescription config;
if (!ConfigDescription::Parse(config_str, &config)) {
diag->Error(DiagMessage() << "invalid config '" << config_str << "' in split parameter '"
<< arg << "'");
return false;
}
out_split->configs.insert(config);
}
return true;
}
std::unique_ptr<IConfigFilter> ParseConfigFilterParameters(const std::vector<std::string>& args,
IDiagnostics* diag) {
std::unique_ptr<AxisConfigFilter> filter = util::make_unique<AxisConfigFilter>();
for (const std::string& config_arg : args) {
for (const StringPiece& config_str : util::Tokenize(config_arg, ',')) {
ConfigDescription config;
LocaleValue lv;
if (lv.InitFromFilterString(config_str)) {
lv.WriteTo(&config);
} else if (!ConfigDescription::Parse(config_str, &config)) {
diag->Error(DiagMessage() << "invalid config '" << config_str << "' for -c option");
return {};
}
if (config.density != 0) {
diag->Warn(DiagMessage() << "ignoring density '" << config << "' for -c option");
} else {
filter->AddConfig(config);
}
}
}
return std::move(filter);
}
// Adjust the SplitConstraints so that their SDK version is stripped if it
// is less than or equal to the minSdk. Otherwise the resources that have had
// their SDK version stripped due to minSdk won't ever match.
std::vector<SplitConstraints> AdjustSplitConstraintsForMinSdk(
int min_sdk, const std::vector<SplitConstraints>& split_constraints) {
std::vector<SplitConstraints> adjusted_constraints;
adjusted_constraints.reserve(split_constraints.size());
for (const SplitConstraints& constraints : split_constraints) {
SplitConstraints constraint;
for (const ConfigDescription& config : constraints.configs) {
if (config.sdkVersion <= min_sdk) {
constraint.configs.insert(config.CopyWithoutSdkVersion());
} else {
constraint.configs.insert(config);
}
}
adjusted_constraints.push_back(std::move(constraint));
}
return adjusted_constraints;
}
static xml::AaptAttribute CreateAttributeWithId(const ResourceId& id) {
return xml::AaptAttribute{id, Attribute(true)};
}
std::unique_ptr<xml::XmlResource> GenerateSplitManifest(const AppInfo& app_info,
const SplitConstraints& constraints) {
const ResourceId kVersionCode(0x0101021b);
const ResourceId kRevisionCode(0x010104d5);
const ResourceId kHasCode(0x0101000c);
std::unique_ptr<xml::XmlResource> doc = util::make_unique<xml::XmlResource>();
std::unique_ptr<xml::Namespace> namespace_android = util::make_unique<xml::Namespace>();
namespace_android->namespace_uri = xml::kSchemaAndroid;
namespace_android->namespace_prefix = "android";
std::unique_ptr<xml::Element> manifest_el = util::make_unique<xml::Element>();
manifest_el->name = "manifest";
manifest_el->attributes.push_back(xml::Attribute{"", "package", app_info.package});
if (app_info.version_code) {
const uint32_t version_code = app_info.version_code.value();
manifest_el->attributes.push_back(xml::Attribute{
xml::kSchemaAndroid, "versionCode", std::to_string(version_code),
CreateAttributeWithId(kVersionCode),
util::make_unique<BinaryPrimitive>(android::Res_value::TYPE_INT_DEC, version_code)});
}
if (app_info.revision_code) {
const uint32_t revision_code = app_info.revision_code.value();
manifest_el->attributes.push_back(xml::Attribute{
xml::kSchemaAndroid, "revisionCode", std::to_string(revision_code),
CreateAttributeWithId(kRevisionCode),
util::make_unique<BinaryPrimitive>(android::Res_value::TYPE_INT_DEC, revision_code)});
}
std::stringstream split_name;
if (app_info.split_name) {
split_name << app_info.split_name.value() << ".";
}
split_name << "config." << util::Joiner(constraints.configs, "_");
manifest_el->attributes.push_back(xml::Attribute{"", "split", split_name.str()});
if (app_info.split_name) {
manifest_el->attributes.push_back(
xml::Attribute{"", "configForSplit", app_info.split_name.value()});
}
std::unique_ptr<xml::Element> application_el = util::make_unique<xml::Element>();
application_el->name = "application";
application_el->attributes.push_back(
xml::Attribute{xml::kSchemaAndroid, "hasCode", "false", CreateAttributeWithId(kHasCode),
util::make_unique<BinaryPrimitive>(android::Res_value::TYPE_INT_BOOLEAN, 0u)});
manifest_el->AppendChild(std::move(application_el));
namespace_android->AppendChild(std::move(manifest_el));
doc->root = std::move(namespace_android);
return doc;
}
static Maybe<std::string> ExtractCompiledString(xml::Attribute* attr, std::string* out_error) {
if (attr->compiled_value != nullptr) {
String* compiled_str = ValueCast<String>(attr->compiled_value.get());
if (compiled_str != nullptr) {
if (!compiled_str->value->empty()) {
return *compiled_str->value;
} else {
*out_error = "compiled value is an empty string";
return {};
}
}
*out_error = "compiled value is not a string";
return {};
}
// Fallback to the plain text value if there is one.
if (!attr->value.empty()) {
return attr->value;
}
*out_error = "value is an empty string";
return {};
}
static Maybe<uint32_t> ExtractCompiledInt(xml::Attribute* attr, std::string* out_error) {
if (attr->compiled_value != nullptr) {
BinaryPrimitive* compiled_prim = ValueCast<BinaryPrimitive>(attr->compiled_value.get());
if (compiled_prim != nullptr) {
if (compiled_prim->value.dataType >= android::Res_value::TYPE_FIRST_INT &&
compiled_prim->value.dataType <= android::Res_value::TYPE_LAST_INT) {
return compiled_prim->value.data;
}
}
*out_error = "compiled value is not an integer";
return {};
}
// Fallback to the plain text value if there is one.
Maybe<uint32_t> integer = ResourceUtils::ParseInt(attr->value);
if (integer) {
return integer;
}
std::stringstream error_msg;
error_msg << "'" << attr->value << "' is not a valid integer";
*out_error = error_msg.str();
return {};
}
static Maybe<int> ExtractSdkVersion(xml::Attribute* attr, std::string* out_error) {
if (attr->compiled_value != nullptr) {
BinaryPrimitive* compiled_prim = ValueCast<BinaryPrimitive>(attr->compiled_value.get());
if (compiled_prim != nullptr) {
if (compiled_prim->value.dataType >= android::Res_value::TYPE_FIRST_INT &&
compiled_prim->value.dataType <= android::Res_value::TYPE_LAST_INT) {
return compiled_prim->value.data;
}
*out_error = "compiled value is not an integer or string";
return {};
}
String* compiled_str = ValueCast<String>(attr->compiled_value.get());
if (compiled_str != nullptr) {
Maybe<int> sdk_version = ResourceUtils::ParseSdkVersion(*compiled_str->value);
if (sdk_version) {
return sdk_version;
}
*out_error = "compiled string value is not a valid SDK version";
return {};
}
*out_error = "compiled value is not an integer or string";
return {};
}
// Fallback to the plain text value if there is one.
Maybe<int> sdk_version = ResourceUtils::ParseSdkVersion(attr->value);
if (sdk_version) {
return sdk_version;
}
std::stringstream error_msg;
error_msg << "'" << attr->value << "' is not a valid SDK version";
*out_error = error_msg.str();
return {};
}
Maybe<AppInfo> ExtractAppInfoFromBinaryManifest(xml::XmlResource* xml_res, IDiagnostics* diag) {
// Make sure the first element is <manifest> with package attribute.
xml::Element* manifest_el = xml::FindRootElement(xml_res->root.get());
if (manifest_el == nullptr) {
return {};
}
AppInfo app_info;
if (!manifest_el->namespace_uri.empty() || manifest_el->name != "manifest") {
diag->Error(DiagMessage(xml_res->file.source) << "root tag must be <manifest>");
return {};
}
xml::Attribute* package_attr = manifest_el->FindAttribute({}, "package");
if (!package_attr) {
diag->Error(DiagMessage(xml_res->file.source) << "<manifest> must have a 'package' attribute");
return {};
}
std::string error_msg;
Maybe<std::string> maybe_package = ExtractCompiledString(package_attr, &error_msg);
if (!maybe_package) {
diag->Error(DiagMessage(xml_res->file.source.WithLine(manifest_el->line_number))
<< "invalid package name: " << error_msg);
return {};
}
app_info.package = maybe_package.value();
if (xml::Attribute* version_code_attr =
manifest_el->FindAttribute(xml::kSchemaAndroid, "versionCode")) {
Maybe<uint32_t> maybe_code = ExtractCompiledInt(version_code_attr, &error_msg);
if (!maybe_code) {
diag->Error(DiagMessage(xml_res->file.source.WithLine(manifest_el->line_number))
<< "invalid android:versionCode: " << error_msg);
return {};
}
app_info.version_code = maybe_code.value();
}
if (xml::Attribute* revision_code_attr =
manifest_el->FindAttribute(xml::kSchemaAndroid, "revisionCode")) {
Maybe<uint32_t> maybe_code = ExtractCompiledInt(revision_code_attr, &error_msg);
if (!maybe_code) {
diag->Error(DiagMessage(xml_res->file.source.WithLine(manifest_el->line_number))
<< "invalid android:revisionCode: " << error_msg);
return {};
}
app_info.revision_code = maybe_code.value();
}
if (xml::Attribute* split_name_attr = manifest_el->FindAttribute({}, "split")) {
Maybe<std::string> maybe_split_name = ExtractCompiledString(split_name_attr, &error_msg);
if (!maybe_split_name) {
diag->Error(DiagMessage(xml_res->file.source.WithLine(manifest_el->line_number))
<< "invalid split name: " << error_msg);
return {};
}
app_info.split_name = maybe_split_name.value();
}
if (xml::Element* uses_sdk_el = manifest_el->FindChild({}, "uses-sdk")) {
if (xml::Attribute* min_sdk =
uses_sdk_el->FindAttribute(xml::kSchemaAndroid, "minSdkVersion")) {
Maybe<int> maybe_sdk = ExtractSdkVersion(min_sdk, &error_msg);
if (!maybe_sdk) {
diag->Error(DiagMessage(xml_res->file.source.WithLine(uses_sdk_el->line_number))
<< "invalid android:minSdkVersion: " << error_msg);
return {};
}
app_info.min_sdk_version = maybe_sdk.value();
}
}
return app_info;
}
} // namespace aapt

64
tools/aapt2/cmd/Util.h Normal file
View File

@@ -0,0 +1,64 @@
/*
* 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_SPLIT_UTIL_H
#define AAPT_SPLIT_UTIL_H
#include "androidfw/StringPiece.h"
#include "AppInfo.h"
#include "Diagnostics.h"
#include "SdkConstants.h"
#include "filter/ConfigFilter.h"
#include "split/TableSplitter.h"
#include "util/Maybe.h"
#include "xml/XmlDom.h"
namespace aapt {
// Parses a configuration density (ex. hdpi, xxhdpi, 234dpi, anydpi, etc).
// Returns Nothing and logs a human friendly error message if the string was not legal.
Maybe<uint16_t> ParseTargetDensityParameter(const android::StringPiece& arg, IDiagnostics* diag);
// Parses a string of the form 'path/to/output.apk:<config>[,<config>...]' and fills in
// `out_path` with the path and `out_split` with the set of ConfigDescriptions.
// Returns false and logs a human friendly error message if the string was not legal.
bool ParseSplitParameter(const android::StringPiece& arg, IDiagnostics* diag, std::string* out_path,
SplitConstraints* out_split);
// Parses a set of config filter strings of the form 'en,fr-rFR' and returns an IConfigFilter.
// Returns nullptr and logs a human friendly error message if the string was not legal.
std::unique_ptr<IConfigFilter> ParseConfigFilterParameters(const std::vector<std::string>& args,
IDiagnostics* diag);
// Adjust the SplitConstraints so that their SDK version is stripped if it
// is less than or equal to the min_sdk. Otherwise the resources that have had
// their SDK version stripped due to min_sdk won't ever match.
std::vector<SplitConstraints> AdjustSplitConstraintsForMinSdk(
int min_sdk, const std::vector<SplitConstraints>& split_constraints);
// Generates a split AndroidManifest.xml given the split constraints and app info. The resulting
// XmlResource does not need to be linked via XmlReferenceLinker.
// This will never fail/return nullptr.
std::unique_ptr<xml::XmlResource> GenerateSplitManifest(const AppInfo& app_info,
const SplitConstraints& constraints);
// Extracts relevant info from the AndroidManifest.xml.
Maybe<AppInfo> ExtractAppInfoFromBinaryManifest(xml::XmlResource* xml_res, IDiagnostics* diag);
} // namespace aapt
#endif /* AAPT_SPLIT_UTIL_H */

View File

@@ -1,43 +0,0 @@
/*
* Copyright (C) 2016 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/Io.h"
#include <cstring>
namespace aapt {
namespace io {
bool Copy(OutputStream* out, InputStream* in) {
const void* in_buffer;
size_t in_len;
while (in->Next(&in_buffer, &in_len)) {
void* out_buffer;
size_t out_len;
if (!out->Next(&out_buffer, &out_len)) {
return !out->HadError();
}
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);
}
return !in->HadError();
}
} // namespace io
} // namespace aapt

View File

@@ -87,10 +87,6 @@ class OutputStream {
virtual bool HadError() const = 0;
};
// 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
} // namespace aapt

95
tools/aapt2/io/Util.cpp Normal file
View File

@@ -0,0 +1,95 @@
/*
* 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/Util.h"
#include "google/protobuf/io/zero_copy_stream_impl_lite.h"
namespace aapt {
namespace io {
bool CopyInputStreamToArchive(IAaptContext* context, InputStream* in, const std::string& out_path,
uint32_t compression_flags, IArchiveWriter* writer) {
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;
}
bool CopyFileToArchive(IAaptContext* context, io::IFile* file, const std::string& out_path,
uint32_t compression_flags, IArchiveWriter* writer) {
std::unique_ptr<io::IData> data = file->OpenAsData();
if (!data) {
context->GetDiagnostics()->Error(DiagMessage(file->GetSource()) << "failed to open file");
return false;
}
return CopyInputStreamToArchive(context, data.get(), out_path, compression_flags, writer);
}
bool CopyProtoToArchive(IAaptContext* context, ::google::protobuf::MessageLite* proto_msg,
const std::string& out_path, uint32_t compression_flags,
IArchiveWriter* writer) {
if (context->IsVerbose()) {
context->GetDiagnostics()->Note(DiagMessage() << "writing " << out_path << " to archive");
}
if (writer->StartEntry(out_path, compression_flags)) {
// 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;
}
}
if (writer->FinishEntry()) {
return true;
}
}
context->GetDiagnostics()->Error(DiagMessage() << "failed to write " << out_path
<< " to archive: " << writer->GetError());
return false;
}
bool Copy(OutputStream* out, InputStream* in) {
const void* in_buffer;
size_t in_len;
while (in->Next(&in_buffer, &in_len)) {
void* out_buffer;
size_t out_len;
if (!out->Next(&out_buffer, &out_len)) {
return !out->HadError();
}
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);
}
return !in->HadError();
}
} // namespace io
} // namespace aapt

49
tools/aapt2/io/Util.h Normal file
View File

@@ -0,0 +1,49 @@
/*
* 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_UTIL_H
#define AAPT_IO_UTIL_H
#include <string>
#include "google/protobuf/message_lite.h"
#include "flatten/Archive.h"
#include "io/File.h"
#include "io/Io.h"
#include "process/IResourceTableConsumer.h"
namespace aapt {
namespace io {
bool CopyInputStreamToArchive(IAaptContext* context, InputStream* in, const std::string& out_path,
uint32_t compression_flags, IArchiveWriter* writer);
bool CopyFileToArchive(IAaptContext* context, IFile* file, const std::string& out_path,
uint32_t compression_flags, IArchiveWriter* writer);
bool CopyProtoToArchive(IAaptContext* context, ::google::protobuf::MessageLite* proto_msg,
const std::string& out_path, uint32_t compression_flags,
IArchiveWriter* writer);
// 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
} // namespace aapt
#endif /* AAPT_IO_UTIL_H */

View File

@@ -1,201 +0,0 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <memory>
#include <vector>
#include "androidfw/StringPiece.h"
#include "Diagnostics.h"
#include "Flags.h"
#include "LoadedApk.h"
#include "SdkConstants.h"
#include "flatten/TableFlattener.h"
#include "optimize/ResourceDeduper.h"
#include "optimize/VersionCollapser.h"
#include "split/TableSplitter.h"
using android::StringPiece;
namespace aapt {
struct OptimizeOptions {
// Path to the output APK.
std::string output_path;
// List of screen density configurations the APK will be optimized for.
std::vector<ConfigDescription> target_configs;
TableFlattenerOptions table_flattener_options;
};
class OptimizeContext : public IAaptContext {
public:
IDiagnostics* GetDiagnostics() override { return &diagnostics_; }
NameMangler* GetNameMangler() override {
abort();
return nullptr;
}
const std::string& GetCompilationPackage() override {
static std::string empty;
return empty;
}
uint8_t GetPackageId() override { return 0; }
SymbolTable* GetExternalSymbols() override {
abort();
return nullptr;
}
bool IsVerbose() override { return verbose_; }
void SetVerbose(bool val) { verbose_ = val; }
void SetMinSdkVersion(int sdk_version) { sdk_version_ = sdk_version; }
int GetMinSdkVersion() override { return sdk_version_; }
private:
StdErrDiagnostics diagnostics_;
bool verbose_ = false;
int sdk_version_ = 0;
};
class OptimizeCommand {
public:
OptimizeCommand(OptimizeContext* context, const OptimizeOptions& options)
: options_(options),
context_(context) {}
int Run(std::unique_ptr<LoadedApk> apk) {
if (context_->IsVerbose()) {
context_->GetDiagnostics()->Note(DiagMessage() << "Optimizing APK...");
}
VersionCollapser collapser;
if (!collapser.Consume(context_, apk->GetResourceTable())) {
return 1;
}
ResourceDeduper deduper;
if (!deduper.Consume(context_, apk->GetResourceTable())) {
context_->GetDiagnostics()->Error(DiagMessage() << "failed deduping resources");
return 1;
}
// Stripping the APK using the TableSplitter with no splits and the target
// densities as the preferred densities. The resource table is modified in
// place in the LoadedApk.
TableSplitterOptions splitter_options;
for (auto& config : options_.target_configs) {
splitter_options.preferred_densities.push_back(config.density);
}
std::vector<SplitConstraints> splits;
TableSplitter splitter(splits, splitter_options);
splitter.SplitTable(apk->GetResourceTable());
std::unique_ptr<IArchiveWriter> writer =
CreateZipFileArchiveWriter(context_->GetDiagnostics(), options_.output_path);
if (!apk->WriteToArchive(context_, options_.table_flattener_options, writer.get())) {
return 1;
}
return 0;
}
private:
OptimizeOptions options_;
OptimizeContext* context_;
};
int Optimize(const std::vector<StringPiece>& args) {
OptimizeContext context;
OptimizeOptions options;
Maybe<std::string> target_densities;
bool verbose = false;
Flags flags =
Flags()
.RequiredFlag("-o", "Path to the output APK.", &options.output_path)
.OptionalFlag(
"--target-densities",
"Comma separated list of the screen densities that the APK will "
"be optimized for. All the resources that would be unused on "
"devices of the given densities will be removed from the APK.",
&target_densities)
.OptionalSwitch("--enable-sparse-encoding",
"Enables encoding sparse entries using a binary search tree.\n"
"This decreases APK size at the cost of resource retrieval performance.",
&options.table_flattener_options.use_sparse_entries)
.OptionalSwitch("-v", "Enables verbose logging", &verbose);
if (!flags.Parse("aapt2 optimize", args, &std::cerr)) {
return 1;
}
if (flags.GetArgs().size() != 1u) {
std::cerr << "must have one APK as argument.\n\n";
flags.Usage("aapt2 optimize", &std::cerr);
return 1;
}
std::unique_ptr<LoadedApk> apk =
LoadedApk::LoadApkFromPath(&context, flags.GetArgs()[0]);
if (!apk) {
return 1;
}
if (verbose) {
context.SetVerbose(verbose);
}
if (target_densities) {
// Parse the target screen densities.
for (const StringPiece& config_str : util::Tokenize(target_densities.value(), ',')) {
ConfigDescription config;
if (!ConfigDescription::Parse(config_str, &config) || config.density == 0) {
context.GetDiagnostics()->Error(
DiagMessage() << "invalid density '" << config_str
<< "' for --target-densities option");
return 1;
}
// Clear the version that can be automatically added.
config.sdkVersion = 0;
if (config.diff(ConfigDescription::DefaultConfig()) !=
ConfigDescription::CONFIG_DENSITY) {
context.GetDiagnostics()->Error(
DiagMessage() << "invalid density '" << config_str
<< "' for --target-densities option. Must be only a "
<< "density value.");
return 1;
}
options.target_configs.push_back(config);
}
}
// TODO(adamlesinski): Read manfiest and set the proper minSdkVersion.
// context.SetMinSdkVersion(SDK_O);
OptimizeCommand cmd(&context, options);
return cmd.Run(std::move(apk));
}
} // namespace aapt

View File

@@ -1,5 +1,10 @@
# Android Asset Packaging Tool 2.0 (AAPT2) release notes
## Version 2.13
### `aapt2 optimize ...`
- aapt2 optimize can now split a binary APK with the same --split parameters as the link
phase.
## Version 2.12
### `aapt2 optimize ...`
- aapt2 optimize now understands map (complex) values under the type `id`. It ignores their

View File

@@ -73,15 +73,16 @@ class ReferenceIdToNameVisitor : public ValueVisitor {
} // namespace
BinaryResourceParser::BinaryResourceParser(IAaptContext* context,
ResourceTable* table,
const Source& source,
const void* data, size_t len)
BinaryResourceParser::BinaryResourceParser(IAaptContext* context, ResourceTable* table,
const Source& source, const void* data, size_t len,
io::IFileCollection* files)
: context_(context),
table_(table),
source_(source),
data_(data),
data_len_(len) {}
data_len_(len),
files_(files) {
}
bool BinaryResourceParser::Parse() {
ResChunkPullParser parser(data_, data_len_);
@@ -359,16 +360,14 @@ bool BinaryResourceParser::ParseType(const ResourceTablePackage* package,
std::unique_ptr<Value> resource_value;
if (entry->flags & ResTable_entry::FLAG_COMPLEX) {
const ResTable_map_entry* mapEntry =
static_cast<const ResTable_map_entry*>(entry);
const ResTable_map_entry* mapEntry = static_cast<const ResTable_map_entry*>(entry);
// TODO(adamlesinski): Check that the entry count is valid.
resource_value = ParseMapEntry(name, config, mapEntry);
} else {
const Res_value* value =
(const Res_value*)((const uint8_t*)entry +
util::DeviceToHost32(entry->size));
resource_value = ParseValue(name, config, value, entry->flags);
(const Res_value*)((const uint8_t*)entry + util::DeviceToHost32(entry->size));
resource_value = ParseValue(name, config, *value);
}
if (!resource_value) {
@@ -388,8 +387,7 @@ bool BinaryResourceParser::ParseType(const ResourceTablePackage* package,
Symbol symbol;
symbol.state = SymbolState::kPublic;
symbol.source = source_.WithLine(0);
if (!table_->SetSymbolStateAllowMangled(name, res_id, symbol,
context_->GetDiagnostics())) {
if (!table_->SetSymbolStateAllowMangled(name, res_id, symbol, context_->GetDiagnostics())) {
return false;
}
}
@@ -419,70 +417,25 @@ bool BinaryResourceParser::ParseLibrary(const ResChunk_header* chunk) {
return true;
}
std::unique_ptr<Item> BinaryResourceParser::ParseValue(
const ResourceNameRef& name, const ConfigDescription& config,
const Res_value* value, uint16_t flags) {
if (name.type == ResourceType::kId) {
return util::make_unique<Id>();
}
const uint32_t data = util::DeviceToHost32(value->data);
if (value->dataType == Res_value::TYPE_STRING) {
const std::string str = util::GetString(value_pool_, data);
const ResStringPool_span* spans = value_pool_.styleAt(data);
// Check if the string has a valid style associated with it.
if (spans != nullptr && spans->name.index != ResStringPool_span::END) {
StyleString style_str = {str};
while (spans->name.index != ResStringPool_span::END) {
style_str.spans.push_back(
Span{util::GetString(value_pool_, spans->name.index),
spans->firstChar, spans->lastChar});
spans++;
std::unique_ptr<Item> BinaryResourceParser::ParseValue(const ResourceNameRef& name,
const ConfigDescription& config,
const android::Res_value& value) {
std::unique_ptr<Item> item = ResourceUtils::ParseBinaryResValue(name.type, config, value_pool_,
value, &table_->string_pool);
if (files_ != nullptr && item != nullptr) {
FileReference* file_ref = ValueCast<FileReference>(item.get());
if (file_ref != nullptr) {
file_ref->file = files_->FindFile(*file_ref->path);
if (file_ref->file == nullptr) {
context_->GetDiagnostics()->Error(DiagMessage() << "resource " << name << " for config '"
<< config << "' is a file reference to '"
<< *file_ref->path
<< "' but no such path exists");
return {};
}
return util::make_unique<StyledString>(table_->string_pool.MakeRef(
style_str,
StringPool::Context(StringPool::Context::kStylePriority, config)));
} else {
if (name.type != ResourceType::kString && util::StartsWith(str, "res/")) {
// This must be a FileReference.
return util::make_unique<FileReference>(table_->string_pool.MakeRef(
str,
StringPool::Context(StringPool::Context::kHighPriority, config)));
}
// There are no styles associated with this string, so treat it as
// a simple string.
return util::make_unique<String>(
table_->string_pool.MakeRef(str, StringPool::Context(config)));
}
}
if (value->dataType == Res_value::TYPE_REFERENCE ||
value->dataType == Res_value::TYPE_ATTRIBUTE ||
value->dataType == Res_value::TYPE_DYNAMIC_REFERENCE ||
value->dataType == Res_value::TYPE_DYNAMIC_ATTRIBUTE) {
Reference::Type type = Reference::Type::kResource;
if (value->dataType == Res_value::TYPE_ATTRIBUTE ||
value->dataType == Res_value::TYPE_DYNAMIC_ATTRIBUTE) {
type = Reference::Type::kAttribute;
}
if (data == 0) {
// A reference of 0, must be the magic @null reference.
Res_value null_type = {};
null_type.dataType = Res_value::TYPE_REFERENCE;
return util::make_unique<BinaryPrimitive>(null_type);
}
// This is a normal reference.
return util::make_unique<Reference>(data, type);
}
// Treat this as a raw binary primitive.
return util::make_unique<BinaryPrimitive>(*value);
return item;
}
std::unique_ptr<Value> BinaryResourceParser::ParseMapEntry(
@@ -528,7 +481,7 @@ std::unique_ptr<Style> BinaryResourceParser::ParseStyle(
Style::Entry style_entry;
style_entry.key = Reference(util::DeviceToHost32(map_entry.name.ident));
style_entry.value = ParseValue(name, config, &map_entry.value, 0);
style_entry.value = ParseValue(name, config, map_entry.value);
if (!style_entry.value) {
return {};
}
@@ -586,7 +539,7 @@ std::unique_ptr<Array> BinaryResourceParser::ParseArray(
const ResTable_map_entry* map) {
std::unique_ptr<Array> array = util::make_unique<Array>();
for (const ResTable_map& map_entry : map) {
array->items.push_back(ParseValue(name, config, &map_entry.value, 0));
array->items.push_back(ParseValue(name, config, map_entry.value));
}
return array;
}
@@ -596,7 +549,7 @@ std::unique_ptr<Plural> BinaryResourceParser::ParsePlural(
const ResTable_map_entry* map) {
std::unique_ptr<Plural> plural = util::make_unique<Plural>();
for (const ResTable_map& map_entry : map) {
std::unique_ptr<Item> item = ParseValue(name, config, &map_entry.value, 0);
std::unique_ptr<Item> item = ParseValue(name, config, map_entry.value);
if (!item) {
return {};
}

View File

@@ -45,8 +45,8 @@ class BinaryResourceParser {
* Creates a parser, which will read `len` bytes from `data`, and
* add any resources parsed to `table`. `source` is for logging purposes.
*/
BinaryResourceParser(IAaptContext* context, ResourceTable* table,
const Source& source, const void* data, size_t data_len);
BinaryResourceParser(IAaptContext* context, ResourceTable* table, const Source& source,
const void* data, size_t data_len, io::IFileCollection* files = nullptr);
/*
* Parses the binary resource table and returns true if successful.
@@ -63,10 +63,8 @@ class BinaryResourceParser {
const android::ResChunk_header* chunk);
bool ParseLibrary(const android::ResChunk_header* chunk);
std::unique_ptr<Item> ParseValue(const ResourceNameRef& name,
const ConfigDescription& config,
const android::Res_value* value,
uint16_t flags);
std::unique_ptr<Item> ParseValue(const ResourceNameRef& name, const ConfigDescription& config,
const android::Res_value& value);
std::unique_ptr<Value> ParseMapEntry(const ResourceNameRef& name,
const ConfigDescription& config,
@@ -104,6 +102,9 @@ class BinaryResourceParser {
const void* data_;
const size_t data_len_;
// Optional file collection from which to create io::IFile objects.
io::IFileCollection* files_;
// The standard value string pool for resource values.
android::ResStringPool value_pool_;

View File

@@ -99,8 +99,7 @@ Maybe<std::string> GetFullyQualifiedClassName(const android::StringPiece& packag
const android::StringPiece& class_name);
/**
* Makes a std::unique_ptr<> with the template parameter inferred by the
* compiler.
* Makes a std::unique_ptr<> with the template parameter inferred by the compiler.
* This will be present in C++14 and can be removed then.
*/
template <typename T, class... Args>

View File

@@ -25,6 +25,7 @@
#include "android-base/logging.h"
#include "ResourceUtils.h"
#include "XmlPullParser.h"
#include "util/Util.h"
@@ -193,16 +194,14 @@ static void XMLCALL CommentDataHandler(void* user_data, const char* comment) {
stack->pending_comment += comment;
}
std::unique_ptr<XmlResource> Inflate(std::istream* in, IDiagnostics* diag,
const Source& source) {
std::unique_ptr<XmlResource> Inflate(std::istream* in, IDiagnostics* diag, const Source& source) {
Stack stack;
XML_Parser parser = XML_ParserCreateNS(nullptr, kXmlNamespaceSep);
XML_SetUserData(parser, &stack);
XML_UseParserAsHandlerArg(parser);
XML_SetElementHandler(parser, StartElementHandler, EndElementHandler);
XML_SetNamespaceDeclHandler(parser, StartNamespaceHandler,
EndNamespaceHandler);
XML_SetNamespaceDeclHandler(parser, StartNamespaceHandler, EndNamespaceHandler);
XML_SetCharacterDataHandler(parser, CharacterDataHandler);
XML_SetCommentHandler(parser, CommentDataHandler);
@@ -215,8 +214,7 @@ std::unique_ptr<XmlResource> Inflate(std::istream* in, IDiagnostics* diag,
break;
}
if (XML_Parse(parser, buffer, in->gcount(), in->eof()) ==
XML_STATUS_ERROR) {
if (XML_Parse(parser, buffer, in->gcount(), in->eof()) == XML_STATUS_ERROR) {
stack.root = {};
diag->Error(DiagMessage(source.WithLine(XML_GetCurrentLineNumber(parser)))
<< XML_ErrorString(XML_GetErrorCode(parser)));
@@ -226,13 +224,12 @@ std::unique_ptr<XmlResource> Inflate(std::istream* in, IDiagnostics* diag,
XML_ParserFree(parser);
if (stack.root) {
return util::make_unique<XmlResource>(ResourceFile{{}, {}, source},
std::move(stack.root));
return util::make_unique<XmlResource>(ResourceFile{{}, {}, source}, std::move(stack.root));
}
return {};
}
static void CopyAttributes(Element* el, android::ResXMLParser* parser) {
static void CopyAttributes(Element* el, android::ResXMLParser* parser, StringPool* out_pool) {
const size_t attr_count = parser->getAttributeCount();
if (attr_count > 0) {
el->attributes.reserve(attr_count);
@@ -253,18 +250,26 @@ static void CopyAttributes(Element* el, android::ResXMLParser* parser) {
if (str16) {
attr.value = util::Utf16ToUtf8(StringPiece16(str16, len));
}
android::Res_value res_value;
if (parser->getAttributeValue(i, &res_value) > 0) {
attr.compiled_value = ResourceUtils::ParseBinaryResValue(
ResourceType::kAnim, {}, parser->getStrings(), res_value, out_pool);
}
el->attributes.push_back(std::move(attr));
}
}
}
std::unique_ptr<XmlResource> Inflate(const void* data, size_t data_len,
IDiagnostics* diag, const Source& source) {
std::unique_ptr<XmlResource> Inflate(const void* data, size_t data_len, IDiagnostics* diag,
const Source& source) {
// We import the android namespace because on Windows NO_ERROR is a macro, not
// an enum, which
// causes errors when qualifying it with android::
using namespace android;
StringPool string_pool;
std::unique_ptr<Node> root;
std::stack<Node*> node_stack;
@@ -307,7 +312,7 @@ std::unique_ptr<XmlResource> Inflate(const void* data, size_t data_len,
node->name = util::Utf16ToUtf8(StringPiece16(str16, len));
}
CopyAttributes(node.get(), &tree);
CopyAttributes(node.get(), &tree, &string_pool);
new_node = std::move(node);
break;
@@ -352,7 +357,7 @@ std::unique_ptr<XmlResource> Inflate(const void* data, size_t data_len,
}
}
}
return util::make_unique<XmlResource>(ResourceFile{}, std::move(root));
return util::make_unique<XmlResource>(ResourceFile{}, std::move(root), std::move(string_pool));
}
std::unique_ptr<Node> Namespace::Clone() {

View File

@@ -129,21 +129,21 @@ class XmlResource {
public:
ResourceFile file;
std::unique_ptr<xml::Node> root;
StringPool string_pool;
};
/**
* Inflates an XML DOM from a text stream, logging errors to the logger.
* Returns the root node on success, or nullptr on failure.
*/
std::unique_ptr<XmlResource> Inflate(std::istream* in, IDiagnostics* diag,
const Source& source);
std::unique_ptr<XmlResource> Inflate(std::istream* in, IDiagnostics* diag, const Source& source);
/**
* Inflates an XML DOM from a binary ResXMLTree, logging errors to the logger.
* Returns the root node on success, or nullptr on failure.
*/
std::unique_ptr<XmlResource> Inflate(const void* data, size_t data_len,
IDiagnostics* diag, const Source& source);
std::unique_ptr<XmlResource> Inflate(const void* data, size_t data_len, IDiagnostics* diag,
const Source& source);
Element* FindRootElement(XmlResource* doc);
Element* FindRootElement(Node* node);