# Lint as: python3 # # Copyright (C) 2019 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. """This script generates C-Suite configuration files for a list of apps.""" import argparse import contextlib import glob import os import string import sys from typing import IO, Set, Text _ANDROID_BP_FILE_NAME = 'Android.bp' _ANDROID_XML_FILE_NAME = 'AndroidTest.xml' _AUTO_GENERATE_NOTE = 'THIS FILE WAS AUTO-GENERATED. DO NOT EDIT MANUALLY!' DEFAULT_BUILD_MODULE_TEMPLATE = string.Template("""\ // Copyright (C) 2019 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. // ${auto_generate_note} csuite_config { name: "csuite_${package_name}", } """) DEFAULT_TEST_MODULE_TEMPLATE = string.Template("""\ """) def generate_all_modules_from_config(package_list_file_path, root_dir, build_module_template_file_path=None, test_module_template_file_path=None): """Generate multiple test and build modules. Args: package_list_file_path: path of a file containing package names. root_dir: root directory that modules will be generated in. build_module_template_file_path: path of a file containing build module template. test_module_template_file_path: path of a file containing test module template. """ build_module_template = DEFAULT_BUILD_MODULE_TEMPLATE test_module_template = DEFAULT_TEST_MODULE_TEMPLATE if build_module_template_file_path: with open(build_module_template_file_path, 'r') as f: build_module_template = string.Template(f.read()) if test_module_template_file_path: with open(test_module_template_file_path, 'r') as f: test_module_template = string.Template(f.read()) remove_existing_package_files(root_dir) with open(package_list_file_path) as fp: for line in parse_package_list(fp): _generate_module_files(line.strip(), root_dir, build_module_template, test_module_template) def remove_existing_package_files(root_dir): for filename in glob.iglob(root_dir + '/**/AndroidTest.xml'): if _is_auto_generated(filename): os.remove(filename) for filename in glob.iglob(root_dir + '/**/Android.bp'): if _is_auto_generated(filename): os.remove(filename) _remove_empty_dirs(root_dir) def _is_auto_generated(filename): with open(filename, 'r') as f: return _AUTO_GENERATE_NOTE in f.read() def _remove_empty_dirs(path): for filename in os.listdir(path): file_path = os.path.join(path, filename) if os.path.isdir(file_path) and not os.listdir(file_path): os.rmdir(file_path) def parse_package_list(package_list_file: IO[bytes]) -> Set[bytes]: packages = {line.strip() for line in package_list_file.readlines()} for package in packages: if package and not package.startswith('#'): yield package def _generate_module_files(package_name, root_dir, build_module_template, test_module_template): """Generate test and build modules for a single package. Args: package_name: package name of test and build modules. root_dir: root directory that modules will be generated in. build_module_template: template for build module. test_module_template: template for test module. """ package_dir = _create_package_dir(root_dir, package_name) build_module_path = os.path.join(package_dir, _ANDROID_BP_FILE_NAME) test_module_path = os.path.join(package_dir, _ANDROID_XML_FILE_NAME) with open(build_module_path, 'w') as f: write_module(build_module_template, package_name, f) with open(test_module_path, 'w') as f: write_module(test_module_template, package_name, f) def _create_package_dir(root_dir, package_name): package_dir_path = os.path.join(root_dir, package_name) os.mkdir(package_dir_path) return package_dir_path def write_module(template: string.Template, package_name: Text, out_file: IO[bytes]) -> Text: """Writes the build or test module for the provided package into a file.""" test_module = template.substitute( package_name=package_name, auto_generate_note=_AUTO_GENERATE_NOTE) out_file.write(test_module) def _file_path(path): if os.path.isfile(path): return path raise argparse.ArgumentTypeError('%s is not a valid path' % path) def _dir_path(path): if os.path.isdir(path): return path raise argparse.ArgumentTypeError('%s is not a valid path' % path) @contextlib.contextmanager def _redirect_sys_output(out, err): current_out, current_err = sys.stdout, sys.stderr try: sys.stdout, sys.stderr = out, err yield finally: sys.stdout, sys.stderr = current_out, current_err def parse_args(args, out=sys.stdout, err=sys.stderr): """Parses the provided sequence of arguments.""" parser = argparse.ArgumentParser() parser.add_argument( '--package-list', type=_file_path, required=True, help='path of the file containing package names') parser.add_argument( '--root-dir', type=_dir_path, required=True, help='path of the root directory that' + 'modules will be generated in') parser.add_argument( '--test-module-template', type=_file_path, required=False, help='path of the file containing test module configuration template') parser.add_argument( '--build-module-template', type=_file_path, required=False, help='path of the file containing build module configuration template') # We redirect stdout and stderr to improve testability since ArgumentParser # always writes to those files. More specifically, the TradeFed python test # runner will choke parsing output that is not in the expected format. with _redirect_sys_output(out, err): return parser.parse_args(args) def main(): parser = parse_args(sys.argv[1:]) generate_all_modules_from_config(parser.package_list, parser.root_dir, parser.build_module_template, parser.test_module_template) if __name__ == '__main__': main()