Jordan Bayles | 5d69441 | 2023-02-23 01:30:05 | [diff] [blame] | 1 | # Copyright 2018 The Chromium Authors |
Ryan Tseng | 85ec17e | 2018-09-07 00:10:05 | [diff] [blame] | 2 | # Use of this source code is governed by a BSD-style license that can be |
| 3 | # found in the LICENSE file. |
Jordan Bayles | 5d69441 | 2023-02-23 01:30:05 | [diff] [blame] | 4 | # pragma pylint: disable=invalid-name, import-error |
| 5 | """ |
| 6 | This file acts as a git pre-submission checker, and uses the support tooling |
| 7 | from depot_tools to check a variety of style and programming requirements. |
| 8 | """ |
Ryan Tseng | 85ec17e | 2018-09-07 00:10:05 | [diff] [blame] | 9 | |
Jordan Bayles | 5d69441 | 2023-02-23 01:30:05 | [diff] [blame] | 10 | from collections import namedtuple |
Jordan Bayles | e50a1d6 | 2020-11-30 20:31:47 | [diff] [blame] | 11 | import os |
mark a. foltz | 01490a4 | 2020-11-19 18:33:36 | [diff] [blame] | 12 | import re |
Jordan Bayles | e50a1d6 | 2020-11-30 20:31:47 | [diff] [blame] | 13 | import sys |
| 14 | |
Jordan Bayles | 5d69441 | 2023-02-23 01:30:05 | [diff] [blame] | 15 | |
Jordan Bayles | e50a1d6 | 2020-11-30 20:31:47 | [diff] [blame] | 16 | _REPO_PATH = os.path.dirname(os.path.realpath('__file__')) |
| 17 | _IMPORT_SUBFOLDERS = ['tools', os.path.join('buildtools', 'checkdeps')] |
| 18 | |
| 19 | # git-cl upload is not compatible with __init__.py based subfolder imports, so |
| 20 | # we extend the system path instead. |
mark a. foltz | 1cc7ba9 | 2023-11-15 18:23:16 | [diff] [blame] | 21 | # |
| 22 | # TODO(crbug.com/1502599): Migrate these modules to depot_tools and import from |
| 23 | # there. |
Jordan Bayles | e50a1d6 | 2020-11-30 20:31:47 | [diff] [blame] | 24 | sys.path.extend(os.path.join(_REPO_PATH, p) for p in _IMPORT_SUBFOLDERS) |
| 25 | |
mark a. foltz | 1cc7ba9 | 2023-11-15 18:23:16 | [diff] [blame] | 26 | |
Jordan Bayles | 5d69441 | 2023-02-23 01:30:05 | [diff] [blame] | 27 | from checkdeps import DepsChecker # pylint: disable=wrong-import-position |
| 28 | import licenses # pylint: disable=wrong-import-position |
Jordan Bayles | 6e279a0 | 2021-05-27 21:24:12 | [diff] [blame] | 29 | |
Jordan Bayles | e50a1d6 | 2020-11-30 20:31:47 | [diff] [blame] | 30 | def _CheckLicenses(input_api, output_api): |
| 31 | """Checks third party licenses and returns a list of violations.""" |
Jordan Bayles | 5d69441 | 2023-02-23 01:30:05 | [diff] [blame] | 32 | # NOTE: the licenses check is confused by the fact that we don't actually |
| 33 | # check ou the libraries in buildtools/third_party, so explicitly exclude |
| 34 | # that folder. See https://crbug.com/1215335 for more info. |
Jordan Bayles | 3adadc6 | 2024-02-01 06:29:41 | [diff] [blame] | 35 | licenses.PRUNE_PATHS.update([ |
| 36 | os.path.join('buildtools', 'third_party'), |
| 37 | os.path.join('third_party', 'libc++'), |
| 38 | os.path.join('third_party', 'libc++abi'), |
mark a. foltz | 105a2e5 | 2025-01-07 00:10:05 | [diff] [blame] | 39 | os.path.join('third_party', 'rust-toolchain'), |
| 40 | os.path.join('third_party', 'depot_tools') |
Jordan Bayles | 3adadc6 | 2024-02-01 06:29:41 | [diff] [blame] | 41 | ]) |
Jordan Bayles | 5d69441 | 2023-02-23 01:30:05 | [diff] [blame] | 42 | |
| 43 | if any(s.LocalPath().startswith('third_party') |
| 44 | for s in input_api.change.AffectedFiles()): |
| 45 | return [ |
| 46 | output_api.PresubmitError(v) |
| 47 | for v in licenses.ScanThirdPartyDirs() |
| 48 | ] |
| 49 | return [] |
btolsch | 9ba2371 | 2019-04-18 23:36:55 | [diff] [blame] | 50 | |
Jordan Bayles | e50a1d6 | 2020-11-30 20:31:47 | [diff] [blame] | 51 | |
| 52 | def _CheckDeps(input_api, output_api): |
| 53 | """Checks DEPS rules and returns a list of violations.""" |
| 54 | deps_checker = DepsChecker(input_api.PresubmitLocalPath()) |
| 55 | deps_checker.CheckDirectory(input_api.PresubmitLocalPath()) |
| 56 | deps_results = deps_checker.results_formatter.GetResults() |
| 57 | return [output_api.PresubmitError(v) for v in deps_results] |
btolsch | 9ba2371 | 2019-04-18 23:36:55 | [diff] [blame] | 58 | |
| 59 | |
Jordan Bayles | 5d69441 | 2023-02-23 01:30:05 | [diff] [blame] | 60 | # Arguments passed to methods by cpplint. |
| 61 | CpplintArgs = namedtuple("CpplintArgs", "filename clean_lines linenum error") |
Jordan Bayles | 963b0a6 | 2020-12-02 21:23:59 | [diff] [blame] | 62 | |
Jordan Bayles | 5d69441 | 2023-02-23 01:30:05 | [diff] [blame] | 63 | # A defined error to return to cpplint. |
| 64 | Error = namedtuple("Error", "type message") |
mark a. foltz | 01490a4 | 2020-11-19 18:33:36 | [diff] [blame] | 65 | |
| 66 | |
Jordan Bayles | 5d69441 | 2023-02-23 01:30:05 | [diff] [blame] | 67 | def _CheckNoRegexMatches(regex, cpplint_args, error, include_cpp_files=True): |
Jordan Bayles | 963b0a6 | 2020-12-02 21:23:59 | [diff] [blame] | 68 | """Checks that there are no matches for a specific regex. |
| 69 | |
| 70 | Args: |
Jordan Bayles | 5d69441 | 2023-02-23 01:30:05 | [diff] [blame] | 71 | regex: The regex to use for matching. |
| 72 | cpplint_args: The arguments passed to us by cpplint. |
| 73 | error: The error to return if we find an issue. |
| 74 | include_cpp_files: Whether C++ files should be checked. |
| 75 | """ |
| 76 | if not include_cpp_files and not cpplint_args.filename.endswith('.h'): |
Jordan Bayles | 963b0a6 | 2020-12-02 21:23:59 | [diff] [blame] | 77 | return |
| 78 | |
Jordan Bayles | 5d69441 | 2023-02-23 01:30:05 | [diff] [blame] | 79 | line = cpplint_args.clean_lines.elided[cpplint_args.linenum] |
Jordan Bayles | 963b0a6 | 2020-12-02 21:23:59 | [diff] [blame] | 80 | matched = regex.match(line) |
| 81 | if matched: |
Jordan Bayles | 5d69441 | 2023-02-23 01:30:05 | [diff] [blame] | 82 | cpplint_args.error( |
| 83 | cpplint_args.filename, cpplint_args.linenum, error.type, 4, |
| 84 | f'Error: {error.message} at {matched.group(0).strip()}') |
| 85 | |
| 86 | |
| 87 | # Matches OSP_CHECK(foo.is_value()) or OSP_DCHECK(foo.is_value()) |
| 88 | _RE_PATTERN_VALUE_CHECK = re.compile( |
| 89 | r'\s*OSP_D?CHECK\([^)]*\.is_value\(\)\);\s*') |
Jordan Bayles | 963b0a6 | 2020-12-02 21:23:59 | [diff] [blame] | 90 | |
| 91 | |
| 92 | def _CheckNoValueDchecks(filename, clean_lines, linenum, error): |
| 93 | """Checks that there are no OSP_DCHECK(foo.is_value()) instances. |
| 94 | |
| 95 | filename: The name of the current file. |
| 96 | clean_lines: A CleansedLines instance containing the file. |
| 97 | linenum: The number of the line to check. |
| 98 | error: The function to call with any errors found. |
| 99 | """ |
Jordan Bayles | 5d69441 | 2023-02-23 01:30:05 | [diff] [blame] | 100 | cpplint_args = CpplintArgs(filename, clean_lines, linenum, error) |
| 101 | error_to_return = Error('runtime/is_value_dchecks', |
| 102 | 'Unnecessary CHECK for ErrorOr::is_value()') |
| 103 | |
| 104 | _CheckNoRegexMatches(_RE_PATTERN_VALUE_CHECK, cpplint_args, |
| 105 | error_to_return) |
| 106 | |
| 107 | |
| 108 | # Matches Foo(Foo&&) when not followed by noexcept. |
| 109 | _RE_PATTERN_MOVE_WITHOUT_NOEXCEPT = re.compile( |
| 110 | r'\s*(?P<classname>\w+)\((?P=classname)&&[^)]*\)\s*(?!noexcept)\s*[{;=]') |
Jordan Bayles | 963b0a6 | 2020-12-02 21:23:59 | [diff] [blame] | 111 | |
| 112 | |
mark a. foltz | 01490a4 | 2020-11-19 18:33:36 | [diff] [blame] | 113 | def _CheckNoexceptOnMove(filename, clean_lines, linenum, error): |
Jordan Bayles | e50a1d6 | 2020-11-30 20:31:47 | [diff] [blame] | 114 | """Checks that move constructors are declared with 'noexcept'. |
mark a. foltz | 01490a4 | 2020-11-19 18:33:36 | [diff] [blame] | 115 | |
mark a. foltz | 01490a4 | 2020-11-19 18:33:36 | [diff] [blame] | 116 | filename: The name of the current file. |
| 117 | clean_lines: A CleansedLines instance containing the file. |
| 118 | linenum: The number of the line to check. |
| 119 | error: The function to call with any errors found. |
Jordan Bayles | 5d69441 | 2023-02-23 01:30:05 | [diff] [blame] | 120 | """ |
| 121 | cpplint_args = CpplintArgs(filename, clean_lines, linenum, error) |
| 122 | error_to_return = Error('runtime/noexcept', |
| 123 | 'Move constructor not declared \'noexcept\'') |
| 124 | |
Jordan Bayles | e50a1d6 | 2020-11-30 20:31:47 | [diff] [blame] | 125 | # We only check headers as noexcept is meaningful on declarations, not |
| 126 | # definitions. This may skip some definitions in .cc files though. |
Jordan Bayles | 5d69441 | 2023-02-23 01:30:05 | [diff] [blame] | 127 | _CheckNoRegexMatches(_RE_PATTERN_MOVE_WITHOUT_NOEXCEPT, cpplint_args, |
| 128 | error_to_return, False) |
| 129 | |
| 130 | |
Jordan Bayles | b926e15 | 2023-10-02 16:53:58 | [diff] [blame] | 131 | # Matches "namespace <foo> {". Since we only check one line at a time, we |
| 132 | # need to call this twice. |
| 133 | _RE_PATTERN_UNNESTED_NAMESPACE = re.compile( |
| 134 | r'namespace +\w+ +\{') |
| 135 | |
| 136 | |
| 137 | def _CheckUnnestedNamespaces(filename, clean_lines, linenum, error): |
| 138 | """Checks that nestable namespaces are nested. |
| 139 | |
| 140 | filename: The name of the current file. |
| 141 | clean_lines: A CleansedLines instance containing the file. |
| 142 | linenum: The number of the line to check. |
| 143 | error: The function to call with any errors found. |
| 144 | """ |
| 145 | |
| 146 | # If we have a match for a namespace on this line, check the next line for |
| 147 | # an nestable namespace declaration. |
| 148 | re = _RE_PATTERN_UNNESTED_NAMESPACE |
| 149 | if re.match(clean_lines.elided[linenum]): |
| 150 | cpplint_args = CpplintArgs(filename, clean_lines, linenum + 1, error) |
| 151 | error_to_return = Error('runtime/nested_namespace', |
| 152 | 'Please nest namespaces when possible.') |
| 153 | _CheckNoRegexMatches(re, cpplint_args, error_to_return) |
| 154 | |
| 155 | |
Jordan Bayles | 5d69441 | 2023-02-23 01:30:05 | [diff] [blame] | 156 | # Gives additional debug information whenever a linting error occurs. |
| 157 | _CPPLINT_VERBOSE_LEVEL = 4 |
mark a. foltz | 01490a4 | 2020-11-19 18:33:36 | [diff] [blame] | 158 | |
Jordan Bayles | b926e15 | 2023-10-02 16:53:58 | [diff] [blame] | 159 | |
mark a. foltz | 01490a4 | 2020-11-19 18:33:36 | [diff] [blame] | 160 | # - We disable c++11 header checks since Open Screen allows them. |
| 161 | # - We disable whitespace/braces because of various false positives. |
| 162 | # - There are some false positives with 'explicit' checks, but it's useful |
| 163 | # enough to keep. |
| 164 | # - We add a custom check for 'noexcept' usage. |
| 165 | def _CheckChangeLintsClean(input_api, output_api): |
Jordan Bayles | e50a1d6 | 2020-11-30 20:31:47 | [diff] [blame] | 166 | """Checks that all '.cc' and '.h' files pass cpplint.py.""" |
| 167 | cpplint = input_api.cpplint |
Jordan Bayles | 425c136 | 2021-05-27 21:33:11 | [diff] [blame] | 168 | # Directive that allows access to a protected member _XX of a client class. |
Jordan Bayles | e50a1d6 | 2020-11-30 20:31:47 | [diff] [blame] | 169 | # pylint: disable=protected-access |
| 170 | cpplint._cpplint_state.ResetErrorCounts() |
mark a. foltz | 01490a4 | 2020-11-19 18:33:36 | [diff] [blame] | 171 | |
Jordan Bayles | e50a1d6 | 2020-11-30 20:31:47 | [diff] [blame] | 172 | cpplint._SetFilters('-build/c++11,-whitespace/braces') |
| 173 | files = [ |
| 174 | f.AbsoluteLocalPath() for f in input_api.AffectedSourceFiles(None) |
| 175 | ] |
Jordan Bayles | 5d69441 | 2023-02-23 01:30:05 | [diff] [blame] | 176 | |
Jordan Bayles | e50a1d6 | 2020-11-30 20:31:47 | [diff] [blame] | 177 | for file_name in files: |
Jordan Bayles | b926e15 | 2023-10-02 16:53:58 | [diff] [blame] | 178 | cpplint.ProcessFile(file_name, _CPPLINT_VERBOSE_LEVEL, [ |
| 179 | _CheckNoexceptOnMove, _CheckNoValueDchecks, |
| 180 | _CheckUnnestedNamespaces |
| 181 | ]) |
mark a. foltz | 01490a4 | 2020-11-19 18:33:36 | [diff] [blame] | 182 | |
Jordan Bayles | e50a1d6 | 2020-11-30 20:31:47 | [diff] [blame] | 183 | if cpplint._cpplint_state.error_count: |
| 184 | if input_api.is_committing: |
| 185 | res_type = output_api.PresubmitError |
| 186 | else: |
| 187 | res_type = output_api.PresubmitPromptWarning |
| 188 | return [res_type('Changelist failed cpplint.py check.')] |
mark a. foltz | 01490a4 | 2020-11-19 18:33:36 | [diff] [blame] | 189 | |
Jordan Bayles | e50a1d6 | 2020-11-30 20:31:47 | [diff] [blame] | 190 | return [] |
mark a. foltz | 01490a4 | 2020-11-19 18:33:36 | [diff] [blame] | 191 | |
| 192 | |
Jordan Bayles | 4d9a1ed | 2023-02-22 19:26:01 | [diff] [blame] | 193 | def _CheckLuciCfgLint(input_api, output_api): |
| 194 | """Check that the luci configs pass the linter.""" |
| 195 | path = os.path.join('infra', 'config', 'global', 'main.star') |
Jordan Bayles | b926e15 | 2023-10-02 16:53:58 | [diff] [blame] | 196 | if not input_api.AffectedSourceFiles( |
| 197 | lambda f: os.path.samefile(f.AbsoluteLocalPath(), path)): |
Jordan Bayles | 4d9a1ed | 2023-02-22 19:26:01 | [diff] [blame] | 198 | return [] |
| 199 | |
| 200 | result = [] |
| 201 | result.extend(input_api.RunTests([input_api.Command( |
| 202 | 'lucicfg lint', |
| 203 | [ |
| 204 | 'lucicfg' if not input_api.is_windows else 'lucicfg.bat', 'lint', |
| 205 | path, '--log-level', 'debug' if input_api.verbose else 'warning' |
| 206 | ], |
| 207 | { |
| 208 | 'stderr': input_api.subprocess.STDOUT, |
| 209 | 'shell': input_api.is_windows, # to resolve *.bat |
| 210 | 'cwd': input_api.PresubmitLocalPath(), |
| 211 | }, |
| 212 | output_api.PresubmitError)])) |
| 213 | return result |
Jordan Bayles | 0a68246 | 2021-09-02 18:44:48 | [diff] [blame] | 214 | |
| 215 | |
btolsch | 333aecd | 2019-04-18 23:21:23 | [diff] [blame] | 216 | def _CommonChecks(input_api, output_api): |
Jordan Bayles | 5d69441 | 2023-02-23 01:30:05 | [diff] [blame] | 217 | """Performs a list of checks that should be used for both presubmission and |
| 218 | upload validation. |
| 219 | """ |
Jordan Bayles | e50a1d6 | 2020-11-30 20:31:47 | [diff] [blame] | 220 | # PanProjectChecks include: |
| 221 | # CheckLongLines (@ 80 cols) |
| 222 | # CheckChangeHasNoTabs |
| 223 | # CheckChangeHasNoStrayWhitespace |
Jordan Bayles | e50a1d6 | 2020-11-30 20:31:47 | [diff] [blame] | 224 | # CheckChangeWasUploaded (if committing) |
| 225 | # CheckChangeHasDescription |
| 226 | # CheckDoNotSubmitInDescription |
| 227 | # CheckDoNotSubmitInFiles |
Jordan Bayles | 5d69441 | 2023-02-23 01:30:05 | [diff] [blame] | 228 | # CheckLicenses |
Jordan Bayles | e50a1d6 | 2020-11-30 20:31:47 | [diff] [blame] | 229 | results = input_api.canned_checks.PanProjectChecks(input_api, |
| 230 | output_api, |
| 231 | owners_check=False) |
mark a. foltz | 4410e8e | 2020-05-08 00:10:17 | [diff] [blame] | 232 | |
Jordan Bayles | e50a1d6 | 2020-11-30 20:31:47 | [diff] [blame] | 233 | # No carriage return characters, files end with one EOL (\n). |
| 234 | results.extend( |
| 235 | input_api.canned_checks.CheckChangeHasNoCrAndHasOnlyOneEol( |
| 236 | input_api, output_api)) |
mark a. foltz | 4410e8e | 2020-05-08 00:10:17 | [diff] [blame] | 237 | |
Jordan Bayles | 425c136 | 2021-05-27 21:33:11 | [diff] [blame] | 238 | # Ensure code change is gender inclusive. |
Jordan Bayles | e50a1d6 | 2020-11-30 20:31:47 | [diff] [blame] | 239 | results.extend( |
| 240 | input_api.canned_checks.CheckGenderNeutral(input_api, output_api)) |
mark a. foltz | 4410e8e | 2020-05-08 00:10:17 | [diff] [blame] | 241 | |
Jordan Bayles | 425c136 | 2021-05-27 21:33:11 | [diff] [blame] | 242 | # Ensure code change to do items uses TODO(bug) or TODO(user) format. |
| 243 | # TODO(bug) is generally preferred. |
Jordan Bayles | e50a1d6 | 2020-11-30 20:31:47 | [diff] [blame] | 244 | results.extend( |
| 245 | input_api.canned_checks.CheckChangeTodoHasOwner(input_api, output_api)) |
mark a. foltz | 4410e8e | 2020-05-08 00:10:17 | [diff] [blame] | 246 | |
Jordan Bayles | 425c136 | 2021-05-27 21:33:11 | [diff] [blame] | 247 | # Ensure code change passes linter cleanly. |
Jordan Bayles | e50a1d6 | 2020-11-30 20:31:47 | [diff] [blame] | 248 | results.extend(_CheckChangeLintsClean(input_api, output_api)) |
mark a. foltz | 4410e8e | 2020-05-08 00:10:17 | [diff] [blame] | 249 | |
Jordan Bayles | 425c136 | 2021-05-27 21:33:11 | [diff] [blame] | 250 | # Ensure code change has already had clang-format ran. |
Jordan Bayles | e50a1d6 | 2020-11-30 20:31:47 | [diff] [blame] | 251 | results.extend( |
| 252 | input_api.canned_checks.CheckPatchFormatted(input_api, |
| 253 | output_api, |
| 254 | bypass_warnings=False)) |
mark a. foltz | 4410e8e | 2020-05-08 00:10:17 | [diff] [blame] | 255 | |
Jordan Bayles | 425c136 | 2021-05-27 21:33:11 | [diff] [blame] | 256 | # Ensure code change has had GN formatting ran. |
Jordan Bayles | e50a1d6 | 2020-11-30 20:31:47 | [diff] [blame] | 257 | results.extend( |
| 258 | input_api.canned_checks.CheckGNFormatted(input_api, output_api)) |
mark a. foltz | 4410e8e | 2020-05-08 00:10:17 | [diff] [blame] | 259 | |
Jordan Bayles | 425c136 | 2021-05-27 21:33:11 | [diff] [blame] | 260 | # Run buildtools/checkdeps on code change. |
Jordan Bayles | e50a1d6 | 2020-11-30 20:31:47 | [diff] [blame] | 261 | results.extend(_CheckDeps(input_api, output_api)) |
| 262 | |
Jordan Bayles | 4d9a1ed | 2023-02-22 19:26:01 | [diff] [blame] | 263 | # Ensure the LUCI configs pass the linter. |
| 264 | results.extend(_CheckLuciCfgLint(input_api, output_api)) |
| 265 | |
Jordan Bayles | 425c136 | 2021-05-27 21:33:11 | [diff] [blame] | 266 | # Run tools/licenses on code change. |
Jordan Bayles | 5d69441 | 2023-02-23 01:30:05 | [diff] [blame] | 267 | results.extend(_CheckLicenses(input_api, output_api)) |
Jordan Bayles | e50a1d6 | 2020-11-30 20:31:47 | [diff] [blame] | 268 | |
| 269 | return results |
btolsch | 333aecd | 2019-04-18 23:21:23 | [diff] [blame] | 270 | |
| 271 | |
Ryan Tseng | 85ec17e | 2018-09-07 00:10:05 | [diff] [blame] | 272 | def CheckChangeOnUpload(input_api, output_api): |
Jordan Bayles | 5d69441 | 2023-02-23 01:30:05 | [diff] [blame] | 273 | """Checks the changelist whenever there is an upload (`git cl upload`)""" |
Jordan Bayles | e50a1d6 | 2020-11-30 20:31:47 | [diff] [blame] | 274 | # We always run the OnCommit checks, as well as some additional checks. |
| 275 | results = CheckChangeOnCommit(input_api, output_api) |
Josip Sokcevic | 401815e | 2022-02-10 18:33:13 | [diff] [blame] | 276 | results.extend( |
| 277 | input_api.canned_checks.CheckChangedLUCIConfigs(input_api, output_api)) |
Jordan Bayles | e50a1d6 | 2020-11-30 20:31:47 | [diff] [blame] | 278 | return results |
btolsch | 333aecd | 2019-04-18 23:21:23 | [diff] [blame] | 279 | |
| 280 | |
| 281 | def CheckChangeOnCommit(input_api, output_api): |
Jordan Bayles | 5d69441 | 2023-02-23 01:30:05 | [diff] [blame] | 282 | """Checks the changelist whenever there is commit (`git cl commit`)""" |
Jordan Bayles | e50a1d6 | 2020-11-30 20:31:47 | [diff] [blame] | 283 | return _CommonChecks(input_api, output_api) |