blob: 10640415e0e3dfb60566ee620fb457aeb89c6146 [file] [log] [blame]
Luca Farsi5717d6f2023-12-28 15:09:28 -08001# Copyright 2024, The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15"""Script to build only the necessary modules for general-tests along
16
17with whatever other targets are passed in.
18"""
19
20import argparse
21from collections.abc import Sequence
22import json
23import os
24import pathlib
25import re
26import subprocess
27import sys
28from typing import Any, Dict, Set, Text
29
30import test_mapping_module_retriever
31
32
33# List of modules that are always required to be in general-tests.zip
34REQUIRED_MODULES = frozenset(
35 ['cts-tradefed', 'vts-tradefed', 'compatibility-host-util', 'soong_zip']
36)
37
38
39def build_test_suites(argv):
40 args = parse_args(argv)
41
42 if not args.change_info:
43 build_everything(args)
44 return
45
46 # Call the class to map changed files to modules to build.
47 # TODO(lucafarsi): Move this into a replaceable class.
48 build_affected_modules(args)
49
50
51def parse_args(argv):
52 argparser = argparse.ArgumentParser()
53 argparser.add_argument(
54 'extra_targets', nargs='*', help='Extra test suites to build.'
55 )
56 argparser.add_argument('--target_product')
57 argparser.add_argument('--target_release')
58 argparser.add_argument(
59 '--with_dexpreopt_boot_img_and_system_server_only', action='store_true'
60 )
61 argparser.add_argument('--dist_dir')
62 argparser.add_argument('--change_info', nargs='?')
63 argparser.add_argument('--extra_required_modules', nargs='*')
64
65 return argparser.parse_args()
66
67
68def build_everything(args: argparse.Namespace):
69 build_command = base_build_command(args)
70 build_command.append('general-tests')
71
Luca Farsib559eef2024-01-17 16:14:55 -080072 run_command(build_command, print_output=True)
Luca Farsi5717d6f2023-12-28 15:09:28 -080073
74
75def build_affected_modules(args: argparse.Namespace):
76 modules_to_build = find_modules_to_build(
77 pathlib.Path(args.change_info), args.extra_required_modules
78 )
79
80 # Call the build command with everything.
81 build_command = base_build_command(args)
82 build_command.extend(modules_to_build)
83
Luca Farsib559eef2024-01-17 16:14:55 -080084 run_command(build_command, print_output=True)
Luca Farsi5717d6f2023-12-28 15:09:28 -080085
Luca Farsib559eef2024-01-17 16:14:55 -080086 zip_build_outputs(modules_to_build, args.dist_dir, args.target_release)
Luca Farsi5717d6f2023-12-28 15:09:28 -080087
88
89def base_build_command(args: argparse.Namespace) -> list:
90 build_command = []
91 build_command.append('time')
92 build_command.append('./build/soong/soong_ui.bash')
93 build_command.append('--make-mode')
94 build_command.append('dist')
95 build_command.append('DIST_DIR=' + args.dist_dir)
96 build_command.append('TARGET_PRODUCT=' + args.target_product)
97 build_command.append('TARGET_RELEASE=' + args.target_release)
Luca Farsi212d3862024-01-12 11:22:06 -080098 if args.with_dexpreopt_boot_img_and_system_server_only:
99 build_command.append('WITH_DEXPREOPT_BOOT_IMG_AND_SYSTEM_SERVER_ONLY=true')
Luca Farsi5717d6f2023-12-28 15:09:28 -0800100 build_command.extend(args.extra_targets)
101
102 return build_command
103
104
Luca Farsib559eef2024-01-17 16:14:55 -0800105def run_command(
106 args: list[str],
107 env: Dict[Text, Text] = os.environ,
108 print_output: bool = False,
109) -> str:
Luca Farsi5717d6f2023-12-28 15:09:28 -0800110 result = subprocess.run(
111 args=args,
112 text=True,
113 capture_output=True,
114 check=False,
Luca Farsib559eef2024-01-17 16:14:55 -0800115 env=env,
Luca Farsi5717d6f2023-12-28 15:09:28 -0800116 )
117 # If the process failed, print its stdout and propagate the exception.
118 if not result.returncode == 0:
119 print('Build command failed! output:')
120 print('stdout: ' + result.stdout)
121 print('stderr: ' + result.stderr)
122
123 result.check_returncode()
Luca Farsib559eef2024-01-17 16:14:55 -0800124
125 if print_output:
126 print(result.stdout)
127
Luca Farsi5717d6f2023-12-28 15:09:28 -0800128 return result.stdout
129
130
131def find_modules_to_build(
132 change_info: pathlib.Path, extra_required_modules: list[Text]
133) -> Set[Text]:
134 changed_files = find_changed_files(change_info)
135
136 test_mappings = test_mapping_module_retriever.GetTestMappings(
137 changed_files, set()
138 )
139
140 # Soong_zip is required to generate the output zip so always build it.
141 modules_to_build = set(REQUIRED_MODULES)
142 if extra_required_modules:
143 modules_to_build.update(extra_required_modules)
144
145 modules_to_build.update(find_affected_modules(test_mappings, changed_files))
146
147 return modules_to_build
148
149
150def find_changed_files(change_info: pathlib.Path) -> Set[Text]:
151 with open(change_info) as change_info_file:
152 change_info_contents = json.load(change_info_file)
153
154 changed_files = set()
155
156 for change in change_info_contents['changes']:
157 project_path = change.get('projectPath') + '/'
158
159 for revision in change.get('revisions'):
160 for file_info in revision.get('fileInfos'):
161 changed_files.add(project_path + file_info.get('path'))
162
163 return changed_files
164
165
166def find_affected_modules(
167 test_mappings: Dict[str, Any], changed_files: Set[Text]
168) -> Set[Text]:
169 modules = set()
170
171 # The test_mappings object returned by GetTestMappings is organized as
172 # follows:
173 # {
174 # 'test_mapping_file_path': {
175 # 'group_name' : [
176 # 'name': 'module_name',
177 # ],
178 # }
179 # }
180 for test_mapping in test_mappings.values():
181 for group in test_mapping.values():
182 for entry in group:
183 module_name = entry.get('name', None)
184
185 if not module_name:
186 continue
187
188 file_patterns = entry.get('file_patterns')
189 if not file_patterns:
190 modules.add(module_name)
191 continue
192
193 if matches_file_patterns(file_patterns, changed_files):
194 modules.add(module_name)
195 continue
196
197 return modules
198
199
200# TODO(lucafarsi): Share this logic with the original logic in
201# test_mapping_test_retriever.py
202def matches_file_patterns(
203 file_patterns: list[Text], changed_files: Set[Text]
204) -> bool:
205 for changed_file in changed_files:
206 for pattern in file_patterns:
207 if re.search(pattern, changed_file):
208 return True
209
210 return False
211
212
Luca Farsib559eef2024-01-17 16:14:55 -0800213def zip_build_outputs(
214 modules_to_build: Set[Text], dist_dir: Text, target_release: Text
215):
Luca Farsi5717d6f2023-12-28 15:09:28 -0800216 src_top = os.environ.get('TOP', os.getcwd())
217
218 # Call dumpvars to get the necessary things.
219 # TODO(lucafarsi): Don't call soong_ui 4 times for this, --dumpvars-mode can
220 # do it but it requires parsing.
Luca Farsib559eef2024-01-17 16:14:55 -0800221 host_out_testcases = get_soong_var('HOST_OUT_TESTCASES', target_release)
222 target_out_testcases = get_soong_var('TARGET_OUT_TESTCASES', target_release)
223 product_out = get_soong_var('PRODUCT_OUT', target_release)
224 soong_host_out = get_soong_var('SOONG_HOST_OUT', target_release)
225 host_out = get_soong_var('HOST_OUT', target_release)
Luca Farsi5717d6f2023-12-28 15:09:28 -0800226
227 # Call the class to package the outputs.
228 # TODO(lucafarsi): Move this code into a replaceable class.
229 host_paths = []
230 target_paths = []
231 for module in modules_to_build:
232 host_path = os.path.join(host_out_testcases, module)
233 if os.path.exists(host_path):
234 host_paths.append(host_path)
235
236 target_path = os.path.join(target_out_testcases, module)
237 if os.path.exists(target_path):
238 target_paths.append(target_path)
239
240 zip_command = ['time', os.path.join(host_out, 'bin', 'soong_zip')]
241
242 # Add host testcases.
243 zip_command.append('-C')
244 zip_command.append(os.path.join(src_top, soong_host_out))
245 zip_command.append('-P')
246 zip_command.append('host/')
247 for path in host_paths:
248 zip_command.append('-D')
249 zip_command.append(path)
250
251 # Add target testcases.
252 zip_command.append('-C')
253 zip_command.append(os.path.join(src_top, product_out))
254 zip_command.append('-P')
255 zip_command.append('target')
256 for path in target_paths:
257 zip_command.append('-D')
258 zip_command.append(path)
259
260 # TODO(lucafarsi): Push this logic into a general-tests-minimal build command
261 # Add necessary tools. These are also hardcoded in general-tests.mk.
262 framework_path = os.path.join(soong_host_out, 'framework')
263
264 zip_command.append('-C')
265 zip_command.append(framework_path)
266 zip_command.append('-P')
267 zip_command.append('host/tools')
268 zip_command.append('-f')
269 zip_command.append(os.path.join(framework_path, 'cts-tradefed.jar'))
270 zip_command.append('-f')
271 zip_command.append(
272 os.path.join(framework_path, 'compatibility-host-util.jar')
273 )
274 zip_command.append('-f')
275 zip_command.append(os.path.join(framework_path, 'vts-tradefed.jar'))
276
277 # Zip to the DIST dir.
278 zip_command.append('-o')
279 zip_command.append(os.path.join(dist_dir, 'general-tests.zip'))
280
Luca Farsib559eef2024-01-17 16:14:55 -0800281 run_command(zip_command, print_output=True)
Luca Farsi5717d6f2023-12-28 15:09:28 -0800282
283
Luca Farsib559eef2024-01-17 16:14:55 -0800284def get_soong_var(var: str, target_release: str) -> str:
285 new_env = os.environ.copy()
286 new_env['TARGET_RELEASE'] = target_release
287
Luca Farsi5717d6f2023-12-28 15:09:28 -0800288 value = run_command(
Luca Farsib559eef2024-01-17 16:14:55 -0800289 ['./build/soong/soong_ui.bash', '--dumpvar-mode', '--abs', var],
290 env=new_env,
Luca Farsi5717d6f2023-12-28 15:09:28 -0800291 ).strip()
292 if not value:
293 raise RuntimeError('Necessary soong variable ' + var + ' not found.')
294
295 return value
296
297
298def main(argv):
299 build_test_suites(sys.argv)