blob: 51e1e014f2b8601748d1e49504ae1e34ed10fbe0 [file] [log] [blame]
Ryan Beltran1f2dd082022-04-25 18:42:32 +00001# Copyright 2022 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"""This script emerges packages and retrieves their lints.
6
7Currently support is provided for both general and differential linting of C++
8with Clang Tidy and Rust with Cargo Clippy for all packages within platform2.
9"""
10
11import json
12import sys
Ryan Beltrance85d0f2022-08-09 21:36:39 +000013from typing import List, Text
Ryan Beltran1f2dd082022-04-25 18:42:32 +000014
15from chromite.lib import build_target_lib
16from chromite.lib import commandline
17from chromite.lib import cros_build_lib
Ryan Beltranb2175862022-04-28 19:55:57 +000018from chromite.lib import portage_util
Ryan Beltrance85d0f2022-08-09 21:36:39 +000019from chromite.lib import terminal
Ryan Beltran5514eab2022-04-28 21:40:24 +000020from chromite.lib import workon_helper
Ryan Beltran1f2dd082022-04-25 18:42:32 +000021from chromite.lib.parser import package_info
22from chromite.service import toolchain
23from chromite.utils import file_util
24
25
Ryan Beltran5514eab2022-04-28 21:40:24 +000026def parse_packages(build_target: build_target_lib.BuildTarget,
Ryan Beltranb2175862022-04-28 19:55:57 +000027 packages: List[str]) -> List[package_info.PackageInfo]:
28 """Parse packages and insert the category if none is given.
29
30 Args:
Ryan Beltran5514eab2022-04-28 21:40:24 +000031 build_target: build_target to find ebuild for
Ryan Beltranb2175862022-04-28 19:55:57 +000032 packages: user input package names to parse
33
34 Returns:
35 A list of parsed PackageInfo objects
36 """
37 package_infos: List[package_info.PackageInfo] = []
38 for package in packages:
39 parsed = package_info.parse(package)
40 if not parsed.category:
41 # If a category is not specified, we can get it from the ebuild path.
Ryan Beltranb619a112022-06-22 21:14:58 +000042 if build_target.is_host():
43 ebuild_path = portage_util.FindEbuildForPackage(
44 package, build_target.root)
45 else:
46 ebuild_path = portage_util.FindEbuildForBoardPackage(
47 package, build_target.name, build_target.root)
Ryan Beltranb2175862022-04-28 19:55:57 +000048 ebuild_data = portage_util.EBuild(ebuild_path)
49 parsed = package_info.parse(ebuild_data.package)
50 package_infos.append(parsed)
51 return package_infos
52
53
Ryan Beltrance85d0f2022-08-09 21:36:39 +000054def format_lint(lint: toolchain.LinterFinding) -> Text:
55 """Formats a lint for human readable printing.
56
57 Example output:
58 [ClangTidy] In 'path/to/file.c' on line 36:
59 Also in 'path/to/file.c' on line 40:
60 Also in 'path/to/file.c' on lines 50-53:
61 You did something bad, don't do it.
62
63 Args:
64 lint: A linter finding from the toolchain service.
65
66 Returns:
67 A correctly formatted string ready to be displayed to the user.
68 """
69
70 color = terminal.Color(True)
71 lines = []
72 linter_prefix = color.Color(terminal.Color.YELLOW, f'[{lint.linter}]',
73 background_color=terminal.Color.BLACK)
74 for loc in lint.locations:
75 if not lines:
76 location_prefix = f'\n{linter_prefix} In'
77 else:
78 location_prefix = ' and in'
79 if loc.line_start != loc.line_end:
80 lines.append(
81 f"{location_prefix} '{loc.filepath}' "
82 f'lines {loc.line_start}-{loc.line_end}:')
83 else:
84 lines.append(
85 f"{location_prefix} '{loc.filepath}' line {loc.line_start}:")
86 message_lines = lint.message.split('\n')
87 for line in message_lines:
88 lines.append(f' {line}')
89 lines.append('')
90 return '\n'.join(lines)
91
92
Ryan Beltran1f2dd082022-04-25 18:42:32 +000093def get_arg_parser() -> commandline.ArgumentParser:
94 """Creates an argument parser for this script."""
95 default_board = cros_build_lib.GetDefaultBoard()
96 parser = commandline.ArgumentParser(description=__doc__)
Ryan Beltrandbd7b812022-06-08 23:36:16 +000097
98 board_group = parser.add_mutually_exclusive_group(required=not default_board)
99 board_group.add_argument(
Ryan Beltran1f2dd082022-04-25 18:42:32 +0000100 '-b',
101 '--board',
102 '--build-target',
103 dest='board',
104 default=default_board,
Ryan Beltran1f2dd082022-04-25 18:42:32 +0000105 help='The board to emerge packages for')
Ryan Beltrandbd7b812022-06-08 23:36:16 +0000106 board_group.add_argument(
Ryan Beltranb619a112022-06-22 21:14:58 +0000107 '--host',
108 action='store_true',
109 help='emerge for host instead of board.')
110 board_group.add_argument(
Ryan Beltrandbd7b812022-06-08 23:36:16 +0000111 '--fetch-only',
112 action='store_true',
113 help='Fetch lints from previous run without reseting or calling emerge.')
114
115 parser.add_argument(
116 '--differential',
117 action='store_true',
118 help='only lint lines touched by the last commit')
Ryan Beltran1f2dd082022-04-25 18:42:32 +0000119 parser.add_argument(
120 '-o',
121 '--output',
122 default=sys.stdout,
123 help='File to use instead of stdout.')
124 parser.add_argument(
Ryan Beltrance85d0f2022-08-09 21:36:39 +0000125 '--json',
126 action='store_true',
127 help='Output lints in JSON format.')
128 parser.add_argument(
Ryan Beltran1f2dd082022-04-25 18:42:32 +0000129 '--no-clippy',
130 dest='clippy',
131 action='store_false',
132 help='Disable cargo clippy linter.')
133 parser.add_argument(
134 '--no-tidy',
135 dest='tidy',
136 action='store_false',
137 help='Disable clang tidy linter.')
138 parser.add_argument(
Uwem Wilson05b28b52022-06-29 21:16:37 +0000139 '--no-golint',
140 dest='golint',
141 action='store_false',
142 help='Disable golint linter.')
143 parser.add_argument(
Ryan Beltrandbd7b812022-06-08 23:36:16 +0000144 'packages', nargs='*', help='package(s) to emerge and retrieve lints for')
Ryan Beltran1f2dd082022-04-25 18:42:32 +0000145 return parser
146
147
148def parse_args(argv: List[str]):
149 """Parses arguments in argv and returns the options."""
150 parser = get_arg_parser()
151 opts = parser.parse_args(argv)
152 opts.Freeze()
Ryan Beltrandbd7b812022-06-08 23:36:16 +0000153
154 # A package must be specified unless we are in fetch-only mode
155 if not(opts.fetch_only or opts.packages):
156 parser.error('Emerge requires specified package(s).')
157 if opts.fetch_only and opts.packages:
158 parser.error('Cannot specify packages for fetch-only mode.')
159
Ryan Beltran1f2dd082022-04-25 18:42:32 +0000160 return opts
161
162
163def main(argv: List[str]) -> None:
164 cros_build_lib.AssertInsideChroot()
165 opts = parse_args(argv)
166
Ryan Beltranb619a112022-06-22 21:14:58 +0000167 if opts.host:
168 # BuildTarget interprets None as host target
169 build_target = build_target_lib.BuildTarget(None)
170 else:
171 build_target = build_target_lib.BuildTarget(opts.board)
Ryan Beltran5514eab2022-04-28 21:40:24 +0000172 packages = parse_packages(build_target, opts.packages)
173 package_atoms = [x.atom for x in packages]
Ryan Beltran1f2dd082022-04-25 18:42:32 +0000174
Ryan Beltran5514eab2022-04-28 21:40:24 +0000175 with workon_helper.WorkonScope(build_target, package_atoms):
176 build_linter = toolchain.BuildLinter(packages, build_target.root,
177 opts.differential)
Ryan Beltrandbd7b812022-06-08 23:36:16 +0000178 if opts.fetch_only:
179 lints = build_linter.fetch_findings(
Uwem Wilson05b28b52022-06-29 21:16:37 +0000180 use_clippy=opts.clippy, use_tidy=opts.tidy, use_golint=opts.golint)
Ryan Beltrandbd7b812022-06-08 23:36:16 +0000181 else:
182 lints = build_linter.emerge_with_linting(
Uwem Wilson05b28b52022-06-29 21:16:37 +0000183 use_clippy=opts.clippy, use_tidy=opts.tidy, use_golint=opts.golint)
Ryan Beltran1f2dd082022-04-25 18:42:32 +0000184
Ryan Beltrance85d0f2022-08-09 21:36:39 +0000185 if opts.json:
186 formatted_output = json.dumps([lint._asdict() for lint in lints])
187 else:
188 formatted_output = '\n'.join(format_lint(lint) for lint in lints)
189
Ryan Beltran1f2dd082022-04-25 18:42:32 +0000190 with file_util.Open(opts.output, 'w') as output_file:
Ryan Beltrance85d0f2022-08-09 21:36:39 +0000191 output_file.write(formatted_output)
192 if not opts.json:
193 output_file.write(f'\nFound {len(lints)} lints.')
194 output_file.write('\n')