Adding djsollen to list of owners
[platform/upstream/libSkiaSharp.git] / PRESUBMIT.py
1 # Copyright (c) 2013 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
4
5
6 """Top-level presubmit script for Skia.
7
8 See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts
9 for more details about the presubmit API built into gcl.
10 """
11
12 import fnmatch
13 import os
14 import re
15 import sys
16 import traceback
17
18
19 REVERT_CL_SUBJECT_PREFIX = 'Revert '
20
21 SKIA_TREE_STATUS_URL = 'http://skia-tree-status.appspot.com'
22
23 PUBLIC_API_OWNERS = (
24     'reed@chromium.org',
25     'reed@google.com',
26     'bsalomon@chromium.org',
27     'bsalomon@google.com',
28     'djsollen@chromium.org',
29     'djsollen@google.com',
30 )
31
32 AUTHORS_FILE_NAME = 'AUTHORS'
33
34
35 def _CheckChangeHasEol(input_api, output_api, source_file_filter=None):
36   """Checks that files end with atleast 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 atleast 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
51 def _PythonChecks(input_api, output_api):
52   """Run checks on any modified Python files."""
53   pylint_disabled_warnings = (
54       'F0401',  # Unable to import.
55       'E0611',  # No name in module.
56       'W0232',  # Class has no __init__ method.
57       'E1002',  # Use of super on an old style class.
58       'W0403',  # Relative import used.
59       'R0201',  # Method could be a function.
60       'E1003',  # Using class name in super.
61       'W0613',  # Unused argument.
62   )
63   # Run Pylint on only the modified python files. Unfortunately it still runs
64   # Pylint on the whole file instead of just the modified lines.
65   affected_python_files = []
66   for affected_file in input_api.AffectedSourceFiles(None):
67     affected_file_path = affected_file.LocalPath()
68     if affected_file_path.endswith('.py'):
69       affected_python_files.append(affected_file_path)
70   return input_api.canned_checks.RunPylint(
71       input_api, output_api,
72       disabled_warnings=pylint_disabled_warnings,
73       white_list=affected_python_files)
74
75
76 def _CommonChecks(input_api, output_api):
77   """Presubmit checks common to upload and commit."""
78   results = []
79   sources = lambda x: (x.LocalPath().endswith('.h') or
80                        x.LocalPath().endswith('.gypi') or
81                        x.LocalPath().endswith('.gyp') or
82                        x.LocalPath().endswith('.py') or
83                        x.LocalPath().endswith('.sh') or
84                        x.LocalPath().endswith('.cpp'))
85   results.extend(
86       _CheckChangeHasEol(
87           input_api, output_api, source_file_filter=sources))
88   results.extend(_PythonChecks(input_api, output_api))
89   return results
90
91
92 def CheckChangeOnUpload(input_api, output_api):
93   """Presubmit checks for the change on upload.
94
95   The following are the presubmit checks:
96   * Check change has one and only one EOL.
97   """
98   results = []
99   results.extend(_CommonChecks(input_api, output_api))
100   return results
101
102
103 def _CheckTreeStatus(input_api, output_api, json_url):
104   """Check whether to allow commit.
105
106   Args:
107     input_api: input related apis.
108     output_api: output related apis.
109     json_url: url to download json style status.
110   """
111   tree_status_results = input_api.canned_checks.CheckTreeIsOpen(
112       input_api, output_api, json_url=json_url)
113   if not tree_status_results:
114     # Check for caution state only if tree is not closed.
115     connection = input_api.urllib2.urlopen(json_url)
116     status = input_api.json.loads(connection.read())
117     connection.close()
118     if ('caution' in status['message'].lower() and
119         os.isatty(sys.stdout.fileno())):
120       # Display a prompt only if we are in an interactive shell. Without this
121       # check the commit queue behaves incorrectly because it considers
122       # prompts to be failures.
123       short_text = 'Tree state is: ' + status['general_state']
124       long_text = status['message'] + '\n' + json_url
125       tree_status_results.append(
126           output_api.PresubmitPromptWarning(
127               message=short_text, long_text=long_text))
128   else:
129     # Tree status is closed. Put in message about contacting sheriff.
130     connection = input_api.urllib2.urlopen(
131         SKIA_TREE_STATUS_URL + '/current-sheriff')
132     sheriff_details = input_api.json.loads(connection.read())
133     if sheriff_details:
134       tree_status_results[0]._message += (
135           '\n\nPlease contact the current Skia sheriff (%s) if you are trying '
136           'to submit a build fix\nand do not know how to submit because the '
137           'tree is closed') % sheriff_details['username']
138   return tree_status_results
139
140
141 def _CheckOwnerIsInAuthorsFile(input_api, output_api):
142   results = []
143   issue = input_api.change.issue
144   if issue and input_api.rietveld:
145     issue_properties = input_api.rietveld.get_issue_properties(
146         issue=int(issue), messages=False)
147     owner_email = issue_properties['owner_email']
148
149     try:
150       authors_content = ''
151       for line in open(AUTHORS_FILE_NAME):
152         if not line.startswith('#'):
153           authors_content += line
154       email_fnmatches = re.findall('<(.*)>', authors_content)
155       for email_fnmatch in email_fnmatches:
156         if fnmatch.fnmatch(owner_email, email_fnmatch):
157           # Found a match, the user is in the AUTHORS file break out of the loop
158           break
159       else:
160         # TODO(rmistry): Remove the below CLA messaging once a CLA checker has
161         # been added to the CQ.
162         results.append(
163           output_api.PresubmitError(
164             'The email %s is not in Skia\'s AUTHORS file.\n'
165             'Issue owner, this CL must include an addition to the Skia AUTHORS '
166             'file.\n'
167             'Googler reviewers, please check that the AUTHORS entry '
168             'corresponds to an email address in http://goto/cla-signers. If it '
169             'does not then ask the issue owner to sign the CLA at '
170             'https://developers.google.com/open-source/cla/individual '
171             '(individual) or '
172             'https://developers.google.com/open-source/cla/corporate '
173             '(corporate).'
174             % owner_email))
175     except IOError:
176       # Do not fail if authors file cannot be found.
177       traceback.print_exc()
178       input_api.logging.error('AUTHORS file not found!')
179
180   return results
181
182
183 def _CheckLGTMsForPublicAPI(input_api, output_api):
184   """Check LGTMs for public API changes.
185
186   For public API files make sure there is an LGTM from the list of owners in
187   PUBLIC_API_OWNERS.
188   """
189   results = []
190   requires_owner_check = False
191   for affected_svn_file in input_api.AffectedFiles():
192     affected_file_path = affected_svn_file.AbsoluteLocalPath()
193     file_path, file_ext = os.path.splitext(affected_file_path)
194     # We only care about files that end in .h and are under the include dir.
195     if file_ext == '.h' and 'include' in file_path.split(os.path.sep):
196       requires_owner_check = True
197
198   if not requires_owner_check:
199     return results
200
201   lgtm_from_owner = False
202   issue = input_api.change.issue
203   if issue and input_api.rietveld:
204     issue_properties = input_api.rietveld.get_issue_properties(
205         issue=int(issue), messages=True)
206     if re.match(REVERT_CL_SUBJECT_PREFIX, issue_properties['subject'], re.I):
207       # It is a revert CL, ignore the public api owners check.
208       return results
209     if issue_properties['owner_email'] in PUBLIC_API_OWNERS:
210       # An owner created the CL that is an automatic LGTM.
211       lgtm_from_owner = True
212
213     messages = issue_properties.get('messages')
214     if messages:
215       for message in messages:
216         if (message['sender'] in PUBLIC_API_OWNERS and
217             'lgtm' in message['text'].lower()):
218           # Found an lgtm in a message from an owner.
219           lgtm_from_owner = True
220           break
221
222   if not lgtm_from_owner:
223     results.append(
224         output_api.PresubmitError(
225             'Since the CL is editing public API, you must have an LGTM from '
226             'one of: %s' % str(PUBLIC_API_OWNERS)))
227   return results
228
229
230 def CheckChangeOnCommit(input_api, output_api):
231   """Presubmit checks for the change on commit.
232
233   The following are the presubmit checks:
234   * Check change has one and only one EOL.
235   * Ensures that the Skia tree is open in
236     http://skia-tree-status.appspot.com/. Shows a warning if it is in 'Caution'
237     state and an error if it is in 'Closed' state.
238   """
239   results = []
240   results.extend(_CommonChecks(input_api, output_api))
241   results.extend(
242       _CheckTreeStatus(input_api, output_api, json_url=(
243           SKIA_TREE_STATUS_URL + '/banner-status?format=json')))
244   results.extend(_CheckLGTMsForPublicAPI(input_api, output_api))
245   results.extend(_CheckOwnerIsInAuthorsFile(input_api, output_api))
246   return results