xref: /aosp_15_r20/build/make/ci/build_test_suites.py (revision 9e94795a3d4ef5c1d47486f9a02bb378756cea8a)
1*9e94795aSAndroid Build Coastguard Worker# Copyright 2024, The Android Open Source Project
2*9e94795aSAndroid Build Coastguard Worker#
3*9e94795aSAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License");
4*9e94795aSAndroid Build Coastguard Worker# you may not use this file except in compliance with the License.
5*9e94795aSAndroid Build Coastguard Worker# You may obtain a copy of the License at
6*9e94795aSAndroid Build Coastguard Worker#
7*9e94795aSAndroid Build Coastguard Worker#     http://www.apache.org/licenses/LICENSE-2.0
8*9e94795aSAndroid Build Coastguard Worker#
9*9e94795aSAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software
10*9e94795aSAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS,
11*9e94795aSAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12*9e94795aSAndroid Build Coastguard Worker# See the License for the specific language governing permissions and
13*9e94795aSAndroid Build Coastguard Worker# limitations under the License.
14*9e94795aSAndroid Build Coastguard Worker
15*9e94795aSAndroid Build Coastguard Worker"""Build script for the CI `test_suites` target."""
16*9e94795aSAndroid Build Coastguard Worker
17*9e94795aSAndroid Build Coastguard Workerimport argparse
18*9e94795aSAndroid Build Coastguard Workerfrom dataclasses import dataclass
19*9e94795aSAndroid Build Coastguard Workerimport json
20*9e94795aSAndroid Build Coastguard Workerimport logging
21*9e94795aSAndroid Build Coastguard Workerimport os
22*9e94795aSAndroid Build Coastguard Workerimport pathlib
23*9e94795aSAndroid Build Coastguard Workerimport re
24*9e94795aSAndroid Build Coastguard Workerimport subprocess
25*9e94795aSAndroid Build Coastguard Workerimport sys
26*9e94795aSAndroid Build Coastguard Workerfrom typing import Callable
27*9e94795aSAndroid Build Coastguard Workerfrom build_context import BuildContext
28*9e94795aSAndroid Build Coastguard Workerimport optimized_targets
29*9e94795aSAndroid Build Coastguard Workerimport metrics_agent
30*9e94795aSAndroid Build Coastguard Workerimport test_discovery_agent
31*9e94795aSAndroid Build Coastguard Worker
32*9e94795aSAndroid Build Coastguard Worker
33*9e94795aSAndroid Build Coastguard WorkerREQUIRED_ENV_VARS = frozenset(['TARGET_PRODUCT', 'TARGET_RELEASE', 'TOP', 'DIST_DIR'])
34*9e94795aSAndroid Build Coastguard WorkerSOONG_UI_EXE_REL_PATH = 'build/soong/soong_ui.bash'
35*9e94795aSAndroid Build Coastguard WorkerLOG_PATH = 'logs/build_test_suites.log'
36*9e94795aSAndroid Build Coastguard WorkerREQUIRED_BUILD_TARGETS = frozenset(['dist'])
37*9e94795aSAndroid Build Coastguard Worker
38*9e94795aSAndroid Build Coastguard Worker
39*9e94795aSAndroid Build Coastguard Workerclass Error(Exception):
40*9e94795aSAndroid Build Coastguard Worker
41*9e94795aSAndroid Build Coastguard Worker  def __init__(self, message):
42*9e94795aSAndroid Build Coastguard Worker    super().__init__(message)
43*9e94795aSAndroid Build Coastguard Worker
44*9e94795aSAndroid Build Coastguard Worker
45*9e94795aSAndroid Build Coastguard Workerclass BuildFailureError(Error):
46*9e94795aSAndroid Build Coastguard Worker
47*9e94795aSAndroid Build Coastguard Worker  def __init__(self, return_code):
48*9e94795aSAndroid Build Coastguard Worker    super().__init__(f'Build command failed with return code: f{return_code}')
49*9e94795aSAndroid Build Coastguard Worker    self.return_code = return_code
50*9e94795aSAndroid Build Coastguard Worker
51*9e94795aSAndroid Build Coastguard Worker
52*9e94795aSAndroid Build Coastguard Workerclass BuildPlanner:
53*9e94795aSAndroid Build Coastguard Worker  """Class in charge of determining how to optimize build targets.
54*9e94795aSAndroid Build Coastguard Worker
55*9e94795aSAndroid Build Coastguard Worker  Given the build context and targets to build it will determine a final list of
56*9e94795aSAndroid Build Coastguard Worker  targets to build along with getting a set of packaging functions to package up
57*9e94795aSAndroid Build Coastguard Worker  any output zip files needed by the build.
58*9e94795aSAndroid Build Coastguard Worker  """
59*9e94795aSAndroid Build Coastguard Worker
60*9e94795aSAndroid Build Coastguard Worker  def __init__(
61*9e94795aSAndroid Build Coastguard Worker      self,
62*9e94795aSAndroid Build Coastguard Worker      build_context: BuildContext,
63*9e94795aSAndroid Build Coastguard Worker      args: argparse.Namespace,
64*9e94795aSAndroid Build Coastguard Worker      target_optimizations: dict[str, optimized_targets.OptimizedBuildTarget],
65*9e94795aSAndroid Build Coastguard Worker  ):
66*9e94795aSAndroid Build Coastguard Worker    self.build_context = build_context
67*9e94795aSAndroid Build Coastguard Worker    self.args = args
68*9e94795aSAndroid Build Coastguard Worker    self.target_optimizations = target_optimizations
69*9e94795aSAndroid Build Coastguard Worker
70*9e94795aSAndroid Build Coastguard Worker  def create_build_plan(self):
71*9e94795aSAndroid Build Coastguard Worker
72*9e94795aSAndroid Build Coastguard Worker    if 'optimized_build' not in self.build_context.enabled_build_features:
73*9e94795aSAndroid Build Coastguard Worker      return BuildPlan(set(self.args.extra_targets), set())
74*9e94795aSAndroid Build Coastguard Worker
75*9e94795aSAndroid Build Coastguard Worker    build_targets = set()
76*9e94795aSAndroid Build Coastguard Worker    packaging_commands_getters = []
77*9e94795aSAndroid Build Coastguard Worker    # In order to roll optimizations out differently between test suites and
78*9e94795aSAndroid Build Coastguard Worker    # device builds, we have separate flags.
79*9e94795aSAndroid Build Coastguard Worker    if (
80*9e94795aSAndroid Build Coastguard Worker        'test_suites_zip_test_discovery'
81*9e94795aSAndroid Build Coastguard Worker        in self.build_context.enabled_build_features
82*9e94795aSAndroid Build Coastguard Worker        and not self.args.device_build
83*9e94795aSAndroid Build Coastguard Worker    ) or (
84*9e94795aSAndroid Build Coastguard Worker        'device_zip_test_discovery'
85*9e94795aSAndroid Build Coastguard Worker        in self.build_context.enabled_build_features
86*9e94795aSAndroid Build Coastguard Worker        and self.args.device_build
87*9e94795aSAndroid Build Coastguard Worker    ):
88*9e94795aSAndroid Build Coastguard Worker      preliminary_build_targets = self._collect_preliminary_build_targets()
89*9e94795aSAndroid Build Coastguard Worker    else:
90*9e94795aSAndroid Build Coastguard Worker      preliminary_build_targets = self._legacy_collect_preliminary_build_targets()
91*9e94795aSAndroid Build Coastguard Worker
92*9e94795aSAndroid Build Coastguard Worker      # Keep reporting metrics when test discovery is disabled.
93*9e94795aSAndroid Build Coastguard Worker      # To be removed once test discovery is fully rolled out.
94*9e94795aSAndroid Build Coastguard Worker      optimization_rationale = ''
95*9e94795aSAndroid Build Coastguard Worker      test_discovery_zip_regexes = set()
96*9e94795aSAndroid Build Coastguard Worker      try:
97*9e94795aSAndroid Build Coastguard Worker        test_discovery_zip_regexes = self._get_test_discovery_zip_regexes()
98*9e94795aSAndroid Build Coastguard Worker        logging.info(f'Discovered test discovery regexes: {test_discovery_zip_regexes}')
99*9e94795aSAndroid Build Coastguard Worker      except test_discovery_agent.TestDiscoveryError as e:
100*9e94795aSAndroid Build Coastguard Worker        optimization_rationale = e.message
101*9e94795aSAndroid Build Coastguard Worker        logging.warning(f'Unable to perform test discovery: {optimization_rationale}')
102*9e94795aSAndroid Build Coastguard Worker
103*9e94795aSAndroid Build Coastguard Worker      for target in self.args.extra_targets:
104*9e94795aSAndroid Build Coastguard Worker        if optimization_rationale:
105*9e94795aSAndroid Build Coastguard Worker          get_metrics_agent().report_unoptimized_target(target, optimization_rationale)
106*9e94795aSAndroid Build Coastguard Worker          continue
107*9e94795aSAndroid Build Coastguard Worker        try:
108*9e94795aSAndroid Build Coastguard Worker          regex = r'\b(%s.*)\b' % re.escape(target)
109*9e94795aSAndroid Build Coastguard Worker          if any(re.search(regex, opt) for opt in test_discovery_zip_regexes):
110*9e94795aSAndroid Build Coastguard Worker            get_metrics_agent().report_unoptimized_target(target, 'Test artifact used.')
111*9e94795aSAndroid Build Coastguard Worker            continue
112*9e94795aSAndroid Build Coastguard Worker          get_metrics_agent().report_optimized_target(target)
113*9e94795aSAndroid Build Coastguard Worker        except Exception as e:
114*9e94795aSAndroid Build Coastguard Worker          logging.error(f'unable to parse test discovery output: {repr(e)}')
115*9e94795aSAndroid Build Coastguard Worker
116*9e94795aSAndroid Build Coastguard Worker    for target in preliminary_build_targets:
117*9e94795aSAndroid Build Coastguard Worker      target_optimizer_getter = self.target_optimizations.get(target, None)
118*9e94795aSAndroid Build Coastguard Worker      if not target_optimizer_getter:
119*9e94795aSAndroid Build Coastguard Worker        build_targets.add(target)
120*9e94795aSAndroid Build Coastguard Worker        continue
121*9e94795aSAndroid Build Coastguard Worker
122*9e94795aSAndroid Build Coastguard Worker      target_optimizer = target_optimizer_getter(
123*9e94795aSAndroid Build Coastguard Worker          target, self.build_context, self.args
124*9e94795aSAndroid Build Coastguard Worker      )
125*9e94795aSAndroid Build Coastguard Worker      build_targets.update(target_optimizer.get_build_targets())
126*9e94795aSAndroid Build Coastguard Worker      packaging_commands_getters.append(
127*9e94795aSAndroid Build Coastguard Worker          target_optimizer.get_package_outputs_commands
128*9e94795aSAndroid Build Coastguard Worker      )
129*9e94795aSAndroid Build Coastguard Worker
130*9e94795aSAndroid Build Coastguard Worker    return BuildPlan(build_targets, packaging_commands_getters)
131*9e94795aSAndroid Build Coastguard Worker
132*9e94795aSAndroid Build Coastguard Worker  def _collect_preliminary_build_targets(self):
133*9e94795aSAndroid Build Coastguard Worker    build_targets = set()
134*9e94795aSAndroid Build Coastguard Worker    try:
135*9e94795aSAndroid Build Coastguard Worker      test_discovery_zip_regexes = self._get_test_discovery_zip_regexes()
136*9e94795aSAndroid Build Coastguard Worker      logging.info(f'Discovered test discovery regexes: {test_discovery_zip_regexes}')
137*9e94795aSAndroid Build Coastguard Worker    except test_discovery_agent.TestDiscoveryError as e:
138*9e94795aSAndroid Build Coastguard Worker      optimization_rationale = e.message
139*9e94795aSAndroid Build Coastguard Worker      logging.warning(f'Unable to perform test discovery: {optimization_rationale}')
140*9e94795aSAndroid Build Coastguard Worker
141*9e94795aSAndroid Build Coastguard Worker      for target in self.args.extra_targets:
142*9e94795aSAndroid Build Coastguard Worker        get_metrics_agent().report_unoptimized_target(target, optimization_rationale)
143*9e94795aSAndroid Build Coastguard Worker      return self._legacy_collect_preliminary_build_targets()
144*9e94795aSAndroid Build Coastguard Worker
145*9e94795aSAndroid Build Coastguard Worker    for target in self.args.extra_targets:
146*9e94795aSAndroid Build Coastguard Worker      if target in REQUIRED_BUILD_TARGETS:
147*9e94795aSAndroid Build Coastguard Worker        build_targets.add(target)
148*9e94795aSAndroid Build Coastguard Worker        continue
149*9e94795aSAndroid Build Coastguard Worker
150*9e94795aSAndroid Build Coastguard Worker      regex = r'\b(%s.*)\b' % re.escape(target)
151*9e94795aSAndroid Build Coastguard Worker      for opt in test_discovery_zip_regexes:
152*9e94795aSAndroid Build Coastguard Worker        try:
153*9e94795aSAndroid Build Coastguard Worker          if re.search(regex, opt):
154*9e94795aSAndroid Build Coastguard Worker            get_metrics_agent().report_unoptimized_target(target, 'Test artifact used.')
155*9e94795aSAndroid Build Coastguard Worker            build_targets.add(target)
156*9e94795aSAndroid Build Coastguard Worker            continue
157*9e94795aSAndroid Build Coastguard Worker          get_metrics_agent().report_optimized_target(target)
158*9e94795aSAndroid Build Coastguard Worker        except Exception as e:
159*9e94795aSAndroid Build Coastguard Worker          # In case of exception report as unoptimized
160*9e94795aSAndroid Build Coastguard Worker          build_targets.add(target)
161*9e94795aSAndroid Build Coastguard Worker          get_metrics_agent().report_unoptimized_target(target, f'Error in parsing test discovery output for {target}: {repr(e)}')
162*9e94795aSAndroid Build Coastguard Worker          logging.error(f'unable to parse test discovery output: {repr(e)}')
163*9e94795aSAndroid Build Coastguard Worker
164*9e94795aSAndroid Build Coastguard Worker    return build_targets
165*9e94795aSAndroid Build Coastguard Worker
166*9e94795aSAndroid Build Coastguard Worker  def _legacy_collect_preliminary_build_targets(self):
167*9e94795aSAndroid Build Coastguard Worker    build_targets = set()
168*9e94795aSAndroid Build Coastguard Worker    for target in self.args.extra_targets:
169*9e94795aSAndroid Build Coastguard Worker      if self._unused_target_exclusion_enabled(
170*9e94795aSAndroid Build Coastguard Worker          target
171*9e94795aSAndroid Build Coastguard Worker      ) and not self.build_context.build_target_used(target):
172*9e94795aSAndroid Build Coastguard Worker        continue
173*9e94795aSAndroid Build Coastguard Worker
174*9e94795aSAndroid Build Coastguard Worker      build_targets.add(target)
175*9e94795aSAndroid Build Coastguard Worker    return build_targets
176*9e94795aSAndroid Build Coastguard Worker
177*9e94795aSAndroid Build Coastguard Worker  def _unused_target_exclusion_enabled(self, target: str) -> bool:
178*9e94795aSAndroid Build Coastguard Worker    return (
179*9e94795aSAndroid Build Coastguard Worker        f'{target}_unused_exclusion'
180*9e94795aSAndroid Build Coastguard Worker        in self.build_context.enabled_build_features
181*9e94795aSAndroid Build Coastguard Worker    )
182*9e94795aSAndroid Build Coastguard Worker
183*9e94795aSAndroid Build Coastguard Worker  def _get_test_discovery_zip_regexes(self) -> set[str]:
184*9e94795aSAndroid Build Coastguard Worker    build_target_regexes = set()
185*9e94795aSAndroid Build Coastguard Worker    for test_info in self.build_context.test_infos:
186*9e94795aSAndroid Build Coastguard Worker      tf_command = self._build_tf_command(test_info)
187*9e94795aSAndroid Build Coastguard Worker      discovery_agent = test_discovery_agent.TestDiscoveryAgent(tradefed_args=tf_command)
188*9e94795aSAndroid Build Coastguard Worker      for regex in discovery_agent.discover_test_zip_regexes():
189*9e94795aSAndroid Build Coastguard Worker        build_target_regexes.add(regex)
190*9e94795aSAndroid Build Coastguard Worker    return build_target_regexes
191*9e94795aSAndroid Build Coastguard Worker
192*9e94795aSAndroid Build Coastguard Worker
193*9e94795aSAndroid Build Coastguard Worker  def _build_tf_command(self, test_info) -> list[str]:
194*9e94795aSAndroid Build Coastguard Worker    command = [test_info.command]
195*9e94795aSAndroid Build Coastguard Worker    for extra_option in test_info.extra_options:
196*9e94795aSAndroid Build Coastguard Worker      if not extra_option.get('key'):
197*9e94795aSAndroid Build Coastguard Worker        continue
198*9e94795aSAndroid Build Coastguard Worker      arg_key = '--' + extra_option.get('key')
199*9e94795aSAndroid Build Coastguard Worker      if arg_key == '--build-id':
200*9e94795aSAndroid Build Coastguard Worker        command.append(arg_key)
201*9e94795aSAndroid Build Coastguard Worker        command.append(os.environ.get('BUILD_NUMBER'))
202*9e94795aSAndroid Build Coastguard Worker        continue
203*9e94795aSAndroid Build Coastguard Worker      if extra_option.get('values'):
204*9e94795aSAndroid Build Coastguard Worker        for value in extra_option.get('values'):
205*9e94795aSAndroid Build Coastguard Worker          command.append(arg_key)
206*9e94795aSAndroid Build Coastguard Worker          command.append(value)
207*9e94795aSAndroid Build Coastguard Worker      else:
208*9e94795aSAndroid Build Coastguard Worker        command.append(arg_key)
209*9e94795aSAndroid Build Coastguard Worker
210*9e94795aSAndroid Build Coastguard Worker    return command
211*9e94795aSAndroid Build Coastguard Worker
212*9e94795aSAndroid Build Coastguard Worker@dataclass(frozen=True)
213*9e94795aSAndroid Build Coastguard Workerclass BuildPlan:
214*9e94795aSAndroid Build Coastguard Worker  build_targets: set[str]
215*9e94795aSAndroid Build Coastguard Worker  packaging_commands_getters: list[Callable[[], list[list[str]]]]
216*9e94795aSAndroid Build Coastguard Worker
217*9e94795aSAndroid Build Coastguard Worker
218*9e94795aSAndroid Build Coastguard Workerdef build_test_suites(argv: list[str]) -> int:
219*9e94795aSAndroid Build Coastguard Worker  """Builds all test suites passed in, optimizing based on the build_context content.
220*9e94795aSAndroid Build Coastguard Worker
221*9e94795aSAndroid Build Coastguard Worker  Args:
222*9e94795aSAndroid Build Coastguard Worker    argv: The command line arguments passed in.
223*9e94795aSAndroid Build Coastguard Worker
224*9e94795aSAndroid Build Coastguard Worker  Returns:
225*9e94795aSAndroid Build Coastguard Worker    The exit code of the build.
226*9e94795aSAndroid Build Coastguard Worker  """
227*9e94795aSAndroid Build Coastguard Worker  get_metrics_agent().analysis_start()
228*9e94795aSAndroid Build Coastguard Worker  try:
229*9e94795aSAndroid Build Coastguard Worker    args = parse_args(argv)
230*9e94795aSAndroid Build Coastguard Worker    check_required_env()
231*9e94795aSAndroid Build Coastguard Worker    build_context = BuildContext(load_build_context())
232*9e94795aSAndroid Build Coastguard Worker    build_planner = BuildPlanner(
233*9e94795aSAndroid Build Coastguard Worker        build_context, args, optimized_targets.OPTIMIZED_BUILD_TARGETS
234*9e94795aSAndroid Build Coastguard Worker    )
235*9e94795aSAndroid Build Coastguard Worker    build_plan = build_planner.create_build_plan()
236*9e94795aSAndroid Build Coastguard Worker  except:
237*9e94795aSAndroid Build Coastguard Worker    raise
238*9e94795aSAndroid Build Coastguard Worker  finally:
239*9e94795aSAndroid Build Coastguard Worker    get_metrics_agent().analysis_end()
240*9e94795aSAndroid Build Coastguard Worker
241*9e94795aSAndroid Build Coastguard Worker  try:
242*9e94795aSAndroid Build Coastguard Worker    execute_build_plan(build_plan)
243*9e94795aSAndroid Build Coastguard Worker  except BuildFailureError as e:
244*9e94795aSAndroid Build Coastguard Worker    logging.error('Build command failed! Check build_log for details.')
245*9e94795aSAndroid Build Coastguard Worker    return e.return_code
246*9e94795aSAndroid Build Coastguard Worker  finally:
247*9e94795aSAndroid Build Coastguard Worker    get_metrics_agent().end_reporting()
248*9e94795aSAndroid Build Coastguard Worker
249*9e94795aSAndroid Build Coastguard Worker  return 0
250*9e94795aSAndroid Build Coastguard Worker
251*9e94795aSAndroid Build Coastguard Worker
252*9e94795aSAndroid Build Coastguard Workerdef parse_args(argv: list[str]) -> argparse.Namespace:
253*9e94795aSAndroid Build Coastguard Worker  argparser = argparse.ArgumentParser()
254*9e94795aSAndroid Build Coastguard Worker
255*9e94795aSAndroid Build Coastguard Worker  argparser.add_argument(
256*9e94795aSAndroid Build Coastguard Worker      'extra_targets', nargs='*', help='Extra test suites to build.'
257*9e94795aSAndroid Build Coastguard Worker  )
258*9e94795aSAndroid Build Coastguard Worker  argparser.add_argument(
259*9e94795aSAndroid Build Coastguard Worker      '--device-build',
260*9e94795aSAndroid Build Coastguard Worker      action='store_true',
261*9e94795aSAndroid Build Coastguard Worker      help='Flag to indicate running a device build.',
262*9e94795aSAndroid Build Coastguard Worker  )
263*9e94795aSAndroid Build Coastguard Worker
264*9e94795aSAndroid Build Coastguard Worker  return argparser.parse_args(argv)
265*9e94795aSAndroid Build Coastguard Worker
266*9e94795aSAndroid Build Coastguard Worker
267*9e94795aSAndroid Build Coastguard Workerdef check_required_env():
268*9e94795aSAndroid Build Coastguard Worker  """Check for required env vars.
269*9e94795aSAndroid Build Coastguard Worker
270*9e94795aSAndroid Build Coastguard Worker  Raises:
271*9e94795aSAndroid Build Coastguard Worker    RuntimeError: If any required env vars are not found.
272*9e94795aSAndroid Build Coastguard Worker  """
273*9e94795aSAndroid Build Coastguard Worker  missing_env_vars = sorted(v for v in REQUIRED_ENV_VARS if v not in os.environ)
274*9e94795aSAndroid Build Coastguard Worker
275*9e94795aSAndroid Build Coastguard Worker  if not missing_env_vars:
276*9e94795aSAndroid Build Coastguard Worker    return
277*9e94795aSAndroid Build Coastguard Worker
278*9e94795aSAndroid Build Coastguard Worker  t = ','.join(missing_env_vars)
279*9e94795aSAndroid Build Coastguard Worker  raise Error(f'Missing required environment variables: {t}')
280*9e94795aSAndroid Build Coastguard Worker
281*9e94795aSAndroid Build Coastguard Worker
282*9e94795aSAndroid Build Coastguard Workerdef load_build_context():
283*9e94795aSAndroid Build Coastguard Worker  build_context_path = pathlib.Path(os.environ.get('BUILD_CONTEXT', ''))
284*9e94795aSAndroid Build Coastguard Worker  if build_context_path.is_file():
285*9e94795aSAndroid Build Coastguard Worker    try:
286*9e94795aSAndroid Build Coastguard Worker      with open(build_context_path, 'r') as f:
287*9e94795aSAndroid Build Coastguard Worker        return json.load(f)
288*9e94795aSAndroid Build Coastguard Worker    except json.decoder.JSONDecodeError as e:
289*9e94795aSAndroid Build Coastguard Worker      raise Error(f'Failed to load JSON file: {build_context_path}')
290*9e94795aSAndroid Build Coastguard Worker
291*9e94795aSAndroid Build Coastguard Worker  logging.info('No BUILD_CONTEXT found, skipping optimizations.')
292*9e94795aSAndroid Build Coastguard Worker  return empty_build_context()
293*9e94795aSAndroid Build Coastguard Worker
294*9e94795aSAndroid Build Coastguard Worker
295*9e94795aSAndroid Build Coastguard Workerdef empty_build_context():
296*9e94795aSAndroid Build Coastguard Worker  return {'enabledBuildFeatures': []}
297*9e94795aSAndroid Build Coastguard Worker
298*9e94795aSAndroid Build Coastguard Worker
299*9e94795aSAndroid Build Coastguard Workerdef execute_build_plan(build_plan: BuildPlan):
300*9e94795aSAndroid Build Coastguard Worker  build_command = []
301*9e94795aSAndroid Build Coastguard Worker  build_command.append(get_top().joinpath(SOONG_UI_EXE_REL_PATH))
302*9e94795aSAndroid Build Coastguard Worker  build_command.append('--make-mode')
303*9e94795aSAndroid Build Coastguard Worker  build_command.extend(build_plan.build_targets)
304*9e94795aSAndroid Build Coastguard Worker
305*9e94795aSAndroid Build Coastguard Worker  try:
306*9e94795aSAndroid Build Coastguard Worker    run_command(build_command)
307*9e94795aSAndroid Build Coastguard Worker  except subprocess.CalledProcessError as e:
308*9e94795aSAndroid Build Coastguard Worker    raise BuildFailureError(e.returncode) from e
309*9e94795aSAndroid Build Coastguard Worker
310*9e94795aSAndroid Build Coastguard Worker  get_metrics_agent().packaging_start()
311*9e94795aSAndroid Build Coastguard Worker  try:
312*9e94795aSAndroid Build Coastguard Worker    for packaging_commands_getter in build_plan.packaging_commands_getters:
313*9e94795aSAndroid Build Coastguard Worker      for packaging_command in packaging_commands_getter():
314*9e94795aSAndroid Build Coastguard Worker        run_command(packaging_command)
315*9e94795aSAndroid Build Coastguard Worker  except subprocess.CalledProcessError as e:
316*9e94795aSAndroid Build Coastguard Worker    raise BuildFailureError(e.returncode) from e
317*9e94795aSAndroid Build Coastguard Worker  finally:
318*9e94795aSAndroid Build Coastguard Worker    get_metrics_agent().packaging_end()
319*9e94795aSAndroid Build Coastguard Worker
320*9e94795aSAndroid Build Coastguard Worker
321*9e94795aSAndroid Build Coastguard Workerdef get_top() -> pathlib.Path:
322*9e94795aSAndroid Build Coastguard Worker  return pathlib.Path(os.environ['TOP'])
323*9e94795aSAndroid Build Coastguard Worker
324*9e94795aSAndroid Build Coastguard Worker
325*9e94795aSAndroid Build Coastguard Workerdef run_command(args: list[str], stdout=None):
326*9e94795aSAndroid Build Coastguard Worker  subprocess.run(args=args, check=True, stdout=stdout)
327*9e94795aSAndroid Build Coastguard Worker
328*9e94795aSAndroid Build Coastguard Worker
329*9e94795aSAndroid Build Coastguard Workerdef get_metrics_agent():
330*9e94795aSAndroid Build Coastguard Worker  return metrics_agent.MetricsAgent.instance()
331*9e94795aSAndroid Build Coastguard Worker
332*9e94795aSAndroid Build Coastguard Worker
333*9e94795aSAndroid Build Coastguard Workerdef main(argv):
334*9e94795aSAndroid Build Coastguard Worker  dist_dir = os.environ.get('DIST_DIR')
335*9e94795aSAndroid Build Coastguard Worker  if dist_dir:
336*9e94795aSAndroid Build Coastguard Worker    log_file = pathlib.Path(dist_dir) / LOG_PATH
337*9e94795aSAndroid Build Coastguard Worker    logging.basicConfig(
338*9e94795aSAndroid Build Coastguard Worker        level=logging.DEBUG,
339*9e94795aSAndroid Build Coastguard Worker        format='%(asctime)s %(levelname)s %(message)s',
340*9e94795aSAndroid Build Coastguard Worker        filename=log_file,
341*9e94795aSAndroid Build Coastguard Worker    )
342*9e94795aSAndroid Build Coastguard Worker  sys.exit(build_test_suites(argv))
343*9e94795aSAndroid Build Coastguard Worker
344*9e94795aSAndroid Build Coastguard Worker
345*9e94795aSAndroid Build Coastguard Workerif __name__ == '__main__':
346*9e94795aSAndroid Build Coastguard Worker  main(sys.argv[1:])
347