blob: 57fdc3a9ff486b5283ff860a9d1ce2d5eac5e2d6 [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
Ryan Beltran7d191802021-11-24 00:08:17 +00009import os
Alex Kleincd03a5e2021-10-18 13:23:47 -060010from pathlib import Path
Ryan Beltran7d191802021-11-24 00:08:17 +000011import re
LaMont Jonesb20b3d92019-11-23 11:47:48 -070012
LaMont Jones5d2edcb2019-12-23 11:32:03 -070013from chromite.api import controller
Alex Klein076841b2019-08-29 15:19:39 -060014from chromite.api import faux
Alex Klein231d2da2019-07-22 16:44:45 -060015from chromite.api import validate
LaMont Jones5d2edcb2019-12-23 11:32:03 -070016from chromite.api.controller import controller_util
Tiancong Wang24a3df72019-08-20 15:48:51 -070017from chromite.api.gen.chromite.api import toolchain_pb2
LaMont Jonesfd68cb12020-04-29 16:43:06 -060018from chromite.api.gen.chromite.api.artifacts_pb2 import PrepareForBuildResponse
Chris McDonald1672ddb2021-07-21 11:48:23 -060019from chromite.api.gen.chromiumos.builder_config_pb2 import BuilderConfig
20from chromite.lib import chroot_util
21from chromite.lib import cros_build_lib
Ryan Beltran7d191802021-11-24 00:08:17 +000022from chromite.lib import osutils
Chris McDonald1672ddb2021-07-21 11:48:23 -060023from chromite.lib import toolchain_util
Ryan Beltran7d191802021-11-24 00:08:17 +000024
25if cros_build_lib.IsInsideChroot():
26 # Only used for linting in chroot and requires yaml which is only in chroot
27 from chromite.scripts import tricium_cargo_clippy, tricium_clang_tidy
Tiancong Wangaf050172019-07-10 11:52:03 -070028
Chris McDonald1672ddb2021-07-21 11:48:23 -060029
LaMont Jonesb20b3d92019-11-23 11:47:48 -070030_Handlers = collections.namedtuple('_Handlers', ['name', 'prepare', 'bundle'])
31_TOOLCHAIN_ARTIFACT_HANDLERS = {
LaMont Jonescd1503d2020-03-04 09:09:59 -070032 BuilderConfig.Artifacts.UNVERIFIED_CHROME_LLVM_ORDERFILE:
LaMont Jonesd3944582020-03-04 10:37:05 -070033 _Handlers('UnverifiedChromeLlvmOrderfile',
LaMont Jones5d2edcb2019-12-23 11:32:03 -070034 toolchain_util.PrepareForBuild,
35 toolchain_util.BundleArtifacts),
LaMont Jonescd1503d2020-03-04 09:09:59 -070036 BuilderConfig.Artifacts.VERIFIED_CHROME_LLVM_ORDERFILE:
LaMont Jones3fed7f22020-03-04 10:15:11 -070037 _Handlers('VerifiedChromeLlvmOrderfile', toolchain_util.PrepareForBuild,
LaMont Jones5d2edcb2019-12-23 11:32:03 -070038 toolchain_util.BundleArtifacts),
LaMont Jonesb20b3d92019-11-23 11:47:48 -070039 BuilderConfig.Artifacts.CHROME_CLANG_WARNINGS_FILE:
LaMont Jones3fed7f22020-03-04 10:15:11 -070040 _Handlers('ChromeClangWarningsFile', toolchain_util.PrepareForBuild,
LaMont Jones90bab632020-01-27 15:58:26 -070041 toolchain_util.BundleArtifacts),
LaMont Jonesb20b3d92019-11-23 11:47:48 -070042 BuilderConfig.Artifacts.UNVERIFIED_LLVM_PGO_FILE:
LaMont Jones3fed7f22020-03-04 10:15:11 -070043 _Handlers('UnverifiedLlvmPgoFile', toolchain_util.PrepareForBuild,
LaMont Jones90bab632020-01-27 15:58:26 -070044 toolchain_util.BundleArtifacts),
LaMont Jones45ca6c42020-02-05 09:39:09 -070045 BuilderConfig.Artifacts.UNVERIFIED_CHROME_BENCHMARK_AFDO_FILE:
46 _Handlers('UnverifiedChromeBenchmarkAfdoFile',
LaMont Jones90bab632020-01-27 15:58:26 -070047 toolchain_util.PrepareForBuild,
48 toolchain_util.BundleArtifacts),
LaMont Jones3fed7f22020-03-04 10:15:11 -070049 BuilderConfig.Artifacts.CHROME_DEBUG_BINARY:
Tiancong Wangba2a1c22021-01-19 10:45:06 -080050 _Handlers('ChromeDebugBinary', toolchain_util.PrepareForBuild,
LaMont Jones3fed7f22020-03-04 10:15:11 -070051 toolchain_util.BundleArtifacts),
LaMont Jones53bddd02020-03-12 15:02:54 -060052 BuilderConfig.Artifacts.UNVERIFIED_CHROME_BENCHMARK_PERF_FILE:
53 _Handlers('UnverifiedChromeBenchmarkPerfFile',
54 toolchain_util.PrepareForBuild,
55 toolchain_util.BundleArtifacts),
LaMont Jones45ca6c42020-02-05 09:39:09 -070056 BuilderConfig.Artifacts.VERIFIED_CHROME_BENCHMARK_AFDO_FILE:
57 _Handlers('VerifiedChromeBenchmarkAfdoFile',
LaMont Jones90bab632020-01-27 15:58:26 -070058 toolchain_util.PrepareForBuild,
59 toolchain_util.BundleArtifacts),
LaMont Jones45ca6c42020-02-05 09:39:09 -070060 BuilderConfig.Artifacts.UNVERIFIED_KERNEL_CWP_AFDO_FILE:
LaMont Jones3fed7f22020-03-04 10:15:11 -070061 _Handlers('UnverifiedKernelCwpAfdoFile', toolchain_util.PrepareForBuild,
LaMont Jones45ca6c42020-02-05 09:39:09 -070062 toolchain_util.BundleArtifacts),
63 BuilderConfig.Artifacts.VERIFIED_KERNEL_CWP_AFDO_FILE:
LaMont Jones3fed7f22020-03-04 10:15:11 -070064 _Handlers('VerifiedKernelCwpAfdoFile', toolchain_util.PrepareForBuild,
LaMont Jones45ca6c42020-02-05 09:39:09 -070065 toolchain_util.BundleArtifacts),
66 BuilderConfig.Artifacts.UNVERIFIED_CHROME_CWP_AFDO_FILE:
LaMont Jones3fed7f22020-03-04 10:15:11 -070067 _Handlers('UnverifiedChromeCwpAfdoFile', toolchain_util.PrepareForBuild,
LaMont Jones45ca6c42020-02-05 09:39:09 -070068 toolchain_util.BundleArtifacts),
69 BuilderConfig.Artifacts.VERIFIED_CHROME_CWP_AFDO_FILE:
LaMont Jones3fed7f22020-03-04 10:15:11 -070070 _Handlers('VerifiedChromeCwpAfdoFile', toolchain_util.PrepareForBuild,
LaMont Jones45ca6c42020-02-05 09:39:09 -070071 toolchain_util.BundleArtifacts),
72 BuilderConfig.Artifacts.VERIFIED_RELEASE_AFDO_FILE:
LaMont Jones3fed7f22020-03-04 10:15:11 -070073 _Handlers('VerifiedReleaseAfdoFile', toolchain_util.PrepareForBuild,
LaMont Jones90bab632020-01-27 15:58:26 -070074 toolchain_util.BundleArtifacts),
Tiancong Wang91cf1dd2020-05-05 10:30:22 -070075 BuilderConfig.Artifacts.TOOLCHAIN_WARNING_LOGS:
76 _Handlers('ToolchainWarningLogs', toolchain_util.PrepareForBuild,
77 toolchain_util.BundleArtifacts),
Tiancong Wangfe3dbd22020-06-12 15:45:55 -070078 BuilderConfig.Artifacts.CHROME_AFDO_PROFILE_FOR_ANDROID_LINUX:
79 _Handlers('ChromeAFDOProfileForAndroidLinux',
80 toolchain_util.PrepareForBuild,
81 toolchain_util.BundleArtifacts),
Jian Cai6190cc82020-06-12 16:24:32 -070082 BuilderConfig.Artifacts.CLANG_CRASH_DIAGNOSES:
83 _Handlers('ClangCrashDiagnoses', toolchain_util.PrepareForBuild,
84 toolchain_util.BundleArtifacts),
Ryan Beltran0be7dcf2020-12-09 18:31:53 +000085 BuilderConfig.Artifacts.COMPILER_RUSAGE_LOG:
86 _Handlers('CompilerRusageLogs', toolchain_util.PrepareForBuild,
87 toolchain_util.BundleArtifacts),
LaMont Jonesb20b3d92019-11-23 11:47:48 -070088}
89
Tiancong Wangd5214132021-01-12 10:43:57 -080090_TOOLCHAIN_COMMIT_HANDLERS = {
91 BuilderConfig.Artifacts.VERIFIED_KERNEL_CWP_AFDO_FILE:
92 'VerifiedKernelCwpAfdoFile'
93}
94
Ryan Beltran7d191802021-11-24 00:08:17 +000095TIDY_BASE_DIR = Path('/tmp/linting_output/clang-tidy')
96
LaMont Jonesb20b3d92019-11-23 11:47:48 -070097
LaMont Jonese7821672020-04-09 08:56:26 -060098def _GetProfileInfoDict(profile_info):
99 """Convert profile_info to a dict.
100
101 Args:
102 profile_info (ArtifactProfileInfo): The artifact profile_info.
103
104 Returns:
105 A dictionary containing profile info.
106 """
107 ret = {}
108 which = profile_info.WhichOneof('artifact_profile_info')
109 if which:
110 value = getattr(profile_info, which)
111 # If it is a message, then use the contents of the message. This works as
112 # long as simple types do not have a 'DESCRIPTOR' attribute. (And protobuf
113 # messages do.)
114 if getattr(value, 'DESCRIPTOR', None):
115 ret.update({k.name: v for k, v in value.ListFields()})
116 else:
117 ret[which] = value
118 return ret
119
120
LaMont Jonesb20b3d92019-11-23 11:47:48 -0700121# TODO(crbug/1031213): When @faux is expanded to have more than success/failure,
122# this should be changed.
123@faux.all_empty
LaMont Jones5d2edcb2019-12-23 11:32:03 -0700124@validate.require('artifact_types')
125# Note: chroot and sysroot are unspecified the first time that the build_target
126# recipe calls PrepareForBuild. The second time, they are specified. No
127# validation check because "all" values are valid.
LaMont Jonesb20b3d92019-11-23 11:47:48 -0700128@validate.validation_complete
129def PrepareForBuild(input_proto, output_proto, _config):
130 """Prepare to build toolchain artifacts.
131
132 The handlers (from _TOOLCHAIN_ARTIFACT_HANDLERS above) are called with:
133 artifact_name (str): name of the artifact type.
LaMont Jones5d2edcb2019-12-23 11:32:03 -0700134 chroot (chroot_lib.Chroot): chroot. Will be None if the chroot has not
135 yet been created.
136 sysroot_path (str): sysroot path inside the chroot (e.g., /build/atlas).
137 Will be an empty string if the sysroot has not yet been created.
138 build_target_name (str): name of the build target (e.g., atlas). Will be
139 an empty string if the sysroot has not yet been created.
140 input_artifacts ({(str) name:[str gs_locations]}): locations for possible
141 input artifacts. The handler is expected to know which keys it should
142 be using, and ignore any keys that it does not understand.
LaMont Jonese7821672020-04-09 08:56:26 -0600143 profile_info ({(str) name: (str) value}) Dictionary containing profile
144 information.
LaMont Jonesa215f1e2019-12-06 10:18:58 -0700145
146 They locate and modify any ebuilds and/or source required for the artifact
147 being created, then return a value from toolchain_util.PrepareForBuildReturn.
LaMont Jonesb20b3d92019-11-23 11:47:48 -0700148
LaMont Jones5d2edcb2019-12-23 11:32:03 -0700149 This function sets output_proto.build_relevance to the result.
150
LaMont Jonesb20b3d92019-11-23 11:47:48 -0700151 Args:
152 input_proto (PrepareForToolchainBuildRequest): The input proto
153 output_proto (PrepareForToolchainBuildResponse): The output proto
154 _config (api_config.ApiConfig): The API call config.
155 """
LaMont Jones5d2edcb2019-12-23 11:32:03 -0700156 if input_proto.chroot.path:
157 chroot = controller_util.ParseChroot(input_proto.chroot)
158 else:
159 chroot = None
LaMont Jones4579e8c2019-12-06 14:20:37 -0700160
LaMont Jones5d2edcb2019-12-23 11:32:03 -0700161 input_artifacts = collections.defaultdict(list)
162 for art in input_proto.input_artifacts:
163 item = _TOOLCHAIN_ARTIFACT_HANDLERS.get(art.input_artifact_type)
164 if item:
165 input_artifacts[item.name].extend(
166 ['gs://%s' % str(x) for x in art.input_artifact_gs_locations])
167
LaMont Jonese7821672020-04-09 08:56:26 -0600168 profile_info = _GetProfileInfoDict(input_proto.profile_info)
LaMont Jones45ca6c42020-02-05 09:39:09 -0700169
LaMont Jones5d2edcb2019-12-23 11:32:03 -0700170 results = set()
171 sysroot_path = input_proto.sysroot.path
172 build_target = input_proto.sysroot.build_target.name
LaMont Jonesb20b3d92019-11-23 11:47:48 -0700173 for artifact_type in input_proto.artifact_types:
LaMont Jones5d2edcb2019-12-23 11:32:03 -0700174 # Unknown artifact_types are an error.
175 handler = _TOOLCHAIN_ARTIFACT_HANDLERS[artifact_type]
176 if handler.prepare:
Tiancong Wangba2a1c22021-01-19 10:45:06 -0800177 results.add(
178 handler.prepare(handler.name, chroot, sysroot_path, build_target,
179 input_artifacts, profile_info))
LaMont Jonesb20b3d92019-11-23 11:47:48 -0700180
181 # Translate the returns from the handlers we called.
182 # If any NEEDED => NEEDED
183 # elif any UNKNOWN => UNKNOWN
184 # elif any POINTLESS => POINTLESS
185 # else UNKNOWN.
LaMont Jonesb20b3d92019-11-23 11:47:48 -0700186 if toolchain_util.PrepareForBuildReturn.NEEDED in results:
LaMont Jonesfd68cb12020-04-29 16:43:06 -0600187 output_proto.build_relevance = PrepareForBuildResponse.NEEDED
LaMont Jonesb20b3d92019-11-23 11:47:48 -0700188 elif toolchain_util.PrepareForBuildReturn.UNKNOWN in results:
LaMont Jonesfd68cb12020-04-29 16:43:06 -0600189 output_proto.build_relevance = PrepareForBuildResponse.UNKNOWN
LaMont Jonesb20b3d92019-11-23 11:47:48 -0700190 elif toolchain_util.PrepareForBuildReturn.POINTLESS in results:
LaMont Jonesfd68cb12020-04-29 16:43:06 -0600191 output_proto.build_relevance = PrepareForBuildResponse.POINTLESS
LaMont Jonesb20b3d92019-11-23 11:47:48 -0700192 else:
LaMont Jonesfd68cb12020-04-29 16:43:06 -0600193 output_proto.build_relevance = PrepareForBuildResponse.UNKNOWN
LaMont Jones5d2edcb2019-12-23 11:32:03 -0700194 return controller.RETURN_CODE_SUCCESS
LaMont Jonesb20b3d92019-11-23 11:47:48 -0700195
196
197# TODO(crbug/1031213): When @faux is expanded to have more than success/failure,
198# this should be changed.
199@faux.all_empty
LaMont Jonese911df02020-04-16 12:40:17 -0600200@validate.require('chroot.path', 'output_dir', 'artifact_types')
LaMont Jones4579e8c2019-12-06 14:20:37 -0700201@validate.exists('output_dir')
LaMont Jones5d2edcb2019-12-23 11:32:03 -0700202@validate.validation_complete
LaMont Jonesb20b3d92019-11-23 11:47:48 -0700203def BundleArtifacts(input_proto, output_proto, _config):
Alex Kleincd03a5e2021-10-18 13:23:47 -0600204 """Bundle valid toolchain artifacts.
LaMont Jonesb20b3d92019-11-23 11:47:48 -0700205
206 The handlers (from _TOOLCHAIN_ARTIFACT_HANDLERS above) are called with:
LaMont Jonesa215f1e2019-12-06 10:18:58 -0700207 artifact_name (str): name of the artifact type
LaMont Jones5d2edcb2019-12-23 11:32:03 -0700208 chroot (chroot_lib.Chroot): chroot
LaMont Jonese911df02020-04-16 12:40:17 -0600209 sysroot_path (str): sysroot path inside the chroot (e.g., /build/atlas),
210 or None.
LaMont Jones5d2edcb2019-12-23 11:32:03 -0700211 chrome_root (str): path to chrome root. (e.g., /b/s/w/ir/k/chrome)
LaMont Jonese911df02020-04-16 12:40:17 -0600212 build_target_name (str): name of the build target (e.g., atlas), or None.
LaMont Jonesb20b3d92019-11-23 11:47:48 -0700213 output_dir (str): absolute path where artifacts are being bundled.
214 (e.g., /b/s/w/ir/k/recipe_cleanup/artifactssptfMU)
LaMont Jonese7821672020-04-09 08:56:26 -0600215 profile_info ({(str) name: (str) value}) Dictionary containing profile
216 information.
LaMont Jonesb20b3d92019-11-23 11:47:48 -0700217
218 Note: the actual upload to GS is done by CI, not here.
219
220 Args:
221 input_proto (BundleToolchainRequest): The input proto
222 output_proto (BundleToolchainResponse): The output proto
223 _config (api_config.ApiConfig): The API call config.
224 """
LaMont Jones5d2edcb2019-12-23 11:32:03 -0700225 chroot = controller_util.ParseChroot(input_proto.chroot)
LaMont Jonesb20b3d92019-11-23 11:47:48 -0700226
LaMont Jonese7821672020-04-09 08:56:26 -0600227 profile_info = _GetProfileInfoDict(input_proto.profile_info)
LaMont Jones45ca6c42020-02-05 09:39:09 -0700228
Alex Kleincd03a5e2021-10-18 13:23:47 -0600229 output_path = Path(input_proto.output_dir)
230
LaMont Jonesb20b3d92019-11-23 11:47:48 -0700231 for artifact_type in input_proto.artifact_types:
LaMont Jones5d2edcb2019-12-23 11:32:03 -0700232 if artifact_type not in _TOOLCHAIN_ARTIFACT_HANDLERS:
233 logging.error('%s not understood', artifact_type)
234 return controller.RETURN_CODE_UNRECOVERABLE
Alex Kleincd03a5e2021-10-18 13:23:47 -0600235
LaMont Jones5d2edcb2019-12-23 11:32:03 -0700236 handler = _TOOLCHAIN_ARTIFACT_HANDLERS[artifact_type]
Alex Kleincd03a5e2021-10-18 13:23:47 -0600237 if not handler or not handler.bundle:
238 logging.warning('%s does not have a handler with a bundle function.',
239 artifact_type)
240 continue
241
242 artifacts = handler.bundle(handler.name, chroot, input_proto.sysroot.path,
243 input_proto.sysroot.build_target.name,
244 input_proto.output_dir, profile_info)
245 if not artifacts:
246 continue
247
248 # Filter out artifacts that do not exist or are empty.
249 usable_artifacts = []
250 for artifact in artifacts:
251 artifact_path = output_path / artifact
252 if not artifact_path.exists():
253 logging.warning('%s is not in the output directory.', artifact)
254 elif not artifact_path.stat().st_size:
255 logging.warning('%s is empty.', artifact)
256 else:
257 usable_artifacts.append(artifact)
258
259 if not usable_artifacts:
260 logging.warning('No usable artifacts for artifact type %s', artifact_type)
261 continue
262
263 # Add all usable artifacts.
264 art_info = output_proto.artifacts_info.add()
265 art_info.artifact_type = artifact_type
266 for artifact in usable_artifacts:
267 art_info.artifacts.add().path = artifact
LaMont Jonesb20b3d92019-11-23 11:47:48 -0700268
269
Tiancong Wangd5214132021-01-12 10:43:57 -0800270def _GetUpdatedFilesResponse(_input_proto, output_proto, _config):
271 """Add successful status to the faux response."""
272 file_info = output_proto.updated_files.add()
273 file_info.path = '/any/modified/file'
274 output_proto.commit_message = 'Commit message'
275
276
277@faux.empty_error
278@faux.success(_GetUpdatedFilesResponse)
279@validate.require('uploaded_artifacts')
280@validate.validation_complete
281def GetUpdatedFiles(input_proto, output_proto, _config):
282 """Use uploaded artifacts to update some updates in a chromeos checkout.
283
284 The function will call toolchain_util.GetUpdatedFiles using the type of
285 uploaded artifacts to make some changes in a checkout, and return the list
286 of change files together with commit message.
287 updated_artifacts: A list of UpdatedArtifacts type which contains a tuple
288 of artifact info and profile info.
289 Note: the actual creation of the commit is done by CI, not here.
290
291 Args:
292 input_proto (GetUpdatedFilesRequest): The input proto
293 output_proto (GetUpdatedFilesResponse): The output proto
294 _config (api_config.ApiConfig): The API call config.
295 """
296 commit_message = ''
297 for artifact in input_proto.uploaded_artifacts:
298 artifact_type = artifact.artifact_info.artifact_type
299 if artifact_type not in _TOOLCHAIN_COMMIT_HANDLERS:
300 logging.error('%s not understood', artifact_type)
301 return controller.RETURN_CODE_UNRECOVERABLE
302 artifact_name = _TOOLCHAIN_COMMIT_HANDLERS[artifact_type]
303 if artifact_name:
304 assert len(artifact.artifact_info.artifacts) == 1, (
305 'Only one file to update per each artifact')
306 updated_files, message = toolchain_util.GetUpdatedFiles(
307 artifact_name, artifact.artifact_info.artifacts[0].path,
308 _GetProfileInfoDict(artifact.profile_info))
309 for f in updated_files:
310 file_info = output_proto.updated_files.add()
311 file_info.path = f
312
313 commit_message += message + '\n'
314 output_proto.commit_message = commit_message
315 # No commit footer is added for now. Can add more here if needed
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 = [
331 f'{package.category}/{package.package_name}'
332 for package in input_proto.packages]
Ryan Beltran7d191802021-11-24 00:08:17 +0000333
334 # rm any existing lints from clang tidy
335 osutils.RmDir(TIDY_BASE_DIR, ignore_missing=True, sudo=True)
336 osutils.SafeMakedirs(TIDY_BASE_DIR, 0o777, sudo=True)
337
Ryan Beltranf9a86f42022-04-13 20:58:18 +0000338 # rm any existing temporary portage files from builds of affected packages:
339 # this is required to make sure lints are always regenerated
340 for package in packages:
341 cache_files_dir = f'{input_proto.sysroot.path}/var/cache/portage/{package}'
342 osutils.RmDir(cache_files_dir, ignore_missing=True)
343
Ryan Beltran0df7fb02021-11-10 20:58:51 +0000344 emerge_cmd = chroot_util.GetEmergeCommand(input_proto.sysroot.path)
345 cros_build_lib.sudo_run(
346 emerge_cmd + packages,
347 preserve_env=True,
348 extra_env={
349 'ENABLE_RUST_CLIPPY': 1,
Ryan Beltran7d191802021-11-24 00:08:17 +0000350 'WITH_TIDY': 'tricium',
351 'FEATURES': 'noclean'
Ryan Beltran0df7fb02021-11-10 20:58:51 +0000352 }
353 )
354
Ryan Beltran923a1312021-07-30 00:28:13 +0000355 # FIXME(b/195056381): default git-repo should be replaced with logic in
356 # build_linters recipe to detect the repo path for applied patches.
Ryan Beltran7d191802021-11-24 00:08:17 +0000357 # As of 01-05-21 only platform2 is supported so this value works temporarily.
358 git_repo_path = '/mnt/host/source/src/platform2/'
359
360 linter_findings = _fetch_clippy_lints(git_repo_path)
361 linter_findings.extend(_fetch_tidy_lints(git_repo_path))
Ryan Beltran27bf3412022-04-13 20:27:37 +0000362
363 if input_proto.filter_modified:
364 linter_findings = _filter_linter_findings(linter_findings, git_repo_path)
365
Ryan Beltran7d191802021-11-24 00:08:17 +0000366 output_proto.findings.extend(linter_findings)
367
368
369LINTER_CODES = {
370 'clang_tidy': toolchain_pb2.LinterFinding.CLANG_TIDY,
371 'cargo_clippy': toolchain_pb2.LinterFinding.CARGO_CLIPPY
372}
373
374
375def _filter_linter_findings(findings, git_repo_path):
Ryan Beltran27bf3412022-04-13 20:27:37 +0000376 """Filters findings to keep only those concerning modified lines."""
Ryan Beltran7d191802021-11-24 00:08:17 +0000377 new_findings = []
378 new_lines = _get_added_lines({git_repo_path: 'HEAD'})
379 for finding in findings:
380 for loc in finding.locations:
381 for (addition_start, addition_end) in new_lines.get(loc.filepath, set()):
382 if addition_start <= loc.line_start < addition_end:
383 new_findings.append(finding)
384 return new_findings
385
386
387def _get_added_lines(git_repos):
388 """Parses the lines with additions from fit diff for the provided repos.
389
390 Args:
391 git_repos: a dictionary mapping repo paths to hashes for `git diff`
392
393 Returns:
394 A dictionary mapping modified filepaths to sets of tuples where each
395 tuple is a (start_line, end_line) pair noting which lines were modified.
396 Note that start_line is inclusive, and end_line is exclusive.
397 """
398 new_lines = {}
399 file_path_pattern = re.compile(r'^\+\+\+ b/(?P<file_path>.*)$')
400 position_pattern = re.compile(
401 r'^@@ -\d+(?:,\d+)? \+(?P<line_num>\d+)(?:,(?P<lines_added>\d+))? @@')
402 for git_repo, git_hash in git_repos.items():
403 cmd = f'git -C {git_repo} diff -U0 {git_hash}^...{git_hash}'
404 diff = cros_build_lib.run(cmd, capture_output=True, shell=True,
405 encoding='utf-8').output
406 current_file = ''
407 for line in diff.splitlines():
408 file_path_match = re.match(file_path_pattern, str(line))
409 if file_path_match:
410 current_file = file_path_match.group('file_path')
411 continue
412 position_match = re.match(position_pattern, str(line))
413 if position_match:
414 if current_file not in new_lines:
415 new_lines[current_file] = set()
416 line_num = int(position_match.group('line_num'))
417 line_count = position_match.group('lines_added')
418 line_count = int(line_count) if line_count is not None else 1
419 new_lines[current_file].add((line_num, line_num + line_count))
420 return new_lines
421
422
423def _fetch_clippy_lints(git_repo_path):
424 """Get lints created by Cargo Clippy during emerge."""
425 lints_dir = '/tmp/cargo_clippy'
Ryan Beltran923a1312021-07-30 00:28:13 +0000426 findings = tricium_cargo_clippy.parse_files(lints_dir, git_repo_path)
427 findings = tricium_cargo_clippy.filter_diagnostics(findings)
Ryan Beltran40e4ad12021-05-17 19:55:03 +0000428 findings_protos = []
429 for finding in findings:
430 location_protos = []
431 for location in finding.locations:
432 location_protos.append(
433 toolchain_pb2.LinterFindingLocation(
434 filepath=location.file_path,
435 line_start=location.line_start,
436 line_end=location.line_end
437 )
438 )
439 findings_protos.append(
440 toolchain_pb2.LinterFinding(
441 message=finding.message,
Ryan Beltran7d191802021-11-24 00:08:17 +0000442 locations=location_protos,
443 linter=LINTER_CODES['cargo_clippy']
Ryan Beltran40e4ad12021-05-17 19:55:03 +0000444 )
445 )
446 return findings_protos
447
448
Ryan Beltran7d191802021-11-24 00:08:17 +0000449def _fetch_tidy_lints(git_repo_path):
450 """Get lints created by Clang Tidy during emerge."""
451
452 def resolve_file_path(file_path):
453 # Remove git repo from prefix
454 file_path = re.sub('^' + git_repo_path, '/', str(file_path))
455 # Remove ebuild work directories from prefix
456 # Such as: "**/<package>-9999/work/<package>-9999/"
457 # or: "**/<package>-0.24.52-r9/work/<package>-0.24.52/"
458 return re.sub(r'(.*/)?([^/]+)-[^/]+/work/[^/]+/+', '', file_path)
459
460 lints = set()
461 for filename in os.listdir(TIDY_BASE_DIR):
462 if filename.endswith('.json'):
463 invocation_result = tricium_clang_tidy.parse_tidy_invocation(
464 TIDY_BASE_DIR / filename)
465 meta, complaints = invocation_result
466 assert not meta.exit_code, (
467 f'Invoking clang-tidy on {meta.lint_target} with flags '
468 f'{meta.invocation} exited with code {meta.exit_code}; '
469 f'output:\n{meta.stdstreams}')
470 lints.update(complaints)
471 return [
472 toolchain_pb2.LinterFinding(
473 message=lint.message,
474 locations=[
475 toolchain_pb2.LinterFindingLocation(
476 filepath=resolve_file_path(lint.file_path),
477 line_start=lint.line_number,
478 line_end=lint.line_number
479 )
480 ],
481 linter=LINTER_CODES['clang_tidy']
482 )
483 for lint in tricium_clang_tidy.filter_tidy_lints(None, None, lints)
484 ]