1#!/usr/bin/env python3 2# 3# Copyright 2019, The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16 17import argparse 18import os 19import sys 20import re 21from resource_utils import get_all_resources, get_resources_from_single_file, add_resource_to_set, Resource, merge_resources 22from git_utils import has_chassis_changes 23from datetime import datetime 24import urllib.request 25 26if sys.version_info[0] != 3: 27 print("Must use python 3") 28 sys.exit(1) 29 30# path to 'packages/apps/Car/libs/car-ui-lib/' 31ROOT_FOLDER = os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), '../../')) 32OUTPUT_FILE_PATH = os.path.join(ROOT_FOLDER, 'tests/apitest/') 33 34 35COPYRIGHT_STR = """Copyright (C) %s The Android Open Source Project 36 37Licensed under the Apache License, Version 2.0 (the "License"); 38you may not use this file except in compliance with the License. 39You may obtain a copy of the License at 40 41 http://www.apache.org/licenses/LICENSE-2.0 42 43Unless required by applicable law or agreed to in writing, software 44distributed under the License is distributed on an "AS IS" BASIS, 45WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 46See the License for the specific language governing permissions and 47limitations under the License.""" % (datetime.today().strftime("%Y")) 48 49 50AUTOGENERATION_NOTICE_STR = """ THIS FILE WAS AUTO GENERATED, DO NOT EDIT MANUALLY. """ 51 52""" 53Script used to update the 'current.xml' file. This is being used as part of pre-submits to 54verify whether resources previously exposed to OEMs are being changed by a CL, potentially 55breaking existing customizations. 56 57Example usage: python auto-generate-resources.py current.xml 58""" 59def main(): 60 parser = argparse.ArgumentParser(description='Check if any existing resources are modified.') 61 parser.add_argument('--sha', help='Git hash of current changes. This script will not run if this is provided and there are no chassis changes.') 62 parser.add_argument('-f', '--file', default='current.xml', help='Name of output file.') 63 parser.add_argument('-c', '--compare', action='store_true', 64 help='Pass this flag if resources need to be compared.') 65 args = parser.parse_args() 66 67 if not has_chassis_changes(args.sha): 68 # Don't run because there were no chassis changes 69 return 70 71 resources = get_all_resources(os.path.join(ROOT_FOLDER, 'car-ui-lib/src/main/res')) 72 check_resource_names(resources, get_resources_from_single_file(os.path.join(OUTPUT_FILE_PATH, 'resource_name_allowed.xml'))) 73 removed_resources = get_resources_from_single_file(os.path.join(ROOT_FOLDER, 'car-ui-lib/src/main/res-overlayable/values/removed_resources.xml')) 74 OVERLAYABLE_OUTPUT_FILE_PATH = os.path.join(ROOT_FOLDER, 'car-ui-lib/src/main/res-overlayable/values/overlayable.xml') 75 output_file = args.file or 'current.xml' 76 77 if args.compare: 78 check_removed_resources(resources, removed_resources) 79 merge_resources(resources, removed_resources) 80 81 old_mapping = get_resources_from_single_file(os.path.join(OUTPUT_FILE_PATH, 'current.xml')) 82 compare_resources(old_mapping, resources, os.path.join(OUTPUT_FILE_PATH, 'current.xml')) 83 84 old_mapping = get_resources_from_single_file(OVERLAYABLE_OUTPUT_FILE_PATH) 85 add_constraintlayout_resources(resources) 86 compare_resources(old_mapping, resources, OVERLAYABLE_OUTPUT_FILE_PATH) 87 else: 88 merge_resources(resources, removed_resources) 89 generate_current_file(resources, output_file) 90 generate_overlayable_file(resources, OVERLAYABLE_OUTPUT_FILE_PATH) 91 92def generate_current_file(resources, output_file='current.xml'): 93 resources = sorted(resources, key=lambda x: x.type + x.name) 94 95 # defer importing lxml to here so that people who aren't editing chassis don't have to have 96 # lxml installed 97 import lxml.etree as etree 98 99 root = etree.Element('resources') 100 101 root.addprevious(etree.Comment(AUTOGENERATION_NOTICE_STR)) 102 for resource in resources: 103 item = etree.SubElement(root, 'public') 104 item.set('type', resource.type) 105 item.set('name', resource.name) 106 107 data = etree.ElementTree(root) 108 109 with open(os.path.join(OUTPUT_FILE_PATH, output_file), 'wb') as f: 110 data.write(f, pretty_print=True, xml_declaration=True, encoding='utf-8') 111 112def generate_overlayable_file(resources, output_file='overlayable.xml'): 113 add_constraintlayout_resources(resources) 114 resources = sorted(resources, key=lambda x: x.type + x.name) 115 116 # defer importing lxml to here so that people who aren't editing chassis don't have to have 117 # lxml installed 118 import lxml.etree as etree 119 120 root = etree.Element('resources') 121 122 root.addprevious(etree.Comment(COPYRIGHT_STR)) 123 root.addprevious(etree.Comment(AUTOGENERATION_NOTICE_STR)) 124 125 overlayable = etree.SubElement(root, 'overlayable') 126 overlayable.set('name', 'car-ui-lib') 127 128 policy = etree.SubElement(overlayable, 'policy') 129 # everything except public. source: frameworks/base/cmds/idmap2/libidmap2_policies/include/idmap2/Policies.h 130 policy.set('type', 'odm|oem|product|signature|system|vendor') 131 132 for resource in resources: 133 item = etree.SubElement(policy, 'item') 134 item.set('type', resource.type) 135 item.set('name', resource.name) 136 137 data = etree.ElementTree(root) 138 139 with open(output_file, 'wb') as f: 140 data.write(f, pretty_print=True, xml_declaration=True, encoding='utf-8') 141 142def add_constraintlayout_resources(resources): 143 # We need these to be able to use base layouts in RROs 144 # This should become unnecessary some time in future? 145 # Please keep this in-sync with res/raw/car_ui_keep.xml 146 url = "https://raw.githubusercontent.com/androidx/constraintlayout/main/constraintlayout/constraintlayout/src/main/res/values/attrs.xml" 147 import xml.etree.ElementTree as ET 148 tree = ET.parse(urllib.request.urlopen(url)) 149 root = tree.getroot() 150 151 # The source here always points to the latest version of attrs.xml from androidx repo 152 # and since platform is not using the latest one, some of the tags should be excluded. 153 unsupported_attrs = [ 154 "circularflow_radiusInDP", 155 "constraint_referenced_tags", 156 "layout_goneMarginBaseline", 157 "circularflow_angles", 158 "circularflow_defaultRadius", 159 "circularflow_defaultAngle", 160 "layout_marginBaseline", 161 "circularflow_viewCenter", 162 "layout_wrapBehaviorInParent", 163 "layout_constraintWidth", 164 "layout_constraintBaseline_toBottomOf", 165 "layout_constraintHeight", 166 "layout_constraintBaseline_toTopOf", 167 ] 168 169 attrs = root.findall("./declare-styleable[@name='ConstraintLayout_Layout']/attr") 170 for attr in attrs: 171 if "android:" not in attr.get('name') and attr.get('name') not in unsupported_attrs: 172 add_resource_to_set(resources, Resource(attr.get('name'), 'attr')) 173 174 175def check_resource_names(resources, allowed): 176 newlist = resources.difference(allowed) 177 failed=False 178 for resource in newlist: 179 resourceType= resource.type 180 resourceName = resource.name 181 if resourceType == 'attr' and not re.match("^CarUi", resourceName, re.IGNORECASE): 182 print(f"Please consider changing {resourceType}/{resourceName} to something like CarUi{resourceName}") 183 failed=True 184 elif resourceType == 'style' and not re.search("CarUi", resourceName, re.IGNORECASE): 185 print(f"Please consider changing {resourceType}/{resourceName} to something like CarUi{resourceName}") 186 failed=True 187 elif resourceType != 'attr' and resourceType != 'style' and not re.match("^car_ui_", resourceName, re.IGNORECASE): 188 print(f"Please consider changing {resourceType}/{resourceName} to something like car_ui_{resourceName}") 189 failed=True 190 if failed: 191 sys.exit(1) 192 193def compare_resources(old_mapping, new_mapping, res_public_file): 194 removed = old_mapping.difference(new_mapping) 195 added = new_mapping.difference(old_mapping) 196 if len(removed) > 0: 197 print('Resources removed:\n' + '\n'.join(map(lambda x: str(x), removed))) 198 if len(added) > 0: 199 print('Resources added:\n' + '\n'.join(map(lambda x: str(x), added))) 200 201 if len(added) + len(removed) > 0: 202 print("Some resource have been modified. If this is intentional please " + 203 "run 'python3 $ANDROID_BUILD_TOP/packages/apps/Car/libs/car-ui-lib/tests/apitest/" + 204 "auto-generate-resources.py' again and submit the new %s" % res_public_file) 205 sys.exit(1) 206 207def check_removed_resources(mapping, removed_resources): 208 intersection = removed_resources.intersection(mapping) 209 if len(intersection) > 0: 210 print('Usage of removed resources detected\n'+ '\n'.join(map(lambda x: str(x), intersection))) 211 212if __name__ == '__main__': 213 main() 214