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