1#!/usr/bin/env python 2# 3# Copyright (C) 2020 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# 17# This script generates C-Suite configuration files for a list of apps. 18 19import argparse 20import glob 21import os 22import sys 23from xml.dom import minidom 24from xml.etree import cElementTree as ET 25from xml.sax import saxutils 26 27from typing import IO, List, Text 28 29_ANDROID_BP_FILE_NAME = 'Android.bp' 30_ANDROID_XML_FILE_NAME = 'AndroidTest.xml' 31 32_TF_TEST_APP_INSTALL_SETUP =\ 33 'com.android.tradefed.targetprep.TestAppInstallSetup' 34_CSUITE_APP_SETUP_PREPARER =\ 35 'com.android.compatibility.targetprep.AppSetupPreparer' 36_CSUITE_LAUNCH_TEST_CLASS =\ 37 'com.android.compatibility.testtype.AppLaunchTest' 38 39_CONFIG_TYPE_TARGET_PREPARER = 'target_preparer' 40_CONFIG_TYPE_TEST = 'test' 41 42 43def generate_all_modules_from_config(package_list_file_path, root_dir): 44 """Generate multiple test and build modules. 45 46 Args: 47 package_list_file_path: path of a file containing package names. 48 root_dir: root directory that modules will be generated in. 49 """ 50 remove_existing_package_files(root_dir) 51 52 with open(package_list_file_path) as fp: 53 for line in parse_package_list(fp): 54 _generate_module_files(line.strip(), root_dir) 55 56 57def remove_existing_package_files(root_dir): 58 for filename in glob.iglob(root_dir + '**/AndroidTest.xml'): 59 if _is_auto_generated(filename): 60 os.remove(filename) 61 62 for filename in glob.iglob(root_dir + '**/Android.bp'): 63 if _is_auto_generated(filename): 64 os.remove(filename) 65 66 _remove_empty_dirs(root_dir) 67 68 69def _is_auto_generated(filename): 70 with open(filename, 'r') as f: 71 return 'auto-generated' in f.read() 72 73 74def _remove_empty_dirs(path): 75 for filename in os.listdir(path): 76 file_path = os.path.join(path, filename) 77 if os.path.isdir(file_path) and not os.listdir(file_path): 78 os.rmdir(file_path) 79 80 81def parse_package_list(package_list_file: IO[bytes]) -> List[bytes]: 82 return { 83 line.strip() for line in package_list_file.readlines() if line.strip()} 84 85 86def _generate_module_files(package_name, root_dir): 87 """Generate test and build modules for a single package. 88 89 Args: 90 package_name: package name of test and build modules. 91 root_dir: root directory that modules will be generated in. 92 """ 93 package_dir = _create_package_dir(root_dir, package_name) 94 95 build_module_path = os.path.join(package_dir, _ANDROID_BP_FILE_NAME) 96 test_module_path = os.path.join(package_dir, _ANDROID_XML_FILE_NAME) 97 98 with open(build_module_path, 'w') as f: 99 write_build_module(package_name, f) 100 101 with open(test_module_path, 'w') as f: 102 write_test_module(package_name, f) 103 104 105def _create_package_dir(root_dir, package_name): 106 package_dir_path = os.path.join(root_dir, package_name) 107 os.mkdir(package_dir_path) 108 109 return package_dir_path 110 111 112def write_build_module(package_name: Text, out_file: IO[bytes]) -> Text: 113 build_module = _BUILD_MODULE_HEADER \ 114 + _BUILD_MODULE_TEMPLATE.format(package_name=package_name) 115 out_file.write(build_module) 116 117 118def write_test_module(package_name: Text, out_file: IO[bytes]) -> Text: 119 configuration = ET.Element('configuration', { 120 'description': 'Tests the compatibility of apps' 121 }) 122 ET.SubElement( 123 configuration, 'option', { 124 'name': 'config-descriptor:metadata', 125 'key': 'plan', 126 'value': 'csuite-launch' 127 } 128 ) 129 ET.SubElement( 130 configuration, 'option', { 131 'name': 'package-name', 132 'value': package_name 133 } 134 ) 135 test_file_name_option = { 136 'name': 'test-file-name', 137 'value': 'csuite-launch-instrumentation.apk' 138 } 139 _add_element_with_option( 140 configuration, 141 _CONFIG_TYPE_TARGET_PREPARER, 142 _TF_TEST_APP_INSTALL_SETUP, 143 options=[test_file_name_option] 144 ) 145 _add_element_with_option( 146 configuration, 147 _CONFIG_TYPE_TARGET_PREPARER, 148 _CSUITE_APP_SETUP_PREPARER 149 ) 150 _add_element_with_option( 151 configuration, 152 _CONFIG_TYPE_TEST, 153 _CSUITE_LAUNCH_TEST_CLASS 154 ) 155 156 test_module = _TEST_MODULE_HEADER + _prettify(configuration) 157 out_file.write(test_module) 158 159 160def _add_element_with_option(elem, sub_elem, class_name, options=None): 161 if options is None: 162 options = [] 163 164 new_elem = ET.SubElement( 165 elem, sub_elem, { 166 'class': class_name, 167 } 168 ) 169 for option in options: 170 ET.SubElement( 171 new_elem, 'option', option 172 ) 173 174 175def _prettify(elem: ET.Element) -> Text: 176 declaration = minidom.Document().toxml() 177 parsed = minidom.parseString(ET.tostring(elem, 'utf-8')) 178 179 return saxutils.unescape( 180 parsed.toprettyxml(indent=' ')[len(declaration) + 1:]) 181 182_BUILD_MODULE_HEADER = """// Copyright (C) 2020 The Android Open Source Project 183// 184// Licensed under the Apache License, Version 2.0 (the "License"); 185// you may not use this file except in compliance with the License. 186// You may obtain a copy of the License at 187// 188// http://www.apache.org/licenses/LICENSE-2.0 189// 190// Unless required by applicable law or agreed to in writing, software 191// distributed under the License is distributed on an "AS IS" BASIS, 192// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 193// See the License for the specific language governing permissions and 194// limitations under the License. 195 196// This file was auto-generated by test/app_compat/csuite/tools/script/generate_module.py. 197// Do not edit manually. 198 199""" 200 201_BUILD_MODULE_TEMPLATE = """csuite_config {{ 202 name: "csuite_{package_name}", 203}} 204""" 205 206_TEST_MODULE_HEADER = """<?xml version="1.0" encoding="utf-8"?> 207<!-- Copyright (C) 2020 The Android Open Source Project 208 Licensed under the Apache License, Version 2.0 (the "License"); 209 you may not use this file except in compliance with the License. 210 You may obtain a copy of the License at 211 212 http://www.apache.org/licenses/LICENSE-2.0 213 214 Unless required by applicable law or agreed to in writing, software 215 distributed under the License is distributed on an "AS IS" BASIS, 216 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 217 See the License for the specific language governing permissions and 218 limitations under the License. 219--> 220<!-- This file was auto-generated by test/app_compat/csuite/tools/script/generate_module.py. 221 Do not edit manually. 222--> 223 224""" 225 226 227def _file_path(path): 228 if os.path.isfile(path): 229 return path 230 raise argparse.ArgumentTypeError('%s is not a valid path' % path) 231 232 233def _dir_path(path): 234 if os.path.isdir(path): 235 return path 236 raise argparse.ArgumentTypeError('%s is not a valid path' % path) 237 238 239def parse_args(args): 240 parser = argparse.ArgumentParser() 241 parser.add_argument('--package_list', 242 type=_file_path, 243 required=True, 244 help='path of the file containing package names') 245 parser.add_argument('--root_dir', 246 type=_dir_path, 247 required=True, 248 help='path of the root directory that' + 249 'modules will be generated in') 250 return parser.parse_args(args) 251 252 253def main(): 254 parser = parse_args(sys.argv[1:]) 255 generate_all_modules_from_config(parser.package_list, parser.root_dir) 256 257if __name__ == '__main__': 258 main() 259