Merge "AAPT2: Add manifest merging" into mnc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
6b717c66a7
@@ -36,6 +36,7 @@ sources := \
|
|||||||
Linker.cpp \
|
Linker.cpp \
|
||||||
Locale.cpp \
|
Locale.cpp \
|
||||||
Logger.cpp \
|
Logger.cpp \
|
||||||
|
ManifestMerger.cpp \
|
||||||
ManifestParser.cpp \
|
ManifestParser.cpp \
|
||||||
ManifestValidator.cpp \
|
ManifestValidator.cpp \
|
||||||
Png.cpp \
|
Png.cpp \
|
||||||
@@ -65,6 +66,7 @@ testSources := \
|
|||||||
JavaClassGenerator_test.cpp \
|
JavaClassGenerator_test.cpp \
|
||||||
Linker_test.cpp \
|
Linker_test.cpp \
|
||||||
Locale_test.cpp \
|
Locale_test.cpp \
|
||||||
|
ManifestMerger_test.cpp \
|
||||||
ManifestParser_test.cpp \
|
ManifestParser_test.cpp \
|
||||||
Maybe_test.cpp \
|
Maybe_test.cpp \
|
||||||
NameMangler_test.cpp \
|
NameMangler_test.cpp \
|
||||||
@@ -102,7 +104,7 @@ else
|
|||||||
endif
|
endif
|
||||||
|
|
||||||
cFlags := -Wall -Werror -Wno-unused-parameter -UNDEBUG
|
cFlags := -Wall -Werror -Wno-unused-parameter -UNDEBUG
|
||||||
cppFlags := -std=c++11 -Wno-missing-field-initializers
|
cppFlags := -std=c++11 -Wno-missing-field-initializers -Wno-unused-private-field
|
||||||
|
|
||||||
# ==========================================================
|
# ==========================================================
|
||||||
# Build the host static library: libaapt2
|
# Build the host static library: libaapt2
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
#include "Flag.h"
|
#include "Flag.h"
|
||||||
#include "JavaClassGenerator.h"
|
#include "JavaClassGenerator.h"
|
||||||
#include "Linker.h"
|
#include "Linker.h"
|
||||||
|
#include "ManifestMerger.h"
|
||||||
#include "ManifestParser.h"
|
#include "ManifestParser.h"
|
||||||
#include "ManifestValidator.h"
|
#include "ManifestValidator.h"
|
||||||
#include "NameMangler.h"
|
#include "NameMangler.h"
|
||||||
@@ -55,6 +56,20 @@ constexpr const char* kAaptVersionStr = "2.0-alpha";
|
|||||||
|
|
||||||
using namespace aapt;
|
using namespace aapt;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used with smart pointers to free malloc'ed memory.
|
||||||
|
*/
|
||||||
|
struct DeleteMalloc {
|
||||||
|
void operator()(void* ptr) {
|
||||||
|
free(ptr);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct StaticLibraryData {
|
||||||
|
Source source;
|
||||||
|
std::unique_ptr<ZipFile> apk;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Collect files from 'root', filtering out any files that do not
|
* Collect files from 'root', filtering out any files that do not
|
||||||
* match the FileFilter 'filter'.
|
* match the FileFilter 'filter'.
|
||||||
@@ -493,6 +508,7 @@ bool copyFile(const AaptOptions& options, const CompileItem& item, ZipFile* outA
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool compileManifest(const AaptOptions& options, const std::shared_ptr<IResolver>& resolver,
|
bool compileManifest(const AaptOptions& options, const std::shared_ptr<IResolver>& resolver,
|
||||||
|
const std::map<std::shared_ptr<ResourceTable>, StaticLibraryData>& libApks,
|
||||||
const android::ResTable& table, ZipFile* outApk) {
|
const android::ResTable& table, ZipFile* outApk) {
|
||||||
if (options.verbose) {
|
if (options.verbose) {
|
||||||
Logger::note(options.manifest) << "compiling AndroidManifest.xml." << std::endl;
|
Logger::note(options.manifest) << "compiling AndroidManifest.xml." << std::endl;
|
||||||
@@ -510,9 +526,40 @@ bool compileManifest(const AaptOptions& options, const std::shared_ptr<IResolver
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ManifestMerger merger({});
|
||||||
|
if (!merger.setAppManifest(options.manifest, options.appInfo.package, std::move(root))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& entry : libApks) {
|
||||||
|
ZipFile* libApk = entry.second.apk.get();
|
||||||
|
const std::u16string& libPackage = entry.first->getPackage();
|
||||||
|
const Source& libSource = entry.second.source;
|
||||||
|
|
||||||
|
ZipEntry* zipEntry = libApk->getEntryByName("AndroidManifest.xml");
|
||||||
|
if (!zipEntry) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<void, DeleteMalloc> uncompressedData = std::unique_ptr<void, DeleteMalloc>(
|
||||||
|
libApk->uncompress(zipEntry));
|
||||||
|
assert(uncompressedData);
|
||||||
|
|
||||||
|
SourceLogger logger(libSource);
|
||||||
|
std::unique_ptr<xml::Node> libRoot = xml::inflate(uncompressedData.get(),
|
||||||
|
zipEntry->getUncompressedLen(), &logger);
|
||||||
|
if (!libRoot) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!merger.mergeLibraryManifest(libSource, libPackage, std::move(libRoot))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
BigBuffer outBuffer(1024);
|
BigBuffer outBuffer(1024);
|
||||||
if (!xml::flattenAndLink(options.manifest, root.get(), options.appInfo.package, resolver, {},
|
if (!xml::flattenAndLink(options.manifest, merger.getMergedXml(), options.appInfo.package,
|
||||||
&outBuffer)) {
|
resolver, {}, &outBuffer)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -667,17 +714,6 @@ static void addApkFilesToLinkQueue(const std::u16string& package, const Source&
|
|||||||
static constexpr int kOpenFlags = ZipFile::kOpenCreate | ZipFile::kOpenTruncate |
|
static constexpr int kOpenFlags = ZipFile::kOpenCreate | ZipFile::kOpenTruncate |
|
||||||
ZipFile::kOpenReadWrite;
|
ZipFile::kOpenReadWrite;
|
||||||
|
|
||||||
struct DeleteMalloc {
|
|
||||||
void operator()(void* ptr) {
|
|
||||||
free(ptr);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct StaticLibraryData {
|
|
||||||
Source source;
|
|
||||||
std::unique_ptr<ZipFile> apk;
|
|
||||||
};
|
|
||||||
|
|
||||||
bool link(const AaptOptions& options, const std::shared_ptr<ResourceTable>& outTable,
|
bool link(const AaptOptions& options, const std::shared_ptr<ResourceTable>& outTable,
|
||||||
const std::shared_ptr<IResolver>& resolver) {
|
const std::shared_ptr<IResolver>& resolver) {
|
||||||
std::map<std::shared_ptr<ResourceTable>, StaticLibraryData> apkFiles;
|
std::map<std::shared_ptr<ResourceTable>, StaticLibraryData> apkFiles;
|
||||||
@@ -770,7 +806,7 @@ bool link(const AaptOptions& options, const std::shared_ptr<ResourceTable>& outT
|
|||||||
}
|
}
|
||||||
|
|
||||||
android::ResTable binTable;
|
android::ResTable binTable;
|
||||||
if (!compileManifest(options, resolver, binTable, &outApk)) {
|
if (!compileManifest(options, resolver, apkFiles, binTable, &outApk)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
376
tools/aapt2/ManifestMerger.cpp
Normal file
376
tools/aapt2/ManifestMerger.cpp
Normal file
@@ -0,0 +1,376 @@
|
|||||||
|
#include "ManifestMerger.h"
|
||||||
|
#include "Maybe.h"
|
||||||
|
#include "ResourceParser.h"
|
||||||
|
#include "Source.h"
|
||||||
|
#include "Util.h"
|
||||||
|
#include "XmlPullParser.h"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <memory>
|
||||||
|
#include <set>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace aapt {
|
||||||
|
|
||||||
|
constexpr const char16_t* kSchemaAndroid = u"http://schemas.android.com/apk/res/android";
|
||||||
|
|
||||||
|
static xml::Element* findManifest(xml::Node* root) {
|
||||||
|
if (!root) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (root->type == xml::NodeType::kNamespace) {
|
||||||
|
if (root->children.empty()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
root = root->children[0].get();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (root && root->type == xml::NodeType::kElement) {
|
||||||
|
xml::Element* el = static_cast<xml::Element*>(root);
|
||||||
|
if (el->namespaceUri.empty() && el->name == u"manifest") {
|
||||||
|
return el;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static xml::Element* findChildWithSameName(xml::Element* parent, xml::Element* src) {
|
||||||
|
xml::Attribute* attrKey = src->findAttribute(kSchemaAndroid, u"name");
|
||||||
|
if (!attrKey) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return parent->findChildWithAttribute(src->namespaceUri, src->name, attrKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool attrLess(const xml::Attribute& lhs, const xml::Attribute& rhs) {
|
||||||
|
return std::tie(lhs.namespaceUri, lhs.name, lhs.value)
|
||||||
|
< std::tie(rhs.namespaceUri, rhs.name, rhs.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int compare(xml::Element* lhs, xml::Element* rhs) {
|
||||||
|
int diff = lhs->attributes.size() - rhs->attributes.size();
|
||||||
|
if (diff != 0) {
|
||||||
|
return diff;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::set<xml::Attribute, decltype(&attrLess)> lhsAttrs(&attrLess);
|
||||||
|
lhsAttrs.insert(lhs->attributes.begin(), lhs->attributes.end());
|
||||||
|
for (auto& attr : rhs->attributes) {
|
||||||
|
if (lhsAttrs.erase(attr) == 0) {
|
||||||
|
// The rhs attribute is not in the left.
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!lhsAttrs.empty()) {
|
||||||
|
// The lhs has attributes not in the rhs.
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ManifestMerger::ManifestMerger(const Options& options) :
|
||||||
|
mOptions(options), mAppLogger({}), mLogger({}) {
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ManifestMerger::setAppManifest(const Source& source, const std::u16string& package,
|
||||||
|
std::unique_ptr<xml::Node> root) {
|
||||||
|
|
||||||
|
mAppLogger = SourceLogger{ source };
|
||||||
|
mRoot = std::move(root);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ManifestMerger::checkEqual(xml::Element* elA, xml::Element* elB) {
|
||||||
|
if (compare(elA, elB) != 0) {
|
||||||
|
mLogger.error(elB->lineNumber)
|
||||||
|
<< "library tag '" << elB->name << "' conflicts with app tag."
|
||||||
|
<< std::endl;
|
||||||
|
mAppLogger.note(elA->lineNumber)
|
||||||
|
<< "app tag '" << elA->name << "' defined here."
|
||||||
|
<< std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<xml::Element*> childrenA = elA->getChildElements();
|
||||||
|
std::vector<xml::Element*> childrenB = elB->getChildElements();
|
||||||
|
|
||||||
|
if (childrenA.size() != childrenB.size()) {
|
||||||
|
mLogger.error(elB->lineNumber)
|
||||||
|
<< "library tag '" << elB->name << "' children conflict with app tag."
|
||||||
|
<< std::endl;
|
||||||
|
mAppLogger.note(elA->lineNumber)
|
||||||
|
<< "app tag '" << elA->name << "' defined here."
|
||||||
|
<< std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto cmp = [](xml::Element* lhs, xml::Element* rhs) -> bool {
|
||||||
|
return compare(lhs, rhs) < 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::sort(childrenA.begin(), childrenA.end(), cmp);
|
||||||
|
std::sort(childrenB.begin(), childrenB.end(), cmp);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < childrenA.size(); i++) {
|
||||||
|
if (!checkEqual(childrenA[i], childrenB[i])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ManifestMerger::mergeNewOrEqual(xml::Element* parentA, xml::Element* elA, xml::Element* elB) {
|
||||||
|
if (!elA) {
|
||||||
|
parentA->addChild(elB->clone());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return checkEqual(elA, elB);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ManifestMerger::mergePreferRequired(xml::Element* parentA, xml::Element* elA,
|
||||||
|
xml::Element* elB) {
|
||||||
|
if (!elA) {
|
||||||
|
parentA->addChild(elB->clone());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
xml::Attribute* reqA = elA->findAttribute(kSchemaAndroid, u"required");
|
||||||
|
xml::Attribute* reqB = elB->findAttribute(kSchemaAndroid, u"required");
|
||||||
|
bool requiredA = !reqA || (reqA->value != u"false" && reqA->value != u"FALSE");
|
||||||
|
bool requiredB = !reqB || (reqB->value != u"false" && reqB->value != u"FALSE");
|
||||||
|
if (!requiredA && requiredB) {
|
||||||
|
if (reqA) {
|
||||||
|
*reqA = xml::Attribute{ kSchemaAndroid, u"required", u"true" };
|
||||||
|
} else {
|
||||||
|
elA->attributes.push_back(xml::Attribute{ kSchemaAndroid, u"required", u"true" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int findIntegerValue(xml::Attribute* attr, int defaultValue) {
|
||||||
|
if (attr) {
|
||||||
|
std::unique_ptr<BinaryPrimitive> integer = ResourceParser::tryParseInt(attr->value);
|
||||||
|
if (integer) {
|
||||||
|
return integer->value.data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ManifestMerger::mergeUsesSdk(xml::Element* elA, xml::Element* elB) {
|
||||||
|
bool error = false;
|
||||||
|
xml::Attribute* minAttrA = nullptr;
|
||||||
|
xml::Attribute* minAttrB = nullptr;
|
||||||
|
if (elA) {
|
||||||
|
minAttrA = elA->findAttribute(kSchemaAndroid, u"minSdkVersion");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (elB) {
|
||||||
|
minAttrB = elB->findAttribute(kSchemaAndroid, u"minSdkVersion");
|
||||||
|
}
|
||||||
|
|
||||||
|
int minSdkA = findIntegerValue(minAttrA, 1);
|
||||||
|
int minSdkB = findIntegerValue(minAttrB, 1);
|
||||||
|
|
||||||
|
if (minSdkA < minSdkB) {
|
||||||
|
std::ostream* out;
|
||||||
|
if (minAttrA) {
|
||||||
|
out = &(mAppLogger.error(elA->lineNumber) << "app declares ");
|
||||||
|
} else if (elA) {
|
||||||
|
out = &(mAppLogger.error(elA->lineNumber) << "app has implied ");
|
||||||
|
} else {
|
||||||
|
out = &(mAppLogger.error() << "app has implied ");
|
||||||
|
}
|
||||||
|
|
||||||
|
*out << "minSdkVersion=" << minSdkA << " but library expects a higher SDK version."
|
||||||
|
<< std::endl;
|
||||||
|
|
||||||
|
// elB is valid because minSdkB wouldn't be greater than minSdkA if it wasn't.
|
||||||
|
mLogger.note(elB->lineNumber)
|
||||||
|
<< "library declares minSdkVersion=" << minSdkB << "."
|
||||||
|
<< std::endl;
|
||||||
|
error = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
xml::Attribute* targetAttrA = nullptr;
|
||||||
|
xml::Attribute* targetAttrB = nullptr;
|
||||||
|
|
||||||
|
if (elA) {
|
||||||
|
targetAttrA = elA->findAttribute(kSchemaAndroid, u"targetSdkVersion");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (elB) {
|
||||||
|
targetAttrB = elB->findAttribute(kSchemaAndroid, u"targetSdkVersion");
|
||||||
|
}
|
||||||
|
|
||||||
|
int targetSdkA = findIntegerValue(targetAttrA, minSdkA);
|
||||||
|
int targetSdkB = findIntegerValue(targetAttrB, minSdkB);
|
||||||
|
|
||||||
|
if (targetSdkA < targetSdkB) {
|
||||||
|
std::ostream* out;
|
||||||
|
if (targetAttrA) {
|
||||||
|
out = &(mAppLogger.warn(elA->lineNumber) << "app declares ");
|
||||||
|
} else if (elA) {
|
||||||
|
out = &(mAppLogger.warn(elA->lineNumber) << "app has implied ");
|
||||||
|
} else {
|
||||||
|
out = &(mAppLogger.warn() << "app has implied ");
|
||||||
|
}
|
||||||
|
|
||||||
|
*out << "targetSdkVerion=" << targetSdkA << " but library expects target SDK "
|
||||||
|
<< targetSdkB << "." << std::endl;
|
||||||
|
|
||||||
|
mLogger.note(elB->lineNumber)
|
||||||
|
<< "library declares targetSdkVersion=" << targetSdkB << "."
|
||||||
|
<< std::endl;
|
||||||
|
error = true;
|
||||||
|
}
|
||||||
|
return !error;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ManifestMerger::mergeApplication(xml::Element* applicationA, xml::Element* applicationB) {
|
||||||
|
if (!applicationA || !applicationB) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool error = false;
|
||||||
|
|
||||||
|
// First make sure that the names are identical.
|
||||||
|
xml::Attribute* nameA = applicationA->findAttribute(kSchemaAndroid, u"name");
|
||||||
|
xml::Attribute* nameB = applicationB->findAttribute(kSchemaAndroid, u"name");
|
||||||
|
if (nameB) {
|
||||||
|
if (!nameA) {
|
||||||
|
applicationA->attributes.push_back(*nameB);
|
||||||
|
} else if (nameA->value != nameB->value) {
|
||||||
|
mLogger.error(applicationB->lineNumber)
|
||||||
|
<< "conflicting application name '"
|
||||||
|
<< nameB->value
|
||||||
|
<< "'." << std::endl;
|
||||||
|
mAppLogger.note(applicationA->lineNumber)
|
||||||
|
<< "application defines application name '"
|
||||||
|
<< nameA->value
|
||||||
|
<< "'." << std::endl;
|
||||||
|
error = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now we descend into the activity/receiver/service/provider tags
|
||||||
|
for (xml::Element* elB : applicationB->getChildElements()) {
|
||||||
|
if (!elB->namespaceUri.empty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (elB->name == u"activity" || elB->name == u"activity-alias"
|
||||||
|
|| elB->name == u"service" || elB->name == u"receiver"
|
||||||
|
|| elB->name == u"provider" || elB->name == u"meta-data") {
|
||||||
|
xml::Element* elA = findChildWithSameName(applicationA, elB);
|
||||||
|
error |= !mergeNewOrEqual(applicationA, elA, elB);
|
||||||
|
} else if (elB->name == u"uses-library") {
|
||||||
|
xml::Element* elA = findChildWithSameName(applicationA, elB);
|
||||||
|
error |= !mergePreferRequired(applicationA, elA, elB);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return !error;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ManifestMerger::mergeLibraryManifest(const Source& source, const std::u16string& package,
|
||||||
|
std::unique_ptr<xml::Node> libRoot) {
|
||||||
|
mLogger = SourceLogger{ source };
|
||||||
|
xml::Element* manifestA = findManifest(mRoot.get());
|
||||||
|
xml::Element* manifestB = findManifest(libRoot.get());
|
||||||
|
if (!manifestA) {
|
||||||
|
mAppLogger.error() << "missing manifest tag." << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!manifestB) {
|
||||||
|
mLogger.error() << "library missing manifest tag." << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool error = false;
|
||||||
|
|
||||||
|
// Do <application> first.
|
||||||
|
xml::Element* applicationA = manifestA->findChild({}, u"application");
|
||||||
|
xml::Element* applicationB = manifestB->findChild({}, u"application");
|
||||||
|
error |= !mergeApplication(applicationA, applicationB);
|
||||||
|
|
||||||
|
// Do <uses-sdk> next.
|
||||||
|
xml::Element* usesSdkA = manifestA->findChild({}, u"uses-sdk");
|
||||||
|
xml::Element* usesSdkB = manifestB->findChild({}, u"uses-sdk");
|
||||||
|
error |= !mergeUsesSdk(usesSdkA, usesSdkB);
|
||||||
|
|
||||||
|
for (xml::Element* elB : manifestB->getChildElements()) {
|
||||||
|
if (!elB->namespaceUri.empty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (elB->name == u"uses-permission" || elB->name == u"permission"
|
||||||
|
|| elB->name == u"permission-group" || elB->name == u"permission-tree") {
|
||||||
|
xml::Element* elA = findChildWithSameName(manifestA, elB);
|
||||||
|
error |= !mergeNewOrEqual(manifestA, elA, elB);
|
||||||
|
} else if (elB->name == u"uses-feature") {
|
||||||
|
xml::Element* elA = findChildWithSameName(manifestA, elB);
|
||||||
|
error |= !mergePreferRequired(manifestA, elA, elB);
|
||||||
|
} else if (elB->name == u"uses-configuration" || elB->name == u"supports-screen"
|
||||||
|
|| elB->name == u"compatible-screens" || elB->name == u"supports-gl-texture") {
|
||||||
|
xml::Element* elA = findChildWithSameName(manifestA, elB);
|
||||||
|
error |= !checkEqual(elA, elB);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return !error;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void printMerged(xml::Node* node, int depth) {
|
||||||
|
std::string indent;
|
||||||
|
for (int i = 0; i < depth; i++) {
|
||||||
|
indent += " ";
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (node->type) {
|
||||||
|
case xml::NodeType::kNamespace:
|
||||||
|
std::cerr << indent << "N: "
|
||||||
|
<< "xmlns:" << static_cast<xml::Namespace*>(node)->namespacePrefix
|
||||||
|
<< "=\"" << static_cast<xml::Namespace*>(node)->namespaceUri
|
||||||
|
<< "\"\n";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case xml::NodeType::kElement:
|
||||||
|
std::cerr << indent << "E: "
|
||||||
|
<< static_cast<xml::Element*>(node)->namespaceUri
|
||||||
|
<< ":" << static_cast<xml::Element*>(node)->name
|
||||||
|
<< "\n";
|
||||||
|
for (const auto& attr : static_cast<xml::Element*>(node)->attributes) {
|
||||||
|
std::cerr << indent << " A: "
|
||||||
|
<< attr.namespaceUri
|
||||||
|
<< ":" << attr.name
|
||||||
|
<< "=\"" << attr.value << "\"\n";
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case xml::NodeType::kText:
|
||||||
|
std::cerr << indent << "T: \"" << static_cast<xml::Text*>(node)->text << "\"\n";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& child : node->children) {
|
||||||
|
printMerged(child.get(), depth + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
xml::Node* ManifestMerger::getMergedXml() {
|
||||||
|
return mRoot.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ManifestMerger::printMerged() {
|
||||||
|
if (!mRoot) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
::aapt::printMerged(mRoot.get(), 0);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace aapt
|
||||||
45
tools/aapt2/ManifestMerger.h
Normal file
45
tools/aapt2/ManifestMerger.h
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
#ifndef AAPT_MANIFEST_MERGER_H
|
||||||
|
#define AAPT_MANIFEST_MERGER_H
|
||||||
|
|
||||||
|
#include "Logger.h"
|
||||||
|
#include "Source.h"
|
||||||
|
#include "XmlDom.h"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace aapt {
|
||||||
|
|
||||||
|
class ManifestMerger {
|
||||||
|
public:
|
||||||
|
struct Options {
|
||||||
|
};
|
||||||
|
|
||||||
|
ManifestMerger(const Options& options);
|
||||||
|
|
||||||
|
bool setAppManifest(const Source& source, const std::u16string& package,
|
||||||
|
std::unique_ptr<xml::Node> root);
|
||||||
|
|
||||||
|
bool mergeLibraryManifest(const Source& source, const std::u16string& package,
|
||||||
|
std::unique_ptr<xml::Node> libRoot);
|
||||||
|
|
||||||
|
xml::Node* getMergedXml();
|
||||||
|
|
||||||
|
bool printMerged();
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool mergeNewOrEqual(xml::Element* parentA, xml::Element* elA, xml::Element* elB);
|
||||||
|
bool mergePreferRequired(xml::Element* parentA, xml::Element* elA, xml::Element* elB);
|
||||||
|
bool checkEqual(xml::Element* elA, xml::Element* elB);
|
||||||
|
bool mergeApplication(xml::Element* applicationA, xml::Element* applicationB);
|
||||||
|
bool mergeUsesSdk(xml::Element* elA, xml::Element* elB);
|
||||||
|
|
||||||
|
Options mOptions;
|
||||||
|
std::unique_ptr<xml::Node> mRoot;
|
||||||
|
SourceLogger mAppLogger;
|
||||||
|
SourceLogger mLogger;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace aapt
|
||||||
|
|
||||||
|
#endif // AAPT_MANIFEST_MERGER_H
|
||||||
121
tools/aapt2/ManifestMerger_test.cpp
Normal file
121
tools/aapt2/ManifestMerger_test.cpp
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2015 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 "ManifestMerger.h"
|
||||||
|
#include "SourceXmlPullParser.h"
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace aapt {
|
||||||
|
|
||||||
|
constexpr const char* kAppManifest = R"EOF(<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<uses-sdk android:minSdkVersion="7" android:targetSdkVersion="21" />
|
||||||
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
|
<uses-feature android:name="android.hardware.GPS" android:required="false" />
|
||||||
|
<application android:name="com.android.library.Application">
|
||||||
|
<activity android:name="com.android.example.MainActivity">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
<service android:name="com.android.library.Service">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="com.android.library.intent.action.SYNC" />
|
||||||
|
</intent-filter>
|
||||||
|
</service>
|
||||||
|
</application>
|
||||||
|
</manifest>
|
||||||
|
)EOF";
|
||||||
|
|
||||||
|
constexpr const char* kLibManifest = R"EOF(<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<uses-sdk android:minSdkVersion="4" android:targetSdkVersion="21" />
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
<uses-feature android:name="android.hardware.GPS" />
|
||||||
|
<uses-permission android:name="android.permission.GPS" />
|
||||||
|
<application android:name="com.android.library.Application">
|
||||||
|
<service android:name="com.android.library.Service">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="com.android.library.intent.action.SYNC" />
|
||||||
|
</intent-filter>
|
||||||
|
</service>
|
||||||
|
<provider android:name="com.android.library.DocumentProvider"
|
||||||
|
android:authorities="com.android.library.documents"
|
||||||
|
android:grantUriPermission="true"
|
||||||
|
android:exported="true"
|
||||||
|
android:permission="android.permission.MANAGE_DOCUMENTS"
|
||||||
|
android:enabled="@bool/atLeastKitKat">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.content.action.DOCUMENTS_PROVIDER" />
|
||||||
|
</intent-filter>
|
||||||
|
</provider>
|
||||||
|
</application>
|
||||||
|
</manifest>
|
||||||
|
)EOF";
|
||||||
|
|
||||||
|
constexpr const char* kBadLibManifest = R"EOF(<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<uses-sdk android:minSdkVersion="17" android:targetSdkVersion="22" />
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
<uses-feature android:name="android.hardware.GPS" />
|
||||||
|
<uses-permission android:name="android.permission.GPS" />
|
||||||
|
<application android:name="com.android.library.Application2">
|
||||||
|
<service android:name="com.android.library.Service">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="com.android.library.intent.action.SYNC_ACTION" />
|
||||||
|
</intent-filter>
|
||||||
|
</service>
|
||||||
|
</application>
|
||||||
|
</manifest>
|
||||||
|
)EOF";
|
||||||
|
|
||||||
|
TEST(ManifestMergerTest, MergeManifestsSuccess) {
|
||||||
|
std::stringstream inA(kAppManifest);
|
||||||
|
std::stringstream inB(kLibManifest);
|
||||||
|
|
||||||
|
const Source sourceA = { "AndroidManifest.xml" };
|
||||||
|
const Source sourceB = { "lib.apk/AndroidManifest.xml" };
|
||||||
|
SourceLogger loggerA(sourceA);
|
||||||
|
SourceLogger loggerB(sourceB);
|
||||||
|
|
||||||
|
ManifestMerger merger({});
|
||||||
|
EXPECT_TRUE(merger.setAppManifest(sourceA, u"com.android.example",
|
||||||
|
xml::inflate(&inA, &loggerA)));
|
||||||
|
EXPECT_TRUE(merger.mergeLibraryManifest(sourceB, u"com.android.library",
|
||||||
|
xml::inflate(&inB, &loggerB)));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ManifestMergerTest, MergeManifestFail) {
|
||||||
|
std::stringstream inA(kAppManifest);
|
||||||
|
std::stringstream inB(kBadLibManifest);
|
||||||
|
|
||||||
|
const Source sourceA = { "AndroidManifest.xml" };
|
||||||
|
const Source sourceB = { "lib.apk/AndroidManifest.xml" };
|
||||||
|
SourceLogger loggerA(sourceA);
|
||||||
|
SourceLogger loggerB(sourceB);
|
||||||
|
|
||||||
|
ManifestMerger merger({});
|
||||||
|
EXPECT_TRUE(merger.setAppManifest(sourceA, u"com.android.example",
|
||||||
|
xml::inflate(&inA, &loggerA)));
|
||||||
|
EXPECT_FALSE(merger.mergeLibraryManifest(sourceB, u"com.android.library",
|
||||||
|
xml::inflate(&inB, &loggerB)));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace aapt
|
||||||
@@ -1,3 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="android.appcompat"/>
|
package="android.appcompat">
|
||||||
|
|
||||||
|
<uses-feature android:name="bloooop" />
|
||||||
|
</manifest>
|
||||||
|
|||||||
Reference in New Issue
Block a user