blob: 0b93ee4af82201601173f0dfb355c0be51d46966 [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
Trent Aptedbd3cb412023-08-18 11:37:02 +100013from chromite.api.gen.chromite.api import sdk_subtools_pb2
Mike Frysinger1cc8f1f2022-04-28 22:40:40 -040014from chromite.api.gen.chromite.api import sysroot_pb2
15from chromite.api.gen.chromite.api import test_pb2
Alex Klein1f67cf32019-10-09 11:13:42 -060016from chromite.api.gen.chromiumos import common_pb2
Alex Klein247d7922023-01-18 15:36:02 -070017from chromite.lib import binpkg
Alex Klein26e472b2020-03-10 14:35:01 -060018from chromite.lib import build_target_lib
George Engelbrecht35b6e8d2021-06-18 09:43:28 -060019from chromite.lib import chroot_lib
Mike Frysinger1cc8f1f2022-04-28 22:40:40 -040020from chromite.lib import constants
Ram Chandrasekare08e3ba2022-04-04 21:42:27 +000021from chromite.lib import goma_lib
Joanna Wang92cad812021-11-03 14:52:08 -070022from chromite.lib import remoteexec_util
George Engelbrecht35b6e8d2021-06-18 09:43:28 -060023from chromite.lib import sysroot_lib
Mike Frysinger1cc8f1f2022-04-28 22:40:40 -040024from chromite.lib.parser import package_info
25
Alex Klein171da612019-08-06 14:00:42 -060026
Alex Klein46c30f32021-11-10 13:12:50 -070027if TYPE_CHECKING:
Alex Klein1699fab2022-09-08 08:46:06 -060028 from chromite.api.gen.chromiumos.build.api import portage_pb2
29
Alex Klein46c30f32021-11-10 13:12:50 -070030
Alex Klein171da612019-08-06 14:00:42 -060031class Error(Exception):
Alex Klein1699fab2022-09-08 08:46:06 -060032 """Base error class for the module."""
Alex Klein171da612019-08-06 14:00:42 -060033
34
35class InvalidMessageError(Error):
Alex Klein1699fab2022-09-08 08:46:06 -060036 """Invalid message."""
Alex Kleina9d500b2019-04-22 15:37:51 -060037
38
Kevin Sheltona8056362022-04-04 16:19:23 -070039def ParseChroot(chroot_message: common_pb2.Chroot) -> chroot_lib.Chroot:
Alex Klein1699fab2022-09-08 08:46:06 -060040 """Create a chroot object from the chroot message.
Alex Klein171da612019-08-06 14:00:42 -060041
Alex Klein1699fab2022-09-08 08:46:06 -060042 Args:
Alex Klein611dddd2022-10-11 17:02:01 -060043 chroot_message: The chroot message.
Alex Klein171da612019-08-06 14:00:42 -060044
Alex Klein1699fab2022-09-08 08:46:06 -060045 Returns:
Alex Klein611dddd2022-10-11 17:02:01 -060046 Chroot: The parsed chroot object.
Alex Klein171da612019-08-06 14:00:42 -060047
Alex Klein1699fab2022-09-08 08:46:06 -060048 Raises:
Alex Klein611dddd2022-10-11 17:02:01 -060049 AssertionError: When the message is not a Chroot message.
Alex Klein1699fab2022-09-08 08:46:06 -060050 """
51 assert isinstance(chroot_message, common_pb2.Chroot)
Alex Klein171da612019-08-06 14:00:42 -060052
Alex Klein1699fab2022-09-08 08:46:06 -060053 path = chroot_message.path or constants.DEFAULT_CHROOT_PATH
54 cache_dir = chroot_message.cache_dir
55 chrome_root = chroot_message.chrome_dir
Brian Norrisa8a1e952023-04-25 15:03:11 -070056 out_path = (
57 Path(chroot_message.out_path)
58 if chroot_message.out_path
59 else constants.DEFAULT_OUT_PATH
60 )
Alex Klein4f0eb432019-05-02 13:56:04 -060061
Alex Klein1699fab2022-09-08 08:46:06 -060062 use_flags = [u.flag for u in chroot_message.env.use_flags]
63 features = [f.feature for f in chroot_message.env.features]
Alex Klein38c7d9e2019-05-08 09:31:19 -060064
Alex Klein1699fab2022-09-08 08:46:06 -060065 env = {}
66 if use_flags:
67 env["USE"] = " ".join(use_flags)
Alex Klein38c7d9e2019-05-08 09:31:19 -060068
Alex Klein1699fab2022-09-08 08:46:06 -060069 # Make sure it'll use the local source to build chrome when we have it.
70 if chrome_root:
71 env["CHROME_ORIGIN"] = "LOCAL_SOURCE"
Alex Kleinb7485bb2019-09-19 13:23:37 -060072
Alex Klein1699fab2022-09-08 08:46:06 -060073 if features:
74 env["FEATURES"] = " ".join(features)
Alex Klein38c7d9e2019-05-08 09:31:19 -060075
Alex Klein1699fab2022-09-08 08:46:06 -060076 chroot = chroot_lib.Chroot(
Brian Norrisd0dfeae2023-03-09 13:06:47 -080077 path=path,
78 out_path=out_path,
79 cache_dir=cache_dir,
80 chrome_root=chrome_root,
81 env=env,
Alex Klein1699fab2022-09-08 08:46:06 -060082 )
Alex Klein171da612019-08-06 14:00:42 -060083
Alex Klein1699fab2022-09-08 08:46:06 -060084 return chroot
Alex Klein171da612019-08-06 14:00:42 -060085
George Engelbrechtc9a8e812021-06-16 18:14:17 -060086
Kevin Sheltona8056362022-04-04 16:19:23 -070087def ParseSysroot(sysroot_message: sysroot_pb2.Sysroot) -> sysroot_lib.Sysroot:
Alex Klein1699fab2022-09-08 08:46:06 -060088 """Create a sysroot object from the sysroot message.
George Engelbrechtc9a8e812021-06-16 18:14:17 -060089
Alex Klein1699fab2022-09-08 08:46:06 -060090 Args:
Alex Klein611dddd2022-10-11 17:02:01 -060091 sysroot_message: The sysroot message.
George Engelbrechtc9a8e812021-06-16 18:14:17 -060092
Alex Klein1699fab2022-09-08 08:46:06 -060093 Returns:
Alex Klein611dddd2022-10-11 17:02:01 -060094 Sysroot: The parsed sysroot object.
George Engelbrechtc9a8e812021-06-16 18:14:17 -060095
Alex Klein1699fab2022-09-08 08:46:06 -060096 Raises:
Alex Klein611dddd2022-10-11 17:02:01 -060097 AssertionError: When the message is not a Sysroot message.
Alex Klein1699fab2022-09-08 08:46:06 -060098 """
99 assert isinstance(sysroot_message, sysroot_pb2.Sysroot)
George Engelbrechtc9a8e812021-06-16 18:14:17 -0600100
Alex Klein1699fab2022-09-08 08:46:06 -0600101 return sysroot_lib.Sysroot(sysroot_message.path)
George Engelbrechtc9a8e812021-06-16 18:14:17 -0600102
103
Joanna Wang92cad812021-11-03 14:52:08 -0700104def ParseRemoteexecConfig(remoteexec_message: common_pb2.RemoteexecConfig):
Alex Klein1699fab2022-09-08 08:46:06 -0600105 """Parse a remoteexec config message."""
106 assert isinstance(remoteexec_message, common_pb2.RemoteexecConfig)
Joanna Wang92cad812021-11-03 14:52:08 -0700107
Alex Klein1699fab2022-09-08 08:46:06 -0600108 if not (
109 remoteexec_message.reclient_dir or remoteexec_message.reproxy_cfg_file
110 ):
111 return None
Joanna Wang92cad812021-11-03 14:52:08 -0700112
Alex Klein1699fab2022-09-08 08:46:06 -0600113 return remoteexec_util.Remoteexec(
114 remoteexec_message.reclient_dir, remoteexec_message.reproxy_cfg_file
115 )
Joanna Wang92cad812021-11-03 14:52:08 -0700116
117
Brian Norrisd0dfeae2023-03-09 13:06:47 -0800118def ParseGomaConfig(goma_message, chroot_path, out_path):
Alex Klein1699fab2022-09-08 08:46:06 -0600119 """Parse a goma config message."""
120 assert isinstance(goma_message, common_pb2.GomaConfig)
Alex Klein915cce92019-12-17 14:19:50 -0700121
Alex Klein1699fab2022-09-08 08:46:06 -0600122 if not goma_message.goma_dir:
123 return None
Alex Klein915cce92019-12-17 14:19:50 -0700124
Alex Klein1699fab2022-09-08 08:46:06 -0600125 # Parse the goma config.
126 chromeos_goma_dir = goma_message.chromeos_goma_dir or None
127 if goma_message.goma_approach == common_pb2.GomaConfig.RBE_STAGING:
128 goma_approach = goma_lib.GomaApproach(
129 "?staging", "staging-goma.chromium.org", True
130 )
131 elif goma_message.goma_approach == common_pb2.GomaConfig.RBE_PROD:
132 goma_approach = goma_lib.GomaApproach(
133 "?prod", "goma.chromium.org", True
134 )
135 else:
136 goma_approach = goma_lib.GomaApproach(
137 "?cros", "goma.chromium.org", True
138 )
Alex Klein915cce92019-12-17 14:19:50 -0700139
Alex Klein1699fab2022-09-08 08:46:06 -0600140 # Note that we are not specifying the goma log_dir so that goma will create
141 # and use a tmp dir for the logs.
142 stats_filename = goma_message.stats_file or None
143 counterz_filename = goma_message.counterz_file or None
Alex Klein915cce92019-12-17 14:19:50 -0700144
Alex Klein1699fab2022-09-08 08:46:06 -0600145 return goma_lib.Goma(
146 goma_message.goma_dir,
Alex Klein1699fab2022-09-08 08:46:06 -0600147 stage_name="BuildAPI",
148 chromeos_goma_dir=chromeos_goma_dir,
149 chroot_dir=chroot_path,
Brian Norrisd0dfeae2023-03-09 13:06:47 -0800150 out_dir=out_path,
Alex Klein1699fab2022-09-08 08:46:06 -0600151 goma_approach=goma_approach,
152 stats_filename=stats_filename,
153 counterz_filename=counterz_filename,
154 )
Alex Klein915cce92019-12-17 14:19:50 -0700155
156
Kevin Sheltona8056362022-04-04 16:19:23 -0700157def ParseBuildTarget(
158 build_target_message: common_pb2.BuildTarget,
Alex Klein1699fab2022-09-08 08:46:06 -0600159 profile_message: Optional[sysroot_pb2.Profile] = None,
Kevin Sheltona8056362022-04-04 16:19:23 -0700160) -> build_target_lib.BuildTarget:
Alex Klein1699fab2022-09-08 08:46:06 -0600161 """Create a BuildTarget object from a build_target message.
Alex Klein171da612019-08-06 14:00:42 -0600162
Alex Klein1699fab2022-09-08 08:46:06 -0600163 Args:
Alex Klein611dddd2022-10-11 17:02:01 -0600164 build_target_message: The BuildTarget message.
165 profile_message: The profile message.
Alex Klein171da612019-08-06 14:00:42 -0600166
Alex Klein1699fab2022-09-08 08:46:06 -0600167 Returns:
Alex Klein611dddd2022-10-11 17:02:01 -0600168 BuildTarget: The parsed instance.
Alex Klein171da612019-08-06 14:00:42 -0600169
Alex Klein1699fab2022-09-08 08:46:06 -0600170 Raises:
Alex Klein611dddd2022-10-11 17:02:01 -0600171 AssertionError: When the field is not a BuildTarget message.
Alex Klein1699fab2022-09-08 08:46:06 -0600172 """
173 assert isinstance(build_target_message, common_pb2.BuildTarget)
174 assert profile_message is None or isinstance(
175 profile_message, sysroot_pb2.Profile
176 )
Alex Klein171da612019-08-06 14:00:42 -0600177
Alex Klein1699fab2022-09-08 08:46:06 -0600178 profile_name = profile_message.name if profile_message else None
179 return build_target_lib.BuildTarget(
180 build_target_message.name, profile=profile_name
181 )
Alex Klein171da612019-08-06 14:00:42 -0600182
183
184def ParseBuildTargets(repeated_build_target_field):
Alex Klein1699fab2022-09-08 08:46:06 -0600185 """Create a BuildTarget for each entry in the repeated field.
Alex Klein171da612019-08-06 14:00:42 -0600186
Alex Klein1699fab2022-09-08 08:46:06 -0600187 Args:
Alex Klein611dddd2022-10-11 17:02:01 -0600188 repeated_build_target_field: The repeated BuildTarget field.
Alex Klein171da612019-08-06 14:00:42 -0600189
Alex Klein1699fab2022-09-08 08:46:06 -0600190 Returns:
Alex Klein611dddd2022-10-11 17:02:01 -0600191 list[BuildTarget]: The parsed BuildTargets.
Alex Klein171da612019-08-06 14:00:42 -0600192
Alex Klein1699fab2022-09-08 08:46:06 -0600193 Raises:
Alex Klein611dddd2022-10-11 17:02:01 -0600194 AssertionError: When the field contains non-BuildTarget messages.
Alex Klein1699fab2022-09-08 08:46:06 -0600195 """
196 return [ParseBuildTarget(target) for target in repeated_build_target_field]
Alex Klein4f0eb432019-05-02 13:56:04 -0600197
198
Alex Klein247d7922023-01-18 15:36:02 -0700199def deserialize_profile(profile: common_pb2.Profile) -> sysroot_lib.Profile:
200 """Deserialize a portage profile message to a Profile object."""
201 return sysroot_lib.Profile(profile.name)
202
203
204def deserialize_package_index_info(
205 message: common_pb2.PackageIndexInfo,
206) -> binpkg.PackageIndexInfo:
207 """Deserialize a PackageIndexInfo message to an object."""
208 return binpkg.PackageIndexInfo(
209 snapshot_sha=message.snapshot_sha,
210 snapshot_number=message.snapshot_number,
211 build_target=ParseBuildTarget(message.build_target),
212 profile=deserialize_profile(message.profile),
213 location=message.location,
214 )
215
216
Alex Klein1699fab2022-09-08 08:46:06 -0600217def serialize_package_info(
218 pkg_info: package_info.PackageInfo,
219 pkg_info_msg: Union[common_pb2.PackageInfo, "portage_pb2.Portage.Package"],
220):
221 """Serialize a PackageInfo object to a PackageInfo proto."""
222 if not isinstance(pkg_info, package_info.PackageInfo):
223 # Allows us to swap everything to serialize_package_info, and search the
224 # logs for usages that aren't passing though a PackageInfo yet.
225 logging.warning(
226 "serialize_package_info: Got a %s instead of a PackageInfo.",
227 type(pkg_info),
228 )
229 pkg_info = package_info.parse(pkg_info)
230 pkg_info_msg.package_name = pkg_info.package
231 if pkg_info.category:
232 pkg_info_msg.category = pkg_info.category
233 if pkg_info.vr:
234 pkg_info_msg.version = pkg_info.vr
Alex Klein1e68a8e2020-10-06 17:25:11 -0600235
236
237def deserialize_package_info(pkg_info_msg):
Alex Klein1699fab2022-09-08 08:46:06 -0600238 """Deserialize a PackageInfo message to a PackageInfo object."""
239 return package_info.parse(PackageInfoToString(pkg_info_msg))
Alex Klein1e68a8e2020-10-06 17:25:11 -0600240
241
Alex Klein1699fab2022-09-08 08:46:06 -0600242def retrieve_package_log_paths(
243 packages: Iterable[package_info.PackageInfo],
244 output_proto: Union[
245 sysroot_pb2.InstallPackagesResponse,
246 sysroot_pb2.InstallToolchainResponse,
Trent Aptedbd3cb412023-08-18 11:37:02 +1000247 sdk_subtools_pb2.BuildSdkSubtoolsResponse,
Alex Klein1699fab2022-09-08 08:46:06 -0600248 test_pb2.BuildTargetUnitTestResponse,
249 ],
250 target_sysroot: sysroot_lib.Sysroot,
251) -> None:
252 """Get the path to the log file for each package that failed to build.
Lizzy Presland29e62452022-01-05 21:58:21 +0000253
Alex Klein1699fab2022-09-08 08:46:06 -0600254 Args:
Alex Klein611dddd2022-10-11 17:02:01 -0600255 packages: A list of packages which failed to build.
256 output_proto: The Response message for a given API call. This response
257 proto must contain a failed_package_data field.
258 target_sysroot: The sysroot used by the build step.
Alex Klein1699fab2022-09-08 08:46:06 -0600259 """
260 for pkg_info in packages:
261 # Grab the paths to the log files for each failed package from the
262 # sysroot.
263 # Logs currently exist within the sysroot in the form of:
264 # /build/${BOARD}/tmp/portage/logs/$CATEGORY:$PF:$TIMESTAMP.log
265 failed_pkg_data_msg = output_proto.failed_package_data.add()
266 serialize_package_info(pkg_info, failed_pkg_data_msg.name)
267 glob_path = os.path.join(
268 target_sysroot.portage_logdir,
269 f"{pkg_info.category}:{pkg_info.pvr}:*.log",
270 )
271 log_files = glob.glob(glob_path)
272 log_files.sort(reverse=True)
273 # Omit path if files don't exist for some reason.
274 if not log_files:
275 logging.warning(
276 "Log file for %s was not found. Search path: %s",
277 pkg_info.cpvr,
278 glob_path,
279 )
280 continue
281 failed_pkg_data_msg.log_path.path = log_files[0]
282 failed_pkg_data_msg.log_path.location = common_pb2.Path.INSIDE
Lizzy Presland29e62452022-01-05 21:58:21 +0000283
284
Alex Klein18a60af2020-06-11 12:08:47 -0600285def PackageInfoToString(package_info_msg):
Alex Klein1699fab2022-09-08 08:46:06 -0600286 """Combine the components into the full package string."""
287 # TODO: Use the lib.parser.package_info.PackageInfo class instead.
288 if not package_info_msg.package_name:
289 raise ValueError("Invalid PackageInfo message.")
Alex Kleina9d500b2019-04-22 15:37:51 -0600290
Alex Klein1699fab2022-09-08 08:46:06 -0600291 c = ("%s/" % package_info_msg.category) if package_info_msg.category else ""
292 p = package_info_msg.package_name
293 v = ("-%s" % package_info_msg.version) if package_info_msg.version else ""
294 return "%s%s%s" % (c, p, v)
Greg Edelston1f5deb62023-03-31 14:22:08 -0600295
296
297def pb2_path_to_pathlib_path(
298 pb2_path: "common_pb2.Path",
299 chroot: Optional["common_pb2.Chroot"] = None,
300) -> Path:
301 """Convert an absolute pb2 path to a pathlib.Path outside the chroot.
302
303 Args:
304 pb2_path: An absolute path, which might be inside or outside chroot.
305 chroot: The chroot that the path might be inside of.
306
307 Returns:
308 A Path pointing to the same location as pb2_path, originating
309 outside the chroot.
310
311 Raises:
312 ValueError: If the given path is relative instead of absolute.
313 ValueError: If the given path is inside the chroot, but a chroot is not
314 provided.
315 """
316 if pb2_path.path[0] != "/":
317 raise ValueError(f"Cannot convert relative path: {pb2_path.path}")
318 if pb2_path.location is common_pb2.Path.Location.OUTSIDE:
319 return Path(pb2_path.path)
320 if chroot is None:
321 raise ValueError("Cannot convert inside path without a chroot.")
Brian Norris41f247b2023-06-30 11:09:40 -0700322 return Path(ParseChroot(chroot).full_path(pb2_path.path))