1# Lint as: python3 2# 3# Copyright (C) 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"""This script generates C-Suite configuration files for a list of apps.""" 17 18import argparse 19import contextlib 20import glob 21import os 22import string 23import sys 24 25from typing import IO, Set, Text 26 27_ANDROID_BP_FILE_NAME = 'Android.bp' 28_ANDROID_XML_FILE_NAME = 'AndroidTest.xml' 29_AUTO_GENERATE_NOTE = 'THIS FILE WAS AUTO-GENERATED. DO NOT EDIT MANUALLY!' 30 31DEFAULT_BUILD_MODULE_TEMPLATE = string.Template("""\ 32// Copyright (C) 2019 The Android Open Source Project 33// 34// Licensed under the Apache License, Version 2.0 (the "License"); 35// you may not use this file except in compliance with the License. 36// You may obtain a copy of the License at 37// 38// http://www.apache.org/licenses/LICENSE-2.0 39// 40// Unless required by applicable law or agreed to in writing, software 41// distributed under the License is distributed on an "AS IS" BASIS, 42// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 43// See the License for the specific language governing permissions and 44// limitations under the License. 45 46// ${auto_generate_note} 47 48csuite_config { 49 name: "csuite_${package_name}", 50} 51""") 52 53DEFAULT_TEST_MODULE_TEMPLATE = string.Template("""\ 54<?xml version="1.0" encoding="utf-8"?> 55<!-- Copyright (C) 2019 The Android Open Source Project 56 57 Licensed under the Apache License, Version 2.0 (the "License"); 58 you may not use this file except in compliance with the License. 59 You may obtain a copy of the License at 60 61 http://www.apache.org/licenses/LICENSE-2.0 62 63 Unless required by applicable law or agreed to in writing, software 64 distributed under the License is distributed on an "AS IS" BASIS, 65 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 66 See the License for the specific language governing permissions and 67 limitations under the License. 68--> 69<!-- ${auto_generate_note}--> 70 71<configuration description="Tests the compatibility of apps"> 72 <option key="plan" name="config-descriptor:metadata" value="app-launch"/> 73 <option name="package-name" value="${package_name}"/> 74 <target_preparer class="com.android.compatibility.targetprep.AppSetupPreparer"> 75 <option name="test-file-name" value="csuite-launch-instrumentation.apk"/> 76 <option name="test-file-name" value="app://${package_name}"/> 77 </target_preparer> 78 <target_preparer class="com.android.compatibility.targetprep.CheckGmsPreparer"/> 79 <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> 80 <option name="run-command" value="input keyevent KEYCODE_WAKEUP"/> 81 <option name="run-command" value="input keyevent KEYCODE_MENU"/> 82 <option name="run-command" value="input keyevent KEYCODE_HOME"/> 83 </target_preparer> 84 <test class="com.android.compatibility.testtype.AppLaunchTest"/> 85</configuration> 86""") 87 88 89def generate_all_modules_from_config(package_list_file_path, 90 root_dir, 91 build_module_template_file_path=None, 92 test_module_template_file_path=None): 93 """Generate multiple test and build modules. 94 95 Args: 96 package_list_file_path: path of a file containing package names. 97 root_dir: root directory that modules will be generated in. 98 build_module_template_file_path: path of a file containing build module 99 template. 100 test_module_template_file_path: path of a file containing test module 101 template. 102 """ 103 build_module_template = DEFAULT_BUILD_MODULE_TEMPLATE 104 test_module_template = DEFAULT_TEST_MODULE_TEMPLATE 105 if build_module_template_file_path: 106 with open(build_module_template_file_path, 'r') as f: 107 build_module_template = string.Template(f.read()) 108 if test_module_template_file_path: 109 with open(test_module_template_file_path, 'r') as f: 110 test_module_template = string.Template(f.read()) 111 112 remove_existing_package_files(root_dir) 113 114 with open(package_list_file_path) as fp: 115 for line in parse_package_list(fp): 116 _generate_module_files(line.strip(), root_dir, build_module_template, 117 test_module_template) 118 119 120def remove_existing_package_files(root_dir): 121 for filename in glob.iglob(root_dir + '/**/AndroidTest.xml'): 122 if _is_auto_generated(filename): 123 os.remove(filename) 124 125 for filename in glob.iglob(root_dir + '/**/Android.bp'): 126 if _is_auto_generated(filename): 127 os.remove(filename) 128 129 _remove_empty_dirs(root_dir) 130 131 132def _is_auto_generated(filename): 133 with open(filename, 'r') as f: 134 return _AUTO_GENERATE_NOTE in f.read() 135 136 137def _remove_empty_dirs(path): 138 for filename in os.listdir(path): 139 file_path = os.path.join(path, filename) 140 if os.path.isdir(file_path) and not os.listdir(file_path): 141 os.rmdir(file_path) 142 143 144def parse_package_list(package_list_file: IO[bytes]) -> Set[bytes]: 145 packages = {line.strip() for line in package_list_file.readlines()} 146 for package in packages: 147 if package and not package.startswith('#'): 148 yield package 149 150 151def _generate_module_files(package_name, root_dir, build_module_template, 152 test_module_template): 153 """Generate test and build modules for a single package. 154 155 Args: 156 package_name: package name of test and build modules. 157 root_dir: root directory that modules will be generated in. 158 build_module_template: template for build module. 159 test_module_template: template for test module. 160 """ 161 package_dir = _create_package_dir(root_dir, package_name) 162 163 build_module_path = os.path.join(package_dir, _ANDROID_BP_FILE_NAME) 164 test_module_path = os.path.join(package_dir, _ANDROID_XML_FILE_NAME) 165 166 with open(build_module_path, 'w') as f: 167 write_module(build_module_template, package_name, f) 168 169 with open(test_module_path, 'w') as f: 170 write_module(test_module_template, package_name, f) 171 172 173def _create_package_dir(root_dir, package_name): 174 package_dir_path = os.path.join(root_dir, package_name) 175 os.mkdir(package_dir_path) 176 177 return package_dir_path 178 179 180def write_module(template: string.Template, package_name: Text, 181 out_file: IO[bytes]) -> Text: 182 """Writes the build or test module for the provided package into a file.""" 183 test_module = template.substitute( 184 package_name=package_name, auto_generate_note=_AUTO_GENERATE_NOTE) 185 out_file.write(test_module) 186 187 188def _file_path(path): 189 if os.path.isfile(path): 190 return path 191 raise argparse.ArgumentTypeError('%s is not a valid path' % path) 192 193 194def _dir_path(path): 195 if os.path.isdir(path): 196 return path 197 raise argparse.ArgumentTypeError('%s is not a valid path' % path) 198 199 200@contextlib.contextmanager 201def _redirect_sys_output(out, err): 202 current_out, current_err = sys.stdout, sys.stderr 203 try: 204 sys.stdout, sys.stderr = out, err 205 yield 206 finally: 207 sys.stdout, sys.stderr = current_out, current_err 208 209 210def parse_args(args, out=sys.stdout, err=sys.stderr): 211 """Parses the provided sequence of arguments.""" 212 parser = argparse.ArgumentParser() 213 parser.add_argument( 214 '--package-list', 215 type=_file_path, 216 required=True, 217 help='path of the file containing package names') 218 parser.add_argument( 219 '--root-dir', 220 type=_dir_path, 221 required=True, 222 help='path of the root directory that' + 'modules will be generated in') 223 parser.add_argument( 224 '--test-module-template', 225 type=_file_path, 226 required=False, 227 help='path of the file containing test module configuration template') 228 parser.add_argument( 229 '--build-module-template', 230 type=_file_path, 231 required=False, 232 help='path of the file containing build module configuration template') 233 234 # We redirect stdout and stderr to improve testability since ArgumentParser 235 # always writes to those files. More specifically, the TradeFed python test 236 # runner will choke parsing output that is not in the expected format. 237 with _redirect_sys_output(out, err): 238 return parser.parse_args(args) 239 240 241def main(): 242 parser = parse_args(sys.argv[1:]) 243 generate_all_modules_from_config(parser.package_list, parser.root_dir, 244 parser.build_module_template, 245 parser.test_module_template) 246 247 248if __name__ == '__main__': 249 main() 250