aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHeemin Seog <hseog@google.com>2021-04-29 14:00:44 -0700
committerHeemin Seog <hseog@google.com>2021-04-30 15:53:16 -0700
commit3ea627f47fb4ec4cf3e60397ce3585bf72deacd8 (patch)
tree764ff7ec7bfbbc53897545aae9329660bfadb4a9
parentc1eaa136602fd93106c38912a79b5279a831741e (diff)
downloadtests-3ea627f47fb4ec4cf3e60397ce3585bf72deacd8.tar.gz
Add generic overlayable.xml scripts
Bug: 186785919 Test: manual with MediaCenter Change-Id: Ic62b079c471ce905c9aa457c4a41aa133cb569f7
-rw-r--r--.gitignore2
-rw-r--r--tools/rro/README18
-rwxr-xr-xtools/rro/generate-overlayable.py79
-rw-r--r--tools/rro/resource_utils.py138
-rwxr-xr-xtools/rro/verify-overlayable.py53
5 files changed, 289 insertions, 1 deletions
diff --git a/.gitignore b/.gitignore
index bd59d21..3e2e239 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,4 +4,4 @@
/.idea
.DS_Store
build/
-
+*.pyc
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()