Check for invalid RROs when updating chassis

Added a preupload check that our /vendor/auto/embedded/car-ui RROs
don't attempt to RRO anything that doesn't exist.

Also added our current.xml presubmit check as a preupload check.

Fixes: 144058191
Test: Manually
Change-Id: I2d8c68d4dc9f885d76b6d4ae7208ce95d774b591
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index 38f9800..dcf9645 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -1,6 +1,9 @@
 [Hook Scripts]
 checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py --sha ${PREUPLOAD_COMMIT}
 ktlint_hook = ${REPO_ROOT}/prebuilts/ktlint/ktlint.py -f ${PREUPLOAD_FILES}
+chassis_current_hook = car-ui-lib/tests/apitest/auto-generate-resources.py --sha ${PREUPLOAD_COMMIT} --compare
+chassis_sample1_hook = car-ui-lib/tests/apitest/verify_rro.py --sha ${PREUPLOAD_COMMIT} --rro ${REPO_ROOT}/vendor/auto/embedded/car-ui/sample1/rro/res --rro ${REPO_ROOT}/vendor/auto/embedded/car-ui/sample1/rro-tabs/res --base car-ui-lib/res
+chassis_sample2_hook = car-ui-lib/tests/apitest/verify_rro.py --sha ${PREUPLOAD_COMMIT} --rro ${REPO_ROOT}/vendor/auto/embedded/car-ui/sample2/rro/res --rro ${REPO_ROOT}/vendor/auto/embedded/car-ui/sample2/rro-tabs/res --base car-ui-lib/res
 
 [Builtin Hooks]
 commit_msg_changeid_field = true
diff --git a/car-ui-lib/tests/apitest/auto-generate-resources.py b/car-ui-lib/tests/apitest/auto-generate-resources.py
old mode 100644
new mode 100755
index d859813..eb2e0e5
--- a/car-ui-lib/tests/apitest/auto-generate-resources.py
+++ b/car-ui-lib/tests/apitest/auto-generate-resources.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/python
 
 # Copyright 2019, The Android Open Source Project
 #
@@ -26,8 +26,8 @@
 import argparse
 import os
 import sys
-import lxml.etree as etree
 from resource_utils import get_all_resources, get_resources_from_single_file, remove_layout_resources
+from git_utils import has_chassis_changes
 
 # path to 'packages/apps/Car/libs/car-ui-lib/'
 ROOT_FOLDER = os.path.dirname(os.path.abspath(__file__)) + '/../..'
