AAPT2: Add diff command

Adds the diff command and various small fixes to issues
discovered when diffing old AAPT built APKs with new AAPT2
built APKS.

Bug:22775504
Change-Id: I682a7fe1cf4b3efa7cbd5d18b333cf2d1046fe1b
This commit is contained in:
Adam Lesinski
2016-04-25 14:20:21 -07:00
parent 832449800e
commit 458b877488
12 changed files with 649 additions and 61 deletions

View File

@@ -115,6 +115,7 @@ testSources := \
toolSources := \
compile/Compile.cpp \
diff/Diff.cpp \
dump/Dump.cpp \
link/Link.cpp

View File

@@ -24,6 +24,7 @@ namespace aapt {
extern int compile(const std::vector<StringPiece>& args);
extern int link(const std::vector<StringPiece>& args);
extern int dump(const std::vector<StringPiece>& args);
extern int diff(const std::vector<StringPiece>& args);
} // namespace aapt
@@ -44,12 +45,14 @@ int main(int argc, char** argv) {
return aapt::link(args);
} else if (command == "dump" || command == "d") {
return aapt::dump(args);
} else if (command == "diff") {
return aapt::diff(args);
}
std::cerr << "unknown command '" << command << "'\n";
} else {
std::cerr << "no command specified\n";
}
std::cerr << "\nusage: aapt2 [compile|link|dump] ..." << std::endl;
std::cerr << "\nusage: aapt2 [compile|link|dump|diff] ..." << std::endl;
return 1;
}

View File

