xref: /aosp_15_r20/tools/asuite/atest/bazel_mode.py (revision c2e18aaa1096c836b086f94603d04f4eb9cf37f5)
1*c2e18aaaSAndroid Build Coastguard Worker# Copyright 2021, The Android Open Source Project
2*c2e18aaaSAndroid Build Coastguard Worker#
3*c2e18aaaSAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License");
4*c2e18aaaSAndroid Build Coastguard Worker# you may not use this file except in compliance with the License.
5*c2e18aaaSAndroid Build Coastguard Worker# You may obtain a copy of the License at
6*c2e18aaaSAndroid Build Coastguard Worker#
7*c2e18aaaSAndroid Build Coastguard Worker#     http://www.apache.org/licenses/LICENSE-2.0
8*c2e18aaaSAndroid Build Coastguard Worker#
9*c2e18aaaSAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software
10*c2e18aaaSAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS,
11*c2e18aaaSAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12*c2e18aaaSAndroid Build Coastguard Worker# See the License for the specific language governing permissions and
13*c2e18aaaSAndroid Build Coastguard Worker# limitations under the License.
14*c2e18aaaSAndroid Build Coastguard Worker
15*c2e18aaaSAndroid Build Coastguard Worker"""Implementation of Atest's Bazel mode.
16*c2e18aaaSAndroid Build Coastguard Worker
17*c2e18aaaSAndroid Build Coastguard WorkerBazel mode runs tests using Bazel by generating a synthetic workspace that
18*c2e18aaaSAndroid Build Coastguard Workercontains test targets. Using Bazel allows Atest to leverage features such as
19*c2e18aaaSAndroid Build Coastguard Workersandboxing, caching, and remote execution.
20*c2e18aaaSAndroid Build Coastguard Worker"""
21*c2e18aaaSAndroid Build Coastguard Worker# pylint: disable=missing-function-docstring
22*c2e18aaaSAndroid Build Coastguard Worker# pylint: disable=missing-class-docstring
23*c2e18aaaSAndroid Build Coastguard Worker# pylint: disable=too-many-lines
24*c2e18aaaSAndroid Build Coastguard Worker
25*c2e18aaaSAndroid Build Coastguard Workerfrom __future__ import annotations
26*c2e18aaaSAndroid Build Coastguard Worker
27*c2e18aaaSAndroid Build Coastguard Workerfrom abc import ABC, abstractmethod
28*c2e18aaaSAndroid Build Coastguard Workerimport argparse
29*c2e18aaaSAndroid Build Coastguard Workerimport atexit
30*c2e18aaaSAndroid Build Coastguard Workerfrom collections import OrderedDict, defaultdict, deque
31*c2e18aaaSAndroid Build Coastguard Workerfrom collections.abc import Iterable
32*c2e18aaaSAndroid Build Coastguard Workerimport contextlib
33*c2e18aaaSAndroid Build Coastguard Workerimport dataclasses
34*c2e18aaaSAndroid Build Coastguard Workerimport enum
35*c2e18aaaSAndroid Build Coastguard Workerimport functools
36*c2e18aaaSAndroid Build Coastguard Workerimport importlib.resources
37*c2e18aaaSAndroid Build Coastguard Workerimport logging
38*c2e18aaaSAndroid Build Coastguard Workerimport os
39*c2e18aaaSAndroid Build Coastguard Workerimport pathlib
40*c2e18aaaSAndroid Build Coastguard Workerimport re
41*c2e18aaaSAndroid Build Coastguard Workerimport shlex
42*c2e18aaaSAndroid Build Coastguard Workerimport shutil
43*c2e18aaaSAndroid Build Coastguard Workerimport subprocess
44*c2e18aaaSAndroid Build Coastguard Workerimport tempfile
45*c2e18aaaSAndroid Build Coastguard Workerimport time
46*c2e18aaaSAndroid Build Coastguard Workerfrom types import MappingProxyType
47*c2e18aaaSAndroid Build Coastguard Workerfrom typing import Any, Callable, Dict, IO, List, Set, Tuple
48*c2e18aaaSAndroid Build Coastguard Workerimport warnings
49*c2e18aaaSAndroid Build Coastguard Workerfrom xml.etree import ElementTree as ET
50*c2e18aaaSAndroid Build Coastguard Worker
51*c2e18aaaSAndroid Build Coastguard Workerfrom atest import atest_configs
52*c2e18aaaSAndroid Build Coastguard Workerfrom atest import atest_utils
53*c2e18aaaSAndroid Build Coastguard Workerfrom atest import constants
54*c2e18aaaSAndroid Build Coastguard Workerfrom atest import module_info
55*c2e18aaaSAndroid Build Coastguard Workerfrom atest.atest_enum import DetectType, ExitCode
56*c2e18aaaSAndroid Build Coastguard Workerfrom atest.metrics import metrics
57*c2e18aaaSAndroid Build Coastguard Workerfrom atest.proto import file_md5_pb2
58*c2e18aaaSAndroid Build Coastguard Workerfrom atest.test_finders import test_finder_base
59*c2e18aaaSAndroid Build Coastguard Workerfrom atest.test_finders import test_info
60*c2e18aaaSAndroid Build Coastguard Workerfrom atest.test_runners import atest_tf_test_runner as tfr
61*c2e18aaaSAndroid Build Coastguard Workerfrom atest.test_runners import test_runner_base as trb
62*c2e18aaaSAndroid Build Coastguard Workerfrom google.protobuf.message import DecodeError
63*c2e18aaaSAndroid Build Coastguard Worker
64*c2e18aaaSAndroid Build Coastguard Worker
65*c2e18aaaSAndroid Build Coastguard WorkerJDK_PACKAGE_NAME = 'prebuilts/robolectric_jdk'
66*c2e18aaaSAndroid Build Coastguard WorkerJDK_NAME = 'jdk'
67*c2e18aaaSAndroid Build Coastguard WorkerROBOLECTRIC_CONFIG = 'build/make/core/robolectric_test_config_template.xml'
68*c2e18aaaSAndroid Build Coastguard Worker
69*c2e18aaaSAndroid Build Coastguard WorkerBAZEL_TEST_LOGS_DIR_NAME = 'bazel-testlogs'
70*c2e18aaaSAndroid Build Coastguard WorkerTEST_OUTPUT_DIR_NAME = 'test.outputs'
71*c2e18aaaSAndroid Build Coastguard WorkerTEST_OUTPUT_ZIP_NAME = 'outputs.zip'
72*c2e18aaaSAndroid Build Coastguard Worker
73*c2e18aaaSAndroid Build Coastguard Worker_BAZEL_WORKSPACE_DIR = 'atest_bazel_workspace'
74*c2e18aaaSAndroid Build Coastguard Worker_SUPPORTED_BAZEL_ARGS = MappingProxyType({
75*c2e18aaaSAndroid Build Coastguard Worker    # https://docs.bazel.build/versions/main/command-line-reference.html#flag--runs_per_test
76*c2e18aaaSAndroid Build Coastguard Worker    constants.ITERATIONS: lambda arg_value: [
77*c2e18aaaSAndroid Build Coastguard Worker        f'--runs_per_test={str(arg_value)}'
78*c2e18aaaSAndroid Build Coastguard Worker    ],
79*c2e18aaaSAndroid Build Coastguard Worker    # https://docs.bazel.build/versions/main/command-line-reference.html#flag--flaky_test_attempts
80*c2e18aaaSAndroid Build Coastguard Worker    constants.RETRY_ANY_FAILURE: lambda arg_value: [
81*c2e18aaaSAndroid Build Coastguard Worker        f'--flaky_test_attempts={str(arg_value)}'
82*c2e18aaaSAndroid Build Coastguard Worker    ],
83*c2e18aaaSAndroid Build Coastguard Worker    # https://docs.bazel.build/versions/main/command-line-reference.html#flag--test_output
84*c2e18aaaSAndroid Build Coastguard Worker    constants.VERBOSE: (
85*c2e18aaaSAndroid Build Coastguard Worker        lambda arg_value: ['--test_output=all'] if arg_value else []
86*c2e18aaaSAndroid Build Coastguard Worker    ),
87*c2e18aaaSAndroid Build Coastguard Worker    constants.BAZEL_ARG: lambda arg_value: [
88*c2e18aaaSAndroid Build Coastguard Worker        item for sublist in arg_value for item in sublist
89*c2e18aaaSAndroid Build Coastguard Worker    ],
90*c2e18aaaSAndroid Build Coastguard Worker})
91*c2e18aaaSAndroid Build Coastguard Worker
92*c2e18aaaSAndroid Build Coastguard Worker# Maps Bazel configuration names to Soong variant names.
93*c2e18aaaSAndroid Build Coastguard Worker_CONFIG_TO_VARIANT = {
94*c2e18aaaSAndroid Build Coastguard Worker    'host': 'host',
95*c2e18aaaSAndroid Build Coastguard Worker    'device': 'target',
96*c2e18aaaSAndroid Build Coastguard Worker}
97*c2e18aaaSAndroid Build Coastguard Worker
98*c2e18aaaSAndroid Build Coastguard Worker
99*c2e18aaaSAndroid Build Coastguard Workerclass AbortRunException(Exception):
100*c2e18aaaSAndroid Build Coastguard Worker  pass
101*c2e18aaaSAndroid Build Coastguard Worker
102*c2e18aaaSAndroid Build Coastguard Worker
103*c2e18aaaSAndroid Build Coastguard Worker@enum.unique
104*c2e18aaaSAndroid Build Coastguard Workerclass Features(enum.Enum):
105*c2e18aaaSAndroid Build Coastguard Worker  NULL_FEATURE = ('--null-feature', 'Enables a no-action feature.', True)
106*c2e18aaaSAndroid Build Coastguard Worker  EXPERIMENTAL_DEVICE_DRIVEN_TEST = (
107*c2e18aaaSAndroid Build Coastguard Worker      '--experimental-device-driven-test',
108*c2e18aaaSAndroid Build Coastguard Worker      'Enables running device-driven tests in Bazel mode.',
109*c2e18aaaSAndroid Build Coastguard Worker      True,
110*c2e18aaaSAndroid Build Coastguard Worker  )
111*c2e18aaaSAndroid Build Coastguard Worker  EXPERIMENTAL_REMOTE_AVD = (
112*c2e18aaaSAndroid Build Coastguard Worker      '--experimental-remote-avd',
113*c2e18aaaSAndroid Build Coastguard Worker      'Enables running device-driven tests in remote AVD.',
114*c2e18aaaSAndroid Build Coastguard Worker      False,
115*c2e18aaaSAndroid Build Coastguard Worker  )
116*c2e18aaaSAndroid Build Coastguard Worker  EXPERIMENTAL_BES_PUBLISH = (
117*c2e18aaaSAndroid Build Coastguard Worker      '--experimental-bes-publish',
118*c2e18aaaSAndroid Build Coastguard Worker      'Upload test results via BES in Bazel mode.',
119*c2e18aaaSAndroid Build Coastguard Worker      False,
120*c2e18aaaSAndroid Build Coastguard Worker  )
121*c2e18aaaSAndroid Build Coastguard Worker  EXPERIMENTAL_JAVA_RUNTIME_DEPENDENCIES = (
122*c2e18aaaSAndroid Build Coastguard Worker      '--experimental-java-runtime-dependencies',
123*c2e18aaaSAndroid Build Coastguard Worker      (
124*c2e18aaaSAndroid Build Coastguard Worker          'Mirrors Soong Java `libs` and `static_libs` as Bazel target '
125*c2e18aaaSAndroid Build Coastguard Worker          'dependencies in the generated workspace. Tradefed test rules use '
126*c2e18aaaSAndroid Build Coastguard Worker          'these dependencies to set up the execution environment and ensure '
127*c2e18aaaSAndroid Build Coastguard Worker          'that all transitive runtime dependencies are present.'
128*c2e18aaaSAndroid Build Coastguard Worker      ),
129*c2e18aaaSAndroid Build Coastguard Worker      True,
130*c2e18aaaSAndroid Build Coastguard Worker  )
131*c2e18aaaSAndroid Build Coastguard Worker  EXPERIMENTAL_REMOTE = (
132*c2e18aaaSAndroid Build Coastguard Worker      '--experimental-remote',
133*c2e18aaaSAndroid Build Coastguard Worker      'Use Bazel remote execution and caching where supported.',
134*c2e18aaaSAndroid Build Coastguard Worker      False,
135*c2e18aaaSAndroid Build Coastguard Worker  )
136*c2e18aaaSAndroid Build Coastguard Worker  EXPERIMENTAL_HOST_DRIVEN_TEST = (
137*c2e18aaaSAndroid Build Coastguard Worker      '--experimental-host-driven-test',
138*c2e18aaaSAndroid Build Coastguard Worker      'Enables running host-driven device tests in Bazel mode.',
139*c2e18aaaSAndroid Build Coastguard Worker      True,
140*c2e18aaaSAndroid Build Coastguard Worker  )
141*c2e18aaaSAndroid Build Coastguard Worker  EXPERIMENTAL_ROBOLECTRIC_TEST = (
142*c2e18aaaSAndroid Build Coastguard Worker      '--experimental-robolectric-test',
143*c2e18aaaSAndroid Build Coastguard Worker      'Enables running Robolectric tests in Bazel mode.',
144*c2e18aaaSAndroid Build Coastguard Worker      True,
145*c2e18aaaSAndroid Build Coastguard Worker  )
146*c2e18aaaSAndroid Build Coastguard Worker  NO_BAZEL_DETAILED_SUMMARY = (
147*c2e18aaaSAndroid Build Coastguard Worker      '--no-bazel-detailed-summary',
148*c2e18aaaSAndroid Build Coastguard Worker      'Disables printing detailed summary of Bazel test results.',
149*c2e18aaaSAndroid Build Coastguard Worker      False,
150*c2e18aaaSAndroid Build Coastguard Worker  )
151*c2e18aaaSAndroid Build Coastguard Worker
152*c2e18aaaSAndroid Build Coastguard Worker  def __init__(self, arg_flag, description, affects_workspace):
153*c2e18aaaSAndroid Build Coastguard Worker    self._arg_flag = arg_flag
154*c2e18aaaSAndroid Build Coastguard Worker    self._description = description
155*c2e18aaaSAndroid Build Coastguard Worker    self.affects_workspace = affects_workspace
156*c2e18aaaSAndroid Build Coastguard Worker
157*c2e18aaaSAndroid Build Coastguard Worker  @property
158*c2e18aaaSAndroid Build Coastguard Worker  def arg_flag(self):
159*c2e18aaaSAndroid Build Coastguard Worker    return self._arg_flag
160*c2e18aaaSAndroid Build Coastguard Worker
161*c2e18aaaSAndroid Build Coastguard Worker  @property
162*c2e18aaaSAndroid Build Coastguard Worker  def description(self):
163*c2e18aaaSAndroid Build Coastguard Worker    return self._description
164*c2e18aaaSAndroid Build Coastguard Worker
165*c2e18aaaSAndroid Build Coastguard Worker
166*c2e18aaaSAndroid Build Coastguard Workerdef add_parser_arguments(parser: argparse.ArgumentParser, dest: str):
167*c2e18aaaSAndroid Build Coastguard Worker  for _, member in Features.__members__.items():
168*c2e18aaaSAndroid Build Coastguard Worker    parser.add_argument(
169*c2e18aaaSAndroid Build Coastguard Worker        member.arg_flag,
170*c2e18aaaSAndroid Build Coastguard Worker        action='append_const',
171*c2e18aaaSAndroid Build Coastguard Worker        const=member,
172*c2e18aaaSAndroid Build Coastguard Worker        dest=dest,
173*c2e18aaaSAndroid Build Coastguard Worker        help=member.description,
174*c2e18aaaSAndroid Build Coastguard Worker    )
175*c2e18aaaSAndroid Build Coastguard Worker
176*c2e18aaaSAndroid Build Coastguard Worker
177*c2e18aaaSAndroid Build Coastguard Workerdef get_bazel_workspace_dir() -> pathlib.Path:
178*c2e18aaaSAndroid Build Coastguard Worker  return atest_utils.get_build_out_dir(_BAZEL_WORKSPACE_DIR)
179*c2e18aaaSAndroid Build Coastguard Worker
180*c2e18aaaSAndroid Build Coastguard Worker
181*c2e18aaaSAndroid Build Coastguard Workerdef generate_bazel_workspace(
182*c2e18aaaSAndroid Build Coastguard Worker    mod_info: module_info.ModuleInfo, enabled_features: Set[Features] = None
183*c2e18aaaSAndroid Build Coastguard Worker):
184*c2e18aaaSAndroid Build Coastguard Worker  """Generate or update the Bazel workspace used for running tests."""
185*c2e18aaaSAndroid Build Coastguard Worker
186*c2e18aaaSAndroid Build Coastguard Worker  start = time.time()
187*c2e18aaaSAndroid Build Coastguard Worker  src_root_path = pathlib.Path(os.environ.get(constants.ANDROID_BUILD_TOP))
188*c2e18aaaSAndroid Build Coastguard Worker  workspace_path = get_bazel_workspace_dir()
189*c2e18aaaSAndroid Build Coastguard Worker  resource_manager = ResourceManager(
190*c2e18aaaSAndroid Build Coastguard Worker      src_root_path=src_root_path,
191*c2e18aaaSAndroid Build Coastguard Worker      resource_root_path=_get_resource_root(),
192*c2e18aaaSAndroid Build Coastguard Worker      product_out_path=pathlib.Path(
193*c2e18aaaSAndroid Build Coastguard Worker          os.environ.get(constants.ANDROID_PRODUCT_OUT)
194*c2e18aaaSAndroid Build Coastguard Worker      ),
195*c2e18aaaSAndroid Build Coastguard Worker      md5_checksum_file_path=workspace_path.joinpath('workspace_md5_checksum'),
196*c2e18aaaSAndroid Build Coastguard Worker  )
197*c2e18aaaSAndroid Build Coastguard Worker  jdk_path = _read_robolectric_jdk_path(
198*c2e18aaaSAndroid Build Coastguard Worker      resource_manager.get_src_file_path(ROBOLECTRIC_CONFIG, True)
199*c2e18aaaSAndroid Build Coastguard Worker  )
200*c2e18aaaSAndroid Build Coastguard Worker
201*c2e18aaaSAndroid Build Coastguard Worker  workspace_generator = WorkspaceGenerator(
202*c2e18aaaSAndroid Build Coastguard Worker      resource_manager=resource_manager,
203*c2e18aaaSAndroid Build Coastguard Worker      workspace_out_path=workspace_path,
204*c2e18aaaSAndroid Build Coastguard Worker      host_out_path=pathlib.Path(os.environ.get(constants.ANDROID_HOST_OUT)),
205*c2e18aaaSAndroid Build Coastguard Worker      build_out_dir=atest_utils.get_build_out_dir(),
206*c2e18aaaSAndroid Build Coastguard Worker      mod_info=mod_info,
207*c2e18aaaSAndroid Build Coastguard Worker      jdk_path=jdk_path,
208*c2e18aaaSAndroid Build Coastguard Worker      enabled_features=enabled_features,
209*c2e18aaaSAndroid Build Coastguard Worker  )
210*c2e18aaaSAndroid Build Coastguard Worker  workspace_generator.generate()
211*c2e18aaaSAndroid Build Coastguard Worker
212*c2e18aaaSAndroid Build Coastguard Worker  metrics.LocalDetectEvent(
213*c2e18aaaSAndroid Build Coastguard Worker      detect_type=DetectType.BAZEL_WORKSPACE_GENERATE_TIME,
214*c2e18aaaSAndroid Build Coastguard Worker      result=int(time.time() - start),
215*c2e18aaaSAndroid Build Coastguard Worker  )
216*c2e18aaaSAndroid Build Coastguard Worker
217*c2e18aaaSAndroid Build Coastguard Worker
218*c2e18aaaSAndroid Build Coastguard Workerdef get_default_build_metadata():
219*c2e18aaaSAndroid Build Coastguard Worker  return BuildMetadata(
220*c2e18aaaSAndroid Build Coastguard Worker      atest_utils.get_manifest_branch(), atest_utils.get_build_target()
221*c2e18aaaSAndroid Build Coastguard Worker  )
222*c2e18aaaSAndroid Build Coastguard Worker
223*c2e18aaaSAndroid Build Coastguard Worker
224*c2e18aaaSAndroid Build Coastguard Workerclass ResourceManager:
225*c2e18aaaSAndroid Build Coastguard Worker  """Class for managing files required to generate a Bazel Workspace."""
226*c2e18aaaSAndroid Build Coastguard Worker
227*c2e18aaaSAndroid Build Coastguard Worker  def __init__(
228*c2e18aaaSAndroid Build Coastguard Worker      self,
229*c2e18aaaSAndroid Build Coastguard Worker      src_root_path: pathlib.Path,
230*c2e18aaaSAndroid Build Coastguard Worker      resource_root_path: pathlib.Path,
231*c2e18aaaSAndroid Build Coastguard Worker      product_out_path: pathlib.Path,
232*c2e18aaaSAndroid Build Coastguard Worker      md5_checksum_file_path: pathlib.Path,
233*c2e18aaaSAndroid Build Coastguard Worker  ):
234*c2e18aaaSAndroid Build Coastguard Worker    self._root_type_to_path = {
235*c2e18aaaSAndroid Build Coastguard Worker        file_md5_pb2.RootType.SRC_ROOT: src_root_path,
236*c2e18aaaSAndroid Build Coastguard Worker        file_md5_pb2.RootType.RESOURCE_ROOT: resource_root_path,
237*c2e18aaaSAndroid Build Coastguard Worker        file_md5_pb2.RootType.ABS_PATH: pathlib.Path(),
238*c2e18aaaSAndroid Build Coastguard Worker        file_md5_pb2.RootType.PRODUCT_OUT: product_out_path,
239*c2e18aaaSAndroid Build Coastguard Worker    }
240*c2e18aaaSAndroid Build Coastguard Worker    self._md5_checksum_file = md5_checksum_file_path
241*c2e18aaaSAndroid Build Coastguard Worker    self._file_checksum_list = file_md5_pb2.FileChecksumList()
242*c2e18aaaSAndroid Build Coastguard Worker
243*c2e18aaaSAndroid Build Coastguard Worker  def get_src_file_path(
244*c2e18aaaSAndroid Build Coastguard Worker      self, rel_path: pathlib.Path = None, affects_workspace: bool = False
245*c2e18aaaSAndroid Build Coastguard Worker  ) -> pathlib.Path:
246*c2e18aaaSAndroid Build Coastguard Worker    """Get the abs file path from the relative path of source_root.
247*c2e18aaaSAndroid Build Coastguard Worker
248*c2e18aaaSAndroid Build Coastguard Worker    Args:
249*c2e18aaaSAndroid Build Coastguard Worker        rel_path: A relative path of the source_root.
250*c2e18aaaSAndroid Build Coastguard Worker        affects_workspace: A boolean of whether the file affects the workspace.
251*c2e18aaaSAndroid Build Coastguard Worker
252*c2e18aaaSAndroid Build Coastguard Worker    Returns:
253*c2e18aaaSAndroid Build Coastguard Worker        A abs path of the file.
254*c2e18aaaSAndroid Build Coastguard Worker    """
255*c2e18aaaSAndroid Build Coastguard Worker    return self._get_file_path(
256*c2e18aaaSAndroid Build Coastguard Worker        file_md5_pb2.RootType.SRC_ROOT, rel_path, affects_workspace
257*c2e18aaaSAndroid Build Coastguard Worker    )
258*c2e18aaaSAndroid Build Coastguard Worker
259*c2e18aaaSAndroid Build Coastguard Worker  def get_resource_file_path(
260*c2e18aaaSAndroid Build Coastguard Worker      self,
261*c2e18aaaSAndroid Build Coastguard Worker      rel_path: pathlib.Path = None,
262*c2e18aaaSAndroid Build Coastguard Worker      affects_workspace: bool = False,
263*c2e18aaaSAndroid Build Coastguard Worker  ) -> pathlib.Path:
264*c2e18aaaSAndroid Build Coastguard Worker    """Get the abs file path from the relative path of resource_root.
265*c2e18aaaSAndroid Build Coastguard Worker
266*c2e18aaaSAndroid Build Coastguard Worker    Args:
267*c2e18aaaSAndroid Build Coastguard Worker        rel_path: A relative path of the resource_root.
268*c2e18aaaSAndroid Build Coastguard Worker        affects_workspace: A boolean of whether the file affects the workspace.
269*c2e18aaaSAndroid Build Coastguard Worker
270*c2e18aaaSAndroid Build Coastguard Worker    Returns:
271*c2e18aaaSAndroid Build Coastguard Worker        A abs path of the file.
272*c2e18aaaSAndroid Build Coastguard Worker    """
273*c2e18aaaSAndroid Build Coastguard Worker    return self._get_file_path(
274*c2e18aaaSAndroid Build Coastguard Worker        file_md5_pb2.RootType.RESOURCE_ROOT, rel_path, affects_workspace
275*c2e18aaaSAndroid Build Coastguard Worker    )
276*c2e18aaaSAndroid Build Coastguard Worker
277*c2e18aaaSAndroid Build Coastguard Worker  def get_product_out_file_path(
278*c2e18aaaSAndroid Build Coastguard Worker      self, rel_path: pathlib.Path = None, affects_workspace: bool = False
279*c2e18aaaSAndroid Build Coastguard Worker  ) -> pathlib.Path:
280*c2e18aaaSAndroid Build Coastguard Worker    """Get the abs file path from the relative path of product out.
281*c2e18aaaSAndroid Build Coastguard Worker
282*c2e18aaaSAndroid Build Coastguard Worker    Args:
283*c2e18aaaSAndroid Build Coastguard Worker        rel_path: A relative path to the product out.
284*c2e18aaaSAndroid Build Coastguard Worker        affects_workspace: A boolean of whether the file affects the workspace.
285*c2e18aaaSAndroid Build Coastguard Worker
286*c2e18aaaSAndroid Build Coastguard Worker    Returns:
287*c2e18aaaSAndroid Build Coastguard Worker        An abs path of the file.
288*c2e18aaaSAndroid Build Coastguard Worker    """
289*c2e18aaaSAndroid Build Coastguard Worker    return self._get_file_path(
290*c2e18aaaSAndroid Build Coastguard Worker        file_md5_pb2.RootType.PRODUCT_OUT, rel_path, affects_workspace
291*c2e18aaaSAndroid Build Coastguard Worker    )
292*c2e18aaaSAndroid Build Coastguard Worker
293*c2e18aaaSAndroid Build Coastguard Worker  def _get_file_path(
294*c2e18aaaSAndroid Build Coastguard Worker      self,
295*c2e18aaaSAndroid Build Coastguard Worker      root_type: file_md5_pb2.RootType,
296*c2e18aaaSAndroid Build Coastguard Worker      rel_path: pathlib.Path,
297*c2e18aaaSAndroid Build Coastguard Worker      affects_workspace: bool = True,
298*c2e18aaaSAndroid Build Coastguard Worker  ) -> pathlib.Path:
299*c2e18aaaSAndroid Build Coastguard Worker    abs_path = self._root_type_to_path[root_type].joinpath(
300*c2e18aaaSAndroid Build Coastguard Worker        rel_path or pathlib.Path()
301*c2e18aaaSAndroid Build Coastguard Worker    )
302*c2e18aaaSAndroid Build Coastguard Worker
303*c2e18aaaSAndroid Build Coastguard Worker    if not affects_workspace:
304*c2e18aaaSAndroid Build Coastguard Worker      return abs_path
305*c2e18aaaSAndroid Build Coastguard Worker
306*c2e18aaaSAndroid Build Coastguard Worker    if abs_path.is_dir():
307*c2e18aaaSAndroid Build Coastguard Worker      for file in abs_path.glob('**/*'):
308*c2e18aaaSAndroid Build Coastguard Worker        self._register_file(root_type, file)
309*c2e18aaaSAndroid Build Coastguard Worker    else:
310*c2e18aaaSAndroid Build Coastguard Worker      self._register_file(root_type, abs_path)
311*c2e18aaaSAndroid Build Coastguard Worker    return abs_path
312*c2e18aaaSAndroid Build Coastguard Worker
313*c2e18aaaSAndroid Build Coastguard Worker  def _register_file(
314*c2e18aaaSAndroid Build Coastguard Worker      self, root_type: file_md5_pb2.RootType, abs_path: pathlib.Path
315*c2e18aaaSAndroid Build Coastguard Worker  ):
316*c2e18aaaSAndroid Build Coastguard Worker    if not abs_path.is_file():
317*c2e18aaaSAndroid Build Coastguard Worker      logging.debug(' ignore %s: not a file.', abs_path)
318*c2e18aaaSAndroid Build Coastguard Worker      return
319*c2e18aaaSAndroid Build Coastguard Worker
320*c2e18aaaSAndroid Build Coastguard Worker    rel_path = abs_path
321*c2e18aaaSAndroid Build Coastguard Worker    if abs_path.is_relative_to(self._root_type_to_path[root_type]):
322*c2e18aaaSAndroid Build Coastguard Worker      rel_path = abs_path.relative_to(self._root_type_to_path[root_type])
323*c2e18aaaSAndroid Build Coastguard Worker
324*c2e18aaaSAndroid Build Coastguard Worker    self._file_checksum_list.file_checksums.append(
325*c2e18aaaSAndroid Build Coastguard Worker        file_md5_pb2.FileChecksum(
326*c2e18aaaSAndroid Build Coastguard Worker            root_type=root_type,
327*c2e18aaaSAndroid Build Coastguard Worker            rel_path=str(rel_path),
328*c2e18aaaSAndroid Build Coastguard Worker            md5sum=atest_utils.md5sum(abs_path),
329*c2e18aaaSAndroid Build Coastguard Worker        )
330*c2e18aaaSAndroid Build Coastguard Worker    )
331*c2e18aaaSAndroid Build Coastguard Worker
332*c2e18aaaSAndroid Build Coastguard Worker  def register_file_with_abs_path(self, abs_path: pathlib.Path):
333*c2e18aaaSAndroid Build Coastguard Worker    """Register a file which affects the workspace.
334*c2e18aaaSAndroid Build Coastguard Worker
335*c2e18aaaSAndroid Build Coastguard Worker    Args:
336*c2e18aaaSAndroid Build Coastguard Worker        abs_path: A abs path of the file.
337*c2e18aaaSAndroid Build Coastguard Worker    """
338*c2e18aaaSAndroid Build Coastguard Worker    self._register_file(file_md5_pb2.RootType.ABS_PATH, abs_path)
339*c2e18aaaSAndroid Build Coastguard Worker
340*c2e18aaaSAndroid Build Coastguard Worker  def save_affects_files_md5(self):
341*c2e18aaaSAndroid Build Coastguard Worker    with open(self._md5_checksum_file, 'wb') as f:
342*c2e18aaaSAndroid Build Coastguard Worker      f.write(self._file_checksum_list.SerializeToString())
343*c2e18aaaSAndroid Build Coastguard Worker
344*c2e18aaaSAndroid Build Coastguard Worker  def check_affects_files_md5(self):
345*c2e18aaaSAndroid Build Coastguard Worker    """Check all affect files are consistent with the actual MD5."""
346*c2e18aaaSAndroid Build Coastguard Worker    if not self._md5_checksum_file.is_file():
347*c2e18aaaSAndroid Build Coastguard Worker      return False
348*c2e18aaaSAndroid Build Coastguard Worker
349*c2e18aaaSAndroid Build Coastguard Worker    with open(self._md5_checksum_file, 'rb') as f:
350*c2e18aaaSAndroid Build Coastguard Worker      file_md5_list = file_md5_pb2.FileChecksumList()
351*c2e18aaaSAndroid Build Coastguard Worker
352*c2e18aaaSAndroid Build Coastguard Worker      try:
353*c2e18aaaSAndroid Build Coastguard Worker        file_md5_list.ParseFromString(f.read())
354*c2e18aaaSAndroid Build Coastguard Worker      except DecodeError:
355*c2e18aaaSAndroid Build Coastguard Worker        atest_utils.print_and_log_warning(
356*c2e18aaaSAndroid Build Coastguard Worker            'Failed to parse the workspace md5 checksum file.'
357*c2e18aaaSAndroid Build Coastguard Worker        )
358*c2e18aaaSAndroid Build Coastguard Worker        return False
359*c2e18aaaSAndroid Build Coastguard Worker
360*c2e18aaaSAndroid Build Coastguard Worker      for file_md5 in file_md5_list.file_checksums:
361*c2e18aaaSAndroid Build Coastguard Worker        abs_path = pathlib.Path(
362*c2e18aaaSAndroid Build Coastguard Worker            self._root_type_to_path[file_md5.root_type]
363*c2e18aaaSAndroid Build Coastguard Worker        ).joinpath(file_md5.rel_path)
364*c2e18aaaSAndroid Build Coastguard Worker        if not abs_path.is_file():
365*c2e18aaaSAndroid Build Coastguard Worker          return False
366*c2e18aaaSAndroid Build Coastguard Worker        if atest_utils.md5sum(abs_path) != file_md5.md5sum:
367*c2e18aaaSAndroid Build Coastguard Worker          return False
368*c2e18aaaSAndroid Build Coastguard Worker      return True
369*c2e18aaaSAndroid Build Coastguard Worker
370*c2e18aaaSAndroid Build Coastguard Worker
371*c2e18aaaSAndroid Build Coastguard Workerclass WorkspaceGenerator:
372*c2e18aaaSAndroid Build Coastguard Worker  """Class for generating a Bazel workspace."""
373*c2e18aaaSAndroid Build Coastguard Worker
374*c2e18aaaSAndroid Build Coastguard Worker  # pylint: disable=too-many-arguments
375*c2e18aaaSAndroid Build Coastguard Worker  def __init__(
376*c2e18aaaSAndroid Build Coastguard Worker      self,
377*c2e18aaaSAndroid Build Coastguard Worker      resource_manager: ResourceManager,
378*c2e18aaaSAndroid Build Coastguard Worker      workspace_out_path: pathlib.Path,
379*c2e18aaaSAndroid Build Coastguard Worker      host_out_path: pathlib.Path,
380*c2e18aaaSAndroid Build Coastguard Worker      build_out_dir: pathlib.Path,
381*c2e18aaaSAndroid Build Coastguard Worker      mod_info: module_info.ModuleInfo,
382*c2e18aaaSAndroid Build Coastguard Worker      jdk_path: pathlib.Path = None,
383*c2e18aaaSAndroid Build Coastguard Worker      enabled_features: Set[Features] = None,
384*c2e18aaaSAndroid Build Coastguard Worker  ):
385*c2e18aaaSAndroid Build Coastguard Worker    """Initializes the generator.
386*c2e18aaaSAndroid Build Coastguard Worker
387*c2e18aaaSAndroid Build Coastguard Worker    Args:
388*c2e18aaaSAndroid Build Coastguard Worker        workspace_out_path: Path where the workspace will be output.
389*c2e18aaaSAndroid Build Coastguard Worker        host_out_path: Path of the ANDROID_HOST_OUT.
390*c2e18aaaSAndroid Build Coastguard Worker        build_out_dir: Path of OUT_DIR
391*c2e18aaaSAndroid Build Coastguard Worker        mod_info: ModuleInfo object.
392*c2e18aaaSAndroid Build Coastguard Worker        enabled_features: Set of enabled features.
393*c2e18aaaSAndroid Build Coastguard Worker    """
394*c2e18aaaSAndroid Build Coastguard Worker    if (
395*c2e18aaaSAndroid Build Coastguard Worker        enabled_features
396*c2e18aaaSAndroid Build Coastguard Worker        and Features.EXPERIMENTAL_REMOTE_AVD in enabled_features
397*c2e18aaaSAndroid Build Coastguard Worker        and Features.EXPERIMENTAL_DEVICE_DRIVEN_TEST not in enabled_features
398*c2e18aaaSAndroid Build Coastguard Worker    ):
399*c2e18aaaSAndroid Build Coastguard Worker      raise ValueError(
400*c2e18aaaSAndroid Build Coastguard Worker          'Cannot run remote device test because '
401*c2e18aaaSAndroid Build Coastguard Worker          '"--experimental-device-driven-test" flag is'
402*c2e18aaaSAndroid Build Coastguard Worker          ' not set.'
403*c2e18aaaSAndroid Build Coastguard Worker      )
404*c2e18aaaSAndroid Build Coastguard Worker    self.enabled_features = enabled_features or set()
405*c2e18aaaSAndroid Build Coastguard Worker    self.resource_manager = resource_manager
406*c2e18aaaSAndroid Build Coastguard Worker    self.workspace_out_path = workspace_out_path
407*c2e18aaaSAndroid Build Coastguard Worker    self.host_out_path = host_out_path
408*c2e18aaaSAndroid Build Coastguard Worker    self.build_out_dir = build_out_dir
409*c2e18aaaSAndroid Build Coastguard Worker    self.mod_info = mod_info
410*c2e18aaaSAndroid Build Coastguard Worker    self.path_to_package = {}
411*c2e18aaaSAndroid Build Coastguard Worker    self.jdk_path = jdk_path
412*c2e18aaaSAndroid Build Coastguard Worker
413*c2e18aaaSAndroid Build Coastguard Worker  def generate(self):
414*c2e18aaaSAndroid Build Coastguard Worker    """Generate a Bazel workspace.
415*c2e18aaaSAndroid Build Coastguard Worker
416*c2e18aaaSAndroid Build Coastguard Worker    If the workspace md5 checksum file doesn't exist or is stale, a new
417*c2e18aaaSAndroid Build Coastguard Worker    workspace will be generated. Otherwise, the existing workspace will be
418*c2e18aaaSAndroid Build Coastguard Worker    reused.
419*c2e18aaaSAndroid Build Coastguard Worker    """
420*c2e18aaaSAndroid Build Coastguard Worker    start = time.time()
421*c2e18aaaSAndroid Build Coastguard Worker    enabled_features_file = self.workspace_out_path.joinpath(
422*c2e18aaaSAndroid Build Coastguard Worker        'atest_bazel_mode_enabled_features'
423*c2e18aaaSAndroid Build Coastguard Worker    )
424*c2e18aaaSAndroid Build Coastguard Worker    enabled_features_file_contents = '\n'.join(
425*c2e18aaaSAndroid Build Coastguard Worker        sorted(f.name for f in self.enabled_features if f.affects_workspace)
426*c2e18aaaSAndroid Build Coastguard Worker    )
427*c2e18aaaSAndroid Build Coastguard Worker
428*c2e18aaaSAndroid Build Coastguard Worker    if self.workspace_out_path.exists():
429*c2e18aaaSAndroid Build Coastguard Worker      # Update the file with the set of the currently enabled features to
430*c2e18aaaSAndroid Build Coastguard Worker      # make sure that changes are detected in the workspace checksum.
431*c2e18aaaSAndroid Build Coastguard Worker      enabled_features_file.write_text(enabled_features_file_contents)
432*c2e18aaaSAndroid Build Coastguard Worker      if self.resource_manager.check_affects_files_md5():
433*c2e18aaaSAndroid Build Coastguard Worker        return
434*c2e18aaaSAndroid Build Coastguard Worker
435*c2e18aaaSAndroid Build Coastguard Worker      # We raise an exception if rmtree fails to avoid leaving stale
436*c2e18aaaSAndroid Build Coastguard Worker      # files in the workspace that could interfere with execution.
437*c2e18aaaSAndroid Build Coastguard Worker      shutil.rmtree(self.workspace_out_path)
438*c2e18aaaSAndroid Build Coastguard Worker
439*c2e18aaaSAndroid Build Coastguard Worker    atest_utils.colorful_print('Generating Bazel workspace.\n', constants.RED)
440*c2e18aaaSAndroid Build Coastguard Worker
441*c2e18aaaSAndroid Build Coastguard Worker    self._add_test_module_targets()
442*c2e18aaaSAndroid Build Coastguard Worker
443*c2e18aaaSAndroid Build Coastguard Worker    self.workspace_out_path.mkdir(parents=True)
444*c2e18aaaSAndroid Build Coastguard Worker    self._generate_artifacts()
445*c2e18aaaSAndroid Build Coastguard Worker
446*c2e18aaaSAndroid Build Coastguard Worker    # Note that we write the set of enabled features despite having written
447*c2e18aaaSAndroid Build Coastguard Worker    # it above since the workspace no longer exists at this point.
448*c2e18aaaSAndroid Build Coastguard Worker    enabled_features_file.write_text(enabled_features_file_contents)
449*c2e18aaaSAndroid Build Coastguard Worker
450*c2e18aaaSAndroid Build Coastguard Worker    self.resource_manager.get_product_out_file_path(
451*c2e18aaaSAndroid Build Coastguard Worker        self.mod_info.mod_info_file_path.relative_to(
452*c2e18aaaSAndroid Build Coastguard Worker            self.resource_manager.get_product_out_file_path()
453*c2e18aaaSAndroid Build Coastguard Worker        ),
454*c2e18aaaSAndroid Build Coastguard Worker        True,
455*c2e18aaaSAndroid Build Coastguard Worker    )
456*c2e18aaaSAndroid Build Coastguard Worker    self.resource_manager.register_file_with_abs_path(enabled_features_file)
457*c2e18aaaSAndroid Build Coastguard Worker    self.resource_manager.save_affects_files_md5()
458*c2e18aaaSAndroid Build Coastguard Worker    metrics.LocalDetectEvent(
459*c2e18aaaSAndroid Build Coastguard Worker        detect_type=DetectType.FULL_GENERATE_BAZEL_WORKSPACE_TIME,
460*c2e18aaaSAndroid Build Coastguard Worker        result=int(time.time() - start),
461*c2e18aaaSAndroid Build Coastguard Worker    )
462*c2e18aaaSAndroid Build Coastguard Worker
463*c2e18aaaSAndroid Build Coastguard Worker  def _add_test_module_targets(self):
464*c2e18aaaSAndroid Build Coastguard Worker    seen = set()
465*c2e18aaaSAndroid Build Coastguard Worker
466*c2e18aaaSAndroid Build Coastguard Worker    for name, info in self.mod_info.name_to_module_info.items():
467*c2e18aaaSAndroid Build Coastguard Worker      # Ignore modules that have a 'host_cross_' prefix since they are
468*c2e18aaaSAndroid Build Coastguard Worker      # duplicates of existing modules. For example,
469*c2e18aaaSAndroid Build Coastguard Worker      # 'host_cross_aapt2_tests' is a duplicate of 'aapt2_tests'. We also
470*c2e18aaaSAndroid Build Coastguard Worker      # ignore modules with a '_32' suffix since these also are redundant
471*c2e18aaaSAndroid Build Coastguard Worker      # given that modules have both 32 and 64-bit variants built by
472*c2e18aaaSAndroid Build Coastguard Worker      # default. See b/77288544#comment6 and b/23566667 for more context.
473*c2e18aaaSAndroid Build Coastguard Worker      if name.endswith('_32') or name.startswith('host_cross_'):
474*c2e18aaaSAndroid Build Coastguard Worker        continue
475*c2e18aaaSAndroid Build Coastguard Worker
476*c2e18aaaSAndroid Build Coastguard Worker      if (
477*c2e18aaaSAndroid Build Coastguard Worker          Features.EXPERIMENTAL_DEVICE_DRIVEN_TEST in self.enabled_features
478*c2e18aaaSAndroid Build Coastguard Worker          and self.mod_info.is_device_driven_test(info)
479*c2e18aaaSAndroid Build Coastguard Worker      ):
480*c2e18aaaSAndroid Build Coastguard Worker        self._resolve_dependencies(
481*c2e18aaaSAndroid Build Coastguard Worker            self._add_device_test_target(info, False), seen
482*c2e18aaaSAndroid Build Coastguard Worker        )
483*c2e18aaaSAndroid Build Coastguard Worker
484*c2e18aaaSAndroid Build Coastguard Worker      if self.mod_info.is_host_unit_test(info):
485*c2e18aaaSAndroid Build Coastguard Worker        self._resolve_dependencies(self._add_deviceless_test_target(info), seen)
486*c2e18aaaSAndroid Build Coastguard Worker      elif (
487*c2e18aaaSAndroid Build Coastguard Worker          Features.EXPERIMENTAL_ROBOLECTRIC_TEST in self.enabled_features
488*c2e18aaaSAndroid Build Coastguard Worker          and self.mod_info.is_modern_robolectric_test(info)
489*c2e18aaaSAndroid Build Coastguard Worker      ):
490*c2e18aaaSAndroid Build Coastguard Worker        self._resolve_dependencies(
491*c2e18aaaSAndroid Build Coastguard Worker            self._add_tradefed_robolectric_test_target(info), seen
492*c2e18aaaSAndroid Build Coastguard Worker        )
493*c2e18aaaSAndroid Build Coastguard Worker      elif (
494*c2e18aaaSAndroid Build Coastguard Worker          Features.EXPERIMENTAL_HOST_DRIVEN_TEST in self.enabled_features
495*c2e18aaaSAndroid Build Coastguard Worker          and self.mod_info.is_host_driven_test(info)
496*c2e18aaaSAndroid Build Coastguard Worker      ):
497*c2e18aaaSAndroid Build Coastguard Worker        self._resolve_dependencies(
498*c2e18aaaSAndroid Build Coastguard Worker            self._add_device_test_target(info, True), seen
499*c2e18aaaSAndroid Build Coastguard Worker        )
500*c2e18aaaSAndroid Build Coastguard Worker
501*c2e18aaaSAndroid Build Coastguard Worker  def _resolve_dependencies(self, top_level_target: Target, seen: Set[Target]):
502*c2e18aaaSAndroid Build Coastguard Worker
503*c2e18aaaSAndroid Build Coastguard Worker    stack = [deque([top_level_target])]
504*c2e18aaaSAndroid Build Coastguard Worker
505*c2e18aaaSAndroid Build Coastguard Worker    while stack:
506*c2e18aaaSAndroid Build Coastguard Worker      top = stack[-1]
507*c2e18aaaSAndroid Build Coastguard Worker
508*c2e18aaaSAndroid Build Coastguard Worker      if not top:
509*c2e18aaaSAndroid Build Coastguard Worker        stack.pop()
510*c2e18aaaSAndroid Build Coastguard Worker        continue
511*c2e18aaaSAndroid Build Coastguard Worker
512*c2e18aaaSAndroid Build Coastguard Worker      target = top.popleft()
513*c2e18aaaSAndroid Build Coastguard Worker
514*c2e18aaaSAndroid Build Coastguard Worker      # Note that we're relying on Python's default identity-based hash
515*c2e18aaaSAndroid Build Coastguard Worker      # and equality methods. This is fine since we actually DO want
516*c2e18aaaSAndroid Build Coastguard Worker      # reference-equality semantics for Target objects in this context.
517*c2e18aaaSAndroid Build Coastguard Worker      if target in seen:
518*c2e18aaaSAndroid Build Coastguard Worker        continue
519*c2e18aaaSAndroid Build Coastguard Worker
520*c2e18aaaSAndroid Build Coastguard Worker      seen.add(target)
521*c2e18aaaSAndroid Build Coastguard Worker
522*c2e18aaaSAndroid Build Coastguard Worker      next_top = deque()
523*c2e18aaaSAndroid Build Coastguard Worker
524*c2e18aaaSAndroid Build Coastguard Worker      for ref in target.dependencies():
525*c2e18aaaSAndroid Build Coastguard Worker        info = ref.info or self._get_module_info(ref.name)
526*c2e18aaaSAndroid Build Coastguard Worker        ref.set(self._add_prebuilt_target(info))
527*c2e18aaaSAndroid Build Coastguard Worker        next_top.append(ref.target())
528*c2e18aaaSAndroid Build Coastguard Worker
529*c2e18aaaSAndroid Build Coastguard Worker      stack.append(next_top)
530*c2e18aaaSAndroid Build Coastguard Worker
531*c2e18aaaSAndroid Build Coastguard Worker  def _add_device_test_target(
532*c2e18aaaSAndroid Build Coastguard Worker      self, info: Dict[str, Any], is_host_driven: bool
533*c2e18aaaSAndroid Build Coastguard Worker  ) -> Target:
534*c2e18aaaSAndroid Build Coastguard Worker    package_name = self._get_module_path(info)
535*c2e18aaaSAndroid Build Coastguard Worker    name_suffix = 'host' if is_host_driven else 'device'
536*c2e18aaaSAndroid Build Coastguard Worker    name = f'{info[constants.MODULE_INFO_ID]}_{name_suffix}'
537*c2e18aaaSAndroid Build Coastguard Worker
538*c2e18aaaSAndroid Build Coastguard Worker    def create():
539*c2e18aaaSAndroid Build Coastguard Worker      return TestTarget.create_device_test_target(
540*c2e18aaaSAndroid Build Coastguard Worker          name,
541*c2e18aaaSAndroid Build Coastguard Worker          package_name,
542*c2e18aaaSAndroid Build Coastguard Worker          info,
543*c2e18aaaSAndroid Build Coastguard Worker          is_host_driven,
544*c2e18aaaSAndroid Build Coastguard Worker      )
545*c2e18aaaSAndroid Build Coastguard Worker
546*c2e18aaaSAndroid Build Coastguard Worker    return self._add_target(package_name, name, create)
547*c2e18aaaSAndroid Build Coastguard Worker
548*c2e18aaaSAndroid Build Coastguard Worker  def _add_deviceless_test_target(self, info: Dict[str, Any]) -> Target:
549*c2e18aaaSAndroid Build Coastguard Worker    package_name = self._get_module_path(info)
550*c2e18aaaSAndroid Build Coastguard Worker    name = f'{info[constants.MODULE_INFO_ID]}_host'
551*c2e18aaaSAndroid Build Coastguard Worker
552*c2e18aaaSAndroid Build Coastguard Worker    def create():
553*c2e18aaaSAndroid Build Coastguard Worker      return TestTarget.create_deviceless_test_target(
554*c2e18aaaSAndroid Build Coastguard Worker          name,
555*c2e18aaaSAndroid Build Coastguard Worker          package_name,
556*c2e18aaaSAndroid Build Coastguard Worker          info,
557*c2e18aaaSAndroid Build Coastguard Worker      )
558*c2e18aaaSAndroid Build Coastguard Worker
559*c2e18aaaSAndroid Build Coastguard Worker    return self._add_target(package_name, name, create)
560*c2e18aaaSAndroid Build Coastguard Worker
561*c2e18aaaSAndroid Build Coastguard Worker  def _add_tradefed_robolectric_test_target(
562*c2e18aaaSAndroid Build Coastguard Worker      self, info: Dict[str, Any]
563*c2e18aaaSAndroid Build Coastguard Worker  ) -> Target:
564*c2e18aaaSAndroid Build Coastguard Worker    package_name = self._get_module_path(info)
565*c2e18aaaSAndroid Build Coastguard Worker    name = f'{info[constants.MODULE_INFO_ID]}_host'
566*c2e18aaaSAndroid Build Coastguard Worker
567*c2e18aaaSAndroid Build Coastguard Worker    return self._add_target(
568*c2e18aaaSAndroid Build Coastguard Worker        package_name,
569*c2e18aaaSAndroid Build Coastguard Worker        name,
570*c2e18aaaSAndroid Build Coastguard Worker        lambda: TestTarget.create_tradefed_robolectric_test_target(
571*c2e18aaaSAndroid Build Coastguard Worker            name, package_name, info, f'//{JDK_PACKAGE_NAME}:{JDK_NAME}'
572*c2e18aaaSAndroid Build Coastguard Worker        ),
573*c2e18aaaSAndroid Build Coastguard Worker    )
574*c2e18aaaSAndroid Build Coastguard Worker
575*c2e18aaaSAndroid Build Coastguard Worker  def _add_prebuilt_target(self, info: Dict[str, Any]) -> Target:
576*c2e18aaaSAndroid Build Coastguard Worker    package_name = self._get_module_path(info)
577*c2e18aaaSAndroid Build Coastguard Worker    name = info[constants.MODULE_INFO_ID]
578*c2e18aaaSAndroid Build Coastguard Worker
579*c2e18aaaSAndroid Build Coastguard Worker    def create():
580*c2e18aaaSAndroid Build Coastguard Worker      return SoongPrebuiltTarget.create(
581*c2e18aaaSAndroid Build Coastguard Worker          self,
582*c2e18aaaSAndroid Build Coastguard Worker          info,
583*c2e18aaaSAndroid Build Coastguard Worker          package_name,
584*c2e18aaaSAndroid Build Coastguard Worker      )
585*c2e18aaaSAndroid Build Coastguard Worker
586*c2e18aaaSAndroid Build Coastguard Worker    return self._add_target(package_name, name, create)
587*c2e18aaaSAndroid Build Coastguard Worker
588*c2e18aaaSAndroid Build Coastguard Worker  def _add_target(
589*c2e18aaaSAndroid Build Coastguard Worker      self, package_path: str, target_name: str, create_fn: Callable
590*c2e18aaaSAndroid Build Coastguard Worker  ) -> Target:
591*c2e18aaaSAndroid Build Coastguard Worker
592*c2e18aaaSAndroid Build Coastguard Worker    package = self.path_to_package.get(package_path)
593*c2e18aaaSAndroid Build Coastguard Worker
594*c2e18aaaSAndroid Build Coastguard Worker    if not package:
595*c2e18aaaSAndroid Build Coastguard Worker      package = Package(package_path)
596*c2e18aaaSAndroid Build Coastguard Worker      self.path_to_package[package_path] = package
597*c2e18aaaSAndroid Build Coastguard Worker
598*c2e18aaaSAndroid Build Coastguard Worker    target = package.get_target(target_name)
599*c2e18aaaSAndroid Build Coastguard Worker
600*c2e18aaaSAndroid Build Coastguard Worker    if target:
601*c2e18aaaSAndroid Build Coastguard Worker      return target
602*c2e18aaaSAndroid Build Coastguard Worker
603*c2e18aaaSAndroid Build Coastguard Worker    target = create_fn()
604*c2e18aaaSAndroid Build Coastguard Worker    package.add_target(target)
605*c2e18aaaSAndroid Build Coastguard Worker
606*c2e18aaaSAndroid Build Coastguard Worker    return target
607*c2e18aaaSAndroid Build Coastguard Worker
608*c2e18aaaSAndroid Build Coastguard Worker  def _get_module_info(self, module_name: str) -> Dict[str, Any]:
609*c2e18aaaSAndroid Build Coastguard Worker    info = self.mod_info.get_module_info(module_name)
610*c2e18aaaSAndroid Build Coastguard Worker
611*c2e18aaaSAndroid Build Coastguard Worker    if not info:
612*c2e18aaaSAndroid Build Coastguard Worker      raise LookupError(
613*c2e18aaaSAndroid Build Coastguard Worker          f'Could not find module `{module_name}` in module_info file'
614*c2e18aaaSAndroid Build Coastguard Worker      )
615*c2e18aaaSAndroid Build Coastguard Worker
616*c2e18aaaSAndroid Build Coastguard Worker    return info
617*c2e18aaaSAndroid Build Coastguard Worker
618*c2e18aaaSAndroid Build Coastguard Worker  def _get_module_path(self, info: Dict[str, Any]) -> str:
619*c2e18aaaSAndroid Build Coastguard Worker    mod_path = info.get(constants.MODULE_PATH)
620*c2e18aaaSAndroid Build Coastguard Worker
621*c2e18aaaSAndroid Build Coastguard Worker    if len(mod_path) < 1:
622*c2e18aaaSAndroid Build Coastguard Worker      module_name = info['module_name']
623*c2e18aaaSAndroid Build Coastguard Worker      raise ValueError(f'Module `{module_name}` does not have any path')
624*c2e18aaaSAndroid Build Coastguard Worker
625*c2e18aaaSAndroid Build Coastguard Worker    if len(mod_path) > 1:
626*c2e18aaaSAndroid Build Coastguard Worker      module_name = info['module_name']
627*c2e18aaaSAndroid Build Coastguard Worker      # We usually have a single path but there are a few exceptions for
628*c2e18aaaSAndroid Build Coastguard Worker      # modules like libLLVM_android and libclang_android.
629*c2e18aaaSAndroid Build Coastguard Worker      # TODO(yangbill): Raise an exception for multiple paths once
630*c2e18aaaSAndroid Build Coastguard Worker      # b/233581382 is resolved.
631*c2e18aaaSAndroid Build Coastguard Worker      warnings.formatwarning = lambda msg, *args, **kwargs: f'{msg}\n'
632*c2e18aaaSAndroid Build Coastguard Worker      warnings.warn(
633*c2e18aaaSAndroid Build Coastguard Worker          f'Module `{module_name}` has more than one path: `{mod_path}`'
634*c2e18aaaSAndroid Build Coastguard Worker      )
635*c2e18aaaSAndroid Build Coastguard Worker
636*c2e18aaaSAndroid Build Coastguard Worker    return mod_path[0]
637*c2e18aaaSAndroid Build Coastguard Worker
638*c2e18aaaSAndroid Build Coastguard Worker  def _generate_artifacts(self):
639*c2e18aaaSAndroid Build Coastguard Worker    """Generate workspace files on disk."""
640*c2e18aaaSAndroid Build Coastguard Worker
641*c2e18aaaSAndroid Build Coastguard Worker    self._create_base_files()
642*c2e18aaaSAndroid Build Coastguard Worker
643*c2e18aaaSAndroid Build Coastguard Worker    self._add_workspace_resource(src='rules', dst='bazel/rules')
644*c2e18aaaSAndroid Build Coastguard Worker    self._add_workspace_resource(src='configs', dst='bazel/configs')
645*c2e18aaaSAndroid Build Coastguard Worker
646*c2e18aaaSAndroid Build Coastguard Worker    if Features.EXPERIMENTAL_DEVICE_DRIVEN_TEST in self.enabled_features:
647*c2e18aaaSAndroid Build Coastguard Worker      self._add_workspace_resource(src='device_def', dst='device_def')
648*c2e18aaaSAndroid Build Coastguard Worker
649*c2e18aaaSAndroid Build Coastguard Worker    self._add_bazel_bootstrap_files()
650*c2e18aaaSAndroid Build Coastguard Worker
651*c2e18aaaSAndroid Build Coastguard Worker    # Symlink to package with toolchain definitions.
652*c2e18aaaSAndroid Build Coastguard Worker    self._symlink(src='prebuilts/build-tools', target='prebuilts/build-tools')
653*c2e18aaaSAndroid Build Coastguard Worker
654*c2e18aaaSAndroid Build Coastguard Worker    device_infra_path = 'vendor/google/tools/atest/device_infra'
655*c2e18aaaSAndroid Build Coastguard Worker    if self.resource_manager.get_src_file_path(device_infra_path).exists():
656*c2e18aaaSAndroid Build Coastguard Worker      self._symlink(src=device_infra_path, target=device_infra_path)
657*c2e18aaaSAndroid Build Coastguard Worker
658*c2e18aaaSAndroid Build Coastguard Worker    self._link_required_src_file_path('external/bazelbuild-rules_python')
659*c2e18aaaSAndroid Build Coastguard Worker    self._link_required_src_file_path('external/bazelbuild-rules_java')
660*c2e18aaaSAndroid Build Coastguard Worker
661*c2e18aaaSAndroid Build Coastguard Worker    self._create_constants_file()
662*c2e18aaaSAndroid Build Coastguard Worker
663*c2e18aaaSAndroid Build Coastguard Worker    self._generate_robolectric_resources()
664*c2e18aaaSAndroid Build Coastguard Worker
665*c2e18aaaSAndroid Build Coastguard Worker    for package in self.path_to_package.values():
666*c2e18aaaSAndroid Build Coastguard Worker      package.generate(self.workspace_out_path)
667*c2e18aaaSAndroid Build Coastguard Worker
668*c2e18aaaSAndroid Build Coastguard Worker  def _generate_robolectric_resources(self):
669*c2e18aaaSAndroid Build Coastguard Worker    if not self.jdk_path:
670*c2e18aaaSAndroid Build Coastguard Worker      return
671*c2e18aaaSAndroid Build Coastguard Worker
672*c2e18aaaSAndroid Build Coastguard Worker    self._generate_jdk_resources()
673*c2e18aaaSAndroid Build Coastguard Worker    self._generate_android_all_resources()
674*c2e18aaaSAndroid Build Coastguard Worker
675*c2e18aaaSAndroid Build Coastguard Worker  def _generate_jdk_resources(self):
676*c2e18aaaSAndroid Build Coastguard Worker    # TODO(b/265596946): Create the JDK toolchain instead of using
677*c2e18aaaSAndroid Build Coastguard Worker    # a filegroup.
678*c2e18aaaSAndroid Build Coastguard Worker    return self._add_target(
679*c2e18aaaSAndroid Build Coastguard Worker        JDK_PACKAGE_NAME,
680*c2e18aaaSAndroid Build Coastguard Worker        JDK_NAME,
681*c2e18aaaSAndroid Build Coastguard Worker        lambda: FilegroupTarget(
682*c2e18aaaSAndroid Build Coastguard Worker            JDK_PACKAGE_NAME,
683*c2e18aaaSAndroid Build Coastguard Worker            JDK_NAME,
684*c2e18aaaSAndroid Build Coastguard Worker            self.resource_manager.get_src_file_path(self.jdk_path),
685*c2e18aaaSAndroid Build Coastguard Worker        ),
686*c2e18aaaSAndroid Build Coastguard Worker    )
687*c2e18aaaSAndroid Build Coastguard Worker
688*c2e18aaaSAndroid Build Coastguard Worker  def _generate_android_all_resources(self):
689*c2e18aaaSAndroid Build Coastguard Worker    package_name = 'android-all'
690*c2e18aaaSAndroid Build Coastguard Worker    name = 'android-all'
691*c2e18aaaSAndroid Build Coastguard Worker
692*c2e18aaaSAndroid Build Coastguard Worker    return self._add_target(
693*c2e18aaaSAndroid Build Coastguard Worker        package_name,
694*c2e18aaaSAndroid Build Coastguard Worker        name,
695*c2e18aaaSAndroid Build Coastguard Worker        lambda: FilegroupTarget(
696*c2e18aaaSAndroid Build Coastguard Worker            package_name, name, self.host_out_path.joinpath(f'testcases/{name}')
697*c2e18aaaSAndroid Build Coastguard Worker        ),
698*c2e18aaaSAndroid Build Coastguard Worker    )
699*c2e18aaaSAndroid Build Coastguard Worker
700*c2e18aaaSAndroid Build Coastguard Worker  def _symlink(self, *, src, target):
701*c2e18aaaSAndroid Build Coastguard Worker    """Create a symbolic link in workspace pointing to source file/dir.
702*c2e18aaaSAndroid Build Coastguard Worker
703*c2e18aaaSAndroid Build Coastguard Worker    Args:
704*c2e18aaaSAndroid Build Coastguard Worker        src: A string of a relative path to root of Android source tree. This is
705*c2e18aaaSAndroid Build Coastguard Worker          the source file/dir path for which the symbolic link will be created.
706*c2e18aaaSAndroid Build Coastguard Worker        target: A string of a relative path to workspace root. This is the
707*c2e18aaaSAndroid Build Coastguard Worker          target file/dir path where the symbolic link will be created.
708*c2e18aaaSAndroid Build Coastguard Worker    """
709*c2e18aaaSAndroid Build Coastguard Worker    symlink = self.workspace_out_path.joinpath(target)
710*c2e18aaaSAndroid Build Coastguard Worker    symlink.parent.mkdir(parents=True, exist_ok=True)
711*c2e18aaaSAndroid Build Coastguard Worker    symlink.symlink_to(self.resource_manager.get_src_file_path(src))
712*c2e18aaaSAndroid Build Coastguard Worker
713*c2e18aaaSAndroid Build Coastguard Worker  def _create_base_files(self):
714*c2e18aaaSAndroid Build Coastguard Worker    self._add_workspace_resource(src='WORKSPACE', dst='WORKSPACE')
715*c2e18aaaSAndroid Build Coastguard Worker    self._add_workspace_resource(src='bazelrc', dst='.bazelrc')
716*c2e18aaaSAndroid Build Coastguard Worker
717*c2e18aaaSAndroid Build Coastguard Worker    self.workspace_out_path.joinpath('BUILD.bazel').touch()
718*c2e18aaaSAndroid Build Coastguard Worker
719*c2e18aaaSAndroid Build Coastguard Worker  def _add_bazel_bootstrap_files(self):
720*c2e18aaaSAndroid Build Coastguard Worker    self._add_workspace_resource(src='bazel.sh', dst='bazel.sh')
721*c2e18aaaSAndroid Build Coastguard Worker    # Restore permissions as execute permissions are not preserved by soong
722*c2e18aaaSAndroid Build Coastguard Worker    # packaging.
723*c2e18aaaSAndroid Build Coastguard Worker    os.chmod(self.workspace_out_path.joinpath('bazel.sh'), 0o755)
724*c2e18aaaSAndroid Build Coastguard Worker    self._symlink(
725*c2e18aaaSAndroid Build Coastguard Worker        src='prebuilts/jdk/jdk21/BUILD.bazel',
726*c2e18aaaSAndroid Build Coastguard Worker        target='prebuilts/jdk/jdk21/BUILD.bazel',
727*c2e18aaaSAndroid Build Coastguard Worker    )
728*c2e18aaaSAndroid Build Coastguard Worker    self._symlink(
729*c2e18aaaSAndroid Build Coastguard Worker        src='prebuilts/jdk/jdk21/linux-x86',
730*c2e18aaaSAndroid Build Coastguard Worker        target='prebuilts/jdk/jdk21/linux-x86',
731*c2e18aaaSAndroid Build Coastguard Worker    )
732*c2e18aaaSAndroid Build Coastguard Worker    self._symlink(
733*c2e18aaaSAndroid Build Coastguard Worker        src='prebuilts/bazel/linux-x86_64/bazel',
734*c2e18aaaSAndroid Build Coastguard Worker        target='prebuilts/bazel/linux-x86_64/bazel',
735*c2e18aaaSAndroid Build Coastguard Worker    )
736*c2e18aaaSAndroid Build Coastguard Worker
737*c2e18aaaSAndroid Build Coastguard Worker  def _add_workspace_resource(self, src, dst):
738*c2e18aaaSAndroid Build Coastguard Worker    """Add resource to the given destination in workspace.
739*c2e18aaaSAndroid Build Coastguard Worker
740*c2e18aaaSAndroid Build Coastguard Worker    Args:
741*c2e18aaaSAndroid Build Coastguard Worker        src: A string of a relative path to root of Bazel artifacts. This is the
742*c2e18aaaSAndroid Build Coastguard Worker          source file/dir path that will be added to workspace.
743*c2e18aaaSAndroid Build Coastguard Worker        dst: A string of a relative path to workspace root. This is the
744*c2e18aaaSAndroid Build Coastguard Worker          destination file/dir path where the artifacts will be added.
745*c2e18aaaSAndroid Build Coastguard Worker    """
746*c2e18aaaSAndroid Build Coastguard Worker    src = self.resource_manager.get_resource_file_path(src, True)
747*c2e18aaaSAndroid Build Coastguard Worker    dst = self.workspace_out_path.joinpath(dst)
748*c2e18aaaSAndroid Build Coastguard Worker    dst.parent.mkdir(parents=True, exist_ok=True)
749*c2e18aaaSAndroid Build Coastguard Worker
750*c2e18aaaSAndroid Build Coastguard Worker    if src.is_file():
751*c2e18aaaSAndroid Build Coastguard Worker      shutil.copy(src, dst)
752*c2e18aaaSAndroid Build Coastguard Worker    else:
753*c2e18aaaSAndroid Build Coastguard Worker      shutil.copytree(src, dst, ignore=shutil.ignore_patterns('__init__.py'))
754*c2e18aaaSAndroid Build Coastguard Worker
755*c2e18aaaSAndroid Build Coastguard Worker  def _create_constants_file(self):
756*c2e18aaaSAndroid Build Coastguard Worker    def variable_name(target_name):
757*c2e18aaaSAndroid Build Coastguard Worker      return re.sub(r'[.-]', '_', target_name) + '_label'
758*c2e18aaaSAndroid Build Coastguard Worker
759*c2e18aaaSAndroid Build Coastguard Worker    targets = []
760*c2e18aaaSAndroid Build Coastguard Worker    seen = set()
761*c2e18aaaSAndroid Build Coastguard Worker
762*c2e18aaaSAndroid Build Coastguard Worker    for module_name in TestTarget.DEVICELESS_TEST_PREREQUISITES.union(
763*c2e18aaaSAndroid Build Coastguard Worker        TestTarget.DEVICE_TEST_PREREQUISITES
764*c2e18aaaSAndroid Build Coastguard Worker    ):
765*c2e18aaaSAndroid Build Coastguard Worker      info = self.mod_info.get_module_info(module_name)
766*c2e18aaaSAndroid Build Coastguard Worker      target = self._add_prebuilt_target(info)
767*c2e18aaaSAndroid Build Coastguard Worker      self._resolve_dependencies(target, seen)
768*c2e18aaaSAndroid Build Coastguard Worker      targets.append(target)
769*c2e18aaaSAndroid Build Coastguard Worker
770*c2e18aaaSAndroid Build Coastguard Worker    with self.workspace_out_path.joinpath('constants.bzl').open('w') as f:
771*c2e18aaaSAndroid Build Coastguard Worker      writer = IndentWriter(f)
772*c2e18aaaSAndroid Build Coastguard Worker      for target in targets:
773*c2e18aaaSAndroid Build Coastguard Worker        writer.write_line(
774*c2e18aaaSAndroid Build Coastguard Worker            '%s = "%s"'
775*c2e18aaaSAndroid Build Coastguard Worker            % (variable_name(target.name()), target.qualified_name())
776*c2e18aaaSAndroid Build Coastguard Worker        )
777*c2e18aaaSAndroid Build Coastguard Worker
778*c2e18aaaSAndroid Build Coastguard Worker  def _link_required_src_file_path(self, path):
779*c2e18aaaSAndroid Build Coastguard Worker    if not self.resource_manager.get_src_file_path(path).exists():
780*c2e18aaaSAndroid Build Coastguard Worker      raise RuntimeError(f'Path `{path}` does not exist in source tree.')
781*c2e18aaaSAndroid Build Coastguard Worker
782*c2e18aaaSAndroid Build Coastguard Worker    self._symlink(src=path, target=path)
783*c2e18aaaSAndroid Build Coastguard Worker
784*c2e18aaaSAndroid Build Coastguard Worker
785*c2e18aaaSAndroid Build Coastguard Worker@functools.cache
786*c2e18aaaSAndroid Build Coastguard Workerdef _get_resource_root() -> pathlib.Path:
787*c2e18aaaSAndroid Build Coastguard Worker  tmp_resource_dir = pathlib.Path(tempfile.mkdtemp())
788*c2e18aaaSAndroid Build Coastguard Worker  atexit.register(lambda: shutil.rmtree(tmp_resource_dir))
789*c2e18aaaSAndroid Build Coastguard Worker
790*c2e18aaaSAndroid Build Coastguard Worker  def _extract_resources(
791*c2e18aaaSAndroid Build Coastguard Worker      resource_path: pathlib.Path,
792*c2e18aaaSAndroid Build Coastguard Worker      dst: pathlib.Path,
793*c2e18aaaSAndroid Build Coastguard Worker      ignore_file_names: list[str] = None,
794*c2e18aaaSAndroid Build Coastguard Worker  ):
795*c2e18aaaSAndroid Build Coastguard Worker    resource = importlib.resources.files(resource_path.as_posix())
796*c2e18aaaSAndroid Build Coastguard Worker    dst.mkdir(parents=True, exist_ok=True)
797*c2e18aaaSAndroid Build Coastguard Worker    for child in resource.iterdir():
798*c2e18aaaSAndroid Build Coastguard Worker      if child.is_file():
799*c2e18aaaSAndroid Build Coastguard Worker        if child.name in ignore_file_names:
800*c2e18aaaSAndroid Build Coastguard Worker          continue
801*c2e18aaaSAndroid Build Coastguard Worker        with importlib.resources.as_file(child) as child_file:
802*c2e18aaaSAndroid Build Coastguard Worker          shutil.copy(child_file, dst.joinpath(child.name))
803*c2e18aaaSAndroid Build Coastguard Worker      elif child.is_dir():
804*c2e18aaaSAndroid Build Coastguard Worker        _extract_resources(
805*c2e18aaaSAndroid Build Coastguard Worker            resource_path.joinpath(child.name),
806*c2e18aaaSAndroid Build Coastguard Worker            dst.joinpath(child.name),
807*c2e18aaaSAndroid Build Coastguard Worker            ignore_file_names,
808*c2e18aaaSAndroid Build Coastguard Worker        )
809*c2e18aaaSAndroid Build Coastguard Worker      else:
810*c2e18aaaSAndroid Build Coastguard Worker        atest_utils.print_and_log_warning(
811*c2e18aaaSAndroid Build Coastguard Worker            'Ignoring unknown resource: %s', child
812*c2e18aaaSAndroid Build Coastguard Worker        )
813*c2e18aaaSAndroid Build Coastguard Worker
814*c2e18aaaSAndroid Build Coastguard Worker  try:
815*c2e18aaaSAndroid Build Coastguard Worker    _extract_resources(
816*c2e18aaaSAndroid Build Coastguard Worker        pathlib.Path('atest/bazel/resources'),
817*c2e18aaaSAndroid Build Coastguard Worker        tmp_resource_dir,
818*c2e18aaaSAndroid Build Coastguard Worker        ignore_file_names=['__init__.py'],
819*c2e18aaaSAndroid Build Coastguard Worker    )
820*c2e18aaaSAndroid Build Coastguard Worker  except ModuleNotFoundError as e:
821*c2e18aaaSAndroid Build Coastguard Worker    logging.debug(
822*c2e18aaaSAndroid Build Coastguard Worker        'Bazel resource not found from package path, possible due to running'
823*c2e18aaaSAndroid Build Coastguard Worker        ' atest from source. Returning resource source path instead: %s',
824*c2e18aaaSAndroid Build Coastguard Worker        e,
825*c2e18aaaSAndroid Build Coastguard Worker    )
826*c2e18aaaSAndroid Build Coastguard Worker    return pathlib.Path(os.path.dirname(__file__)).joinpath('bazel/resources')
827*c2e18aaaSAndroid Build Coastguard Worker
828*c2e18aaaSAndroid Build Coastguard Worker  return tmp_resource_dir
829*c2e18aaaSAndroid Build Coastguard Worker
830*c2e18aaaSAndroid Build Coastguard Worker
831*c2e18aaaSAndroid Build Coastguard Workerclass Package:
832*c2e18aaaSAndroid Build Coastguard Worker  """Class for generating an entire Package on disk."""
833*c2e18aaaSAndroid Build Coastguard Worker
834*c2e18aaaSAndroid Build Coastguard Worker  def __init__(self, path: str):
835*c2e18aaaSAndroid Build Coastguard Worker    self.path = path
836*c2e18aaaSAndroid Build Coastguard Worker    self.imports = defaultdict(set)
837*c2e18aaaSAndroid Build Coastguard Worker    self.name_to_target = OrderedDict()
838*c2e18aaaSAndroid Build Coastguard Worker
839*c2e18aaaSAndroid Build Coastguard Worker  def add_target(self, target):
840*c2e18aaaSAndroid Build Coastguard Worker    target_name = target.name()
841*c2e18aaaSAndroid Build Coastguard Worker
842*c2e18aaaSAndroid Build Coastguard Worker    if target_name in self.name_to_target:
843*c2e18aaaSAndroid Build Coastguard Worker      raise ValueError(
844*c2e18aaaSAndroid Build Coastguard Worker          f'Cannot add target `{target_name}` which already'
845*c2e18aaaSAndroid Build Coastguard Worker          f' exists in package `{self.path}`'
846*c2e18aaaSAndroid Build Coastguard Worker      )
847*c2e18aaaSAndroid Build Coastguard Worker
848*c2e18aaaSAndroid Build Coastguard Worker    self.name_to_target[target_name] = target
849*c2e18aaaSAndroid Build Coastguard Worker
850*c2e18aaaSAndroid Build Coastguard Worker    for i in target.required_imports():
851*c2e18aaaSAndroid Build Coastguard Worker      self.imports[i.bzl_package].add(i.symbol)
852*c2e18aaaSAndroid Build Coastguard Worker
853*c2e18aaaSAndroid Build Coastguard Worker  def generate(self, workspace_out_path: pathlib.Path):
854*c2e18aaaSAndroid Build Coastguard Worker    package_dir = workspace_out_path.joinpath(self.path)
855*c2e18aaaSAndroid Build Coastguard Worker    package_dir.mkdir(parents=True, exist_ok=True)
856*c2e18aaaSAndroid Build Coastguard Worker
857*c2e18aaaSAndroid Build Coastguard Worker    self._create_filesystem_layout(package_dir)
858*c2e18aaaSAndroid Build Coastguard Worker    self._write_build_file(package_dir)
859*c2e18aaaSAndroid Build Coastguard Worker
860*c2e18aaaSAndroid Build Coastguard Worker  def _create_filesystem_layout(self, package_dir: pathlib.Path):
861*c2e18aaaSAndroid Build Coastguard Worker    for target in self.name_to_target.values():
862*c2e18aaaSAndroid Build Coastguard Worker      target.create_filesystem_layout(package_dir)
863*c2e18aaaSAndroid Build Coastguard Worker
864*c2e18aaaSAndroid Build Coastguard Worker  def _write_build_file(self, package_dir: pathlib.Path):
865*c2e18aaaSAndroid Build Coastguard Worker    with package_dir.joinpath('BUILD.bazel').open('w') as f:
866*c2e18aaaSAndroid Build Coastguard Worker      f.write('package(default_visibility = ["//visibility:public"])\n')
867*c2e18aaaSAndroid Build Coastguard Worker      f.write('\n')
868*c2e18aaaSAndroid Build Coastguard Worker
869*c2e18aaaSAndroid Build Coastguard Worker      for bzl_package, symbols in sorted(self.imports.items()):
870*c2e18aaaSAndroid Build Coastguard Worker        symbols_text = ', '.join('"%s"' % s for s in sorted(symbols))
871*c2e18aaaSAndroid Build Coastguard Worker        f.write(f'load("{bzl_package}", {symbols_text})\n')
872*c2e18aaaSAndroid Build Coastguard Worker
873*c2e18aaaSAndroid Build Coastguard Worker      for target in self.name_to_target.values():
874*c2e18aaaSAndroid Build Coastguard Worker        f.write('\n')
875*c2e18aaaSAndroid Build Coastguard Worker        target.write_to_build_file(f)
876*c2e18aaaSAndroid Build Coastguard Worker
877*c2e18aaaSAndroid Build Coastguard Worker  def get_target(self, target_name: str) -> Target:
878*c2e18aaaSAndroid Build Coastguard Worker    return self.name_to_target.get(target_name, None)
879*c2e18aaaSAndroid Build Coastguard Worker
880*c2e18aaaSAndroid Build Coastguard Worker
881*c2e18aaaSAndroid Build Coastguard Worker@dataclasses.dataclass(frozen=True)
882*c2e18aaaSAndroid Build Coastguard Workerclass Import:
883*c2e18aaaSAndroid Build Coastguard Worker  bzl_package: str
884*c2e18aaaSAndroid Build Coastguard Worker  symbol: str
885*c2e18aaaSAndroid Build Coastguard Worker
886*c2e18aaaSAndroid Build Coastguard Worker
887*c2e18aaaSAndroid Build Coastguard Worker@dataclasses.dataclass(frozen=True)
888*c2e18aaaSAndroid Build Coastguard Workerclass Config:
889*c2e18aaaSAndroid Build Coastguard Worker  name: str
890*c2e18aaaSAndroid Build Coastguard Worker  out_path: pathlib.Path
891*c2e18aaaSAndroid Build Coastguard Worker
892*c2e18aaaSAndroid Build Coastguard Worker
893*c2e18aaaSAndroid Build Coastguard Workerclass ModuleRef:
894*c2e18aaaSAndroid Build Coastguard Worker
895*c2e18aaaSAndroid Build Coastguard Worker  @staticmethod
896*c2e18aaaSAndroid Build Coastguard Worker  def for_info(info) -> ModuleRef:
897*c2e18aaaSAndroid Build Coastguard Worker    return ModuleRef(info=info)
898*c2e18aaaSAndroid Build Coastguard Worker
899*c2e18aaaSAndroid Build Coastguard Worker  @staticmethod
900*c2e18aaaSAndroid Build Coastguard Worker  def for_name(name) -> ModuleRef:
901*c2e18aaaSAndroid Build Coastguard Worker    return ModuleRef(name=name)
902*c2e18aaaSAndroid Build Coastguard Worker
903*c2e18aaaSAndroid Build Coastguard Worker  def __init__(self, info=None, name=None):
904*c2e18aaaSAndroid Build Coastguard Worker    self.info = info
905*c2e18aaaSAndroid Build Coastguard Worker    self.name = name
906*c2e18aaaSAndroid Build Coastguard Worker    self._target = None
907*c2e18aaaSAndroid Build Coastguard Worker
908*c2e18aaaSAndroid Build Coastguard Worker  def target(self) -> Target:
909*c2e18aaaSAndroid Build Coastguard Worker    if not self._target:
910*c2e18aaaSAndroid Build Coastguard Worker      target_name = self.info[constants.MODULE_INFO_ID]
911*c2e18aaaSAndroid Build Coastguard Worker      raise ValueError(f'Target not set for ref `{target_name}`')
912*c2e18aaaSAndroid Build Coastguard Worker
913*c2e18aaaSAndroid Build Coastguard Worker    return self._target
914*c2e18aaaSAndroid Build Coastguard Worker
915*c2e18aaaSAndroid Build Coastguard Worker  def set(self, target):
916*c2e18aaaSAndroid Build Coastguard Worker    self._target = target
917*c2e18aaaSAndroid Build Coastguard Worker
918*c2e18aaaSAndroid Build Coastguard Worker
919*c2e18aaaSAndroid Build Coastguard Workerclass Target(ABC):
920*c2e18aaaSAndroid Build Coastguard Worker  """Abstract class for a Bazel target."""
921*c2e18aaaSAndroid Build Coastguard Worker
922*c2e18aaaSAndroid Build Coastguard Worker  @abstractmethod
923*c2e18aaaSAndroid Build Coastguard Worker  def name(self) -> str:
924*c2e18aaaSAndroid Build Coastguard Worker    pass
925*c2e18aaaSAndroid Build Coastguard Worker
926*c2e18aaaSAndroid Build Coastguard Worker  def package_name(self) -> str:
927*c2e18aaaSAndroid Build Coastguard Worker    pass
928*c2e18aaaSAndroid Build Coastguard Worker
929*c2e18aaaSAndroid Build Coastguard Worker  def qualified_name(self) -> str:
930*c2e18aaaSAndroid Build Coastguard Worker    return f'//{self.package_name()}:{self.name()}'
931*c2e18aaaSAndroid Build Coastguard Worker
932*c2e18aaaSAndroid Build Coastguard Worker  def required_imports(self) -> Set[Import]:
933*c2e18aaaSAndroid Build Coastguard Worker    return set()
934*c2e18aaaSAndroid Build Coastguard Worker
935*c2e18aaaSAndroid Build Coastguard Worker  def supported_configs(self) -> Set[Config]:
936*c2e18aaaSAndroid Build Coastguard Worker    return set()
937*c2e18aaaSAndroid Build Coastguard Worker
938*c2e18aaaSAndroid Build Coastguard Worker  def dependencies(self) -> List[ModuleRef]:
939*c2e18aaaSAndroid Build Coastguard Worker    return []
940*c2e18aaaSAndroid Build Coastguard Worker
941*c2e18aaaSAndroid Build Coastguard Worker  def write_to_build_file(self, f: IO):
942*c2e18aaaSAndroid Build Coastguard Worker    pass
943*c2e18aaaSAndroid Build Coastguard Worker
944*c2e18aaaSAndroid Build Coastguard Worker  def create_filesystem_layout(self, package_dir: pathlib.Path):
945*c2e18aaaSAndroid Build Coastguard Worker    pass
946*c2e18aaaSAndroid Build Coastguard Worker
947*c2e18aaaSAndroid Build Coastguard Worker
948*c2e18aaaSAndroid Build Coastguard Workerclass FilegroupTarget(Target):
949*c2e18aaaSAndroid Build Coastguard Worker
950*c2e18aaaSAndroid Build Coastguard Worker  def __init__(
951*c2e18aaaSAndroid Build Coastguard Worker      self, package_name: str, target_name: str, srcs_root: pathlib.Path
952*c2e18aaaSAndroid Build Coastguard Worker  ):
953*c2e18aaaSAndroid Build Coastguard Worker    self._package_name = package_name
954*c2e18aaaSAndroid Build Coastguard Worker    self._target_name = target_name
955*c2e18aaaSAndroid Build Coastguard Worker    self._srcs_root = srcs_root
956*c2e18aaaSAndroid Build Coastguard Worker
957*c2e18aaaSAndroid Build Coastguard Worker  def name(self) -> str:
958*c2e18aaaSAndroid Build Coastguard Worker    return self._target_name
959*c2e18aaaSAndroid Build Coastguard Worker
960*c2e18aaaSAndroid Build Coastguard Worker  def package_name(self) -> str:
961*c2e18aaaSAndroid Build Coastguard Worker    return self._package_name
962*c2e18aaaSAndroid Build Coastguard Worker
963*c2e18aaaSAndroid Build Coastguard Worker  def write_to_build_file(self, f: IO):
964*c2e18aaaSAndroid Build Coastguard Worker    writer = IndentWriter(f)
965*c2e18aaaSAndroid Build Coastguard Worker    build_file_writer = BuildFileWriter(writer)
966*c2e18aaaSAndroid Build Coastguard Worker
967*c2e18aaaSAndroid Build Coastguard Worker    writer.write_line('filegroup(')
968*c2e18aaaSAndroid Build Coastguard Worker
969*c2e18aaaSAndroid Build Coastguard Worker    with writer.indent():
970*c2e18aaaSAndroid Build Coastguard Worker      build_file_writer.write_string_attribute('name', self._target_name)
971*c2e18aaaSAndroid Build Coastguard Worker      build_file_writer.write_glob_attribute(
972*c2e18aaaSAndroid Build Coastguard Worker          'srcs', [f'{self._target_name}_files/**']
973*c2e18aaaSAndroid Build Coastguard Worker      )
974*c2e18aaaSAndroid Build Coastguard Worker
975*c2e18aaaSAndroid Build Coastguard Worker    writer.write_line(')')
976*c2e18aaaSAndroid Build Coastguard Worker
977*c2e18aaaSAndroid Build Coastguard Worker  def create_filesystem_layout(self, package_dir: pathlib.Path):
978*c2e18aaaSAndroid Build Coastguard Worker    symlink = package_dir.joinpath(f'{self._target_name}_files')
979*c2e18aaaSAndroid Build Coastguard Worker    symlink.symlink_to(self._srcs_root)
980*c2e18aaaSAndroid Build Coastguard Worker
981*c2e18aaaSAndroid Build Coastguard Worker
982*c2e18aaaSAndroid Build Coastguard Workerclass TestTarget(Target):
983*c2e18aaaSAndroid Build Coastguard Worker  """Class for generating a test target."""
984*c2e18aaaSAndroid Build Coastguard Worker
985*c2e18aaaSAndroid Build Coastguard Worker  DEVICELESS_TEST_PREREQUISITES = frozenset({
986*c2e18aaaSAndroid Build Coastguard Worker      'adb',
987*c2e18aaaSAndroid Build Coastguard Worker      'atest-tradefed',
988*c2e18aaaSAndroid Build Coastguard Worker      'atest_script_help.sh',
989*c2e18aaaSAndroid Build Coastguard Worker      'atest_tradefed.sh',
990*c2e18aaaSAndroid Build Coastguard Worker      'tradefed',
991*c2e18aaaSAndroid Build Coastguard Worker      'tradefed-test-framework',
992*c2e18aaaSAndroid Build Coastguard Worker      'bazel-result-reporter',
993*c2e18aaaSAndroid Build Coastguard Worker  })
994*c2e18aaaSAndroid Build Coastguard Worker
995*c2e18aaaSAndroid Build Coastguard Worker  DEVICE_TEST_PREREQUISITES = frozenset(
996*c2e18aaaSAndroid Build Coastguard Worker      DEVICELESS_TEST_PREREQUISITES.union(
997*c2e18aaaSAndroid Build Coastguard Worker          frozenset({
998*c2e18aaaSAndroid Build Coastguard Worker              'aapt',
999*c2e18aaaSAndroid Build Coastguard Worker              'aapt2',
1000*c2e18aaaSAndroid Build Coastguard Worker              'compatibility-tradefed',
1001*c2e18aaaSAndroid Build Coastguard Worker              'vts-core-tradefed-harness',
1002*c2e18aaaSAndroid Build Coastguard Worker          })
1003*c2e18aaaSAndroid Build Coastguard Worker      )
1004*c2e18aaaSAndroid Build Coastguard Worker  )
1005*c2e18aaaSAndroid Build Coastguard Worker
1006*c2e18aaaSAndroid Build Coastguard Worker  @staticmethod
1007*c2e18aaaSAndroid Build Coastguard Worker  def create_deviceless_test_target(
1008*c2e18aaaSAndroid Build Coastguard Worker      name: str, package_name: str, info: Dict[str, Any]
1009*c2e18aaaSAndroid Build Coastguard Worker  ):
1010*c2e18aaaSAndroid Build Coastguard Worker    return TestTarget(
1011*c2e18aaaSAndroid Build Coastguard Worker        package_name,
1012*c2e18aaaSAndroid Build Coastguard Worker        'tradefed_deviceless_test',
1013*c2e18aaaSAndroid Build Coastguard Worker        {
1014*c2e18aaaSAndroid Build Coastguard Worker            'name': name,
1015*c2e18aaaSAndroid Build Coastguard Worker            'test': ModuleRef.for_info(info),
1016*c2e18aaaSAndroid Build Coastguard Worker            'module_name': info['module_name'],
1017*c2e18aaaSAndroid Build Coastguard Worker            'tags': info.get(constants.MODULE_TEST_OPTIONS_TAGS, []),
1018*c2e18aaaSAndroid Build Coastguard Worker        },
1019*c2e18aaaSAndroid Build Coastguard Worker        TestTarget.DEVICELESS_TEST_PREREQUISITES,
1020*c2e18aaaSAndroid Build Coastguard Worker    )
1021*c2e18aaaSAndroid Build Coastguard Worker
1022*c2e18aaaSAndroid Build Coastguard Worker  @staticmethod
1023*c2e18aaaSAndroid Build Coastguard Worker  def create_device_test_target(
1024*c2e18aaaSAndroid Build Coastguard Worker      name: str, package_name: str, info: Dict[str, Any], is_host_driven: bool
1025*c2e18aaaSAndroid Build Coastguard Worker  ):
1026*c2e18aaaSAndroid Build Coastguard Worker    rule = (
1027*c2e18aaaSAndroid Build Coastguard Worker        'tradefed_host_driven_device_test'
1028*c2e18aaaSAndroid Build Coastguard Worker        if is_host_driven
1029*c2e18aaaSAndroid Build Coastguard Worker        else 'tradefed_device_driven_test'
1030*c2e18aaaSAndroid Build Coastguard Worker    )
1031*c2e18aaaSAndroid Build Coastguard Worker
1032*c2e18aaaSAndroid Build Coastguard Worker    return TestTarget(
1033*c2e18aaaSAndroid Build Coastguard Worker        package_name,
1034*c2e18aaaSAndroid Build Coastguard Worker        rule,
1035*c2e18aaaSAndroid Build Coastguard Worker        {
1036*c2e18aaaSAndroid Build Coastguard Worker            'name': name,
1037*c2e18aaaSAndroid Build Coastguard Worker            'test': ModuleRef.for_info(info),
1038*c2e18aaaSAndroid Build Coastguard Worker            'module_name': info['module_name'],
1039*c2e18aaaSAndroid Build Coastguard Worker            'suites': set(info.get(constants.MODULE_COMPATIBILITY_SUITES, [])),
1040*c2e18aaaSAndroid Build Coastguard Worker            'tradefed_deps': list(
1041*c2e18aaaSAndroid Build Coastguard Worker                map(
1042*c2e18aaaSAndroid Build Coastguard Worker                    ModuleRef.for_name, info.get(constants.MODULE_HOST_DEPS, [])
1043*c2e18aaaSAndroid Build Coastguard Worker                )
1044*c2e18aaaSAndroid Build Coastguard Worker            ),
1045*c2e18aaaSAndroid Build Coastguard Worker            'tags': info.get(constants.MODULE_TEST_OPTIONS_TAGS, []),
1046*c2e18aaaSAndroid Build Coastguard Worker        },
1047*c2e18aaaSAndroid Build Coastguard Worker        TestTarget.DEVICE_TEST_PREREQUISITES,
1048*c2e18aaaSAndroid Build Coastguard Worker    )
1049*c2e18aaaSAndroid Build Coastguard Worker
1050*c2e18aaaSAndroid Build Coastguard Worker  @staticmethod
1051*c2e18aaaSAndroid Build Coastguard Worker  def create_tradefed_robolectric_test_target(
1052*c2e18aaaSAndroid Build Coastguard Worker      name: str, package_name: str, info: Dict[str, Any], jdk_label: str
1053*c2e18aaaSAndroid Build Coastguard Worker  ):
1054*c2e18aaaSAndroid Build Coastguard Worker    return TestTarget(
1055*c2e18aaaSAndroid Build Coastguard Worker        package_name,
1056*c2e18aaaSAndroid Build Coastguard Worker        'tradefed_robolectric_test',
1057*c2e18aaaSAndroid Build Coastguard Worker        {
1058*c2e18aaaSAndroid Build Coastguard Worker            'name': name,
1059*c2e18aaaSAndroid Build Coastguard Worker            'test': ModuleRef.for_info(info),
1060*c2e18aaaSAndroid Build Coastguard Worker            'module_name': info['module_name'],
1061*c2e18aaaSAndroid Build Coastguard Worker            'tags': info.get(constants.MODULE_TEST_OPTIONS_TAGS, []),
1062*c2e18aaaSAndroid Build Coastguard Worker            'jdk': jdk_label,
1063*c2e18aaaSAndroid Build Coastguard Worker        },
1064*c2e18aaaSAndroid Build Coastguard Worker        TestTarget.DEVICELESS_TEST_PREREQUISITES,
1065*c2e18aaaSAndroid Build Coastguard Worker    )
1066*c2e18aaaSAndroid Build Coastguard Worker
1067*c2e18aaaSAndroid Build Coastguard Worker  def __init__(
1068*c2e18aaaSAndroid Build Coastguard Worker      self,
1069*c2e18aaaSAndroid Build Coastguard Worker      package_name: str,
1070*c2e18aaaSAndroid Build Coastguard Worker      rule_name: str,
1071*c2e18aaaSAndroid Build Coastguard Worker      attributes: Dict[str, Any],
1072*c2e18aaaSAndroid Build Coastguard Worker      prerequisites=frozenset(),
1073*c2e18aaaSAndroid Build Coastguard Worker  ):
1074*c2e18aaaSAndroid Build Coastguard Worker    self._attributes = attributes
1075*c2e18aaaSAndroid Build Coastguard Worker    self._package_name = package_name
1076*c2e18aaaSAndroid Build Coastguard Worker    self._rule_name = rule_name
1077*c2e18aaaSAndroid Build Coastguard Worker    self._prerequisites = prerequisites
1078*c2e18aaaSAndroid Build Coastguard Worker
1079*c2e18aaaSAndroid Build Coastguard Worker  def name(self) -> str:
1080*c2e18aaaSAndroid Build Coastguard Worker    return self._attributes['name']
1081*c2e18aaaSAndroid Build Coastguard Worker
1082*c2e18aaaSAndroid Build Coastguard Worker  def package_name(self) -> str:
1083*c2e18aaaSAndroid Build Coastguard Worker    return self._package_name
1084*c2e18aaaSAndroid Build Coastguard Worker
1085*c2e18aaaSAndroid Build Coastguard Worker  def required_imports(self) -> Set[Import]:
1086*c2e18aaaSAndroid Build Coastguard Worker    return {Import('//bazel/rules:tradefed_test.bzl', self._rule_name)}
1087*c2e18aaaSAndroid Build Coastguard Worker
1088*c2e18aaaSAndroid Build Coastguard Worker  def dependencies(self) -> List[ModuleRef]:
1089*c2e18aaaSAndroid Build Coastguard Worker    prerequisite_refs = map(ModuleRef.for_name, self._prerequisites)
1090*c2e18aaaSAndroid Build Coastguard Worker
1091*c2e18aaaSAndroid Build Coastguard Worker    declared_dep_refs = []
1092*c2e18aaaSAndroid Build Coastguard Worker    for value in self._attributes.values():
1093*c2e18aaaSAndroid Build Coastguard Worker      if isinstance(value, Iterable):
1094*c2e18aaaSAndroid Build Coastguard Worker        declared_dep_refs.extend(
1095*c2e18aaaSAndroid Build Coastguard Worker            [dep for dep in value if isinstance(dep, ModuleRef)]
1096*c2e18aaaSAndroid Build Coastguard Worker        )
1097*c2e18aaaSAndroid Build Coastguard Worker      elif isinstance(value, ModuleRef):
1098*c2e18aaaSAndroid Build Coastguard Worker        declared_dep_refs.append(value)
1099*c2e18aaaSAndroid Build Coastguard Worker
1100*c2e18aaaSAndroid Build Coastguard Worker    return declared_dep_refs + list(prerequisite_refs)
1101*c2e18aaaSAndroid Build Coastguard Worker
1102*c2e18aaaSAndroid Build Coastguard Worker  def write_to_build_file(self, f: IO):
1103*c2e18aaaSAndroid Build Coastguard Worker    prebuilt_target_name = self._attributes['test'].target().qualified_name()
1104*c2e18aaaSAndroid Build Coastguard Worker    writer = IndentWriter(f)
1105*c2e18aaaSAndroid Build Coastguard Worker    build_file_writer = BuildFileWriter(writer)
1106*c2e18aaaSAndroid Build Coastguard Worker
1107*c2e18aaaSAndroid Build Coastguard Worker    writer.write_line(f'{self._rule_name}(')
1108*c2e18aaaSAndroid Build Coastguard Worker
1109*c2e18aaaSAndroid Build Coastguard Worker    with writer.indent():
1110*c2e18aaaSAndroid Build Coastguard Worker      build_file_writer.write_string_attribute('name', self._attributes['name'])
1111*c2e18aaaSAndroid Build Coastguard Worker
1112*c2e18aaaSAndroid Build Coastguard Worker      build_file_writer.write_string_attribute(
1113*c2e18aaaSAndroid Build Coastguard Worker          'module_name', self._attributes['module_name']
1114*c2e18aaaSAndroid Build Coastguard Worker      )
1115*c2e18aaaSAndroid Build Coastguard Worker
1116*c2e18aaaSAndroid Build Coastguard Worker      build_file_writer.write_string_attribute('test', prebuilt_target_name)
1117*c2e18aaaSAndroid Build Coastguard Worker
1118*c2e18aaaSAndroid Build Coastguard Worker      build_file_writer.write_label_list_attribute(
1119*c2e18aaaSAndroid Build Coastguard Worker          'tradefed_deps', self._attributes.get('tradefed_deps')
1120*c2e18aaaSAndroid Build Coastguard Worker      )
1121*c2e18aaaSAndroid Build Coastguard Worker
1122*c2e18aaaSAndroid Build Coastguard Worker      build_file_writer.write_string_list_attribute(
1123*c2e18aaaSAndroid Build Coastguard Worker          'suites', sorted(self._attributes.get('suites', []))
1124*c2e18aaaSAndroid Build Coastguard Worker      )
1125*c2e18aaaSAndroid Build Coastguard Worker
1126*c2e18aaaSAndroid Build Coastguard Worker      build_file_writer.write_string_list_attribute(
1127*c2e18aaaSAndroid Build Coastguard Worker          'tags', sorted(self._attributes.get('tags', []))
1128*c2e18aaaSAndroid Build Coastguard Worker      )
1129*c2e18aaaSAndroid Build Coastguard Worker
1130*c2e18aaaSAndroid Build Coastguard Worker      build_file_writer.write_label_attribute(
1131*c2e18aaaSAndroid Build Coastguard Worker          'jdk', self._attributes.get('jdk', None)
1132*c2e18aaaSAndroid Build Coastguard Worker      )
1133*c2e18aaaSAndroid Build Coastguard Worker
1134*c2e18aaaSAndroid Build Coastguard Worker    writer.write_line(')')
1135*c2e18aaaSAndroid Build Coastguard Worker
1136*c2e18aaaSAndroid Build Coastguard Worker
1137*c2e18aaaSAndroid Build Coastguard Workerdef _read_robolectric_jdk_path(
1138*c2e18aaaSAndroid Build Coastguard Worker    test_xml_config_template: pathlib.Path,
1139*c2e18aaaSAndroid Build Coastguard Worker) -> pathlib.Path:
1140*c2e18aaaSAndroid Build Coastguard Worker  if not test_xml_config_template.is_file():
1141*c2e18aaaSAndroid Build Coastguard Worker    return None
1142*c2e18aaaSAndroid Build Coastguard Worker
1143*c2e18aaaSAndroid Build Coastguard Worker  xml_root = ET.parse(test_xml_config_template).getroot()
1144*c2e18aaaSAndroid Build Coastguard Worker  option = xml_root.find(".//option[@name='java-folder']")
1145*c2e18aaaSAndroid Build Coastguard Worker  jdk_path = pathlib.Path(option.get('value', ''))
1146*c2e18aaaSAndroid Build Coastguard Worker
1147*c2e18aaaSAndroid Build Coastguard Worker  if not jdk_path.is_relative_to('prebuilts/jdk'):
1148*c2e18aaaSAndroid Build Coastguard Worker    raise ValueError(
1149*c2e18aaaSAndroid Build Coastguard Worker        f'Failed to get "java-folder" from `{test_xml_config_template}`'
1150*c2e18aaaSAndroid Build Coastguard Worker    )
1151*c2e18aaaSAndroid Build Coastguard Worker
1152*c2e18aaaSAndroid Build Coastguard Worker  return jdk_path
1153*c2e18aaaSAndroid Build Coastguard Worker
1154*c2e18aaaSAndroid Build Coastguard Worker
1155*c2e18aaaSAndroid Build Coastguard Workerclass BuildFileWriter:
1156*c2e18aaaSAndroid Build Coastguard Worker  """Class for writing BUILD files."""
1157*c2e18aaaSAndroid Build Coastguard Worker
1158*c2e18aaaSAndroid Build Coastguard Worker  def __init__(self, underlying: IndentWriter):
1159*c2e18aaaSAndroid Build Coastguard Worker    self._underlying = underlying
1160*c2e18aaaSAndroid Build Coastguard Worker
1161*c2e18aaaSAndroid Build Coastguard Worker  def write_string_attribute(self, attribute_name, value):
1162*c2e18aaaSAndroid Build Coastguard Worker    if value is None:
1163*c2e18aaaSAndroid Build Coastguard Worker      return
1164*c2e18aaaSAndroid Build Coastguard Worker
1165*c2e18aaaSAndroid Build Coastguard Worker    self._underlying.write_line(f'{attribute_name} = "{value}",')
1166*c2e18aaaSAndroid Build Coastguard Worker
1167*c2e18aaaSAndroid Build Coastguard Worker  def write_label_attribute(self, attribute_name: str, label_name: str):
1168*c2e18aaaSAndroid Build Coastguard Worker    if label_name is None:
1169*c2e18aaaSAndroid Build Coastguard Worker      return
1170*c2e18aaaSAndroid Build Coastguard Worker
1171*c2e18aaaSAndroid Build Coastguard Worker    self._underlying.write_line(f'{attribute_name} = "{label_name}",')
1172*c2e18aaaSAndroid Build Coastguard Worker
1173*c2e18aaaSAndroid Build Coastguard Worker  def write_string_list_attribute(self, attribute_name, values):
1174*c2e18aaaSAndroid Build Coastguard Worker    if not values:
1175*c2e18aaaSAndroid Build Coastguard Worker      return
1176*c2e18aaaSAndroid Build Coastguard Worker
1177*c2e18aaaSAndroid Build Coastguard Worker    self._underlying.write_line(f'{attribute_name} = [')
1178*c2e18aaaSAndroid Build Coastguard Worker
1179*c2e18aaaSAndroid Build Coastguard Worker    with self._underlying.indent():
1180*c2e18aaaSAndroid Build Coastguard Worker      for value in values:
1181*c2e18aaaSAndroid Build Coastguard Worker        self._underlying.write_line(f'"{value}",')
1182*c2e18aaaSAndroid Build Coastguard Worker
1183*c2e18aaaSAndroid Build Coastguard Worker    self._underlying.write_line('],')
1184*c2e18aaaSAndroid Build Coastguard Worker
1185*c2e18aaaSAndroid Build Coastguard Worker  def write_label_list_attribute(
1186*c2e18aaaSAndroid Build Coastguard Worker      self, attribute_name: str, modules: List[ModuleRef]
1187*c2e18aaaSAndroid Build Coastguard Worker  ):
1188*c2e18aaaSAndroid Build Coastguard Worker    if not modules:
1189*c2e18aaaSAndroid Build Coastguard Worker      return
1190*c2e18aaaSAndroid Build Coastguard Worker
1191*c2e18aaaSAndroid Build Coastguard Worker    self._underlying.write_line(f'{attribute_name} = [')
1192*c2e18aaaSAndroid Build Coastguard Worker
1193*c2e18aaaSAndroid Build Coastguard Worker    with self._underlying.indent():
1194*c2e18aaaSAndroid Build Coastguard Worker      for label in sorted(set(m.target().qualified_name() for m in modules)):
1195*c2e18aaaSAndroid Build Coastguard Worker        self._underlying.write_line(f'"{label}",')
1196*c2e18aaaSAndroid Build Coastguard Worker
1197*c2e18aaaSAndroid Build Coastguard Worker    self._underlying.write_line('],')
1198*c2e18aaaSAndroid Build Coastguard Worker
1199*c2e18aaaSAndroid Build Coastguard Worker  def write_glob_attribute(self, attribute_name: str, patterns: List[str]):
1200*c2e18aaaSAndroid Build Coastguard Worker    self._underlying.write_line(f'{attribute_name} = glob([')
1201*c2e18aaaSAndroid Build Coastguard Worker
1202*c2e18aaaSAndroid Build Coastguard Worker    with self._underlying.indent():
1203*c2e18aaaSAndroid Build Coastguard Worker      for pattern in patterns:
1204*c2e18aaaSAndroid Build Coastguard Worker        self._underlying.write_line(f'"{pattern}",')
1205*c2e18aaaSAndroid Build Coastguard Worker
1206*c2e18aaaSAndroid Build Coastguard Worker    self._underlying.write_line(']),')
1207*c2e18aaaSAndroid Build Coastguard Worker
1208*c2e18aaaSAndroid Build Coastguard Worker
1209*c2e18aaaSAndroid Build Coastguard Worker@dataclasses.dataclass(frozen=True)
1210*c2e18aaaSAndroid Build Coastguard Workerclass Dependencies:
1211*c2e18aaaSAndroid Build Coastguard Worker  static_dep_refs: List[ModuleRef]
1212*c2e18aaaSAndroid Build Coastguard Worker  runtime_dep_refs: List[ModuleRef]
1213*c2e18aaaSAndroid Build Coastguard Worker  data_dep_refs: List[ModuleRef]
1214*c2e18aaaSAndroid Build Coastguard Worker  device_data_dep_refs: List[ModuleRef]
1215*c2e18aaaSAndroid Build Coastguard Worker
1216*c2e18aaaSAndroid Build Coastguard Worker
1217*c2e18aaaSAndroid Build Coastguard Workerclass SoongPrebuiltTarget(Target):
1218*c2e18aaaSAndroid Build Coastguard Worker  """Class for generating a Soong prebuilt target on disk."""
1219*c2e18aaaSAndroid Build Coastguard Worker
1220*c2e18aaaSAndroid Build Coastguard Worker  @staticmethod
1221*c2e18aaaSAndroid Build Coastguard Worker  def create(
1222*c2e18aaaSAndroid Build Coastguard Worker      gen: WorkspaceGenerator, info: Dict[str, Any], package_name: str = ''
1223*c2e18aaaSAndroid Build Coastguard Worker  ):
1224*c2e18aaaSAndroid Build Coastguard Worker    module_name = info['module_name']
1225*c2e18aaaSAndroid Build Coastguard Worker
1226*c2e18aaaSAndroid Build Coastguard Worker    configs = [
1227*c2e18aaaSAndroid Build Coastguard Worker        Config('host', gen.host_out_path),
1228*c2e18aaaSAndroid Build Coastguard Worker        Config('device', gen.resource_manager.get_product_out_file_path()),
1229*c2e18aaaSAndroid Build Coastguard Worker    ]
1230*c2e18aaaSAndroid Build Coastguard Worker
1231*c2e18aaaSAndroid Build Coastguard Worker    installed_paths = get_module_installed_paths(
1232*c2e18aaaSAndroid Build Coastguard Worker        info, gen.resource_manager.get_src_file_path()
1233*c2e18aaaSAndroid Build Coastguard Worker    )
1234*c2e18aaaSAndroid Build Coastguard Worker    config_files = group_paths_by_config(configs, installed_paths)
1235*c2e18aaaSAndroid Build Coastguard Worker
1236*c2e18aaaSAndroid Build Coastguard Worker    # For test modules, we only create symbolic link to the 'testcases'
1237*c2e18aaaSAndroid Build Coastguard Worker    # directory since the information in module-info is not accurate.
1238*c2e18aaaSAndroid Build Coastguard Worker    if gen.mod_info.is_tradefed_testable_module(info):
1239*c2e18aaaSAndroid Build Coastguard Worker      config_files = {
1240*c2e18aaaSAndroid Build Coastguard Worker          c: [c.out_path.joinpath(f'testcases/{module_name}')]
1241*c2e18aaaSAndroid Build Coastguard Worker          for c in config_files.keys()
1242*c2e18aaaSAndroid Build Coastguard Worker      }
1243*c2e18aaaSAndroid Build Coastguard Worker
1244*c2e18aaaSAndroid Build Coastguard Worker    enabled_features = gen.enabled_features
1245*c2e18aaaSAndroid Build Coastguard Worker
1246*c2e18aaaSAndroid Build Coastguard Worker    return SoongPrebuiltTarget(
1247*c2e18aaaSAndroid Build Coastguard Worker        info,
1248*c2e18aaaSAndroid Build Coastguard Worker        package_name,
1249*c2e18aaaSAndroid Build Coastguard Worker        config_files,
1250*c2e18aaaSAndroid Build Coastguard Worker        Dependencies(
1251*c2e18aaaSAndroid Build Coastguard Worker            static_dep_refs=find_static_dep_refs(
1252*c2e18aaaSAndroid Build Coastguard Worker                gen.mod_info,
1253*c2e18aaaSAndroid Build Coastguard Worker                info,
1254*c2e18aaaSAndroid Build Coastguard Worker                configs,
1255*c2e18aaaSAndroid Build Coastguard Worker                gen.resource_manager.get_src_file_path(),
1256*c2e18aaaSAndroid Build Coastguard Worker                enabled_features,
1257*c2e18aaaSAndroid Build Coastguard Worker            ),
1258*c2e18aaaSAndroid Build Coastguard Worker            runtime_dep_refs=find_runtime_dep_refs(
1259*c2e18aaaSAndroid Build Coastguard Worker                gen.mod_info,
1260*c2e18aaaSAndroid Build Coastguard Worker                info,
1261*c2e18aaaSAndroid Build Coastguard Worker                configs,
1262*c2e18aaaSAndroid Build Coastguard Worker                gen.resource_manager.get_src_file_path(),
1263*c2e18aaaSAndroid Build Coastguard Worker                enabled_features,
1264*c2e18aaaSAndroid Build Coastguard Worker            ),
1265*c2e18aaaSAndroid Build Coastguard Worker            data_dep_refs=find_data_dep_refs(
1266*c2e18aaaSAndroid Build Coastguard Worker                gen.mod_info,
1267*c2e18aaaSAndroid Build Coastguard Worker                info,
1268*c2e18aaaSAndroid Build Coastguard Worker                configs,
1269*c2e18aaaSAndroid Build Coastguard Worker                gen.resource_manager.get_src_file_path(),
1270*c2e18aaaSAndroid Build Coastguard Worker            ),
1271*c2e18aaaSAndroid Build Coastguard Worker            device_data_dep_refs=find_device_data_dep_refs(gen, info),
1272*c2e18aaaSAndroid Build Coastguard Worker        ),
1273*c2e18aaaSAndroid Build Coastguard Worker        [
1274*c2e18aaaSAndroid Build Coastguard Worker            c
1275*c2e18aaaSAndroid Build Coastguard Worker            for c in configs
1276*c2e18aaaSAndroid Build Coastguard Worker            if c.name
1277*c2e18aaaSAndroid Build Coastguard Worker            in map(str.lower, info.get(constants.MODULE_SUPPORTED_VARIANTS, []))
1278*c2e18aaaSAndroid Build Coastguard Worker        ],
1279*c2e18aaaSAndroid Build Coastguard Worker    )
1280*c2e18aaaSAndroid Build Coastguard Worker
1281*c2e18aaaSAndroid Build Coastguard Worker  def __init__(
1282*c2e18aaaSAndroid Build Coastguard Worker      self,
1283*c2e18aaaSAndroid Build Coastguard Worker      info: Dict[str, Any],
1284*c2e18aaaSAndroid Build Coastguard Worker      package_name: str,
1285*c2e18aaaSAndroid Build Coastguard Worker      config_files: Dict[Config, List[pathlib.Path]],
1286*c2e18aaaSAndroid Build Coastguard Worker      deps: Dependencies,
1287*c2e18aaaSAndroid Build Coastguard Worker      supported_configs: List[Config],
1288*c2e18aaaSAndroid Build Coastguard Worker  ):
1289*c2e18aaaSAndroid Build Coastguard Worker    self._target_name = info[constants.MODULE_INFO_ID]
1290*c2e18aaaSAndroid Build Coastguard Worker    self._module_name = info[constants.MODULE_NAME]
1291*c2e18aaaSAndroid Build Coastguard Worker    self._package_name = package_name
1292*c2e18aaaSAndroid Build Coastguard Worker    self.config_files = config_files
1293*c2e18aaaSAndroid Build Coastguard Worker    self.deps = deps
1294*c2e18aaaSAndroid Build Coastguard Worker    self.suites = info.get(constants.MODULE_COMPATIBILITY_SUITES, [])
1295*c2e18aaaSAndroid Build Coastguard Worker    self._supported_configs = supported_configs
1296*c2e18aaaSAndroid Build Coastguard Worker
1297*c2e18aaaSAndroid Build Coastguard Worker  def name(self) -> str:
1298*c2e18aaaSAndroid Build Coastguard Worker    return self._target_name
1299*c2e18aaaSAndroid Build Coastguard Worker
1300*c2e18aaaSAndroid Build Coastguard Worker  def package_name(self) -> str:
1301*c2e18aaaSAndroid Build Coastguard Worker    return self._package_name
1302*c2e18aaaSAndroid Build Coastguard Worker
1303*c2e18aaaSAndroid Build Coastguard Worker  def required_imports(self) -> Set[Import]:
1304*c2e18aaaSAndroid Build Coastguard Worker    return {
1305*c2e18aaaSAndroid Build Coastguard Worker        Import('//bazel/rules:soong_prebuilt.bzl', self._rule_name()),
1306*c2e18aaaSAndroid Build Coastguard Worker    }
1307*c2e18aaaSAndroid Build Coastguard Worker
1308*c2e18aaaSAndroid Build Coastguard Worker  @functools.lru_cache(maxsize=128)
1309*c2e18aaaSAndroid Build Coastguard Worker  def supported_configs(self) -> Set[Config]:
1310*c2e18aaaSAndroid Build Coastguard Worker    # We deduce the supported configs from the installed paths since the
1311*c2e18aaaSAndroid Build Coastguard Worker    # build exports incorrect metadata for some module types such as
1312*c2e18aaaSAndroid Build Coastguard Worker    # Robolectric. The information exported from the build is only used if
1313*c2e18aaaSAndroid Build Coastguard Worker    # the module does not have any installed paths.
1314*c2e18aaaSAndroid Build Coastguard Worker    # TODO(b/232929584): Remove this once all modules correctly export the
1315*c2e18aaaSAndroid Build Coastguard Worker    #  supported variants.
1316*c2e18aaaSAndroid Build Coastguard Worker    supported_configs = set(self.config_files.keys())
1317*c2e18aaaSAndroid Build Coastguard Worker    if supported_configs:
1318*c2e18aaaSAndroid Build Coastguard Worker      return supported_configs
1319*c2e18aaaSAndroid Build Coastguard Worker
1320*c2e18aaaSAndroid Build Coastguard Worker    return self._supported_configs
1321*c2e18aaaSAndroid Build Coastguard Worker
1322*c2e18aaaSAndroid Build Coastguard Worker  def dependencies(self) -> List[ModuleRef]:
1323*c2e18aaaSAndroid Build Coastguard Worker    all_deps = set(self.deps.runtime_dep_refs)
1324*c2e18aaaSAndroid Build Coastguard Worker    all_deps.update(self.deps.data_dep_refs)
1325*c2e18aaaSAndroid Build Coastguard Worker    all_deps.update(self.deps.device_data_dep_refs)
1326*c2e18aaaSAndroid Build Coastguard Worker    all_deps.update(self.deps.static_dep_refs)
1327*c2e18aaaSAndroid Build Coastguard Worker    return list(all_deps)
1328*c2e18aaaSAndroid Build Coastguard Worker
1329*c2e18aaaSAndroid Build Coastguard Worker  def write_to_build_file(self, f: IO):
1330*c2e18aaaSAndroid Build Coastguard Worker    writer = IndentWriter(f)
1331*c2e18aaaSAndroid Build Coastguard Worker    build_file_writer = BuildFileWriter(writer)
1332*c2e18aaaSAndroid Build Coastguard Worker
1333*c2e18aaaSAndroid Build Coastguard Worker    writer.write_line(f'{self._rule_name()}(')
1334*c2e18aaaSAndroid Build Coastguard Worker
1335*c2e18aaaSAndroid Build Coastguard Worker    with writer.indent():
1336*c2e18aaaSAndroid Build Coastguard Worker      writer.write_line(f'name = "{self._target_name}",')
1337*c2e18aaaSAndroid Build Coastguard Worker      writer.write_line(f'module_name = "{self._module_name}",')
1338*c2e18aaaSAndroid Build Coastguard Worker      self._write_files_attribute(writer)
1339*c2e18aaaSAndroid Build Coastguard Worker      self._write_deps_attribute(
1340*c2e18aaaSAndroid Build Coastguard Worker          writer, 'static_deps', self.deps.static_dep_refs
1341*c2e18aaaSAndroid Build Coastguard Worker      )
1342*c2e18aaaSAndroid Build Coastguard Worker      self._write_deps_attribute(
1343*c2e18aaaSAndroid Build Coastguard Worker          writer, 'runtime_deps', self.deps.runtime_dep_refs
1344*c2e18aaaSAndroid Build Coastguard Worker      )
1345*c2e18aaaSAndroid Build Coastguard Worker      self._write_deps_attribute(writer, 'data', self.deps.data_dep_refs)
1346*c2e18aaaSAndroid Build Coastguard Worker
1347*c2e18aaaSAndroid Build Coastguard Worker      build_file_writer.write_label_list_attribute(
1348*c2e18aaaSAndroid Build Coastguard Worker          'device_data', self.deps.device_data_dep_refs
1349*c2e18aaaSAndroid Build Coastguard Worker      )
1350*c2e18aaaSAndroid Build Coastguard Worker      build_file_writer.write_string_list_attribute(
1351*c2e18aaaSAndroid Build Coastguard Worker          'suites', sorted(self.suites)
1352*c2e18aaaSAndroid Build Coastguard Worker      )
1353*c2e18aaaSAndroid Build Coastguard Worker
1354*c2e18aaaSAndroid Build Coastguard Worker    writer.write_line(')')
1355*c2e18aaaSAndroid Build Coastguard Worker
1356*c2e18aaaSAndroid Build Coastguard Worker  def create_filesystem_layout(self, package_dir: pathlib.Path):
1357*c2e18aaaSAndroid Build Coastguard Worker    prebuilts_dir = package_dir.joinpath(self._target_name)
1358*c2e18aaaSAndroid Build Coastguard Worker    prebuilts_dir.mkdir()
1359*c2e18aaaSAndroid Build Coastguard Worker
1360*c2e18aaaSAndroid Build Coastguard Worker    for config, files in self.config_files.items():
1361*c2e18aaaSAndroid Build Coastguard Worker      config_prebuilts_dir = prebuilts_dir.joinpath(config.name)
1362*c2e18aaaSAndroid Build Coastguard Worker      config_prebuilts_dir.mkdir()
1363*c2e18aaaSAndroid Build Coastguard Worker
1364*c2e18aaaSAndroid Build Coastguard Worker      for f in files:
1365*c2e18aaaSAndroid Build Coastguard Worker        rel_path = f.relative_to(config.out_path)
1366*c2e18aaaSAndroid Build Coastguard Worker        symlink = config_prebuilts_dir.joinpath(rel_path)
1367*c2e18aaaSAndroid Build Coastguard Worker        symlink.parent.mkdir(parents=True, exist_ok=True)
1368*c2e18aaaSAndroid Build Coastguard Worker        symlink.symlink_to(f)
1369*c2e18aaaSAndroid Build Coastguard Worker
1370*c2e18aaaSAndroid Build Coastguard Worker  def _rule_name(self):
1371*c2e18aaaSAndroid Build Coastguard Worker    return (
1372*c2e18aaaSAndroid Build Coastguard Worker        'soong_prebuilt' if self.config_files else 'soong_uninstalled_prebuilt'
1373*c2e18aaaSAndroid Build Coastguard Worker    )
1374*c2e18aaaSAndroid Build Coastguard Worker
1375*c2e18aaaSAndroid Build Coastguard Worker  def _write_files_attribute(self, writer: IndentWriter):
1376*c2e18aaaSAndroid Build Coastguard Worker    if not self.config_files:
1377*c2e18aaaSAndroid Build Coastguard Worker      return
1378*c2e18aaaSAndroid Build Coastguard Worker
1379*c2e18aaaSAndroid Build Coastguard Worker    writer.write('files = ')
1380*c2e18aaaSAndroid Build Coastguard Worker    write_config_select(
1381*c2e18aaaSAndroid Build Coastguard Worker        writer,
1382*c2e18aaaSAndroid Build Coastguard Worker        self.config_files,
1383*c2e18aaaSAndroid Build Coastguard Worker        lambda c, _: writer.write(
1384*c2e18aaaSAndroid Build Coastguard Worker            f'glob(["{self._target_name}/{c.name}/**/*"])'
1385*c2e18aaaSAndroid Build Coastguard Worker        ),
1386*c2e18aaaSAndroid Build Coastguard Worker    )
1387*c2e18aaaSAndroid Build Coastguard Worker    writer.write_line(',')
1388*c2e18aaaSAndroid Build Coastguard Worker
1389*c2e18aaaSAndroid Build Coastguard Worker  def _write_deps_attribute(self, writer, attribute_name, module_refs):
1390*c2e18aaaSAndroid Build Coastguard Worker    config_deps = filter_configs(
1391*c2e18aaaSAndroid Build Coastguard Worker        group_targets_by_config(r.target() for r in module_refs),
1392*c2e18aaaSAndroid Build Coastguard Worker        self.supported_configs(),
1393*c2e18aaaSAndroid Build Coastguard Worker    )
1394*c2e18aaaSAndroid Build Coastguard Worker
1395*c2e18aaaSAndroid Build Coastguard Worker    if not config_deps:
1396*c2e18aaaSAndroid Build Coastguard Worker      return
1397*c2e18aaaSAndroid Build Coastguard Worker
1398*c2e18aaaSAndroid Build Coastguard Worker    for config in self.supported_configs():
1399*c2e18aaaSAndroid Build Coastguard Worker      config_deps.setdefault(config, [])
1400*c2e18aaaSAndroid Build Coastguard Worker
1401*c2e18aaaSAndroid Build Coastguard Worker    writer.write(f'{attribute_name} = ')
1402*c2e18aaaSAndroid Build Coastguard Worker    write_config_select(
1403*c2e18aaaSAndroid Build Coastguard Worker        writer,
1404*c2e18aaaSAndroid Build Coastguard Worker        config_deps,
1405*c2e18aaaSAndroid Build Coastguard Worker        lambda _, targets: write_target_list(writer, targets),
1406*c2e18aaaSAndroid Build Coastguard Worker    )
1407*c2e18aaaSAndroid Build Coastguard Worker    writer.write_line(',')
1408*c2e18aaaSAndroid Build Coastguard Worker
1409*c2e18aaaSAndroid Build Coastguard Worker
1410*c2e18aaaSAndroid Build Coastguard Workerdef group_paths_by_config(
1411*c2e18aaaSAndroid Build Coastguard Worker    configs: List[Config], paths: List[pathlib.Path]
1412*c2e18aaaSAndroid Build Coastguard Worker) -> Dict[Config, List[pathlib.Path]]:
1413*c2e18aaaSAndroid Build Coastguard Worker
1414*c2e18aaaSAndroid Build Coastguard Worker  config_files = defaultdict(list)
1415*c2e18aaaSAndroid Build Coastguard Worker
1416*c2e18aaaSAndroid Build Coastguard Worker  for f in paths:
1417*c2e18aaaSAndroid Build Coastguard Worker    matching_configs = [c for c in configs if _is_relative_to(f, c.out_path)]
1418*c2e18aaaSAndroid Build Coastguard Worker
1419*c2e18aaaSAndroid Build Coastguard Worker    if not matching_configs:
1420*c2e18aaaSAndroid Build Coastguard Worker      continue
1421*c2e18aaaSAndroid Build Coastguard Worker
1422*c2e18aaaSAndroid Build Coastguard Worker    # The path can only appear in ANDROID_HOST_OUT for host target or
1423*c2e18aaaSAndroid Build Coastguard Worker    # ANDROID_PRODUCT_OUT, but cannot appear in both.
1424*c2e18aaaSAndroid Build Coastguard Worker    if len(matching_configs) > 1:
1425*c2e18aaaSAndroid Build Coastguard Worker      raise ValueError(
1426*c2e18aaaSAndroid Build Coastguard Worker          f'Installed path `{f}` is not in'
1427*c2e18aaaSAndroid Build Coastguard Worker          ' ANDROID_HOST_OUT or ANDROID_PRODUCT_OUT'
1428*c2e18aaaSAndroid Build Coastguard Worker      )
1429*c2e18aaaSAndroid Build Coastguard Worker
1430*c2e18aaaSAndroid Build Coastguard Worker    config_files[matching_configs[0]].append(f)
1431*c2e18aaaSAndroid Build Coastguard Worker
1432*c2e18aaaSAndroid Build Coastguard Worker  return config_files
1433*c2e18aaaSAndroid Build Coastguard Worker
1434*c2e18aaaSAndroid Build Coastguard Worker
1435*c2e18aaaSAndroid Build Coastguard Workerdef group_targets_by_config(
1436*c2e18aaaSAndroid Build Coastguard Worker    targets: List[Target],
1437*c2e18aaaSAndroid Build Coastguard Worker) -> Dict[Config, List[Target]]:
1438*c2e18aaaSAndroid Build Coastguard Worker
1439*c2e18aaaSAndroid Build Coastguard Worker  config_to_targets = defaultdict(list)
1440*c2e18aaaSAndroid Build Coastguard Worker
1441*c2e18aaaSAndroid Build Coastguard Worker  for target in targets:
1442*c2e18aaaSAndroid Build Coastguard Worker    for config in target.supported_configs():
1443*c2e18aaaSAndroid Build Coastguard Worker      config_to_targets[config].append(target)
1444*c2e18aaaSAndroid Build Coastguard Worker
1445*c2e18aaaSAndroid Build Coastguard Worker  return config_to_targets
1446*c2e18aaaSAndroid Build Coastguard Worker
1447*c2e18aaaSAndroid Build Coastguard Worker
1448*c2e18aaaSAndroid Build Coastguard Workerdef filter_configs(
1449*c2e18aaaSAndroid Build Coastguard Worker    config_dict: Dict[Config, Any],
1450*c2e18aaaSAndroid Build Coastguard Worker    configs: Set[Config],
1451*c2e18aaaSAndroid Build Coastguard Worker) -> Dict[Config, Any]:
1452*c2e18aaaSAndroid Build Coastguard Worker  return {k: v for (k, v) in config_dict.items() if k in configs}
1453*c2e18aaaSAndroid Build Coastguard Worker
1454*c2e18aaaSAndroid Build Coastguard Worker
1455*c2e18aaaSAndroid Build Coastguard Workerdef _is_relative_to(path1: pathlib.Path, path2: pathlib.Path) -> bool:
1456*c2e18aaaSAndroid Build Coastguard Worker  """Return True if the path is relative to another path or False."""
1457*c2e18aaaSAndroid Build Coastguard Worker  # Note that this implementation is required because Path.is_relative_to only
1458*c2e18aaaSAndroid Build Coastguard Worker  # exists starting with Python 3.9.
1459*c2e18aaaSAndroid Build Coastguard Worker  try:
1460*c2e18aaaSAndroid Build Coastguard Worker    path1.relative_to(path2)
1461*c2e18aaaSAndroid Build Coastguard Worker    return True
1462*c2e18aaaSAndroid Build Coastguard Worker  except ValueError:
1463*c2e18aaaSAndroid Build Coastguard Worker    return False
1464*c2e18aaaSAndroid Build Coastguard Worker
1465*c2e18aaaSAndroid Build Coastguard Worker
1466*c2e18aaaSAndroid Build Coastguard Workerdef get_module_installed_paths(
1467*c2e18aaaSAndroid Build Coastguard Worker    info: Dict[str, Any], src_root_path: pathlib.Path
1468*c2e18aaaSAndroid Build Coastguard Worker) -> List[pathlib.Path]:
1469*c2e18aaaSAndroid Build Coastguard Worker
1470*c2e18aaaSAndroid Build Coastguard Worker  # Install paths in module-info are usually relative to the Android
1471*c2e18aaaSAndroid Build Coastguard Worker  # source root ${ANDROID_BUILD_TOP}. When the output directory is
1472*c2e18aaaSAndroid Build Coastguard Worker  # customized by the user however, the install paths are absolute.
1473*c2e18aaaSAndroid Build Coastguard Worker  def resolve(install_path_string):
1474*c2e18aaaSAndroid Build Coastguard Worker    install_path = pathlib.Path(install_path_string)
1475*c2e18aaaSAndroid Build Coastguard Worker    if not install_path.expanduser().is_absolute():
1476*c2e18aaaSAndroid Build Coastguard Worker      return src_root_path.joinpath(install_path)
1477*c2e18aaaSAndroid Build Coastguard Worker    return install_path
1478*c2e18aaaSAndroid Build Coastguard Worker
1479*c2e18aaaSAndroid Build Coastguard Worker  return map(resolve, info.get(constants.MODULE_INSTALLED, []))
1480*c2e18aaaSAndroid Build Coastguard Worker
1481*c2e18aaaSAndroid Build Coastguard Worker
1482*c2e18aaaSAndroid Build Coastguard Workerdef find_runtime_dep_refs(
1483*c2e18aaaSAndroid Build Coastguard Worker    mod_info: module_info.ModuleInfo,
1484*c2e18aaaSAndroid Build Coastguard Worker    info: module_info.Module,
1485*c2e18aaaSAndroid Build Coastguard Worker    configs: List[Config],
1486*c2e18aaaSAndroid Build Coastguard Worker    src_root_path: pathlib.Path,
1487*c2e18aaaSAndroid Build Coastguard Worker    enabled_features: List[Features],
1488*c2e18aaaSAndroid Build Coastguard Worker) -> List[ModuleRef]:
1489*c2e18aaaSAndroid Build Coastguard Worker  """Return module references for runtime dependencies."""
1490*c2e18aaaSAndroid Build Coastguard Worker
1491*c2e18aaaSAndroid Build Coastguard Worker  # We don't use the `dependencies` module-info field for shared libraries
1492*c2e18aaaSAndroid Build Coastguard Worker  # since it's ambiguous and could generate more targets and pull in more
1493*c2e18aaaSAndroid Build Coastguard Worker  # dependencies than necessary. In particular, libraries that support both
1494*c2e18aaaSAndroid Build Coastguard Worker  # static and dynamic linking could end up becoming runtime dependencies
1495*c2e18aaaSAndroid Build Coastguard Worker  # even though the build specifies static linking. For example, if a target
1496*c2e18aaaSAndroid Build Coastguard Worker  # 'T' is statically linked to 'U' which supports both variants, the latter
1497*c2e18aaaSAndroid Build Coastguard Worker  # still appears as a dependency. Since we can't tell, this would result in
1498*c2e18aaaSAndroid Build Coastguard Worker  # the shared library variant of 'U' being added on the library path.
1499*c2e18aaaSAndroid Build Coastguard Worker  libs = set()
1500*c2e18aaaSAndroid Build Coastguard Worker  libs.update(info.get(constants.MODULE_SHARED_LIBS, []))
1501*c2e18aaaSAndroid Build Coastguard Worker  libs.update(info.get(constants.MODULE_RUNTIME_DEPS, []))
1502*c2e18aaaSAndroid Build Coastguard Worker
1503*c2e18aaaSAndroid Build Coastguard Worker  if Features.EXPERIMENTAL_JAVA_RUNTIME_DEPENDENCIES in enabled_features:
1504*c2e18aaaSAndroid Build Coastguard Worker    libs.update(info.get(constants.MODULE_LIBS, []))
1505*c2e18aaaSAndroid Build Coastguard Worker
1506*c2e18aaaSAndroid Build Coastguard Worker  runtime_dep_refs = _find_module_refs(mod_info, configs, src_root_path, libs)
1507*c2e18aaaSAndroid Build Coastguard Worker
1508*c2e18aaaSAndroid Build Coastguard Worker  runtime_library_class = {'RLIB_LIBRARIES', 'DYLIB_LIBRARIES'}
1509*c2e18aaaSAndroid Build Coastguard Worker  # We collect rlibs even though they are technically static libraries since
1510*c2e18aaaSAndroid Build Coastguard Worker  # they could refer to dylibs which are required at runtime. Generating
1511*c2e18aaaSAndroid Build Coastguard Worker  # Bazel targets for these intermediate modules keeps the generator simple
1512*c2e18aaaSAndroid Build Coastguard Worker  # and preserves the shape (isomorphic) of the Soong structure making the
1513*c2e18aaaSAndroid Build Coastguard Worker  # workspace easier to debug.
1514*c2e18aaaSAndroid Build Coastguard Worker  for dep_name in info.get(constants.MODULE_DEPENDENCIES, []):
1515*c2e18aaaSAndroid Build Coastguard Worker    dep_info = mod_info.get_module_info(dep_name)
1516*c2e18aaaSAndroid Build Coastguard Worker    if not dep_info:
1517*c2e18aaaSAndroid Build Coastguard Worker      continue
1518*c2e18aaaSAndroid Build Coastguard Worker    if not runtime_library_class.intersection(
1519*c2e18aaaSAndroid Build Coastguard Worker        dep_info.get(constants.MODULE_CLASS, [])
1520*c2e18aaaSAndroid Build Coastguard Worker    ):
1521*c2e18aaaSAndroid Build Coastguard Worker      continue
1522*c2e18aaaSAndroid Build Coastguard Worker    runtime_dep_refs.append(ModuleRef.for_info(dep_info))
1523*c2e18aaaSAndroid Build Coastguard Worker
1524*c2e18aaaSAndroid Build Coastguard Worker  return runtime_dep_refs
1525*c2e18aaaSAndroid Build Coastguard Worker
1526*c2e18aaaSAndroid Build Coastguard Worker
1527*c2e18aaaSAndroid Build Coastguard Workerdef find_data_dep_refs(
1528*c2e18aaaSAndroid Build Coastguard Worker    mod_info: module_info.ModuleInfo,
1529*c2e18aaaSAndroid Build Coastguard Worker    info: module_info.Module,
1530*c2e18aaaSAndroid Build Coastguard Worker    configs: List[Config],
1531*c2e18aaaSAndroid Build Coastguard Worker    src_root_path: pathlib.Path,
1532*c2e18aaaSAndroid Build Coastguard Worker) -> List[ModuleRef]:
1533*c2e18aaaSAndroid Build Coastguard Worker  """Return module references for data dependencies."""
1534*c2e18aaaSAndroid Build Coastguard Worker
1535*c2e18aaaSAndroid Build Coastguard Worker  return _find_module_refs(
1536*c2e18aaaSAndroid Build Coastguard Worker      mod_info, configs, src_root_path, info.get(constants.MODULE_DATA_DEPS, [])
1537*c2e18aaaSAndroid Build Coastguard Worker  )
1538*c2e18aaaSAndroid Build Coastguard Worker
1539*c2e18aaaSAndroid Build Coastguard Worker
1540*c2e18aaaSAndroid Build Coastguard Workerdef find_device_data_dep_refs(
1541*c2e18aaaSAndroid Build Coastguard Worker    gen: WorkspaceGenerator,
1542*c2e18aaaSAndroid Build Coastguard Worker    info: module_info.Module,
1543*c2e18aaaSAndroid Build Coastguard Worker) -> List[ModuleRef]:
1544*c2e18aaaSAndroid Build Coastguard Worker  """Return module references for device data dependencies."""
1545*c2e18aaaSAndroid Build Coastguard Worker
1546*c2e18aaaSAndroid Build Coastguard Worker  return _find_module_refs(
1547*c2e18aaaSAndroid Build Coastguard Worker      gen.mod_info,
1548*c2e18aaaSAndroid Build Coastguard Worker      [Config('device', gen.resource_manager.get_product_out_file_path())],
1549*c2e18aaaSAndroid Build Coastguard Worker      gen.resource_manager.get_src_file_path(),
1550*c2e18aaaSAndroid Build Coastguard Worker      info.get(constants.MODULE_TARGET_DEPS, []),
1551*c2e18aaaSAndroid Build Coastguard Worker  )
1552*c2e18aaaSAndroid Build Coastguard Worker
1553*c2e18aaaSAndroid Build Coastguard Worker
1554*c2e18aaaSAndroid Build Coastguard Workerdef find_static_dep_refs(
1555*c2e18aaaSAndroid Build Coastguard Worker    mod_info: module_info.ModuleInfo,
1556*c2e18aaaSAndroid Build Coastguard Worker    info: module_info.Module,
1557*c2e18aaaSAndroid Build Coastguard Worker    configs: List[Config],
1558*c2e18aaaSAndroid Build Coastguard Worker    src_root_path: pathlib.Path,
1559*c2e18aaaSAndroid Build Coastguard Worker    enabled_features: List[Features],
1560*c2e18aaaSAndroid Build Coastguard Worker) -> List[ModuleRef]:
1561*c2e18aaaSAndroid Build Coastguard Worker  """Return module references for static libraries."""
1562*c2e18aaaSAndroid Build Coastguard Worker
1563*c2e18aaaSAndroid Build Coastguard Worker  if Features.EXPERIMENTAL_JAVA_RUNTIME_DEPENDENCIES not in enabled_features:
1564*c2e18aaaSAndroid Build Coastguard Worker    return []
1565*c2e18aaaSAndroid Build Coastguard Worker
1566*c2e18aaaSAndroid Build Coastguard Worker  static_libs = set()
1567*c2e18aaaSAndroid Build Coastguard Worker  static_libs.update(info.get(constants.MODULE_STATIC_LIBS, []))
1568*c2e18aaaSAndroid Build Coastguard Worker  static_libs.update(info.get(constants.MODULE_STATIC_DEPS, []))
1569*c2e18aaaSAndroid Build Coastguard Worker
1570*c2e18aaaSAndroid Build Coastguard Worker  return _find_module_refs(mod_info, configs, src_root_path, static_libs)
1571*c2e18aaaSAndroid Build Coastguard Worker
1572*c2e18aaaSAndroid Build Coastguard Worker
1573*c2e18aaaSAndroid Build Coastguard Workerdef _find_module_refs(
1574*c2e18aaaSAndroid Build Coastguard Worker    mod_info: module_info.ModuleInfo,
1575*c2e18aaaSAndroid Build Coastguard Worker    configs: List[Config],
1576*c2e18aaaSAndroid Build Coastguard Worker    src_root_path: pathlib.Path,
1577*c2e18aaaSAndroid Build Coastguard Worker    module_names: List[str],
1578*c2e18aaaSAndroid Build Coastguard Worker) -> List[ModuleRef]:
1579*c2e18aaaSAndroid Build Coastguard Worker  """Return module references for modules."""
1580*c2e18aaaSAndroid Build Coastguard Worker
1581*c2e18aaaSAndroid Build Coastguard Worker  module_refs = []
1582*c2e18aaaSAndroid Build Coastguard Worker
1583*c2e18aaaSAndroid Build Coastguard Worker  for name in module_names:
1584*c2e18aaaSAndroid Build Coastguard Worker    info = mod_info.get_module_info(name)
1585*c2e18aaaSAndroid Build Coastguard Worker    if not info:
1586*c2e18aaaSAndroid Build Coastguard Worker      continue
1587*c2e18aaaSAndroid Build Coastguard Worker
1588*c2e18aaaSAndroid Build Coastguard Worker    installed_paths = get_module_installed_paths(info, src_root_path)
1589*c2e18aaaSAndroid Build Coastguard Worker    config_files = group_paths_by_config(configs, installed_paths)
1590*c2e18aaaSAndroid Build Coastguard Worker    if not config_files:
1591*c2e18aaaSAndroid Build Coastguard Worker      continue
1592*c2e18aaaSAndroid Build Coastguard Worker
1593*c2e18aaaSAndroid Build Coastguard Worker    module_refs.append(ModuleRef.for_info(info))
1594*c2e18aaaSAndroid Build Coastguard Worker
1595*c2e18aaaSAndroid Build Coastguard Worker  return module_refs
1596*c2e18aaaSAndroid Build Coastguard Worker
1597*c2e18aaaSAndroid Build Coastguard Worker
1598*c2e18aaaSAndroid Build Coastguard Workerclass IndentWriter:
1599*c2e18aaaSAndroid Build Coastguard Worker
1600*c2e18aaaSAndroid Build Coastguard Worker  def __init__(self, f: IO):
1601*c2e18aaaSAndroid Build Coastguard Worker    self._file = f
1602*c2e18aaaSAndroid Build Coastguard Worker    self._indent_level = 0
1603*c2e18aaaSAndroid Build Coastguard Worker    self._indent_string = 4 * ' '
1604*c2e18aaaSAndroid Build Coastguard Worker    self._indent_next = True
1605*c2e18aaaSAndroid Build Coastguard Worker
1606*c2e18aaaSAndroid Build Coastguard Worker  def write_line(self, text: str = ''):
1607*c2e18aaaSAndroid Build Coastguard Worker    if text:
1608*c2e18aaaSAndroid Build Coastguard Worker      self.write(text)
1609*c2e18aaaSAndroid Build Coastguard Worker
1610*c2e18aaaSAndroid Build Coastguard Worker    self._file.write('\n')
1611*c2e18aaaSAndroid Build Coastguard Worker    self._indent_next = True
1612*c2e18aaaSAndroid Build Coastguard Worker
1613*c2e18aaaSAndroid Build Coastguard Worker  def write(self, text):
1614*c2e18aaaSAndroid Build Coastguard Worker    if self._indent_next:
1615*c2e18aaaSAndroid Build Coastguard Worker      self._file.write(self._indent_string * self._indent_level)
1616*c2e18aaaSAndroid Build Coastguard Worker      self._indent_next = False
1617*c2e18aaaSAndroid Build Coastguard Worker
1618*c2e18aaaSAndroid Build Coastguard Worker    self._file.write(text)
1619*c2e18aaaSAndroid Build Coastguard Worker
1620*c2e18aaaSAndroid Build Coastguard Worker  @contextlib.contextmanager
1621*c2e18aaaSAndroid Build Coastguard Worker  def indent(self):
1622*c2e18aaaSAndroid Build Coastguard Worker    self._indent_level += 1
1623*c2e18aaaSAndroid Build Coastguard Worker    yield
1624*c2e18aaaSAndroid Build Coastguard Worker    self._indent_level -= 1
1625*c2e18aaaSAndroid Build Coastguard Worker
1626*c2e18aaaSAndroid Build Coastguard Worker
1627*c2e18aaaSAndroid Build Coastguard Workerdef write_config_select(
1628*c2e18aaaSAndroid Build Coastguard Worker    writer: IndentWriter,
1629*c2e18aaaSAndroid Build Coastguard Worker    config_dict: Dict[Config, Any],
1630*c2e18aaaSAndroid Build Coastguard Worker    write_value_fn: Callable,
1631*c2e18aaaSAndroid Build Coastguard Worker):
1632*c2e18aaaSAndroid Build Coastguard Worker  writer.write_line('select({')
1633*c2e18aaaSAndroid Build Coastguard Worker
1634*c2e18aaaSAndroid Build Coastguard Worker  with writer.indent():
1635*c2e18aaaSAndroid Build Coastguard Worker    for config, value in sorted(config_dict.items(), key=lambda c: c[0].name):
1636*c2e18aaaSAndroid Build Coastguard Worker
1637*c2e18aaaSAndroid Build Coastguard Worker      writer.write(f'"//bazel/rules:{config.name}": ')
1638*c2e18aaaSAndroid Build Coastguard Worker      write_value_fn(config, value)
1639*c2e18aaaSAndroid Build Coastguard Worker      writer.write_line(',')
1640*c2e18aaaSAndroid Build Coastguard Worker
1641*c2e18aaaSAndroid Build Coastguard Worker  writer.write('})')
1642*c2e18aaaSAndroid Build Coastguard Worker
1643*c2e18aaaSAndroid Build Coastguard Worker
1644*c2e18aaaSAndroid Build Coastguard Workerdef write_target_list(writer: IndentWriter, targets: List[Target]):
1645*c2e18aaaSAndroid Build Coastguard Worker  writer.write_line('[')
1646*c2e18aaaSAndroid Build Coastguard Worker
1647*c2e18aaaSAndroid Build Coastguard Worker  with writer.indent():
1648*c2e18aaaSAndroid Build Coastguard Worker    for label in sorted(set(t.qualified_name() for t in targets)):
1649*c2e18aaaSAndroid Build Coastguard Worker      writer.write_line(f'"{label}",')
1650*c2e18aaaSAndroid Build Coastguard Worker
1651*c2e18aaaSAndroid Build Coastguard Worker  writer.write(']')
1652*c2e18aaaSAndroid Build Coastguard Worker
1653*c2e18aaaSAndroid Build Coastguard Worker
1654*c2e18aaaSAndroid Build Coastguard Workerdef _decorate_find_method(mod_info, finder_method_func, host, enabled_features):
1655*c2e18aaaSAndroid Build Coastguard Worker  """A finder_method decorator to override TestInfo properties."""
1656*c2e18aaaSAndroid Build Coastguard Worker
1657*c2e18aaaSAndroid Build Coastguard Worker  def use_bazel_runner(finder_obj, test_id):
1658*c2e18aaaSAndroid Build Coastguard Worker    test_infos = finder_method_func(finder_obj, test_id)
1659*c2e18aaaSAndroid Build Coastguard Worker    if not test_infos:
1660*c2e18aaaSAndroid Build Coastguard Worker      return test_infos
1661*c2e18aaaSAndroid Build Coastguard Worker    for tinfo in test_infos:
1662*c2e18aaaSAndroid Build Coastguard Worker      m_info = mod_info.get_module_info(tinfo.test_name)
1663*c2e18aaaSAndroid Build Coastguard Worker
1664*c2e18aaaSAndroid Build Coastguard Worker      # TODO(b/262200630): Refactor the duplicated logic in
1665*c2e18aaaSAndroid Build Coastguard Worker      # _decorate_find_method() and _add_test_module_targets() to
1666*c2e18aaaSAndroid Build Coastguard Worker      # determine whether a test should run with Atest Bazel Mode.
1667*c2e18aaaSAndroid Build Coastguard Worker
1668*c2e18aaaSAndroid Build Coastguard Worker      # Only enable modern Robolectric tests since those are the only ones
1669*c2e18aaaSAndroid Build Coastguard Worker      # TF currently supports.
1670*c2e18aaaSAndroid Build Coastguard Worker      if mod_info.is_modern_robolectric_test(m_info):
1671*c2e18aaaSAndroid Build Coastguard Worker        if Features.EXPERIMENTAL_ROBOLECTRIC_TEST in enabled_features:
1672*c2e18aaaSAndroid Build Coastguard Worker          tinfo.test_runner = BazelTestRunner.NAME
1673*c2e18aaaSAndroid Build Coastguard Worker        continue
1674*c2e18aaaSAndroid Build Coastguard Worker
1675*c2e18aaaSAndroid Build Coastguard Worker      # Only run device-driven tests in Bazel mode when '--host' is not
1676*c2e18aaaSAndroid Build Coastguard Worker      # specified and the feature is enabled.
1677*c2e18aaaSAndroid Build Coastguard Worker      if not host and mod_info.is_device_driven_test(m_info):
1678*c2e18aaaSAndroid Build Coastguard Worker        if Features.EXPERIMENTAL_DEVICE_DRIVEN_TEST in enabled_features:
1679*c2e18aaaSAndroid Build Coastguard Worker          tinfo.test_runner = BazelTestRunner.NAME
1680*c2e18aaaSAndroid Build Coastguard Worker        continue
1681*c2e18aaaSAndroid Build Coastguard Worker
1682*c2e18aaaSAndroid Build Coastguard Worker      if mod_info.is_suite_in_compatibility_suites(
1683*c2e18aaaSAndroid Build Coastguard Worker          'host-unit-tests', m_info
1684*c2e18aaaSAndroid Build Coastguard Worker      ) or (
1685*c2e18aaaSAndroid Build Coastguard Worker          Features.EXPERIMENTAL_HOST_DRIVEN_TEST in enabled_features
1686*c2e18aaaSAndroid Build Coastguard Worker          and mod_info.is_host_driven_test(m_info)
1687*c2e18aaaSAndroid Build Coastguard Worker      ):
1688*c2e18aaaSAndroid Build Coastguard Worker        tinfo.test_runner = BazelTestRunner.NAME
1689*c2e18aaaSAndroid Build Coastguard Worker    return test_infos
1690*c2e18aaaSAndroid Build Coastguard Worker
1691*c2e18aaaSAndroid Build Coastguard Worker  return use_bazel_runner
1692*c2e18aaaSAndroid Build Coastguard Worker
1693*c2e18aaaSAndroid Build Coastguard Worker
1694*c2e18aaaSAndroid Build Coastguard Workerdef create_new_finder(
1695*c2e18aaaSAndroid Build Coastguard Worker    mod_info: module_info.ModuleInfo,
1696*c2e18aaaSAndroid Build Coastguard Worker    finder: test_finder_base.TestFinderBase,
1697*c2e18aaaSAndroid Build Coastguard Worker    host: bool,
1698*c2e18aaaSAndroid Build Coastguard Worker    enabled_features: List[Features] = None,
1699*c2e18aaaSAndroid Build Coastguard Worker):
1700*c2e18aaaSAndroid Build Coastguard Worker  """Create new test_finder_base.Finder with decorated find_method.
1701*c2e18aaaSAndroid Build Coastguard Worker
1702*c2e18aaaSAndroid Build Coastguard Worker  Args:
1703*c2e18aaaSAndroid Build Coastguard Worker    mod_info: ModuleInfo object.
1704*c2e18aaaSAndroid Build Coastguard Worker    finder: Test Finder class.
1705*c2e18aaaSAndroid Build Coastguard Worker    host: Whether to run the host variant.
1706*c2e18aaaSAndroid Build Coastguard Worker    enabled_features: List of enabled features.
1707*c2e18aaaSAndroid Build Coastguard Worker
1708*c2e18aaaSAndroid Build Coastguard Worker  Returns:
1709*c2e18aaaSAndroid Build Coastguard Worker      List of ordered find methods.
1710*c2e18aaaSAndroid Build Coastguard Worker  """
1711*c2e18aaaSAndroid Build Coastguard Worker  return test_finder_base.Finder(
1712*c2e18aaaSAndroid Build Coastguard Worker      finder.test_finder_instance,
1713*c2e18aaaSAndroid Build Coastguard Worker      _decorate_find_method(
1714*c2e18aaaSAndroid Build Coastguard Worker          mod_info, finder.find_method, host, enabled_features or []
1715*c2e18aaaSAndroid Build Coastguard Worker      ),
1716*c2e18aaaSAndroid Build Coastguard Worker      finder.finder_info,
1717*c2e18aaaSAndroid Build Coastguard Worker  )
1718*c2e18aaaSAndroid Build Coastguard Worker
1719*c2e18aaaSAndroid Build Coastguard Worker
1720*c2e18aaaSAndroid Build Coastguard Workerclass RunCommandError(subprocess.CalledProcessError):
1721*c2e18aaaSAndroid Build Coastguard Worker  """CalledProcessError but including debug information when it fails."""
1722*c2e18aaaSAndroid Build Coastguard Worker
1723*c2e18aaaSAndroid Build Coastguard Worker  def __str__(self):
1724*c2e18aaaSAndroid Build Coastguard Worker    return f'{super().__str__()}\nstdout={self.stdout}\n\nstderr={self.stderr}'
1725*c2e18aaaSAndroid Build Coastguard Worker
1726*c2e18aaaSAndroid Build Coastguard Worker
1727*c2e18aaaSAndroid Build Coastguard Workerdef default_run_command(args: List[str], cwd: pathlib.Path) -> str:
1728*c2e18aaaSAndroid Build Coastguard Worker  result = subprocess.run(
1729*c2e18aaaSAndroid Build Coastguard Worker      args=args,
1730*c2e18aaaSAndroid Build Coastguard Worker      cwd=cwd,
1731*c2e18aaaSAndroid Build Coastguard Worker      text=True,
1732*c2e18aaaSAndroid Build Coastguard Worker      capture_output=True,
1733*c2e18aaaSAndroid Build Coastguard Worker      check=False,
1734*c2e18aaaSAndroid Build Coastguard Worker  )
1735*c2e18aaaSAndroid Build Coastguard Worker  if result.returncode:
1736*c2e18aaaSAndroid Build Coastguard Worker    # Provide a more detailed log message including stdout and stderr.
1737*c2e18aaaSAndroid Build Coastguard Worker    raise RunCommandError(
1738*c2e18aaaSAndroid Build Coastguard Worker        result.returncode, result.args, result.stdout, result.stderr
1739*c2e18aaaSAndroid Build Coastguard Worker    )
1740*c2e18aaaSAndroid Build Coastguard Worker  return result.stdout
1741*c2e18aaaSAndroid Build Coastguard Worker
1742*c2e18aaaSAndroid Build Coastguard Worker
1743*c2e18aaaSAndroid Build Coastguard Worker@dataclasses.dataclass
1744*c2e18aaaSAndroid Build Coastguard Workerclass BuildMetadata:
1745*c2e18aaaSAndroid Build Coastguard Worker  build_branch: str
1746*c2e18aaaSAndroid Build Coastguard Worker  build_target: str
1747*c2e18aaaSAndroid Build Coastguard Worker
1748*c2e18aaaSAndroid Build Coastguard Worker
1749*c2e18aaaSAndroid Build Coastguard Workerclass BazelTestRunner(trb.TestRunnerBase):
1750*c2e18aaaSAndroid Build Coastguard Worker  """Bazel Test Runner class."""
1751*c2e18aaaSAndroid Build Coastguard Worker
1752*c2e18aaaSAndroid Build Coastguard Worker  NAME = 'BazelTestRunner'
1753*c2e18aaaSAndroid Build Coastguard Worker  EXECUTABLE = 'none'
1754*c2e18aaaSAndroid Build Coastguard Worker
1755*c2e18aaaSAndroid Build Coastguard Worker  # pylint: disable=redefined-outer-name
1756*c2e18aaaSAndroid Build Coastguard Worker  # pylint: disable=too-many-arguments
1757*c2e18aaaSAndroid Build Coastguard Worker  def __init__(
1758*c2e18aaaSAndroid Build Coastguard Worker      self,
1759*c2e18aaaSAndroid Build Coastguard Worker      results_dir,
1760*c2e18aaaSAndroid Build Coastguard Worker      mod_info: module_info.ModuleInfo,
1761*c2e18aaaSAndroid Build Coastguard Worker      extra_args: Dict[str, Any] = None,
1762*c2e18aaaSAndroid Build Coastguard Worker      src_top: pathlib.Path = None,
1763*c2e18aaaSAndroid Build Coastguard Worker      workspace_path: pathlib.Path = None,
1764*c2e18aaaSAndroid Build Coastguard Worker      run_command: Callable = default_run_command,
1765*c2e18aaaSAndroid Build Coastguard Worker      build_metadata: BuildMetadata = None,
1766*c2e18aaaSAndroid Build Coastguard Worker      env: Dict[str, str] = None,
1767*c2e18aaaSAndroid Build Coastguard Worker      generate_workspace_fn: Callable = generate_bazel_workspace,
1768*c2e18aaaSAndroid Build Coastguard Worker      enabled_features: Set[str] = None,
1769*c2e18aaaSAndroid Build Coastguard Worker      **kwargs,
1770*c2e18aaaSAndroid Build Coastguard Worker  ):
1771*c2e18aaaSAndroid Build Coastguard Worker    super().__init__(results_dir, **kwargs)
1772*c2e18aaaSAndroid Build Coastguard Worker    self.mod_info = mod_info
1773*c2e18aaaSAndroid Build Coastguard Worker    self.src_top = src_top or pathlib.Path(
1774*c2e18aaaSAndroid Build Coastguard Worker        os.environ.get(constants.ANDROID_BUILD_TOP)
1775*c2e18aaaSAndroid Build Coastguard Worker    )
1776*c2e18aaaSAndroid Build Coastguard Worker    self.starlark_file = _get_resource_root().joinpath(
1777*c2e18aaaSAndroid Build Coastguard Worker        'format_as_soong_module_name.cquery'
1778*c2e18aaaSAndroid Build Coastguard Worker    )
1779*c2e18aaaSAndroid Build Coastguard Worker
1780*c2e18aaaSAndroid Build Coastguard Worker    self.bazel_workspace = workspace_path or get_bazel_workspace_dir()
1781*c2e18aaaSAndroid Build Coastguard Worker    self.bazel_binary = self.bazel_workspace.joinpath('bazel.sh')
1782*c2e18aaaSAndroid Build Coastguard Worker    self.run_command = run_command
1783*c2e18aaaSAndroid Build Coastguard Worker    self._extra_args = extra_args or {}
1784*c2e18aaaSAndroid Build Coastguard Worker    self.build_metadata = build_metadata or get_default_build_metadata()
1785*c2e18aaaSAndroid Build Coastguard Worker    self.env = env or os.environ
1786*c2e18aaaSAndroid Build Coastguard Worker    self._generate_workspace_fn = generate_workspace_fn
1787*c2e18aaaSAndroid Build Coastguard Worker    self._enabled_features = (
1788*c2e18aaaSAndroid Build Coastguard Worker        enabled_features
1789*c2e18aaaSAndroid Build Coastguard Worker        if enabled_features is not None
1790*c2e18aaaSAndroid Build Coastguard Worker        else atest_configs.GLOBAL_ARGS.bazel_mode_features
1791*c2e18aaaSAndroid Build Coastguard Worker    )
1792*c2e18aaaSAndroid Build Coastguard Worker
1793*c2e18aaaSAndroid Build Coastguard Worker  # pylint: disable=unused-argument
1794*c2e18aaaSAndroid Build Coastguard Worker  def run_tests(self, test_infos, extra_args, reporter):
1795*c2e18aaaSAndroid Build Coastguard Worker    """Run the list of test_infos.
1796*c2e18aaaSAndroid Build Coastguard Worker
1797*c2e18aaaSAndroid Build Coastguard Worker    Args:
1798*c2e18aaaSAndroid Build Coastguard Worker        test_infos: List of TestInfo.
1799*c2e18aaaSAndroid Build Coastguard Worker        extra_args: Dict of extra args to add to test run.
1800*c2e18aaaSAndroid Build Coastguard Worker        reporter: An instance of result_report.ResultReporter.
1801*c2e18aaaSAndroid Build Coastguard Worker    """
1802*c2e18aaaSAndroid Build Coastguard Worker    ret_code = ExitCode.SUCCESS
1803*c2e18aaaSAndroid Build Coastguard Worker
1804*c2e18aaaSAndroid Build Coastguard Worker    try:
1805*c2e18aaaSAndroid Build Coastguard Worker      run_cmds = self.generate_run_commands(test_infos, extra_args)
1806*c2e18aaaSAndroid Build Coastguard Worker    except AbortRunException as e:
1807*c2e18aaaSAndroid Build Coastguard Worker      atest_utils.colorful_print(f'Stop running test(s): {e}', constants.RED)
1808*c2e18aaaSAndroid Build Coastguard Worker      return ExitCode.ERROR
1809*c2e18aaaSAndroid Build Coastguard Worker
1810*c2e18aaaSAndroid Build Coastguard Worker    for run_cmd in run_cmds:
1811*c2e18aaaSAndroid Build Coastguard Worker      subproc = self.run(run_cmd, output_to_stdout=True)
1812*c2e18aaaSAndroid Build Coastguard Worker      ret_code |= self.wait_for_subprocess(subproc)
1813*c2e18aaaSAndroid Build Coastguard Worker
1814*c2e18aaaSAndroid Build Coastguard Worker    self.organize_test_logs(test_infos)
1815*c2e18aaaSAndroid Build Coastguard Worker
1816*c2e18aaaSAndroid Build Coastguard Worker    return ret_code
1817*c2e18aaaSAndroid Build Coastguard Worker
1818*c2e18aaaSAndroid Build Coastguard Worker  def organize_test_logs(self, test_infos: List[test_info.TestInfo]):
1819*c2e18aaaSAndroid Build Coastguard Worker    for t_info in test_infos:
1820*c2e18aaaSAndroid Build Coastguard Worker      test_output_dir, package_name, target_suffix = (
1821*c2e18aaaSAndroid Build Coastguard Worker          self.retrieve_test_output_info(t_info)
1822*c2e18aaaSAndroid Build Coastguard Worker      )
1823*c2e18aaaSAndroid Build Coastguard Worker      if test_output_dir.joinpath(TEST_OUTPUT_ZIP_NAME).exists():
1824*c2e18aaaSAndroid Build Coastguard Worker        # TEST_OUTPUT_ZIP file exist when BES uploading is enabled.
1825*c2e18aaaSAndroid Build Coastguard Worker        # Showing the BES link to users instead of the local log.
1826*c2e18aaaSAndroid Build Coastguard Worker        continue
1827*c2e18aaaSAndroid Build Coastguard Worker
1828*c2e18aaaSAndroid Build Coastguard Worker      # AtestExecutionInfo will find all log files in 'results_dir/log'
1829*c2e18aaaSAndroid Build Coastguard Worker      # directory and generate an HTML file to display to users when
1830*c2e18aaaSAndroid Build Coastguard Worker      # 'results_dir/log' directory exist.
1831*c2e18aaaSAndroid Build Coastguard Worker      log_path = pathlib.Path(self.results_dir).joinpath(
1832*c2e18aaaSAndroid Build Coastguard Worker          'log', f'{package_name}', f'{t_info.test_name}_{target_suffix}'
1833*c2e18aaaSAndroid Build Coastguard Worker      )
1834*c2e18aaaSAndroid Build Coastguard Worker      log_path.parent.mkdir(parents=True, exist_ok=True)
1835*c2e18aaaSAndroid Build Coastguard Worker      if not log_path.is_symlink():
1836*c2e18aaaSAndroid Build Coastguard Worker        log_path.symlink_to(test_output_dir)
1837*c2e18aaaSAndroid Build Coastguard Worker
1838*c2e18aaaSAndroid Build Coastguard Worker  def _get_feature_config_or_warn(self, feature, env_var_name):
1839*c2e18aaaSAndroid Build Coastguard Worker    feature_config = self.env.get(env_var_name)
1840*c2e18aaaSAndroid Build Coastguard Worker    if not feature_config:
1841*c2e18aaaSAndroid Build Coastguard Worker      atest_utils.print_and_log_warning(
1842*c2e18aaaSAndroid Build Coastguard Worker          'Ignoring `%s` because the `%s` environment variable is not set.',
1843*c2e18aaaSAndroid Build Coastguard Worker          # pylint: disable=no-member
1844*c2e18aaaSAndroid Build Coastguard Worker          feature,
1845*c2e18aaaSAndroid Build Coastguard Worker          env_var_name,
1846*c2e18aaaSAndroid Build Coastguard Worker      )
1847*c2e18aaaSAndroid Build Coastguard Worker    return feature_config
1848*c2e18aaaSAndroid Build Coastguard Worker
1849*c2e18aaaSAndroid Build Coastguard Worker  def _get_bes_publish_args(self, feature: Features) -> List[str]:
1850*c2e18aaaSAndroid Build Coastguard Worker    bes_publish_config = self._get_feature_config_or_warn(
1851*c2e18aaaSAndroid Build Coastguard Worker        feature, 'ATEST_BAZEL_BES_PUBLISH_CONFIG'
1852*c2e18aaaSAndroid Build Coastguard Worker    )
1853*c2e18aaaSAndroid Build Coastguard Worker
1854*c2e18aaaSAndroid Build Coastguard Worker    if not bes_publish_config:
1855*c2e18aaaSAndroid Build Coastguard Worker      return []
1856*c2e18aaaSAndroid Build Coastguard Worker
1857*c2e18aaaSAndroid Build Coastguard Worker    branch = self.build_metadata.build_branch
1858*c2e18aaaSAndroid Build Coastguard Worker    target = self.build_metadata.build_target
1859*c2e18aaaSAndroid Build Coastguard Worker
1860*c2e18aaaSAndroid Build Coastguard Worker    return [
1861*c2e18aaaSAndroid Build Coastguard Worker        f'--config={bes_publish_config}',
1862*c2e18aaaSAndroid Build Coastguard Worker        f'--build_metadata=ab_branch={branch}',
1863*c2e18aaaSAndroid Build Coastguard Worker        f'--build_metadata=ab_target={target}',
1864*c2e18aaaSAndroid Build Coastguard Worker    ]
1865*c2e18aaaSAndroid Build Coastguard Worker
1866*c2e18aaaSAndroid Build Coastguard Worker  def _get_remote_args(self, feature):
1867*c2e18aaaSAndroid Build Coastguard Worker    remote_config = self._get_feature_config_or_warn(
1868*c2e18aaaSAndroid Build Coastguard Worker        feature, 'ATEST_BAZEL_REMOTE_CONFIG'
1869*c2e18aaaSAndroid Build Coastguard Worker    )
1870*c2e18aaaSAndroid Build Coastguard Worker    if not remote_config:
1871*c2e18aaaSAndroid Build Coastguard Worker      return []
1872*c2e18aaaSAndroid Build Coastguard Worker    return [f'--config={remote_config}']
1873*c2e18aaaSAndroid Build Coastguard Worker
1874*c2e18aaaSAndroid Build Coastguard Worker  def _get_remote_avd_args(self, feature):
1875*c2e18aaaSAndroid Build Coastguard Worker    remote_avd_config = self._get_feature_config_or_warn(
1876*c2e18aaaSAndroid Build Coastguard Worker        feature, 'ATEST_BAZEL_REMOTE_AVD_CONFIG'
1877*c2e18aaaSAndroid Build Coastguard Worker    )
1878*c2e18aaaSAndroid Build Coastguard Worker    if not remote_avd_config:
1879*c2e18aaaSAndroid Build Coastguard Worker      raise ValueError(
1880*c2e18aaaSAndroid Build Coastguard Worker          'Cannot run remote device test because '
1881*c2e18aaaSAndroid Build Coastguard Worker          'ATEST_BAZEL_REMOTE_AVD_CONFIG '
1882*c2e18aaaSAndroid Build Coastguard Worker          'environment variable is not set.'
1883*c2e18aaaSAndroid Build Coastguard Worker      )
1884*c2e18aaaSAndroid Build Coastguard Worker    return [f'--config={remote_avd_config}']
1885*c2e18aaaSAndroid Build Coastguard Worker
1886*c2e18aaaSAndroid Build Coastguard Worker  def host_env_check(self):
1887*c2e18aaaSAndroid Build Coastguard Worker    """Check that host env has everything we need.
1888*c2e18aaaSAndroid Build Coastguard Worker
1889*c2e18aaaSAndroid Build Coastguard Worker    We actually can assume the host env is fine because we have the same
1890*c2e18aaaSAndroid Build Coastguard Worker    requirements that atest has. Update this to check for android env vars
1891*c2e18aaaSAndroid Build Coastguard Worker    if that changes.
1892*c2e18aaaSAndroid Build Coastguard Worker    """
1893*c2e18aaaSAndroid Build Coastguard Worker
1894*c2e18aaaSAndroid Build Coastguard Worker  def get_test_runner_build_reqs(self, test_infos) -> Set[str]:
1895*c2e18aaaSAndroid Build Coastguard Worker    if not test_infos:
1896*c2e18aaaSAndroid Build Coastguard Worker      return set()
1897*c2e18aaaSAndroid Build Coastguard Worker
1898*c2e18aaaSAndroid Build Coastguard Worker    self._generate_workspace_fn(
1899*c2e18aaaSAndroid Build Coastguard Worker        self.mod_info,
1900*c2e18aaaSAndroid Build Coastguard Worker        self._enabled_features,
1901*c2e18aaaSAndroid Build Coastguard Worker    )
1902*c2e18aaaSAndroid Build Coastguard Worker
1903*c2e18aaaSAndroid Build Coastguard Worker    deps_expression = ' + '.join(
1904*c2e18aaaSAndroid Build Coastguard Worker        sorted(self.test_info_target_label(i) for i in test_infos)
1905*c2e18aaaSAndroid Build Coastguard Worker    )
1906*c2e18aaaSAndroid Build Coastguard Worker
1907*c2e18aaaSAndroid Build Coastguard Worker    with tempfile.NamedTemporaryFile() as query_file:
1908*c2e18aaaSAndroid Build Coastguard Worker      with open(query_file.name, 'w', encoding='utf-8') as _query_file:
1909*c2e18aaaSAndroid Build Coastguard Worker        _query_file.write(f'deps(tests({deps_expression}))')
1910*c2e18aaaSAndroid Build Coastguard Worker
1911*c2e18aaaSAndroid Build Coastguard Worker      query_args = [
1912*c2e18aaaSAndroid Build Coastguard Worker          str(self.bazel_binary),
1913*c2e18aaaSAndroid Build Coastguard Worker          'cquery',
1914*c2e18aaaSAndroid Build Coastguard Worker          f'--query_file={query_file.name}',
1915*c2e18aaaSAndroid Build Coastguard Worker          '--output=starlark',
1916*c2e18aaaSAndroid Build Coastguard Worker          f'--starlark:file={self.starlark_file}',
1917*c2e18aaaSAndroid Build Coastguard Worker      ]
1918*c2e18aaaSAndroid Build Coastguard Worker
1919*c2e18aaaSAndroid Build Coastguard Worker      output = self.run_command(query_args, self.bazel_workspace)
1920*c2e18aaaSAndroid Build Coastguard Worker
1921*c2e18aaaSAndroid Build Coastguard Worker    targets = set()
1922*c2e18aaaSAndroid Build Coastguard Worker    robolectric_tests = set(
1923*c2e18aaaSAndroid Build Coastguard Worker        filter(
1924*c2e18aaaSAndroid Build Coastguard Worker            self._is_robolectric_test_suite,
1925*c2e18aaaSAndroid Build Coastguard Worker            [test.test_name for test in test_infos],
1926*c2e18aaaSAndroid Build Coastguard Worker        )
1927*c2e18aaaSAndroid Build Coastguard Worker    )
1928*c2e18aaaSAndroid Build Coastguard Worker
1929*c2e18aaaSAndroid Build Coastguard Worker    modules_to_variant = _parse_cquery_output(output)
1930*c2e18aaaSAndroid Build Coastguard Worker
1931*c2e18aaaSAndroid Build Coastguard Worker    for module, variants in modules_to_variant.items():
1932*c2e18aaaSAndroid Build Coastguard Worker
1933*c2e18aaaSAndroid Build Coastguard Worker      # Skip specifying the build variant for Robolectric test modules
1934*c2e18aaaSAndroid Build Coastguard Worker      # since they are special. Soong builds them with the `target`
1935*c2e18aaaSAndroid Build Coastguard Worker      # variant although are installed as 'host' modules.
1936*c2e18aaaSAndroid Build Coastguard Worker      if module in robolectric_tests:
1937*c2e18aaaSAndroid Build Coastguard Worker        targets.add(module)
1938*c2e18aaaSAndroid Build Coastguard Worker        continue
1939*c2e18aaaSAndroid Build Coastguard Worker
1940*c2e18aaaSAndroid Build Coastguard Worker      targets.add(_soong_target_for_variants(module, variants))
1941*c2e18aaaSAndroid Build Coastguard Worker
1942*c2e18aaaSAndroid Build Coastguard Worker    return targets
1943*c2e18aaaSAndroid Build Coastguard Worker
1944*c2e18aaaSAndroid Build Coastguard Worker  def _is_robolectric_test_suite(self, module_name: str) -> bool:
1945*c2e18aaaSAndroid Build Coastguard Worker    return self.mod_info.is_robolectric_test_suite(
1946*c2e18aaaSAndroid Build Coastguard Worker        self.mod_info.get_module_info(module_name)
1947*c2e18aaaSAndroid Build Coastguard Worker    )
1948*c2e18aaaSAndroid Build Coastguard Worker
1949*c2e18aaaSAndroid Build Coastguard Worker  def test_info_target_label(self, test: test_info.TestInfo) -> str:
1950*c2e18aaaSAndroid Build Coastguard Worker    module_name = test.test_name
1951*c2e18aaaSAndroid Build Coastguard Worker    info = self.mod_info.get_module_info(module_name)
1952*c2e18aaaSAndroid Build Coastguard Worker    package_name = info.get(constants.MODULE_PATH)[0]
1953*c2e18aaaSAndroid Build Coastguard Worker    target_suffix = self.get_target_suffix(info)
1954*c2e18aaaSAndroid Build Coastguard Worker
1955*c2e18aaaSAndroid Build Coastguard Worker    return f'//{package_name}:{module_name}_{target_suffix}'
1956*c2e18aaaSAndroid Build Coastguard Worker
1957*c2e18aaaSAndroid Build Coastguard Worker  def retrieve_test_output_info(
1958*c2e18aaaSAndroid Build Coastguard Worker      self, test_info: test_info.TestInfo
1959*c2e18aaaSAndroid Build Coastguard Worker  ) -> Tuple[pathlib.Path, str, str]:
1960*c2e18aaaSAndroid Build Coastguard Worker    """Return test output information.
1961*c2e18aaaSAndroid Build Coastguard Worker
1962*c2e18aaaSAndroid Build Coastguard Worker    Args:
1963*c2e18aaaSAndroid Build Coastguard Worker        test_info (test_info.TestInfo): Information about the test.
1964*c2e18aaaSAndroid Build Coastguard Worker
1965*c2e18aaaSAndroid Build Coastguard Worker    Returns:
1966*c2e18aaaSAndroid Build Coastguard Worker        Tuple[pathlib.Path, str, str]: A tuple containing the following
1967*c2e18aaaSAndroid Build Coastguard Worker        elements:
1968*c2e18aaaSAndroid Build Coastguard Worker            - test_output_dir (pathlib.Path): Absolute path of the test output
1969*c2e18aaaSAndroid Build Coastguard Worker                folder.
1970*c2e18aaaSAndroid Build Coastguard Worker            - package_name (str): Name of the package.
1971*c2e18aaaSAndroid Build Coastguard Worker            - target_suffix (str): Target suffix.
1972*c2e18aaaSAndroid Build Coastguard Worker    """
1973*c2e18aaaSAndroid Build Coastguard Worker    module_name = test_info.test_name
1974*c2e18aaaSAndroid Build Coastguard Worker    info = self.mod_info.get_module_info(module_name)
1975*c2e18aaaSAndroid Build Coastguard Worker    package_name = info.get(constants.MODULE_PATH)[0]
1976*c2e18aaaSAndroid Build Coastguard Worker    target_suffix = self.get_target_suffix(info)
1977*c2e18aaaSAndroid Build Coastguard Worker
1978*c2e18aaaSAndroid Build Coastguard Worker    test_output_dir = pathlib.Path(
1979*c2e18aaaSAndroid Build Coastguard Worker        self.bazel_workspace,
1980*c2e18aaaSAndroid Build Coastguard Worker        BAZEL_TEST_LOGS_DIR_NAME,
1981*c2e18aaaSAndroid Build Coastguard Worker        package_name,
1982*c2e18aaaSAndroid Build Coastguard Worker        f'{module_name}_{target_suffix}',
1983*c2e18aaaSAndroid Build Coastguard Worker        TEST_OUTPUT_DIR_NAME,
1984*c2e18aaaSAndroid Build Coastguard Worker    )
1985*c2e18aaaSAndroid Build Coastguard Worker
1986*c2e18aaaSAndroid Build Coastguard Worker    return test_output_dir, package_name, target_suffix
1987*c2e18aaaSAndroid Build Coastguard Worker
1988*c2e18aaaSAndroid Build Coastguard Worker  def get_target_suffix(self, info: Dict[str, Any]) -> str:
1989*c2e18aaaSAndroid Build Coastguard Worker    """Return 'host' or 'device' accordingly to the variant of the test."""
1990*c2e18aaaSAndroid Build Coastguard Worker    if not self._extra_args.get(
1991*c2e18aaaSAndroid Build Coastguard Worker        constants.HOST, False
1992*c2e18aaaSAndroid Build Coastguard Worker    ) and self.mod_info.is_device_driven_test(info):
1993*c2e18aaaSAndroid Build Coastguard Worker      return 'device'
1994*c2e18aaaSAndroid Build Coastguard Worker    return 'host'
1995*c2e18aaaSAndroid Build Coastguard Worker
1996*c2e18aaaSAndroid Build Coastguard Worker  @staticmethod
1997*c2e18aaaSAndroid Build Coastguard Worker  def _get_bazel_feature_args(
1998*c2e18aaaSAndroid Build Coastguard Worker      feature: Features, extra_args: Dict[str, Any], generator: Callable
1999*c2e18aaaSAndroid Build Coastguard Worker  ) -> List[str]:
2000*c2e18aaaSAndroid Build Coastguard Worker    if feature not in extra_args.get('BAZEL_MODE_FEATURES', []):
2001*c2e18aaaSAndroid Build Coastguard Worker      return []
2002*c2e18aaaSAndroid Build Coastguard Worker    return generator(feature)
2003*c2e18aaaSAndroid Build Coastguard Worker
2004*c2e18aaaSAndroid Build Coastguard Worker  # pylint: disable=unused-argument
2005*c2e18aaaSAndroid Build Coastguard Worker  def generate_run_commands(self, test_infos, extra_args, port=None):
2006*c2e18aaaSAndroid Build Coastguard Worker    """Generate a list of run commands from TestInfos.
2007*c2e18aaaSAndroid Build Coastguard Worker
2008*c2e18aaaSAndroid Build Coastguard Worker    Args:
2009*c2e18aaaSAndroid Build Coastguard Worker        test_infos: A set of TestInfo instances.
2010*c2e18aaaSAndroid Build Coastguard Worker        extra_args: A Dict of extra args to append.
2011*c2e18aaaSAndroid Build Coastguard Worker        port: Optional. An int of the port number to send events to.
2012*c2e18aaaSAndroid Build Coastguard Worker
2013*c2e18aaaSAndroid Build Coastguard Worker    Returns:
2014*c2e18aaaSAndroid Build Coastguard Worker        A list of run commands to run the tests.
2015*c2e18aaaSAndroid Build Coastguard Worker    """
2016*c2e18aaaSAndroid Build Coastguard Worker    startup_options = ''
2017*c2e18aaaSAndroid Build Coastguard Worker    bazelrc = self.env.get('ATEST_BAZELRC')
2018*c2e18aaaSAndroid Build Coastguard Worker
2019*c2e18aaaSAndroid Build Coastguard Worker    if bazelrc:
2020*c2e18aaaSAndroid Build Coastguard Worker      startup_options = f'--bazelrc={bazelrc}'
2021*c2e18aaaSAndroid Build Coastguard Worker
2022*c2e18aaaSAndroid Build Coastguard Worker    target_patterns = ' '.join(
2023*c2e18aaaSAndroid Build Coastguard Worker        self.test_info_target_label(i) for i in test_infos
2024*c2e18aaaSAndroid Build Coastguard Worker    )
2025*c2e18aaaSAndroid Build Coastguard Worker
2026*c2e18aaaSAndroid Build Coastguard Worker    bazel_args = parse_args(test_infos, extra_args)
2027*c2e18aaaSAndroid Build Coastguard Worker
2028*c2e18aaaSAndroid Build Coastguard Worker    # If BES is not enabled, use the option of
2029*c2e18aaaSAndroid Build Coastguard Worker    # '--nozip_undeclared_test_outputs' to not compress the test outputs.
2030*c2e18aaaSAndroid Build Coastguard Worker    # And the URL of test outputs will be printed in terminal.
2031*c2e18aaaSAndroid Build Coastguard Worker    bazel_args.extend(
2032*c2e18aaaSAndroid Build Coastguard Worker        self._get_bazel_feature_args(
2033*c2e18aaaSAndroid Build Coastguard Worker            Features.EXPERIMENTAL_BES_PUBLISH,
2034*c2e18aaaSAndroid Build Coastguard Worker            extra_args,
2035*c2e18aaaSAndroid Build Coastguard Worker            self._get_bes_publish_args,
2036*c2e18aaaSAndroid Build Coastguard Worker        )
2037*c2e18aaaSAndroid Build Coastguard Worker        or ['--nozip_undeclared_test_outputs']
2038*c2e18aaaSAndroid Build Coastguard Worker    )
2039*c2e18aaaSAndroid Build Coastguard Worker    bazel_args.extend(
2040*c2e18aaaSAndroid Build Coastguard Worker        self._get_bazel_feature_args(
2041*c2e18aaaSAndroid Build Coastguard Worker            Features.EXPERIMENTAL_REMOTE, extra_args, self._get_remote_args
2042*c2e18aaaSAndroid Build Coastguard Worker        )
2043*c2e18aaaSAndroid Build Coastguard Worker    )
2044*c2e18aaaSAndroid Build Coastguard Worker    bazel_args.extend(
2045*c2e18aaaSAndroid Build Coastguard Worker        self._get_bazel_feature_args(
2046*c2e18aaaSAndroid Build Coastguard Worker            Features.EXPERIMENTAL_REMOTE_AVD,
2047*c2e18aaaSAndroid Build Coastguard Worker            extra_args,
2048*c2e18aaaSAndroid Build Coastguard Worker            self._get_remote_avd_args,
2049*c2e18aaaSAndroid Build Coastguard Worker        )
2050*c2e18aaaSAndroid Build Coastguard Worker    )
2051*c2e18aaaSAndroid Build Coastguard Worker
2052*c2e18aaaSAndroid Build Coastguard Worker    # This is an alternative to shlex.join that doesn't exist in Python
2053*c2e18aaaSAndroid Build Coastguard Worker    # versions < 3.8.
2054*c2e18aaaSAndroid Build Coastguard Worker    bazel_args_str = ' '.join(shlex.quote(arg) for arg in bazel_args)
2055*c2e18aaaSAndroid Build Coastguard Worker
2056*c2e18aaaSAndroid Build Coastguard Worker    # Use 'cd' instead of setting the working directory in the subprocess
2057*c2e18aaaSAndroid Build Coastguard Worker    # call for a working --dry-run command that users can run.
2058*c2e18aaaSAndroid Build Coastguard Worker    return [
2059*c2e18aaaSAndroid Build Coastguard Worker        f'cd {self.bazel_workspace} && '
2060*c2e18aaaSAndroid Build Coastguard Worker        f'{self.bazel_binary} {startup_options} '
2061*c2e18aaaSAndroid Build Coastguard Worker        f'test {target_patterns} {bazel_args_str}'
2062*c2e18aaaSAndroid Build Coastguard Worker    ]
2063*c2e18aaaSAndroid Build Coastguard Worker
2064*c2e18aaaSAndroid Build Coastguard Worker
2065*c2e18aaaSAndroid Build Coastguard Workerdef parse_args(
2066*c2e18aaaSAndroid Build Coastguard Worker    test_infos: List[test_info.TestInfo], extra_args: Dict[str, Any]
2067*c2e18aaaSAndroid Build Coastguard Worker) -> Dict[str, Any]:
2068*c2e18aaaSAndroid Build Coastguard Worker  """Parse commandline args and passes supported args to bazel.
2069*c2e18aaaSAndroid Build Coastguard Worker
2070*c2e18aaaSAndroid Build Coastguard Worker  Args:
2071*c2e18aaaSAndroid Build Coastguard Worker      test_infos: A set of TestInfo instances.
2072*c2e18aaaSAndroid Build Coastguard Worker      extra_args: A Dict of extra args to append.
2073*c2e18aaaSAndroid Build Coastguard Worker
2074*c2e18aaaSAndroid Build Coastguard Worker  Returns:
2075*c2e18aaaSAndroid Build Coastguard Worker      A list of args to append to the run command.
2076*c2e18aaaSAndroid Build Coastguard Worker  """
2077*c2e18aaaSAndroid Build Coastguard Worker
2078*c2e18aaaSAndroid Build Coastguard Worker  args_to_append = []
2079*c2e18aaaSAndroid Build Coastguard Worker  # Make a copy of the `extra_args` dict to avoid modifying it for other
2080*c2e18aaaSAndroid Build Coastguard Worker  # Atest runners.
2081*c2e18aaaSAndroid Build Coastguard Worker  extra_args_copy = extra_args.copy()
2082*c2e18aaaSAndroid Build Coastguard Worker
2083*c2e18aaaSAndroid Build Coastguard Worker  # Remove the `--host` flag since we already pass that in the rule's
2084*c2e18aaaSAndroid Build Coastguard Worker  # implementation.
2085*c2e18aaaSAndroid Build Coastguard Worker  extra_args_copy.pop(constants.HOST, None)
2086*c2e18aaaSAndroid Build Coastguard Worker
2087*c2e18aaaSAndroid Build Coastguard Worker  # Remove the serial arg since Bazel mode does not support device tests and
2088*c2e18aaaSAndroid Build Coastguard Worker  # the serial / -s arg conflicts with the TF null device option specified in
2089*c2e18aaaSAndroid Build Coastguard Worker  # the rule implementation (-n).
2090*c2e18aaaSAndroid Build Coastguard Worker  extra_args_copy.pop(constants.SERIAL, None)
2091*c2e18aaaSAndroid Build Coastguard Worker
2092*c2e18aaaSAndroid Build Coastguard Worker  # Map args to their native Bazel counterparts.
2093*c2e18aaaSAndroid Build Coastguard Worker  for arg in _SUPPORTED_BAZEL_ARGS:
2094*c2e18aaaSAndroid Build Coastguard Worker    if arg not in extra_args_copy:
2095*c2e18aaaSAndroid Build Coastguard Worker      continue
2096*c2e18aaaSAndroid Build Coastguard Worker    args_to_append.extend(_map_to_bazel_args(arg, extra_args_copy[arg]))
2097*c2e18aaaSAndroid Build Coastguard Worker    # Remove the argument since we already mapped it to a Bazel option
2098*c2e18aaaSAndroid Build Coastguard Worker    # and no longer need it mapped to a Tradefed argument below.
2099*c2e18aaaSAndroid Build Coastguard Worker    del extra_args_copy[arg]
2100*c2e18aaaSAndroid Build Coastguard Worker
2101*c2e18aaaSAndroid Build Coastguard Worker  # TODO(b/215461642): Store the extra_args in the top-level object so
2102*c2e18aaaSAndroid Build Coastguard Worker  # that we don't have to re-parse the extra args to get BAZEL_ARG again.
2103*c2e18aaaSAndroid Build Coastguard Worker  tf_args, _ = tfr.extra_args_to_tf_args(extra_args_copy)
2104*c2e18aaaSAndroid Build Coastguard Worker
2105*c2e18aaaSAndroid Build Coastguard Worker  # Add ATest include filter argument to allow testcase filtering.
2106*c2e18aaaSAndroid Build Coastguard Worker  tf_args.extend(tfr.get_include_filter(test_infos))
2107*c2e18aaaSAndroid Build Coastguard Worker
2108*c2e18aaaSAndroid Build Coastguard Worker  args_to_append.extend([f'--test_arg={i}' for i in tf_args])
2109*c2e18aaaSAndroid Build Coastguard Worker
2110*c2e18aaaSAndroid Build Coastguard Worker  # Disable test result caching when wait-for-debugger flag is set.
2111*c2e18aaaSAndroid Build Coastguard Worker  if '--wait-for-debugger' in tf_args:
2112*c2e18aaaSAndroid Build Coastguard Worker    # Remove the --cache_test_results flag if it's already set.
2113*c2e18aaaSAndroid Build Coastguard Worker    args_to_append = [
2114*c2e18aaaSAndroid Build Coastguard Worker        arg
2115*c2e18aaaSAndroid Build Coastguard Worker        for arg in args_to_append
2116*c2e18aaaSAndroid Build Coastguard Worker        if not arg.startswith('--cache_test_results')
2117*c2e18aaaSAndroid Build Coastguard Worker    ]
2118*c2e18aaaSAndroid Build Coastguard Worker    args_to_append.append('--cache_test_results=no')
2119*c2e18aaaSAndroid Build Coastguard Worker
2120*c2e18aaaSAndroid Build Coastguard Worker  # Default to --test_output=errors unless specified otherwise
2121*c2e18aaaSAndroid Build Coastguard Worker  if not any(arg.startswith('--test_output=') for arg in args_to_append):
2122*c2e18aaaSAndroid Build Coastguard Worker    args_to_append.append('--test_output=errors')
2123*c2e18aaaSAndroid Build Coastguard Worker
2124*c2e18aaaSAndroid Build Coastguard Worker  # Default to --test_summary=detailed unless specified otherwise, or if the
2125*c2e18aaaSAndroid Build Coastguard Worker  # feature is disabled
2126*c2e18aaaSAndroid Build Coastguard Worker  if not any(arg.startswith('--test_summary=') for arg in args_to_append) and (
2127*c2e18aaaSAndroid Build Coastguard Worker      Features.NO_BAZEL_DETAILED_SUMMARY
2128*c2e18aaaSAndroid Build Coastguard Worker      not in extra_args.get('BAZEL_MODE_FEATURES', [])
2129*c2e18aaaSAndroid Build Coastguard Worker  ):
2130*c2e18aaaSAndroid Build Coastguard Worker    args_to_append.append('--test_summary=detailed')
2131*c2e18aaaSAndroid Build Coastguard Worker
2132*c2e18aaaSAndroid Build Coastguard Worker  return args_to_append
2133*c2e18aaaSAndroid Build Coastguard Worker
2134*c2e18aaaSAndroid Build Coastguard Worker
2135*c2e18aaaSAndroid Build Coastguard Workerdef _map_to_bazel_args(arg: str, arg_value: Any) -> List[str]:
2136*c2e18aaaSAndroid Build Coastguard Worker  return (
2137*c2e18aaaSAndroid Build Coastguard Worker      _SUPPORTED_BAZEL_ARGS[arg](arg_value)
2138*c2e18aaaSAndroid Build Coastguard Worker      if arg in _SUPPORTED_BAZEL_ARGS
2139*c2e18aaaSAndroid Build Coastguard Worker      else []
2140*c2e18aaaSAndroid Build Coastguard Worker  )
2141*c2e18aaaSAndroid Build Coastguard Worker
2142*c2e18aaaSAndroid Build Coastguard Worker
2143*c2e18aaaSAndroid Build Coastguard Workerdef _parse_cquery_output(output: str) -> Dict[str, Set[str]]:
2144*c2e18aaaSAndroid Build Coastguard Worker  module_to_build_variants = defaultdict(set)
2145*c2e18aaaSAndroid Build Coastguard Worker
2146*c2e18aaaSAndroid Build Coastguard Worker  for line in filter(bool, map(str.strip, output.splitlines())):
2147*c2e18aaaSAndroid Build Coastguard Worker    module_name, build_variant = line.split(':')
2148*c2e18aaaSAndroid Build Coastguard Worker    module_to_build_variants[module_name].add(build_variant)
2149*c2e18aaaSAndroid Build Coastguard Worker
2150*c2e18aaaSAndroid Build Coastguard Worker  return module_to_build_variants
2151*c2e18aaaSAndroid Build Coastguard Worker
2152*c2e18aaaSAndroid Build Coastguard Worker
2153*c2e18aaaSAndroid Build Coastguard Workerdef _soong_target_for_variants(
2154*c2e18aaaSAndroid Build Coastguard Worker    module_name: str, build_variants: Set[str]
2155*c2e18aaaSAndroid Build Coastguard Worker) -> str:
2156*c2e18aaaSAndroid Build Coastguard Worker
2157*c2e18aaaSAndroid Build Coastguard Worker  if not build_variants:
2158*c2e18aaaSAndroid Build Coastguard Worker    raise ValueError(
2159*c2e18aaaSAndroid Build Coastguard Worker        f'Missing the build variants for module {module_name} in cquery output!'
2160*c2e18aaaSAndroid Build Coastguard Worker    )
2161*c2e18aaaSAndroid Build Coastguard Worker
2162*c2e18aaaSAndroid Build Coastguard Worker  if len(build_variants) > 1:
2163*c2e18aaaSAndroid Build Coastguard Worker    return module_name
2164*c2e18aaaSAndroid Build Coastguard Worker
2165*c2e18aaaSAndroid Build Coastguard Worker  return f'{module_name}-{_CONFIG_TO_VARIANT[list(build_variants)[0]]}'
2166