blob: 8d1fd2d7c6c9bcbf4b3c21de972d50f80a7e1023 [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 Klein26e472b2020-03-10 14:35:01 -060015from chromite.lib import build_target_lib
George Engelbrecht35b6e8d2021-06-18 09:43:28 -060016from chromite.lib import chroot_lib
Mike Frysinger1cc8f1f2022-04-28 22:40:40 -040017from chromite.lib import constants
Ram Chandrasekare08e3ba2022-04-04 21:42:27 +000018from chromite.lib import goma_lib
Joanna Wang92cad812021-11-03 14:52:08 -070019from chromite.lib import remoteexec_util
George Engelbrecht35b6e8d2021-06-18 09:43:28 -060020from chromite.lib import sysroot_lib
Mike Frysinger1cc8f1f2022-04-28 22:40:40 -040021from chromite.lib.parser import package_info
22
Alex Klein171da612019-08-06 14:00:42 -060023
Alex Klein46c30f32021-11-10 13:12:50 -070024if TYPE_CHECKING:
Alex Klein1699fab2022-09-08 08:46:06 -060025 from chromite.api.gen.chromiumos.build.api import portage_pb2
26
Alex Klein46c30f32021-11-10 13:12:50 -070027
Alex Klein171da612019-08-06 14:00:42 -060028class Error(Exception):
Alex Klein1699fab2022-09-08 08:46:06 -060029 """Base error class for the module."""
Alex Klein171da612019-08-06 14:00:42 -060030
31
32class InvalidMessageError(Error):
Alex Klein1699fab2022-09-08 08:46:06 -060033 """Invalid message."""
Alex Kleina9d500b2019-04-22 15:37:51 -060034
35
Kevin Sheltona8056362022-04-04 16:19:23 -070036def ParseChroot(chroot_message: common_pb2.Chroot) -> chroot_lib.Chroot:
Alex Klein1699fab2022-09-08 08:46:06 -060037 """Create a chroot object from the chroot message.
Alex Klein171da612019-08-06 14:00:42 -060038
Alex Klein1699fab2022-09-08 08:46:06 -060039 Args:
Alex Klein611dddd2022-10-11 17:02:01 -060040 chroot_message: The chroot message.
Alex Klein171da612019-08-06 14:00:42 -060041
Alex Klein1699fab2022-09-08 08:46:06 -060042 Returns:
Alex Klein611dddd2022-10-11 17:02:01 -060043 Chroot: The parsed chroot object.
Alex Klein171da612019-08-06 14:00:42 -060044
Alex Klein1699fab2022-09-08 08:46:06 -060045 Raises:
Alex Klein611dddd2022-10-11 17:02:01 -060046 AssertionError: When the message is not a Chroot message.
Alex Klein1699fab2022-09-08 08:46:06 -060047 """
48 assert isinstance(chroot_message, common_pb2.Chroot)
Alex Klein171da612019-08-06 14:00:42 -060049
Alex Klein1699fab2022-09-08 08:46:06 -060050 path = chroot_message.path or constants.DEFAULT_CHROOT_PATH
51 cache_dir = chroot_message.cache_dir
52 chrome_root = chroot_message.chrome_dir
Alex Klein4f0eb432019-05-02 13:56:04 -060053
Alex Klein1699fab2022-09-08 08:46:06 -060054 use_flags = [u.flag for u in chroot_message.env.use_flags]
55 features = [f.feature for f in chroot_message.env.features]
Alex Klein38c7d9e2019-05-08 09:31:19 -060056
Alex Klein1699fab2022-09-08 08:46:06 -060057 env = {}
58 if use_flags:
59 env["USE"] = " ".join(use_flags)
Alex Klein38c7d9e2019-05-08 09:31:19 -060060
Alex Klein1699fab2022-09-08 08:46:06 -060061 # Make sure it'll use the local source to build chrome when we have it.
62 if chrome_root:
63 env["CHROME_ORIGIN"] = "LOCAL_SOURCE"
Alex Kleinb7485bb2019-09-19 13:23:37 -060064
Alex Klein1699fab2022-09-08 08:46:06 -060065 if features:
66 env["FEATURES"] = " ".join(features)
Alex Klein38c7d9e2019-05-08 09:31:19 -060067
Alex Klein1699fab2022-09-08 08:46:06 -060068 chroot = chroot_lib.Chroot(
69 path=path, cache_dir=cache_dir, chrome_root=chrome_root, env=env
70 )
Alex Klein171da612019-08-06 14:00:42 -060071
Alex Klein1699fab2022-09-08 08:46:06 -060072 return chroot
Alex Klein171da612019-08-06 14:00:42 -060073
George Engelbrechtc9a8e812021-06-16 18:14:17 -060074
Kevin Sheltona8056362022-04-04 16:19:23 -070075def ParseSysroot(sysroot_message: sysroot_pb2.Sysroot) -> sysroot_lib.Sysroot:
Alex Klein1699fab2022-09-08 08:46:06 -060076 """Create a sysroot object from the sysroot message.
George Engelbrechtc9a8e812021-06-16 18:14:17 -060077
Alex Klein1699fab2022-09-08 08:46:06 -060078 Args:
Alex Klein611dddd2022-10-11 17:02:01 -060079 sysroot_message: The sysroot message.
George Engelbrechtc9a8e812021-06-16 18:14:17 -060080
Alex Klein1699fab2022-09-08 08:46:06 -060081 Returns:
Alex Klein611dddd2022-10-11 17:02:01 -060082 Sysroot: The parsed sysroot object.
George Engelbrechtc9a8e812021-06-16 18:14:17 -060083
Alex Klein1699fab2022-09-08 08:46:06 -060084 Raises:
Alex Klein611dddd2022-10-11 17:02:01 -060085 AssertionError: When the message is not a Sysroot message.
Alex Klein1699fab2022-09-08 08:46:06 -060086 """
87 assert isinstance(sysroot_message, sysroot_pb2.Sysroot)
George Engelbrechtc9a8e812021-06-16 18:14:17 -060088
Alex Klein1699fab2022-09-08 08:46:06 -060089 return sysroot_lib.Sysroot(sysroot_message.path)
George Engelbrechtc9a8e812021-06-16 18:14:17 -060090
91
Joanna Wang92cad812021-11-03 14:52:08 -070092def ParseRemoteexecConfig(remoteexec_message: common_pb2.RemoteexecConfig):
Alex Klein1699fab2022-09-08 08:46:06 -060093 """Parse a remoteexec config message."""
94 assert isinstance(remoteexec_message, common_pb2.RemoteexecConfig)
Joanna Wang92cad812021-11-03 14:52:08 -070095
Alex Klein1699fab2022-09-08 08:46:06 -060096 if not (
97 remoteexec_message.reclient_dir or remoteexec_message.reproxy_cfg_file
98 ):
99 return None
Joanna Wang92cad812021-11-03 14:52:08 -0700100
Alex Klein1699fab2022-09-08 08:46:06 -0600101 return remoteexec_util.Remoteexec(
102 remoteexec_message.reclient_dir, remoteexec_message.reproxy_cfg_file
103 )
Joanna Wang92cad812021-11-03 14:52:08 -0700104
105
Alex Klein915cce92019-12-17 14:19:50 -0700106def ParseGomaConfig(goma_message, chroot_path):
Alex Klein1699fab2022-09-08 08:46:06 -0600107 """Parse a goma config message."""
108 assert isinstance(goma_message, common_pb2.GomaConfig)
Alex Klein915cce92019-12-17 14:19:50 -0700109
Alex Klein1699fab2022-09-08 08:46:06 -0600110 if not goma_message.goma_dir:
111 return None
Alex Klein915cce92019-12-17 14:19:50 -0700112
Alex Klein1699fab2022-09-08 08:46:06 -0600113 # Parse the goma config.
114 chromeos_goma_dir = goma_message.chromeos_goma_dir or None
115 if goma_message.goma_approach == common_pb2.GomaConfig.RBE_STAGING:
116 goma_approach = goma_lib.GomaApproach(
117 "?staging", "staging-goma.chromium.org", True
118 )
119 elif goma_message.goma_approach == common_pb2.GomaConfig.RBE_PROD:
120 goma_approach = goma_lib.GomaApproach(
121 "?prod", "goma.chromium.org", True
122 )
123 else:
124 goma_approach = goma_lib.GomaApproach(
125 "?cros", "goma.chromium.org", True
126 )
Alex Klein915cce92019-12-17 14:19:50 -0700127
Alex Klein1699fab2022-09-08 08:46:06 -0600128 # Note that we are not specifying the goma log_dir so that goma will create
129 # and use a tmp dir for the logs.
130 stats_filename = goma_message.stats_file or None
131 counterz_filename = goma_message.counterz_file or None
Alex Klein915cce92019-12-17 14:19:50 -0700132
Alex Klein1699fab2022-09-08 08:46:06 -0600133 return goma_lib.Goma(
134 goma_message.goma_dir,
Alex Klein1699fab2022-09-08 08:46:06 -0600135 stage_name="BuildAPI",
136 chromeos_goma_dir=chromeos_goma_dir,
137 chroot_dir=chroot_path,
138 goma_approach=goma_approach,
139 stats_filename=stats_filename,
140 counterz_filename=counterz_filename,
141 )
Alex Klein915cce92019-12-17 14:19:50 -0700142
143
Kevin Sheltona8056362022-04-04 16:19:23 -0700144def ParseBuildTarget(
145 build_target_message: common_pb2.BuildTarget,
Alex Klein1699fab2022-09-08 08:46:06 -0600146 profile_message: Optional[sysroot_pb2.Profile] = None,
Kevin Sheltona8056362022-04-04 16:19:23 -0700147) -> build_target_lib.BuildTarget:
Alex Klein1699fab2022-09-08 08:46:06 -0600148 """Create a BuildTarget object from a build_target message.
Alex Klein171da612019-08-06 14:00:42 -0600149
Alex Klein1699fab2022-09-08 08:46:06 -0600150 Args:
Alex Klein611dddd2022-10-11 17:02:01 -0600151 build_target_message: The BuildTarget message.
152 profile_message: The profile message.
Alex Klein171da612019-08-06 14:00:42 -0600153
Alex Klein1699fab2022-09-08 08:46:06 -0600154 Returns:
Alex Klein611dddd2022-10-11 17:02:01 -0600155 BuildTarget: The parsed instance.
Alex Klein171da612019-08-06 14:00:42 -0600156
Alex Klein1699fab2022-09-08 08:46:06 -0600157 Raises:
Alex Klein611dddd2022-10-11 17:02:01 -0600158 AssertionError: When the field is not a BuildTarget message.
Alex Klein1699fab2022-09-08 08:46:06 -0600159 """
160 assert isinstance(build_target_message, common_pb2.BuildTarget)
161 assert profile_message is None or isinstance(
162 profile_message, sysroot_pb2.Profile
163 )
Alex Klein171da612019-08-06 14:00:42 -0600164
Alex Klein1699fab2022-09-08 08:46:06 -0600165 profile_name = profile_message.name if profile_message else None
166 return build_target_lib.BuildTarget(
167 build_target_message.name, profile=profile_name
168 )
Alex Klein171da612019-08-06 14:00:42 -0600169
170
171def ParseBuildTargets(repeated_build_target_field):
Alex Klein1699fab2022-09-08 08:46:06 -0600172 """Create a BuildTarget for each entry in the repeated field.
Alex Klein171da612019-08-06 14:00:42 -0600173
Alex Klein1699fab2022-09-08 08:46:06 -0600174 Args:
Alex Klein611dddd2022-10-11 17:02:01 -0600175 repeated_build_target_field: The repeated BuildTarget field.
Alex Klein171da612019-08-06 14:00:42 -0600176
Alex Klein1699fab2022-09-08 08:46:06 -0600177 Returns:
Alex Klein611dddd2022-10-11 17:02:01 -0600178 list[BuildTarget]: The parsed BuildTargets.
Alex Klein171da612019-08-06 14:00:42 -0600179
Alex Klein1699fab2022-09-08 08:46:06 -0600180 Raises:
Alex Klein611dddd2022-10-11 17:02:01 -0600181 AssertionError: When the field contains non-BuildTarget messages.
Alex Klein1699fab2022-09-08 08:46:06 -0600182 """
183 return [ParseBuildTarget(target) for target in repeated_build_target_field]
Alex Klein4f0eb432019-05-02 13:56:04 -0600184
185
Alex Klein1699fab2022-09-08 08:46:06 -0600186def serialize_package_info(
187 pkg_info: package_info.PackageInfo,
188 pkg_info_msg: Union[common_pb2.PackageInfo, "portage_pb2.Portage.Package"],
189):
190 """Serialize a PackageInfo object to a PackageInfo proto."""
191 if not isinstance(pkg_info, package_info.PackageInfo):
192 # Allows us to swap everything to serialize_package_info, and search the
193 # logs for usages that aren't passing though a PackageInfo yet.
194 logging.warning(
195 "serialize_package_info: Got a %s instead of a PackageInfo.",
196 type(pkg_info),
197 )
198 pkg_info = package_info.parse(pkg_info)
199 pkg_info_msg.package_name = pkg_info.package
200 if pkg_info.category:
201 pkg_info_msg.category = pkg_info.category
202 if pkg_info.vr:
203 pkg_info_msg.version = pkg_info.vr
Alex Klein1e68a8e2020-10-06 17:25:11 -0600204
205
206def deserialize_package_info(pkg_info_msg):
Alex Klein1699fab2022-09-08 08:46:06 -0600207 """Deserialize a PackageInfo message to a PackageInfo object."""
208 return package_info.parse(PackageInfoToString(pkg_info_msg))
Alex Klein1e68a8e2020-10-06 17:25:11 -0600209
210
Alex Klein1699fab2022-09-08 08:46:06 -0600211def retrieve_package_log_paths(
212 packages: Iterable[package_info.PackageInfo],
213 output_proto: Union[
214 sysroot_pb2.InstallPackagesResponse,
215 sysroot_pb2.InstallToolchainResponse,
216 test_pb2.BuildTargetUnitTestResponse,
217 ],
218 target_sysroot: sysroot_lib.Sysroot,
219) -> None:
220 """Get the path to the log file for each package that failed to build.
Lizzy Presland29e62452022-01-05 21:58:21 +0000221
Alex Klein1699fab2022-09-08 08:46:06 -0600222 Args:
Alex Klein611dddd2022-10-11 17:02:01 -0600223 packages: A list of packages which failed to build.
224 output_proto: The Response message for a given API call. This response
225 proto must contain a failed_package_data field.
226 target_sysroot: The sysroot used by the build step.
Alex Klein1699fab2022-09-08 08:46:06 -0600227 """
228 for pkg_info in packages:
229 # Grab the paths to the log files for each failed package from the
230 # sysroot.
231 # Logs currently exist within the sysroot in the form of:
232 # /build/${BOARD}/tmp/portage/logs/$CATEGORY:$PF:$TIMESTAMP.log
233 failed_pkg_data_msg = output_proto.failed_package_data.add()
234 serialize_package_info(pkg_info, failed_pkg_data_msg.name)
235 glob_path = os.path.join(
236 target_sysroot.portage_logdir,
237 f"{pkg_info.category}:{pkg_info.pvr}:*.log",
238 )
239 log_files = glob.glob(glob_path)
240 log_files.sort(reverse=True)
241 # Omit path if files don't exist for some reason.
242 if not log_files:
243 logging.warning(
244 "Log file for %s was not found. Search path: %s",
245 pkg_info.cpvr,
246 glob_path,
247 )
248 continue
249 failed_pkg_data_msg.log_path.path = log_files[0]
250 failed_pkg_data_msg.log_path.location = common_pb2.Path.INSIDE
Lizzy Presland29e62452022-01-05 21:58:21 +0000251
252
Alex Klein18a60af2020-06-11 12:08:47 -0600253def PackageInfoToCPV(package_info_msg):
Alex Klein1699fab2022-09-08 08:46:06 -0600254 """Helper to translate a PackageInfo message into a CPV."""
255 if not package_info_msg or not package_info_msg.package_name:
256 return None
Alex Kleina9d500b2019-04-22 15:37:51 -0600257
Alex Klein1699fab2022-09-08 08:46:06 -0600258 return package_info.SplitCPV(
259 PackageInfoToString(package_info_msg), strict=False
260 )
Alex Kleina9d500b2019-04-22 15:37:51 -0600261
262
Alex Klein18a60af2020-06-11 12:08:47 -0600263def PackageInfoToString(package_info_msg):
Alex Klein1699fab2022-09-08 08:46:06 -0600264 """Combine the components into the full package string."""
265 # TODO: Use the lib.parser.package_info.PackageInfo class instead.
266 if not package_info_msg.package_name:
267 raise ValueError("Invalid PackageInfo message.")
Alex Kleina9d500b2019-04-22 15:37:51 -0600268
Alex Klein1699fab2022-09-08 08:46:06 -0600269 c = ("%s/" % package_info_msg.category) if package_info_msg.category else ""
270 p = package_info_msg.package_name
271 v = ("-%s" % package_info_msg.version) if package_info_msg.version else ""
272 return "%s%s%s" % (c, p, v)