AAPT2: Implement density stripping and initial Split support
When a preferred density is supplied, the closest matching densities will be selected, the rest stripped from the APK. Split support will be enabled in a later CL. Command line support is still needed, but the foundation is ready. Bug:25958912 Change-Id: I56d599806b4ec4ffa24e17aad48d47130ca05c08
This commit is contained in:
@@ -47,6 +47,7 @@ sources := \
|
||||
proto/ProtoHelpers.cpp \
|
||||
proto/TableProtoDeserializer.cpp \
|
||||
proto/TableProtoSerializer.cpp \
|
||||
split/TableSplitter.cpp \
|
||||
unflatten/BinaryResourceParser.cpp \
|
||||
unflatten/ResChunkPullParser.cpp \
|
||||
util/BigBuffer.cpp \
|
||||
@@ -90,6 +91,7 @@ testSources := \
|
||||
link/XmlReferenceLinker_test.cpp \
|
||||
process/SymbolTable_test.cpp \
|
||||
proto/TableProtoSerializer_test.cpp \
|
||||
split/TableSplitter_test.cpp \
|
||||
util/BigBuffer_test.cpp \
|
||||
util/Maybe_test.cpp \
|
||||
util/StringPiece_test.cpp \
|
||||
@@ -103,6 +105,7 @@ testSources := \
|
||||
ResourceParser_test.cpp \
|
||||
ResourceTable_test.cpp \
|
||||
ResourceUtils_test.cpp \
|
||||
SdkConstants_test.cpp \
|
||||
StringPool_test.cpp \
|
||||
ValueVisitor_test.cpp \
|
||||
xml/XmlDom_test.cpp \
|
||||
|
||||
@@ -30,7 +30,8 @@
|
||||
|
||||
namespace aapt {
|
||||
|
||||
struct PrintVisitor : public ValueVisitor {
|
||||
class PrintVisitor : public ValueVisitor {
|
||||
public:
|
||||
using ValueVisitor::visit;
|
||||
|
||||
void visit(Attribute* attr) override {
|
||||
@@ -69,7 +70,11 @@ struct PrintVisitor : public ValueVisitor {
|
||||
for (const auto& entry : style->entries) {
|
||||
std::cout << "\n ";
|
||||
if (entry.key.name) {
|
||||
std::cout << entry.key.name.value().package << ":" << entry.key.name.value().entry;
|
||||
const ResourceName& name = entry.key.name.value();
|
||||
if (!name.package.empty()) {
|
||||
std::cout << name.package << ":";
|
||||
}
|
||||
std::cout << name.entry;
|
||||
}
|
||||
|
||||
if (entry.key.id) {
|
||||
@@ -89,7 +94,21 @@ struct PrintVisitor : public ValueVisitor {
|
||||
}
|
||||
|
||||
void visit(Styleable* styleable) override {
|
||||
styleable->print(&std::cout);
|
||||
std::cout << "(styleable)";
|
||||
for (const auto& attr : styleable->entries) {
|
||||
std::cout << "\n ";
|
||||
if (attr.name) {
|
||||
const ResourceName& name = attr.name.value();
|
||||
if (!name.package.empty()) {
|
||||
std::cout << name.package << ":";
|
||||
}
|
||||
std::cout << name.entry;
|
||||
}
|
||||
|
||||
if (attr.id) {
|
||||
std::cout << "(" << attr.id.value() << ")";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void visitItem(Item* item) override {
|
||||
@@ -97,7 +116,9 @@ struct PrintVisitor : public ValueVisitor {
|
||||
}
|
||||
};
|
||||
|
||||
void Debug::printTable(ResourceTable* table) {
|
||||
void Debug::printTable(ResourceTable* table, const DebugPrintTableOptions& options) {
|
||||
PrintVisitor visitor;
|
||||
|
||||
for (auto& package : table->packages) {
|
||||
std::cout << "Package name=" << package->name;
|
||||
if (package->id) {
|
||||
@@ -106,7 +127,7 @@ void Debug::printTable(ResourceTable* table) {
|
||||
std::cout << std::endl;
|
||||
|
||||
for (const auto& type : package->types) {
|
||||
std::cout << " type " << type->type;
|
||||
std::cout << "\n type " << type->type;
|
||||
if (type->id) {
|
||||
std::cout << " id=" << std::hex << (int) type->id.value() << std::dec;
|
||||
}
|
||||
@@ -142,10 +163,12 @@ void Debug::printTable(ResourceTable* table) {
|
||||
|
||||
std::cout << std::endl;
|
||||
|
||||
PrintVisitor visitor;
|
||||
for (const auto& value : entry->values) {
|
||||
std::cout << " (" << value->config << ") ";
|
||||
value->value->accept(&visitor);
|
||||
if (options.showSources && !value->value->getSource().path.empty()) {
|
||||
std::cout << " src=" << value->value->getSource();
|
||||
}
|
||||
std::cout << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,8 +25,12 @@
|
||||
|
||||
namespace aapt {
|
||||
|
||||
struct DebugPrintTableOptions {
|
||||
bool showSources = false;
|
||||
};
|
||||
|
||||
struct Debug {
|
||||
static void printTable(ResourceTable* table);
|
||||
static void printTable(ResourceTable* table, const DebugPrintTableOptions& options = {});
|
||||
static void printStyleGraph(ResourceTable* table,
|
||||
const ResourceName& targetStyle);
|
||||
static void dumpHex(const void* data, size_t len);
|
||||
|
||||
@@ -277,20 +277,31 @@ bool ResourceTable::addFileReference(const ResourceNameRef& name,
|
||||
const Source& source,
|
||||
const StringPiece16& path,
|
||||
IDiagnostics* diag) {
|
||||
return addFileReference(name, config, source, path, resolveValueCollision, diag);
|
||||
return addFileReferenceImpl(name, config, source, path, nullptr, kValidNameChars, diag);
|
||||
}
|
||||
|
||||
bool ResourceTable::addFileReference(const ResourceNameRef& name,
|
||||
const ConfigDescription& config,
|
||||
const Source& source,
|
||||
const StringPiece16& path,
|
||||
std::function<int(Value*,Value*)> conflictResolver,
|
||||
IDiagnostics* diag) {
|
||||
bool ResourceTable::addFileReferenceAllowMangled(const ResourceNameRef& name,
|
||||
const ConfigDescription& config,
|
||||
const Source& source,
|
||||
const StringPiece16& path,
|
||||
io::IFile* file,
|
||||
IDiagnostics* diag) {
|
||||
return addFileReferenceImpl(name, config, source, path, file, kValidNameMangledChars, diag);
|
||||
}
|
||||
|
||||
bool ResourceTable::addFileReferenceImpl(const ResourceNameRef& name,
|
||||
const ConfigDescription& config,
|
||||
const Source& source,
|
||||
const StringPiece16& path,
|
||||
io::IFile* file,
|
||||
const char16_t* validChars,
|
||||
IDiagnostics* diag) {
|
||||
std::unique_ptr<FileReference> fileRef = util::make_unique<FileReference>(
|
||||
stringPool.makeRef(path));
|
||||
fileRef->setSource(source);
|
||||
fileRef->file = file;
|
||||
return addResourceImpl(name, ResourceId{}, config, StringPiece{}, std::move(fileRef),
|
||||
kValidNameChars, conflictResolver, diag);
|
||||
kValidNameChars, resolveValueCollision, diag);
|
||||
}
|
||||
|
||||
bool ResourceTable::addResourceAllowMangled(const ResourceNameRef& name,
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include "ResourceValues.h"
|
||||
#include "Source.h"
|
||||
#include "StringPool.h"
|
||||
#include "io/File.h"
|
||||
|
||||
#include <android-base/macros.h>
|
||||
#include <map>
|
||||
@@ -202,17 +203,17 @@ public:
|
||||
IDiagnostics* diag);
|
||||
|
||||
bool addFileReference(const ResourceNameRef& name,
|
||||
const ConfigDescription& config,
|
||||
const Source& source,
|
||||
const StringPiece16& path,
|
||||
IDiagnostics* diag);
|
||||
const ConfigDescription& config,
|
||||
const Source& source,
|
||||
const StringPiece16& path,
|
||||
IDiagnostics* diag);
|
||||
|
||||
bool addFileReference(const ResourceNameRef& name,
|
||||
const ConfigDescription& config,
|
||||
const Source& source,
|
||||
const StringPiece16& path,
|
||||
std::function<int(Value*,Value*)> conflictResolver,
|
||||
IDiagnostics* diag);
|
||||
bool addFileReferenceAllowMangled(const ResourceNameRef& name,
|
||||
const ConfigDescription& config,
|
||||
const Source& source,
|
||||
const StringPiece16& path,
|
||||
io::IFile* file,
|
||||
IDiagnostics* diag);
|
||||
|
||||
/**
|
||||
* Same as addResource, but doesn't verify the validity of the name. This is used
|
||||
@@ -280,6 +281,14 @@ public:
|
||||
private:
|
||||
ResourceTablePackage* findOrCreatePackage(const StringPiece16& name);
|
||||
|
||||
bool addFileReferenceImpl(const ResourceNameRef& name,
|
||||
const ConfigDescription& config,
|
||||
const Source& source,
|
||||
const StringPiece16& path,
|
||||
io::IFile* file,
|
||||
const char16_t* validChars,
|
||||
IDiagnostics* diag);
|
||||
|
||||
bool addResourceImpl(const ResourceNameRef& name,
|
||||
ResourceId resId,
|
||||
const ConfigDescription& config,
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include "ResourceUtils.h"
|
||||
#include "ResourceValues.h"
|
||||
#include "ValueVisitor.h"
|
||||
#include "io/File.h"
|
||||
#include "util/Util.h"
|
||||
|
||||
#include <androidfw/ResourceTypes.h>
|
||||
@@ -190,6 +191,7 @@ bool FileReference::flatten(android::Res_value* outValue) const {
|
||||
|
||||
FileReference* FileReference::clone(StringPool* newPool) const {
|
||||
FileReference* fr = new FileReference(newPool->makeRef(*path));
|
||||
fr->file = file;
|
||||
fr->mComment = mComment;
|
||||
fr->mSource = mSource;
|
||||
return fr;
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "Diagnostics.h"
|
||||
#include "Resource.h"
|
||||
#include "StringPool.h"
|
||||
#include "io/File.h"
|
||||
#include "util/Maybe.h"
|
||||
|
||||
#include <array>
|
||||
@@ -226,6 +227,11 @@ private:
|
||||
struct FileReference : public BaseItem<FileReference> {
|
||||
StringPool::Ref path;
|
||||
|
||||
/**
|
||||
* A handle to the file object from which this file can be read.
|
||||
*/
|
||||
io::IFile* file = nullptr;
|
||||
|
||||
FileReference() = default;
|
||||
FileReference(const StringPool::Ref& path);
|
||||
|
||||
|
||||
38
tools/aapt2/SdkConstants_test.cpp
Normal file
38
tools/aapt2/SdkConstants_test.cpp
Normal file
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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 "SdkConstants.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
TEST(SdkConstantsTest, FirstAttributeIsSdk1) {
|
||||
EXPECT_EQ(1u, findAttributeSdkLevel(ResourceId(0x01010000)));
|
||||
}
|
||||
|
||||
TEST(SdkConstantsTest, AllAttributesAfterLollipopAreLollipopMR1) {
|
||||
EXPECT_EQ(SDK_LOLLIPOP, findAttributeSdkLevel(ResourceId(0x010103f7)));
|
||||
EXPECT_EQ(SDK_LOLLIPOP, findAttributeSdkLevel(ResourceId(0x010104ce)));
|
||||
|
||||
EXPECT_EQ(SDK_LOLLIPOP_MR1, findAttributeSdkLevel(ResourceId(0x010104cf)));
|
||||
EXPECT_EQ(SDK_LOLLIPOP_MR1, findAttributeSdkLevel(ResourceId(0x010104d8)));
|
||||
|
||||
EXPECT_EQ(SDK_LOLLIPOP_MR1, findAttributeSdkLevel(ResourceId(0x010104d9)));
|
||||
EXPECT_EQ(SDK_LOLLIPOP_MR1, findAttributeSdkLevel(ResourceId(0x0101ffff)));
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
@@ -424,8 +424,17 @@ static bool compileFile(IAaptContext* context, const CompileOptions& options,
|
||||
class CompileContext : public IAaptContext {
|
||||
private:
|
||||
StdErrDiagnostics mDiagnostics;
|
||||
bool mVerbose = false;
|
||||
|
||||
public:
|
||||
void setVerbose(bool val) {
|
||||
mVerbose = val;
|
||||
}
|
||||
|
||||
bool verbose() override {
|
||||
return mVerbose;
|
||||
}
|
||||
|
||||
IDiagnostics* getDiagnostics() override {
|
||||
return &mDiagnostics;
|
||||
}
|
||||
@@ -453,8 +462,10 @@ public:
|
||||
* Entry point for compilation phase. Parses arguments and dispatches to the correct steps.
|
||||
*/
|
||||
int compile(const std::vector<StringPiece>& args) {
|
||||
CompileContext context;
|
||||
CompileOptions options;
|
||||
|
||||
bool verbose = false;
|
||||
Flags flags = Flags()
|
||||
.requiredFlag("-o", "Output path", &options.outputPath)
|
||||
.optionalFlag("--dir", "Directory to scan for resources", &options.resDir)
|
||||
@@ -462,12 +473,13 @@ int compile(const std::vector<StringPiece>& args) {
|
||||
"(en-XA and ar-XB)", &options.pseudolocalize)
|
||||
.optionalSwitch("--legacy", "Treat errors that used to be valid in AAPT as warnings",
|
||||
&options.legacyMode)
|
||||
.optionalSwitch("-v", "Enables verbose logging", &options.verbose);
|
||||
.optionalSwitch("-v", "Enables verbose logging", &verbose);
|
||||
if (!flags.parse("aapt2 compile", args, &std::cerr)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
CompileContext context;
|
||||
context.setVerbose(verbose);
|
||||
|
||||
std::unique_ptr<IArchiveWriter> archiveWriter;
|
||||
|
||||
std::vector<ResourcePathData> inputData;
|
||||
|
||||
@@ -103,21 +103,32 @@ public:
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool verbose() override {
|
||||
return mVerbose;
|
||||
}
|
||||
|
||||
void setVerbose(bool val) {
|
||||
mVerbose = val;
|
||||
}
|
||||
|
||||
private:
|
||||
StdErrDiagnostics mDiagnostics;
|
||||
bool mVerbose = false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Entry point for dump command.
|
||||
*/
|
||||
int dump(const std::vector<StringPiece>& args) {
|
||||
//DumpOptions options;
|
||||
Flags flags = Flags();
|
||||
bool verbose = false;
|
||||
Flags flags = Flags()
|
||||
.optionalSwitch("-v", "increase verbosity of output", &verbose);
|
||||
if (!flags.parse("aapt2 dump", args, &std::cerr)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
DumpContext context;
|
||||
context.setVerbose(verbose);
|
||||
|
||||
for (const std::string& arg : flags.getArgs()) {
|
||||
tryDumpFile(&context, arg);
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
#include "process/IResourceTableConsumer.h"
|
||||
#include "process/SymbolTable.h"
|
||||
#include "proto/ProtoSerialize.h"
|
||||
#include "split/TableSplitter.h"
|
||||
#include "unflatten/BinaryResourceParser.h"
|
||||
#include "util/Files.h"
|
||||
#include "util/StringPiece.h"
|
||||
@@ -63,15 +64,14 @@ struct LinkOptions {
|
||||
bool noAutoVersion = false;
|
||||
bool staticLib = false;
|
||||
bool generateNonFinalIds = false;
|
||||
bool verbose = false;
|
||||
bool outputToDirectory = false;
|
||||
bool autoAddOverlay = false;
|
||||
bool doNotCompressAnything = false;
|
||||
std::vector<std::string> extensionsToNotCompress;
|
||||
Maybe<std::u16string> privateSymbols;
|
||||
ManifestFixerOptions manifestFixerOptions;
|
||||
IConfigFilter* configFilter = nullptr;
|
||||
std::unordered_set<std::string> products;
|
||||
TableSplitterOptions tableSplitterOptions;
|
||||
};
|
||||
|
||||
struct LinkContext : public IAaptContext {
|
||||
@@ -80,6 +80,7 @@ struct LinkContext : public IAaptContext {
|
||||
std::u16string mCompilationPackage;
|
||||
uint8_t mPackageId;
|
||||
std::unique_ptr<ISymbolTable> mSymbols;
|
||||
bool mVerbose = false;
|
||||
|
||||
IDiagnostics* getDiagnostics() override {
|
||||
return &mDiagnostics;
|
||||
@@ -100,8 +101,376 @@ struct LinkContext : public IAaptContext {
|
||||
ISymbolTable* getExternalSymbols() override {
|
||||
return mSymbols.get();
|
||||
}
|
||||
|
||||
bool verbose() override {
|
||||
return mVerbose;
|
||||
}
|
||||
};
|
||||
|
||||
static bool copyFileToArchive(io::IFile* file, const std::string& outPath,
|
||||
uint32_t compressionFlags,
|
||||
IArchiveWriter* writer, IAaptContext* context) {
|
||||
std::unique_ptr<io::IData> data = file->openAsData();
|
||||
if (!data) {
|
||||
context->getDiagnostics()->error(DiagMessage(file->getSource())
|
||||
<< "failed to open file");
|
||||
return false;
|
||||
}
|
||||
|
||||
CompiledFileInputStream inputStream(data->data(), data->size());
|
||||
if (!inputStream.CompiledFile()) {
|
||||
context->getDiagnostics()->error(DiagMessage(file->getSource())
|
||||
<< "invalid compiled file header");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (context->verbose()) {
|
||||
context->getDiagnostics()->note(DiagMessage() << "writing " << outPath << " to archive");
|
||||
}
|
||||
|
||||
if (writer->startEntry(outPath, compressionFlags)) {
|
||||
if (writer->writeEntry(reinterpret_cast<const uint8_t*>(inputStream.data()),
|
||||
inputStream.size())) {
|
||||
if (writer->finishEntry()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context->getDiagnostics()->error(DiagMessage() << "failed to write file " << outPath);
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool flattenXml(xml::XmlResource* xmlRes, const StringPiece& path, Maybe<size_t> maxSdkLevel,
|
||||
bool keepRawValues, IArchiveWriter* writer, IAaptContext* context) {
|
||||
BigBuffer buffer(1024);
|
||||
XmlFlattenerOptions options = {};
|
||||
options.keepRawValues = keepRawValues;
|
||||
options.maxSdkLevel = maxSdkLevel;
|
||||
XmlFlattener flattener(&buffer, options);
|
||||
if (!flattener.consume(context, xmlRes)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (context->verbose()) {
|
||||
DiagMessage msg;
|
||||
msg << "writing " << path << " to archive";
|
||||
if (maxSdkLevel) {
|
||||
msg << " maxSdkLevel=" << maxSdkLevel.value();
|
||||
}
|
||||
context->getDiagnostics()->note(msg);
|
||||
}
|
||||
|
||||
if (writer->startEntry(path, ArchiveEntry::kCompress)) {
|
||||
if (writer->writeEntry(buffer)) {
|
||||
if (writer->finishEntry()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
context->getDiagnostics()->error(DiagMessage() << "failed to write " << path << " to archive");
|
||||
return false;
|
||||
}
|
||||
|
||||
/*static std::unique_ptr<ResourceTable> loadTable(const Source& source, const void* data, size_t len,
|
||||
IDiagnostics* diag) {
|
||||
std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>();
|
||||
BinaryResourceParser parser(diag, table.get(), source, data, len);
|
||||
if (!parser.parse()) {
|
||||
return {};
|
||||
}
|
||||
return table;
|
||||
}*/
|
||||
|
||||
static std::unique_ptr<ResourceTable> loadTableFromPb(const Source& source,
|
||||
const void* data, size_t len,
|
||||
IDiagnostics* diag) {
|
||||
pb::ResourceTable pbTable;
|
||||
if (!pbTable.ParseFromArray(data, len)) {
|
||||
diag->error(DiagMessage(source) << "invalid compiled table");
|
||||
return {};
|
||||
}
|
||||
|
||||
std::unique_ptr<ResourceTable> table = deserializeTableFromPb(pbTable, source, diag);
|
||||
if (!table) {
|
||||
return {};
|
||||
}
|
||||
return table;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inflates an XML file from the source path.
|
||||
*/
|
||||
static std::unique_ptr<xml::XmlResource> loadXml(const std::string& path, IDiagnostics* diag) {
|
||||
std::ifstream fin(path, std::ifstream::binary);
|
||||
if (!fin) {
|
||||
diag->error(DiagMessage(path) << strerror(errno));
|
||||
return {};
|
||||
}
|
||||
return xml::inflate(&fin, diag, Source(path));
|
||||
}
|
||||
|
||||
static std::unique_ptr<xml::XmlResource> loadBinaryXmlSkipFileExport(const Source& source,
|
||||
const void* data, size_t len,
|
||||
IDiagnostics* diag) {
|
||||
CompiledFileInputStream inputStream(data, len);
|
||||
if (!inputStream.CompiledFile()) {
|
||||
diag->error(DiagMessage(source) << "invalid compiled file header");
|
||||
return {};
|
||||
}
|
||||
|
||||
const uint8_t* xmlData = reinterpret_cast<const uint8_t*>(inputStream.data());
|
||||
const size_t xmlDataLen = inputStream.size();
|
||||
|
||||
std::unique_ptr<xml::XmlResource> xmlRes = xml::inflate(xmlData, xmlDataLen, diag, source);
|
||||
if (!xmlRes) {
|
||||
return {};
|
||||
}
|
||||
return xmlRes;
|
||||
}
|
||||
|
||||
static std::unique_ptr<ResourceFile> loadFileExportHeader(const Source& source,
|
||||
const void* data, size_t len,
|
||||
IDiagnostics* diag) {
|
||||
CompiledFileInputStream inputStream(data, len);
|
||||
const pb::CompiledFile* pbFile = inputStream.CompiledFile();
|
||||
if (!pbFile) {
|
||||
diag->error(DiagMessage(source) << "invalid compiled file header");
|
||||
return {};
|
||||
}
|
||||
|
||||
std::unique_ptr<ResourceFile> resFile = deserializeCompiledFileFromPb(*pbFile, source, diag);
|
||||
if (!resFile) {
|
||||
return {};
|
||||
}
|
||||
return resFile;
|
||||
}
|
||||
|
||||
struct ResourceFileFlattenerOptions {
|
||||
bool noAutoVersion = false;
|
||||
bool keepRawValues = false;
|
||||
bool doNotCompressAnything = false;
|
||||
std::vector<std::string> extensionsToNotCompress;
|
||||
};
|
||||
|
||||
class ResourceFileFlattener {
|
||||
public:
|
||||
ResourceFileFlattener(const ResourceFileFlattenerOptions& options,
|
||||
IAaptContext* context, proguard::KeepSet* keepSet) :
|
||||
mOptions(options), mContext(context), mKeepSet(keepSet) {
|
||||
}
|
||||
|
||||
bool flatten(ResourceTable* table, IArchiveWriter* archiveWriter);
|
||||
|
||||
private:
|
||||
struct FileOperation {
|
||||
io::IFile* fileToCopy;
|
||||
std::unique_ptr<xml::XmlResource> xmlToFlatten;
|
||||
std::string dstPath;
|
||||
};
|
||||
|
||||
uint32_t getCompressionFlags(const StringPiece& str);
|
||||
|
||||
std::unique_ptr<xml::XmlResource> linkAndVersionXmlFile(const ResourceEntry* entry,
|
||||
const ResourceFile& fileDesc,
|
||||
io::IFile* file,
|
||||
ResourceTable* table);
|
||||
|
||||
ResourceFileFlattenerOptions mOptions;
|
||||
IAaptContext* mContext;
|
||||
proguard::KeepSet* mKeepSet;
|
||||
};
|
||||
|
||||
uint32_t ResourceFileFlattener::getCompressionFlags(const StringPiece& str) {
|
||||
if (mOptions.doNotCompressAnything) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (const std::string& extension : mOptions.extensionsToNotCompress) {
|
||||
if (util::stringEndsWith<char>(str, extension)) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return ArchiveEntry::kCompress;
|
||||
}
|
||||
|
||||
std::unique_ptr<xml::XmlResource> ResourceFileFlattener::linkAndVersionXmlFile(
|
||||
const ResourceEntry* entry,
|
||||
const ResourceFile& fileDesc,
|
||||
io::IFile* file,
|
||||
ResourceTable* table) {
|
||||
const StringPiece srcPath = file->getSource().path;
|
||||
if (mContext->verbose()) {
|
||||
mContext->getDiagnostics()->note(DiagMessage() << "linking " << srcPath);
|
||||
}
|
||||
|
||||
std::unique_ptr<io::IData> data = file->openAsData();
|
||||
if (!data) {
|
||||
mContext->getDiagnostics()->error(DiagMessage(file->getSource()) << "failed to open file");
|
||||
return {};
|
||||
}
|
||||
|
||||
std::unique_ptr<xml::XmlResource> xmlRes;
|
||||
if (util::stringEndsWith<char>(srcPath, ".flat")) {
|
||||
xmlRes = loadBinaryXmlSkipFileExport(file->getSource(), data->data(), data->size(),
|
||||
mContext->getDiagnostics());
|
||||
} else {
|
||||
xmlRes = xml::inflate(data->data(), data->size(), mContext->getDiagnostics(),
|
||||
file->getSource());
|
||||
}
|
||||
|
||||
if (!xmlRes) {
|
||||
return {};
|
||||
}
|
||||
|
||||
// Copy the the file description header.
|
||||
xmlRes->file = fileDesc;
|
||||
|
||||
XmlReferenceLinker xmlLinker;
|
||||
if (!xmlLinker.consume(mContext, xmlRes.get())) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (!proguard::collectProguardRules(xmlRes->file.source, xmlRes.get(), mKeepSet)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (!mOptions.noAutoVersion) {
|
||||
// Find the first SDK level used that is higher than this defined config and
|
||||
// not superseded by a lower or equal SDK level resource.
|
||||
for (int sdkLevel : xmlLinker.getSdkLevels()) {
|
||||
if (sdkLevel > xmlRes->file.config.sdkVersion) {
|
||||
if (!shouldGenerateVersionedResource(entry, xmlRes->file.config, sdkLevel)) {
|
||||
// If we shouldn't generate a versioned resource, stop checking.
|
||||
break;
|
||||
}
|
||||
|
||||
ResourceFile versionedFileDesc = xmlRes->file;
|
||||
versionedFileDesc.config.sdkVersion = sdkLevel;
|
||||
|
||||
if (mContext->verbose()) {
|
||||
mContext->getDiagnostics()->note(DiagMessage(versionedFileDesc.source)
|
||||
<< "auto-versioning resource from config '"
|
||||
<< xmlRes->file.config << "' -> '"
|
||||
<< versionedFileDesc.config << "'");
|
||||
}
|
||||
|
||||
std::u16string genPath = util::utf8ToUtf16(ResourceUtils::buildResourceFileName(
|
||||
versionedFileDesc, mContext->getNameMangler()));
|
||||
|
||||
bool added = table->addFileReferenceAllowMangled(versionedFileDesc.name,
|
||||
versionedFileDesc.config,
|
||||
versionedFileDesc.source,
|
||||
genPath,
|
||||
file,
|
||||
mContext->getDiagnostics());
|
||||
if (!added) {
|
||||
return {};
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return xmlRes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Do not insert or remove any resources while executing in this function. It will
|
||||
* corrupt the iteration order.
|
||||
*/
|
||||
bool ResourceFileFlattener::flatten(ResourceTable* table, IArchiveWriter* archiveWriter) {
|
||||
bool error = false;
|
||||
std::map<std::pair<ConfigDescription, StringPiece16>, FileOperation> configSortedFiles;
|
||||
|
||||
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.
|
||||
configSortedFiles.clear();
|
||||
for (auto& entry : type->entries) {
|
||||
// Iterate via indices because auto generated values can be inserted ahead of
|
||||
// the value being processed.
|
||||
for (size_t i = 0; i < entry->values.size(); i++) {
|
||||
ResourceConfigValue* configValue = entry->values[i].get();
|
||||
|
||||
FileReference* fileRef = valueCast<FileReference>(configValue->value.get());
|
||||
if (!fileRef) {
|
||||
continue;
|
||||
}
|
||||
|
||||
io::IFile* file = fileRef->file;
|
||||
if (!file) {
|
||||
mContext->getDiagnostics()->error(DiagMessage(fileRef->getSource())
|
||||
<< "file not found");
|
||||
return false;
|
||||
}
|
||||
|
||||
FileOperation fileOp;
|
||||
fileOp.dstPath = util::utf16ToUtf8(*fileRef->path);
|
||||
|
||||
const StringPiece srcPath = file->getSource().path;
|
||||
if (type->type != ResourceType::kRaw &&
|
||||
(util::stringEndsWith<char>(srcPath, ".xml.flat") ||
|
||||
util::stringEndsWith<char>(srcPath, ".xml"))) {
|
||||
ResourceFile fileDesc;
|
||||
fileDesc.config = configValue->config;
|
||||
fileDesc.name = ResourceName(pkg->name, type->type, entry->name);
|
||||
fileDesc.source = fileRef->getSource();
|
||||
fileOp.xmlToFlatten = linkAndVersionXmlFile(entry.get(), fileDesc,
|
||||
file, table);
|
||||
if (!fileOp.xmlToFlatten) {
|
||||
error = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
} else {
|
||||
fileOp.fileToCopy = file;
|
||||
}
|
||||
|
||||
// NOTE(adamlesinski): Explicitly construct a StringPiece16 here, or else
|
||||
// we end up copying the string in the std::make_pair() method, then creating
|
||||
// a StringPiece16 from the copy, which would cause us to end up referencing
|
||||
// garbage in the map.
|
||||
const StringPiece16 entryName(entry->name);
|
||||
configSortedFiles[std::make_pair(configValue->config, entryName)] =
|
||||
std::move(fileOp);
|
||||
}
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Now flatten the sorted values.
|
||||
for (auto& mapEntry : configSortedFiles) {
|
||||
const ConfigDescription& config = mapEntry.first.first;
|
||||
const FileOperation& fileOp = mapEntry.second;
|
||||
|
||||
if (fileOp.xmlToFlatten) {
|
||||
Maybe<size_t> maxSdkLevel;
|
||||
if (!mOptions.noAutoVersion) {
|
||||
maxSdkLevel = std::max<size_t>(config.sdkVersion, 1u);
|
||||
}
|
||||
|
||||
bool result = flattenXml(fileOp.xmlToFlatten.get(), fileOp.dstPath, maxSdkLevel,
|
||||
mOptions.keepRawValues,
|
||||
archiveWriter, mContext);
|
||||
if (!result) {
|
||||
error = true;
|
||||
}
|
||||
} else {
|
||||
bool result = copyFileToArchive(fileOp.fileToCopy, fileOp.dstPath,
|
||||
getCompressionFlags(fileOp.dstPath),
|
||||
archiveWriter, mContext);
|
||||
if (!result) {
|
||||
error = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return !error;
|
||||
}
|
||||
|
||||
class LinkCommand {
|
||||
public:
|
||||
LinkCommand(LinkContext* context, const LinkOptions& options) :
|
||||
@@ -123,7 +492,7 @@ public:
|
||||
std::unique_ptr<ISymbolTable> createSymbolTableFromIncludePaths() {
|
||||
AssetManagerSymbolTableBuilder builder;
|
||||
for (const std::string& path : mOptions.includePaths) {
|
||||
if (mOptions.verbose) {
|
||||
if (mContext->verbose()) {
|
||||
mContext->getDiagnostics()->note(DiagMessage(path) << "loading include path");
|
||||
}
|
||||
|
||||
@@ -140,125 +509,6 @@ public:
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
std::unique_ptr<ResourceTable> loadTable(const Source& source, const void* data, size_t len) {
|
||||
std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>();
|
||||
BinaryResourceParser parser(mContext, table.get(), source, data, len);
|
||||
if (!parser.parse()) {
|
||||
return {};
|
||||
}
|
||||
return table;
|
||||
}
|
||||
|
||||
std::unique_ptr<ResourceTable> loadTableFromPb(const Source& source,
|
||||
const void* data, size_t len) {
|
||||
pb::ResourceTable pbTable;
|
||||
if (!pbTable.ParseFromArray(data, len)) {
|
||||
mContext->getDiagnostics()->error(DiagMessage(source) << "invalid compiled table");
|
||||
return {};
|
||||
}
|
||||
|
||||
std::unique_ptr<ResourceTable> table = deserializeTableFromPb(pbTable, source,
|
||||
mContext->getDiagnostics());
|
||||
if (!table) {
|
||||
return {};
|
||||
}
|
||||
return table;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inflates an XML file from the source path.
|
||||
*/
|
||||
static std::unique_ptr<xml::XmlResource> loadXml(const std::string& path, IDiagnostics* diag) {
|
||||
std::ifstream fin(path, std::ifstream::binary);
|
||||
if (!fin) {
|
||||
diag->error(DiagMessage(path) << strerror(errno));
|
||||
return {};
|
||||
}
|
||||
|
||||
return xml::inflate(&fin, diag, Source(path));
|
||||
}
|
||||
|
||||
static std::unique_ptr<xml::XmlResource> loadBinaryXmlSkipFileExport(
|
||||
const Source& source,
|
||||
const void* data, size_t len,
|
||||
IDiagnostics* diag) {
|
||||
CompiledFileInputStream inputStream(data, len);
|
||||
if (!inputStream.CompiledFile()) {
|
||||
diag->error(DiagMessage(source) << "invalid compiled file header");
|
||||
return {};
|
||||
}
|
||||
|
||||
const uint8_t* xmlData = reinterpret_cast<const uint8_t*>(inputStream.data());
|
||||
const size_t xmlDataLen = inputStream.size();
|
||||
|
||||
std::unique_ptr<xml::XmlResource> xmlRes = xml::inflate(xmlData, xmlDataLen, diag, source);
|
||||
if (!xmlRes) {
|
||||
return {};
|
||||
}
|
||||
return xmlRes;
|
||||
}
|
||||
|
||||
static std::unique_ptr<ResourceFile> loadFileExportHeader(const Source& source,
|
||||
const void* data, size_t len,
|
||||
IDiagnostics* diag) {
|
||||
CompiledFileInputStream inputStream(data, len);
|
||||
const pb::CompiledFile* pbFile = inputStream.CompiledFile();
|
||||
if (!pbFile) {
|
||||
diag->error(DiagMessage(source) << "invalid compiled file header");
|
||||
return {};
|
||||
}
|
||||
|
||||
std::unique_ptr<ResourceFile> resFile = deserializeCompiledFileFromPb(*pbFile, source,
|
||||
diag);
|
||||
if (!resFile) {
|
||||
return {};
|
||||
}
|
||||
return resFile;
|
||||
}
|
||||
|
||||
uint32_t getCompressionFlags(const StringPiece& str) {
|
||||
if (mOptions.doNotCompressAnything) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (const std::string& extension : mOptions.extensionsToNotCompress) {
|
||||
if (util::stringEndsWith<char>(str, extension)) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return ArchiveEntry::kCompress;
|
||||
}
|
||||
|
||||
bool copyFileToArchive(io::IFile* file, const std::string& outPath,
|
||||
IArchiveWriter* writer) {
|
||||
std::unique_ptr<io::IData> data = file->openAsData();
|
||||
if (!data) {
|
||||
mContext->getDiagnostics()->error(DiagMessage(file->getSource())
|
||||
<< "failed to open file");
|
||||
return false;
|
||||
}
|
||||
|
||||
CompiledFileInputStream inputStream(data->data(), data->size());
|
||||
if (!inputStream.CompiledFile()) {
|
||||
mContext->getDiagnostics()->error(DiagMessage(file->getSource())
|
||||
<< "invalid compiled file header");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (writer->startEntry(outPath, getCompressionFlags(outPath))) {
|
||||
if (writer->writeEntry(reinterpret_cast<const uint8_t*>(inputStream.data()),
|
||||
inputStream.size())) {
|
||||
if (writer->finishEntry()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mContext->getDiagnostics()->error(
|
||||
DiagMessage(mOptions.outputPath) << "failed to write file " << outPath);
|
||||
return false;
|
||||
}
|
||||
|
||||
Maybe<AppInfo> extractAppInfoFromManifest(xml::XmlResource* xmlRes) {
|
||||
// Make sure the first element is <manifest> with package attribute.
|
||||
if (xml::Element* manifestEl = xml::findRootElement(xmlRes->root.get())) {
|
||||
@@ -349,28 +599,6 @@ public:
|
||||
return false;
|
||||
}
|
||||
|
||||
bool flattenXml(xml::XmlResource* xmlRes, const StringPiece& path, Maybe<size_t> maxSdkLevel,
|
||||
IArchiveWriter* writer) {
|
||||
BigBuffer buffer(1024);
|
||||
XmlFlattenerOptions options = {};
|
||||
options.keepRawValues = mOptions.staticLib;
|
||||
options.maxSdkLevel = maxSdkLevel;
|
||||
XmlFlattener flattener(&buffer, options);
|
||||
if (!flattener.consume(mContext, xmlRes)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (writer->startEntry(path, ArchiveEntry::kCompress)) {
|
||||
if (writer->writeEntry(buffer)) {
|
||||
if (writer->finishEntry()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
mContext->getDiagnostics()->error(
|
||||
DiagMessage() << "failed to write " << path << " to archive");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool writeJavaFile(ResourceTable* table, const StringPiece16& packageNameToGenerate,
|
||||
const StringPiece16& outPackage, JavaClassGeneratorOptions javaOptions) {
|
||||
@@ -456,7 +684,7 @@ public:
|
||||
}
|
||||
|
||||
bool mergeResourceTable(io::IFile* file, bool override) {
|
||||
if (mOptions.verbose) {
|
||||
if (mContext->verbose()) {
|
||||
mContext->getDiagnostics()->note(DiagMessage() << "linking " << file->getSource());
|
||||
}
|
||||
|
||||
@@ -467,8 +695,9 @@ public:
|
||||
return false;
|
||||
}
|
||||
|
||||
std::unique_ptr<ResourceTable> table = loadTableFromPb(file->getSource(), data->data(),
|
||||
data->size());
|
||||
std::unique_ptr<ResourceTable> table = loadTableFromPb(file->getSource(),
|
||||
data->data(), data->size(),
|
||||
mContext->getDiagnostics());
|
||||
if (!table) {
|
||||
return false;
|
||||
}
|
||||
@@ -483,7 +712,7 @@ public:
|
||||
}
|
||||
|
||||
bool mergeCompiledFile(io::IFile* file, std::unique_ptr<ResourceFile> fileDesc, bool overlay) {
|
||||
if (mOptions.verbose) {
|
||||
if (mContext->verbose()) {
|
||||
mContext->getDiagnostics()->note(DiagMessage() << "adding " << file->getSource());
|
||||
}
|
||||
|
||||
@@ -628,10 +857,9 @@ public:
|
||||
|
||||
TableMergerOptions tableMergerOptions;
|
||||
tableMergerOptions.autoAddOverlay = mOptions.autoAddOverlay;
|
||||
tableMergerOptions.filter = mOptions.configFilter;
|
||||
mTableMerger = util::make_unique<TableMerger>(mContext, &mFinalTable, tableMergerOptions);
|
||||
|
||||
if (mOptions.verbose) {
|
||||
if (mContext->verbose()) {
|
||||
mContext->getDiagnostics()->note(
|
||||
DiagMessage() << "linking package '" << mContext->mCompilationPackage << "' "
|
||||
<< "with package ID " << std::hex << (int) mContext->mPackageId);
|
||||
@@ -692,6 +920,15 @@ public:
|
||||
mContext->getDiagnostics()->error(DiagMessage() << "failed stripping products");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// TODO(adamlesinski): Actually pass in split constraints and handle splits at the file
|
||||
// level.
|
||||
TableSplitter tableSplitter({}, mOptions.tableSplitterOptions);
|
||||
if (!tableSplitter.verifySplitConstraints(mContext)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
tableSplitter.splitTable(&mFinalTable);
|
||||
}
|
||||
|
||||
proguard::KeepSet proguardKeepSet;
|
||||
@@ -728,8 +965,10 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
if (!flattenXml(manifestXml.get(), "AndroidManifest.xml", {},
|
||||
archiveWriter.get())) {
|
||||
const bool keepRawValues = mOptions.staticLib;
|
||||
bool result = flattenXml(manifestXml.get(), "AndroidManifest.xml", {},
|
||||
keepRawValues, archiveWriter.get(), mContext);
|
||||
if (!result) {
|
||||
error = true;
|
||||
}
|
||||
} else {
|
||||
@@ -742,113 +981,14 @@ public:
|
||||
return 1;
|
||||
}
|
||||
|
||||
for (auto& mergeEntry : mTableMerger->getFilesToMerge()) {
|
||||
const ResourceKeyRef& key = mergeEntry.first;
|
||||
const FileToMerge& fileToMerge = mergeEntry.second;
|
||||
ResourceFileFlattenerOptions fileFlattenerOptions;
|
||||
fileFlattenerOptions.keepRawValues = mOptions.staticLib;
|
||||
fileFlattenerOptions.doNotCompressAnything = mOptions.doNotCompressAnything;
|
||||
fileFlattenerOptions.extensionsToNotCompress = mOptions.extensionsToNotCompress;
|
||||
fileFlattenerOptions.noAutoVersion = mOptions.noAutoVersion;
|
||||
ResourceFileFlattener fileFlattener(fileFlattenerOptions, mContext, &proguardKeepSet);
|
||||
|
||||
const StringPiece path = fileToMerge.file->getSource().path;
|
||||
|
||||
if (key.name.type != ResourceType::kRaw &&
|
||||
(util::stringEndsWith<char>(path, ".xml.flat") ||
|
||||
util::stringEndsWith<char>(path, ".xml"))) {
|
||||
if (mOptions.verbose) {
|
||||
mContext->getDiagnostics()->note(DiagMessage() << "linking " << path);
|
||||
}
|
||||
|
||||
io::IFile* file = fileToMerge.file;
|
||||
std::unique_ptr<io::IData> data = file->openAsData();
|
||||
if (!data) {
|
||||
mContext->getDiagnostics()->error(DiagMessage(file->getSource())
|
||||
<< "failed to open file");
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::unique_ptr<xml::XmlResource> xmlRes;
|
||||
if (util::stringEndsWith<char>(path, ".flat")) {
|
||||
xmlRes = loadBinaryXmlSkipFileExport(file->getSource(),
|
||||
data->data(), data->size(),
|
||||
mContext->getDiagnostics());
|
||||
} else {
|
||||
xmlRes = xml::inflate(data->data(), data->size(), mContext->getDiagnostics(),
|
||||
file->getSource());
|
||||
}
|
||||
|
||||
if (!xmlRes) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Create the file description header.
|
||||
xmlRes->file = ResourceFile{
|
||||
key.name.toResourceName(),
|
||||
key.config,
|
||||
fileToMerge.originalSource,
|
||||
};
|
||||
|
||||
XmlReferenceLinker xmlLinker;
|
||||
if (xmlLinker.consume(mContext, xmlRes.get())) {
|
||||
if (!proguard::collectProguardRules(xmlRes->file.source, xmlRes.get(),
|
||||
&proguardKeepSet)) {
|
||||
error = true;
|
||||
}
|
||||
|
||||
Maybe<size_t> maxSdkLevel;
|
||||
if (!mOptions.noAutoVersion) {
|
||||
maxSdkLevel = std::max<size_t>(xmlRes->file.config.sdkVersion, 1u);
|
||||
}
|
||||
|
||||
if (!flattenXml(xmlRes.get(), fileToMerge.dstPath, maxSdkLevel,
|
||||
archiveWriter.get())) {
|
||||
error = true;
|
||||
}
|
||||
|
||||
if (!mOptions.noAutoVersion) {
|
||||
Maybe<ResourceTable::SearchResult> result = mFinalTable.findResource(
|
||||
xmlRes->file.name);
|
||||
for (int sdkLevel : xmlLinker.getSdkLevels()) {
|
||||
if (sdkLevel > xmlRes->file.config.sdkVersion &&
|
||||
shouldGenerateVersionedResource(result.value().entry,
|
||||
xmlRes->file.config,
|
||||
sdkLevel)) {
|
||||
xmlRes->file.config.sdkVersion = sdkLevel;
|
||||
|
||||
std::string genResourcePath = ResourceUtils::buildResourceFileName(
|
||||
xmlRes->file, mContext->getNameMangler());
|
||||
|
||||
bool added = mFinalTable.addFileReference(
|
||||
xmlRes->file.name,
|
||||
xmlRes->file.config,
|
||||
xmlRes->file.source,
|
||||
util::utf8ToUtf16(genResourcePath),
|
||||
mContext->getDiagnostics());
|
||||
if (!added) {
|
||||
error = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!flattenXml(xmlRes.get(), genResourcePath, sdkLevel,
|
||||
archiveWriter.get())) {
|
||||
error = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
error = true;
|
||||
}
|
||||
} else {
|
||||
if (mOptions.verbose) {
|
||||
mContext->getDiagnostics()->note(DiagMessage() << "copying " << path);
|
||||
}
|
||||
|
||||
if (!copyFileToArchive(fileToMerge.file, fileToMerge.dstPath,
|
||||
archiveWriter.get())) {
|
||||
error = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (error) {
|
||||
if (!fileFlattener.flatten(&mFinalTable, archiveWriter.get())) {
|
||||
mContext->getDiagnostics()->error(DiagMessage() << "failed linking file resources");
|
||||
return 1;
|
||||
}
|
||||
@@ -912,8 +1052,10 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
if (mOptions.verbose) {
|
||||
Debug::printTable(&mFinalTable);
|
||||
if (mContext->verbose()) {
|
||||
DebugPrintTableOptions debugPrintTableOptions;
|
||||
debugPrintTableOptions.showSources = true;
|
||||
Debug::printTable(&mFinalTable, debugPrintTableOptions);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -934,6 +1076,7 @@ private:
|
||||
};
|
||||
|
||||
int link(const std::vector<StringPiece>& args) {
|
||||
LinkContext context;
|
||||
LinkOptions options;
|
||||
Maybe<std::string> privateSymbolsPackage;
|
||||
Maybe<std::string> minSdkVersion, targetSdkVersion;
|
||||
@@ -942,6 +1085,7 @@ int link(const std::vector<StringPiece>& args) {
|
||||
Maybe<std::string> customJavaPackage;
|
||||
std::vector<std::string> extraJavaPackages;
|
||||
Maybe<std::string> configs;
|
||||
Maybe<std::string> preferredDensity;
|
||||
Maybe<std::string> productList;
|
||||
bool legacyXFlag = false;
|
||||
bool requireLocalization = false;
|
||||
@@ -966,6 +1110,9 @@ int link(const std::vector<StringPiece>& args) {
|
||||
&requireLocalization)
|
||||
.optionalFlag("-c", "Comma separated list of configurations to include. The default\n"
|
||||
"is all configurations", &configs)
|
||||
.optionalFlag("--preferred-density",
|
||||
"Selects the closest matching density and strips out all others.",
|
||||
&preferredDensity)
|
||||
.optionalFlag("--product", "Comma separated list of product names to keep",
|
||||
&productList)
|
||||
.optionalSwitch("--output-to-dir", "Outputs the APK contents to a directory specified "
|
||||
@@ -1001,14 +1148,12 @@ int link(const std::vector<StringPiece>& args) {
|
||||
&renameInstrumentationTargetPackage)
|
||||
.optionalFlagList("-0", "File extensions not to compress",
|
||||
&options.extensionsToNotCompress)
|
||||
.optionalSwitch("-v", "Enables verbose logging", &options.verbose);
|
||||
.optionalSwitch("-v", "Enables verbose logging", &context.mVerbose);
|
||||
|
||||
if (!flags.parse("aapt2 link", args, &std::cerr)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
LinkContext context;
|
||||
|
||||
if (privateSymbolsPackage) {
|
||||
options.privateSymbols = util::utf8ToUtf16(privateSymbolsPackage.value());
|
||||
}
|
||||
@@ -1082,7 +1227,29 @@ int link(const std::vector<StringPiece>& args) {
|
||||
}
|
||||
}
|
||||
|
||||
options.configFilter = &filter;
|
||||
options.tableSplitterOptions.configFilter = &filter;
|
||||
}
|
||||
|
||||
if (preferredDensity) {
|
||||
ConfigDescription preferredDensityConfig;
|
||||
if (!ConfigDescription::parse(preferredDensity.value(), &preferredDensityConfig)) {
|
||||
context.getDiagnostics()->error(DiagMessage() << "invalid density '"
|
||||
<< preferredDensity.value()
|
||||
<< "' for --preferred-density option");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Clear the version that can be automatically added.
|
||||
preferredDensityConfig.sdkVersion = 0;
|
||||
|
||||
if (preferredDensityConfig.diff(ConfigDescription::defaultConfig())
|
||||
!= ConfigDescription::CONFIG_DENSITY) {
|
||||
context.getDiagnostics()->error(DiagMessage() << "invalid preferred density '"
|
||||
<< preferredDensity.value() << "'. "
|
||||
<< "Preferred density must only be a density value");
|
||||
return 1;
|
||||
}
|
||||
options.tableSplitterOptions.preferredDensity = preferredDensityConfig.density;
|
||||
}
|
||||
|
||||
LinkCommand cmd(&context, options);
|
||||
|
||||
@@ -98,8 +98,7 @@ bool TableMerger::mergeAndMangle(const Source& src, const StringPiece16& package
|
||||
return false;
|
||||
}
|
||||
|
||||
mFilesToMerge[ResourceKeyRef(name, config)] = FileToMerge{
|
||||
f, oldFile->getSource(), util::utf16ToUtf8(*newFile->path) };
|
||||
newFile->file = f;
|
||||
return true;
|
||||
};
|
||||
|
||||
@@ -198,10 +197,6 @@ bool TableMerger::doMerge(const Source& src,
|
||||
for (auto& srcValue : srcEntry->values) {
|
||||
ResourceConfigValue* dstValue = dstEntry->findValue(srcValue->config,
|
||||
srcValue->product);
|
||||
|
||||
const bool stripConfig = mOptions.filter ?
|
||||
!mOptions.filter->match(srcValue->config) : false;
|
||||
|
||||
if (dstValue) {
|
||||
const int collisionResult = ResourceTable::resolveValueCollision(
|
||||
dstValue->value.get(), srcValue->value.get());
|
||||
@@ -227,10 +222,6 @@ bool TableMerger::doMerge(const Source& src,
|
||||
|
||||
}
|
||||
|
||||
if (stripConfig) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!dstValue) {
|
||||
// Force create the entry if we didn't have it.
|
||||
dstValue = dstEntry->findOrCreateValue(srcValue->config, srcValue->product);
|
||||
@@ -286,6 +277,7 @@ bool TableMerger::mergeFileImpl(const ResourceFile& fileDesc, io::IFile* file, b
|
||||
std::unique_ptr<FileReference> fileRef = util::make_unique<FileReference>(
|
||||
table.stringPool.makeRef(path));
|
||||
fileRef->setSource(fileDesc.source);
|
||||
fileRef->file = file;
|
||||
|
||||
ResourceTablePackage* pkg = table.createPackage(fileDesc.name.package, 0x0);
|
||||
pkg->findOrCreateType(fileDesc.name.type)
|
||||
@@ -293,15 +285,8 @@ bool TableMerger::mergeFileImpl(const ResourceFile& fileDesc, io::IFile* file, b
|
||||
->findOrCreateValue(fileDesc.config, {})
|
||||
->value = std::move(fileRef);
|
||||
|
||||
auto callback = [&](const ResourceNameRef& name, const ConfigDescription& config,
|
||||
FileReference* newFile, FileReference* oldFile) -> bool {
|
||||
mFilesToMerge[ResourceKeyRef(name, config)] = FileToMerge{
|
||||
file, oldFile->getSource(), util::utf16ToUtf8(*newFile->path) };
|
||||
return true;
|
||||
};
|
||||
|
||||
return doMerge(file->getSource(), &table, pkg,
|
||||
false /* mangle */, overlay /* overlay */, true /* allow new */, callback);
|
||||
false /* mangle */, overlay /* overlay */, true /* allow new */, {});
|
||||
}
|
||||
|
||||
bool TableMerger::mergeFile(const ResourceFile& fileDesc, io::IFile* file) {
|
||||
|
||||
@@ -30,33 +30,11 @@
|
||||
|
||||
namespace aapt {
|
||||
|
||||
struct FileToMerge {
|
||||
/**
|
||||
* The compiled file from which to read the data.
|
||||
*/
|
||||
io::IFile* file;
|
||||
|
||||
/**
|
||||
* Where the original, uncompiled file came from.
|
||||
*/
|
||||
Source originalSource;
|
||||
|
||||
/**
|
||||
* The destination path within the APK/archive.
|
||||
*/
|
||||
std::string dstPath;
|
||||
};
|
||||
|
||||
struct TableMergerOptions {
|
||||
/**
|
||||
* If true, resources in overlays can be added without previously having existed.
|
||||
*/
|
||||
bool autoAddOverlay = false;
|
||||
|
||||
/**
|
||||
* A filter that removes resources whose configurations don't match.
|
||||
*/
|
||||
IConfigFilter* filter = nullptr;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -81,10 +59,6 @@ public:
|
||||
*/
|
||||
TableMerger(IAaptContext* context, ResourceTable* outTable, const TableMergerOptions& options);
|
||||
|
||||
const std::map<ResourceKeyRef, FileToMerge>& getFilesToMerge() {
|
||||
return mFilesToMerge;
|
||||
}
|
||||
|
||||
const std::set<std::u16string>& getMergedPackages() const {
|
||||
return mMergedPackages;
|
||||
}
|
||||
@@ -119,8 +93,7 @@ public:
|
||||
private:
|
||||
using FileMergeCallback = std::function<bool(const ResourceNameRef&,
|
||||
const ConfigDescription& config,
|
||||
FileReference*,
|
||||
FileReference*)>;
|
||||
FileReference*, FileReference*)>;
|
||||
|
||||
IAaptContext* mContext;
|
||||
ResourceTable* mMasterTable;
|
||||
@@ -128,7 +101,6 @@ private:
|
||||
ResourceTablePackage* mMasterPackage;
|
||||
|
||||
std::set<std::u16string> mMergedPackages;
|
||||
std::map<ResourceKeyRef, FileToMerge> mFilesToMerge;
|
||||
|
||||
bool mergeFileImpl(const ResourceFile& fileDesc, io::IFile* file, bool overlay);
|
||||
|
||||
|
||||
@@ -97,16 +97,6 @@ TEST_F(TableMergerTest, MergeFile) {
|
||||
test::parseConfigOrDie("hdpi-v4"));
|
||||
ASSERT_NE(nullptr, file);
|
||||
EXPECT_EQ(std::u16string(u"res/layout-hdpi-v4/main.xml"), *file->path);
|
||||
|
||||
ResourceName name = test::parseNameOrDie(u"@com.app.a:layout/main");
|
||||
ResourceKeyRef key = { name, test::parseConfigOrDie("hdpi-v4") };
|
||||
|
||||
auto iter = merger.getFilesToMerge().find(key);
|
||||
ASSERT_NE(merger.getFilesToMerge().end(), iter);
|
||||
|
||||
const FileToMerge& actualFileToMerge = iter->second;
|
||||
EXPECT_EQ(&testFile, actualFileToMerge.file);
|
||||
EXPECT_EQ(std::string("res/layout-hdpi-v4/main.xml"), actualFileToMerge.dstPath);
|
||||
}
|
||||
|
||||
TEST_F(TableMergerTest, MergeFileOverlay) {
|
||||
@@ -122,14 +112,6 @@ TEST_F(TableMergerTest, MergeFileOverlay) {
|
||||
|
||||
ASSERT_TRUE(merger.mergeFile(fileDesc, &fileA));
|
||||
ASSERT_TRUE(merger.mergeFileOverlay(fileDesc, &fileB));
|
||||
|
||||
ResourceName name = test::parseNameOrDie(u"@com.app.a:xml/foo");
|
||||
ResourceKeyRef key = { name, ConfigDescription{} };
|
||||
auto iter = merger.getFilesToMerge().find(key);
|
||||
ASSERT_NE(merger.getFilesToMerge().end(), iter);
|
||||
|
||||
const FileToMerge& actualFileToMerge = iter->second;
|
||||
EXPECT_EQ(&fileB, actualFileToMerge.file);
|
||||
}
|
||||
|
||||
TEST_F(TableMergerTest, MergeFileReferences) {
|
||||
@@ -157,15 +139,6 @@ TEST_F(TableMergerTest, MergeFileReferences) {
|
||||
f = test::getValue<FileReference>(&finalTable, u"@com.app.a:xml/com.app.b$file");
|
||||
ASSERT_NE(f, nullptr);
|
||||
EXPECT_EQ(std::u16string(u"res/xml/com.app.b$file.xml"), *f->path);
|
||||
|
||||
ResourceName name = test::parseNameOrDie(u"@com.app.a:xml/com.app.b$file");
|
||||
ResourceKeyRef key = { name, ConfigDescription{} };
|
||||
auto iter = merger.getFilesToMerge().find(key);
|
||||
ASSERT_NE(merger.getFilesToMerge().end(), iter);
|
||||
|
||||
const FileToMerge& actualFileToMerge = iter->second;
|
||||
EXPECT_EQ(Source("res/xml/file.xml"), actualFileToMerge.file->getSource());
|
||||
EXPECT_EQ(std::string("res/xml/com.app.b$file.xml"), actualFileToMerge.dstPath);
|
||||
}
|
||||
|
||||
TEST_F(TableMergerTest, OverrideResourceWithOverlay) {
|
||||
@@ -244,36 +217,4 @@ TEST_F(TableMergerTest, FailToMergeNewResourceWithoutAutoAddOverlay) {
|
||||
ASSERT_FALSE(merger.mergeOverlay({}, tableB.get()));
|
||||
}
|
||||
|
||||
TEST_F(TableMergerTest, MergeAndStripResourcesNotMatchingFilter) {
|
||||
ResourceTable finalTable;
|
||||
TableMergerOptions options;
|
||||
options.autoAddOverlay = false;
|
||||
|
||||
AxisConfigFilter filter;
|
||||
filter.addConfig(test::parseConfigOrDie("en"));
|
||||
options.filter = &filter;
|
||||
|
||||
test::TestFile fileA("res/layout-en/main.xml"), fileB("res/layout-fr-rFR/main.xml");
|
||||
const ResourceName name = test::parseNameOrDie(u"@com.app.a:layout/main");
|
||||
const ConfigDescription configEn = test::parseConfigOrDie("en");
|
||||
const ConfigDescription configFr = test::parseConfigOrDie("fr-rFR");
|
||||
|
||||
TableMerger merger(mContext.get(), &finalTable, options);
|
||||
ASSERT_TRUE(merger.mergeFile(ResourceFile{ name, configEn }, &fileA));
|
||||
ASSERT_TRUE(merger.mergeFile(ResourceFile{ name, configFr }, &fileB));
|
||||
|
||||
EXPECT_NE(nullptr, test::getValueForConfig<FileReference>(&finalTable,
|
||||
u"@com.app.a:layout/main",
|
||||
configEn));
|
||||
EXPECT_EQ(nullptr, test::getValueForConfig<FileReference>(&finalTable,
|
||||
u"@com.app.a:layout/main",
|
||||
configFr));
|
||||
|
||||
EXPECT_NE(merger.getFilesToMerge().end(),
|
||||
merger.getFilesToMerge().find(ResourceKeyRef(name, configEn)));
|
||||
|
||||
EXPECT_EQ(merger.getFilesToMerge().end(),
|
||||
merger.getFilesToMerge().find(ResourceKeyRef(name, configFr)));
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
@@ -40,6 +40,7 @@ struct IAaptContext {
|
||||
virtual StringPiece16 getCompilationPackage() = 0;
|
||||
virtual uint8_t getPackageId() = 0;
|
||||
virtual NameMangler* getNameMangler() = 0;
|
||||
virtual bool verbose() = 0;
|
||||
};
|
||||
|
||||
struct IResourceTableConsumer {
|
||||
|
||||
264
tools/aapt2/split/TableSplitter.cpp
Normal file
264
tools/aapt2/split/TableSplitter.cpp
Normal file
@@ -0,0 +1,264 @@
|
||||
/*
|
||||
* 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 "ConfigDescription.h"
|
||||
#include "ResourceTable.h"
|
||||
#include "split/TableSplitter.h"
|
||||
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
using ConfigClaimedMap = std::unordered_map<ResourceConfigValue*, bool>;
|
||||
using ConfigDensityGroups = std::map<ConfigDescription, std::vector<ResourceConfigValue*>>;
|
||||
|
||||
static ConfigDescription copyWithoutDensity(const ConfigDescription& config) {
|
||||
ConfigDescription withoutDensity = config;
|
||||
withoutDensity.density = 0;
|
||||
return withoutDensity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects values that match exactly the constraints given.
|
||||
*/
|
||||
class SplitValueSelector {
|
||||
public:
|
||||
SplitValueSelector(const SplitConstraints& constraints) {
|
||||
for (const ConfigDescription& config : constraints.configs) {
|
||||
if (config.density == 0) {
|
||||
mDensityIndependentConfigs.insert(config);
|
||||
} else {
|
||||
mDensityDependentConfigToDensityMap[copyWithoutDensity(config)] = config.density;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<ResourceConfigValue*> selectValues(const ConfigDensityGroups& densityGroups,
|
||||
ConfigClaimedMap* claimedValues) {
|
||||
std::vector<ResourceConfigValue*> selected;
|
||||
|
||||
// Select the regular values.
|
||||
for (auto& entry : *claimedValues) {
|
||||
// Check if the entry has a density.
|
||||
ResourceConfigValue* configValue = entry.first;
|
||||
if (configValue->config.density == 0 && !entry.second) {
|
||||
// This is still available.
|
||||
if (mDensityIndependentConfigs.find(configValue->config) !=
|
||||
mDensityIndependentConfigs.end()) {
|
||||
selected.push_back(configValue);
|
||||
|
||||
// Mark the entry as taken.
|
||||
entry.second = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now examine the densities
|
||||
for (auto& entry : densityGroups) {
|
||||
// We do not care if the value is claimed, since density values can be
|
||||
// in multiple splits.
|
||||
const ConfigDescription& config = entry.first;
|
||||
const std::vector<ResourceConfigValue*>& relatedValues = entry.second;
|
||||
|
||||
auto densityValueIter = mDensityDependentConfigToDensityMap.find(config);
|
||||
if (densityValueIter != mDensityDependentConfigToDensityMap.end()) {
|
||||
// Select the best one!
|
||||
ConfigDescription targetDensity = config;
|
||||
targetDensity.density = densityValueIter->second;
|
||||
|
||||
ResourceConfigValue* bestValue = nullptr;
|
||||
for (ResourceConfigValue* thisValue : relatedValues) {
|
||||
if (!bestValue ||
|
||||
thisValue->config.isBetterThan(bestValue->config, &targetDensity)) {
|
||||
bestValue = thisValue;
|
||||
}
|
||||
|
||||
// When we select one of these, they are all claimed such that the base
|
||||
// doesn't include any anymore.
|
||||
(*claimedValues)[thisValue] = true;
|
||||
}
|
||||
assert(bestValue);
|
||||
selected.push_back(bestValue);
|
||||
}
|
||||
}
|
||||
return selected;
|
||||
}
|
||||
|
||||
private:
|
||||
std::set<ConfigDescription> mDensityIndependentConfigs;
|
||||
std::map<ConfigDescription, uint16_t> mDensityDependentConfigToDensityMap;
|
||||
};
|
||||
|
||||
/**
|
||||
* Marking non-preferred densities as claimed will make sure the base doesn't include them,
|
||||
* leaving only the preferred density behind.
|
||||
*/
|
||||
static void markNonPreferredDensitiesAsClaimed(uint16_t preferredDensity,
|
||||
const ConfigDensityGroups& densityGroups,
|
||||
ConfigClaimedMap* configClaimedMap) {
|
||||
for (auto& entry : densityGroups) {
|
||||
const ConfigDescription& config = entry.first;
|
||||
const std::vector<ResourceConfigValue*>& relatedValues = entry.second;
|
||||
|
||||
ConfigDescription targetDensity = config;
|
||||
targetDensity.density = preferredDensity;
|
||||
ResourceConfigValue* bestValue = nullptr;
|
||||
for (ResourceConfigValue* thisValue : relatedValues) {
|
||||
if (!bestValue) {
|
||||
bestValue = thisValue;
|
||||
} else if (thisValue->config.isBetterThan(bestValue->config, &targetDensity)) {
|
||||
// Claim the previous value so that it is not included in the base.
|
||||
(*configClaimedMap)[bestValue] = true;
|
||||
bestValue = thisValue;
|
||||
} else {
|
||||
// Claim this value so that it is not included in the base.
|
||||
(*configClaimedMap)[thisValue] = true;
|
||||
}
|
||||
}
|
||||
assert(bestValue);
|
||||
}
|
||||
}
|
||||
|
||||
bool TableSplitter::verifySplitConstraints(IAaptContext* context) {
|
||||
bool error = false;
|
||||
for (size_t i = 0; i < mSplitConstraints.size(); i++) {
|
||||
for (size_t j = i + 1; j < mSplitConstraints.size(); j++) {
|
||||
for (const ConfigDescription& config : mSplitConstraints[i].configs) {
|
||||
if (mSplitConstraints[j].configs.find(config) !=
|
||||
mSplitConstraints[j].configs.end()) {
|
||||
context->getDiagnostics()->error(DiagMessage() << "config '" << config
|
||||
<< "' appears in multiple splits, "
|
||||
<< "target split ambiguous");
|
||||
error = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return !error;
|
||||
}
|
||||
|
||||
void TableSplitter::splitTable(ResourceTable* originalTable) {
|
||||
const size_t splitCount = mSplitConstraints.size();
|
||||
for (auto& pkg : originalTable->packages) {
|
||||
// Initialize all packages for splits.
|
||||
for (size_t idx = 0; idx < splitCount; idx++) {
|
||||
ResourceTable* splitTable = mSplits[idx].get();
|
||||
splitTable->createPackage(pkg->name, pkg->id);
|
||||
}
|
||||
|
||||
for (auto& type : pkg->types) {
|
||||
if (type->type == ResourceType::kMipmap) {
|
||||
// Always keep mipmaps.
|
||||
continue;
|
||||
}
|
||||
|
||||
for (auto& entry : type->entries) {
|
||||
if (mConfigFilter) {
|
||||
// First eliminate any resource that we definitely don't want.
|
||||
for (std::unique_ptr<ResourceConfigValue>& configValue : entry->values) {
|
||||
if (!mConfigFilter->match(configValue->config)) {
|
||||
// null out the entry. We will clean up and remove nulls at the end
|
||||
// for performance reasons.
|
||||
configValue.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Organize the values into two separate buckets. Those that are density-dependent
|
||||
// and those that are density-independent.
|
||||
// One density technically matches all density, it's just that some densities
|
||||
// match better. So we need to be aware of the full set of densities to make this
|
||||
// decision.
|
||||
ConfigDensityGroups densityGroups;
|
||||
ConfigClaimedMap configClaimedMap;
|
||||
for (const std::unique_ptr<ResourceConfigValue>& configValue : entry->values) {
|
||||
if (configValue) {
|
||||
configClaimedMap[configValue.get()] = false;
|
||||
|
||||
if (configValue->config.density != 0) {
|
||||
// Create a bucket for this density-dependent config.
|
||||
densityGroups[copyWithoutDensity(configValue->config)]
|
||||
.push_back(configValue.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// First we check all the splits. If it doesn't match one of the splits, we
|
||||
// leave it in the base.
|
||||
for (size_t idx = 0; idx < splitCount; idx++) {
|
||||
const SplitConstraints& splitConstraint = mSplitConstraints[idx];
|
||||
ResourceTable* splitTable = mSplits[idx].get();
|
||||
|
||||
// Select the values we want from this entry for this split.
|
||||
SplitValueSelector selector(splitConstraint);
|
||||
std::vector<ResourceConfigValue*> selectedValues =
|
||||
selector.selectValues(densityGroups, &configClaimedMap);
|
||||
|
||||
// No need to do any work if we selected nothing.
|
||||
if (!selectedValues.empty()) {
|
||||
// Create the same resource structure in the split. We do this lazily
|
||||
// because we might not have actual values for each type/entry.
|
||||
ResourceTablePackage* splitPkg = splitTable->findPackage(pkg->name);
|
||||
ResourceTableType* splitType = splitPkg->findOrCreateType(type->type);
|
||||
if (!splitType->id) {
|
||||
splitType->id = type->id;
|
||||
splitType->symbolStatus = type->symbolStatus;
|
||||
}
|
||||
|
||||
ResourceEntry* splitEntry = splitType->findOrCreateEntry(entry->name);
|
||||
if (!splitEntry->id) {
|
||||
splitEntry->id = entry->id;
|
||||
splitEntry->symbolStatus = entry->symbolStatus;
|
||||
}
|
||||
|
||||
// Copy the selected values into the new Split Entry.
|
||||
for (ResourceConfigValue* configValue : selectedValues) {
|
||||
ResourceConfigValue* newConfigValue = splitEntry->findOrCreateValue(
|
||||
configValue->config, configValue->product);
|
||||
newConfigValue->value = std::unique_ptr<Value>(
|
||||
configValue->value->clone(&splitTable->stringPool));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mPreferredDensity) {
|
||||
markNonPreferredDensitiesAsClaimed(mPreferredDensity.value(),
|
||||
densityGroups,
|
||||
&configClaimedMap);
|
||||
}
|
||||
|
||||
// All splits are handled, now check to see what wasn't claimed and remove
|
||||
// whatever exists in other splits.
|
||||
for (std::unique_ptr<ResourceConfigValue>& configValue : entry->values) {
|
||||
if (configValue && configClaimedMap[configValue.get()]) {
|
||||
// Claimed, remove from base.
|
||||
configValue.reset();
|
||||
}
|
||||
}
|
||||
|
||||
// Now erase all nullptrs.
|
||||
entry->values.erase(
|
||||
std::remove(entry->values.begin(), entry->values.end(), nullptr),
|
||||
entry->values.end());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
78
tools/aapt2/split/TableSplitter.h
Normal file
78
tools/aapt2/split/TableSplitter.h
Normal file
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef AAPT_SPLIT_TABLESPLITTER_H
|
||||
#define AAPT_SPLIT_TABLESPLITTER_H
|
||||
|
||||
#include "ConfigDescription.h"
|
||||
#include "ResourceTable.h"
|
||||
#include "filter/ConfigFilter.h"
|
||||
#include "process/IResourceTableConsumer.h"
|
||||
|
||||
#include <android-base/macros.h>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
struct SplitConstraints {
|
||||
std::set<ConfigDescription> configs;
|
||||
};
|
||||
|
||||
struct TableSplitterOptions {
|
||||
/**
|
||||
* The preferred density to keep in the table, stripping out all others.
|
||||
*/
|
||||
Maybe<uint16_t> preferredDensity;
|
||||
|
||||
/**
|
||||
* Configuration filter that determines which resource configuration values end up in
|
||||
* the final table.
|
||||
*/
|
||||
IConfigFilter* configFilter = nullptr;
|
||||
};
|
||||
|
||||
class TableSplitter {
|
||||
public:
|
||||
TableSplitter(const std::vector<SplitConstraints>& splits,
|
||||
const TableSplitterOptions& options) :
|
||||
mSplitConstraints(splits), mPreferredDensity(options.preferredDensity),
|
||||
mConfigFilter(options.configFilter) {
|
||||
for (size_t i = 0; i < mSplitConstraints.size(); i++) {
|
||||
mSplits.push_back(util::make_unique<ResourceTable>());
|
||||
}
|
||||
}
|
||||
|
||||
bool verifySplitConstraints(IAaptContext* context);
|
||||
|
||||
void splitTable(ResourceTable* originalTable);
|
||||
|
||||
const std::vector<std::unique_ptr<ResourceTable>>& getSplits() {
|
||||
return mSplits;
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<SplitConstraints> mSplitConstraints;
|
||||
std::vector<std::unique_ptr<ResourceTable>> mSplits;
|
||||
Maybe<uint16_t> mPreferredDensity;
|
||||
IConfigFilter* mConfigFilter;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(TableSplitter);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* AAPT_SPLIT_TABLESPLITTER_H */
|
||||
107
tools/aapt2/split/TableSplitter_test.cpp
Normal file
107
tools/aapt2/split/TableSplitter_test.cpp
Normal file
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* 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 "split/TableSplitter.h"
|
||||
#include "test/Builders.h"
|
||||
#include "test/Common.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
TEST(TableSplitterTest, NoSplitPreferredDensity) {
|
||||
std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
|
||||
.addFileReference(u"@android:drawable/icon", u"res/drawable-mdpi/icon.png",
|
||||
test::parseConfigOrDie("mdpi"))
|
||||
.addFileReference(u"@android:drawable/icon", u"res/drawable-hdpi/icon.png",
|
||||
test::parseConfigOrDie("hdpi"))
|
||||
.addFileReference(u"@android:drawable/icon", u"res/drawable-xhdpi/icon.png",
|
||||
test::parseConfigOrDie("xhdpi"))
|
||||
.addFileReference(u"@android:drawable/icon", u"res/drawable-xxhdpi/icon.png",
|
||||
test::parseConfigOrDie("xxhdpi"))
|
||||
.addSimple(u"@android:string/one", {})
|
||||
.build();
|
||||
|
||||
TableSplitterOptions options;
|
||||
options.preferredDensity = ConfigDescription::DENSITY_XHIGH;
|
||||
TableSplitter splitter({}, options);
|
||||
splitter.splitTable(table.get());
|
||||
|
||||
EXPECT_EQ(nullptr, test::getValueForConfig<FileReference>(table.get(),
|
||||
u"@android:drawable/icon",
|
||||
test::parseConfigOrDie("mdpi")));
|
||||
EXPECT_EQ(nullptr, test::getValueForConfig<FileReference>(table.get(),
|
||||
u"@android:drawable/icon",
|
||||
test::parseConfigOrDie("hdpi")));
|
||||
EXPECT_NE(nullptr, test::getValueForConfig<FileReference>(table.get(),
|
||||
u"@android:drawable/icon",
|
||||
test::parseConfigOrDie("xhdpi")));
|
||||
EXPECT_EQ(nullptr, test::getValueForConfig<FileReference>(table.get(),
|
||||
u"@android:drawable/icon",
|
||||
test::parseConfigOrDie("xxhdpi")));
|
||||
EXPECT_NE(nullptr, test::getValue<Id>(table.get(), u"@android:string/one"));
|
||||
}
|
||||
|
||||
TEST(TableSplitterTest, SplitTableByConfigAndDensity) {
|
||||
ResourceTable table;
|
||||
|
||||
const ResourceName foo = test::parseNameOrDie(u"@android:string/foo");
|
||||
ASSERT_TRUE(table.addResource(foo, test::parseConfigOrDie("land-hdpi"), {},
|
||||
util::make_unique<Id>(),
|
||||
test::getDiagnostics()));
|
||||
ASSERT_TRUE(table.addResource(foo, test::parseConfigOrDie("land-xhdpi"), {},
|
||||
util::make_unique<Id>(),
|
||||
test::getDiagnostics()));
|
||||
ASSERT_TRUE(table.addResource(foo, test::parseConfigOrDie("land-xxhdpi"), {},
|
||||
util::make_unique<Id>(),
|
||||
test::getDiagnostics()));
|
||||
|
||||
std::vector<SplitConstraints> constraints;
|
||||
constraints.push_back(SplitConstraints{ { test::parseConfigOrDie("land-mdpi") } });
|
||||
constraints.push_back(SplitConstraints{ { test::parseConfigOrDie("land-xhdpi") } });
|
||||
|
||||
TableSplitter splitter(constraints, TableSplitterOptions{});
|
||||
splitter.splitTable(&table);
|
||||
|
||||
ASSERT_EQ(2u, splitter.getSplits().size());
|
||||
|
||||
ResourceTable* splitOne = splitter.getSplits()[0].get();
|
||||
ResourceTable* splitTwo = splitter.getSplits()[1].get();
|
||||
|
||||
// Since a split was defined, all densities should be gone from base.
|
||||
EXPECT_EQ(nullptr, test::getValueForConfig<Id>(&table, u"@android:string/foo",
|
||||
test::parseConfigOrDie("land-hdpi")));
|
||||
EXPECT_EQ(nullptr, test::getValueForConfig<Id>(&table, u"@android:string/foo",
|
||||
test::parseConfigOrDie("land-xhdpi")));
|
||||
EXPECT_EQ(nullptr, test::getValueForConfig<Id>(&table, u"@android:string/foo",
|
||||
test::parseConfigOrDie("land-xxhdpi")));
|
||||
|
||||
EXPECT_NE(nullptr, test::getValueForConfig<Id>(splitOne, u"@android:string/foo",
|
||||
test::parseConfigOrDie("land-hdpi")));
|
||||
EXPECT_EQ(nullptr, test::getValueForConfig<Id>(splitOne, u"@android:string/foo",
|
||||
test::parseConfigOrDie("land-xhdpi")));
|
||||
EXPECT_EQ(nullptr, test::getValueForConfig<Id>(splitOne, u"@android:string/foo",
|
||||
test::parseConfigOrDie("land-xxhdpi")));
|
||||
|
||||
EXPECT_EQ(nullptr, test::getValueForConfig<Id>(splitTwo, u"@android:string/foo",
|
||||
test::parseConfigOrDie("land-hdpi")));
|
||||
EXPECT_NE(nullptr, test::getValueForConfig<Id>(splitTwo, u"@android:string/foo",
|
||||
test::parseConfigOrDie("land-xhdpi")));
|
||||
EXPECT_EQ(nullptr, test::getValueForConfig<Id>(splitTwo, u"@android:string/foo",
|
||||
test::parseConfigOrDie("land-xxhdpi")));
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
@@ -71,6 +71,10 @@ public:
|
||||
assert(mNameMangler && "test name mangler not set");
|
||||
return mNameMangler.get();
|
||||
}
|
||||
|
||||
bool verbose() override {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
class ContextBuilder {
|
||||
|
||||
Reference in New Issue
Block a user