@@ -42,11 +42,16 @@
 """
 def main():
     parser = argparse.ArgumentParser(description='Check if any existing resources are modified.')
+    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.')
     parser.add_argument('-f', '--file', default='current.xml', help='Name of output file.')
     parser.add_argument('-c', '--compare', action='store_true',
                         help='Pass this flag if resources need to be compared.')
     args = parser.parse_args()
 
+    if not has_chassis_changes(args.sha):
+        # Don't run because there were no chassis changes
+        return
+
     output_file = args.file or 'current.xml'
     if args.compare:
         compare_resources(ROOT_FOLDER+'/res', OUTPUT_FILE_PATH + 'current.xml')
@@ -57,6 +62,10 @@
     resources = remove_layout_resources(get_all_resources(res_folder))
     resources = sorted(resources, key=lambda x: x.type + x.name)
 
+    # defer importing lxml to here so that people who aren't editing chassis don't have to have
+    # lxml installed
+    import lxml.etree as etree
+
     root = etree.Element('resources')
 
     root.addprevious(etree.Comment('This file is AUTO GENERATED, DO NOT EDIT MANUALLY.'))
@@ -83,6 +92,8 @@
         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 auto-generate-resources.py' again and submit the new current.xml")
         sys.exit(1)
 
 if __name__ == '__main__':
diff --git a/car-ui-lib/tests/apitest/git_utils.py b/car-ui-lib/tests/apitest/git_utils.py
new file mode 100755
index 0000000..a7b5089
--- /dev/null
+++ b/car-ui-lib/tests/apitest/git_utils.py
@@ -0,0 +1,39 @@
+#!/usr/bin/python
+
+# Copyright 2019, The Android Open Source Project
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use, copy,
+# modify, merge, publish, distribute, sublicense, and/or sell copies
+# of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+#
+
+import subprocess
+
+def has_chassis_changes(sha):
+    if sha is None:
+        return True
+
+    result = subprocess.Popen(['git', 'diff-tree', '--no-commit-id', '--name-only', '-r', sha],
+                              stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+    stdout, stderr = result.communicate()
+
+    if result.returncode != 0:
+        raise Exception("Git error: "+str(stdout)+str(stderr))
+
+    return 'car-ui-lib' in str(stdout)
diff --git a/car-ui-lib/tests/apitest/resource_utils.py b/car-ui-lib/tests/apitest/resource_utils.py
old mode 100644
new mode 100755
index 13bbcff..a15c97b
--- a/car-ui-lib/tests/apitest/resource_utils.py
+++ b/car-ui-lib/tests/apitest/resource_utils.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/python
 
 # Copyright 2019, The Android Open Source Project
 #
@@ -24,8 +24,6 @@
 #
 
 import os
-import sys
-import lxml.etree as etree
 
 class ResourceLocation:
     def __init__(self, file, line=None):
@@ -47,9 +45,13 @@
             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):
@@ -91,6 +93,9 @@
     return resources
 
 def get_resources_from_single_file(filename):
+    # defer importing lxml to here so that people who aren't editing chassis don't have to have
+    # lxml installed
+    import lxml.etree as etree
     doc = etree.parse(filename)
     resourceTag = doc.getroot()
     result = set()
@@ -132,5 +137,6 @@
     else:
         resourceset.update([resource])
 
-if __name__ == '__main__':
-    print(get_all_resources(sys.argv[1]))
+def merge_resources(set1, set2):
+    for resource in set2:
+        add_resource_to_set(set1, resource)
diff --git a/car-ui-lib/tests/apitest/verify_rro.py b/car-ui-lib/tests/apitest/verify_rro.py
new file mode 100755
index 0000000..96dadc3
--- /dev/null
+++ b/car-ui-lib/tests/apitest/verify_rro.py
@@ -0,0 +1,61 @@
+#!/usr/bin/python
+
+# Copyright 2019, The Android Open Source Project
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use, copy,
+# modify, merge, publish, distribute, sublicense, and/or sell copies
+# of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+#
+
+import argparse
+import sys
+from resource_utils import get_all_resources, remove_layout_resources, merge_resources
+from git_utils import has_chassis_changes
+
+def main():
+    parser = argparse.ArgumentParser(description="Check that an rro does not attempt to overlay any resources that don't exist")
+    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.')
+    parser.add_argument('-r', '--rro', action='append', nargs=1, help='res folder of an RRO')
+    parser.add_argument('-b', '--base', action='append', nargs=1, help='res folder of what is being RROd')
+    args = parser.parse_args()
+
+    if not has_chassis_changes(args.sha):
+        # Don't run because there were no chassis changes
+        return
+
+    if args.rro is None or args.base is None:
+        parser.print_help()
+        sys.exit(1)
+
+    rro_resources = set()
+    for resDir in args.rro:
+        merge_resources(rro_resources, remove_layout_resources(get_all_resources(resDir[0])))
+
+    base_resources = set()
+    for resDir in args.base:
+        merge_resources(base_resources, remove_layout_resources(get_all_resources(resDir[0])))
+
+    extras = rro_resources.difference(base_resources)
+    if len(extras) > 0:
+        print("RRO attempting to override resources that don't exist:\n"
+              + '\n'.join(map(lambda x: str(x), extras)))
+        sys.exit(1)
+
+if __name__ == "__main__":
+    main()