blob: 0cf3a59e80cf095dd5bd38a90937b05b9ba24f4b [file] [log] [blame]
Mike Frysingerf1ba7ad2022-09-12 05:42:57 -04001# Copyright 2019 The ChromiumOS Authors
Alex Kleina9d500b2019-04-22 15:37:51 -06002# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5"""Utility functions that are useful for controllers."""
Lizzy Presland29e62452022-01-05 21:58:21 +00006
7import glob
Alex Klein28e59a62021-09-09 15:45:14 -06008import logging
Lizzy Presland29e62452022-01-05 21:58:21 +00009import os
Lizzy Presland084c20f2022-03-30 19:13:50 +000010from typing import Iterable, Optional, TYPE_CHECKING, Union
Alex Kleina9d500b2019-04-22 15:37:51 -060011
Mike Frysinger1cc8f1f2022-04-28 22:40:40 -040012from chromite.api.gen.chromite.api import sysroot_pb2
13from chromite.api.gen.chromite.api import test_pb2
Alex Klein1f67cf32019-10-09 11:13:42 -060014from chromite.api.gen.chromiumos import common_pb2
Alex Klein247d7922023-01-18 15:36:02 -070015from chromite.lib import binpkg
Alex Klein26e472b2020-03-10 14:35:01 -060016from chromite.lib import build_target_lib
George Engelbrecht35b6e8d2021-06-18 09:43:28 -060017from chromite.lib import chroot_lib
Mike Frysinger1cc8f1f2022-04-28 22:40:40 -040018from chromite.lib import constants
Ram Chandrasekare08e3ba2022-04-04 21:42:27 +000019from chromite.lib import goma_lib
Joanna Wang92cad812021-11-03 14:52:08 -070020from chromite.lib import remoteexec_util
George Engelbrecht35b6e8d2021-06-18 09:43:28 -060021from chromite.lib import sysroot_lib
Mike Frysinger1cc8f1f2022-04-28 22:40:40 -040022from chromite.lib.parser import package_info
23
Alex Klein171da612019-08-06 14:00:42 -060024
Alex Klein46c30f32021-11-10 13:12:50 -070025if TYPE_CHECKING:
Alex Klein1699fab2022-09-08 08:46:06 -060026 from chromite.api.gen.chromiumos.build.api import portage_pb2
27
Alex Klein46c30f32021-11-10 13:12:50 -070028
Alex Klein171da612019-08-06 14:00:42 -060029class Error(Exception):
Alex Klein1699fab2022-09-08 08:46:06 -060030 """Base error class for the module."""
Alex Klein171da612019-08-06 14:00:42 -060031
32
33class InvalidMessageError(Error):
Alex Klein1699fab2022-09-08 08:46:06 -060034 """Invalid message."""
Alex Kleina9d500b2019-04-22 15:37:51 -060035
36
Kevin Sheltona8056362022-04-04 16:19:23 -070037def ParseChroot(chroot_message: common_pb2.Chroot) -> chroot_lib.Chroot:
Alex Klein1699fab2022-09-08 08:46:06 -060038 """Create a chroot object from the chroot message.
Alex Klein171da612019-08-06 14:00:42 -060039
Alex Klein1699fab2022-09-08 08:46:06 -060040 Args:
Alex Klein611dddd2022-10-11 17:02:01 -060041 chroot_message: The chroot message.
Alex Klein171da612019-08-06 14:00:42 -060042
Alex Klein1699fab2022-09-08 08:46:06 -060043 Returns:
Alex Klein611dddd2022-10-11 17:02:01 -060044 Chroot: The parsed chroot object.
Alex Klein171da612019-08-06 14:00:42 -060045
Alex Klein1699fab2022-09-08 08:46:06 -060046 Raises:
Alex Klein611dddd2022-10-11 17:02:01 -060047 AssertionError: When the message is not a Chroot message.
Alex Klein1699fab2022-09-08 08:46:06 -060048 """
49 assert isinstance(chroot_message, common_pb2.Chroot)
Alex Klein171da612019-08-06 14:00:42 -060050
Alex Klein1699fab2022-09-08 08:46:06 -060051 path = chroot_message.path or constants.DEFAULT_CHROOT_PATH
52 cache_dir = chroot_message.cache_dir
53 chrome_root = chroot_message.chrome_dir
Brian Norrisd0dfeae2023-03-09 13:06:47 -080054 out_path = chroot_message.out_path or constants.DEFAULT_OUT_PATH
Alex Klein4f0eb432019-05-02 13:56:04 -060055
Alex Klein1699fab2022-09-08 08:46:06 -060056 use_flags = [u.flag for u in chroot_message.env.use_flags]
57 features = [f.feature for f in chroot_message.env.features]
Alex Klein38c7d9e2019-05-08 09:31:19 -060058
Alex Klein1699fab2022-09-08 08:46:06 -060059 env = {}
60 if use_flags:
61 env["USE"] = " ".join(use_flags)
Alex Klein38c7d9e2019-05-08 09:31:19 -060062
Alex Klein1699fab2022-09-08 08:46:06 -060063 # Make sure it'll use the local source to build chrome when we have it.
64 if chrome_root:
65 env["CHROME_ORIGIN"] = "LOCAL_SOURCE"
Alex Kleinb7485bb2019-09-19 13:23:37 -060066
Alex Klein1699fab2022-09-08 08:46:06 -060067 if features:
68 env["FEATURES"] = " ".join(features)
Alex Klein38c7d9e2019-05-08 09:31:19 -060069
Alex Klein1699fab2022-09-08 08:46:06 -060070 chroot = chroot_lib.Chroot(
Brian Norrisd0dfeae2023-03-09 13:06:47 -080071 path=path,
72 out_path=out_path,
73 cache_dir=cache_dir,
74 chrome_root=chrome_root,
75 env=env,
Alex Klein1699fab2022-09-08 08:46:06 -060076 )
Alex Klein171da612019-08-06 14:00:42 -060077
Alex Klein1699fab2022-09-08 08:46:06 -060078 return chroot
Alex Klein171da612019-08-06 14:00:42 -060079
George Engelbrechtc9a8e812021-06-16 18:14:17 -060080
Kevin Sheltona8056362022-04-04 16:19:23 -070081def ParseSysroot(sysroot_message: sysroot_pb2.Sysroot) -> sysroot_lib.Sysroot:
Alex Klein1699fab2022-09-08 08:46:06 -060082 """Create a sysroot object from the sysroot message.
George Engelbrechtc9a8e812021-06-16 18:14:17 -060083
Alex Klein1699fab2022-09-08 08:46:06 -060084 Args:
Alex Klein611dddd2022-10-11 17:02:01 -060085 sysroot_message: The sysroot message.
George Engelbrechtc9a8e812021-06-16 18:14:17 -060086
Alex Klein1699fab2022-09-08 08:46:06 -060087 Returns:
Alex Klein611dddd2022-10-11 17:02:01 -060088 Sysroot: The parsed sysroot object.
George Engelbrechtc9a8e812021-06-16 18:14:17 -060089
Alex Klein1699fab2022-09-08 08:46:06 -060090 Raises:
Alex Klein611dddd2022-10-11 17:02:01 -060091 AssertionError: When the message is not a Sysroot message.
Alex Klein1699fab2022-09-08 08:46:06 -060092 """
93 assert isinstance(sysroot_message, sysroot_pb2.Sysroot)
George Engelbrechtc9a8e812021-06-16 18:14:17 -060094
Alex Klein1699fab2022-09-08 08:46:06 -060095 return sysroot_lib.Sysroot(sysroot_message.path)
George Engelbrechtc9a8e812021-06-16 18:14:17 -060096
97
Joanna Wang92cad812021-11-03 14:52:08 -070098def ParseRemoteexecConfig(remoteexec_message: common_pb2.RemoteexecConfig):
Alex Klein1699fab2022-09-08 08:46:06 -060099 """Parse a remoteexec config message."""
100 assert isinstance(remoteexec_message, common_pb2.RemoteexecConfig)
Joanna Wang92cad812021-11-03 14:52:08 -0700101
Alex Klein1699fab2022-09-08 08:46:06 -0600102 if not (
103 remoteexec_message.reclient_dir or remoteexec_message.reproxy_cfg_file
104 ):
105 return None
Joanna Wang92cad812021-11-03 14:52:08 -0700106
Alex Klein1699fab2022-09-08 08:46:06 -0600107 return remoteexec_util.Remoteexec(
108 remoteexec_message.reclient_dir, remoteexec_message.reproxy_cfg_file
109 )
Joanna Wang92cad812021-11-03 14:52:08 -0700110
111
Brian Norrisd0dfeae2023-03-09 13:06:47 -0800112def ParseGomaConfig(goma_message, chroot_path, out_path):
Alex Klein1699fab2022-09-08 08:46:06 -0600113 """Parse a goma config message."""
114 assert isinstance(goma_message, common_pb2.GomaConfig)
Alex Klein915cce92019-12-17 14:19:50 -0700115
Alex Klein1699fab2022-09-08 08:46:06 -0600116 if not goma_message.goma_dir:
117 return None
Alex Klein915cce92019-12-17 14:19:50 -0700118
Alex Klein1699fab2022-09-08 08:46:06 -0600119 # Parse the goma config.
120 chromeos_goma_dir = goma_message.chromeos_goma_dir or None
121 if goma_message.goma_approach == common_pb2.GomaConfig.RBE_STAGING:
122 goma_approach = goma_lib.GomaApproach(
123 "?staging", "staging-goma.chromium.org", True
124 )
125 elif goma_message.goma_approach == common_pb2.GomaConfig.RBE_PROD:
126 goma_approach = goma_lib.GomaApproach(
127 "?prod", "goma.chromium.org", True
128 )
129 else:
130 goma_approach = goma_lib.GomaApproach(
131 "?cros", "goma.chromium.org", True
132 )
Alex Klein915cce92019-12-17 14:19:50 -0700133
Alex Klein1699fab2022-09-08 08:46:06 -0600134 # Note that we are not specifying the goma log_dir so that goma will create
135 # and use a tmp dir for the logs.
136 stats_filename = goma_message.stats_file or None
137 counterz_filename = goma_message.counterz_file or None
Alex Klein915cce92019-12-17 14:19:50 -0700138
Alex Klein1699fab2022-09-08 08:46:06 -0600139 return goma_lib.Goma(
140 goma_message.goma_dir,
Alex Klein1699fab2022-09-08 08:46:06 -0600141 stage_name="BuildAPI",
142 chromeos_goma_dir=chromeos_goma_dir,
143 chroot_dir=chroot_path,
Brian Norrisd0dfeae2023-03-09 13:06:47 -0800144 out_dir=out_path,
Alex Klein1699fab2022-09-08 08:46:06 -0600145 goma_approach=goma_approach,
146 stats_filename=stats_filename,
147 counterz_filename=counterz_filename,
148 )
Alex Klein915cce92019-12-17 14:19:50 -0700149
150
Kevin Sheltona8056362022-04-04 16:19:23 -0700151def ParseBuildTarget(
152 build_target_message: common_pb2.BuildTarget,
Alex Klein1699fab2022-09-08 08:46:06 -0600153 profile_message: Optional[sysroot_pb2.Profile] = None,
Kevin Sheltona8056362022-04-04 16:19:23 -0700154) -> build_target_lib.BuildTarget:
Alex Klein1699fab2022-09-08 08:46:06 -0600155 """Create a BuildTarget object from a build_target message.
Alex Klein171da612019-08-06 14:00:42 -0600156
Alex Klein1699fab2022-09-08 08:46:06 -0600157 Args:
Alex Klein611dddd2022-10-11 17:02:01 -0600158 build_target_message: The BuildTarget message.
159 profile_message: The profile message.
Alex Klein171da612019-08-06 14:00:42 -0600160
Alex Klein1699fab2022-09-08 08:46:06 -0600161 Returns:
Alex Klein611dddd2022-10-11 17:02:01 -0600162 BuildTarget: The parsed instance.
Alex Klein171da612019-08-06 14:00:42 -0600163
Alex Klein1699fab2022-09-08 08:46:06 -0600164 Raises:
Alex Klein611dddd2022-10-11 17:02:01 -0600165 AssertionError: When the field is not a BuildTarget message.
Alex Klein1699fab2022-09-08 08:46:06 -0600166 """
167 assert isinstance(build_target_message, common_pb2.BuildTarget)
168 assert profile_message is None or isinstance(
169 profile_message, sysroot_pb2.Profile
170 )
Alex Klein171da612019-08-06 14:00:42 -0600171
Alex Klein1699fab2022-09-08 08:46:06 -0600172 profile_name = profile_message.name if profile_message else None
173 return build_target_lib.BuildTarget(
174 build_target_message.name, profile=profile_name
175 )
Alex Klein171da612019-08-06 14:00:42 -0600176
177
178def ParseBuildTargets(repeated_build_target_field):
Alex Klein1699fab2022-09-08 08:46:06 -0600179 """Create a BuildTarget for each entry in the repeated field.
Alex Klein171da612019-08-06 14:00:42 -0600180
Alex Klein1699fab2022-09-08 08:46:06 -0600181 Args:
Alex Klein611dddd2022-10-11 17:02:01 -0600182 repeated_build_target_field: The repeated BuildTarget field.
Alex Klein171da612019-08-06 14:00:42 -0600183
Alex Klein1699fab2022-09-08 08:46:06 -0600184 Returns:
Alex Klein611dddd2022-10-11 17:02:01 -0600185 list[BuildTarget]: The parsed BuildTargets.
Alex Klein171da612019-08-06 14:00:42 -0600186
Alex Klein1699fab2022-09-08 08:46:06 -0600187 Raises:
Alex Klein611dddd2022-10-11 17:02:01 -0600188 AssertionError: When the field contains non-BuildTarget messages.
Alex Klein1699fab2022-09-08 08:46:06 -0600189 """
190 return [ParseBuildTarget(target) for target in repeated_build_target_field]
Alex Klein4f0eb432019-05-02 13:56:04 -0600191
192
Alex Klein247d7922023-01-18 15:36:02 -0700193def deserialize_profile(profile: common_pb2.Profile) -> sysroot_lib.Profile:
194 """Deserialize a portage profile message to a Profile object."""
195 return sysroot_lib.Profile(profile.name)
196
197
198def deserialize_package_index_info(
199 message: common_pb2.PackageIndexInfo,
200) -> binpkg.PackageIndexInfo:
201 """Deserialize a PackageIndexInfo message to an object."""
202 return binpkg.PackageIndexInfo(
203 snapshot_sha=message.snapshot_sha,
204 snapshot_number=message.snapshot_number,
205 build_target=ParseBuildTarget(message.build_target),
206 profile=deserialize_profile(message.profile),
207 location=message.location,
208 )
209
210
Alex Klein1699fab2022-09-08 08:46:06 -0600211def serialize_package_info(
212 pkg_info: package_info.PackageInfo,
213 pkg_info_msg: Union[common_pb2.PackageInfo, "portage_pb2.Portage.Package"],
214):
215 """Serialize a PackageInfo object to a PackageInfo proto."""
216 if not isinstance(pkg_info, package_info.PackageInfo):
217 # Allows us to swap everything to serialize_package_info, and search the
218 # logs for usages that aren't passing though a PackageInfo yet.
219 logging.warning(
220 "serialize_package_info: Got a %s instead of a PackageInfo.",
221 type(pkg_info),
222 )
223 pkg_info = package_info.parse(pkg_info)
224 pkg_info_msg.package_name = pkg_info.package
225 if pkg_info.category:
226 pkg_info_msg.category = pkg_info.category
227 if pkg_info.vr:
228 pkg_info_msg.version = pkg_info.vr
Alex Klein1e68a8e2020-10-06 17:25:11 -0600229
230
231def deserialize_package_info(pkg_info_msg):
Alex Klein1699fab2022-09-08 08:46:06 -0600232 """Deserialize a PackageInfo message to a PackageInfo object."""
233 return package_info.parse(PackageInfoToString(pkg_info_msg))
Alex Klein1e68a8e2020-10-06 17:25:11 -0600234
235
Alex Klein1699fab2022-09-08 08:46:06 -0600236def retrieve_package_log_paths(
237 packages: Iterable[package_info.PackageInfo],
238 output_proto: Union[
239 sysroot_pb2.InstallPackagesResponse,
240 sysroot_pb2.InstallToolchainResponse,
241 test_pb2.BuildTargetUnitTestResponse,
242 ],
243 target_sysroot: sysroot_lib.Sysroot,
244) -> None:
245 """Get the path to the log file for each package that failed to build.
Lizzy Presland29e62452022-01-05 21:58:21 +0000246
Alex Klein1699fab2022-09-08 08:46:06 -0600247 Args:
Alex Klein611dddd2022-10-11 17:02:01 -0600248 packages: A list of packages which failed to build.
249 output_proto: The Response message for a given API call. This response
250 proto must contain a failed_package_data field.
251 target_sysroot: The sysroot used by the build step.
Alex Klein1699fab2022-09-08 08:46:06 -0600252 """
253 for pkg_info in packages:
254 # Grab the paths to the log files for each failed package from the
255 # sysroot.
256 # Logs currently exist within the sysroot in the form of:
257 # /build/${BOARD}/tmp/portage/logs/$CATEGORY:$PF:$TIMESTAMP.log
258 failed_pkg_data_msg = output_proto.failed_package_data.add()
259 serialize_package_info(pkg_info, failed_pkg_data_msg.name)
260 glob_path = os.path.join(
261 target_sysroot.portage_logdir,
262 f"{pkg_info.category}:{pkg_info.pvr}:*.log",
263 )
264 log_files = glob.glob(glob_path)
265 log_files.sort(reverse=True)
266 # Omit path if files don't exist for some reason.
267 if not log_files:
268 logging.warning(
269 "Log file for %s was not found. Search path: %s",
270 pkg_info.cpvr,
271 glob_path,
272 )
273 continue
274 failed_pkg_data_msg.log_path.path = log_files[0]
275 failed_pkg_data_msg.log_path.location = common_pb2.Path.INSIDE
Lizzy Presland29e62452022-01-05 21:58:21 +0000276
277
Alex Klein18a60af2020-06-11 12:08:47 -0600278def PackageInfoToCPV(package_info_msg):
Alex Klein1699fab2022-09-08 08:46:06 -0600279 """Helper to translate a PackageInfo message into a CPV."""
280 if not package_info_msg or not package_info_msg.package_name:
281 return None
Alex Kleina9d500b2019-04-22 15:37:51 -0600282
Alex Klein1699fab2022-09-08 08:46:06 -0600283 return package_info.SplitCPV(
284 PackageInfoToString(package_info_msg), strict=False
285 )
Alex Kleina9d500b2019-04-22 15:37:51 -0600286
287
Alex Klein18a60af2020-06-11 12:08:47 -0600288def PackageInfoToString(package_info_msg):
Alex Klein1699fab2022-09-08 08:46:06 -0600289 """Combine the components into the full package string."""
290 # TODO: Use the lib.parser.package_info.PackageInfo class instead.
291 if not package_info_msg.package_name:
292 raise ValueError("Invalid PackageInfo message.")
Alex Kleina9d500b2019-04-22 15:37:51 -0600293
Alex Klein1699fab2022-09-08 08:46:06 -0600294 c = ("%s/" % package_info_msg.category) if package_info_msg.category else ""
295 p = package_info_msg.package_name
296 v = ("-%s" % package_info_msg.version) if package_info_msg.version else ""
297 return "%s%s%s" % (c, p, v)