xref: /aosp_15_r20/frameworks/base/tools/aapt2/tools/finalize_res.py (revision d57664e9bc4670b3ecf6748a746a57c557b6bc9e)
1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3
4#  Copyright (C) 2022 The Android Open Source Project
5#
6#  Licensed under the Apache License, Version 2.0 (the "License");
7#  you may not use this file except in compliance with the License.
8#  You may obtain a copy of the License at
9#
10#       http://www.apache.org/licenses/LICENSE-2.0
11#
12#  Unless required by applicable law or agreed to in writing, software
13#  distributed under the License is distributed on an "AS IS" BASIS,
14#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15#  See the License for the specific language governing permissions and
16#  limitations under the License.
17#
18# Licensed under the Apache License, Version 2.0 (the 'License');
19# you may not use this file except in compliance with the License.
20# You may obtain a copy of the License at
21#
22#      http://www.apache.org/licenses/LICENSE-2.0
23#
24# Unless required by applicable law or agreed to in writing, software
25# distributed under the License is distributed on an 'AS IS' BASIS,
26# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
27# See the License for the specific language governing permissions and
28# limitations under the License.
29
30"""
31Finalize resource values in <staging-public-group> tags
32and convert those to <staging-public-group-final>
33
34Usage: $ANDROID_BUILD_TOP/frameworks/base/tools/aapt2/tools/finalize_res.py \
35           $ANDROID_BUILD_TOP/frameworks/base/core/res/res/values/public-staging.xml \
36           $ANDROID_BUILD_TOP/frameworks/base/core/res/res/values/public-final.xml
37"""
38
39import re
40import sys
41
42resTypes = ["attr", "id", "style", "string", "dimen", "color", "array", "drawable", "layout",
43            "anim", "animator", "interpolator", "mipmap", "integer", "transition", "raw", "bool",
44            "fraction"]
45
46_type_ids = {}
47_type = ""
48
49_lowest_staging_first_id = 0x01FFFFFF
50
51"""
52    Created finalized <public> declarations for staging resources, ignoring them if they've been
53    prefixed with removed_. The IDs are assigned without holes starting from the last ID for that
54    type currently finalized in public-final.xml.
55"""
56def finalize_item(raw):
57    name = raw.group(1)
58    if re.match(r'_*removed.+', name):
59        return ""
60    id = _type_ids[_type]
61    _type_ids[_type] += 1
62    return '  <public type="%s" name="%s" id="%s" />\n' % (_type, name, '0x{0:0{1}x}'.format(id, 8))
63
64
65"""
66    Finalizes staging-public-groups if they have any entries in them. Also keeps track of the
67    lowest first-id of the non-empty groups so that the next release's staging-public-groups can
68    be assigned the next down shifted first-id.
69"""
70def finalize_group(raw):
71    global _type, _lowest_staging_first_id
72    _type = raw.group(1)
73    id = int(raw.group(2), 16)
74    _type_ids[_type] = _type_ids.get(_type, id)
75    (res, count) = re.subn(' {0,4}<public name="(.+?)" */>\n', finalize_item, raw.group(3))
76    if count > 0:
77        res = raw.group(0).replace("staging-public-group",
78                                   "staging-public-group-final") + '\n' + res
79        _lowest_staging_first_id = min(id, _lowest_staging_first_id)
80    return res
81
82"""
83    Collects the max ID for each resType so that the new IDs can be assigned afterwards
84"""
85def collect_ids(raw):
86    for m in re.finditer(r'<public type="(.+?)" name=".+?" id="(.+?)" />', raw):
87        type = m.group(1)
88        id = int(m.group(2), 16)
89        _type_ids[type] = max(id + 1, _type_ids.get(type, 0))
90
91
92with open(sys.argv[1], "r+") as stagingFile:
93    with open(sys.argv[2], "r+") as finalFile:
94        existing = finalFile.read()
95        # Cut out the closing resources tag so that it can be concatenated easily later
96        existing = "\n".join(existing.rsplit("</resources>", 1))
97
98        # Collect the IDs from the existing already finalized resources
99        collect_ids(existing)
100
101        staging = stagingFile.read()
102        stagingSplit = staging.rsplit("<resources>")
103        staging = stagingSplit[1]
104        staging = re.sub(
105            r'<staging-public-group type="(.+?)" first-id="(.+?)">(.+?)</staging-public-group>',
106            finalize_group, staging, flags=re.DOTALL)
107        staging = re.sub(r' *\n', '\n', staging)
108        staging = re.sub(r'\n{3,}', '\n\n', staging)
109
110        # First write the existing finalized declarations and then append the new stuff
111        finalFile.seek(0)
112        finalFile.write(existing.strip("\n"))
113        finalFile.write("\n\n")
114        finalFile.write(staging.strip("\n"))
115        finalFile.write("\n")
116        finalFile.truncate()
117
118        stagingFile.seek(0)
119        # Include the documentation from public-staging.xml that was previously split out
120        stagingFile.write(stagingSplit[0])
121        # Write the next platform header
122        stagingFile.write("<resources>\n\n")
123        stagingFile.write("  <!-- ===============================================================\n")
124        stagingFile.write("    Resources added in version NEXT of the platform\n\n")
125        stagingFile.write("    NOTE: After this version of the platform is forked, changes cannot be made to the root\n")
126        stagingFile.write("    branch's groups for that release. Only merge changes to the forked platform branch.\n")
127        stagingFile.write("    =============================================================== -->\n")
128        stagingFile.write("  <eat-comment/>\n\n")
129
130        # Seed the next release's staging-public-groups as empty declarations,
131        # so its easy for another developer to expose a new public resource
132        nextId = _lowest_staging_first_id - 0x00010000
133        for resType in resTypes:
134            stagingFile.write('  <staging-public-group type="%s" first-id="%s">\n'
135                              '  </staging-public-group>\n\n' %
136                              (resType, '0x{0:0{1}x}'.format(nextId, 8)))
137            nextId -= 0x00010000
138
139        # Close the resources tag and truncate, since the file will be shorter than the previous
140        stagingFile.write("</resources>\n")
141        stagingFile.truncate()
142