Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit acf11ed7 authored by Adam Lesinski's avatar Adam Lesinski Committed by Android Git Automerger
Browse files

am 6b717c66: Merge "AAPT2: Add manifest merging" into mnc-dev

* commit '6b717c66':
  AAPT2: Add manifest merging
parents 621c88b9 6b717c66
Loading
Loading
Loading
Loading
+3 −1
Original line number Diff line number Diff line
@@ -36,6 +36,7 @@ sources := \
	Linker.cpp \
	Locale.cpp \
	Logger.cpp \
	ManifestMerger.cpp \
	ManifestParser.cpp \
	ManifestValidator.cpp \
	Png.cpp \
@@ -65,6 +66,7 @@ testSources := \
	JavaClassGenerator_test.cpp \
	Linker_test.cpp \
	Locale_test.cpp \
	ManifestMerger_test.cpp \
	ManifestParser_test.cpp \
	Maybe_test.cpp \
	NameMangler_test.cpp \
@@ -102,7 +104,7 @@ else
endif

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
+50 −14
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@
#include "Flag.h"
#include "JavaClassGenerator.h"
#include "Linker.h"
#include "ManifestMerger.h"
#include "ManifestParser.h"
#include "ManifestValidator.h"
#include "NameMangler.h"
@@ -55,6 +56,20 @@ constexpr const char* kAaptVersionStr = "2.0-alpha";

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
 * 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,
                     const std::map<std::shared_ptr<ResourceTable>, StaticLibraryData>& libApks,
                     const android::ResTable& table, ZipFile* outApk) {
    if (options.verbose) {
        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;
    }

    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);
    if (!xml::flattenAndLink(options.manifest, root.get(), options.appInfo.package, resolver, {},
                &outBuffer)) {
    if (!xml::flattenAndLink(options.manifest, merger.getMergedXml(), options.appInfo.package,
                resolver, {}, &outBuffer)) {
        return false;
    }

@@ -667,17 +714,6 @@ static void addApkFilesToLinkQueue(const std::u16string& package, const Source&
static constexpr int kOpenFlags = ZipFile::kOpenCreate | ZipFile::kOpenTruncate |
        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,
          const std::shared_ptr<IResolver>& resolver) {
    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;
    if (!compileManifest(options, resolver, binTable, &outApk)) {
    if (!compileManifest(options, resolver, apkFiles, binTable, &outApk)) {
        return false;
    }

+376 −0
Original line number Diff line number Diff line
#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 −0
Original line number Diff line number Diff line
#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 −0
Original line number Diff line number Diff line
/*
 * 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
Loading