blob: 59bccd77cd614dfa7124855a8c5c6d687fb5ae5e [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
Alex Klein4f0eb432019-05-02 13:56:04 -060054
Alex Klein1699fab2022-09-08 08:46:06 -060055 use_flags = [u.flag for u in chroot_message.env.use_flags]
56 features = [f.feature for f in chroot_message.env.features]
Alex Klein38c7d9e2019-05-08 09:31:19 -060057
Alex Klein1699fab2022-09-08 08:46:06 -060058 env = {}
59 if use_flags:
60 env["USE"] = " ".join(use_flags)
Alex Klein38c7d9e2019-05-08 09:31:19 -060061
Alex Klein1699fab2022-09-08 08:46:06 -060062 # Make sure it'll use the local source to build chrome when we have it.
63 if chrome_root:
64 env["CHROME_ORIGIN"] = "LOCAL_SOURCE"
Alex Kleinb7485bb2019-09-19 13:23:37 -060065
Alex Klein1699fab2022-09-08 08:46:06 -060066 if features:
67 env["FEATURES"] = " ".join(features)
Alex Klein38c7d9e2019-05-08 09:31:19 -060068
Alex Klein1699fab2022-09-08 08:46:06 -060069 chroot = chroot_lib.Chroot(
70 path=path, cache_dir=cache_dir, chrome_root=chrome_root, env=env
71 )
Alex Klein171da612019-08-06 14:00:42 -060072
Alex Klein1699fab2022-09-08 08:46:06 -060073 return chroot
Alex Klein171da612019-08-06 14:00:42 -060074
George Engelbrechtc9a8e812021-06-16 18:14:17 -060075
Kevin Sheltona8056362022-04-04 16:19:23 -070076def ParseSysroot(sysroot_message: sysroot_pb2.Sysroot) -> sysroot_lib.Sysroot:
Alex Klein1699fab2022-09-08 08:46:06 -060077 """Create a sysroot object from the sysroot message.
George Engelbrechtc9a8e812021-06-16 18:14:17 -060078
Alex Klein1699fab2022-09-08 08:46:06 -060079 Args:
Alex Klein611dddd2022-10-11 17:02:01 -060080 sysroot_message: The sysroot message.
George Engelbrechtc9a8e812021-06-16 18:14:17 -060081
Alex Klein1699fab2022-09-08 08:46:06 -060082 Returns:
Alex Klein611dddd2022-10-11 17:02:01 -060083 Sysroot: The parsed sysroot object.
George Engelbrechtc9a8e812021-06-16 18:14:17 -060084
Alex Klein1699fab2022-09-08 08:46:06 -060085 Raises:
Alex Klein611dddd2022-10-11 17:02:01 -060086 AssertionError: When the message is not a Sysroot message.
Alex Klein1699fab2022-09-08 08:46:06 -060087 """
88 assert isinstance(sysroot_message, sysroot_pb2.Sysroot)
George Engelbrechtc9a8e812021-06-16 18:14:17 -060089
Alex Klein1699fab2022-09-08 08:46:06 -060090 return sysroot_lib.Sysroot(sysroot_message.path)
George Engelbrechtc9a8e812021-06-16 18:14:17 -060091
92
Joanna Wang92cad812021-11-03 14:52:08 -070093def ParseRemoteexecConfig(remoteexec_message: common_pb2.RemoteexecConfig):
Alex Klein1699fab2022-09-08 08:46:06 -060094 """Parse a remoteexec config message."""
95 assert isinstance(remoteexec_message, common_pb2.RemoteexecConfig)
Joanna Wang92cad812021-11-03 14:52:08 -070096
Alex Klein1699fab2022-09-08 08:46:06 -060097 if not (
98 remoteexec_message.reclient_dir or remoteexec_message.reproxy_cfg_file
99 ):
100 return None
Joanna Wang92cad812021-11-03 14:52:08 -0700101
Alex Klein1699fab2022-09-08 08:46:06 -0600102 return remoteexec_util.Remoteexec(
103 remoteexec_message.reclient_dir, remoteexec_message.reproxy_cfg_file
104 )
Joanna Wang92cad812021-11-03 14:52:08 -0700105
106
Alex Klein915cce92019-12-17 14:19:50 -0700107def ParseGomaConfig(goma_message, chroot_path):
Alex Klein1699fab2022-09-08 08:46:06 -0600108 """Parse a goma config message."""
109 assert isinstance(goma_message, common_pb2.GomaConfig)
Alex Klein915cce92019-12-17 14:19:50 -0700110
Alex Klein1699fab2022-09-08 08:46:06 -0600111 if not goma_message.goma_dir:
112 return None
Alex Klein915cce92019-12-17 14:19:50 -0700113
Alex Klein1699fab2022-09-08 08:46:06 -0600114 # Parse the goma config.
115 chromeos_goma_dir = goma_message.chromeos_goma_dir or None
116 if goma_message.goma_approach == common_pb2.GomaConfig.RBE_STAGING:
117 goma_approach = goma_lib.GomaApproach(
118 "?staging", "staging-goma.chromium.org", True
119 )
120 elif goma_message.goma_approach == common_pb2.GomaConfig.RBE_PROD:
121 goma_approach = goma_lib.GomaApproach(
122 "?prod", "goma.chromium.org", True
123 )
124 else:
125 goma_approach = goma_lib.GomaApproach(
126 "?cros", "goma.chromium.org", True
127 )
Alex Klein915cce92019-12-17 14:19:50 -0700128
Alex Klein1699fab2022-09-08 08:46:06 -0600129 # Note that we are not specifying the goma log_dir so that goma will create
130 # and use a tmp dir for the logs.
131 stats_filename = goma_message.stats_file or None
132 counterz_filename = goma_message.counterz_file or None
Alex Klein915cce92019-12-17 14:19:50 -0700133
Alex Klein1699fab2022-09-08 08:46:06 -0600134 return goma_lib.Goma(
135 goma_message.goma_dir,
Alex Klein1699fab2022-09-08 08:46:06 -0600136 stage_name="BuildAPI",
137 chromeos_goma_dir=chromeos_goma_dir,
138 chroot_dir=chroot_path,
139 goma_approach=goma_approach,
140 stats_filename=stats_filename,
141 counterz_filename=counterz_filename,
142 )
Alex Klein915cce92019-12-17 14:19:50 -0700143
144
Kevin Sheltona8056362022-04-04 16:19:23 -0700145def ParseBuildTarget(
146 build_target_message: common_pb2.BuildTarget,
Alex Klein1699fab2022-09-08 08:46:06 -0600147 profile_message: Optional[sysroot_pb2.Profile] = None,
Kevin Sheltona8056362022-04-04 16:19:23 -0700148) -> build_target_lib.BuildTarget:
Alex Klein1699fab2022-09-08 08:46:06 -0600149 """Create a BuildTarget object from a build_target message.
Alex Klein171da612019-08-06 14:00:42 -0600150
Alex Klein1699fab2022-09-08 08:46:06 -0600151 Args:
Alex Klein611dddd2022-10-11 17:02:01 -0600152 build_target_message: The BuildTarget message.
153 profile_message: The profile message.
Alex Klein171da612019-08-06 14:00:42 -0600154
Alex Klein1699fab2022-09-08 08:46:06 -0600155 Returns:
Alex Klein611dddd2022-10-11 17:02:01 -0600156 BuildTarget: The parsed instance.
Alex Klein171da612019-08-06 14:00:42 -0600157
Alex Klein1699fab2022-09-08 08:46:06 -0600158 Raises:
Alex Klein611dddd2022-10-11 17:02:01 -0600159 AssertionError: When the field is not a BuildTarget message.
Alex Klein1699fab2022-09-08 08:46:06 -0600160 """
161 assert isinstance(build_target_message, common_pb2.BuildTarget)
162 assert profile_message is None or isinstance(
163 profile_message, sysroot_pb2.Profile
164 )
Alex Klein171da612019-08-06 14:00:42 -0600165
Alex Klein1699fab2022-09-08 08:46:06 -0600166 profile_name = profile_message.name if profile_message else None
167 return build_target_lib.BuildTarget(
168 build_target_message.name, profile=profile_name
169 )
Alex Klein171da612019-08-06 14:00:42 -0600170
171
172def ParseBuildTargets(repeated_build_target_field):
Alex Klein1699fab2022-09-08 08:46:06 -0600173 """Create a BuildTarget for each entry in the repeated field.
Alex Klein171da612019-08-06 14:00:42 -0600174
Alex Klein1699fab2022-09-08 08:46:06 -0600175 Args:
Alex Klein611dddd2022-10-11 17:02:01 -0600176 repeated_build_target_field: The repeated BuildTarget field.
Alex Klein171da612019-08-06 14:00:42 -0600177
Alex Klein1699fab2022-09-08 08:46:06 -0600178 Returns:
Alex Klein611dddd2022-10-11 17:02:01 -0600179 list[BuildTarget]: The parsed BuildTargets.
Alex Klein171da612019-08-06 14:00:42 -0600180
Alex Klein1699fab2022-09-08 08:46:06 -0600181 Raises:
Alex Klein611dddd2022-10-11 17:02:01 -0600182 AssertionError: When the field contains non-BuildTarget messages.
Alex Klein1699fab2022-09-08 08:46:06 -0600183 """
184 return [ParseBuildTarget(target) for target in repeated_build_target_field]
Alex Klein4f0eb432019-05-02 13:56:04 -0600185
186
Alex Klein247d7922023-01-18 15:36:02 -0700187def deserialize_profile(profile: common_pb2.Profile) -> sysroot_lib.Profile:
188 """Deserialize a portage profile message to a Profile object."""
189 return sysroot_lib.Profile(profile.name)
190
191
192def deserialize_package_index_info(
193 message: common_pb2.PackageIndexInfo,
194) -> binpkg.PackageIndexInfo:
195 """Deserialize a PackageIndexInfo message to an object."""
196 return binpkg.PackageIndexInfo(
197 snapshot_sha=message.snapshot_sha,
198 snapshot_number=message.snapshot_number,
199 build_target=ParseBuildTarget(message.build_target),
200 profile=deserialize_profile(message.profile),
201 location=message.location,
202 )
203
204
Alex Klein1699fab2022-09-08 08:46:06 -0600205def serialize_package_info(
206 pkg_info: package_info.PackageInfo,
207 pkg_info_msg: Union[common_pb2.PackageInfo, "portage_pb2.Portage.Package"],
208):
209 """Serialize a PackageInfo object to a PackageInfo proto."""
210 if not isinstance(pkg_info, package_info.PackageInfo):
211 # Allows us to swap everything to serialize_package_info, and search the
212 # logs for usages that aren't passing though a PackageInfo yet.
213 logging.warning(
214 "serialize_package_info: Got a %s instead of a PackageInfo.",
215 type(pkg_info),
216 )
217 pkg_info = package_info.parse(pkg_info)
218 pkg_info_msg.package_name = pkg_info.package
219 if pkg_info.category:
220 pkg_info_msg.category = pkg_info.category
221 if pkg_info.vr:
222 pkg_info_msg.version = pkg_info.vr
Alex Klein1e68a8e2020-10-06 17:25:11 -0600223
224
225def deserialize_package_info(pkg_info_msg):
Alex Klein1699fab2022-09-08 08:46:06 -0600226 """Deserialize a PackageInfo message to a PackageInfo object."""
227 return package_info.parse(PackageInfoToString(pkg_info_msg))
Alex Klein1e68a8e2020-10-06 17:25:11 -0600228
229
Alex Klein1699fab2022-09-08 08:46:06 -0600230def retrieve_package_log_paths(
231 packages: Iterable[package_info.PackageInfo],
232 output_proto: Union[
233 sysroot_pb2.InstallPackagesResponse,
234 sysroot_pb2.InstallToolchainResponse,
235 test_pb2.BuildTargetUnitTestResponse,
236 ],
237 target_sysroot: sysroot_lib.Sysroot,
238) -> None:
239 """Get the path to the log file for each package that failed to build.
Lizzy Presland29e62452022-01-05 21:58:21 +0000240
Alex Klein1699fab2022-09-08 08:46:06 -0600241 Args:
Alex Klein611dddd2022-10-11 17:02:01 -0600242 packages: A list of packages which failed to build.
243 output_proto: The Response message for a given API call. This response
244 proto must contain a failed_package_data field.
245 target_sysroot: The sysroot used by the build step.
Alex Klein1699fab2022-09-08 08:46:06 -0600246 """
247 for pkg_info in packages:
248 # Grab the paths to the log files for each failed package from the
249 # sysroot.
250 # Logs currently exist within the sysroot in the form of:
251 # /build/${BOARD}/tmp/portage/logs/$CATEGORY:$PF:$TIMESTAMP.log
252 failed_pkg_data_msg = output_proto.failed_package_data.add()
253 serialize_package_info(pkg_info, failed_pkg_data_msg.name)
254 glob_path = os.path.join(
255 target_sysroot.portage_logdir,
256 f"{pkg_info.category}:{pkg_info.pvr}:*.log",
257 )
258 log_files = glob.glob(glob_path)
259 log_files.sort(reverse=True)
260 # Omit path if files don't exist for some reason.
261 if not log_files:
262 logging.warning(
263 "Log file for %s was not found. Search path: %s",
264 pkg_info.cpvr,
265 glob_path,
266 )
267 continue
268 failed_pkg_data_msg.log_path.path = log_files[0]
269 failed_pkg_data_msg.log_path.location = common_pb2.Path.INSIDE
Lizzy Presland29e62452022-01-05 21:58:21 +0000270
271
Alex Klein18a60af2020-06-11 12:08:47 -0600272def PackageInfoToCPV(package_info_msg):
Alex Klein1699fab2022-09-08 08:46:06 -0600273 """Helper to translate a PackageInfo message into a CPV."""
274 if not package_info_msg or not package_info_msg.package_name:
275 return None
Alex Kleina9d500b2019-04-22 15:37:51 -0600276
Alex Klein1699fab2022-09-08 08:46:06 -0600277 return package_info.SplitCPV(
278 PackageInfoToString(package_info_msg), strict=False
279 )
Alex Kleina9d500b2019-04-22 15:37:51 -0600280
281
Alex Klein18a60af2020-06-11 12:08:47 -0600282def PackageInfoToString(package_info_msg):
Alex Klein1699fab2022-09-08 08:46:06 -0600283 """Combine the components into the full package string."""
284 # TODO: Use the lib.parser.package_info.PackageInfo class instead.
285 if not package_info_msg.package_name:
286 raise ValueError("Invalid PackageInfo message.")
Alex Kleina9d500b2019-04-22 15:37:51 -0600287
Alex Klein1699fab2022-09-08 08:46:06 -0600288 c = ("%s/" % package_info_msg.category) if package_info_msg.category else ""
289 p = package_info_msg.package_name
290 v = ("-%s" % package_info_msg.version) if package_info_msg.version else ""
291 return "%s%s%s" % (c, p, v)