blob: 1f55c9880b7ca4a64afd394bb9cdb744f72f0578 [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
Mike Frysinger1cc8f1f2022-04-28 22:40:40 -040025
Ryan Beltran7d191802021-11-24 00:08:17 +000026if cros_build_lib.IsInsideChroot():
27 # Only used for linting in chroot and requires yaml which is only in chroot
Mike Frysinger1cc8f1f2022-04-28 22:40:40 -040028 from chromite.scripts import tricium_cargo_clippy
29 from chromite.scripts import tricium_clang_tidy
Tiancong Wangaf050172019-07-10 11:52:03 -070030
Chris McDonald1672ddb2021-07-21 11:48:23 -060031
LaMont Jonesb20b3d92019-11-23 11:47:48 -070032_Handlers = collections.namedtuple('_Handlers', ['name', 'prepare', 'bundle'])
33_TOOLCHAIN_ARTIFACT_HANDLERS = {
LaMont Jonescd1503d2020-03-04 09:09:59 -070034 BuilderConfig.Artifacts.UNVERIFIED_CHROME_LLVM_ORDERFILE:
LaMont Jonesd3944582020-03-04 10:37:05 -070035 _Handlers('UnverifiedChromeLlvmOrderfile',
LaMont Jones5d2edcb2019-12-23 11:32:03 -070036 toolchain_util.PrepareForBuild,
37 toolchain_util.BundleArtifacts),
LaMont Jonescd1503d2020-03-04 09:09:59 -070038 BuilderConfig.Artifacts.VERIFIED_CHROME_LLVM_ORDERFILE:
LaMont Jones3fed7f22020-03-04 10:15:11 -070039 _Handlers('VerifiedChromeLlvmOrderfile', toolchain_util.PrepareForBuild,
LaMont Jones5d2edcb2019-12-23 11:32:03 -070040 toolchain_util.BundleArtifacts),
LaMont Jonesb20b3d92019-11-23 11:47:48 -070041 BuilderConfig.Artifacts.CHROME_CLANG_WARNINGS_FILE:
LaMont Jones3fed7f22020-03-04 10:15:11 -070042 _Handlers('ChromeClangWarningsFile', toolchain_util.PrepareForBuild,
LaMont Jones90bab632020-01-27 15:58:26 -070043 toolchain_util.BundleArtifacts),
LaMont Jonesb20b3d92019-11-23 11:47:48 -070044 BuilderConfig.Artifacts.UNVERIFIED_LLVM_PGO_FILE:
LaMont Jones3fed7f22020-03-04 10:15:11 -070045 _Handlers('UnverifiedLlvmPgoFile', toolchain_util.PrepareForBuild,
LaMont Jones90bab632020-01-27 15:58:26 -070046 toolchain_util.BundleArtifacts),
LaMont Jones45ca6c42020-02-05 09:39:09 -070047 BuilderConfig.Artifacts.UNVERIFIED_CHROME_BENCHMARK_AFDO_FILE:
48 _Handlers('UnverifiedChromeBenchmarkAfdoFile',
LaMont Jones90bab632020-01-27 15:58:26 -070049 toolchain_util.PrepareForBuild,
50 toolchain_util.BundleArtifacts),
LaMont Jones3fed7f22020-03-04 10:15:11 -070051 BuilderConfig.Artifacts.CHROME_DEBUG_BINARY:
Tiancong Wangba2a1c22021-01-19 10:45:06 -080052 _Handlers('ChromeDebugBinary', toolchain_util.PrepareForBuild,
LaMont Jones3fed7f22020-03-04 10:15:11 -070053 toolchain_util.BundleArtifacts),
LaMont Jones53bddd02020-03-12 15:02:54 -060054 BuilderConfig.Artifacts.UNVERIFIED_CHROME_BENCHMARK_PERF_FILE:
55 _Handlers('UnverifiedChromeBenchmarkPerfFile',
56 toolchain_util.PrepareForBuild,
57 toolchain_util.BundleArtifacts),
LaMont Jones45ca6c42020-02-05 09:39:09 -070058 BuilderConfig.Artifacts.VERIFIED_CHROME_BENCHMARK_AFDO_FILE:
59 _Handlers('VerifiedChromeBenchmarkAfdoFile',
LaMont Jones90bab632020-01-27 15:58:26 -070060 toolchain_util.PrepareForBuild,
61 toolchain_util.BundleArtifacts),
LaMont Jones45ca6c42020-02-05 09:39:09 -070062 BuilderConfig.Artifacts.UNVERIFIED_KERNEL_CWP_AFDO_FILE:
LaMont Jones3fed7f22020-03-04 10:15:11 -070063 _Handlers('UnverifiedKernelCwpAfdoFile', toolchain_util.PrepareForBuild,
LaMont Jones45ca6c42020-02-05 09:39:09 -070064 toolchain_util.BundleArtifacts),
65 BuilderConfig.Artifacts.VERIFIED_KERNEL_CWP_AFDO_FILE:
LaMont Jones3fed7f22020-03-04 10:15:11 -070066 _Handlers('VerifiedKernelCwpAfdoFile', toolchain_util.PrepareForBuild,
LaMont Jones45ca6c42020-02-05 09:39:09 -070067 toolchain_util.BundleArtifacts),
68 BuilderConfig.Artifacts.UNVERIFIED_CHROME_CWP_AFDO_FILE:
LaMont Jones3fed7f22020-03-04 10:15:11 -070069 _Handlers('UnverifiedChromeCwpAfdoFile', toolchain_util.PrepareForBuild,
LaMont Jones45ca6c42020-02-05 09:39:09 -070070 toolchain_util.BundleArtifacts),
71 BuilderConfig.Artifacts.VERIFIED_CHROME_CWP_AFDO_FILE:
LaMont Jones3fed7f22020-03-04 10:15:11 -070072 _Handlers('VerifiedChromeCwpAfdoFile', toolchain_util.PrepareForBuild,
LaMont Jones45ca6c42020-02-05 09:39:09 -070073 toolchain_util.BundleArtifacts),
74 BuilderConfig.Artifacts.VERIFIED_RELEASE_AFDO_FILE:
LaMont Jones3fed7f22020-03-04 10:15:11 -070075 _Handlers('VerifiedReleaseAfdoFile', toolchain_util.PrepareForBuild,
LaMont Jones90bab632020-01-27 15:58:26 -070076 toolchain_util.BundleArtifacts),
Tiancong Wang91cf1dd2020-05-05 10:30:22 -070077 BuilderConfig.Artifacts.TOOLCHAIN_WARNING_LOGS:
78 _Handlers('ToolchainWarningLogs', toolchain_util.PrepareForBuild,
79 toolchain_util.BundleArtifacts),
Tiancong Wangfe3dbd22020-06-12 15:45:55 -070080 BuilderConfig.Artifacts.CHROME_AFDO_PROFILE_FOR_ANDROID_LINUX:
81 _Handlers('ChromeAFDOProfileForAndroidLinux',
82 toolchain_util.PrepareForBuild,
83 toolchain_util.BundleArtifacts),
Jian Cai6190cc82020-06-12 16:24:32 -070084 BuilderConfig.Artifacts.CLANG_CRASH_DIAGNOSES:
85 _Handlers('ClangCrashDiagnoses', toolchain_util.PrepareForBuild,
86 toolchain_util.BundleArtifacts),
Ryan Beltran0be7dcf2020-12-09 18:31:53 +000087 BuilderConfig.Artifacts.COMPILER_RUSAGE_LOG:
88 _Handlers('CompilerRusageLogs', toolchain_util.PrepareForBuild,
89 toolchain_util.BundleArtifacts),
LaMont Jonesb20b3d92019-11-23 11:47:48 -070090}
91
Tiancong Wangd5214132021-01-12 10:43:57 -080092_TOOLCHAIN_COMMIT_HANDLERS = {
93 BuilderConfig.Artifacts.VERIFIED_KERNEL_CWP_AFDO_FILE:
94 'VerifiedKernelCwpAfdoFile'
95}
96
Ryan Beltran7d191802021-11-24 00:08:17 +000097TIDY_BASE_DIR = Path('/tmp/linting_output/clang-tidy')
98
LaMont Jonesb20b3d92019-11-23 11:47:48 -070099
LaMont Jonese7821672020-04-09 08:56:26 -0600100def _GetProfileInfoDict(profile_info):
101 """Convert profile_info to a dict.
102
103 Args:
104 profile_info (ArtifactProfileInfo): The artifact profile_info.
105
106 Returns:
107 A dictionary containing profile info.
108 """
109 ret = {}
110 which = profile_info.WhichOneof('artifact_profile_info')
111 if which:
112 value = getattr(profile_info, which)
113 # If it is a message, then use the contents of the message. This works as
114 # long as simple types do not have a 'DESCRIPTOR' attribute. (And protobuf
115 # messages do.)
116 if getattr(value, 'DESCRIPTOR', None):
117 ret.update({k.name: v for k, v in value.ListFields()})
118 else:
119 ret[which] = value
120 return ret
121
122
LaMont Jonesb20b3d92019-11-23 11:47:48 -0700123# TODO(crbug/1031213): When @faux is expanded to have more than success/failure,
124# this should be changed.
125@faux.all_empty
LaMont Jones5d2edcb2019-12-23 11:32:03 -0700126@validate.require('artifact_types')
127# Note: chroot and sysroot are unspecified the first time that the build_target
128# recipe calls PrepareForBuild. The second time, they are specified. No
129# validation check because "all" values are valid.
LaMont Jonesb20b3d92019-11-23 11:47:48 -0700130@validate.validation_complete
131def PrepareForBuild(input_proto, output_proto, _config):
132 """Prepare to build toolchain artifacts.
133
134 The handlers (from _TOOLCHAIN_ARTIFACT_HANDLERS above) are called with:
135 artifact_name (str): name of the artifact type.
LaMont Jones5d2edcb2019-12-23 11:32:03 -0700136 chroot (chroot_lib.Chroot): chroot. Will be None if the chroot has not
137 yet been created.
138 sysroot_path (str): sysroot path inside the chroot (e.g., /build/atlas).
139 Will be an empty string if the sysroot has not yet been created.
140 build_target_name (str): name of the build target (e.g., atlas). Will be
141 an empty string if the sysroot has not yet been created.
142 input_artifacts ({(str) name:[str gs_locations]}): locations for possible
143 input artifacts. The handler is expected to know which keys it should
144 be using, and ignore any keys that it does not understand.
LaMont Jonese7821672020-04-09 08:56:26 -0600145 profile_info ({(str) name: (str) value}) Dictionary containing profile
146 information.
LaMont Jonesa215f1e2019-12-06 10:18:58 -0700147
148 They locate and modify any ebuilds and/or source required for the artifact
149 being created, then return a value from toolchain_util.PrepareForBuildReturn.
LaMont Jonesb20b3d92019-11-23 11:47:48 -0700150
LaMont Jones5d2edcb2019-12-23 11:32:03 -0700151 This function sets output_proto.build_relevance to the result.
152
LaMont Jonesb20b3d92019-11-23 11:47:48 -0700153 Args:
154 input_proto (PrepareForToolchainBuildRequest): The input proto
155 output_proto (PrepareForToolchainBuildResponse): The output proto
156 _config (api_config.ApiConfig): The API call config.
157 """
LaMont Jones5d2edcb2019-12-23 11:32:03 -0700158 if input_proto.chroot.path:
159 chroot = controller_util.ParseChroot(input_proto.chroot)
160 else:
161 chroot = None
LaMont Jones4579e8c2019-12-06 14:20:37 -0700162
LaMont Jones5d2edcb2019-12-23 11:32:03 -0700163 input_artifacts = collections.defaultdict(list)
164 for art in input_proto.input_artifacts:
165 item = _TOOLCHAIN_ARTIFACT_HANDLERS.get(art.input_artifact_type)
166 if item:
167 input_artifacts[item.name].extend(
168 ['gs://%s' % str(x) for x in art.input_artifact_gs_locations])
169
LaMont Jonese7821672020-04-09 08:56:26 -0600170 profile_info = _GetProfileInfoDict(input_proto.profile_info)
LaMont Jones45ca6c42020-02-05 09:39:09 -0700171
LaMont Jones5d2edcb2019-12-23 11:32:03 -0700172 results = set()
173 sysroot_path = input_proto.sysroot.path
174 build_target = input_proto.sysroot.build_target.name
LaMont Jonesb20b3d92019-11-23 11:47:48 -0700175 for artifact_type in input_proto.artifact_types:
LaMont Jones5d2edcb2019-12-23 11:32:03 -0700176 # Unknown artifact_types are an error.
177 handler = _TOOLCHAIN_ARTIFACT_HANDLERS[artifact_type]
178 if handler.prepare:
Tiancong Wangba2a1c22021-01-19 10:45:06 -0800179 results.add(
180 handler.prepare(handler.name, chroot, sysroot_path, build_target,
181 input_artifacts, profile_info))
LaMont Jonesb20b3d92019-11-23 11:47:48 -0700182
183 # Translate the returns from the handlers we called.
184 # If any NEEDED => NEEDED
185 # elif any UNKNOWN => UNKNOWN
186 # elif any POINTLESS => POINTLESS
187 # else UNKNOWN.
LaMont Jonesb20b3d92019-11-23 11:47:48 -0700188 if toolchain_util.PrepareForBuildReturn.NEEDED in results:
LaMont Jonesfd68cb12020-04-29 16:43:06 -0600189 output_proto.build_relevance = PrepareForBuildResponse.NEEDED
LaMont Jonesb20b3d92019-11-23 11:47:48 -0700190 elif toolchain_util.PrepareForBuildReturn.UNKNOWN in results:
LaMont Jonesfd68cb12020-04-29 16:43:06 -0600191 output_proto.build_relevance = PrepareForBuildResponse.UNKNOWN
LaMont Jonesb20b3d92019-11-23 11:47:48 -0700192 elif toolchain_util.PrepareForBuildReturn.POINTLESS in results:
LaMont Jonesfd68cb12020-04-29 16:43:06 -0600193 output_proto.build_relevance = PrepareForBuildResponse.POINTLESS
LaMont Jonesb20b3d92019-11-23 11:47:48 -0700194 else:
LaMont Jonesfd68cb12020-04-29 16:43:06 -0600195 output_proto.build_relevance = PrepareForBuildResponse.UNKNOWN
LaMont Jones5d2edcb2019-12-23 11:32:03 -0700196 return controller.RETURN_CODE_SUCCESS
LaMont Jonesb20b3d92019-11-23 11:47:48 -0700197
198
199# TODO(crbug/1031213): When @faux is expanded to have more than success/failure,
200# this should be changed.
201@faux.all_empty
LaMont Jonese911df02020-04-16 12:40:17 -0600202@validate.require('chroot.path', 'output_dir', 'artifact_types')
LaMont Jones4579e8c2019-12-06 14:20:37 -0700203@validate.exists('output_dir')
LaMont Jones5d2edcb2019-12-23 11:32:03 -0700204@validate.validation_complete
LaMont Jonesb20b3d92019-11-23 11:47:48 -0700205def BundleArtifacts(input_proto, output_proto, _config):
Alex Kleincd03a5e2021-10-18 13:23:47 -0600206 """Bundle valid toolchain artifacts.
LaMont Jonesb20b3d92019-11-23 11:47:48 -0700207
208 The handlers (from _TOOLCHAIN_ARTIFACT_HANDLERS above) are called with:
LaMont Jonesa215f1e2019-12-06 10:18:58 -0700209 artifact_name (str): name of the artifact type
LaMont Jones5d2edcb2019-12-23 11:32:03 -0700210 chroot (chroot_lib.Chroot): chroot
LaMont Jonese911df02020-04-16 12:40:17 -0600211 sysroot_path (str): sysroot path inside the chroot (e.g., /build/atlas),
212 or None.
LaMont Jones5d2edcb2019-12-23 11:32:03 -0700213 chrome_root (str): path to chrome root. (e.g., /b/s/w/ir/k/chrome)
LaMont Jonese911df02020-04-16 12:40:17 -0600214 build_target_name (str): name of the build target (e.g., atlas), or None.
LaMont Jonesb20b3d92019-11-23 11:47:48 -0700215 output_dir (str): absolute path where artifacts are being bundled.
216 (e.g., /b/s/w/ir/k/recipe_cleanup/artifactssptfMU)
LaMont Jonese7821672020-04-09 08:56:26 -0600217 profile_info ({(str) name: (str) value}) Dictionary containing profile
218 information.
LaMont Jonesb20b3d92019-11-23 11:47:48 -0700219
220 Note: the actual upload to GS is done by CI, not here.
221
222 Args:
223 input_proto (BundleToolchainRequest): The input proto
224 output_proto (BundleToolchainResponse): The output proto
225 _config (api_config.ApiConfig): The API call config.
226 """
LaMont Jones5d2edcb2019-12-23 11:32:03 -0700227 chroot = controller_util.ParseChroot(input_proto.chroot)
LaMont Jonesb20b3d92019-11-23 11:47:48 -0700228
LaMont Jonese7821672020-04-09 08:56:26 -0600229 profile_info = _GetProfileInfoDict(input_proto.profile_info)
LaMont Jones45ca6c42020-02-05 09:39:09 -0700230
Alex Kleincd03a5e2021-10-18 13:23:47 -0600231 output_path = Path(input_proto.output_dir)
232
LaMont Jonesb20b3d92019-11-23 11:47:48 -0700233 for artifact_type in input_proto.artifact_types:
LaMont Jones5d2edcb2019-12-23 11:32:03 -0700234 if artifact_type not in _TOOLCHAIN_ARTIFACT_HANDLERS:
235 logging.error('%s not understood', artifact_type)
236 return controller.RETURN_CODE_UNRECOVERABLE
Alex Kleincd03a5e2021-10-18 13:23:47 -0600237
LaMont Jones5d2edcb2019-12-23 11:32:03 -0700238 handler = _TOOLCHAIN_ARTIFACT_HANDLERS[artifact_type]
Alex Kleincd03a5e2021-10-18 13:23:47 -0600239 if not handler or not handler.bundle:
240 logging.warning('%s does not have a handler with a bundle function.',
241 artifact_type)
242 continue
243
244 artifacts = handler.bundle(handler.name, chroot, input_proto.sysroot.path,
245 input_proto.sysroot.build_target.name,
246 input_proto.output_dir, profile_info)
247 if not artifacts:
248 continue
249
250 # Filter out artifacts that do not exist or are empty.
251 usable_artifacts = []
252 for artifact in artifacts:
253 artifact_path = output_path / artifact
254 if not artifact_path.exists():
255 logging.warning('%s is not in the output directory.', artifact)
256 elif not artifact_path.stat().st_size:
257 logging.warning('%s is empty.', artifact)
258 else:
259 usable_artifacts.append(artifact)
260
261 if not usable_artifacts:
262 logging.warning('No usable artifacts for artifact type %s', artifact_type)
263 continue
264
265 # Add all usable artifacts.
266 art_info = output_proto.artifacts_info.add()
267 art_info.artifact_type = artifact_type
268 for artifact in usable_artifacts:
269 art_info.artifacts.add().path = artifact
LaMont Jonesb20b3d92019-11-23 11:47:48 -0700270
271
Tiancong Wangd5214132021-01-12 10:43:57 -0800272def _GetUpdatedFilesResponse(_input_proto, output_proto, _config):
273 """Add successful status to the faux response."""
274 file_info = output_proto.updated_files.add()
275 file_info.path = '/any/modified/file'
276 output_proto.commit_message = 'Commit message'
277
278
279@faux.empty_error
280@faux.success(_GetUpdatedFilesResponse)
281@validate.require('uploaded_artifacts')
282@validate.validation_complete
283def GetUpdatedFiles(input_proto, output_proto, _config):
284 """Use uploaded artifacts to update some updates in a chromeos checkout.
285
286 The function will call toolchain_util.GetUpdatedFiles using the type of
287 uploaded artifacts to make some changes in a checkout, and return the list
288 of change files together with commit message.
289 updated_artifacts: A list of UpdatedArtifacts type which contains a tuple
290 of artifact info and profile info.
291 Note: the actual creation of the commit is done by CI, not here.
292
293 Args:
294 input_proto (GetUpdatedFilesRequest): The input proto
295 output_proto (GetUpdatedFilesResponse): The output proto
296 _config (api_config.ApiConfig): The API call config.
297 """
298 commit_message = ''
299 for artifact in input_proto.uploaded_artifacts:
300 artifact_type = artifact.artifact_info.artifact_type
301 if artifact_type not in _TOOLCHAIN_COMMIT_HANDLERS:
302 logging.error('%s not understood', artifact_type)
303 return controller.RETURN_CODE_UNRECOVERABLE
304 artifact_name = _TOOLCHAIN_COMMIT_HANDLERS[artifact_type]
305 if artifact_name:
306 assert len(artifact.artifact_info.artifacts) == 1, (
307 'Only one file to update per each artifact')
308 updated_files, message = toolchain_util.GetUpdatedFiles(
309 artifact_name, artifact.artifact_info.artifacts[0].path,
310 _GetProfileInfoDict(artifact.profile_info))
311 for f in updated_files:
312 file_info = output_proto.updated_files.add()
313 file_info.path = f
314
315 commit_message += message + '\n'
316 output_proto.commit_message = commit_message
317 # No commit footer is added for now. Can add more here if needed
318
319
Ryan Beltran0df7fb02021-11-10 20:58:51 +0000320@faux.all_empty
321@validate.exists('sysroot.path')
322@validate.require('packages')
323@validate.validation_complete
324def EmergeWithLinting(input_proto, output_proto, _config):
325 """Emerge packages with linter features enabled and retrieves all findings.
326
327 Args:
328 input_proto (LinterRequest): The nput proto with package and sysroot info.
329 output_proto (LinterResponse): The output proto where findings are stored.
330 _config (api_config.ApiConfig): The API call config (unused).
331 """
332 packages = [
333 f'{package.category}/{package.package_name}'
334 for package in input_proto.packages]
Ryan Beltran7d191802021-11-24 00:08:17 +0000335
336 # rm any existing lints from clang tidy
337 osutils.RmDir(TIDY_BASE_DIR, ignore_missing=True, sudo=True)
338 osutils.SafeMakedirs(TIDY_BASE_DIR, 0o777, sudo=True)
339
Ryan Beltranf9a86f42022-04-13 20:58:18 +0000340 # rm any existing temporary portage files from builds of affected packages:
341 # this is required to make sure lints are always regenerated
342 for package in packages:
343 cache_files_dir = f'{input_proto.sysroot.path}/var/cache/portage/{package}'
344 osutils.RmDir(cache_files_dir, ignore_missing=True)
345
Ryan Beltran0df7fb02021-11-10 20:58:51 +0000346 emerge_cmd = chroot_util.GetEmergeCommand(input_proto.sysroot.path)
347 cros_build_lib.sudo_run(
348 emerge_cmd + packages,
349 preserve_env=True,
350 extra_env={
351 'ENABLE_RUST_CLIPPY': 1,
Ryan Beltran7d191802021-11-24 00:08:17 +0000352 'WITH_TIDY': 'tricium',
353 'FEATURES': 'noclean'
Ryan Beltran0df7fb02021-11-10 20:58:51 +0000354 }
355 )
356
Ryan Beltran923a1312021-07-30 00:28:13 +0000357 # FIXME(b/195056381): default git-repo should be replaced with logic in
358 # build_linters recipe to detect the repo path for applied patches.
Ryan Beltran7d191802021-11-24 00:08:17 +0000359 # As of 01-05-21 only platform2 is supported so this value works temporarily.
360 git_repo_path = '/mnt/host/source/src/platform2/'
361
362 linter_findings = _fetch_clippy_lints(git_repo_path)
363 linter_findings.extend(_fetch_tidy_lints(git_repo_path))
Ryan Beltran27bf3412022-04-13 20:27:37 +0000364
365 if input_proto.filter_modified:
366 linter_findings = _filter_linter_findings(linter_findings, git_repo_path)
367
Ryan Beltran7d191802021-11-24 00:08:17 +0000368 output_proto.findings.extend(linter_findings)
369
370
371LINTER_CODES = {
372 'clang_tidy': toolchain_pb2.LinterFinding.CLANG_TIDY,
373 'cargo_clippy': toolchain_pb2.LinterFinding.CARGO_CLIPPY
374}
375
376
377def _filter_linter_findings(findings, git_repo_path):
Ryan Beltran27bf3412022-04-13 20:27:37 +0000378 """Filters findings to keep only those concerning modified lines."""
Ryan Beltran7d191802021-11-24 00:08:17 +0000379 new_findings = []
380 new_lines = _get_added_lines({git_repo_path: 'HEAD'})
381 for finding in findings:
382 for loc in finding.locations:
383 for (addition_start, addition_end) in new_lines.get(loc.filepath, set()):
384 if addition_start <= loc.line_start < addition_end:
385 new_findings.append(finding)
386 return new_findings
387
388
389def _get_added_lines(git_repos):
390 """Parses the lines with additions from fit diff for the provided repos.
391
392 Args:
393 git_repos: a dictionary mapping repo paths to hashes for `git diff`
394
395 Returns:
396 A dictionary mapping modified filepaths to sets of tuples where each
397 tuple is a (start_line, end_line) pair noting which lines were modified.
398 Note that start_line is inclusive, and end_line is exclusive.
399 """
400 new_lines = {}
401 file_path_pattern = re.compile(r'^\+\+\+ b/(?P<file_path>.*)$')
402 position_pattern = re.compile(
403 r'^@@ -\d+(?:,\d+)? \+(?P<line_num>\d+)(?:,(?P<lines_added>\d+))? @@')
404 for git_repo, git_hash in git_repos.items():
405 cmd = f'git -C {git_repo} diff -U0 {git_hash}^...{git_hash}'
406 diff = cros_build_lib.run(cmd, capture_output=True, shell=True,
407 encoding='utf-8').output
408 current_file = ''
409 for line in diff.splitlines():
410 file_path_match = re.match(file_path_pattern, str(line))
411 if file_path_match:
412 current_file = file_path_match.group('file_path')
413 continue
414 position_match = re.match(position_pattern, str(line))
415 if position_match:
416 if current_file not in new_lines:
417 new_lines[current_file] = set()
418 line_num = int(position_match.group('line_num'))
419 line_count = position_match.group('lines_added')
420 line_count = int(line_count) if line_count is not None else 1
421 new_lines[current_file].add((line_num, line_num + line_count))
422 return new_lines
423
424
425def _fetch_clippy_lints(git_repo_path):
426 """Get lints created by Cargo Clippy during emerge."""
427 lints_dir = '/tmp/cargo_clippy'
Ryan Beltran923a1312021-07-30 00:28:13 +0000428 findings = tricium_cargo_clippy.parse_files(lints_dir, git_repo_path)
429 findings = tricium_cargo_clippy.filter_diagnostics(findings)
Ryan Beltran40e4ad12021-05-17 19:55:03 +0000430 findings_protos = []
431 for finding in findings:
432 location_protos = []
433 for location in finding.locations:
434 location_protos.append(
435 toolchain_pb2.LinterFindingLocation(
436 filepath=location.file_path,
437 line_start=location.line_start,
438 line_end=location.line_end
439 )
440 )
441 findings_protos.append(
442 toolchain_pb2.LinterFinding(
443 message=finding.message,
Ryan Beltran7d191802021-11-24 00:08:17 +0000444 locations=location_protos,
445 linter=LINTER_CODES['cargo_clippy']
Ryan Beltran40e4ad12021-05-17 19:55:03 +0000446 )
447 )
448 return findings_protos
449
450
Ryan Beltran7d191802021-11-24 00:08:17 +0000451def _fetch_tidy_lints(git_repo_path):
452 """Get lints created by Clang Tidy during emerge."""
453
454 def resolve_file_path(file_path):
455 # Remove git repo from prefix
456 file_path = re.sub('^' + git_repo_path, '/', str(file_path))
457 # Remove ebuild work directories from prefix
458 # Such as: "**/<package>-9999/work/<package>-9999/"
459 # or: "**/<package>-0.24.52-r9/work/<package>-0.24.52/"
460 return re.sub(r'(.*/)?([^/]+)-[^/]+/work/[^/]+/+', '', file_path)
461
462 lints = set()
463 for filename in os.listdir(TIDY_BASE_DIR):
464 if filename.endswith('.json'):
465 invocation_result = tricium_clang_tidy.parse_tidy_invocation(
466 TIDY_BASE_DIR / filename)
467 meta, complaints = invocation_result
468 assert not meta.exit_code, (
469 f'Invoking clang-tidy on {meta.lint_target} with flags '
470 f'{meta.invocation} exited with code {meta.exit_code}; '
471 f'output:\n{meta.stdstreams}')
472 lints.update(complaints)
473 return [
474 toolchain_pb2.LinterFinding(
475 message=lint.message,
476 locations=[
477 toolchain_pb2.LinterFindingLocation(
478 filepath=resolve_file_path(lint.file_path),
479 line_start=lint.line_number,
480 line_end=lint.line_number
481 )
482 ],
483 linter=LINTER_CODES['clang_tidy']
484 )
485 for lint in tricium_clang_tidy.filter_tidy_lints(None, None, lints)
486 ]