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

Commit 60e7729b authored by Linus Tufvesson's avatar Linus Tufvesson
Browse files

Make finalize_res flag aware

This continues down the rabbit hole of using regex to parse the staging
file. Modified finalize_item to be called directly with matches and
return a tuple of strings that go in various places. It is a bit cryptic
but so was the inline regex modification before too. The proper solution
is to parse the xml and handle it.

As it is now it is very brittle
1. "removed_" being a magic constant that the rest of the resource
   frameworks knows how to handle
2. It still doesn't handle proper xml and has strong assumptions about
   how the items are listed
3. There is lots of room for improving the python code :)

It is however the path of least resistant right now to get finalization
moving forward.

Flag: EXEMPTE modifying a script used for finalization
Bug: 390120561
Test: frameworks/base/tools/aapt2/tools/finalize_res.py frameworks/base/core/res/res/values/public-staging.xml frameworks/base/core/res/res/values/public-final.xml
Change-Id: I8e3bda38a5229754fa679a79061527bc246d9e08
parent 5ae6e84c
Loading
Loading
Loading
Loading
+87 −11
Original line number Diff line number Diff line
@@ -38,13 +38,20 @@ Usage: $ANDROID_BUILD_TOP/frameworks/base/tools/aapt2/tools/finalize_res.py \

import re
import sys
import subprocess
from collections import defaultdict

resTypes = ["attr", "id", "style", "string", "dimen", "color", "array", "drawable", "layout",
            "anim", "animator", "interpolator", "mipmap", "integer", "transition", "raw", "bool",
            "fraction"]

_aconfig_map = {}
_not_finalized = defaultdict(list)
_type_ids = {}
_type = ""
_finalized_flags = set()
_non_finalized_flags = set()


_lowest_staging_first_id = 0x01FFFFFF

@@ -53,13 +60,48 @@ _lowest_staging_first_id = 0x01FFFFFF
    prefixed with removed_. The IDs are assigned without holes starting from the last ID for that
    type currently finalized in public-final.xml.
"""
def finalize_item(raw):
    name = raw.group(1)
    if re.match(r'_*removed.+', name):
        return ""
def finalize_item(comment_and_item):
    print("Processing:\n" + comment_and_item)
    name = re.search('<public name="(.+?)"',comment_and_item, flags=re.DOTALL).group(1)
    if re.match('removed_.+', name):
        # Remove it from <staging-public-group> in public-staging.xml
        # Include it as is in <staging-public-group-final> in public-final.xml
        # Don't assign an id in public-final.xml
        return ("", comment_and_item, "")

    comment = re.search(' *<!--.+?-->\n', comment_and_item, flags=re.DOTALL).group(0)

    flag = re.search('<!-- @FlaggedApi\((.+?)\)', comment, flags=re.DOTALL).group(1)
    if flag.startswith("\""):
        # Flag is a string value, just remove "
        flag = flag.replace("\"", "")
    else:
        # Flag is a java constant, convert to string value
        flag = flag.replace(".Flags.FLAG_", ".").lower()

    if flag not in _aconfig_map:
        raise Exception("Unknown flag: " + flag)

    # READ_ONLY-ENABLED is a magic string from printflags output below
    if _aconfig_map[flag] != "READ_ONLY-ENABLED":
        _non_finalized_flags.add(flag)
        # Keep it as is in <staging-public-group> in public-staging.xml
        # Include as magic constant "removed_" in <staging-public-group-final> in public-final.xml
        # Don't assign an id in public-final.xml
        return (comment_and_item, "    <public name=\"removed_\" />\n", "")

    _finalized_flags.add(flag)

    id = _type_ids[_type]
    _type_ids[_type] += 1
    return '  <public type="%s" name="%s" id="%s" />\n' % (_type, name, '0x{0:0{1}x}'.format(id, 8))

    # Removes one indentation step to align the comment with the item outside the
    comment = re.sub("^  ", "", comment, flags=re.MULTILINE)

    # Remove from <staging-public-group> in public-staging.xml
    # Include as is in <staging-public-group-final> in public-final.xml
    # Assign an id in public-final.xml
    return ("", comment_and_item, comment + '  <public type="%s" name="%s" id="%s" />\n' % (_type, name, '0x{0:0{1}x}'.format(id, 8)))


"""
@@ -72,10 +114,26 @@ def finalize_group(raw):
    _type = raw.group(1)
    id = int(raw.group(2), 16)
    _type_ids[_type] = _type_ids.get(_type, id)
    (res, count) = re.subn(' {0,4}<public name="(.+?)" */>\n', finalize_item, raw.group(3))
    if count > 0:
        res = raw.group(0).replace("staging-public-group",
                                   "staging-public-group-final") + '\n' + res


    all = re.findall(' *<!--.*?<public name=".+?" */>\n', raw.group(3), flags=re.DOTALL)
    res = ""
    group_matches = ""
    for match in all:
        (staging_group, final_group, final_id_assignment) = finalize_item(match)

        if staging_group:
             _not_finalized[_type].append(staging_group)

        if final_group:
            group_matches += final_group

        if final_id_assignment:
            res += final_id_assignment

    # Only add it to final.xml if new ids were actually assigned
    if res:
        res = '<staging-public-group-final type="%s" first-id="%s">\n%s  </staging-public-group-final>\n\n%s' % (_type, raw.group(2), group_matches, res)
        _lowest_staging_first_id = min(id, _lowest_staging_first_id)
    return res

@@ -88,6 +146,13 @@ def collect_ids(raw):
        id = int(m.group(2), 16)
        _type_ids[type] = max(id + 1, _type_ids.get(type, 0))

# This is a hack and assumes this script is run from the top directory
output=subprocess.run("printflags --format='{fully_qualified_name} {permission}-{state}'", shell=True, capture_output=True, encoding="utf-8", check=True)
for line in output.stdout.splitlines():
    parts = line.split()
    key = parts[0]
    value = parts[1]
    _aconfig_map[key]=value

with open(sys.argv[1], "r+") as stagingFile:
    with open(sys.argv[2], "r+") as finalFile:
@@ -132,10 +197,21 @@ with open(sys.argv[1], "r+") as stagingFile:
        nextId = _lowest_staging_first_id - 0x00010000
        for resType in resTypes:
            stagingFile.write('  <staging-public-group type="%s" first-id="%s">\n'
                              '  </staging-public-group>\n\n' %
                              (resType, '0x{0:0{1}x}'.format(nextId, 8)))
                               % (resType, '0x{0:0{1}x}'.format(nextId, 8)))
            for item in _not_finalized[resType]:
                stagingFile.write(item)
            stagingFile.write('  </staging-public-group>\n\n')
            nextId -= 0x00010000

        # Close the resources tag and truncate, since the file will be shorter than the previous
        stagingFile.write("</resources>\n")
        stagingFile.truncate()


print("\nFlags that had resources that were NOT finalized:")
for flag in sorted(_non_finalized_flags):
    print(flag)

print("\nFlags that had resources that were finalized:")
for flag in sorted(_finalized_flags):
    print(flag)