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
diff --git a/tools/aapt2/Android.mk b/tools/aapt2/Android.mk
index 3a1e2bb..4b5ea65 100644
--- a/tools/aapt2/Android.mk
+++ b/tools/aapt2/Android.mk
@@ -115,6 +115,7 @@
toolSources := \
compile/Compile.cpp \
+ diff/Diff.cpp \
dump/Dump.cpp \
link/Link.cpp
diff --git a/tools/aapt2/Main.cpp b/tools/aapt2/Main.cpp
index a2fadd9..00d8aae 100644
--- a/tools/aapt2/Main.cpp
+++ b/tools/aapt2/Main.cpp
@@ -24,6 +24,7 @@
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 @@
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;
}
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index 9704d970..a84c306 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -152,7 +152,7 @@
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 @@
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)) {
diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp
index 8d734f3..e700ed9 100644
--- a/tools/aapt2/ResourceTable.cpp
+++ b/tools/aapt2/ResourceTable.cpp
@@ -189,6 +189,17 @@
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.
diff --git a/tools/aapt2/ResourceTable.h b/tools/aapt2/ResourceTable.h
index 7f5c2b8..5690ea6 100644
--- a/tools/aapt2/ResourceTable.h
+++ b/tools/aapt2/ResourceTable.h
@@ -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 @@
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);
diff --git a/tools/aapt2/ResourceUtils.cpp b/tools/aapt2/ResourceUtils.cpp
index 74c48b0..a0a7efc 100644
--- a/tools/aapt2/ResourceUtils.cpp
+++ b/tools/aapt2/ResourceUtils.cpp
@@ -289,7 +289,7 @@
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()) {
diff --git a/tools/aapt2/ResourceValues.cpp b/tools/aapt2/ResourceValues.cpp
index dd7ff01..c10b134 100644
--- a/tools/aapt2/ResourceValues.cpp
+++ b/tools/aapt2/ResourceValues.cpp
@@ -39,6 +39,14 @@
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 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 @@
}
}
+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 @@
*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 @@
*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 @@
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 @@
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 @@
*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 @@
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 @@
<< "]";
}
+ 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 @@
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 @@
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 @@
<< "]";
}
+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 @@
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);
}
diff --git a/tools/aapt2/ResourceValues.h b/tools/aapt2/ResourceValues.h
index 43354ac..aa1b550 100644
--- a/tools/aapt2/ResourceValues.h
+++ b/tools/aapt2/ResourceValues.h
@@ -54,6 +54,18 @@
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 @@
mComment = std::move(str);
}
+ virtual bool equals(const Value* value) const = 0;
+
/**
* Calls the appropriate overload of ValueVisitor.
*/
@@ -103,6 +117,7 @@
Source mSource;
std::u16string mComment;
bool mWeak = false;
+ bool mTranslateable = true;
};
/**
@@ -158,6 +173,7 @@
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 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 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 @@
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 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;
};
diff --git a/tools/aapt2/ValueVisitor.h b/tools/aapt2/ValueVisitor.h
index ea2aa55..b8bc5db 100644
--- a/tools/aapt2/ValueVisitor.h
+++ b/tools/aapt2/ValueVisitor.h
@@ -127,6 +127,11 @@
}
};
+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 @@
return visitor.value;
}
-
inline void visitAllValuesInPackage(ResourceTablePackage* pkg, RawValueVisitor* visitor) {
for (auto& type : pkg->types) {
for (auto& entry : type->entries) {
diff --git a/tools/aapt2/compile/PseudolocaleGenerator.cpp b/tools/aapt2/compile/PseudolocaleGenerator.cpp
index 99c2077..d080e16 100644
--- a/tools/aapt2/compile/PseudolocaleGenerator.cpp
+++ b/tools/aapt2/compile/PseudolocaleGenerator.cpp
@@ -128,23 +128,6 @@
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 @@
}
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 @@
}
void visit(StyledString* string) override {
- if (!string->isTranslateable()) {
- return;
- }
-
mItem = pseudolocalizeStyledString(string, mMethod, mPool);
mItem->setWeak(true);
}
@@ -238,14 +213,26 @@
}
}
+/**
+ * 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());
diff --git a/tools/aapt2/diff/Diff.cpp b/tools/aapt2/diff/Diff.cpp
new file mode 100644
index 0000000..20b7b59
--- /dev/null
+++ b/tools/aapt2/diff/Diff.cpp
@@ -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
diff --git a/tools/aapt2/proto/TableProtoDeserializer.cpp b/tools/aapt2/proto/TableProtoDeserializer.cpp
index 82e4fb0..1ec48f0 100644
--- a/tools/aapt2/proto/TableProtoDeserializer.cpp
+++ b/tools/aapt2/proto/TableProtoDeserializer.cpp
@@ -234,6 +234,8 @@
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);