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

Commit bf0bd0f9 authored by Adam Lesinski's avatar Adam Lesinski
Browse files

AAPT2: Add support to specify stable IDs

The --stable-ids flag allows the user to specify a file containing
a list of resource name and resource ID pairs in the form of:

package:type/name = 0xPPTTEEEE

This assigns the given resource the specified ID. It helps ensure
that when adding or removing resources, IDs are assigned in a stable
fashion.

If a package, type, or name is not found, no error or warning is
raised.

Change-Id: Ibc2f4e05cc924be255fedd862d835cb5b18d7584
parent ef556916
Loading
Loading
Loading
Loading
+16 −1
Original line number Diff line number Diff line
@@ -19,9 +19,10 @@

#include "ConfigDescription.h"
#include "Source.h"

#include "util/StringPiece.h"

#include <utils/JenkinsHash.h>

#include <iomanip>
#include <limits>
#include <sstream>
@@ -353,4 +354,18 @@ inline bool operator==(const SourcedResourceName& lhs, const SourcedResourceName

} // namespace aapt

namespace std {

template <> struct hash<aapt::ResourceName> {
    size_t operator()(const aapt::ResourceName& name) const {
        android::hash_t h = 0;
        h = android::JenkinsHashMix(h, hash<string>()(name.package));
        h = android::JenkinsHashMix(h, static_cast<uint32_t>(name.type));
        h = android::JenkinsHashMix(h, hash<string>()(name.entry));
        return static_cast<size_t>(h);
    }
};

} // namespace std

