Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
PRESUBMIT.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2# Copyright (c) 2013 The Chromium Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6
7"""Top-level presubmit script for Skia.
8
9See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts
10for more details about the presubmit API built into gcl.
11"""
12
13import fnmatch
14import os
15import re
16import subprocess
17import sys
18import traceback
19
20
21RELEASE_NOTES_DIR = 'relnotes'
22RELEASE_NOTES_FILE_NAME = 'RELEASE_NOTES.md'
23RELEASE_NOTES_README = '//relnotes/README.md'
24
25GOLD_TRYBOT_URL = 'https://gold.skia.org/search?issue='
26
27SERVICE_ACCOUNT_SUFFIX = [
28 '@%s.iam.gserviceaccount.com' % project for project in [
29 'skia-buildbots.google.com', 'skia-swarming-bots', 'skia-public',
30 'skia-corp.google.com', 'chops-service-accounts']]
31
32USE_PYTHON3 = True
33
34
35def _CheckChangeHasEol(input_api, output_api, source_file_filter=None):
36 """Checks that files end with at least one \n (LF)."""
37 eof_files = []
38 for f in input_api.AffectedSourceFiles(source_file_filter):
39 contents = input_api.ReadFile(f, 'rb')
40 # Check that the file ends in at least one newline character.
41 if len(contents) > 1 and contents[-1:] != '\n':
42 eof_files.append(f.LocalPath())
43
44 if eof_files:
45 return [output_api.PresubmitPromptWarning(
46 'These files should end in a newline character:',
47 items=eof_files)]
48 return []
49
50
51def _JsonChecks(input_api, output_api):
52 """Run checks on any modified json files."""
53 failing_files = []
54 for affected_file in input_api.AffectedFiles(None):
55 affected_file_path = affected_file.LocalPath()
56 is_json = affected_file_path.endswith('.json')
57 is_metadata = (affected_file_path.startswith('site/') and
58 affected_file_path.endswith('/METADATA'))
59 if is_json or is_metadata:
60 try:
61 input_api.json.load(open(affected_file_path, 'r'))
62 except ValueError:
63 failing_files.append(affected_file_path)
64
65 results = []
66 if failing_files:
67 results.append(
68 output_api.PresubmitError(
69 'The following files contain invalid json:\n%s\n\n' %
70 '\n'.join(failing_files)))
71 return results
72
73
74def _IfDefChecks(input_api, output_api):
75 """Ensures if/ifdef are not before includes. See skbug/3362 for details."""
76 comment_block_start_pattern = re.compile('^\s*\/\*.*$')
77 comment_block_middle_pattern = re.compile('^\s+\*.*')
78 comment_block_end_pattern = re.compile('^\s+\*\/.*$')
79 single_line_comment_pattern = re.compile('^\s*//.*$')
80 def is_comment(line):
81 return (comment_block_start_pattern.match(line) or
82 comment_block_middle_pattern.match(line) or
83 comment_block_end_pattern.match(line) or
84 single_line_comment_pattern.match(line))
85
86 empty_line_pattern = re.compile('^\s*$')
87 def is_empty_line(line):
88 return empty_line_pattern.match(line)
89
90 failing_files = []
91 for affected_file in input_api.AffectedSourceFiles(None):
92 affected_file_path = affected_file.LocalPath()
93 if affected_file_path.endswith('.cpp') or affected_file_path.endswith('.h'):
94 f = open(affected_file_path)
95 for line in f:
96 if is_comment(line) or is_empty_line(line):
97 continue
98 # The below will be the first real line after comments and newlines.
99 if line.startswith('#if 0 '):
100 pass
101 elif line.startswith('#if ') or line.startswith('#ifdef '):
102 failing_files.append(affected_file_path)
103 break
104
105 results = []
106 if failing_files:
107 results.append(
108 output_api.PresubmitError(
109 'The following files have #if or #ifdef before includes:\n%s\n\n'
110 'See https://bug.skia.org/3362 for why this should be fixed.' %
111 '\n'.join(failing_files)))
112 return results
113
114
115def _CopyrightChecks(input_api, output_api, source_file_filter=None):
116 results = []
117 year_pattern = r'\d{4}'
118 year_range_pattern = r'%s(-%s)?' % (year_pattern, year_pattern)
119 years_pattern = r'%s(,%s)*,?' % (year_range_pattern, year_range_pattern)
120 copyright_pattern = (
121 r'Copyright (\‍([cC]\‍) )?%s \w+' % years_pattern)
122
123 for affected_file in input_api.AffectedSourceFiles(source_file_filter):
124 if ('third_party/' in affected_file.LocalPath() or
125 'tests/sksl/' in affected_file.LocalPath() or
126 'bazel/rbe/' in affected_file.LocalPath() or
127 'bazel/external/' in affected_file.LocalPath() or
128 'bazel/exporter/interfaces/mocks/' in affected_file.LocalPath()):
129 continue
130 contents = input_api.ReadFile(affected_file, 'rb')
131 if not re.search(copyright_pattern, contents):
132 results.append(output_api.PresubmitError(
133 '%s is missing a correct copyright header.' % affected_file))
134 return results
135
136
137def _InfraTests(input_api, output_api):
138 """Run the infra tests."""
139 results = []
140 if not any(f.LocalPath().startswith('infra')
141 for f in input_api.AffectedFiles()):
142 return results
143
144 cmd = ['python3', os.path.join('infra', 'bots', 'infra_tests.py')]
145 try:
146 subprocess.check_output(cmd)
147 except subprocess.CalledProcessError as e:
148 results.append(output_api.PresubmitError(
149 '`%s` failed:\n%s' % (' '.join(cmd), e.output)))
150 return results
151
152
153def _CheckGNFormatted(input_api, output_api):
154 """Make sure any .gn files we're changing have been formatted."""
155 files = []
156 for f in input_api.AffectedFiles(include_deletes=False):
157 if (f.LocalPath().endswith('.gn') or
158 f.LocalPath().endswith('.gni')):
159 files.append(f)
160 if not files:
161 return []
162
163 cmd = ['python3', os.path.join('bin', 'fetch-gn')]
164 try:
165 subprocess.check_output(cmd)
166 except subprocess.CalledProcessError as e:
167 return [output_api.PresubmitError(
168 '`%s` failed:\n%s' % (' '.join(cmd), e.output))]
169
170 results = []
171 for f in files:
172 gn = 'gn.exe' if 'win32' in sys.platform else 'gn'
173 gn = os.path.join(input_api.PresubmitLocalPath(), 'bin', gn)
174 cmd = [gn, 'format', '--dry-run', f.LocalPath()]
175 try:
176 subprocess.check_output(cmd)
177 except subprocess.CalledProcessError:
178 fix = 'bin/gn format ' + f.LocalPath()
179 results.append(output_api.PresubmitError(
180 '`%s` failed, try\n\t%s' % (' '.join(cmd), fix)))
181 return results
182
183
184def _CheckGitConflictMarkers(input_api, output_api):
185 pattern = input_api.re.compile('^(?:<<<<<<<|>>>>>>>) |^=======$')
186 results = []
187 for f in input_api.AffectedFiles():
188 for line_num, line in f.ChangedContents():
189 if f.LocalPath().endswith('.md'):
190 # First-level headers in markdown look a lot like version control
191 # conflict markers. http://daringfireball.net/projects/markdown/basics
192 continue
193 if pattern.match(line):
194 results.append(
195 output_api.PresubmitError(
196 'Git conflict markers found in %s:%d %s' % (
197 f.LocalPath(), line_num, line)))
198 return results
199
200
201def _CheckIncludesFormatted(input_api, output_api):
202 """Make sure #includes in files we're changing have been formatted."""
203 files = [str(f) for f in input_api.AffectedFiles() if f.Action() != 'D']
204 cmd = ['python3',
205 'tools/rewrite_includes.py',
206 '--dry-run'] + files
207 if 0 != subprocess.call(cmd):
208 return [output_api.PresubmitError('`%s` failed' % ' '.join(cmd))]
209 return []
210
211
213 def __init__(self, output_api):
214 self.output_api = output_api
215 self.old_warning = None
216 def __enter__(self):
217 self.old_warning = self.output_api.PresubmitPromptWarning
218 self.output_api.PresubmitPromptWarning = self.output_api.PresubmitError
219 return self.output_api
220 def __exit__(self, ex_type, ex_value, ex_traceback):
221 self.output_api.PresubmitPromptWarning = self.old_warning
222
223
224def _RegenerateAllExamplesCPP(input_api, output_api):
225 """Regenerates all_examples.cpp if an example was added or deleted."""
226 if not any(f.LocalPath().startswith('docs/examples/')
227 for f in input_api.AffectedFiles()):
228 return []
229 command_str = 'tools/fiddle/make_all_examples_cpp.py'
230 cmd = ['python3', command_str]
231 if 0 != subprocess.call(cmd):
232 return [output_api.PresubmitError('`%s` failed' % ' '.join(cmd))]
233
234 results = []
235 git_diff_output = input_api.subprocess.check_output(
236 ['git', 'diff', '--no-ext-diff'])
237 if git_diff_output:
238 results += [output_api.PresubmitError(
239 'Diffs found after running "%s":\n\n%s\n'
240 'Please commit or discard the above changes.' % (
241 command_str,
242 git_diff_output,
243 )
244 )]
245 return results
246
247
248def _CheckExamplesForPrivateAPIs(input_api, output_api):
249 """We only want our checked-in examples (aka fiddles) to show public API."""
250 banned_includes = [
251 input_api.re.compile(r'#\s*include\s+("src/.*)'),
252 input_api.re.compile(r'#\s*include\s+("include/private/.*)'),
253 ]
254 file_filter = lambda x: (x.LocalPath().startswith('docs/examples/'))
255 errors = []
256 for affected_file in input_api.AffectedSourceFiles(file_filter):
257 affected_filepath = affected_file.LocalPath()
258 for (line_num, line) in affected_file.ChangedContents():
259 for re in banned_includes:
260 match = re.search(line)
261 if match:
262 errors.append('%s:%s: Fiddles should not use private/internal API like %s.' % (
263 affected_filepath, line_num, match.group(1)))
264
265 if errors:
266 return [output_api.PresubmitError('\n'.join(errors))]
267 return []
268
269
270def _CheckGeneratedBazelBUILDFiles(input_api, output_api):
271 if 'win32' in sys.platform:
272 # TODO(crbug.com/skia/12541): Remove when Bazel builds work on Windows.
273 # Note: `make` is not installed on Windows by default.
274 return []
275 if 'darwin' in sys.platform:
276 # This takes too long on Mac with default settings. Probably due to sandboxing.
277 return []
278 for affected_file in input_api.AffectedFiles(include_deletes=True):
279 affected_file_path = affected_file.LocalPath()
280 if (affected_file_path.endswith('.go') or
281 affected_file_path.endswith('BUILD.bazel')):
282 return _RunCommandAndCheckGitDiff(output_api,
283 ['make', '-C', 'bazel', 'generate_go'])
284 return [] # No modified Go source files.
285
286
287def _CheckBazelBUILDFiles(input_api, output_api):
288 """Makes sure our BUILD.bazel files are compatible with G3."""
289 results = []
290 for affected_file in input_api.AffectedFiles(include_deletes=False):
291 affected_file_path = affected_file.LocalPath()
292 is_bazel = affected_file_path.endswith('BUILD.bazel')
293 # This list lines up with the one in autoroller_lib.py (see G3).
294 excluded_paths = ["infra/", "bazel/rbe/", "bazel/external/", "bazel/common_config_settings/",
295 "modules/canvaskit/go/", "experimental/", "bazel/platform", "third_party/",
296 "tests/", "resources/", "bazel/deps_parser/", "bazel/exporter_tool/",
297 "tools/gpu/gl/interface/", "bazel/utils/", "include/config/",
298 "bench/", "example/external_client/"]
299 is_excluded = any(affected_file_path.startswith(n) for n in excluded_paths)
300 if is_bazel and not is_excluded:
301 with open(affected_file_path, 'r') as file:
302 contents = file.read()
303 if 'exports_files_legacy(' not in contents:
304 results.append(output_api.PresubmitError(
305 ('%s needs to call exports_files_legacy() to support legacy G3 ' +
306 'rules.\nPut this near the top of the file, beneath ' +
307 'licenses(["notice"]).') % affected_file_path
308 ))
309 if 'licenses(["notice"])' not in contents:
310 results.append(output_api.PresubmitError(
311 ('%s needs to have\nlicenses(["notice"])\nimmediately after ' +
312 'the load() calls to comply with G3 policies.') % affected_file_path
313 ))
314 if 'cc_library(' in contents and '"skia_cc_library"' not in contents:
315 results.append(output_api.PresubmitError(
316 ('%s needs to load skia_cc_library from macros.bzl instead of using the ' +
317 'native one. This allows us to build differently for G3.\n' +
318 'Add "skia_cc_library" to load("//bazel:macros.bzl", ...)')
319 % affected_file_path
320 ))
321 if 'default_applicable_licenses' not in contents:
322 # See https://opensource.google/documentation/reference/thirdparty/new_license_rules
323 results.append(output_api.PresubmitError(
324 ('%s needs to have\npackage(default_applicable_licenses = ["//:license"])\n'+
325 'to comply with G3 policies') % affected_file_path
326 ))
327 return results
328
329
330def _CheckPublicBzl(input_api, output_api):
331 """Reminds devs to add/remove files from public.bzl."""
332 results = []
333 public_bzl = ''
334 with open('public.bzl', 'r', encoding='utf-8') as f:
335 public_bzl = f.read().strip()
336 for affected_file in input_api.AffectedFiles(include_deletes=True):
337 # action is A for newly added, D for newly deleted, M for modified
338 action = affected_file.Action()
339 affected_file_path = affected_file.LocalPath()
340 if ((affected_file_path.startswith("include") or affected_file_path.startswith("src")) and
341 (affected_file_path.endswith(".cpp") or affected_file_path.endswith(".h") or
342 affected_file_path.endswith(".mm"))):
343 affected_file_path = '"' + affected_file_path + '"'
344 if action == "D" and affected_file_path in public_bzl:
345 results.append(output_api.PresubmitError(
346 "Need to delete %s from public.bzl (or rename it)" % affected_file_path))
347 elif action == "A" and affected_file_path not in public_bzl:
348 results.append(output_api.PresubmitPromptWarning(
349 "You may need to add %s to public.bzl" % affected_file_path))
350 return results
351
352
353def _RunCommandAndCheckGitDiff(output_api, command):
354 """Run an arbitrary command. Fail if it produces any diffs."""
355 command_str = ' '.join(command)
356 results = []
357
358 try:
359 output = subprocess.check_output(
360 command,
361 stderr=subprocess.STDOUT, encoding='utf-8')
362 except subprocess.CalledProcessError as e:
363 results += [output_api.PresubmitError(
364 'Command "%s" returned non-zero exit code %d. Output: \n\n%s' % (
365 command_str,
366 e.returncode,
367 e.output,
368 )
369 )]
370
371 git_diff_output = subprocess.check_output(
372 ['git', 'diff', '--no-ext-diff'], encoding='utf-8')
373 if git_diff_output:
374 results += [output_api.PresubmitError(
375 'Diffs found after running "%s":\n\n%s\n'
376 'Please commit or discard the above changes.' % (
377 command_str,
378 git_diff_output,
379 )
380 )]
381
382 return results
383
384
385def _CheckGNIGenerated(input_api, output_api):
386 """Ensures that the generated *.gni files are current.
387
388 The Bazel project files are authoritative and some *.gni files are
389 generated from them using the exporter_tool. This check ensures they
390 are still current.
391 """
392 if 'win32' in sys.platform:
393 # TODO(crbug.com/skia/12541): Remove when Bazel builds work on Windows.
394 # Note: `make` is not installed on Windows by default.
395 return [
396 output_api.PresubmitPromptWarning(
397 'Skipping Bazel=>GNI export check on Windows (unsupported platform).'
398 )
399 ]
400 if 'darwin' in sys.platform:
401 # This takes too long on Mac with default settings. Probably due to sandboxing.
402 return []
403 should_run = False
404 for affected_file in input_api.AffectedFiles(include_deletes=True):
405 affected_file_path = affected_file.LocalPath()
406 if affected_file_path.endswith('BUILD.bazel') or affected_file_path.endswith('.gni'):
407 should_run = True
408 # Generate GNI files and verify no changes.
409 if should_run:
410 return _RunCommandAndCheckGitDiff(output_api,
411 ['make', '-C', 'bazel', 'generate_gni'])
412
413 # No Bazel build files changed.
414 return []
415
416
417def _CheckBuildifier(input_api, output_api):
418 """Runs Buildifier and fails on linting errors, or if it produces any diffs.
419
420 This check only runs if the affected files include any WORKSPACE, BUILD,
421 BUILD.bazel or *.bzl files.
422 """
423 files = []
424 # Please keep the below exclude patterns in sync with those in the //:buildifier rule definition.
425 for affected_file in input_api.AffectedFiles(include_deletes=False):
426 affected_file_path = affected_file.LocalPath()
427 if affected_file_path.endswith('BUILD.bazel') or affected_file_path.endswith('.bzl'):
428 if not affected_file_path.endswith('public.bzl') and \
429 not affected_file_path.endswith('go_repositories.bzl') and \
430 not "bazel/rbe/gce_linux/" in affected_file_path and \
431 not affected_file_path.startswith("third_party/externals/") and \
432 not "node_modules/" in affected_file_path: # Skip generated files.
433 files.append(affected_file_path)
434 if not files:
435 return []
436 try:
437 subprocess.check_output(
438 ['buildifier', '--version'],
439 stderr=subprocess.STDOUT)
440 except:
441 return [output_api.PresubmitNotifyResult(
442 'Skipping buildifier check because it is not on PATH. \n' +
443 'You can download it from https://github.com/bazelbuild/buildtools/releases')]
444
446 # One can change --lint=warn to --lint=fix to have things automatically fixed where possible.
447 # However, --lint=fix will not cause a presubmit error if there are things that require
448 # manual intervention, so we leave --lint=warn on by default.
449 #
450 # Please keep the below arguments in sync with those in the //:buildifier rule definition.
451 output_api, [
452 'buildifier',
453 '--mode=fix',
454 '--lint=warn',
455 '--warnings',
456 ','.join([
457 '-native-android',
458 '-native-cc',
459 '-native-py',
460 ])
461 ] + files)
462
463
464def _CheckBannedAPIs(input_api, output_api):
465 """Check source code for functions and packages that should not be used."""
466
467 # A list of tuples of a regex to match an API and a suggested replacement for
468 # that API. There is an optional third parameter for files which *can* use this
469 # API without warning.
470 banned_replacements = [
471 (r'std::stof\‍(', 'std::strtof(), which does not throw'),
472 (r'std::stod\‍(', 'std::strtod(), which does not throw'),
473 (r'std::stold\‍(', 'std::strtold(), which does not throw'),
474 ]
475
476 # These defines are either there or not, and using them with just an #if is a
477 # subtle, frustrating bug.
478 existence_defines = ['SK_GANESH', 'SK_GRAPHITE', 'SK_GL', 'SK_VULKAN', 'SK_DAWN', 'SK_METAL',
479 'SK_DIRECT3D', 'SK_DEBUG', 'GR_TEST_UTILS', 'GRAPHITE_TEST_UTILS']
480 for d in existence_defines:
481 banned_replacements.append(('#if {}'.format(d),
482 '#if defined({})'.format(d)))
483 compiled_replacements = []
484 for rep in banned_replacements:
485 exceptions = []
486 if len(rep) == 3:
487 (re, replacement, exceptions) = rep
488 else:
489 (re, replacement) = rep
490
491 compiled_re = input_api.re.compile(re)
492 compiled_exceptions = [input_api.re.compile(exc) for exc in exceptions]
493 compiled_replacements.append(
494 (compiled_re, replacement, compiled_exceptions))
495
496 errors = []
497 file_filter = lambda x: (x.LocalPath().endswith('.h') or
498 x.LocalPath().endswith('.cpp') or
499 x.LocalPath().endswith('.cc') or
500 x.LocalPath().endswith('.m') or
501 x.LocalPath().endswith('.mm'))
502 for affected_file in input_api.AffectedSourceFiles(file_filter):
503 affected_filepath = affected_file.LocalPath()
504 for (line_num, line) in affected_file.ChangedContents():
505 for (re, replacement, exceptions) in compiled_replacements:
506 match = re.search(line)
507 if match:
508 for exc in exceptions:
509 if exc.search(affected_filepath):
510 break
511 else:
512 errors.append('%s:%s: Instead of %s, please use %s.' % (
513 affected_filepath, line_num, match.group(), replacement))
514
515 if errors:
516 return [output_api.PresubmitError('\n'.join(errors))]
517
518 return []
519
520
521def _CheckDEPS(input_api, output_api):
522 """If DEPS was modified, run the deps_parser to update bazel/deps.bzl"""
523 needs_running = False
524 for affected_file in input_api.AffectedFiles(include_deletes=False):
525 affected_file_path = affected_file.LocalPath()
526 if affected_file_path.endswith('DEPS') or affected_file_path.endswith('deps.bzl'):
527 needs_running = True
528 break
529 if not needs_running:
530 return []
531 try:
532 subprocess.check_output(
533 ['bazelisk', '--version'],
534 stderr=subprocess.STDOUT)
535 except:
536 return [output_api.PresubmitNotifyResult(
537 'Skipping DEPS check because bazelisk is not on PATH. \n' +
538 'You can download it from https://github.com/bazelbuild/bazelisk/releases/tag/v1.14.0')]
539
541 output_api, ['bazelisk', 'run', '//bazel/deps_parser'])
542
543
544def _CommonChecks(input_api, output_api):
545 """Presubmit checks common to upload and commit."""
546 results = []
547 sources = lambda x: (x.LocalPath().endswith('.h') or
548 x.LocalPath().endswith('.py') or
549 x.LocalPath().endswith('.sh') or
550 x.LocalPath().endswith('.m') or
551 x.LocalPath().endswith('.mm') or
552 x.LocalPath().endswith('.go') or
553 x.LocalPath().endswith('.c') or
554 x.LocalPath().endswith('.cc') or
555 x.LocalPath().endswith('.cpp'))
556 results.extend(_CheckChangeHasEol(
557 input_api, output_api, source_file_filter=sources))
558 with _WarningsAsErrors(output_api):
559 results.extend(input_api.canned_checks.CheckChangeHasNoCR(
560 input_api, output_api, source_file_filter=sources))
561 results.extend(input_api.canned_checks.CheckChangeHasNoStrayWhitespace(
562 input_api, output_api, source_file_filter=sources))
563 results.extend(_JsonChecks(input_api, output_api))
564 results.extend(_IfDefChecks(input_api, output_api))
565 results.extend(_CopyrightChecks(input_api, output_api,
566 source_file_filter=sources))
567 results.extend(_CheckIncludesFormatted(input_api, output_api))
568 results.extend(_CheckGNFormatted(input_api, output_api))
569 results.extend(_CheckGitConflictMarkers(input_api, output_api))
570 results.extend(_RegenerateAllExamplesCPP(input_api, output_api))
571 results.extend(_CheckExamplesForPrivateAPIs(input_api, output_api))
572 results.extend(_CheckBazelBUILDFiles(input_api, output_api))
573 results.extend(_CheckBannedAPIs(input_api, output_api))
574 return results
575
576
577def CheckChangeOnUpload(input_api, output_api):
578 """Presubmit checks for the change on upload."""
579 results = []
580 results.extend(_CommonChecks(input_api, output_api))
581 # Run on upload, not commit, since the presubmit bot apparently doesn't have
582 # coverage or Go installed.
583 results.extend(_InfraTests(input_api, output_api))
584 results.extend(_CheckTopReleaseNotesChanged(input_api, output_api))
585 results.extend(_CheckReleaseNotesForPublicAPI(input_api, output_api))
586 # Only check public.bzl on upload because new files are likely to be a source
587 # of false positives and we don't want to unnecessarily block commits.
588 results.extend(_CheckPublicBzl(input_api, output_api))
589 # Buildifier might not be on the CI machines.
590 results.extend(_CheckBuildifier(input_api, output_api))
591 # We don't want this to block the CQ (for now).
592 results.extend(_CheckDEPS(input_api, output_api))
593 # Bazelisk is not yet included in the Presubmit job.
594 results.extend(_CheckGeneratedBazelBUILDFiles(input_api, output_api))
595 results.extend(_CheckGNIGenerated(input_api, output_api))
596 return results
597
598
599class CodeReview(object):
600 """Abstracts which codereview tool is used for the specified issue."""
601
602 def __init__(self, input_api):
603 self._issue = input_api.change.issue
604 self._gerrit = input_api.gerrit
605
606 def GetOwnerEmail(self):
607 return self._gerrit.GetChangeOwner(self._issue)
608
609 def GetSubject(self):
610 return self._gerrit.GetChangeInfo(self._issue)['subject']
611
612 def GetDescription(self):
613 return self._gerrit.GetChangeDescription(self._issue)
614
615 def GetReviewers(self):
616 code_review_label = (
617 self._gerrit.GetChangeInfo(self._issue)['labels']['Code-Review'])
618 return [r['email'] for r in code_review_label.get('all', [])]
619
620 def GetApprovers(self):
621 approvers = []
622 code_review_label = (
623 self._gerrit.GetChangeInfo(self._issue)['labels']['Code-Review'])
624 for m in code_review_label.get('all', []):
625 if m.get("value") == 1:
626 approvers.append(m["email"])
627 return approvers
628
629
630def _CheckReleaseNotesForPublicAPI(input_api, output_api):
631 """Checks to see if a release notes file is added or edited with public API changes."""
632 results = []
633 public_api_changed = False
634 release_file_changed = False
635 for affected_file in input_api.AffectedFiles():
636 affected_file_path = affected_file.LocalPath()
637 file_path, file_ext = os.path.splitext(affected_file_path)
638 # We only care about files that end in .h and are under the top-level
639 # include dir, but not include/private.
640 if (file_ext == '.h' and
641 file_path.split(os.path.sep)[0] == 'include' and
642 'private' not in file_path):
643 public_api_changed = True
644 elif os.path.dirname(file_path) == RELEASE_NOTES_DIR:
645 release_file_changed = True
646
647 if public_api_changed and not release_file_changed:
648 results.append(output_api.PresubmitPromptWarning(
649 'If this change affects a client API, please add a new summary '
650 'file in the %s directory. More information can be found in '
651 '%s.' % (RELEASE_NOTES_DIR, RELEASE_NOTES_README)))
652 return results
653
654
655def _CheckTopReleaseNotesChanged(input_api, output_api):
656 """Warns if the top level release notes file was changed.
657
658 The top level file is now auto-edited, and new release notes should
659 be added to the RELEASE_NOTES_DIR directory"""
660 results = []
661 top_relnotes_changed = False
662 release_file_changed = False
663 for affected_file in input_api.AffectedFiles():
664 affected_file_path = affected_file.LocalPath()
665 file_path, file_ext = os.path.splitext(affected_file_path)
666 if affected_file_path == RELEASE_NOTES_FILE_NAME:
667 top_relnotes_changed = True
668 elif os.path.dirname(file_path) == RELEASE_NOTES_DIR:
669 release_file_changed = True
670 # When relnotes_util is run it will modify RELEASE_NOTES_FILE_NAME
671 # and delete the individual note files in RELEASE_NOTES_DIR.
672 # So, if both paths are modified do not emit a warning.
673 if top_relnotes_changed and not release_file_changed:
674 results.append(output_api.PresubmitPromptWarning(
675 'Do not edit %s directly. %s is automatically edited during the '
676 'release process. Release notes should be added as new files in '
677 'the %s directory. More information can be found in %s.' % (RELEASE_NOTES_FILE_NAME,
678 RELEASE_NOTES_FILE_NAME,
679 RELEASE_NOTES_DIR,
680 RELEASE_NOTES_README)))
681 return results
682
683
684def PostUploadHook(gerrit, change, output_api):
685 """git cl upload will call this hook after the issue is created/modified.
686
687 This hook does the following:
688 * Adds a link to preview docs changes if there are any docs changes in the CL.
689 * Adds 'No-Try: true' if the CL contains only docs changes.
690 """
691 if not change.issue:
692 return []
693
694 # Skip PostUploadHooks for all auto-commit service account bots. New
695 # patchsets (caused due to PostUploadHooks) invalidates the CQ+2 vote from
696 # the "--use-commit-queue" flag to "git cl upload".
697 for suffix in SERVICE_ACCOUNT_SUFFIX:
698 if change.author_email.endswith(suffix):
699 return []
700
701 results = []
702 at_least_one_docs_change = False
703 all_docs_changes = True
704 for affected_file in change.AffectedFiles():
705 affected_file_path = affected_file.LocalPath()
706 file_path, _ = os.path.splitext(affected_file_path)
707 if 'site' == file_path.split(os.path.sep)[0]:
708 at_least_one_docs_change = True
709 else:
710 all_docs_changes = False
711 if at_least_one_docs_change and not all_docs_changes:
712 break
713
714 footers = change.GitFootersFromDescription()
715 description_changed = False
716
717 # If the change includes only doc changes then add No-Try: true in the
718 # CL's description if it does not exist yet.
719 if all_docs_changes and 'true' not in footers.get('No-Try', []):
720 description_changed = True
721 change.AddDescriptionFooter('No-Try', 'true')
722 results.append(
723 output_api.PresubmitNotifyResult(
724 'This change has only doc changes. Automatically added '
725 '\'No-Try: true\' to the CL\'s description'))
726
727 # If the description has changed update it.
728 if description_changed:
729 gerrit.UpdateDescription(
730 change.FullDescriptionText(), change.issue)
731
732 return results
733
734
735def CheckChangeOnCommit(input_api, output_api):
736 """Presubmit checks for the change on commit."""
737 results = []
738 results.extend(_CommonChecks(input_api, output_api))
739 # Checks for the presence of 'DO NOT''SUBMIT' in CL description and in
740 # content of files.
741 results.extend(
742 input_api.canned_checks.CheckDoNotSubmit(input_api, output_api))
743 return results
__init__(self, input_api)
Definition PRESUBMIT.py:602
__exit__(self, ex_type, ex_value, ex_traceback)
Definition PRESUBMIT.py:220
__init__(self, output_api)
Definition PRESUBMIT.py:213
uint32_t uint32_t * format
_InfraTests(input_api, output_api)
Definition PRESUBMIT.py:137
CheckChangeOnUpload(input_api, output_api)
Definition PRESUBMIT.py:70
_CheckIncludesFormatted(input_api, output_api)
Definition PRESUBMIT.py:201
_CheckGitConflictMarkers(input_api, output_api)
Definition PRESUBMIT.py:184
_RegenerateAllExamplesCPP(input_api, output_api)
Definition PRESUBMIT.py:224
_CheckDEPS(input_api, output_api)
Definition PRESUBMIT.py:521
_CheckBuildifier(input_api, output_api)
Definition PRESUBMIT.py:417
_CheckReleaseNotesForPublicAPI(input_api, output_api)
Definition PRESUBMIT.py:630
_CheckGNIGenerated(input_api, output_api)
Definition PRESUBMIT.py:385
_CheckTopReleaseNotesChanged(input_api, output_api)
Definition PRESUBMIT.py:655
PostUploadHook(gerrit, change, output_api)
Definition PRESUBMIT.py:684
_IfDefChecks(input_api, output_api)
Definition PRESUBMIT.py:74
_CommonChecks(input_api, output_api)
Definition PRESUBMIT.py:516
_JsonChecks(input_api, output_api)
Definition PRESUBMIT.py:51
CheckChangeOnCommit(input_api, output_api)
Definition PRESUBMIT.py:66
_CopyrightChecks(input_api, output_api, source_file_filter=None)
Definition PRESUBMIT.py:115
_CheckGNFormatted(input_api, output_api)
Definition PRESUBMIT.py:153
_CheckBannedAPIs(input_api, output_api)
Definition PRESUBMIT.py:464
_CheckChangeHasEol(input_api, output_api, source_file_filter=None)
Definition PRESUBMIT.py:35
_RunCommandAndCheckGitDiff(output_api, command)
Definition PRESUBMIT.py:353
_CheckExamplesForPrivateAPIs(input_api, output_api)
Definition PRESUBMIT.py:248
_CheckBazelBUILDFiles(input_api, output_api)
Definition PRESUBMIT.py:287
_CheckPublicBzl(input_api, output_api)
Definition PRESUBMIT.py:330
_CheckGeneratedBazelBUILDFiles(input_api, output_api)
Definition PRESUBMIT.py:270