blob: b7da66cf134d25b5ccab44323590d7dca86fc366 [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
Brian Norris09937012023-03-31 15:16:55 -070010from pathlib import Path
Lizzy Presland084c20f2022-03-30 19:13:50 +000011from typing import Iterable, Optional, TYPE_CHECKING, Union
Alex Kleina9d500b2019-04-22 15:37:51 -060012
Mike Frysinger1cc8f1f2022-04-28 22:40:40 -040013from chromite.api.gen.chromite.api import sysroot_pb2
14from chromite.api.gen.chromite.api import test_pb2
Alex Klein1f67cf32019-10-09 11:13:42 -060015from chromite.api.gen.chromiumos import common_pb2
Alex Klein247d7922023-01-18 15:36:02 -070016from chromite.lib import binpkg
Alex Klein26e472b2020-03-10 14:35:01 -060017from chromite.lib import build_target_lib
George Engelbrecht35b6e8d2021-06-18 09:43:28 -060018from chromite.lib import chroot_lib
Mike Frysinger1cc8f1f2022-04-28 22:40:40 -040019from chromite.lib import constants
Ram Chandrasekare08e3ba2022-04-04 21:42:27 +000020from chromite.lib import goma_lib
Joanna Wang92cad812021-11-03 14:52:08 -070021from chromite.lib import remoteexec_util
George Engelbrecht35b6e8d2021-06-18 09:43:28 -060022from chromite.lib import sysroot_lib
Mike Frysinger1cc8f1f2022-04-28 22:40:40 -040023from chromite.lib.parser import package_info
24
Alex Klein171da612019-08-06 14:00:42 -060025
Alex Klein46c30f32021-11-10 13:12:50 -070026if TYPE_CHECKING:
Alex Klein1699fab2022-09-08 08:46:06 -060027 from chromite.api.gen.chromiumos.build.api import portage_pb2
28
Alex Klein46c30f32021-11-10 13:12:50 -070029
Alex Klein171da612019-08-06 14:00:42 -060030class Error(Exception):
Alex Klein1699fab2022-09-08 08:46:06 -060031 """Base error class for the module."""
Alex Klein171da612019-08-06 14:00:42 -060032
33
34class InvalidMessageError(Error):
Alex Klein1699fab2022-09-08 08:46:06 -060035 """Invalid message."""
Alex Kleina9d500b2019-04-22 15:37:51 -060036
37
Kevin Sheltona8056362022-04-04 16:19:23 -070038def ParseChroot(chroot_message: common_pb2.Chroot) -> chroot_lib.Chroot:
Alex Klein1699fab2022-09-08 08:46:06 -060039 """Create a chroot object from the chroot message.
Alex Klein171da612019-08-06 14:00:42 -060040
Alex Klein1699fab2022-09-08 08:46:06 -060041 Args:
Alex Klein611dddd2022-10-11 17:02:01 -060042 chroot_message: The chroot message.
Alex Klein171da612019-08-06 14:00:42 -060043
Alex Klein1699fab2022-09-08 08:46:06 -060044 Returns:
Alex Klein611dddd2022-10-11 17:02:01 -060045 Chroot: The parsed chroot object.
Alex Klein171da612019-08-06 14:00:42 -060046
Alex Klein1699fab2022-09-08 08:46:06 -060047 Raises:
Alex Klein611dddd2022-10-11 17:02:01 -060048 AssertionError: When the message is not a Chroot message.
Alex Klein1699fab2022-09-08 08:46:06 -060049 """
50 assert isinstance(chroot_message, common_pb2.Chroot)
Alex Klein171da612019-08-06 14:00:42 -060051
Alex Klein1699fab2022-09-08 08:46:06 -060052 path = chroot_message.path or constants.DEFAULT_CHROOT_PATH
53 cache_dir = chroot_message.cache_dir
54 chrome_root = chroot_message.chrome_dir
Brian Norris09937012023-03-31 15:16:55 -070055 out_path = Path(chroot_message.out_path) or constants.DEFAULT_OUT_PATH
Alex Klein4f0eb432019-05-02 13:56:04 -060056
Alex Klein1699fab2022-09-08 08:46:06 -060057 use_flags = [u.flag for u in chroot_message.env.use_flags]
58 features = [f.feature for f in chroot_message.env.features]
Alex Klein38c7d9e2019-05-08 09:31:19 -060059
Alex Klein1699fab2022-09-08 08:46:06 -060060 env = {}
61 if use_flags:
62 env["USE"] = " ".join(use_flags)
Alex Klein38c7d9e2019-05-08 09:31:19 -060063
Alex Klein1699fab2022-09-08 08:46:06 -060064 # Make sure it'll use the local source to build chrome when we have it.
65 if chrome_root:
66 env["CHROME_ORIGIN"] = "LOCAL_SOURCE"
Alex Kleinb7485bb2019-09-19 13:23:37 -060067
Alex Klein1699fab2022-09-08 08:46:06 -060068 if features:
69 env["FEATURES"] = " ".join(features)
Alex Klein38c7d9e2019-05-08 09:31:19 -060070
Alex Klein1699fab2022-09-08 08:46:06 -060071 chroot = chroot_lib.Chroot(
Brian Norrisd0dfeae2023-03-09 13:06:47 -080072 path=path,
73 out_path=out_path,
74 cache_dir=cache_dir,
75 chrome_root=chrome_root,
76 env=env,
Alex Klein1699fab2022-09-08 08:46:06 -060077 )
Alex Klein171da612019-08-06 14:00:42 -060078
Alex Klein1699fab2022-09-08 08:46:06 -060079 return chroot
Alex Klein171da612019-08-06 14:00:42 -060080
George Engelbrechtc9a8e812021-06-16 18:14:17 -060081
Kevin Sheltona8056362022-04-04 16:19:23 -070082def ParseSysroot(sysroot_message: sysroot_pb2.Sysroot) -> sysroot_lib.Sysroot:
Alex Klein1699fab2022-09-08 08:46:06 -060083 """Create a sysroot object from the sysroot message.
George Engelbrechtc9a8e812021-06-16 18:14:17 -060084
Alex Klein1699fab2022-09-08 08:46:06 -060085 Args:
Alex Klein611dddd2022-10-11 17:02:01 -060086 sysroot_message: The sysroot message.
George Engelbrechtc9a8e812021-06-16 18:14:17 -060087
Alex Klein1699fab2022-09-08 08:46:06 -060088 Returns:
Alex Klein611dddd2022-10-11 17:02:01 -060089 Sysroot: The parsed sysroot object.
George Engelbrechtc9a8e812021-06-16 18:14:17 -060090
Alex Klein1699fab2022-09-08 08:46:06 -060091 Raises:
Alex Klein611dddd2022-10-11 17:02:01 -060092 AssertionError: When the message is not a Sysroot message.
Alex Klein1699fab2022-09-08 08:46:06 -060093 """
94 assert isinstance(sysroot_message, sysroot_pb2.Sysroot)
George Engelbrechtc9a8e812021-06-16 18:14:17 -060095
Alex Klein1699fab2022-09-08 08:46:06 -060096 return sysroot_lib.Sysroot(sysroot_message.path)
George Engelbrechtc9a8e812021-06-16 18:14:17 -060097
98
Joanna Wang92cad812021-11-03 14:52:08 -070099def ParseRemoteexecConfig(remoteexec_message: common_pb2.RemoteexecConfig):
Alex Klein1699fab2022-09-08 08:46:06 -0600100 """Parse a remoteexec config message."""
101 assert isinstance(remoteexec_message, common_pb2.RemoteexecConfig)
Joanna Wang92cad812021-11-03 14:52:08 -0700102
Alex Klein1699fab2022-09-08 08:46:06 -0600103 if not (
104 remoteexec_message.reclient_dir or remoteexec_message.reproxy_cfg_file
105 ):
106 return None
Joanna Wang92cad812021-11-03 14:52:08 -0700107
Alex Klein1699fab2022-09-08 08:46:06 -0600108 return remoteexec_util.Remoteexec(
109 remoteexec_message.reclient_dir, remoteexec_message.reproxy_cfg_file
110 )
Joanna Wang92cad812021-11-03 14:52:08 -0700111
112
Brian Norrisd0dfeae2023-03-09 13:06:47 -0800113def ParseGomaConfig(goma_message, chroot_path, out_path):
Alex Klein1699fab2022-09-08 08:46:06 -0600114 """Parse a goma config message."""
115 assert isinstance(goma_message, common_pb2.GomaConfig)
Alex Klein915cce92019-12-17 14:19:50 -0700116
Alex Klein1699fab2022-09-08 08:46:06 -0600117 if not goma_message.goma_dir:
118 return None
Alex Klein915cce92019-12-17 14:19:50 -0700119
Alex Klein1699fab2022-09-08 08:46:06 -0600120 # Parse the goma config.
121 chromeos_goma_dir = goma_message.chromeos_goma_dir or None
122 if goma_message.goma_approach == common_pb2.GomaConfig.RBE_STAGING:
123 goma_approach = goma_lib.GomaApproach(
124 "?staging", "staging-goma.chromium.org", True
125 )
126 elif goma_message.goma_approach == common_pb2.GomaConfig.RBE_PROD:
127 goma_approach = goma_lib.GomaApproach(
128 "?prod", "goma.chromium.org", True
129 )
130 else:
131 goma_approach = goma_lib.GomaApproach(
132 "?cros", "goma.chromium.org", True
133 )
Alex Klein915cce92019-12-17 14:19:50 -0700134
Alex Klein1699fab2022-09-08 08:46:06 -0600135 # Note that we are not specifying the goma log_dir so that goma will create
136 # and use a tmp dir for the logs.
137 stats_filename = goma_message.stats_file or None
138 counterz_filename = goma_message.counterz_file or None
Alex Klein915cce92019-12-17 14:19:50 -0700139
Alex Klein1699fab2022-09-08 08:46:06 -0600140 return goma_lib.Goma(
141 goma_message.goma_dir,
Alex Klein1699fab2022-09-08 08:46:06 -0600142 stage_name="BuildAPI",
143 chromeos_goma_dir=chromeos_goma_dir,
144 chroot_dir=chroot_path,
Brian Norrisd0dfeae2023-03-09 13:06:47 -0800145 out_dir=out_path,
Alex Klein1699fab2022-09-08 08:46:06 -0600146 goma_approach=goma_approach,
147 stats_filename=stats_filename,
148 counterz_filename=counterz_filename,
149 )
Alex Klein915cce92019-12-17 14:19:50 -0700150
151
Kevin Sheltona8056362022-04-04 16:19:23 -0700152def ParseBuildTarget(
153 build_target_message: common_pb2.BuildTarget,
Alex Klein1699fab2022-09-08 08:46:06 -0600154 profile_message: Optional[sysroot_pb2.Profile] = None,
Kevin Sheltona8056362022-04-04 16:19:23 -0700155) -> build_target_lib.BuildTarget:
Alex Klein1699fab2022-09-08 08:46:06 -0600156 """Create a BuildTarget object from a build_target message.
Alex Klein171da612019-08-06 14:00:42 -0600157
Alex Klein1699fab2022-09-08 08:46:06 -0600158 Args:
Alex Klein611dddd2022-10-11 17:02:01 -0600159 build_target_message: The BuildTarget message.
160 profile_message: The profile message.
Alex Klein171da612019-08-06 14:00:42 -0600161
Alex Klein1699fab2022-09-08 08:46:06 -0600162 Returns:
Alex Klein611dddd2022-10-11 17:02:01 -0600163 BuildTarget: The parsed instance.
Alex Klein171da612019-08-06 14:00:42 -0600164
Alex Klein1699fab2022-09-08 08:46:06 -0600165 Raises:
Alex Klein611dddd2022-10-11 17:02:01 -0600166 AssertionError: When the field is not a BuildTarget message.
Alex Klein1699fab2022-09-08 08:46:06 -0600167 """
168 assert isinstance(build_target_message, common_pb2.BuildTarget)
169 assert profile_message is None or isinstance(
170 profile_message, sysroot_pb2.Profile
171 )
Alex Klein171da612019-08-06 14:00:42 -0600172
Alex Klein1699fab2022-09-08 08:46:06 -0600173 profile_name = profile_message.name if profile_message else None
174 return build_target_lib.BuildTarget(
175 build_target_message.name, profile=profile_name
176 )
Alex Klein171da612019-08-06 14:00:42 -0600177
178
179def ParseBuildTargets(repeated_build_target_field):
Alex Klein1699fab2022-09-08 08:46:06 -0600180 """Create a BuildTarget for each entry in the repeated field.
Alex Klein171da612019-08-06 14:00:42 -0600181
Alex Klein1699fab2022-09-08 08:46:06 -0600182 Args:
Alex Klein611dddd2022-10-11 17:02:01 -0600183 repeated_build_target_field: The repeated BuildTarget field.
Alex Klein171da612019-08-06 14:00:42 -0600184
Alex Klein1699fab2022-09-08 08:46:06 -0600185 Returns:
Alex Klein611dddd2022-10-11 17:02:01 -0600186 list[BuildTarget]: The parsed BuildTargets.
Alex Klein171da612019-08-06 14:00:42 -0600187
Alex Klein1699fab2022-09-08 08:46:06 -0600188 Raises:
Alex Klein611dddd2022-10-11 17:02:01 -0600189 AssertionError: When the field contains non-BuildTarget messages.
Alex Klein1699fab2022-09-08 08:46:06 -0600190 """
191 return [ParseBuildTarget(target) for target in repeated_build_target_field]
Alex Klein4f0eb432019-05-02 13:56:04 -0600192
193
Alex Klein247d7922023-01-18 15:36:02 -0700194def deserialize_profile(profile: common_pb2.Profile) -> sysroot_lib.Profile:
195 """Deserialize a portage profile message to a Profile object."""
196 return sysroot_lib.Profile(profile.name)
197
198
199def deserialize_package_index_info(
200 message: common_pb2.PackageIndexInfo,
201) -> binpkg.PackageIndexInfo:
202 """Deserialize a PackageIndexInfo message to an object."""
203 return binpkg.PackageIndexInfo(
204 snapshot_sha=message.snapshot_sha,
205 snapshot_number=message.snapshot_number,
206 build_target=ParseBuildTarget(message.build_target),
207 profile=deserialize_profile(message.profile),
208 location=message.location,
209 )
210
211
Alex Klein1699fab2022-09-08 08:46:06 -0600212def serialize_package_info(
213 pkg_info: package_info.PackageInfo,
214 pkg_info_msg: Union[common_pb2.PackageInfo, "portage_pb2.Portage.Package"],
215):
216 """Serialize a PackageInfo object to a PackageInfo proto."""
217 if not isinstance(pkg_info, package_info.PackageInfo):
218 # Allows us to swap everything to serialize_package_info, and search the
219 # logs for usages that aren't passing though a PackageInfo yet.
220 logging.warning(
221 "serialize_package_info: Got a %s instead of a PackageInfo.",
222 type(pkg_info),
223 )
224 pkg_info = package_info.parse(pkg_info)
225 pkg_info_msg.package_name = pkg_info.package
226 if pkg_info.category:
227 pkg_info_msg.category = pkg_info.category
228 if pkg_info.vr:
229 pkg_info_msg.version = pkg_info.vr
Alex Klein1e68a8e2020-10-06 17:25:11 -0600230
231
232def deserialize_package_info(pkg_info_msg):
Alex Klein1699fab2022-09-08 08:46:06 -0600233 """Deserialize a PackageInfo message to a PackageInfo object."""
234 return package_info.parse(PackageInfoToString(pkg_info_msg))
Alex Klein1e68a8e2020-10-06 17:25:11 -0600235
236
Alex Klein1699fab2022-09-08 08:46:06 -0600237def retrieve_package_log_paths(
238 packages: Iterable[package_info.PackageInfo],
239 output_proto: Union[
240 sysroot_pb2.InstallPackagesResponse,
241 sysroot_pb2.InstallToolchainResponse,
242 test_pb2.BuildTargetUnitTestResponse,
243 ],
244 target_sysroot: sysroot_lib.Sysroot,
245) -> None:
246 """Get the path to the log file for each package that failed to build.
Lizzy Presland29e62452022-01-05 21:58:21 +0000247
Alex Klein1699fab2022-09-08 08:46:06 -0600248 Args:
Alex Klein611dddd2022-10-11 17:02:01 -0600249 packages: A list of packages which failed to build.
250 output_proto: The Response message for a given API call. This response
251 proto must contain a failed_package_data field.
252 target_sysroot: The sysroot used by the build step.
Alex Klein1699fab2022-09-08 08:46:06 -0600253 """
254 for pkg_info in packages:
255 # Grab the paths to the log files for each failed package from the
256 # sysroot.
257 # Logs currently exist within the sysroot in the form of:
258 # /build/${BOARD}/tmp/portage/logs/$CATEGORY:$PF:$TIMESTAMP.log
259 failed_pkg_data_msg = output_proto.failed_package_data.add()
260 serialize_package_info(pkg_info, failed_pkg_data_msg.name)
261 glob_path = os.path.join(
262 target_sysroot.portage_logdir,
263 f"{pkg_info.category}:{pkg_info.pvr}:*.log",
264 )
265 log_files = glob.glob(glob_path)
266 log_files.sort(reverse=True)
267 # Omit path if files don't exist for some reason.
268 if not log_files:
269 logging.warning(
270 "Log file for %s was not found. Search path: %s",
271 pkg_info.cpvr,
272 glob_path,
273 )
274 continue
275 failed_pkg_data_msg.log_path.path = log_files[0]
276 failed_pkg_data_msg.log_path.location = common_pb2.Path.INSIDE
Lizzy Presland29e62452022-01-05 21:58:21 +0000277
278
Alex Klein18a60af2020-06-11 12:08:47 -0600279def PackageInfoToCPV(package_info_msg):
Alex Klein1699fab2022-09-08 08:46:06 -0600280 """Helper to translate a PackageInfo message into a CPV."""
281 if not package_info_msg or not package_info_msg.package_name:
282 return None
Alex Kleina9d500b2019-04-22 15:37:51 -0600283
Alex Klein1699fab2022-09-08 08:46:06 -0600284 return package_info.SplitCPV(
285 PackageInfoToString(package_info_msg), strict=False
286 )
Alex Kleina9d500b2019-04-22 15:37:51 -0600287
288
Alex Klein18a60af2020-06-11 12:08:47 -0600289def PackageInfoToString(package_info_msg):
Alex Klein1699fab2022-09-08 08:46:06 -0600290 """Combine the components into the full package string."""
291 # TODO: Use the lib.parser.package_info.PackageInfo class instead.
292 if not package_info_msg.package_name:
293 raise ValueError("Invalid PackageInfo message.")
Alex Kleina9d500b2019-04-22 15:37:51 -0600294
Alex Klein1699fab2022-09-08 08:46:06 -0600295 c = ("%s/" % package_info_msg.category) if package_info_msg.category else ""
296 p = package_info_msg.package_name
297 v = ("-%s" % package_info_msg.version) if package_info_msg.version else ""
298 return "%s%s%s" % (c, p, v)
Greg Edelston1f5deb62023-03-31 14:22:08 -0600299
300
301def pb2_path_to_pathlib_path(
302 pb2_path: "common_pb2.Path",
303 chroot: Optional["common_pb2.Chroot"] = None,
304) -> Path:
305 """Convert an absolute pb2 path to a pathlib.Path outside the chroot.
306
307 Args:
308 pb2_path: An absolute path, which might be inside or outside chroot.
309 chroot: The chroot that the path might be inside of.
310
311 Returns:
312 A Path pointing to the same location as pb2_path, originating
313 outside the chroot.
314
315 Raises:
316 ValueError: If the given path is relative instead of absolute.
317 ValueError: If the given path is inside the chroot, but a chroot is not
318 provided.
319 """
320 if pb2_path.path[0] != "/":
321 raise ValueError(f"Cannot convert relative path: {pb2_path.path}")
322 if pb2_path.location is common_pb2.Path.Location.OUTSIDE:
323 return Path(pb2_path.path)
324 if chroot is None:
325 raise ValueError("Cannot convert inside path without a chroot.")
326 path_relative_to_root = pb2_path.path[1:]
327 return Path(chroot.path, path_relative_to_root)