diff options
author | Heemin Seog <hseog@google.com> | 2021-04-29 14:00:44 -0700 |
---|---|---|
committer | Heemin Seog <hseog@google.com> | 2021-04-30 15:53:16 -0700 |
commit | 3ea627f47fb4ec4cf3e60397ce3585bf72deacd8 (patch) | |
tree | 764ff7ec7bfbbc53897545aae9329660bfadb4a9 /tools | |
parent | c1eaa136602fd93106c38912a79b5279a831741e (diff) | |
download | tests-3ea627f47fb4ec4cf3e60397ce3585bf72deacd8.tar.gz |
Add generic overlayable.xml scripts
Bug: 186785919
Test: manual with MediaCenter
Change-Id: Ic62b079c471ce905c9aa457c4a41aa133cb569f7
Diffstat (limited to 'tools')
-rw-r--r-- | tools/rro/README | 18 | ||||
-rwxr-xr-x | tools/rro/generate-overlayable.py | 79 | ||||
-rw-r--r-- | tools/rro/resource_utils.py | 138 | ||||
-rwxr-xr-x | tools/rro/verify-overlayable.py | 53 |
4 files changed, 288 insertions, 0 deletions
diff --git a/tools/rro/README b/tools/rro/README new file mode 100644 index 0000000..dcc91d7 --- /dev/null +++ b/tools/rro/README @@ -0,0 +1,18 @@ +These scripts are used to generate and verify overlayable.xml files. + +Sample invocations (Media Center as an example). + +To generate: +PROJECT_TOP=$ANDROID_BUILD_TOP/packages/apps/Car/Media +python $ANDROID_BUILD_TOP/packages/apps/Car/tests/tools/rro/generate-overlayable.py \ + -n CarMediaApp \ + -r $PROJECT_TOP/res \ + -e $PROJECT_TOP/res/values/overlayable.xml $PROJECT_TOP/res/xml/automotive_app_desc.xml \ + -o $PROJECT_TOP/res/values/overlayable.xml + +To verify: +PROJECT_TOP=$ANDROID_BUILD_TOP/packages/apps/Car/Media +python $ANDROID_BUILD_TOP/packages/apps/Car/tests/tools/rro/verify-overlayable.py \ + -r $PROJECT_TOP/res \ + -e $PROJECT_TOP/res/values/overlayable.xml $PROJECT_TOP/res/xml/automotive_app_desc.xml \ + -o $PROJECT_TOP/res/values/overlayable.xml
\ No newline at end of file diff --git a/tools/rro/generate-overlayable.py b/tools/rro/generate-overlayable.py new file mode 100755 index 0000000..1dcb107 --- /dev/null +++ b/tools/rro/generate-overlayable.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python3 +# Copyright (C) 2021 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import argparse +import sys +from resource_utils import get_all_resources, Resource +from datetime import datetime +import lxml.etree as etree +if sys.version_info[0] != 3: + print("Must use python 3") + sys.exit(1) + +COPYRIGHT_STR = """ Copyright (C) %s The Android Open Source Project +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License.""" % (datetime.today().strftime("%Y")) + +AUTOGENERATION_NOTICE_STR = """ +THIS FILE WAS AUTO GENERATED, DO NOT EDIT MANUALLY. +REGENERATE USING packages/apps/Car/tests/tools/rro/generate-overlayable.py +""" + +""" +Script used to update the 'overlayable.xml' file. +""" +def main(): + parser = argparse.ArgumentParser(description='Generate overlayable.xml.') + optional_args = parser.add_argument_group('optional arguments') + optional_args.add_argument('-t', '--policyType', default='system|product|signature', help='Policy type for the overlay - delimited by |') + optional_args.add_argument('-e', '--excludeFiles', nargs='*', help='File paths (absolute or relative to cwd) that should be excluded when generating overlayable.xml') + optional_args.add_argument('-o', '--outputFile', default='', help='Output file path (absolute or relative to cwd). If empty, output to stdout') + required_args = parser.add_argument_group('required arguments') + required_args.add_argument('-n', '--targetName', help='Overlayable name for the overlay.', required=True) + required_args.add_argument('-r', '--resourcePath', help='Path to resource directory (absolute or relative to cwd)', required=True) + args = parser.parse_args() + + resources = get_all_resources(args.resourcePath, args.excludeFiles) + generate_overlayable_file(resources, args.targetName, args.policyType, args.outputFile) + +def generate_overlayable_file(resources, target_name, policy_type, output_file): + resources = sorted(resources, key=lambda x: x.type + x.name) + root = etree.Element('resources') + root.addprevious(etree.Comment(COPYRIGHT_STR)) + root.addprevious(etree.Comment(AUTOGENERATION_NOTICE_STR)) + overlayable = etree.SubElement(root, 'overlayable') + overlayable.set('name', target_name) + policy = etree.SubElement(overlayable, 'policy') + policy.set('type', policy_type) + for resource in resources: + item = etree.SubElement(policy, 'item') + item.set('type', resource.type) + item.set('name', resource.name) + data = etree.ElementTree(root) + if not output_file: + print(etree.tostring(data, pretty_print=True, xml_declaration=True).decode()) + else: + with open(output_file, 'wb') as f: + data.write(f, pretty_print=True, xml_declaration=True, encoding='utf-8') + +if __name__ == '__main__': + main() diff --git a/tools/rro/resource_utils.py b/tools/rro/resource_utils.py new file mode 100644 index 0000000..5e860f9 --- /dev/null +++ b/tools/rro/resource_utils.py @@ -0,0 +1,138 @@ +#!/usr/bin/env python3 +# Copyright (C) 2021 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import re +import lxml.etree as etree + +class ResourceLocation: + def __init__(self, file, line=None): + self.file = file + self.line = line + def __str__(self): + if self.line is not None: + return self.file + ':' + str(self.line) + else: + return self.file + +class Resource: + def __init__(self, name, type, location=None): + self.name = name + self.type = type + self.locations = [] + if location is not None: + self.locations.append(location) + def __eq__(self, other): + if isinstance(other, _Grab): + return other == self + return self.name == other.name and self.type == other.type + def __ne__(self, other): + if isinstance(other, _Grab): + return other != self + return self.name != other.name or self.type != other.type + def __hash__(self): + return hash((self.name, self.type)) + def __str__(self): + result = '' + for location in self.locations: + result += str(location) + ': ' + result += '<'+self.type+' name="'+self.name+'"' + return result + '>' + def __repr__(self): + return str(self) + +def get_all_resources(resDir, excluded_resource_files=[]): + excluded_resource_files = [os.path.abspath(file) for file in excluded_resource_files] + allResDirs = [f for f in os.listdir(resDir) if os.path.isdir(os.path.join(resDir, f))] + valuesDirs = [f for f in allResDirs if f.startswith('values')] + fileDirs = [f for f in allResDirs if not f.startswith('values')] + resources = set() + # Get the filenames of the all the files in all the fileDirs + for dir in fileDirs: + type = dir.split('-')[0] + for file in os.listdir(os.path.join(resDir, dir)): + filePath = os.path.abspath(os.path.join(resDir, dir, file)) + if file.endswith('.xml') and filePath not in excluded_resource_files: + add_resource_to_set(resources, + Resource(file[:-4], type, + ResourceLocation(os.path.join(resDir, dir, file)))) + if dir.startswith("layout"): + for resource in get_ids_from_layout_file(os.path.join(resDir, dir, file)): + add_resource_to_set(resources, resource) + for dir in valuesDirs: + for file in os.listdir(os.path.join(resDir, dir)): + filePath = os.path.abspath(os.path.join(resDir, dir, file)) + if file.endswith('.xml') and filePath not in excluded_resource_files: + for resource in get_resources_from_single_file(os.path.join(resDir, dir, file)): + add_resource_to_set(resources, resource) + return resources + +def get_ids_from_layout_file(filename): + result = set() + with open(filename, 'r') as file: + r = re.compile("@\+id/([a-zA-Z0-9_]+)") + for i in r.findall(file.read()): + add_resource_to_set(result, Resource(i, 'id', ResourceLocation(filename))) + return result + +def get_resources_from_single_file(filename): + doc = etree.parse(filename) + root = doc.getroot() + result = set() + for resource in root: + if resource.tag == 'declare-styleable' or resource.tag is etree.Comment: + continue + resName = resource.get('name') + resType = resource.tag + if resType == "string-array" or resType == "integer-array": + resType = "array" + if resource.tag == 'item' or resource.tag == 'public': + resType = resource.get('type') + if resType == 'overlayable': + for policy in resource: + for overlayable in policy: + resName = overlayable.get('name') + resType = overlayable.get('type') + add_resource_to_set(result, Resource(resName, resType, + ResourceLocation(filename, resource.sourceline))) + else: + add_resource_to_set(result, Resource(resName, resType, + ResourceLocation(filename, resource.sourceline))) + return result + +# Used to get objects out of sets +class _Grab: + def __init__(self, value): + self.search_value = value + def __hash__(self): + return hash(self.search_value) + def __eq__(self, other): + if self.search_value == other: + self.actual_value = other + return True + return False + +def add_resource_to_set(resourceset, resource): + if (resource.name == None): + return + grabber = _Grab(resource) + if grabber in resourceset: + grabber.actual_value.locations.extend(resource.locations) + else: + resourceset.update([resource]) + +def merge_resources(set1, set2): + for resource in set2: + add_resource_to_set(set1, resource) diff --git a/tools/rro/verify-overlayable.py b/tools/rro/verify-overlayable.py new file mode 100755 index 0000000..b469e3c --- /dev/null +++ b/tools/rro/verify-overlayable.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 +# Copyright (C) 2021 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import argparse +import sys +from resource_utils import get_all_resources, get_resources_from_single_file, Resource + +if sys.version_info[0] != 3: + print("Must use python 3") + sys.exit(1) + +""" +Script used to verify the 'overlayable.xml' file. +""" +def main(): + parser = argparse.ArgumentParser(description='Verify overlayable.xml.') + optional_args = parser.add_argument_group('optional arguments') + optional_args.add_argument('-e', '--excludeFiles', nargs='*', help='File paths (absolute or relative to cwd) that should be excluded when generating overlayable.xml') + required_args = parser.add_argument_group('required arguments') + required_args.add_argument('-r', '--resourcePath', help='Path to resource directory (absolute or relative to cwd)', required=True) + required_args.add_argument('-o', '--overlayableFilePath', help='Filepath to overlayable.xml (absolute or relative to cwd).', required=True) + args = parser.parse_args() + + resources = get_all_resources(args.resourcePath, args.excludeFiles) + old_mapping = get_resources_from_single_file(args.overlayableFilePath) + compare_resources(old_mapping, resources, args.overlayableFilePath) + +def compare_resources(old_mapping, new_mapping, res_public_file): + removed = old_mapping.difference(new_mapping) + added = new_mapping.difference(old_mapping) + if len(removed) > 0: + print('Resources removed:\n' + '\n'.join(map(lambda x: str(x), removed))) + if len(added) > 0: + print('Resources added:\n' + '\n'.join(map(lambda x: str(x), added))) + if len(added) + len(removed) > 0: + print("Some resource have been modified. If this is intentional please " + + "run 'python generate-overlayable.py' again and submit the new %s" % res_public_file) + sys.exit(1) + +if __name__ == '__main__': + main() |