blob: 5480e1678b53dbb4c33213fbff000945b896730a [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
Mike Frysingerea11fdd2022-05-06 22:59:33 -040021
Ryan Beltranf2a5dcc2022-04-19 20:34:00 +000022# TODO(b/229665884): Move the implementation details for most/all endpoints to:
23# chromite/services/toolchain.py
24# This migration has been done for linting endpoints but not yet for others.
Chris McDonald1672ddb2021-07-21 11:48:23 -060025
LaMont Jonesb20b3d92019-11-23 11:47:48 -070026_Handlers = collections.namedtuple('_Handlers', ['name', 'prepare', 'bundle'])
27_TOOLCHAIN_ARTIFACT_HANDLERS = {
LaMont Jonescd1503d2020-03-04 09:09:59 -070028 BuilderConfig.Artifacts.UNVERIFIED_CHROME_LLVM_ORDERFILE:
LaMont Jonesd3944582020-03-04 10:37:05 -070029 _Handlers('UnverifiedChromeLlvmOrderfile',
LaMont Jones5d2edcb2019-12-23 11:32:03 -070030 toolchain_util.PrepareForBuild,
31 toolchain_util.BundleArtifacts),
LaMont Jonescd1503d2020-03-04 09:09:59 -070032 BuilderConfig.Artifacts.VERIFIED_CHROME_LLVM_ORDERFILE:
LaMont Jones3fed7f22020-03-04 10:15:11 -070033 _Handlers('VerifiedChromeLlvmOrderfile', toolchain_util.PrepareForBuild,
LaMont Jones5d2edcb2019-12-23 11:32:03 -070034 toolchain_util.BundleArtifacts),
LaMont Jonesb20b3d92019-11-23 11:47:48 -070035 BuilderConfig.Artifacts.CHROME_CLANG_WARNINGS_FILE:
LaMont Jones3fed7f22020-03-04 10:15:11 -070036 _Handlers('ChromeClangWarningsFile', toolchain_util.PrepareForBuild,
LaMont Jones90bab632020-01-27 15:58:26 -070037 toolchain_util.BundleArtifacts),
LaMont Jonesb20b3d92019-11-23 11:47:48 -070038 BuilderConfig.Artifacts.UNVERIFIED_LLVM_PGO_FILE:
LaMont Jones3fed7f22020-03-04 10:15:11 -070039 _Handlers('UnverifiedLlvmPgoFile', toolchain_util.PrepareForBuild,
LaMont Jones90bab632020-01-27 15:58:26 -070040 toolchain_util.BundleArtifacts),
LaMont Jones45ca6c42020-02-05 09:39:09 -070041 BuilderConfig.Artifacts.UNVERIFIED_CHROME_BENCHMARK_AFDO_FILE:
42 _Handlers('UnverifiedChromeBenchmarkAfdoFile',
LaMont Jones90bab632020-01-27 15:58:26 -070043 toolchain_util.PrepareForBuild,
44 toolchain_util.BundleArtifacts),
LaMont Jones3fed7f22020-03-04 10:15:11 -070045 BuilderConfig.Artifacts.CHROME_DEBUG_BINARY:
Tiancong Wangba2a1c22021-01-19 10:45:06 -080046 _Handlers('ChromeDebugBinary', toolchain_util.PrepareForBuild,
LaMont Jones3fed7f22020-03-04 10:15:11 -070047 toolchain_util.BundleArtifacts),
LaMont Jones53bddd02020-03-12 15:02:54 -060048 BuilderConfig.Artifacts.UNVERIFIED_CHROME_BENCHMARK_PERF_FILE:
49 _Handlers('UnverifiedChromeBenchmarkPerfFile',
50 toolchain_util.PrepareForBuild,
51 toolchain_util.BundleArtifacts),
LaMont Jones45ca6c42020-02-05 09:39:09 -070052 BuilderConfig.Artifacts.VERIFIED_CHROME_BENCHMARK_AFDO_FILE:
53 _Handlers('VerifiedChromeBenchmarkAfdoFile',
LaMont Jones90bab632020-01-27 15:58:26 -070054 toolchain_util.PrepareForBuild,
55 toolchain_util.BundleArtifacts),
LaMont Jones45ca6c42020-02-05 09:39:09 -070056 BuilderConfig.Artifacts.UNVERIFIED_KERNEL_CWP_AFDO_FILE:
LaMont Jones3fed7f22020-03-04 10:15:11 -070057 _Handlers('UnverifiedKernelCwpAfdoFile', toolchain_util.PrepareForBuild,
LaMont Jones45ca6c42020-02-05 09:39:09 -070058 toolchain_util.BundleArtifacts),
59 BuilderConfig.Artifacts.VERIFIED_KERNEL_CWP_AFDO_FILE:
LaMont Jones3fed7f22020-03-04 10:15:11 -070060 _Handlers('VerifiedKernelCwpAfdoFile', toolchain_util.PrepareForBuild,
LaMont Jones45ca6c42020-02-05 09:39:09 -070061 toolchain_util.BundleArtifacts),
62 BuilderConfig.Artifacts.UNVERIFIED_CHROME_CWP_AFDO_FILE:
LaMont Jones3fed7f22020-03-04 10:15:11 -070063 _Handlers('UnverifiedChromeCwpAfdoFile', toolchain_util.PrepareForBuild,
LaMont Jones45ca6c42020-02-05 09:39:09 -070064 toolchain_util.BundleArtifacts),
65 BuilderConfig.Artifacts.VERIFIED_CHROME_CWP_AFDO_FILE:
LaMont Jones3fed7f22020-03-04 10:15:11 -070066 _Handlers('VerifiedChromeCwpAfdoFile', toolchain_util.PrepareForBuild,
LaMont Jones45ca6c42020-02-05 09:39:09 -070067 toolchain_util.BundleArtifacts),
68 BuilderConfig.Artifacts.VERIFIED_RELEASE_AFDO_FILE:
LaMont Jones3fed7f22020-03-04 10:15:11 -070069 _Handlers('VerifiedReleaseAfdoFile', toolchain_util.PrepareForBuild,
LaMont Jones90bab632020-01-27 15:58:26 -070070 toolchain_util.BundleArtifacts),
Tiancong Wang91cf1dd2020-05-05 10:30:22 -070071 BuilderConfig.Artifacts.TOOLCHAIN_WARNING_LOGS:
72 _Handlers('ToolchainWarningLogs', toolchain_util.PrepareForBuild,
73 toolchain_util.BundleArtifacts),
Tiancong Wangfe3dbd22020-06-12 15:45:55 -070074 BuilderConfig.Artifacts.CHROME_AFDO_PROFILE_FOR_ANDROID_LINUX:
75 _Handlers('ChromeAFDOProfileForAndroidLinux',
76 toolchain_util.PrepareForBuild,
77 toolchain_util.BundleArtifacts),
Jian Cai6190cc82020-06-12 16:24:32 -070078 BuilderConfig.Artifacts.CLANG_CRASH_DIAGNOSES:
79 _Handlers('ClangCrashDiagnoses', toolchain_util.PrepareForBuild,
80 toolchain_util.BundleArtifacts),
Ryan Beltran0be7dcf2020-12-09 18:31:53 +000081 BuilderConfig.Artifacts.COMPILER_RUSAGE_LOG:
82 _Handlers('CompilerRusageLogs', toolchain_util.PrepareForBuild,
83 toolchain_util.BundleArtifacts),
LaMont Jonesb20b3d92019-11-23 11:47:48 -070084}
85
Tiancong Wangd5214132021-01-12 10:43:57 -080086_TOOLCHAIN_COMMIT_HANDLERS = {
87 BuilderConfig.Artifacts.VERIFIED_KERNEL_CWP_AFDO_FILE:
88 'VerifiedKernelCwpAfdoFile'
89}
90
LaMont Jonese7821672020-04-09 08:56:26 -060091
LaMont Jonesb20b3d92019-11-23 11:47:48 -070092# TODO(crbug/1031213): When @faux is expanded to have more than success/failure,
93# this should be changed.
94@faux.all_empty
LaMont Jones5d2edcb2019-12-23 11:32:03 -070095@validate.require('artifact_types')
96# Note: chroot and sysroot are unspecified the first time that the build_target
97# recipe calls PrepareForBuild. The second time, they are specified. No
98# validation check because "all" values are valid.
LaMont Jonesb20b3d92019-11-23 11:47:48 -070099@validate.validation_complete
100def PrepareForBuild(input_proto, output_proto, _config):
101 """Prepare to build toolchain artifacts.
102
103 The handlers (from _TOOLCHAIN_ARTIFACT_HANDLERS above) are called with:
104 artifact_name (str): name of the artifact type.
LaMont Jones5d2edcb2019-12-23 11:32:03 -0700105 chroot (chroot_lib.Chroot): chroot. Will be None if the chroot has not
106 yet been created.
107 sysroot_path (str): sysroot path inside the chroot (e.g., /build/atlas).
108 Will be an empty string if the sysroot has not yet been created.
109 build_target_name (str): name of the build target (e.g., atlas). Will be
110 an empty string if the sysroot has not yet been created.
111 input_artifacts ({(str) name:[str gs_locations]}): locations for possible
112 input artifacts. The handler is expected to know which keys it should
113 be using, and ignore any keys that it does not understand.
LaMont Jonese7821672020-04-09 08:56:26 -0600114 profile_info ({(str) name: (str) value}) Dictionary containing profile
115 information.
LaMont Jonesa215f1e2019-12-06 10:18:58 -0700116
117 They locate and modify any ebuilds and/or source required for the artifact
118 being created, then return a value from toolchain_util.PrepareForBuildReturn.
LaMont Jonesb20b3d92019-11-23 11:47:48 -0700119
LaMont Jones5d2edcb2019-12-23 11:32:03 -0700120 This function sets output_proto.build_relevance to the result.
121
LaMont Jonesb20b3d92019-11-23 11:47:48 -0700122 Args:
123 input_proto (PrepareForToolchainBuildRequest): The input proto
124 output_proto (PrepareForToolchainBuildResponse): The output proto
125 _config (api_config.ApiConfig): The API call config.
126 """
LaMont Jones5d2edcb2019-12-23 11:32:03 -0700127 if input_proto.chroot.path:
128 chroot = controller_util.ParseChroot(input_proto.chroot)
129 else:
130 chroot = None
LaMont Jones4579e8c2019-12-06 14:20:37 -0700131
LaMont Jones5d2edcb2019-12-23 11:32:03 -0700132 input_artifacts = collections.defaultdict(list)
133 for art in input_proto.input_artifacts:
134 item = _TOOLCHAIN_ARTIFACT_HANDLERS.get(art.input_artifact_type)
135 if item:
136 input_artifacts[item.name].extend(
137 ['gs://%s' % str(x) for x in art.input_artifact_gs_locations])
138
LaMont Jonese7821672020-04-09 08:56:26 -0600139 profile_info = _GetProfileInfoDict(input_proto.profile_info)
LaMont Jones45ca6c42020-02-05 09:39:09 -0700140
LaMont Jones5d2edcb2019-12-23 11:32:03 -0700141 results = set()
142 sysroot_path = input_proto.sysroot.path
143 build_target = input_proto.sysroot.build_target.name
LaMont Jonesb20b3d92019-11-23 11:47:48 -0700144 for artifact_type in input_proto.artifact_types:
LaMont Jones5d2edcb2019-12-23 11:32:03 -0700145 # Unknown artifact_types are an error.
146 handler = _TOOLCHAIN_ARTIFACT_HANDLERS[artifact_type]
147 if handler.prepare:
Tiancong Wangba2a1c22021-01-19 10:45:06 -0800148 results.add(
149 handler.prepare(handler.name, chroot, sysroot_path, build_target,
150 input_artifacts, profile_info))
LaMont Jonesb20b3d92019-11-23 11:47:48 -0700151
152 # Translate the returns from the handlers we called.
153 # If any NEEDED => NEEDED
154 # elif any UNKNOWN => UNKNOWN
155 # elif any POINTLESS => POINTLESS
156 # else UNKNOWN.
LaMont Jonesb20b3d92019-11-23 11:47:48 -0700157 if toolchain_util.PrepareForBuildReturn.NEEDED in results:
LaMont Jonesfd68cb12020-04-29 16:43:06 -0600158 output_proto.build_relevance = PrepareForBuildResponse.NEEDED
LaMont Jonesb20b3d92019-11-23 11:47:48 -0700159 elif toolchain_util.PrepareForBuildReturn.UNKNOWN in results:
LaMont Jonesfd68cb12020-04-29 16:43:06 -0600160 output_proto.build_relevance = PrepareForBuildResponse.UNKNOWN
LaMont Jonesb20b3d92019-11-23 11:47:48 -0700161 elif toolchain_util.PrepareForBuildReturn.POINTLESS in results:
LaMont Jonesfd68cb12020-04-29 16:43:06 -0600162 output_proto.build_relevance = PrepareForBuildResponse.POINTLESS
LaMont Jonesb20b3d92019-11-23 11:47:48 -0700163 else:
LaMont Jonesfd68cb12020-04-29 16:43:06 -0600164 output_proto.build_relevance = PrepareForBuildResponse.UNKNOWN
LaMont Jones5d2edcb2019-12-23 11:32:03 -0700165 return controller.RETURN_CODE_SUCCESS
LaMont Jonesb20b3d92019-11-23 11:47:48 -0700166
167
168# TODO(crbug/1031213): When @faux is expanded to have more than success/failure,
169# this should be changed.
170@faux.all_empty
LaMont Jonese911df02020-04-16 12:40:17 -0600171@validate.require('chroot.path', 'output_dir', 'artifact_types')
LaMont Jones4579e8c2019-12-06 14:20:37 -0700172@validate.exists('output_dir')
LaMont Jones5d2edcb2019-12-23 11:32:03 -0700173@validate.validation_complete
LaMont Jonesb20b3d92019-11-23 11:47:48 -0700174def BundleArtifacts(input_proto, output_proto, _config):
Alex Kleincd03a5e2021-10-18 13:23:47 -0600175 """Bundle valid toolchain artifacts.
LaMont Jonesb20b3d92019-11-23 11:47:48 -0700176
177 The handlers (from _TOOLCHAIN_ARTIFACT_HANDLERS above) are called with:
LaMont Jonesa215f1e2019-12-06 10:18:58 -0700178 artifact_name (str): name of the artifact type
LaMont Jones5d2edcb2019-12-23 11:32:03 -0700179 chroot (chroot_lib.Chroot): chroot
LaMont Jonese911df02020-04-16 12:40:17 -0600180 sysroot_path (str): sysroot path inside the chroot (e.g., /build/atlas),
181 or None.
LaMont Jones5d2edcb2019-12-23 11:32:03 -0700182 chrome_root (str): path to chrome root. (e.g., /b/s/w/ir/k/chrome)
LaMont Jonese911df02020-04-16 12:40:17 -0600183 build_target_name (str): name of the build target (e.g., atlas), or None.
LaMont Jonesb20b3d92019-11-23 11:47:48 -0700184 output_dir (str): absolute path where artifacts are being bundled.
185 (e.g., /b/s/w/ir/k/recipe_cleanup/artifactssptfMU)
LaMont Jonese7821672020-04-09 08:56:26 -0600186 profile_info ({(str) name: (str) value}) Dictionary containing profile
187 information.
LaMont Jonesb20b3d92019-11-23 11:47:48 -0700188
189 Note: the actual upload to GS is done by CI, not here.
190
191 Args:
192 input_proto (BundleToolchainRequest): The input proto
193 output_proto (BundleToolchainResponse): The output proto
194 _config (api_config.ApiConfig): The API call config.
195 """
LaMont Jones5d2edcb2019-12-23 11:32:03 -0700196 chroot = controller_util.ParseChroot(input_proto.chroot)
LaMont Jonesb20b3d92019-11-23 11:47:48 -0700197
LaMont Jonese7821672020-04-09 08:56:26 -0600198 profile_info = _GetProfileInfoDict(input_proto.profile_info)
LaMont Jones45ca6c42020-02-05 09:39:09 -0700199
Alex Kleincd03a5e2021-10-18 13:23:47 -0600200 output_path = Path(input_proto.output_dir)
201
LaMont Jonesb20b3d92019-11-23 11:47:48 -0700202 for artifact_type in input_proto.artifact_types:
LaMont Jones5d2edcb2019-12-23 11:32:03 -0700203 if artifact_type not in _TOOLCHAIN_ARTIFACT_HANDLERS:
204 logging.error('%s not understood', artifact_type)
205 return controller.RETURN_CODE_UNRECOVERABLE
Alex Kleincd03a5e2021-10-18 13:23:47 -0600206
LaMont Jones5d2edcb2019-12-23 11:32:03 -0700207 handler = _TOOLCHAIN_ARTIFACT_HANDLERS[artifact_type]
Alex Kleincd03a5e2021-10-18 13:23:47 -0600208 if not handler or not handler.bundle:
209 logging.warning('%s does not have a handler with a bundle function.',
210 artifact_type)
211 continue
212
213 artifacts = handler.bundle(handler.name, chroot, input_proto.sysroot.path,
214 input_proto.sysroot.build_target.name,
215 input_proto.output_dir, profile_info)
216 if not artifacts:
217 continue
218
219 # Filter out artifacts that do not exist or are empty.
220 usable_artifacts = []
221 for artifact in artifacts:
222 artifact_path = output_path / artifact
223 if not artifact_path.exists():
224 logging.warning('%s is not in the output directory.', artifact)
225 elif not artifact_path.stat().st_size:
226 logging.warning('%s is empty.', artifact)
227 else:
228 usable_artifacts.append(artifact)
229
230 if not usable_artifacts:
231 logging.warning('No usable artifacts for artifact type %s', artifact_type)
232 continue
233
234 # Add all usable artifacts.
235 art_info = output_proto.artifacts_info.add()
236 art_info.artifact_type = artifact_type
237 for artifact in usable_artifacts:
238 art_info.artifacts.add().path = artifact
LaMont Jonesb20b3d92019-11-23 11:47:48 -0700239
240
Tiancong Wangd5214132021-01-12 10:43:57 -0800241def _GetUpdatedFilesResponse(_input_proto, output_proto, _config):
242 """Add successful status to the faux response."""
243 file_info = output_proto.updated_files.add()
244 file_info.path = '/any/modified/file'
245 output_proto.commit_message = 'Commit message'
246
247
248@faux.empty_error
249@faux.success(_GetUpdatedFilesResponse)
250@validate.require('uploaded_artifacts')
251@validate.validation_complete
252def GetUpdatedFiles(input_proto, output_proto, _config):
253 """Use uploaded artifacts to update some updates in a chromeos checkout.
254
255 The function will call toolchain_util.GetUpdatedFiles using the type of
256 uploaded artifacts to make some changes in a checkout, and return the list
257 of change files together with commit message.
258 updated_artifacts: A list of UpdatedArtifacts type which contains a tuple
259 of artifact info and profile info.
260 Note: the actual creation of the commit is done by CI, not here.
261
262 Args:
263 input_proto (GetUpdatedFilesRequest): The input proto
264 output_proto (GetUpdatedFilesResponse): The output proto
265 _config (api_config.ApiConfig): The API call config.
266 """
267 commit_message = ''
268 for artifact in input_proto.uploaded_artifacts:
269 artifact_type = artifact.artifact_info.artifact_type
270 if artifact_type not in _TOOLCHAIN_COMMIT_HANDLERS:
271 logging.error('%s not understood', artifact_type)
272 return controller.RETURN_CODE_UNRECOVERABLE
273 artifact_name = _TOOLCHAIN_COMMIT_HANDLERS[artifact_type]
274 if artifact_name:
275 assert len(artifact.artifact_info.artifacts) == 1, (
276 'Only one file to update per each artifact')
277 updated_files, message = toolchain_util.GetUpdatedFiles(
278 artifact_name, artifact.artifact_info.artifacts[0].path,
279 _GetProfileInfoDict(artifact.profile_info))
280 for f in updated_files:
281 file_info = output_proto.updated_files.add()
282 file_info.path = f
283
284 commit_message += message + '\n'
285 output_proto.commit_message = commit_message
286 # No commit footer is added for now. Can add more here if needed
287
288
Ryan Beltranf2a5dcc2022-04-19 20:34:00 +0000289def _GetProfileInfoDict(profile_info):
290 """Convert profile_info to a dict.
291
292 Args:
293 profile_info (ArtifactProfileInfo): The artifact profile_info.
294
295 Returns:
296 A dictionary containing profile info.
297 """
298 ret = {}
299 which = profile_info.WhichOneof('artifact_profile_info')
300 if which:
301 value = getattr(profile_info, which)
302 # If it is a message, then use the contents of the message. This works as
303 # long as simple types do not have a 'DESCRIPTOR' attribute. (And protobuf
304 # messages do.)
305 if getattr(value, 'DESCRIPTOR', None):
306 ret.update({k.name: v for k, v in value.ListFields()})
307 else:
308 ret[which] = value
309 return ret
310
311
312LINTER_CODES = {
313 'clang_tidy': toolchain_pb2.LinterFinding.CLANG_TIDY,
314 'cargo_clippy': toolchain_pb2.LinterFinding.CARGO_CLIPPY
315}
316
317
Ryan Beltran0df7fb02021-11-10 20:58:51 +0000318@faux.all_empty
319@validate.exists('sysroot.path')
320@validate.require('packages')
321@validate.validation_complete
322def EmergeWithLinting(input_proto, output_proto, _config):
323 """Emerge packages with linter features enabled and retrieves all findings.
324
325 Args:
326 input_proto (LinterRequest): The nput proto with package and sysroot info.
327 output_proto (LinterResponse): The output proto where findings are stored.
328 _config (api_config.ApiConfig): The API call config (unused).
329 """
330 packages = [
Ryan Beltranf2a5dcc2022-04-19 20:34:00 +0000331 controller_util.deserialize_package_info(package)
332 for package in input_proto.packages
333 ]
Ryan Beltran7d191802021-11-24 00:08:17 +0000334
Ryan Beltranf2a5dcc2022-04-19 20:34:00 +0000335 build_linter = toolchain.BuildLinter(
336 packages,
337 input_proto.sysroot.path,
338 differential=input_proto.filter_modified)
Ryan Beltran7d191802021-11-24 00:08:17 +0000339
Ryan Beltranf2a5dcc2022-04-19 20:34:00 +0000340 findings = build_linter.emerge_with_linting(use_clippy=True, use_tidy=True)
Ryan Beltranf9a86f42022-04-13 20:58:18 +0000341
Ryan Beltran7d191802021-11-24 00:08:17 +0000342 for finding in findings:
Ryan Beltranf2a5dcc2022-04-19 20:34:00 +0000343 locations = []
Ryan Beltran40e4ad12021-05-17 19:55:03 +0000344 for location in finding.locations:
Ryan Beltranf2a5dcc2022-04-19 20:34:00 +0000345 locations.append(
Ryan Beltran40e4ad12021-05-17 19:55:03 +0000346 toolchain_pb2.LinterFindingLocation(
Ryan Beltranf2a5dcc2022-04-19 20:34:00 +0000347 filepath=location.filepath,
Ryan Beltran40e4ad12021-05-17 19:55:03 +0000348 line_start=location.line_start,
Ryan Beltranf2a5dcc2022-04-19 20:34:00 +0000349 line_end=location.line_end))
350 output_proto.findings.append(
Ryan Beltran40e4ad12021-05-17 19:55:03 +0000351 toolchain_pb2.LinterFinding(
352 message=finding.message,
Ryan Beltranf2a5dcc2022-04-19 20:34:00 +0000353 locations=locations,
354 linter=LINTER_CODES[finding.linter]))