blob: 79789229c5bda640c55acdfd9f68d756b9286be1 [file] [log] [blame]
Tiancong Wangaf050172019-07-10 11:52:03 -07001# Copyright 2019 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5"""Toolchain-related operations."""
6
LaMont Jonesb20b3d92019-11-23 11:47:48 -07007import collections
Chris McDonald1672ddb2021-07-21 11:48:23 -06008import logging
Alex Kleincd03a5e2021-10-18 13:23:47 -06009from pathlib import Path
LaMont Jonesb20b3d92019-11-23 11:47:48 -070010
LaMont Jones5d2edcb2019-12-23 11:32:03 -070011from chromite.api import controller
Alex Klein076841b2019-08-29 15:19:39 -060012from chromite.api import faux
Alex Klein231d2da2019-07-22 16:44:45 -060013from chromite.api import validate
LaMont Jones5d2edcb2019-12-23 11:32:03 -070014from chromite.api.controller import controller_util
Tiancong Wang24a3df72019-08-20 15:48:51 -070015from chromite.api.gen.chromite.api import toolchain_pb2
LaMont Jonesfd68cb12020-04-29 16:43:06 -060016from chromite.api.gen.chromite.api.artifacts_pb2 import PrepareForBuildResponse
Chris McDonald1672ddb2021-07-21 11:48:23 -060017from chromite.api.gen.chromiumos.builder_config_pb2 import BuilderConfig
Chris McDonald1672ddb2021-07-21 11:48:23 -060018from chromite.lib import toolchain_util
Ryan Beltranf2a5dcc2022-04-19 20:34:00 +000019from chromite.service import toolchain
Ryan Beltran7d191802021-11-24 00:08:17 +000020
Ryan Beltranf2a5dcc2022-04-19 20:34:00 +000021# TODO(b/229665884): Move the implementation details for most/all endpoints to:
22# chromite/services/toolchain.py
23# This migration has been done for linting endpoints but not yet for others.
Chris McDonald1672ddb2021-07-21 11:48:23 -060024
LaMont Jonesb20b3d92019-11-23 11:47:48 -070025_Handlers = collections.namedtuple('_Handlers', ['name', 'prepare', 'bundle'])
26_TOOLCHAIN_ARTIFACT_HANDLERS = {
LaMont Jonescd1503d2020-03-04 09:09:59 -070027 BuilderConfig.Artifacts.UNVERIFIED_CHROME_LLVM_ORDERFILE:
LaMont Jonesd3944582020-03-04 10:37:05 -070028 _Handlers('UnverifiedChromeLlvmOrderfile',
LaMont Jones5d2edcb2019-12-23 11:32:03 -070029 toolchain_util.PrepareForBuild,
30 toolchain_util.BundleArtifacts),
LaMont Jonescd1503d2020-03-04 09:09:59 -070031 BuilderConfig.Artifacts.VERIFIED_CHROME_LLVM_ORDERFILE:
LaMont Jones3fed7f22020-03-04 10:15:11 -070032 _Handlers('VerifiedChromeLlvmOrderfile', toolchain_util.PrepareForBuild,
LaMont Jones5d2edcb2019-12-23 11:32:03 -070033 toolchain_util.BundleArtifacts),
LaMont Jonesb20b3d92019-11-23 11:47:48 -070034 BuilderConfig.Artifacts.CHROME_CLANG_WARNINGS_FILE:
LaMont Jones3fed7f22020-03-04 10:15:11 -070035 _Handlers('ChromeClangWarningsFile', toolchain_util.PrepareForBuild,
LaMont Jones90bab632020-01-27 15:58:26 -070036 toolchain_util.BundleArtifacts),
LaMont Jonesb20b3d92019-11-23 11:47:48 -070037 BuilderConfig.Artifacts.UNVERIFIED_LLVM_PGO_FILE:
LaMont Jones3fed7f22020-03-04 10:15:11 -070038 _Handlers('UnverifiedLlvmPgoFile', toolchain_util.PrepareForBuild,
LaMont Jones90bab632020-01-27 15:58:26 -070039 toolchain_util.BundleArtifacts),
LaMont Jones45ca6c42020-02-05 09:39:09 -070040 BuilderConfig.Artifacts.UNVERIFIED_CHROME_BENCHMARK_AFDO_FILE:
41 _Handlers('UnverifiedChromeBenchmarkAfdoFile',
LaMont Jones90bab632020-01-27 15:58:26 -070042 toolchain_util.PrepareForBuild,
43 toolchain_util.BundleArtifacts),
LaMont Jones3fed7f22020-03-04 10:15:11 -070044 BuilderConfig.Artifacts.CHROME_DEBUG_BINARY:
Tiancong Wangba2a1c22021-01-19 10:45:06 -080045 _Handlers('ChromeDebugBinary', toolchain_util.PrepareForBuild,
LaMont Jones3fed7f22020-03-04 10:15:11 -070046 toolchain_util.BundleArtifacts),
LaMont Jones53bddd02020-03-12 15:02:54 -060047 BuilderConfig.Artifacts.UNVERIFIED_CHROME_BENCHMARK_PERF_FILE:
48 _Handlers('UnverifiedChromeBenchmarkPerfFile',
49 toolchain_util.PrepareForBuild,
50 toolchain_util.BundleArtifacts),
LaMont Jones45ca6c42020-02-05 09:39:09 -070051 BuilderConfig.Artifacts.VERIFIED_CHROME_BENCHMARK_AFDO_FILE:
52 _Handlers('VerifiedChromeBenchmarkAfdoFile',
LaMont Jones90bab632020-01-27 15:58:26 -070053 toolchain_util.PrepareForBuild,
54 toolchain_util.BundleArtifacts),
LaMont Jones45ca6c42020-02-05 09:39:09 -070055 BuilderConfig.Artifacts.UNVERIFIED_KERNEL_CWP_AFDO_FILE:
LaMont Jones3fed7f22020-03-04 10:15:11 -070056 _Handlers('UnverifiedKernelCwpAfdoFile', toolchain_util.PrepareForBuild,
LaMont Jones45ca6c42020-02-05 09:39:09 -070057 toolchain_util.BundleArtifacts),
58 BuilderConfig.Artifacts.VERIFIED_KERNEL_CWP_AFDO_FILE:
LaMont Jones3fed7f22020-03-04 10:15:11 -070059 _Handlers('VerifiedKernelCwpAfdoFile', toolchain_util.PrepareForBuild,
LaMont Jones45ca6c42020-02-05 09:39:09 -070060 toolchain_util.BundleArtifacts),
61 BuilderConfig.Artifacts.UNVERIFIED_CHROME_CWP_AFDO_FILE:
LaMont Jones3fed7f22020-03-04 10:15:11 -070062 _Handlers('UnverifiedChromeCwpAfdoFile', toolchain_util.PrepareForBuild,
LaMont Jones45ca6c42020-02-05 09:39:09 -070063 toolchain_util.BundleArtifacts),
64 BuilderConfig.Artifacts.VERIFIED_CHROME_CWP_AFDO_FILE:
LaMont Jones3fed7f22020-03-04 10:15:11 -070065 _Handlers('VerifiedChromeCwpAfdoFile', toolchain_util.PrepareForBuild,
LaMont Jones45ca6c42020-02-05 09:39:09 -070066 toolchain_util.BundleArtifacts),
67 BuilderConfig.Artifacts.VERIFIED_RELEASE_AFDO_FILE:
LaMont Jones3fed7f22020-03-04 10:15:11 -070068 _Handlers('VerifiedReleaseAfdoFile', toolchain_util.PrepareForBuild,
LaMont Jones90bab632020-01-27 15:58:26 -070069 toolchain_util.BundleArtifacts),
Tiancong Wang91cf1dd2020-05-05 10:30:22 -070070 BuilderConfig.Artifacts.TOOLCHAIN_WARNING_LOGS:
71 _Handlers('ToolchainWarningLogs', toolchain_util.PrepareForBuild,
72 toolchain_util.BundleArtifacts),
Tiancong Wangfe3dbd22020-06-12 15:45:55 -070073 BuilderConfig.Artifacts.CHROME_AFDO_PROFILE_FOR_ANDROID_LINUX:
74 _Handlers('ChromeAFDOProfileForAndroidLinux',
75 toolchain_util.PrepareForBuild,
76 toolchain_util.BundleArtifacts),
Jian Cai6190cc82020-06-12 16:24:32 -070077 BuilderConfig.Artifacts.CLANG_CRASH_DIAGNOSES:
78 _Handlers('ClangCrashDiagnoses', toolchain_util.PrepareForBuild,
79 toolchain_util.BundleArtifacts),
Ryan Beltran0be7dcf2020-12-09 18:31:53 +000080 BuilderConfig.Artifacts.COMPILER_RUSAGE_LOG:
81 _Handlers('CompilerRusageLogs', toolchain_util.PrepareForBuild,
82 toolchain_util.BundleArtifacts),
LaMont Jonesb20b3d92019-11-23 11:47:48 -070083}
84
Tiancong Wangd5214132021-01-12 10:43:57 -080085_TOOLCHAIN_COMMIT_HANDLERS = {
86 BuilderConfig.Artifacts.VERIFIED_KERNEL_CWP_AFDO_FILE:
87 'VerifiedKernelCwpAfdoFile'
88}
89
LaMont Jonese7821672020-04-09 08:56:26 -060090
LaMont Jonesb20b3d92019-11-23 11:47:48 -070091# TODO(crbug/1031213): When @faux is expanded to have more than success/failure,
92# this should be changed.
93@faux.all_empty
LaMont Jones5d2edcb2019-12-23 11:32:03 -070094@validate.require('artifact_types')
95# Note: chroot and sysroot are unspecified the first time that the build_target
96# recipe calls PrepareForBuild. The second time, they are specified. No
97# validation check because "all" values are valid.
LaMont Jonesb20b3d92019-11-23 11:47:48 -070098@validate.validation_complete
99def PrepareForBuild(input_proto, output_proto, _config):
100 """Prepare to build toolchain artifacts.
101
102 The handlers (from _TOOLCHAIN_ARTIFACT_HANDLERS above) are called with:
103 artifact_name (str): name of the artifact type.
LaMont Jones5d2edcb2019-12-23 11:32:03 -0700104 chroot (chroot_lib.Chroot): chroot. Will be None if the chroot has not
105 yet been created.
106 sysroot_path (str): sysroot path inside the chroot (e.g., /build/atlas).
107 Will be an empty string if the sysroot has not yet been created.
108 build_target_name (str): name of the build target (e.g., atlas). Will be
109 an empty string if the sysroot has not yet been created.
110 input_artifacts ({(str) name:[str gs_locations]}): locations for possible
111 input artifacts. The handler is expected to know which keys it should
112 be using, and ignore any keys that it does not understand.
LaMont Jonese7821672020-04-09 08:56:26 -0600113 profile_info ({(str) name: (str) value}) Dictionary containing profile
114 information.
LaMont Jonesa215f1e2019-12-06 10:18:58 -0700115
116 They locate and modify any ebuilds and/or source required for the artifact
117 being created, then return a value from toolchain_util.PrepareForBuildReturn.
LaMont Jonesb20b3d92019-11-23 11:47:48 -0700118
LaMont Jones5d2edcb2019-12-23 11:32:03 -0700119 This function sets output_proto.build_relevance to the result.
120
LaMont Jonesb20b3d92019-11-23 11:47:48 -0700121 Args:
122 input_proto (PrepareForToolchainBuildRequest): The input proto
123 output_proto (PrepareForToolchainBuildResponse): The output proto
124 _config (api_config.ApiConfig): The API call config.
125 """
LaMont Jones5d2edcb2019-12-23 11:32:03 -0700126 if input_proto.chroot.path:
127 chroot = controller_util.ParseChroot(input_proto.chroot)
128 else:
129 chroot = None
LaMont Jones4579e8c2019-12-06 14:20:37 -0700130
LaMont Jones5d2edcb2019-12-23 11:32:03 -0700131 input_artifacts = collections.defaultdict(list)
132 for art in input_proto.input_artifacts:
133 item = _TOOLCHAIN_ARTIFACT_HANDLERS.get(art.input_artifact_type)
134 if item:
135 input_artifacts[item.name].extend(
136 ['gs://%s' % str(x) for x in art.input_artifact_gs_locations])
137
LaMont Jonese7821672020-04-09 08:56:26 -0600138 profile_info = _GetProfileInfoDict(input_proto.profile_info)
LaMont Jones45ca6c42020-02-05 09:39:09 -0700139
LaMont Jones5d2edcb2019-12-23 11:32:03 -0700140 results = set()
141 sysroot_path = input_proto.sysroot.path
142 build_target = input_proto.sysroot.build_target.name
LaMont Jonesb20b3d92019-11-23 11:47:48 -0700143 for artifact_type in input_proto.artifact_types:
LaMont Jones5d2edcb2019-12-23 11:32:03 -0700144 # Unknown artifact_types are an error.
145 handler = _TOOLCHAIN_ARTIFACT_HANDLERS[artifact_type]
146 if handler.prepare:
Tiancong Wangba2a1c22021-01-19 10:45:06 -0800147 results.add(
148 handler.prepare(handler.name, chroot, sysroot_path, build_target,
149 input_artifacts, profile_info))
LaMont Jonesb20b3d92019-11-23 11:47:48 -0700150
151 # Translate the returns from the handlers we called.
152 # If any NEEDED => NEEDED
153 # elif any UNKNOWN => UNKNOWN
154 # elif any POINTLESS => POINTLESS
155 # else UNKNOWN.
LaMont Jonesb20b3d92019-11-23 11:47:48 -0700156 if toolchain_util.PrepareForBuildReturn.NEEDED in results:
LaMont Jonesfd68cb12020-04-29 16:43:06 -0600157 output_proto.build_relevance = PrepareForBuildResponse.NEEDED
LaMont Jonesb20b3d92019-11-23 11:47:48 -0700158 elif toolchain_util.PrepareForBuildReturn.UNKNOWN in results:
LaMont Jonesfd68cb12020-04-29 16:43:06 -0600159 output_proto.build_relevance = PrepareForBuildResponse.UNKNOWN
LaMont Jonesb20b3d92019-11-23 11:47:48 -0700160 elif toolchain_util.PrepareForBuildReturn.POINTLESS in results:
LaMont Jonesfd68cb12020-04-29 16:43:06 -0600161 output_proto.build_relevance = PrepareForBuildResponse.POINTLESS
LaMont Jonesb20b3d92019-11-23 11:47:48 -0700162 else:
LaMont Jonesfd68cb12020-04-29 16:43:06 -0600163 output_proto.build_relevance = PrepareForBuildResponse.UNKNOWN
LaMont Jones5d2edcb2019-12-23 11:32:03 -0700164 return controller.RETURN_CODE_SUCCESS
LaMont Jonesb20b3d92019-11-23 11:47:48 -0700165
166
167# TODO(crbug/1031213): When @faux is expanded to have more than success/failure,
168# this should be changed.
169@faux.all_empty
LaMont Jonese911df02020-04-16 12:40:17 -0600170@validate.require('chroot.path', 'output_dir', 'artifact_types')
LaMont Jones4579e8c2019-12-06 14:20:37 -0700171@validate.exists('output_dir')
LaMont Jones5d2edcb2019-12-23 11:32:03 -0700172@validate.validation_complete
LaMont Jonesb20b3d92019-11-23 11:47:48 -0700173def BundleArtifacts(input_proto, output_proto, _config):
Alex Kleincd03a5e2021-10-18 13:23:47 -0600174 """Bundle valid toolchain artifacts.
LaMont Jonesb20b3d92019-11-23 11:47:48 -0700175
176 The handlers (from _TOOLCHAIN_ARTIFACT_HANDLERS above) are called with:
LaMont Jonesa215f1e2019-12-06 10:18:58 -0700177 artifact_name (str): name of the artifact type
LaMont Jones5d2edcb2019-12-23 11:32:03 -0700178 chroot (chroot_lib.Chroot): chroot
LaMont Jonese911df02020-04-16 12:40:17 -0600179 sysroot_path (str): sysroot path inside the chroot (e.g., /build/atlas),
180 or None.
LaMont Jones5d2edcb2019-12-23 11:32:03 -0700181 chrome_root (str): path to chrome root. (e.g., /b/s/w/ir/k/chrome)
LaMont Jonese911df02020-04-16 12:40:17 -0600182 build_target_name (str): name of the build target (e.g., atlas), or None.
LaMont Jonesb20b3d92019-11-23 11:47:48 -0700183 output_dir (str): absolute path where artifacts are being bundled.
184 (e.g., /b/s/w/ir/k/recipe_cleanup/artifactssptfMU)
LaMont Jonese7821672020-04-09 08:56:26 -0600185 profile_info ({(str) name: (str) value}) Dictionary containing profile
186 information.
LaMont Jonesb20b3d92019-11-23 11:47:48 -0700187
188 Note: the actual upload to GS is done by CI, not here.
189
190 Args:
191 input_proto (BundleToolchainRequest): The input proto
192 output_proto (BundleToolchainResponse): The output proto
193 _config (api_config.ApiConfig): The API call config.
194 """
LaMont Jones5d2edcb2019-12-23 11:32:03 -0700195 chroot = controller_util.ParseChroot(input_proto.chroot)
LaMont Jonesb20b3d92019-11-23 11:47:48 -0700196
LaMont Jonese7821672020-04-09 08:56:26 -0600197 profile_info = _GetProfileInfoDict(input_proto.profile_info)
LaMont Jones45ca6c42020-02-05 09:39:09 -0700198
Alex Kleincd03a5e2021-10-18 13:23:47 -0600199 output_path = Path(input_proto.output_dir)
200
LaMont Jonesb20b3d92019-11-23 11:47:48 -0700201 for artifact_type in input_proto.artifact_types:
LaMont Jones5d2edcb2019-12-23 11:32:03 -0700202 if artifact_type not in _TOOLCHAIN_ARTIFACT_HANDLERS:
203 logging.error('%s not understood', artifact_type)
204 return controller.RETURN_CODE_UNRECOVERABLE
Alex Kleincd03a5e2021-10-18 13:23:47 -0600205
LaMont Jones5d2edcb2019-12-23 11:32:03 -0700206 handler = _TOOLCHAIN_ARTIFACT_HANDLERS[artifact_type]
Alex Kleincd03a5e2021-10-18 13:23:47 -0600207 if not handler or not handler.bundle:
208 logging.warning('%s does not have a handler with a bundle function.',
209 artifact_type)
210 continue
211
212 artifacts = handler.bundle(handler.name, chroot, input_proto.sysroot.path,
213 input_proto.sysroot.build_target.name,
214 input_proto.output_dir, profile_info)
215 if not artifacts:
216 continue
217
218 # Filter out artifacts that do not exist or are empty.
219 usable_artifacts = []
220 for artifact in artifacts:
221 artifact_path = output_path / artifact
222 if not artifact_path.exists():
223 logging.warning('%s is not in the output directory.', artifact)
224 elif not artifact_path.stat().st_size:
225 logging.warning('%s is empty.', artifact)
226 else:
227 usable_artifacts.append(artifact)
228
229 if not usable_artifacts:
230 logging.warning('No usable artifacts for artifact type %s', artifact_type)
231 continue
232
233 # Add all usable artifacts.
234 art_info = output_proto.artifacts_info.add()
235 art_info.artifact_type = artifact_type
236 for artifact in usable_artifacts:
237 art_info.artifacts.add().path = artifact
LaMont Jonesb20b3d92019-11-23 11:47:48 -0700238
239
Tiancong Wangd5214132021-01-12 10:43:57 -0800240def _GetUpdatedFilesResponse(_input_proto, output_proto, _config):
241 """Add successful status to the faux response."""
242 file_info = output_proto.updated_files.add()
243 file_info.path = '/any/modified/file'
244 output_proto.commit_message = 'Commit message'
245
246
247@faux.empty_error
248@faux.success(_GetUpdatedFilesResponse)
249@validate.require('uploaded_artifacts')
250@validate.validation_complete
251def GetUpdatedFiles(input_proto, output_proto, _config):
252 """Use uploaded artifacts to update some updates in a chromeos checkout.
253
254 The function will call toolchain_util.GetUpdatedFiles using the type of
255 uploaded artifacts to make some changes in a checkout, and return the list
256 of change files together with commit message.
257 updated_artifacts: A list of UpdatedArtifacts type which contains a tuple
258 of artifact info and profile info.
259 Note: the actual creation of the commit is done by CI, not here.
260
261 Args:
262 input_proto (GetUpdatedFilesRequest): The input proto
263 output_proto (GetUpdatedFilesResponse): The output proto
264 _config (api_config.ApiConfig): The API call config.
265 """
266 commit_message = ''
267 for artifact in input_proto.uploaded_artifacts:
268 artifact_type = artifact.artifact_info.artifact_type
269 if artifact_type not in _TOOLCHAIN_COMMIT_HANDLERS:
270 logging.error('%s not understood', artifact_type)
271 return controller.RETURN_CODE_UNRECOVERABLE
272 artifact_name = _TOOLCHAIN_COMMIT_HANDLERS[artifact_type]
273 if artifact_name:
274 assert len(artifact.artifact_info.artifacts) == 1, (
275 'Only one file to update per each artifact')
276 updated_files, message = toolchain_util.GetUpdatedFiles(
277 artifact_name, artifact.artifact_info.artifacts[0].path,
278 _GetProfileInfoDict(artifact.profile_info))
279 for f in updated_files:
280 file_info = output_proto.updated_files.add()
281 file_info.path = f
282
283 commit_message += message + '\n'
284 output_proto.commit_message = commit_message
285 # No commit footer is added for now. Can add more here if needed
286
287
Ryan Beltranf2a5dcc2022-04-19 20:34:00 +0000288def _GetProfileInfoDict(profile_info):
289 """Convert profile_info to a dict.
290
291 Args:
292 profile_info (ArtifactProfileInfo): The artifact profile_info.
293
294 Returns:
295 A dictionary containing profile info.
296 """
297 ret = {}
298 which = profile_info.WhichOneof('artifact_profile_info')
299 if which:
300 value = getattr(profile_info, which)
301 # If it is a message, then use the contents of the message. This works as
302 # long as simple types do not have a 'DESCRIPTOR' attribute. (And protobuf
303 # messages do.)
304 if getattr(value, 'DESCRIPTOR', None):
305 ret.update({k.name: v for k, v in value.ListFields()})
306 else:
307 ret[which] = value
308 return ret
309
310
311LINTER_CODES = {
312 'clang_tidy': toolchain_pb2.LinterFinding.CLANG_TIDY,
313 'cargo_clippy': toolchain_pb2.LinterFinding.CARGO_CLIPPY
314}
315
316
Ryan Beltran0df7fb02021-11-10 20:58:51 +0000317@faux.all_empty
318@validate.exists('sysroot.path')
319@validate.require('packages')
320@validate.validation_complete
321def EmergeWithLinting(input_proto, output_proto, _config):
322 """Emerge packages with linter features enabled and retrieves all findings.
323
324 Args:
325 input_proto (LinterRequest): The nput proto with package and sysroot info.
326 output_proto (LinterResponse): The output proto where findings are stored.
327 _config (api_config.ApiConfig): The API call config (unused).
328 """
329 packages = [
Ryan Beltranf2a5dcc2022-04-19 20:34:00 +0000330 controller_util.deserialize_package_info(package)
331 for package in input_proto.packages
332 ]
Ryan Beltran7d191802021-11-24 00:08:17 +0000333
Ryan Beltranf2a5dcc2022-04-19 20:34:00 +0000334 build_linter = toolchain.BuildLinter(
335 packages,
336 input_proto.sysroot.path,
337 differential=input_proto.filter_modified)
Ryan Beltran7d191802021-11-24 00:08:17 +0000338
Ryan Beltranf2a5dcc2022-04-19 20:34:00 +0000339 findings = build_linter.emerge_with_linting(use_clippy=True, use_tidy=True)
Ryan Beltranf9a86f42022-04-13 20:58:18 +0000340
Ryan Beltran7d191802021-11-24 00:08:17 +0000341 for finding in findings:
Ryan Beltranf2a5dcc2022-04-19 20:34:00 +0000342 locations = []
Ryan Beltran40e4ad12021-05-17 19:55:03 +0000343 for location in finding.locations:
Ryan Beltranf2a5dcc2022-04-19 20:34:00 +0000344 locations.append(
Ryan Beltran40e4ad12021-05-17 19:55:03 +0000345 toolchain_pb2.LinterFindingLocation(
Ryan Beltranf2a5dcc2022-04-19 20:34:00 +0000346 filepath=location.filepath,
Ryan Beltran40e4ad12021-05-17 19:55:03 +0000347 line_start=location.line_start,
Ryan Beltranf2a5dcc2022-04-19 20:34:00 +0000348 line_end=location.line_end))
349 output_proto.findings.append(
Ryan Beltran40e4ad12021-05-17 19:55:03 +0000350 toolchain_pb2.LinterFinding(
351 message=finding.message,
Ryan Beltranf2a5dcc2022-04-19 20:34:00 +0000352 locations=locations,
353 linter=LINTER_CODES[finding.linter]))