1#!/usr/bin/env python3 2# Copyright (C) 2021 The Android Open Source Project 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); 5# you may not use this file except in compliance with the License. 6# You may obtain a copy of the License at 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, 12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13# See the License for the specific language governing permissions and 14# limitations under the License. 15 16import os 17import re 18try: 19 import lxml.etree as etree 20except ImportError: 21 print("Please install 'lxml' python package and retry.") 22 sys.exit(1) 23 24class ResourceLocation: 25 def __init__(self, file, line=None): 26 self.file = file 27 self.line = line 28 def __str__(self): 29 if self.line is not None: 30 return self.file + ':' + str(self.line) 31 else: 32 return self.file 33 34class Resource: 35 def __init__(self, name, type, location=None): 36 self.name = name 37 self.type = type 38 self.locations = [] 39 if location is not None: 40 self.locations.append(location) 41 def __eq__(self, other): 42 if isinstance(other, _Grab): 43 return other == self 44 return self.name == other.name and self.type == other.type 45 def __ne__(self, other): 46 if isinstance(other, _Grab): 47 return other != self 48 return self.name != other.name or self.type != other.type 49 def __hash__(self): 50 return hash((self.name, self.type)) 51 def __str__(self): 52 result = '' 53 for location in self.locations: 54 result += str(location) + ': ' 55 result += '<'+self.type+' name="'+self.name+'"' 56 return result + '>' 57 def __repr__(self): 58 return str(self) 59 60def get_all_resources(resDir, excluded_resource_files=[]): 61 excluded_resource_files = [os.path.abspath(file) for file in excluded_resource_files] 62 allResDirs = [f for f in os.listdir(resDir) if os.path.isdir(os.path.join(resDir, f))] 63 valuesDirs = [f for f in allResDirs if f.startswith('values')] 64 fileDirs = [f for f in allResDirs if not f.startswith('values')] 65 resources = set() 66 # Get the filenames of the all the files in all the fileDirs 67 for dir in fileDirs: 68 type = dir.split('-')[0] 69 for file in os.listdir(os.path.join(resDir, dir)): 70 filePath = os.path.abspath(os.path.join(resDir, dir, file)) 71 if file.endswith('.xml') and filePath not in excluded_resource_files: 72 add_resource_to_set(resources, 73 Resource(file[:-4], type, 74 ResourceLocation(os.path.join(resDir, dir, file)))) 75 if dir.startswith("layout"): 76 for resource in get_ids_from_layout_file(os.path.join(resDir, dir, file)): 77 add_resource_to_set(resources, resource) 78 for dir in valuesDirs: 79 for file in os.listdir(os.path.join(resDir, dir)): 80 filePath = os.path.abspath(os.path.join(resDir, dir, file)) 81 if file.endswith('.xml') and filePath not in excluded_resource_files: 82 for resource in get_resources_from_single_file(os.path.join(resDir, dir, file), 83 dir != "values"): 84 add_resource_to_set(resources, resource) 85 return resources 86 87def get_ids_from_layout_file(filename): 88 result = set() 89 with open(filename, 'r') as file: 90 r = re.compile("@\+id/([a-zA-Z0-9_]+)") 91 for i in r.findall(file.read()): 92 add_resource_to_set(result, Resource(i, 'id', ResourceLocation(filename))) 93 return result 94 95def get_resources_from_single_file(filename, ignore_strings=False): 96 doc = etree.parse(filename) 97 root = doc.getroot() 98 result = set() 99 for resource in root: 100 if resource.tag is etree.Comment: 101 continue 102 if resource.tag == 'declare-styleable': 103 for attr in resource: 104 resName = attr.get('name') 105 # Skip resources beginning with 'android:' as they are part of the framework 106 # resources. This script finds only the app's resources. 107 if resName is None or resName.startswith('android:'): 108 continue 109 resType = "attr" 110 add_resource_to_set(result, Resource(resName, resType, ResourceLocation(filename, attr.sourceline))) 111 continue 112 resName = resource.get('name') 113 resType = resource.tag 114 if resType == "string-array" or resType == "integer-array": 115 resType = "array" 116 if resource.tag == 'item' or resource.tag == 'public': 117 resType = resource.get('type') 118 if (resType == 'string' or resType == 'plurals') and ignore_strings: 119 continue 120 if resType == 'overlayable': 121 for policy in resource: 122 for overlayable in policy: 123 resName = overlayable.get('name') 124 resType = overlayable.get('type') 125 add_resource_to_set(result, Resource(resName, resType, 126 ResourceLocation(filename, resource.sourceline))) 127 else: 128 add_resource_to_set(result, Resource(resName, resType, 129 ResourceLocation(filename, resource.sourceline))) 130 return result 131 132# Used to get objects out of sets 133class _Grab: 134 def __init__(self, value): 135 self.search_value = value 136 def __hash__(self): 137 return hash(self.search_value) 138 def __eq__(self, other): 139 if self.search_value == other: 140 self.actual_value = other 141 return True 142 return False 143 144def add_resource_to_set(resourceset, resource): 145 if (resource.name == None): 146 return 147 grabber = _Grab(resource) 148 if grabber in resourceset: 149 grabber.actual_value.locations.extend(resource.locations) 150 else: 151 resourceset.update([resource]) 152 153def merge_resources(set1, set2): 154 for resource in set2: 155 add_resource_to_set(set1, resource) 156 157def get_androidx_resources(): 158 # source: https://android.googlesource.com/platform/frameworks/opt/sherpa/+/studio-3.0/constraintlayout/src/main/res/values/attrs.xml 159 resources = set() 160 add_resource_to_set(resources, Resource('layout_optimizationLevel', 'attr')) 161 add_resource_to_set(resources, Resource('constraintSet', 'attr')) 162 add_resource_to_set(resources, Resource('barrierDirection', 'attr')) 163 add_resource_to_set(resources, Resource('constraint_referenced_ids', 'attr')) 164 add_resource_to_set(resources, Resource('chainUseRtl', 'attr')) 165 add_resource_to_set(resources, Resource('title', 'attr')) 166 add_resource_to_set(resources, Resource('layout_constraintGuide_begin', 'attr')) 167 add_resource_to_set(resources, Resource('layout_constraintGuide_end', 'attr')) 168 add_resource_to_set(resources, Resource('layout_constraintGuide_percent', 'attr')) 169 add_resource_to_set(resources, Resource('layout_constraintLeft_toLeftOf', 'attr')) 170 add_resource_to_set(resources, Resource('layout_constraintLeft_toRightOf', 'attr')) 171 add_resource_to_set(resources, Resource('layout_constraintRight_toLeftOf', 'attr')) 172 add_resource_to_set(resources, Resource('layout_constraintRight_toRightOf', 'attr')) 173 add_resource_to_set(resources, Resource('layout_constraintTop_toTopOf', 'attr')) 174 add_resource_to_set(resources, Resource('layout_constraintTop_toBottomOf', 'attr')) 175 add_resource_to_set(resources, Resource('layout_constraintBottom_toTopOf', 'attr')) 176 add_resource_to_set(resources, Resource('layout_constraintBottom_toBottomOf', 'attr')) 177 add_resource_to_set(resources, Resource('layout_constraintBaseline_toBaselineOf', 'attr')) 178 add_resource_to_set(resources, Resource('layout_constraintStart_toEndOf', 'attr')) 179 add_resource_to_set(resources, Resource('layout_constraintStart_toStartOf', 'attr')) 180 add_resource_to_set(resources, Resource('layout_constraintEnd_toStartOf', 'attr')) 181 add_resource_to_set(resources, Resource('layout_constraintEnd_toEndOf', 'attr')) 182 add_resource_to_set(resources, Resource('layout_goneMarginLeft', 'attr')) 183 add_resource_to_set(resources, Resource('layout_goneMarginTop', 'attr')) 184 add_resource_to_set(resources, Resource('layout_goneMarginRight', 'attr')) 185 add_resource_to_set(resources, Resource('layout_goneMarginBottom', 'attr')) 186 add_resource_to_set(resources, Resource('layout_goneMarginStart', 'attr')) 187 add_resource_to_set(resources, Resource('layout_goneMarginEnd', 'attr')) 188 add_resource_to_set(resources, Resource('layout_constraintHorizontal_bias', 'attr')) 189 add_resource_to_set(resources, Resource('layout_constraintVertical_bias', 'attr')) 190 add_resource_to_set(resources, Resource('layout_constraintWidth_default', 'attr')) 191 add_resource_to_set(resources, Resource('layout_constraintHeight_default', 'attr')) 192 add_resource_to_set(resources, Resource('layout_constraintWidth_min', 'attr')) 193 add_resource_to_set(resources, Resource('layout_constraintWidth_max', 'attr')) 194 add_resource_to_set(resources, Resource('layout_constraintWidth_percent', 'attr')) 195 add_resource_to_set(resources, Resource('layout_constraintHeight_min', 'attr')) 196 add_resource_to_set(resources, Resource('layout_constraintHeight_max', 'attr')) 197 add_resource_to_set(resources, Resource('layout_constraintHeight_percent', 'attr')) 198 add_resource_to_set(resources, Resource('layout_constraintLeft_creator', 'attr')) 199 add_resource_to_set(resources, Resource('layout_constraintTop_creator', 'attr')) 200 add_resource_to_set(resources, Resource('layout_constraintRight_creator', 'attr')) 201 add_resource_to_set(resources, Resource('layout_constraintBottom_creator', 'attr')) 202 add_resource_to_set(resources, Resource('layout_constraintBaseline_creator', 'attr')) 203 add_resource_to_set(resources, Resource('layout_constraintDimensionRatio', 'attr')) 204 add_resource_to_set(resources, Resource('layout_constraintHorizontal_weight', 'attr')) 205 add_resource_to_set(resources, Resource('layout_constraintVertical_weight', 'attr')) 206 add_resource_to_set(resources, Resource('layout_constraintHorizontal_chainStyle', 'attr')) 207 add_resource_to_set(resources, Resource('layout_constraintVertical_chainStyle', 'attr')) 208 add_resource_to_set(resources, Resource('layout_editor_absoluteX', 'attr')) 209 add_resource_to_set(resources, Resource('layout_editor_absoluteY', 'attr')) 210 return resources 211