1fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot# Copyright (c) 2013 The Chromium Authors. All rights reserved.
2fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot# Use of this source code is governed by a BSD-style license that can be
3fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot# found in the LICENSE file.
4fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
5fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
6fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot"""Top-level presubmit script for Skia.
7fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
8fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team RobotSee http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts
9fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robotfor more details about the presubmit API built into gcl.
10fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot"""
11fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
12fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robotimport collections
13fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robotimport csv
14fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robotimport fnmatch
15fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robotimport os
16fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robotimport re
17fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robotimport subprocess
18fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robotimport sys
19fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robotimport traceback
20fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
21fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
22fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team RobotREVERT_CL_SUBJECT_PREFIX = 'Revert '
23fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
24fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team RobotSKIA_TREE_STATUS_URL = 'http://skia-tree-status.appspot.com'
25fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
26fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot# Please add the complete email address here (and not just 'xyz@' or 'xyz').
27fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team RobotPUBLIC_API_OWNERS = (
28fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    'reed@chromium.org',
29fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    'reed@google.com',
30fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    'bsalomon@chromium.org',
31fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    'bsalomon@google.com',
32fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    'djsollen@chromium.org',
33fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    'djsollen@google.com',
34fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    'hcm@chromium.org',
35fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    'hcm@google.com',
36fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot)
37fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
38fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team RobotAUTO_COMMIT_BOTS = (
39fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    'update-docs@skia.org',
40fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    'update-skps@skia.org'
41fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot)
42fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
43fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team RobotAUTHORS_FILE_NAME = 'AUTHORS'
44fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
45fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team RobotDOCS_PREVIEW_URL = 'https://skia.org/?cl='
46fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team RobotGOLD_TRYBOT_URL = 'https://gold.skia.org/search?issue='
47fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
48fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot# Path to CQ bots feature is described in https://bug.skia.org/4364
49fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team RobotPATH_PREFIX_TO_EXTRA_TRYBOTS = {
50fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    'src/opts/': ('skia.primary:'
51fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot      'Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-SKNX_NO_SIMD'),
52fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    'include/private/SkAtomics.h': ('skia.primary:'
53fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot      'Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-TSAN,'
54fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot      'Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-TSAN'
55fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    ),
56fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
57fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    # Below are examples to show what is possible with this feature.
58fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    # 'src/svg/': 'master1:abc;master2:def',
59fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    # 'src/svg/parser/': 'master3:ghi,jkl;master4:mno',
60fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    # 'src/image/SkImage_Base.h': 'master5:pqr,stu;master1:abc1;master2:def',
61fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot}
62fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
63fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team RobotSERVICE_ACCOUNT_SUFFIX = '@skia-buildbots.google.com.iam.gserviceaccount.com'
64fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
65fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
66fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robotdef _CheckChangeHasEol(input_api, output_api, source_file_filter=None):
67fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  """Checks that files end with atleast one \n (LF)."""
68fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  eof_files = []
69fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  for f in input_api.AffectedSourceFiles(source_file_filter):
70fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    contents = input_api.ReadFile(f, 'rb')
71fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    # Check that the file ends in atleast one newline character.
72fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    if len(contents) > 1 and contents[-1:] != '\n':
73fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot      eof_files.append(f.LocalPath())
74fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
75fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  if eof_files:
76fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    return [output_api.PresubmitPromptWarning(
77fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot      'These files should end in a newline character:',
78fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot      items=eof_files)]
79fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  return []
80fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
81fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
82fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robotdef _PythonChecks(input_api, output_api):
83fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  """Run checks on any modified Python files."""
84fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  pylint_disabled_files = (
85fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot      'infra/bots/recipes.py',
86fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  )
87fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  pylint_disabled_warnings = (
88fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot      'F0401',  # Unable to import.
89fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot      'E0611',  # No name in module.
90fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot      'W0232',  # Class has no __init__ method.
91fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot      'E1002',  # Use of super on an old style class.
92fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot      'W0403',  # Relative import used.
93fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot      'R0201',  # Method could be a function.
94fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot      'E1003',  # Using class name in super.
95fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot      'W0613',  # Unused argument.
96fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot      'W0105',  # String statement has no effect.
97fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  )
98fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  # Run Pylint on only the modified python files. Unfortunately it still runs
99fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  # Pylint on the whole file instead of just the modified lines.
100fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  affected_python_files = []
101fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  for affected_file in input_api.AffectedSourceFiles(None):
102fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    affected_file_path = affected_file.LocalPath()
103fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    if affected_file_path.endswith('.py'):
104fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot      if affected_file_path not in pylint_disabled_files:
105fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot        affected_python_files.append(affected_file_path)
106fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  return input_api.canned_checks.RunPylint(
107fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot      input_api, output_api,
108fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot      disabled_warnings=pylint_disabled_warnings,
109fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot      white_list=affected_python_files)
110fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
111fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
112fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robotdef _IfDefChecks(input_api, output_api):
113fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  """Ensures if/ifdef are not before includes. See skbug/3362 for details."""
114fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  comment_block_start_pattern = re.compile('^\s*\/\*.*$')
115fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  comment_block_middle_pattern = re.compile('^\s+\*.*')
116fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  comment_block_end_pattern = re.compile('^\s+\*\/.*$')
117fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  single_line_comment_pattern = re.compile('^\s*//.*$')
118fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  def is_comment(line):
119fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    return (comment_block_start_pattern.match(line) or
120fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot            comment_block_middle_pattern.match(line) or
121fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot            comment_block_end_pattern.match(line) or
122fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot            single_line_comment_pattern.match(line))
123fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
124fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  empty_line_pattern = re.compile('^\s*$')
125fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  def is_empty_line(line):
126fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    return empty_line_pattern.match(line)
127fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
128fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  failing_files = []
129fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  for affected_file in input_api.AffectedSourceFiles(None):
130fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    affected_file_path = affected_file.LocalPath()
131fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    if affected_file_path.endswith('.cpp') or affected_file_path.endswith('.h'):
132fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot      f = open(affected_file_path)
133fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot      for line in f.xreadlines():
134fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot        if is_comment(line) or is_empty_line(line):
135fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot          continue
136fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot        # The below will be the first real line after comments and newlines.
137fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot        if line.startswith('#if 0 '):
138fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot          pass
139fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot        elif line.startswith('#if ') or line.startswith('#ifdef '):
140fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot          failing_files.append(affected_file_path)
141fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot        break
142fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
143fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  results = []
144fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  if failing_files:
145fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    results.append(
146fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot        output_api.PresubmitError(
147fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot            'The following files have #if or #ifdef before includes:\n%s\n\n'
148fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot            'See https://bug.skia.org/3362 for why this should be fixed.' %
149fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot                '\n'.join(failing_files)))
150fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  return results
151fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
152fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
153fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robotdef _CopyrightChecks(input_api, output_api, source_file_filter=None):
154fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  results = []
155fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  year_pattern = r'\d{4}'
156fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  year_range_pattern = r'%s(-%s)?' % (year_pattern, year_pattern)
157fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  years_pattern = r'%s(,%s)*,?' % (year_range_pattern, year_range_pattern)
158fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  copyright_pattern = (
159fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot      r'Copyright (\([cC]\) )?%s \w+' % years_pattern)
160fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
161fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  for affected_file in input_api.AffectedSourceFiles(source_file_filter):
162fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    if 'third_party' in affected_file.LocalPath():
163fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot      continue
164fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    contents = input_api.ReadFile(affected_file, 'rb')
165fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    if not re.search(copyright_pattern, contents):
166fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot      results.append(output_api.PresubmitError(
167fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot          '%s is missing a correct copyright header.' % affected_file))
168fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  return results
169fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
170fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
171fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robotdef _ToolFlags(input_api, output_api):
172fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  """Make sure `{dm,nanobench}_flags.py test` passes if modified."""
173fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  results = []
174fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  sources = lambda x: ('dm_flags.py'        in x.LocalPath() or
175fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot                       'nanobench_flags.py' in x.LocalPath())
176fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  for f in input_api.AffectedSourceFiles(sources):
177fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    if 0 != subprocess.call(['python', f.LocalPath(), 'test']):
178fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot      results.append(output_api.PresubmitError('`python %s test` failed' % f))
179fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  return results
180fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
181fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
182fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robotdef _InfraTests(input_api, output_api):
183fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  """Run the infra tests."""
184fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  results = []
185fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  if not any(f.LocalPath().startswith('infra')
186fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot             for f in input_api.AffectedFiles()):
187fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    return results
188fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
189fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  cmd = ['python', os.path.join('infra', 'bots', 'infra_tests.py')]
190fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  try:
191fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    subprocess.check_output(cmd)
192fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  except subprocess.CalledProcessError as e:
193fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    results.append(output_api.PresubmitError(
194fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot        '`%s` failed:\n%s' % (' '.join(cmd), e.output)))
195fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  return results
196fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
197fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
198fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robotdef _CheckGNFormatted(input_api, output_api):
199fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  """Make sure any .gn files we're changing have been formatted."""
200fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  results = []
201fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  for f in input_api.AffectedFiles():
202fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    if (not f.LocalPath().endswith('.gn') and
203fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot        not f.LocalPath().endswith('.gni')):
204fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot      continue
205fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
206fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    gn = 'gn.bat' if 'win32' in sys.platform else 'gn'
207fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    cmd = [gn, 'format', '--dry-run', f.LocalPath()]
208fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    try:
209fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot      subprocess.check_output(cmd)
210fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    except subprocess.CalledProcessError:
211fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot      fix = 'gn format ' + f.LocalPath()
212fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot      results.append(output_api.PresubmitError(
213fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot          '`%s` failed, try\n\t%s' % (' '.join(cmd), fix)))
214fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  return results
215fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
216fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
217fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robotdef _CommonChecks(input_api, output_api):
218fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  """Presubmit checks common to upload and commit."""
219fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  results = []
220fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  sources = lambda x: (x.LocalPath().endswith('.h') or
221fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot                       x.LocalPath().endswith('.py') or
222fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot                       x.LocalPath().endswith('.sh') or
223fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot                       x.LocalPath().endswith('.m') or
224fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot                       x.LocalPath().endswith('.mm') or
225fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot                       x.LocalPath().endswith('.go') or
226fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot                       x.LocalPath().endswith('.c') or
227fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot                       x.LocalPath().endswith('.cc') or
228fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot                       x.LocalPath().endswith('.cpp'))
229fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  results.extend(
230fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot      _CheckChangeHasEol(
231fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot          input_api, output_api, source_file_filter=sources))
232fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  results.extend(
233fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot      input_api.canned_checks.CheckChangeHasNoCR(
234fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot          input_api, output_api, source_file_filter=sources))
235fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  results.extend(
236fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot      input_api.canned_checks.CheckChangeHasNoStrayWhitespace(
237fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot          input_api, output_api, source_file_filter=sources))
238fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  results.extend(_PythonChecks(input_api, output_api))
239fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  results.extend(_IfDefChecks(input_api, output_api))
240fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  results.extend(_CopyrightChecks(input_api, output_api,
241fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot                                  source_file_filter=sources))
242fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  results.extend(_ToolFlags(input_api, output_api))
243fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  return results
244fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
245fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
246fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robotdef CheckChangeOnUpload(input_api, output_api):
247fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  """Presubmit checks for the change on upload.
248fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
249fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  The following are the presubmit checks:
250fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  * Check change has one and only one EOL.
251fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  """
252fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  results = []
253fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  results.extend(_CommonChecks(input_api, output_api))
254fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  # Run on upload, not commit, since the presubmit bot apparently doesn't have
255fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  # coverage or Go installed.
256fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  results.extend(_InfraTests(input_api, output_api))
257fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
258fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  results.extend(_CheckGNFormatted(input_api, output_api))
259fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  return results
260fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
261fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
262fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robotdef _CheckTreeStatus(input_api, output_api, json_url):
263fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  """Check whether to allow commit.
264fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
265fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  Args:
266fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    input_api: input related apis.
267fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    output_api: output related apis.
268fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    json_url: url to download json style status.
269fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  """
270fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  tree_status_results = input_api.canned_checks.CheckTreeIsOpen(
271fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot      input_api, output_api, json_url=json_url)
272fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  if not tree_status_results:
273fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    # Check for caution state only if tree is not closed.
274fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    connection = input_api.urllib2.urlopen(json_url)
275fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    status = input_api.json.loads(connection.read())
276fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    connection.close()
277fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    if ('caution' in status['message'].lower() and
278fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot        os.isatty(sys.stdout.fileno())):
279fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot      # Display a prompt only if we are in an interactive shell. Without this
280fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot      # check the commit queue behaves incorrectly because it considers
281fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot      # prompts to be failures.
282fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot      short_text = 'Tree state is: ' + status['general_state']
283fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot      long_text = status['message'] + '\n' + json_url
284fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot      tree_status_results.append(
285fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot          output_api.PresubmitPromptWarning(
286fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot              message=short_text, long_text=long_text))
287fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  else:
288fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    # Tree status is closed. Put in message about contacting sheriff.
289fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    connection = input_api.urllib2.urlopen(
290fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot        SKIA_TREE_STATUS_URL + '/current-sheriff')
291fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    sheriff_details = input_api.json.loads(connection.read())
292fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    if sheriff_details:
293fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot      tree_status_results[0]._message += (
294fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot          '\n\nPlease contact the current Skia sheriff (%s) if you are trying '
295fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot          'to submit a build fix\nand do not know how to submit because the '
296fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot          'tree is closed') % sheriff_details['username']
297fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  return tree_status_results
298fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
299fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
300fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robotclass CodeReview(object):
301fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  """Abstracts which codereview tool is used for the specified issue."""
302fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
303fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  def __init__(self, input_api):
304fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    self._issue = input_api.change.issue
305fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    self._gerrit = input_api.gerrit
306fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
307fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  def GetOwnerEmail(self):
308fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    return self._gerrit.GetChangeOwner(self._issue)
309fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
310fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  def GetSubject(self):
311fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    return self._gerrit.GetChangeInfo(self._issue)['subject']
312fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
313fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  def GetDescription(self):
314fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    return self._gerrit.GetChangeDescription(self._issue)
315fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
316fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  def IsDryRun(self):
317fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    return self._gerrit.GetChangeInfo(
318fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot        self._issue)['labels']['Commit-Queue'].get('value', 0) == 1
319fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
320fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  def GetReviewers(self):
321fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    code_review_label = (
322fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot        self._gerrit.GetChangeInfo(self._issue)['labels']['Code-Review'])
323fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    return [r['email'] for r in code_review_label.get('all', [])]
324fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
325fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  def GetApprovers(self):
326fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    approvers = []
327fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    code_review_label = (
328fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot        self._gerrit.GetChangeInfo(self._issue)['labels']['Code-Review'])
329fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    for m in code_review_label.get('all', []):
330fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot      if m.get("value") == 1:
331fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot        approvers.append(m["email"])
332fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    return approvers
333fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
334fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
335fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robotdef _CheckOwnerIsInAuthorsFile(input_api, output_api):
336fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  results = []
337fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  if input_api.change.issue:
338fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    cr = CodeReview(input_api)
339fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
340fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    owner_email = cr.GetOwnerEmail()
341fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
342fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    # Service accounts don't need to be in AUTHORS.
343fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    if owner_email.endswith(SERVICE_ACCOUNT_SUFFIX):
344fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot      return results
345fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
346fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    try:
347fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot      authors_content = ''
348fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot      for line in open(AUTHORS_FILE_NAME):
349fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot        if not line.startswith('#'):
350fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot          authors_content += line
351fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot      email_fnmatches = re.findall('<(.*)>', authors_content)
352fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot      for email_fnmatch in email_fnmatches:
353fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot        if fnmatch.fnmatch(owner_email, email_fnmatch):
354fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot          # Found a match, the user is in the AUTHORS file break out of the loop
355fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot          break
356fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot      else:
357fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot        results.append(
358fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot          output_api.PresubmitError(
359fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot            'The email %s is not in Skia\'s AUTHORS file.\n'
360fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot            'Issue owner, this CL must include an addition to the Skia AUTHORS '
361fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot            'file.'
362fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot            % owner_email))
363fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    except IOError:
364fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot      # Do not fail if authors file cannot be found.
365fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot      traceback.print_exc()
366fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot      input_api.logging.error('AUTHORS file not found!')
367fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
368fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  return results
369fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
370fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
371fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robotdef _CheckLGTMsForPublicAPI(input_api, output_api):
372fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  """Check LGTMs for public API changes.
373fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
374fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  For public API files make sure there is an LGTM from the list of owners in
375fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  PUBLIC_API_OWNERS.
376fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  """
377fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  results = []
378fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  requires_owner_check = False
379fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  for affected_file in input_api.AffectedFiles():
380fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    affected_file_path = affected_file.LocalPath()
381fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    file_path, file_ext = os.path.splitext(affected_file_path)
382fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    # We only care about files that end in .h and are under the top-level
383fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    # include dir, but not include/private.
384fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    if (file_ext == '.h' and
385fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot        'include' == file_path.split(os.path.sep)[0] and
386fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot        'private' not in file_path):
387fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot      requires_owner_check = True
388fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
389fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  if not requires_owner_check:
390fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    return results
391fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
392fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  lgtm_from_owner = False
393fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  if input_api.change.issue:
394fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    cr = CodeReview(input_api)
395fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
396fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    if re.match(REVERT_CL_SUBJECT_PREFIX, cr.GetSubject(), re.I):
397fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot      # It is a revert CL, ignore the public api owners check.
398fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot      return results
399fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
400fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    if cr.IsDryRun():
401fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot      # Ignore public api owners check for dry run CLs since they are not
402fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot      # going to be committed.
403fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot      return results
404fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
405fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    if input_api.gerrit:
406fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot      for reviewer in cr.GetReviewers():
407fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot        if reviewer in PUBLIC_API_OWNERS:
408fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot          # If an owner is specified as an reviewer in Gerrit then ignore the
409fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot          # public api owners check.
410fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot          return results
411fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    else:
412fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot      match = re.search(r'^TBR=(.*)$', cr.GetDescription(), re.M)
413fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot      if match:
414fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot        tbr_section = match.group(1).strip().split(' ')[0]
415fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot        tbr_entries = tbr_section.split(',')
416fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot        for owner in PUBLIC_API_OWNERS:
417fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot          if owner in tbr_entries or owner.split('@')[0] in tbr_entries:
418fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot            # If an owner is specified in the TBR= line then ignore the public
419fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot            # api owners check.
420fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot            return results
421fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
422fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    if cr.GetOwnerEmail() in PUBLIC_API_OWNERS:
423fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot      # An owner created the CL that is an automatic LGTM.
424fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot      lgtm_from_owner = True
425fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
426fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    for approver in cr.GetApprovers():
427fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot      if approver in PUBLIC_API_OWNERS:
428fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot        # Found an lgtm in a message from an owner.
429fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot        lgtm_from_owner = True
430fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot        break
431fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
432fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  if not lgtm_from_owner:
433fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    results.append(
434fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot        output_api.PresubmitError(
435fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot            "If this CL adds to or changes Skia's public API, you need an LGTM "
436fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot            "from any of %s.  If this CL only removes from or doesn't change "
437fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot            "Skia's public API, please add a short note to the CL saying so. "
438fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot            "Add one of the owners as a reviewer to your CL as well as to the "
439fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot            "TBR= line.  If you don't know if this CL affects Skia's public "
440fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot            "API, treat it like it does." % str(PUBLIC_API_OWNERS)))
441fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  return results
442fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
443fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
444fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robotdef _FooterExists(footers, key, value):
445fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  for k, v in footers:
446fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    if k == key and v == value:
447fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot      return True
448fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  return False
449fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
450fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
451fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robotdef PostUploadHook(cl, change, output_api):
452fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  """git cl upload will call this hook after the issue is created/modified.
453fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
454fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  This hook does the following:
455fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  * Adds a link to preview docs changes if there are any docs changes in the CL.
456fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  * Adds 'No-Try: true' if the CL contains only docs changes.
457fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  * Adds 'No-Tree-Checks: true' for non master branch changes since they do not
458fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    need to be gated on the master branch's tree.
459fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  * Adds 'No-Try: true' for non master branch changes since trybots do not yet
460fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    work on them.
461fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  * Adds 'No-Presubmit: true' for non master branch changes since those don't
462fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    run the presubmit checks.
463fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  * Adds extra trybots for the paths defined in PATH_TO_EXTRA_TRYBOTS.
464fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  """
465fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
466fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  results = []
467fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  atleast_one_docs_change = False
468fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  all_docs_changes = True
469fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  for affected_file in change.AffectedFiles():
470fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    affected_file_path = affected_file.LocalPath()
471fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    file_path, _ = os.path.splitext(affected_file_path)
472fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    if 'site' == file_path.split(os.path.sep)[0]:
473fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot      atleast_one_docs_change = True
474fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    else:
475fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot      all_docs_changes = False
476fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    if atleast_one_docs_change and not all_docs_changes:
477fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot      break
478fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
479fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  issue = cl.issue
480fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  if issue:
481fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    # Skip PostUploadHooks for all auto-commit bots. New patchsets (caused
482fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    # due to PostUploadHooks) invalidates the CQ+2 vote from the
483fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    # "--use-commit-queue" flag to "git cl upload".
484fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    if cl.GetIssueOwner() in AUTO_COMMIT_BOTS:
485fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot      return results
486fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
487fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    original_description_lines, footers = cl.GetDescriptionFooters()
488fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    new_description_lines = list(original_description_lines)
489fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
490fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    # If the change includes only doc changes then add No-Try: true in the
491fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    # CL's description if it does not exist yet.
492fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    if all_docs_changes and not _FooterExists(footers, 'No-Try', 'true'):
493fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot      new_description_lines.append('No-Try: true')
494fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot      results.append(
495fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot          output_api.PresubmitNotifyResult(
496fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot              'This change has only doc changes. Automatically added '
497fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot              '\'No-Try: true\' to the CL\'s description'))
498fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
499fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    # If there is atleast one docs change then add preview link in the CL's
500fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    # description if it does not already exist there.
501fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    docs_preview_link = '%s%s' % (DOCS_PREVIEW_URL, issue)
502fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    docs_preview_line = 'Docs-Preview: %s' % docs_preview_link
503fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    if (atleast_one_docs_change and
504fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot        not _FooterExists(footers, 'Docs-Preview', docs_preview_link)):
505fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot      # Automatically add a link to where the docs can be previewed.
506fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot      new_description_lines.append(docs_preview_line)
507fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot      results.append(
508fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot          output_api.PresubmitNotifyResult(
509fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot              'Automatically added a link to preview the docs changes to the '
510fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot              'CL\'s description'))
511fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
512fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    # If the target ref is not master then add 'No-Tree-Checks: true' and
513fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    # 'No-Try: true' to the CL's description if it does not already exist there.
514fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    target_ref = cl.GetRemoteBranch()[1]
515fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    if target_ref != 'refs/remotes/origin/master':
516fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot      if not _FooterExists(footers, 'No-Tree-Checks', 'true'):
517fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot        new_description_lines.append('No-Tree-Checks: true')
518fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot        results.append(
519fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot            output_api.PresubmitNotifyResult(
520fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot                'Branch changes do not need to rely on the master branch\'s '
521fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot                'tree status. Automatically added \'No-Tree-Checks: true\' to '
522fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot                'the CL\'s description'))
523fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot      if not _FooterExists(footers, 'No-Try', 'true'):
524fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot        new_description_lines.append('No-Try: true')
525fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot        results.append(
526fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot            output_api.PresubmitNotifyResult(
527fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot                'Trybots do not yet work for non-master branches. '
528fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot                'Automatically added \'No-Try: true\' to the CL\'s '
529fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot                'description'))
530fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot      if not _FooterExists(footers, 'No-Presubmit', 'true'):
531fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot        new_description_lines.append('No-Presubmit: true')
532fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot        results.append(
533fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot            output_api.PresubmitNotifyResult(
534fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot                'Branch changes do not run the presubmit checks.'))
535fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
536fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    # Automatically set Cq-Include-Trybots if any of the changed files here
537fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    # begin with the paths of interest.
538fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    bots_to_include = []
539fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    for affected_file in change.AffectedFiles():
540fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot      affected_file_path = affected_file.LocalPath()
541fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot      for path_prefix, extra_bots in PATH_PREFIX_TO_EXTRA_TRYBOTS.iteritems():
542fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot        if affected_file_path.startswith(path_prefix):
543fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot          results.append(
544fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot              output_api.PresubmitNotifyResult(
545fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot                  'Your CL modifies the path %s.\nAutomatically adding %s to '
546fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot                  'the CL description.' % (affected_file_path, extra_bots)))
547fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot          bots_to_include.append(extra_bots)
548fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    if bots_to_include:
549fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot      output_api.EnsureCQIncludeTrybotsAreAdded(
550fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot          cl, bots_to_include, new_description_lines)
551fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
552fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    # If the description has changed update it.
553fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    if new_description_lines != original_description_lines:
554fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot      # Add a new line separating the new contents from the old contents.
555fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot      new_description_lines.insert(len(original_description_lines), '')
556fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot      cl.UpdateDescriptionFooters(new_description_lines, footers)
557fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
558fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    return results
559fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
560fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
561fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robotdef CheckChangeOnCommit(input_api, output_api):
562fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  """Presubmit checks for the change on commit.
563fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot
564fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  The following are the presubmit checks:
565fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  * Check change has one and only one EOL.
566fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  * Ensures that the Skia tree is open in
567fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    http://skia-tree-status.appspot.com/. Shows a warning if it is in 'Caution'
568fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot    state and an error if it is in 'Closed' state.
569fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  """
570fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  results = []
571fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  results.extend(_CommonChecks(input_api, output_api))
572fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  results.extend(
573fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot      _CheckTreeStatus(input_api, output_api, json_url=(
574fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot          SKIA_TREE_STATUS_URL + '/banner-status?format=json')))
575fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  results.extend(_CheckLGTMsForPublicAPI(input_api, output_api))
576fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  results.extend(_CheckOwnerIsInAuthorsFile(input_api, output_api))
577fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  # Checks for the presence of 'DO NOT''SUBMIT' in CL description and in
578fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  # content of files.
579fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  results.extend(
580fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot      input_api.canned_checks.CheckDoNotSubmit(input_api, output_api))
581fe17456d5e528078ce69b5f15cf7adf1fab963fandroid-build-team Robot  return results
582