blob: 07c61896536afe133e9cdc9ca2b0fcd1d1118c03 [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 Norrisa8a1e952023-04-25 15:03:11 -070055 out_path = (
56 Path(chroot_message.out_path)
57 if chroot_message.out_path
58 else constants.DEFAULT_OUT_PATH
59 )
Alex Klein4f0eb432019-05-02 13:56:04 -060060
Alex Klein1699fab2022-09-08 08:46:06 -060061 use_flags = [u.flag for u in chroot_message.env.use_flags]
62 features = [f.feature for f in chroot_message.env.features]
Alex Klein38c7d9e2019-05-08 09:31:19 -060063
Alex Klein1699fab2022-09-08 08:46:06 -060064 env = {}
65 if use_flags:
66 env["USE"] = " ".join(use_flags)
Alex Klein38c7d9e2019-05-08 09:31:19 -060067
Alex Klein1699fab2022-09-08 08:46:06 -060068 # Make sure it'll use the local source to build chrome when we have it.
69 if chrome_root:
70 env["CHROME_ORIGIN"] = "LOCAL_SOURCE"
Alex Kleinb7485bb2019-09-19 13:23:37 -060071
Alex Klein1699fab2022-09-08 08:46:06 -060072 if features:
73 env["FEATURES"] = " ".join(features)
Alex Klein38c7d9e2019-05-08 09:31:19 -060074
Alex Klein1699fab2022-09-08 08:46:06 -060075 chroot = chroot_lib.Chroot(
Brian Norrisd0dfeae2023-03-09 13:06:47 -080076 path=path,
77 out_path=out_path,
78 cache_dir=cache_dir,
79 chrome_root=chrome_root,
80 env=env,
Alex Klein1699fab2022-09-08 08:46:06 -060081 )
Alex Klein171da612019-08-06 14:00:42 -060082
Alex Klein1699fab2022-09-08 08:46:06 -060083 return chroot
Alex Klein171da612019-08-06 14:00:42 -060084
George Engelbrechtc9a8e812021-06-16 18:14:17 -060085
Kevin Sheltona8056362022-04-04 16:19:23 -070086def ParseSysroot(sysroot_message: sysroot_pb2.Sysroot) -> sysroot_lib.Sysroot:
Alex Klein1699fab2022-09-08 08:46:06 -060087 """Create a sysroot object from the sysroot message.
George Engelbrechtc9a8e812021-06-16 18:14:17 -060088
Alex Klein1699fab2022-09-08 08:46:06 -060089 Args:
Alex Klein611dddd2022-10-11 17:02:01 -060090 sysroot_message: The sysroot message.
George Engelbrechtc9a8e812021-06-16 18:14:17 -060091
Alex Klein1699fab2022-09-08 08:46:06 -060092 Returns:
Alex Klein611dddd2022-10-11 17:02:01 -060093 Sysroot: The parsed sysroot object.
George Engelbrechtc9a8e812021-06-16 18:14:17 -060094
Alex Klein1699fab2022-09-08 08:46:06 -060095 Raises:
Alex Klein611dddd2022-10-11 17:02:01 -060096 AssertionError: When the message is not a Sysroot message.
Alex Klein1699fab2022-09-08 08:46:06 -060097 """
98 assert isinstance(sysroot_message, sysroot_pb2.Sysroot)
George Engelbrechtc9a8e812021-06-16 18:14:17 -060099
Alex Klein1699fab2022-09-08 08:46:06 -0600100 return sysroot_lib.Sysroot(sysroot_message.path)
George Engelbrechtc9a8e812021-06-16 18:14:17 -0600101
102
Joanna Wang92cad812021-11-03 14:52:08 -0700103def ParseRemoteexecConfig(remoteexec_message: common_pb2.RemoteexecConfig):
Alex Klein1699fab2022-09-08 08:46:06 -0600104 """Parse a remoteexec config message."""
105 assert isinstance(remoteexec_message, common_pb2.RemoteexecConfig)
Joanna Wang92cad812021-11-03 14:52:08 -0700106
Alex Klein1699fab2022-09-08 08:46:06 -0600107 if not (
108 remoteexec_message.reclient_dir or remoteexec_message.reproxy_cfg_file
109 ):
110 return None
Joanna Wang92cad812021-11-03 14:52:08 -0700111
Alex Klein1699fab2022-09-08 08:46:06 -0600112 return remoteexec_util.Remoteexec(
113 remoteexec_message.reclient_dir, remoteexec_message.reproxy_cfg_file
114 )
Joanna Wang92cad812021-11-03 14:52:08 -0700115
116
Brian Norrisd0dfeae2023-03-09 13:06:47 -0800117def ParseGomaConfig(goma_message, chroot_path, out_path):
Alex Klein1699fab2022-09-08 08:46:06 -0600118 """Parse a goma config message."""
119 assert isinstance(goma_message, common_pb2.GomaConfig)
Alex Klein915cce92019-12-17 14:19:50 -0700120
Alex Klein1699fab2022-09-08 08:46:06 -0600121 if not goma_message.goma_dir:
122 return None
Alex Klein915cce92019-12-17 14:19:50 -0700123
Alex Klein1699fab2022-09-08 08:46:06 -0600124 # Parse the goma config.
125 chromeos_goma_dir = goma_message.chromeos_goma_dir or None
126 if goma_message.goma_approach == common_pb2.GomaConfig.RBE_STAGING:
127 goma_approach = goma_lib.GomaApproach(
128 "?staging", "staging-goma.chromium.org", True
129 )
130 elif goma_message.goma_approach == common_pb2.GomaConfig.RBE_PROD:
131 goma_approach = goma_lib.GomaApproach(
132 "?prod", "goma.chromium.org", True
133 )
134 else:
135 goma_approach = goma_lib.GomaApproach(
136 "?cros", "goma.chromium.org", True
137 )
Alex Klein915cce92019-12-17 14:19:50 -0700138
Alex Klein1699fab2022-09-08 08:46:06 -0600139 # Note that we are not specifying the goma log_dir so that goma will create
140 # and use a tmp dir for the logs.
141 stats_filename = goma_message.stats_file or None
142 counterz_filename = goma_message.counterz_file or None
Alex Klein915cce92019-12-17 14:19:50 -0700143
Alex Klein1699fab2022-09-08 08:46:06 -0600144 return goma_lib.Goma(
145 goma_message.goma_dir,
Alex Klein1699fab2022-09-08 08:46:06 -0600146 stage_name="BuildAPI",
147 chromeos_goma_dir=chromeos_goma_dir,
148 chroot_dir=chroot_path,
Brian Norrisd0dfeae2023-03-09 13:06:47 -0800149 out_dir=out_path,
Alex Klein1699fab2022-09-08 08:46:06 -0600150 goma_approach=goma_approach,
151 stats_filename=stats_filename,
152 counterz_filename=counterz_filename,
153 )
Alex Klein915cce92019-12-17 14:19:50 -0700154
155
Kevin Sheltona8056362022-04-04 16:19:23 -0700156def ParseBuildTarget(
157 build_target_message: common_pb2.BuildTarget,
Alex Klein1699fab2022-09-08 08:46:06 -0600158 profile_message: Optional[sysroot_pb2.Profile] = None,
Kevin Sheltona8056362022-04-04 16:19:23 -0700159) -> build_target_lib.BuildTarget:
Alex Klein1699fab2022-09-08 08:46:06 -0600160 """Create a BuildTarget object from a build_target message.
Alex Klein171da612019-08-06 14:00:42 -0600161
Alex Klein1699fab2022-09-08 08:46:06 -0600162 Args:
Alex Klein611dddd2022-10-11 17:02:01 -0600163 build_target_message: The BuildTarget message.
164 profile_message: The profile message.
Alex Klein171da612019-08-06 14:00:42 -0600165
Alex Klein1699fab2022-09-08 08:46:06 -0600166 Returns:
Alex Klein611dddd2022-10-11 17:02:01 -0600167 BuildTarget: The parsed instance.
Alex Klein171da612019-08-06 14:00:42 -0600168
Alex Klein1699fab2022-09-08 08:46:06 -0600169 Raises:
Alex Klein611dddd2022-10-11 17:02:01 -0600170 AssertionError: When the field is not a BuildTarget message.
Alex Klein1699fab2022-09-08 08:46:06 -0600171 """
172 assert isinstance(build_target_message, common_pb2.BuildTarget)
173 assert profile_message is None or isinstance(
174 profile_message, sysroot_pb2.Profile
175 )
Alex Klein171da612019-08-06 14:00:42 -0600176
Alex Klein1699fab2022-09-08 08:46:06 -0600177 profile_name = profile_message.name if profile_message else None
178 return build_target_lib.BuildTarget(
179 build_target_message.name, profile=profile_name
180 )
Alex Klein171da612019-08-06 14:00:42 -0600181
182
183def ParseBuildTargets(repeated_build_target_field):
Alex Klein1699fab2022-09-08 08:46:06 -0600184 """Create a BuildTarget for each entry in the repeated field.
Alex Klein171da612019-08-06 14:00:42 -0600185
Alex Klein1699fab2022-09-08 08:46:06 -0600186 Args:
Alex Klein611dddd2022-10-11 17:02:01 -0600187 repeated_build_target_field: The repeated BuildTarget field.
Alex Klein171da612019-08-06 14:00:42 -0600188
Alex Klein1699fab2022-09-08 08:46:06 -0600189 Returns:
Alex Klein611dddd2022-10-11 17:02:01 -0600190 list[BuildTarget]: The parsed BuildTargets.
Alex Klein171da612019-08-06 14:00:42 -0600191
Alex Klein1699fab2022-09-08 08:46:06 -0600192 Raises:
Alex Klein611dddd2022-10-11 17:02:01 -0600193 AssertionError: When the field contains non-BuildTarget messages.
Alex Klein1699fab2022-09-08 08:46:06 -0600194 """
195 return [ParseBuildTarget(target) for target in repeated_build_target_field]
Alex Klein4f0eb432019-05-02 13:56:04 -0600196
197
Alex Klein247d7922023-01-18 15:36:02 -0700198def deserialize_profile(profile: common_pb2.Profile) -> sysroot_lib.Profile:
199 """Deserialize a portage profile message to a Profile object."""
200 return sysroot_lib.Profile(profile.name)
201
202
203def deserialize_package_index_info(
204 message: common_pb2.PackageIndexInfo,
205) -> binpkg.PackageIndexInfo:
206 """Deserialize a PackageIndexInfo message to an object."""
207 return binpkg.PackageIndexInfo(
208 snapshot_sha=message.snapshot_sha,
209 snapshot_number=message.snapshot_number,
210 build_target=ParseBuildTarget(message.build_target),
211 profile=deserialize_profile(message.profile),
212 location=message.location,
213 )
214
215
Alex Klein1699fab2022-09-08 08:46:06 -0600216def serialize_package_info(
217 pkg_info: package_info.PackageInfo,
218 pkg_info_msg: Union[common_pb2.PackageInfo, "portage_pb2.Portage.Package"],
219):
220 """Serialize a PackageInfo object to a PackageInfo proto."""
221 if not isinstance(pkg_info, package_info.PackageInfo):
222 # Allows us to swap everything to serialize_package_info, and search the
223 # logs for usages that aren't passing though a PackageInfo yet.
224 logging.warning(
225 "serialize_package_info: Got a %s instead of a PackageInfo.",
226 type(pkg_info),
227 )
228 pkg_info = package_info.parse(pkg_info)
229 pkg_info_msg.package_name = pkg_info.package
230 if pkg_info.category:
231 pkg_info_msg.category = pkg_info.category
232 if pkg_info.vr:
233 pkg_info_msg.version = pkg_info.vr
Alex Klein1e68a8e2020-10-06 17:25:11 -0600234
235
236def deserialize_package_info(pkg_info_msg):
Alex Klein1699fab2022-09-08 08:46:06 -0600237 """Deserialize a PackageInfo message to a PackageInfo object."""
238 return package_info.parse(PackageInfoToString(pkg_info_msg))
Alex Klein1e68a8e2020-10-06 17:25:11 -0600239
240
Alex Klein1699fab2022-09-08 08:46:06 -0600241def retrieve_package_log_paths(
242 packages: Iterable[package_info.PackageInfo],
243 output_proto: Union[
244 sysroot_pb2.InstallPackagesResponse,
245 sysroot_pb2.InstallToolchainResponse,
246 test_pb2.BuildTargetUnitTestResponse,
247 ],
248 target_sysroot: sysroot_lib.Sysroot,
249) -> None:
250 """Get the path to the log file for each package that failed to build.
Lizzy Presland29e62452022-01-05 21:58:21 +0000251
Alex Klein1699fab2022-09-08 08:46:06 -0600252 Args:
Alex Klein611dddd2022-10-11 17:02:01 -0600253 packages: A list of packages which failed to build.
254 output_proto: The Response message for a given API call. This response
255 proto must contain a failed_package_data field.
256 target_sysroot: The sysroot used by the build step.
Alex Klein1699fab2022-09-08 08:46:06 -0600257 """
258 for pkg_info in packages:
259 # Grab the paths to the log files for each failed package from the
260 # sysroot.
261 # Logs currently exist within the sysroot in the form of:
262 # /build/${BOARD}/tmp/portage/logs/$CATEGORY:$PF:$TIMESTAMP.log
263 failed_pkg_data_msg = output_proto.failed_package_data.add()
264 serialize_package_info(pkg_info, failed_pkg_data_msg.name)
265 glob_path = os.path.join(
266 target_sysroot.portage_logdir,
267 f"{pkg_info.category}:{pkg_info.pvr}:*.log",
268 )
269 log_files = glob.glob(glob_path)
270 log_files.sort(reverse=True)
271 # Omit path if files don't exist for some reason.
272 if not log_files:
273 logging.warning(
274 "Log file for %s was not found. Search path: %s",
275 pkg_info.cpvr,
276 glob_path,
277 )
278 continue
279 failed_pkg_data_msg.log_path.path = log_files[0]
280 failed_pkg_data_msg.log_path.location = common_pb2.Path.INSIDE
Lizzy Presland29e62452022-01-05 21:58:21 +0000281
282
Alex Klein18a60af2020-06-11 12:08:47 -0600283def PackageInfoToCPV(package_info_msg):
Alex Klein1699fab2022-09-08 08:46:06 -0600284 """Helper to translate a PackageInfo message into a CPV."""
285 if not package_info_msg or not package_info_msg.package_name:
286 return None
Alex Kleina9d500b2019-04-22 15:37:51 -0600287
Alex Klein1699fab2022-09-08 08:46:06 -0600288 return package_info.SplitCPV(
289 PackageInfoToString(package_info_msg), strict=False
290 )
Alex Kleina9d500b2019-04-22 15:37:51 -0600291
292
Alex Klein18a60af2020-06-11 12:08:47 -0600293def PackageInfoToString(package_info_msg):
Alex Klein1699fab2022-09-08 08:46:06 -0600294 """Combine the components into the full package string."""
295 # TODO: Use the lib.parser.package_info.PackageInfo class instead.
296 if not package_info_msg.package_name:
297 raise ValueError("Invalid PackageInfo message.")
Alex Kleina9d500b2019-04-22 15:37:51 -0600298
Alex Klein1699fab2022-09-08 08:46:06 -0600299 c = ("%s/" % package_info_msg.category) if package_info_msg.category else ""
300 p = package_info_msg.package_name
301 v = ("-%s" % package_info_msg.version) if package_info_msg.version else ""
302 return "%s%s%s" % (c, p, v)
Greg Edelston1f5deb62023-03-31 14:22:08 -0600303
304
305def pb2_path_to_pathlib_path(
306 pb2_path: "common_pb2.Path",
307 chroot: Optional["common_pb2.Chroot"] = None,
308) -> Path:
309 """Convert an absolute pb2 path to a pathlib.Path outside the chroot.
310
311 Args:
312 pb2_path: An absolute path, which might be inside or outside chroot.
313 chroot: The chroot that the path might be inside of.
314
315 Returns:
316 A Path pointing to the same location as pb2_path, originating
317 outside the chroot.
318
319 Raises:
320 ValueError: If the given path is relative instead of absolute.
321 ValueError: If the given path is inside the chroot, but a chroot is not
322 provided.
323 """
324 if pb2_path.path[0] != "/":
325 raise ValueError(f"Cannot convert relative path: {pb2_path.path}")
326 if pb2_path.location is common_pb2.Path.Location.OUTSIDE:
327 return Path(pb2_path.path)
328 if chroot is None:
329 raise ValueError("Cannot convert inside path without a chroot.")
330 path_relative_to_root = pb2_path.path[1:]
331 return Path(chroot.path, path_relative_to_root)