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 os 18import re 19 20class ResourceLocation: 21 def __init__(self, file, line=None): 22 self.file = file 23 self.line = line 24 25 def __str__(self): 26 if self.line is not None: 27 return self.file + ':' + str(self.line) 28 else: 29 return self.file 30 31class Resource: 32 def __init__(self, name, type, location=None): 33 self.name = name 34 self.type = type 35 self.locations = [] 36 if location is not None: 37 self.locations.append(location) 38 39 def __eq__(self, other): 40 if isinstance(other, _Grab): 41 return other == self 42 return self.name == other.name and self.type == other.type 43 44 def __ne__(self, other): 45 if isinstance(other, _Grab): 46 return other != self 47 return self.name != other.name or self.type != other.type 48 49 def __hash__(self): 50 return hash((self.name, self.type)) 51 52 def __str__(self): 53 result = '' 54 for location in self.locations: 55 result += str(location) + ': ' 56 result += '<'+self.type+' name="'+self.name+'"' 57 58 return result + '>' 59 60 def __repr__(self): 61 return str(self) 62 63def get_all_resources(resDir): 64 allResDirs = [f for f in os.listdir(resDir) if os.path.isdir(os.path.join(resDir, f))] 65 valuesDirs = [f for f in allResDirs if f.startswith('values')] 66 fileDirs = [f for f in allResDirs if not f.startswith('values')] 67 68 resources = set() 69 70 # Get the filenames of the all the files in all the fileDirs 71 for dir in fileDirs: 72 type = dir.split('-')[0] 73 for file in os.listdir(os.path.join(resDir, dir)): 74 if file.endswith('.xml'): 75 add_resource_to_set(resources, 76 Resource(file[:-4], type, 77 ResourceLocation(os.path.join(resDir, dir, file)))) 78 if dir.startswith("layout"): 79 for resource in get_ids_from_layout_file(os.path.join(resDir, dir, file)): 80 add_resource_to_set(resources, resource) 81 82 for dir in valuesDirs: 83 for file in os.listdir(os.path.join(resDir, dir)): 84 if file.endswith('.xml'): 85 for resource in get_resources_from_single_file(os.path.join(resDir, dir, file)): 86 add_resource_to_set(resources, resource) 87 88 return resources 89 90def get_ids_from_layout_file(filename): 91 result = set() 92 with open(filename, 'r') as file: 93 r = re.compile("@\+id/([a-zA-Z0-9_]+)") 94 for i in r.findall(file.read()): 95 add_resource_to_set(result, Resource(i, 'id', ResourceLocation(filename))) 96 return result 97 98def get_resources_from_single_file(filename): 99 # defer importing lxml to here so that people who aren't editing chassis don't have to have 100 # lxml installed 101 import lxml.etree as etree 102 doc = etree.parse(filename) 103 root = doc.getroot() 104 result = set() 105 for resource in root: 106 if resource.tag == 'declare-styleable' or resource.tag is etree.Comment: 107 attrs = resource.findall("attr") 108 for attr in attrs: 109 resName = attr.get('name') 110 resType = "attr" 111 if "android:" not in attr.get('name'): 112 add_resource_to_set(result, Resource(resName, resType, 113 ResourceLocation(filename, resource.sourceline))) 114 continue 115 116 resName = resource.get('name') 117 resType = resource.tag 118 if resType == "string-array": 119 resType = "array" 120 if resource.tag == 'item' or resource.tag == 'public': 121 resType = resource.get('type') 122 123 if resType == 'overlayable': 124 for policy in resource: 125 for overlayable in policy: 126 resName = overlayable.get('name') 127 resType = overlayable.get('type') 128 add_resource_to_set(result, Resource(resName, resType, 129 ResourceLocation(filename, resource.sourceline))) 130 else: 131 add_resource_to_set(result, Resource(resName, resType, 132 ResourceLocation(filename, resource.sourceline))) 133 134 return result 135 136# Used to get objects out of sets 137class _Grab: 138 def __init__(self, value): 139 self.search_value = value 140 def __hash__(self): 141 return hash(self.search_value) 142 def __eq__(self, other): 143 if self.search_value == other: 144 self.actual_value = other 145 return True 146 return False 147 148def add_resource_to_set(resourceset, resource): 149 grabber = _Grab(resource) 150 if grabber in resourceset: 151 grabber.actual_value.locations.extend(resource.locations) 152 else: 153 resourceset.update([resource]) 154 155def merge_resources(set1, set2): 156 for resource in set2: 157 add_resource_to_set(set1, resource) 158