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