diff options
author | Adam Lesinski <adamlesinski@google.com> | 2016-04-25 14:20:21 -0700 |
---|---|---|
committer | Adam Lesinski <adamlesinski@google.com> | 2016-04-30 01:36:12 +0000 |
commit | 458b877488c12ea4336d8fc00a95d9c0298bd6d0 (patch) | |
tree | 6169f78510254f6bde737279763049f00eaef200 /tools/aapt2/diff/Diff.cpp | |
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
Diffstat (limited to 'tools/aapt2/diff/Diff.cpp')
-rw-r--r-- | tools/aapt2/diff/Diff.cpp | 411 |
1 files changed, 411 insertions, 0 deletions
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 |