diff options
author | 2016-04-25 14:20:21 -0700 | |
---|---|---|
committer | 2016-04-30 01:36:12 +0000 | |
commit | 458b877488c12ea4336d8fc00a95d9c0298bd6d0 (patch) | |
tree | 6169f78510254f6bde737279763049f00eaef200 | |
parent | 832449800e2e5068ab0eb986bb359906ddf6efdc (diff) |
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
-rw-r--r-- | tools/aapt2/Android.mk | 1 | ||||
-rw-r--r-- | tools/aapt2/Main.cpp | 5 | ||||
-rw-r--r-- | tools/aapt2/ResourceParser.cpp | 12 | ||||
-rw-r--r-- | tools/aapt2/ResourceTable.cpp | 11 | ||||
-rw-r--r-- | tools/aapt2/ResourceTable.h | 4 | ||||
-rw-r--r-- | tools/aapt2/ResourceUtils.cpp | 2 | ||||
-rw-r--r-- | tools/aapt2/ResourceValues.cpp | 172 | ||||
-rw-r--r-- | tools/aapt2/ResourceValues.h | 43 | ||||
-rw-r--r-- | tools/aapt2/ValueVisitor.h | 6 | ||||
-rw-r--r-- | tools/aapt2/compile/PseudolocaleGenerator.cpp | 41 | ||||
-rw-r--r-- | tools/aapt2/diff/Diff.cpp | 411 | ||||
-rw-r--r-- | tools/aapt2/proto/TableProtoDeserializer.cpp | 2 |
12 files changed, 649 insertions, 61 deletions
diff --git a/tools/aapt2/Android.mk b/tools/aapt2/Android.mk index 3a1e2bb3bf08..4b5ea65d8fed 100644 --- a/tools/aapt2/Android.mk +++ b/tools/aapt2/Android.mk @@ -115,6 +115,7 @@ testSources := \ 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 a2fadd95db3f..00d8aaeeda55 100644 --- a/tools/aapt2/Main.cpp +++ b/tools/aapt2/Main.cpp @@ -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; } diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp index 9704d97029b7..a84c306e2733 100644 --- a/tools/aapt2/ResourceParser.cpp +++ b/tools/aapt2/ResourceParser.cpp @@ -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)) { diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp index 8d734f3fc36d..e700ed98365a 100644 --- a/tools/aapt2/ResourceTable.cpp +++ b/tools/aapt2/ResourceTable.cpp @@ -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. diff --git a/tools/aapt2/ResourceTable.h b/tools/aapt2/ResourceTable.h index 7f5c2b8c0f37..5690ea6fa614 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 @@ 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); diff --git a/tools/aapt2/ResourceUtils.cpp b/tools/aapt2/ResourceUtils.cpp index 74c48b0f8426..a0a7efc46476 100644 --- a/tools/aapt2/ResourceUtils.cpp +++ b/tools/aapt2/ResourceUtils.cpp @@ -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()) { diff --git a/tools/aapt2/ResourceValues.cpp b/tools/aapt2/ResourceValues.cpp index dd7ff013e524..c10b134cb36e 100644 --- a/tools/aapt2/ResourceValues.cpp +++ b/tools/aapt2/ResourceValues.cpp @@ -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) { -} - -void String::setTranslateable(bool val) { - mTranslateable = val; +String::String(const StringPool::Ref& ref) : value(ref) { } -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); } diff --git a/tools/aapt2/ResourceValues.h b/tools/aapt2/ResourceValues.h index 43354acf1d0b..aa1b550bdf09 100644 --- a/tools/aapt2/ResourceValues.h +++ b/tools/aapt2/ResourceValues.h @@ -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; }; diff --git a/tools/aapt2/ValueVisitor.h b/tools/aapt2/ValueVisitor.h index ea2aa55764c1..b8bc5db7d6e4 100644 --- a/tools/aapt2/ValueVisitor.h +++ b/tools/aapt2/ValueVisitor.h @@ -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) { diff --git a/tools/aapt2/compile/PseudolocaleGenerator.cpp b/tools/aapt2/compile/PseudolocaleGenerator.cpp index 99c20778c816..d080e16c520b 100644 --- a/tools/aapt2/compile/PseudolocaleGenerator.cpp +++ b/tools/aapt2/compile/PseudolocaleGenerator.cpp @@ -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()); diff --git a/tools/aapt2/diff/Diff.cpp b/tools/aapt2/diff/Diff.cpp new file mode 100644 index 000000000000..20b7b59642ca --- /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 82e4fb0146ab..1ec48f09f228 100644 --- a/tools/aapt2/proto/TableProtoDeserializer.cpp +++ b/tools/aapt2/proto/TableProtoDeserializer.cpp @@ -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); |