#endif // AAPT_RESOURCE_H
+11 −15
Original line number Diff line number Diff line
@@ -589,17 +589,14 @@ bool ResourceParser::parsePublic(xml::XmlPullParser* parser, ParsedResource* out

    outResource->name.type = *parsedType;

    if (Maybe<StringPiece> maybeId = xml::findNonEmptyAttribute(parser, "id")) {
        android::Res_value val;
        std::u16string idStr16 = util::utf8ToUtf16(maybeId.value());
        bool result = android::ResTable::stringToInt(idStr16.data(), idStr16.size(), &val);
        ResourceId resourceId(val.data);
        if (!result || !resourceId.isValid()) {
    if (Maybe<StringPiece> maybeIdStr = xml::findNonEmptyAttribute(parser, "id")) {
        Maybe<ResourceId> maybeId = ResourceUtils::tryParseResourceId(maybeIdStr.value());
        if (!maybeId) {
            mDiag->error(DiagMessage(outResource->source)
                         << "invalid resource ID '" << maybeId.value() << "' in <public>");
            return false;
        }
        outResource->id = resourceId;
        outResource->id = maybeId.value();
    }

    if (*parsedType == ResourceType::kId) {
@@ -626,23 +623,22 @@ bool ResourceParser::parsePublicGroup(xml::XmlPullParser* parser, ParsedResource
        return false;
    }

    Maybe<StringPiece> maybeId = xml::findNonEmptyAttribute(parser, "first-id");
    if (!maybeId) {
    Maybe<StringPiece> maybeIdStr = xml::findNonEmptyAttribute(parser, "first-id");
    if (!maybeIdStr) {
        mDiag->error(DiagMessage(outResource->source)
                     << "<public-group> must have a 'first-id' attribute");
        return false;
    }

    android::Res_value val;
    std::u16string idStr16 = util::utf8ToUtf16(maybeId.value());
    bool result = android::ResTable::stringToInt(idStr16.data(), idStr16.size(), &val);
    ResourceId nextId(val.data);
    if (!result || !nextId.isValid()) {
    Maybe<ResourceId> maybeId = ResourceUtils::tryParseResourceId(maybeIdStr.value());
    if (!maybeId) {
        mDiag->error(DiagMessage(outResource->source)
                     << "invalid resource ID '" << maybeId.value() << "' in <public-group>");
                     << "invalid resource ID '" << maybeIdStr.value() << "' in <public-group>");
        return false;
    }

    ResourceId nextId = maybeId.value();

    std::string comment;
    bool error = false;
    const size_t depth = parser->getDepth();
+16 −0
Original line number Diff line number Diff line
@@ -436,6 +436,22 @@ bool tryParseBool(const StringPiece& str, bool* outValue) {
    return false;
}

Maybe<ResourceId> tryParseResourceId(const StringPiece& str) {
    StringPiece trimmedStr(util::trimWhitespace(str));

    std::u16string str16 = util::utf8ToUtf16(trimmedStr);
    android::Res_value value;
    if (android::ResTable::stringToInt(str16.data(), str16.size(), &value)) {
        if (value.dataType == android::Res_value::TYPE_INT_HEX) {
            ResourceId id(value.data);
            if (id.isValid()) {
                return id;
            }
        }
    }
    return {};
}

Maybe<int> tryParseSdkVersion(const StringPiece& str) {
    StringPiece trimmedStr(util::trimWhitespace(str));

+5 −0
Original line number Diff line number Diff line
@@ -84,6 +84,11 @@ bool isAttributeReference(const StringPiece& str);
 */
bool tryParseBool(const StringPiece& str, bool* outValue);

/**
 * Returns an ID if it the string represented a valid ID.
 */
Maybe<ResourceId> tryParseResourceId(const StringPiece& str);

/**
 * Parses an SDK version, which can be an integer, or a letter from A-Z.
 */
+150 −57
Original line number Diff line number Diff line
@@ -19,87 +19,180 @@
#include "process/IResourceTableConsumer.h"
#include "util/Util.h"

#include <bitset>
#include <cassert>
#include <set>
#include <map>

namespace aapt {

/**
 * Assigns the intended ID to the ResourceTablePackage, ResourceTableType, and ResourceEntry,
 * as long as there is no existing ID or the ID is the same.
 */
static bool assignId(IDiagnostics* diag, const ResourceId id, const ResourceName& name,
                     ResourceTablePackage* pkg, ResourceTableType* type, ResourceEntry* entry) {
    if (pkg->id.value() == id.packageId()) {
        if (!type->id || type->id.value() == id.typeId()) {
            type->id = id.typeId();

            if (!entry->id || entry->id.value() == id.entryId()) {
                entry->id = id.entryId();
                return true;
            }
        }
    }

    const ResourceId existingId(pkg->id.value(),
                                type->id ? type->id.value() : 0,
                                entry->id ? entry->id.value() : 0);
    diag->error(DiagMessage() << "can't assign ID " << id
                << " to resource " << name
                << " with conflicting ID " << existingId);
    return false;
}

bool IdAssigner::consume(IAaptContext* context, ResourceTable* table) {
    std::bitset<256> usedTypeIds;
    std::set<uint16_t> usedEntryIds;
    std::map<ResourceId, ResourceName> assignedIds;

    for (auto& package : table->packages) {
        assert(package->id && "packages must have manually assigned IDs");

        usedTypeIds.reset();

        // Type ID 0 is invalid, reserve it.
        usedTypeIds.set(0);

        // Collect used type IDs.
        for (auto& type : package->types) {
            if (type->id) {
                usedEntryIds.clear();

                if (usedTypeIds[type->id.value()]) {
                    // This ID is already taken!
                    context->getDiagnostics()->error(DiagMessage()
                                                     << "type '" << type->type << "' in "
                                                     << "package '" << package->name << "' has "
                                                     << "duplicate ID "
                                                     << std::hex << (int) type->id.value()
                                                     << std::dec);
            for (auto& entry : type->entries) {
                const ResourceName name(package->name, type->type, entry->name);

                if (mAssignedIdMap) {
                    // Assign the pre-assigned stable ID meant for this resource.
                    const auto iter = mAssignedIdMap->find(name);
                    if (iter != mAssignedIdMap->end()) {
                        const ResourceId assignedId = iter->second;
                        const bool result = assignId(context->getDiagnostics(), assignedId, name,
                                                     package.get(), type.get(), entry.get());
                        if (!result) {
                            return false;
                        }
                    }
                }

                // Mark the type ID as taken.
                usedTypeIds.set(type->id.value());
                if (package->id && type->id && entry->id) {
                    // If the ID is set for this resource, then reserve it.
                    ResourceId resourceId(package->id.value(), type->id.value(), entry->id.value());
                    auto result = assignedIds.insert({ resourceId, name });
                    const ResourceName& existingName = result.first->second;
                    if (!result.second) {
                        context->getDiagnostics()->error(DiagMessage() << "resource " << name
                                                         << " has same ID "
                                                         << resourceId
                                                         << " as " << existingName);
                        return false;
                    }
                }
            }
        }
    }

            // Collect used entry IDs.
            for (auto& entry : type->entries) {
                if (entry->id) {
                    // Mark entry ID as taken.
                    if (!usedEntryIds.insert(entry->id.value()).second) {
                        // This ID existed before!
                        ResourceNameRef nameRef(package->name, type->type, entry->name);
                        context->getDiagnostics()->error(DiagMessage()
                                                         << "resource '" << nameRef << "' "
                                                         << "has duplicate entry ID "
                                                         << std::hex << (int) entry->id.value()
                                                         << std::dec);
    if (mAssignedIdMap) {
        // Reserve all the IDs mentioned in the stable ID map. That way we won't assign
        // IDs that were listed in the map if they don't exist in the table.
        for (const auto& stableIdEntry : *mAssignedIdMap) {
            const ResourceName& preAssignedName = stableIdEntry.first;
            const ResourceId& preAssignedId = stableIdEntry.second;
            auto result = assignedIds.insert({ preAssignedId, preAssignedName });
            const ResourceName& existingName = result.first->second;
            if (!result.second && existingName != preAssignedName) {
                context->getDiagnostics()->error(DiagMessage() << "stable ID " << preAssignedId
                                                 << " for resource " << preAssignedName
                                                 << " is already taken by resource "
                                                 << existingName);
                return false;
            }
        }
    }

            // Assign unused entry IDs.
            const auto endUsedEntryIter = usedEntryIds.end();
            auto nextUsedEntryIter = usedEntryIds.begin();
            uint16_t nextId = 0;
    // Assign any resources without IDs the next available ID. Gaps will be filled if possible,
    // unless those IDs have been reserved.

    const auto assignedIdsIterEnd = assignedIds.end();
    for (auto& package : table->packages) {
        assert(package->id && "packages must have manually assigned IDs");

        // Build a half filled ResourceId object, which will be used to find the closest matching
        // reserved ID in the assignedId map. From that point the next available type ID can be
        // found.
        ResourceId resourceId(package->id.value(), 0, 0);
        uint8_t nextExpectedTypeId = 1;

        // Find the closest matching ResourceId that is <= the one with only the package set.
        auto nextTypeIter = assignedIds.lower_bound(resourceId);
        for (auto& type : package->types) {
            if (!type->id) {
                // We need to assign a type ID. Iterate over the reserved IDs until we find
                // some type ID that is a distance of 2 greater than the last one we've seen.
                // That means there is an available type ID between these reserved IDs.
                while (nextTypeIter != assignedIdsIterEnd) {
                    if (nextTypeIter->first.packageId() != package->id.value()) {
                        break;
                    }

                    const uint8_t typeId = nextTypeIter->first.typeId();
                    if (typeId > nextExpectedTypeId) {
                        // There is a gap in the type IDs, so use the missing one.
                        type->id = nextExpectedTypeId++;
                        break;
                    }

                    // Set our expectation to be the next type ID after the reserved one we
                    // just saw.
                    nextExpectedTypeId = typeId + 1;

                    // Move to the next reserved ID.
                    ++nextTypeIter;
                }

                if (!type->id) {
                    // We must have hit the end of the reserved IDs and not found a gap.
                    // That means the next ID is available.
                    type->id = nextExpectedTypeId++;
                }
            }

            resourceId = ResourceId(package->id.value(), type->id.value(), 0);
            uint16_t nextExpectedEntryId = 0;

            // Find the closest matching ResourceId that is <= the one with only the package
            // and type set.
            auto nextEntryIter = assignedIds.lower_bound(resourceId);
            for (auto& entry : type->entries) {
                if (!entry->id) {
                    // Assign the next available entryID.
                    while (nextUsedEntryIter != endUsedEntryIter &&
                            nextId == *nextUsedEntryIter) {
                        nextId++;
                        ++nextUsedEntryIter;
                    }
                    entry->id = nextId++;
                    // We need to assign an entry ID. Iterate over the reserved IDs until we find
                    // some entry ID that is a distance of 2 greater than the last one we've seen.
                    // That means there is an available entry ID between these reserved IDs.
                    while (nextEntryIter != assignedIdsIterEnd) {
                        if (nextEntryIter->first.packageId() != package->id.value() ||
                                nextEntryIter->first.typeId() != type->id.value()) {
                            break;
                        }

                        const uint16_t entryId = nextEntryIter->first.entryId();
                        if (entryId > nextExpectedEntryId) {
                            // There is a gap in the entry IDs, so use the missing one.
                            entry->id = nextExpectedEntryId++;
                            break;
                        }

                        // Set our expectation to be the next type ID after the reserved one we
                        // just saw.
                        nextExpectedEntryId = entryId + 1;

                        // Move to the next reserved entry ID.
                        ++nextEntryIter;
                    }

        // Assign unused type IDs.
        size_t nextTypeId = 0;
        for (auto& type : package->types) {
            if (!type->id) {
                while (nextTypeId < usedTypeIds.size() && usedTypeIds[nextTypeId]) {
                    nextTypeId++;
                    if (!entry->id) {
                        // We must have hit the end of the reserved IDs and not found a gap.
                        // That means the next ID is available.
                        entry->id = nextExpectedEntryId++;
                    }
                }
                type->id = static_cast<uint8_t>(nextTypeId);
                nextTypeId++;
            }
        }
    }
Loading