blob: c4c9ff21ceae0f44173f92a0b33b37464444c85e [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:
40 chroot_message: The chroot message.
Alex Klein171da612019-08-06 14:00:42 -060041
Alex Klein1699fab2022-09-08 08:46:06 -060042 Returns:
43 Chroot: The parsed chroot object.
Alex Klein171da612019-08-06 14:00:42 -060044
Alex Klein1699fab2022-09-08 08:46:06 -060045 Raises:
46 AssertionError: When the message is not a Chroot message.
47 """
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:
79 sysroot_message: The sysroot message.
George Engelbrechtc9a8e812021-06-16 18:14:17 -060080
Alex Klein1699fab2022-09-08 08:46:06 -060081 Returns:
82 Sysroot: The parsed sysroot object.
George Engelbrechtc9a8e812021-06-16 18:14:17 -060083
Alex Klein1699fab2022-09-08 08:46:06 -060084 Raises:
85 AssertionError: When the message is not a Sysroot message.
86 """
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,
135 goma_message.goma_client_json,
136 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:
152 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:
156 BuildTarget: The parsed instance.
Alex Klein171da612019-08-06 14:00:42 -0600157
Alex Klein1699fab2022-09-08 08:46:06 -0600158 Raises:
159 AssertionError: When the field is not a BuildTarget message.
160 """
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:
176 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:
179 list[BuildTarget]: The parsed BuildTargets.
Alex Klein171da612019-08-06 14:00:42 -0600180
Alex Klein1699fab2022-09-08 08:46:06 -0600181 Raises:
182 AssertionError: When the field contains non-BuildTarget messages.
183 """
184 return [ParseBuildTarget(target) for target in repeated_build_target_field]
Alex Klein4f0eb432019-05-02 13:56:04 -0600185
186
Alex Klein1699fab2022-09-08 08:46:06 -0600187def serialize_package_info(
188 pkg_info: package_info.PackageInfo,
189 pkg_info_msg: Union[common_pb2.PackageInfo, "portage_pb2.Portage.Package"],
190):
191 """Serialize a PackageInfo object to a PackageInfo proto."""
192 if not isinstance(pkg_info, package_info.PackageInfo):
193 # Allows us to swap everything to serialize_package_info, and search the
194 # logs for usages that aren't passing though a PackageInfo yet.
195 logging.warning(
196 "serialize_package_info: Got a %s instead of a PackageInfo.",
197 type(pkg_info),
198 )
199 pkg_info = package_info.parse(pkg_info)
200 pkg_info_msg.package_name = pkg_info.package
201 if pkg_info.category:
202 pkg_info_msg.category = pkg_info.category
203 if pkg_info.vr:
204 pkg_info_msg.version = pkg_info.vr
Alex Klein1e68a8e2020-10-06 17:25:11 -0600205
206
207def deserialize_package_info(pkg_info_msg):
Alex Klein1699fab2022-09-08 08:46:06 -0600208 """Deserialize a PackageInfo message to a PackageInfo object."""
209 return package_info.parse(PackageInfoToString(pkg_info_msg))
Alex Klein1e68a8e2020-10-06 17:25:11 -0600210
211
Alex Klein1699fab2022-09-08 08:46:06 -0600212def retrieve_package_log_paths(
213 packages: Iterable[package_info.PackageInfo],
214 output_proto: Union[
215 sysroot_pb2.InstallPackagesResponse,
216 sysroot_pb2.InstallToolchainResponse,
217 test_pb2.BuildTargetUnitTestResponse,
218 ],
219 target_sysroot: sysroot_lib.Sysroot,
220) -> None:
221 """Get the path to the log file for each package that failed to build.
Lizzy Presland29e62452022-01-05 21:58:21 +0000222
Alex Klein1699fab2022-09-08 08:46:06 -0600223 Args:
224 packages: A list of packages which failed to build.
225 output_proto: The Response message for a given API call. This response proto
226 must contain a failed_package_data field.
227 target_sysroot: The sysroot used by the build step.
228 """
229 for pkg_info in packages:
230 # Grab the paths to the log files for each failed package from the
231 # sysroot.
232 # Logs currently exist within the sysroot in the form of:
233 # /build/${BOARD}/tmp/portage/logs/$CATEGORY:$PF:$TIMESTAMP.log
234 failed_pkg_data_msg = output_proto.failed_package_data.add()
235 serialize_package_info(pkg_info, failed_pkg_data_msg.name)
236 glob_path = os.path.join(
237 target_sysroot.portage_logdir,
238 f"{pkg_info.category}:{pkg_info.pvr}:*.log",
239 )
240 log_files = glob.glob(glob_path)
241 log_files.sort(reverse=True)
242 # Omit path if files don't exist for some reason.
243 if not log_files:
244 logging.warning(
245 "Log file for %s was not found. Search path: %s",
246 pkg_info.cpvr,
247 glob_path,
248 )
249 continue
250 failed_pkg_data_msg.log_path.path = log_files[0]
251 failed_pkg_data_msg.log_path.location = common_pb2.Path.INSIDE
Lizzy Presland29e62452022-01-05 21:58:21 +0000252
253
Alex Klein18a60af2020-06-11 12:08:47 -0600254def PackageInfoToCPV(package_info_msg):
Alex Klein1699fab2022-09-08 08:46:06 -0600255 """Helper to translate a PackageInfo message into a CPV."""
256 if not package_info_msg or not package_info_msg.package_name:
257 return None
Alex Kleina9d500b2019-04-22 15:37:51 -0600258
Alex Klein1699fab2022-09-08 08:46:06 -0600259 return package_info.SplitCPV(
260 PackageInfoToString(package_info_msg), strict=False
261 )
Alex Kleina9d500b2019-04-22 15:37:51 -0600262
263
Alex Klein18a60af2020-06-11 12:08:47 -0600264def PackageInfoToString(package_info_msg):
Alex Klein1699fab2022-09-08 08:46:06 -0600265 """Combine the components into the full package string."""
266 # TODO: Use the lib.parser.package_info.PackageInfo class instead.
267 if not package_info_msg.package_name:
268 raise ValueError("Invalid PackageInfo message.")
Alex Kleina9d500b2019-04-22 15:37:51 -0600269
Alex Klein1699fab2022-09-08 08:46:06 -0600270 c = ("%s/" % package_info_msg.category) if package_info_msg.category else ""
271 p = package_info_msg.package_name
272 v = ("-%s" % package_info_msg.version) if package_info_msg.version else ""
273 return "%s%s%s" % (c, p, v)