@@ -152,7 +152,7 @@ bool ResourceParser::flattenXmlSubtree(xml::XmlPullParser* parser, std::u16strin
break;
}
spanStack.back().lastChar = builder.str().size();
spanStack.back().lastChar = builder.str().size() - 1;
outStyleString->spans.push_back(spanStack.back());
spanStack.pop_back();
@@ -1058,6 +1058,16 @@ bool ResourceParser::parseArrayImpl(xml::XmlPullParser* parser, ParsedResource*
std::unique_ptr<Array> array = util::make_unique<Array>();
bool translateable = mOptions.translatable;
if (Maybe<StringPiece16> translateableAttr = xml::findAttribute(parser, u"translatable")) {
if (!ResourceUtils::tryParseBool(translateableAttr.value(), &translateable)) {
mDiag->error(DiagMessage(outResource->source)
<< "invalid value for 'translatable'. Must be a boolean");
return false;
}
}
array->setTranslateable(translateable);
bool error = false;
const size_t depth = parser->getDepth();
while (xml::XmlPullParser::nextChildNode(parser, depth)) {

View File

@@ -189,6 +189,17 @@ std::vector<ResourceConfigValue*> ResourceEntry::findAllValues(const ConfigDescr
return results;
}
std::vector<ResourceConfigValue*> ResourceEntry::findValuesIf(
const std::function<bool(ResourceConfigValue*)>& f) {
std::vector<ResourceConfigValue*> results;
for (auto& configValue : values) {
if (f(configValue.get())) {
results.push_back(configValue.get());
}
}
return results;
}
/**
* The default handler for collisions. A return value of -1 means keep the
* existing value, 0 means fail, and +1 means take the incoming value.

View File

@@ -26,6 +26,7 @@
#include "io/File.h"
#include <android-base/macros.h>
#include <functional>
#include <map>
#include <memory>
#include <string>
@@ -109,6 +110,9 @@ public:
ResourceConfigValue* findOrCreateValue(const ConfigDescription& config,
const StringPiece& product);
std::vector<ResourceConfigValue*> findAllValues(const ConfigDescription& config);
std::vector<ResourceConfigValue*> findValuesIf(
const std::function<bool(ResourceConfigValue*)>& f);
private:
DISALLOW_COPY_AND_ASSIGN(ResourceEntry);

View File

@@ -289,7 +289,7 @@ std::unique_ptr<BinaryPrimitive> tryParseEnumSymbol(const Attribute* enumAttr,
std::unique_ptr<BinaryPrimitive> tryParseFlagSymbol(const Attribute* flagAttr,
const StringPiece16& str) {
android::Res_value flags = { };
flags.dataType = android::Res_value::TYPE_INT_DEC;
flags.dataType = android::Res_value::TYPE_INT_HEX;
flags.data = 0u;
if (util::trimWhitespace(str).empty()) {

View File

@@ -39,6 +39,14 @@ void BaseItem<Derived>::accept(RawValueVisitor* visitor) {
RawString::RawString(const StringPool::Ref& ref) : value(ref) {
}
bool RawString::equals(const Value* value) const {
const RawString* other = valueCast<RawString>(value);
if (!other) {
return false;
}
return *this->value == *other->value;
}
RawString* RawString::clone(StringPool* newPool) const {
RawString* rs = new RawString(newPool->makeRef(*value));
rs->mComment = mComment;
@@ -66,6 +74,15 @@ Reference::Reference(const ResourceNameRef& n, Type t) :
Reference::Reference(const ResourceId& i, Type type) : id(i), referenceType(type) {
}
bool Reference::equals(const Value* value) const {
const Reference* other = valueCast<Reference>(value);
if (!other) {
return false;
}
return referenceType == other->referenceType && privateReference == other->privateReference &&
id == other->id && name == other->name;
}
bool Reference::flatten(android::Res_value* outValue) const {
outValue->dataType = (referenceType == Reference::Type::kResource) ?
android::Res_value::TYPE_REFERENCE : android::Res_value::TYPE_ATTRIBUTE;
@@ -97,6 +114,10 @@ void Reference::print(std::ostream* out) const {
}
}
bool Id::equals(const Value* value) const {
return valueCast<Id>(value) != nullptr;
}
bool Id::flatten(android::Res_value* out) const {
out->dataType = android::Res_value::TYPE_INT_BOOLEAN;
out->data = util::hostToDevice32(0);
@@ -111,15 +132,15 @@ void Id::print(std::ostream* out) const {
*out << "(id)";
}
String::String(const StringPool::Ref& ref) : value(ref), mTranslateable(true) {
String::String(const StringPool::Ref& ref) : value(ref) {
}
void String::setTranslateable(bool val) {
mTranslateable = val;
}
bool String::isTranslateable() const {
return mTranslateable;
bool String::equals(const Value* value) const {
const String* other = valueCast<String>(value);
if (!other) {
return false;
}
return *this->value == *other->value;
}
bool String::flatten(android::Res_value* outValue) const {
@@ -144,15 +165,24 @@ void String::print(std::ostream* out) const {
*out << "(string) \"" << *value << "\"";
}
StyledString::StyledString(const StringPool::StyleRef& ref) : value(ref), mTranslateable(true) {
StyledString::StyledString(const StringPool::StyleRef& ref) : value(ref) {
}
void StyledString::setTranslateable(bool val) {
mTranslateable = val;
}
bool StyledString::equals(const Value* value) const {
const StyledString* other = valueCast<StyledString>(value);
if (!other) {
return false;
}
bool StyledString::isTranslateable() const {
return mTranslateable;
if (*this->value->str == *other->value->str) {
const std::vector<StringPool::Span>& spansA = this->value->spans;
const std::vector<StringPool::Span>& spansB = other->value->spans;
return std::equal(spansA.begin(), spansA.end(), spansB.begin(),
[](const StringPool::Span& a, const StringPool::Span& b) -> bool {
return *a.name == *b.name && a.firstChar == b.firstChar && a.lastChar == b.lastChar;
});
}
return false;
}
bool StyledString::flatten(android::Res_value* outValue) const {
@@ -174,11 +204,22 @@ StyledString* StyledString::clone(StringPool* newPool) const {
void StyledString::print(std::ostream* out) const {
*out << "(styled string) \"" << *value->str << "\"";
for (const StringPool::Span& span : value->spans) {
*out << " "<< *span.name << ":" << span.firstChar << "," << span.lastChar;
}
}
FileReference::FileReference(const StringPool::Ref& _path) : path(_path) {
}
bool FileReference::equals(const Value* value) const {
const FileReference* other = valueCast<FileReference>(value);
if (!other) {
return false;
}
return *path == *other->path;
}
bool FileReference::flatten(android::Res_value* outValue) const {
if (path.getIndex() > std::numeric_limits<uint32_t>::max()) {
return false;
@@ -209,6 +250,14 @@ BinaryPrimitive::BinaryPrimitive(uint8_t dataType, uint32_t data) {
value.data = data;
}
bool BinaryPrimitive::equals(const Value* value) const {
const BinaryPrimitive* other = valueCast<BinaryPrimitive>(value);
if (!other) {
return false;
}
return this->value.dataType == other->value.dataType && this->value.data == other->value.data;
}
bool BinaryPrimitive::flatten(android::Res_value* outValue) const {
outValue->dataType = value.dataType;
outValue->data = util::hostToDevice32(value.data);
@@ -228,7 +277,7 @@ void BinaryPrimitive::print(std::ostream* out) const {
*out << "(integer) " << static_cast<int32_t>(value.data);
break;
case android::Res_value::TYPE_INT_HEX:
*out << "(integer) " << std::hex << value.data << std::dec;
*out << "(integer) 0x" << std::hex << value.data << std::dec;
break;
case android::Res_value::TYPE_INT_BOOLEAN:
*out << "(boolean) " << (value.data != 0 ? "true" : "false");
@@ -253,6 +302,21 @@ Attribute::Attribute(bool w, uint32_t t) :
mWeak = w;
}
bool Attribute::equals(const Value* value) const {
const Attribute* other = valueCast<Attribute>(value);
if (!other) {
return false;
}
return this->typeMask == other->typeMask && this->minInt == other->minInt &&
this->maxInt == other->maxInt &&
std::equal(this->symbols.begin(), this->symbols.end(),
other->symbols.begin(),
[](const Symbol& a, const Symbol& b) -> bool {
return a.symbol.equals(&b.symbol) && a.value == b.value;
});
}
Attribute* Attribute::clone(StringPool* /*newPool*/) const {
return new Attribute(*this);
}
@@ -365,6 +429,14 @@ void Attribute::print(std::ostream* out) const {
<< "]";
}
if (minInt != std::numeric_limits<int32_t>::min()) {
*out << " min=" << minInt;
}
if (maxInt != std::numeric_limits<int32_t>::max()) {
*out << " max=" << maxInt;
}
if (isWeak()) {
*out << " [weak]";
}
@@ -445,6 +517,21 @@ bool Attribute::matches(const Item* item, DiagMessage* outMsg) const {
return true;
}
bool Style::equals(const Value* value) const {
const Style* other = valueCast<Style>(value);
if (!other) {
return false;
}
if (bool(parent) != bool(other->parent) ||
(parent && other->parent && !parent.value().equals(&other->parent.value()))) {
return false;
}
return std::equal(entries.begin(), entries.end(), other->entries.begin(),
[](const Entry& a, const Entry& b) -> bool {
return a.key.equals(&b.key) && a.value->equals(b.value.get());
});
}
Style* Style::clone(StringPool* newPool) const {
Style* style = new Style();
style->parent = parent;
@@ -484,6 +571,18 @@ static ::std::ostream& operator<<(::std::ostream& out, const Style::Entry& value
return out;
}
bool Array::equals(const Value* value) const {
const Array* other = valueCast<Array>(value);
if (!other) {
return false;
}
return std::equal(items.begin(), items.end(), other->items.begin(),
[](const std::unique_ptr<Item>& a, const std::unique_ptr<Item>& b) -> bool {
return a->equals(b.get());
});
}
Array* Array::clone(StringPool* newPool) const {
Array* array = new Array();
array->mComment = mComment;
@@ -500,6 +599,21 @@ void Array::print(std::ostream* out) const {
<< "]";
}
bool Plural::equals(const Value* value) const {
const Plural* other = valueCast<Plural>(value);
if (!other) {
return false;
}
return std::equal(values.begin(), values.end(), other->values.begin(),
[](const std::unique_ptr<Item>& a, const std::unique_ptr<Item>& b) -> bool {
if (bool(a) != bool(b)) {
return false;
}
return bool(a) == bool(b) || a->equals(b.get());
});
}
Plural* Plural::clone(StringPool* newPool) const {
Plural* p = new Plural();
p->mComment = mComment;
@@ -515,12 +629,42 @@ Plural* Plural::clone(StringPool* newPool) const {
void Plural::print(std::ostream* out) const {
*out << "(plural)";
if (values[Zero]) {
*out << " zero=" << *values[Zero];
}
if (values[One]) {
*out << " one=" << *values[One];
}
if (values[Two]) {
*out << " two=" << *values[Two];
}
if (values[Few]) {
*out << " few=" << *values[Few];
}
if (values[Many]) {
*out << " many=" << *values[Many];
}
}
static ::std::ostream& operator<<(::std::ostream& out, const std::unique_ptr<Item>& item) {
return out << *item;
}
bool Styleable::equals(const Value* value) const {
const Styleable* other = valueCast<Styleable>(value);
if (!other) {
return false;
}
return std::equal(entries.begin(), entries.end(), other->entries.begin(),
[](const Reference& a, const Reference& b) -> bool {
return a.equals(&b);
});
}
Styleable* Styleable::clone(StringPool* /*newPool*/) const {
return new Styleable(*this);
}

View File

@@ -54,6 +54,18 @@ struct Value {
mWeak = val;
}
// Whether the value is marked as translateable.
// This does not persist when flattened.
// It is only used during compilation phase.
void setTranslateable(bool val) {
mTranslateable = val;
}
// Default true.
bool isTranslateable() const {
return mTranslateable;
}
/**
* Returns the source where this value was defined.
*/
@@ -84,6 +96,8 @@ struct Value {
mComment = std::move(str);
}
virtual bool equals(const Value* value) const = 0;
/**
* Calls the appropriate overload of ValueVisitor.
*/
@@ -103,6 +117,7 @@ protected:
Source mSource;
std::u16string mComment;
bool mWeak = false;
bool mTranslateable = true;
};
/**
@@ -158,6 +173,7 @@ struct Reference : public BaseItem<Reference> {
explicit Reference(const ResourceNameRef& n, Type type = Type::kResource);
explicit Reference(const ResourceId& i, Type type = Type::kResource);
bool equals(const Value* value) const override;
bool flatten(android::Res_value* outValue) const override;
Reference* clone(StringPool* newPool) const override;
void print(std::ostream* out) const override;
@@ -168,6 +184,7 @@ struct Reference : public BaseItem<Reference> {
*/
struct Id : public BaseItem<Id> {
Id() { mWeak = true; }
bool equals(const Value* value) const override;
bool flatten(android::Res_value* out) const override;
Id* clone(StringPool* newPool) const override;
void print(std::ostream* out) const override;
@@ -183,6 +200,7 @@ struct RawString : public BaseItem<RawString> {
RawString(const StringPool::Ref& ref);
bool equals(const Value* value) const override;
bool flatten(android::Res_value* outValue) const override;
RawString* clone(StringPool* newPool) const override;
void print(std::ostream* out) const override;
@@ -193,17 +211,10 @@ struct String : public BaseItem<String> {
String(const StringPool::Ref& ref);
// Whether the string is marked as translateable. This does not persist when flattened.
// It is only used during compilation phase.
void setTranslateable(bool val);
bool isTranslateable() const;
bool equals(const Value* value) const override;
bool flatten(android::Res_value* outValue) const override;
String* clone(StringPool* newPool) const override;
void print(std::ostream* out) const override;
private:
bool mTranslateable;
};
struct StyledString : public BaseItem<StyledString> {
@@ -211,17 +222,10 @@ struct StyledString : public BaseItem<StyledString> {
StyledString(const StringPool::StyleRef& ref);
// Whether the string is marked as translateable. This does not persist when flattened.
// It is only used during compilation phase.
void setTranslateable(bool val);
bool isTranslateable() const;
bool equals(const Value* value) const override;
bool flatten(android::Res_value* outValue) const override;
StyledString* clone(StringPool* newPool) const override;
void print(std::ostream* out) const override;
private:
bool mTranslateable;
};
struct FileReference : public BaseItem<FileReference> {
@@ -235,6 +239,7 @@ struct FileReference : public BaseItem<FileReference> {
FileReference() = default;
FileReference(const StringPool::Ref& path);
bool equals(const Value* value) const override;
bool flatten(android::Res_value* outValue) const override;
FileReference* clone(StringPool* newPool) const override;
void print(std::ostream* out) const override;
@@ -250,6 +255,7 @@ struct BinaryPrimitive : public BaseItem<BinaryPrimitive> {
BinaryPrimitive(const android::Res_value& val);
BinaryPrimitive(uint8_t dataType, uint32_t data);
bool equals(const Value* value) const override;
bool flatten(android::Res_value* outValue) const override;
BinaryPrimitive* clone(StringPool* newPool) const override;
void print(std::ostream* out) const override;
@@ -268,6 +274,7 @@ struct Attribute : public BaseValue<Attribute> {
Attribute(bool w, uint32_t t = 0u);
bool equals(const Value* value) const override;
Attribute* clone(StringPool* newPool) const override;
void printMask(std::ostream* out) const;
void print(std::ostream* out) const override;
@@ -290,6 +297,7 @@ struct Style : public BaseValue<Style> {
std::vector<Entry> entries;
bool equals(const Value* value) const override;
Style* clone(StringPool* newPool) const override;
void print(std::ostream* out) const override;
};
@@ -297,6 +305,7 @@ struct Style : public BaseValue<Style> {
struct Array : public BaseValue<Array> {
std::vector<std::unique_ptr<Item>> items;
bool equals(const Value* value) const override;
Array* clone(StringPool* newPool) const override;
void print(std::ostream* out) const override;
};
@@ -314,6 +323,7 @@ struct Plural : public BaseValue<Plural> {
std::array<std::unique_ptr<Item>, Count> values;
bool equals(const Value* value) const override;
Plural* clone(StringPool* newPool) const override;
void print(std::ostream* out) const override;
};
@@ -321,6 +331,7 @@ struct Plural : public BaseValue<Plural> {
struct Styleable : public BaseValue<Styleable> {
std::vector<Reference> entries;
bool equals(const Value* value) const override;
Styleable* clone(StringPool* newPool) const override;
void print(std::ostream* out) const override;
};

View File

@@ -127,6 +127,11 @@ struct DynCastVisitor<Item> : public RawValueVisitor {
}
};
template <typename T>
const T* valueCast(const Value* value) {
return valueCast<T>(const_cast<Value*>(value));
}
/**
* Returns a valid pointer to T if the Value is of subtype T.
* Otherwise, returns nullptr.
@@ -141,7 +146,6 @@ T* valueCast(Value* value) {
return visitor.value;
}
inline void visitAllValuesInPackage(ResourceTablePackage* pkg, RawValueVisitor* visitor) {
for (auto& type : pkg->types) {
for (auto& entry : type->entries) {

View File

@@ -128,23 +128,6 @@ struct Visitor : public RawValueVisitor {
mPool(pool), mMethod(method), mLocalizer(method) {
}
void visit(Array* array) override {
std::unique_ptr<Array> localized = util::make_unique<Array>();
localized->items.resize(array->items.size());
for (size_t i = 0; i < array->items.size(); i++) {
Visitor subVisitor(mPool, mMethod);
array->items[i]->accept(&subVisitor);
if (subVisitor.mItem) {
localized->items[i] = std::move(subVisitor.mItem);
} else {
localized->items[i] = std::unique_ptr<Item>(array->items[i]->clone(mPool));
}
}
localized->setSource(array->getSource());
localized->setWeak(true);
mValue = std::move(localized);
}
void visit(Plural* plural) override {
std::unique_ptr<Plural> localized = util::make_unique<Plural>();
for (size_t i = 0; i < plural->values.size(); i++) {
@@ -164,10 +147,6 @@ struct Visitor : public RawValueVisitor {
}
void visit(String* string) override {
if (!string->isTranslateable()) {
return;
}
std::u16string result = mLocalizer.start() + mLocalizer.text(*string->value) +
mLocalizer.end();
std::unique_ptr<String> localized = util::make_unique<String>(mPool->makeRef(result));
@@ -177,10 +156,6 @@ struct Visitor : public RawValueVisitor {
}
void visit(StyledString* string) override {
if (!string->isTranslateable()) {
return;
}
mItem = pseudolocalizeStyledString(string, mMethod, mPool);
mItem->setWeak(true);
}
@@ -238,14 +213,26 @@ void pseudolocalizeIfNeeded(const Pseudolocalizer::Method method,
}
}
/**
* A value is pseudolocalizable if it does not define a locale (or is the default locale)
* and is translateable.
*/
static bool isPseudolocalizable(ResourceConfigValue* configValue) {
const int diff = configValue->config.diff(ConfigDescription::defaultConfig());
if (diff & ConfigDescription::CONFIG_LOCALE) {
return false;
}
return configValue->value->isTranslateable();
}
} // namespace
bool PseudolocaleGenerator::consume(IAaptContext* context, ResourceTable* table) {
for (auto& package : table->packages) {
for (auto& type : package->types) {
for (auto& entry : type->entries) {
std::vector<ResourceConfigValue*> values = entry->findAllValues(
ConfigDescription::defaultConfig());
std::vector<ResourceConfigValue*> values = entry->findValuesIf(isPseudolocalizable);
for (ResourceConfigValue* value : values) {
pseudolocalizeIfNeeded(Pseudolocalizer::Method::kAccent, value,
&table->stringPool, entry.get());

411
tools/aapt2/diff/Diff.cpp Normal file
View File

@@ -0,0 +1,411 @@
/*
* 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 "Flags.h"
#include "ResourceTable.h"
#include "io/ZipArchive.h"
#include "process/IResourceTableConsumer.h"
#include "process/SymbolTable.h"
#include "unflatten/BinaryResourceParser.h"
#include <android-base/macros.h>
namespace aapt {
class DiffContext : public IAaptContext {
public:
const std::u16string& getCompilationPackage() override {
return mEmpty;
}
uint8_t getPackageId() override {
return 0x0;
}
IDiagnostics* getDiagnostics() override {
return &mDiagnostics;
}
NameMangler* getNameMangler() override {
return &mNameMangler;
}
SymbolTable* getExternalSymbols() override {
return &mSymbolTable;
}
bool verbose() override {
return false;
}
private:
std::u16string mEmpty;
StdErrDiagnostics mDiagnostics;
NameMangler mNameMangler = NameMangler(NameManglerPolicy{});
SymbolTable mSymbolTable;
};
class LoadedApk {
public:
LoadedApk(const Source& source, std::unique_ptr<io::IFileCollection> apk,
std::unique_ptr<ResourceTable> table) :
mSource(source), mApk(std::move(apk)), mTable(std::move(table)) {
}
io::IFileCollection* getFileCollection() {
return mApk.get();
}
ResourceTable* getResourceTable() {
return mTable.get();
}
const Source& getSource() {
return mSource;
}
private:
Source mSource;
std::unique_ptr<io::IFileCollection> mApk;
std::unique_ptr<ResourceTable> mTable;
DISALLOW_COPY_AND_ASSIGN(LoadedApk);
};
static std::unique_ptr<LoadedApk> loadApkFromPath(IAaptContext* context, const StringPiece& path) {
Source source(path);
std::string error;
std::unique_ptr<io::ZipFileCollection> apk = io::ZipFileCollection::create(path, &error);
if (!apk) {
context->getDiagnostics()->error(DiagMessage(source) << error);
return {};
}
io::IFile* file = apk->findFile("resources.arsc");
if (!file) {
context->getDiagnostics()->error(DiagMessage(source) << "no resources.arsc found");
return {};
}
std::unique_ptr<io::IData> data = file->openAsData();
if (!data) {
context->getDiagnostics()->error(DiagMessage(source) << "could not open resources.arsc");
return {};
}
std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>();
BinaryResourceParser parser(context, table.get(), source, data->data(), data->size());
if (!parser.parse()) {
return {};
}
return util::make_unique<LoadedApk>(source, std::move(apk), std::move(table));
}
static void emitDiffLine(const Source& source, const StringPiece& message) {
std::cerr << source << ": " << message << "\n";
}
static bool isSymbolVisibilityDifferent(const Symbol& symbolA, const Symbol& symbolB) {
return symbolA.state != symbolB.state;
}
template <typename Id>
static bool isIdDiff(const Symbol& symbolA, const Maybe<Id>& idA,
const Symbol& symbolB, const Maybe<Id>& idB) {
if (symbolA.state == SymbolState::kPublic || symbolB.state == SymbolState::kPublic) {
return idA != idB;
}
return false;
}
static bool emitResourceConfigValueDiff(IAaptContext* context,
LoadedApk* apkA,
ResourceTablePackage* pkgA,
ResourceTableType* typeA,
ResourceEntry* entryA,
ResourceConfigValue* configValueA,
LoadedApk* apkB,
ResourceTablePackage* pkgB,
ResourceTableType* typeB,
ResourceEntry* entryB,
ResourceConfigValue* configValueB) {
Value* valueA = configValueA->value.get();
Value* valueB = configValueB->value.get();
if (!valueA->equals(valueB)) {
std::stringstream strStream;
strStream << "value " << pkgA->name << ":" << typeA->type << "/" << entryA->name
<< " config=" << configValueA->config << " does not match:\n";
valueA->print(&strStream);
strStream << "\n vs \n";
valueB->print(&strStream);
emitDiffLine(apkB->getSource(), strStream.str());
return true;
}
return false;
}
static bool emitResourceEntryDiff(IAaptContext* context,
LoadedApk* apkA,
ResourceTablePackage* pkgA,
ResourceTableType* typeA,
ResourceEntry* entryA,
LoadedApk* apkB,
ResourceTablePackage* pkgB,
ResourceTableType* typeB,
ResourceEntry* entryB) {
bool diff = false;
for (std::unique_ptr<ResourceConfigValue>& configValueA : entryA->values) {
ResourceConfigValue* configValueB = entryB->findValue(configValueA->config);
if (!configValueB) {
std::stringstream strStream;
strStream << "missing " << pkgA->name << ":" << typeA->type << "/" << entryA->name
<< " config=" << configValueA->config;
emitDiffLine(apkB->getSource(), strStream.str());
diff = true;
} else {
diff |= emitResourceConfigValueDiff(context, apkA, pkgA, typeA, entryA,
configValueA.get(), apkB, pkgB, typeB, entryB,
configValueB);
}
}
// Check for any newly added config values.
for (std::unique_ptr<ResourceConfigValue>& configValueB : entryB->values) {
ResourceConfigValue* configValueA = entryA->findValue(configValueB->config);
if (!configValueA) {
std::stringstream strStream;
strStream << "new config " << pkgB->name << ":" << typeB->type << "/" << entryB->name
<< " config=" << configValueB->config;
emitDiffLine(apkB->getSource(), strStream.str());
diff = true;
}
}
return false;
}
static bool emitResourceTypeDiff(IAaptContext* context,
LoadedApk* apkA,
ResourceTablePackage* pkgA,
ResourceTableType* typeA,
LoadedApk* apkB,
ResourceTablePackage* pkgB,
ResourceTableType* typeB) {
bool diff = false;
for (std::unique_ptr<ResourceEntry>& entryA : typeA->entries) {
ResourceEntry* entryB = typeB->findEntry(entryA->name);
if (!entryB) {
std::stringstream strStream;
strStream << "missing " << pkgA->name << ":" << typeA->type << "/" << entryA->name;
emitDiffLine(apkB->getSource(), strStream.str());
diff = true;
} else {
if (isSymbolVisibilityDifferent(entryA->symbolStatus, entryB->symbolStatus)) {
std::stringstream strStream;
strStream << pkgA->name << ":" << typeA->type << "/" << entryA->name
<< " has different visibility (";
if (entryB->symbolStatus.state == SymbolState::kPublic) {
strStream << "PUBLIC";
} else {
strStream << "PRIVATE";
}
strStream << " vs ";
if (entryA->symbolStatus.state == SymbolState::kPublic) {
strStream << "PUBLIC";
} else {
strStream << "PRIVATE";
}
strStream << ")";
emitDiffLine(apkB->getSource(), strStream.str());
diff = true;
} else if (isIdDiff(entryA->symbolStatus, entryA->id,
entryB->symbolStatus, entryB->id)) {
std::stringstream strStream;
strStream << pkgA->name << ":" << typeA->type << "/" << entryA->name
<< " has different public ID (";
if (entryB->id) {
strStream << "0x" << std::hex << entryB->id.value();
} else {
strStream << "none";
}
strStream << " vs ";
if (entryA->id) {
strStream << "0x " << std::hex << entryA->id.value();
} else {
strStream << "none";
}
strStream << ")";
emitDiffLine(apkB->getSource(), strStream.str());
diff = true;
}
diff |= emitResourceEntryDiff(context, apkA, pkgA, typeA, entryA.get(),
apkB, pkgB, typeB, entryB);
}
}
// Check for any newly added entries.
for (std::unique_ptr<ResourceEntry>& entryB : typeB->entries) {
ResourceEntry* entryA = typeA->findEntry(entryB->name);
if (!entryA) {
std::stringstream strStream;
strStream << "new entry " << pkgB->name << ":" << typeB->type << "/" << entryB->name;
emitDiffLine(apkB->getSource(), strStream.str());
diff = true;
}
}
return diff;
}
static bool emitResourcePackageDiff(IAaptContext* context, LoadedApk* apkA,
ResourceTablePackage* pkgA,
LoadedApk* apkB, ResourceTablePackage* pkgB) {
bool diff = false;
for (std::unique_ptr<ResourceTableType>& typeA : pkgA->types) {
ResourceTableType* typeB = pkgB->findType(typeA->type);
if (!typeB) {
std::stringstream strStream;
strStream << "missing " << pkgA->name << ":" << typeA->type;
emitDiffLine(apkA->getSource(), strStream.str());
diff = true;
} else {
if (isSymbolVisibilityDifferent(typeA->symbolStatus, typeB->symbolStatus)) {
std::stringstream strStream;
strStream << pkgA->name << ":" << typeA->type << " has different visibility (";
if (typeB->symbolStatus.state == SymbolState::kPublic) {
strStream << "PUBLIC";
} else {
strStream << "PRIVATE";
}
strStream << " vs ";
if (typeA->symbolStatus.state == SymbolState::kPublic) {
strStream << "PUBLIC";
} else {
strStream << "PRIVATE";
}
strStream << ")";
emitDiffLine(apkB->getSource(), strStream.str());
diff = true;
} else if (isIdDiff(typeA->symbolStatus, typeA->id, typeB->symbolStatus, typeB->id)) {
std::stringstream strStream;
strStream << pkgA->name << ":" << typeA->type << " has different public ID (";
if (typeB->id) {
strStream << "0x" << std::hex << typeB->id.value();
} else {
strStream << "none";
}
strStream << " vs ";
if (typeA->id) {
strStream << "0x " << std::hex << typeA->id.value();
} else {
strStream << "none";
}
strStream << ")";
emitDiffLine(apkB->getSource(), strStream.str());
diff = true;
}
diff |= emitResourceTypeDiff(context, apkA, pkgA, typeA.get(), apkB, pkgB, typeB);
}
}
// Check for any newly added types.
for (std::unique_ptr<ResourceTableType>& typeB : pkgB->types) {
ResourceTableType* typeA = pkgA->findType(typeB->type);
if (!typeA) {
std::stringstream strStream;
strStream << "new type " << pkgB->name << ":" << typeB->type;
emitDiffLine(apkB->getSource(), strStream.str());
diff = true;
}
}
return diff;
}
static bool emitResourceTableDiff(IAaptContext* context, LoadedApk* apkA, LoadedApk* apkB) {
ResourceTable* tableA = apkA->getResourceTable();
ResourceTable* tableB = apkB->getResourceTable();
bool diff = false;
for (std::unique_ptr<ResourceTablePackage>& pkgA : tableA->packages) {
ResourceTablePackage* pkgB = tableB->findPackage(pkgA->name);
if (!pkgB) {
std::stringstream strStream;
strStream << "missing package " << pkgA->name;
emitDiffLine(apkB->getSource(), strStream.str());
diff = true;
} else {
if (pkgA->id != pkgB->id) {
std::stringstream strStream;
strStream << "package '" << pkgA->name << "' has different id (";
if (pkgB->id) {
strStream << "0x" << std::hex << pkgB->id.value();
} else {
strStream << "none";
}
strStream << " vs ";
if (pkgA->id) {
strStream << "0x" << std::hex << pkgA->id.value();
} else {
strStream << "none";
}
strStream << ")";
emitDiffLine(apkB->getSource(), strStream.str());
diff = true;
}
diff |= emitResourcePackageDiff(context, apkA, pkgA.get(), apkB, pkgB);
}
}
// Check for any newly added packages.
for (std::unique_ptr<ResourceTablePackage>& pkgB : tableB->packages) {
ResourceTablePackage* pkgA = tableA->findPackage(pkgB->name);
if (!pkgA) {
std::stringstream strStream;
strStream << "new package " << pkgB->name;
emitDiffLine(apkB->getSource(), strStream.str());
diff = true;
}
}
return diff;
}
int diff(const std::vector<StringPiece>& args) {
DiffContext context;
Flags flags;
if (!flags.parse("aapt2 diff", args, &std::cerr)) {
return 1;
}
if (flags.getArgs().size() != 2u) {
std::cerr << "must have two apks as arguments.\n\n";
flags.usage("aapt2 diff", &std::cerr);
return 1;
}
std::unique_ptr<LoadedApk> apkA = loadApkFromPath(&context, flags.getArgs()[0]);
std::unique_ptr<LoadedApk> apkB = loadApkFromPath(&context, flags.getArgs()[1]);
if (!apkA || !apkB) {
return 1;
}
if (emitResourceTableDiff(&context, apkA.get(), apkB.get())) {
// We emitted a diff, so return 1 (failure).
return 1;
}
return 0;
}
} // namespace aapt

View File

@@ -234,6 +234,8 @@ private:
const pb::Attribute& pbAttr = pbCompoundValue.attr();
std::unique_ptr<Attribute> attr = util::make_unique<Attribute>(isWeak);
attr->typeMask = pbAttr.format_flags();
attr->minInt = pbAttr.min_int();
attr->maxInt = pbAttr.max_int();
for (const pb::Attribute_Symbol& pbSymbol : pbAttr.symbols()) {
Attribute::Symbol symbol;
deserializeItemCommon(pbSymbol, &symbol.symbol);