Check if the issue owner is in the AUTHORS file in PRESUBMIT.py
[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 )
29
30 AUTHORS_FILE_NAME = 'AUTHORS'
31
32
33 def _CheckChangeHasEol(input_api, output_api, source_file_filter=None):
34   """Checks that files end with atleast one \n (LF)."""
35   eof_files = []
36   for f in input_api.AffectedSourceFiles(source_file_filter):
37     contents = input_api.ReadFile(f, 'rb')
38     # Check that the file ends in atleast one newline character.
39     if len(contents) > 1 and contents[-1:] != '\n':
40       eof_files.append(f.LocalPath())
41
42   if eof_files:
43     return [output_api.PresubmitPromptWarning(
44       'These files should end in a newline character:',
45       items=eof_files)]
46   return []
47
48
49 def _CommonChecks(input_api, output_api):
50   """Presubmit checks common to upload and commit."""
51   results = []
52   sources = lambda x: (x.LocalPath().endswith('.h') or
53                        x.LocalPath().endswith('.gypi') or
54                        x.LocalPath().endswith('.gyp') or
55                        x.LocalPath().endswith('.py') or
56                        x.LocalPath().endswith('.sh') or
57                        x.LocalPath().endswith('.cpp'))
58   results.extend(
59       _CheckChangeHasEol(
60           input_api, output_api, source_file_filter=sources))
61   return results
62
63
64 def CheckChangeOnUpload(input_api, output_api):
65   """Presubmit checks for the change on upload.
66
67   The following are the presubmit checks:
68   * Check change has one and only one EOL.
69   """
70   results = []
71   results.extend(_CommonChecks(input_api, output_api))
72   return results
73
74
75 def _CheckTreeStatus(input_api, output_api, json_url):
76   """Check whether to allow commit.
77
78   Args:
79     input_api: input related apis.
80     output_api: output related apis.
81     json_url: url to download json style status.
82   """
83   tree_status_results = input_api.canned_checks.CheckTreeIsOpen(
84       input_api, output_api, json_url=json_url)
85   if not tree_status_results:
86     # Check for caution state only if tree is not closed.
87     connection = input_api.urllib2.urlopen(json_url)
88     status = input_api.json.loads(connection.read())
89     connection.close()
90     if ('caution' in status['message'].lower() and
91         os.isatty(sys.stdout.fileno())):
92       # Display a prompt only if we are in an interactive shell. Without this
93       # check the commit queue behaves incorrectly because it considers
94       # prompts to be failures.
95       short_text = 'Tree state is: ' + status['general_state']
96       long_text = status['message'] + '\n' + json_url
97       tree_status_results.append(
98           output_api.PresubmitPromptWarning(
99               message=short_text, long_text=long_text))
100   else:
101     # Tree status is closed. Put in message about contacting sheriff.
102     connection = input_api.urllib2.urlopen(
103         SKIA_TREE_STATUS_URL + '/current-sheriff')
104     sheriff_details = input_api.json.loads(connection.read())
105     if sheriff_details:
106       tree_status_results[0]._message += (
107           '\n\nPlease contact the current Skia sheriff (%s) if you are trying '
108           'to submit a build fix\nand do not know how to submit because the '
109           'tree is closed') % sheriff_details['username']
110   return tree_status_results
111
112
113 def _CheckOwnerIsInAuthorsFile(input_api, output_api):
114   results = []
115   issue = input_api.change.issue
116   if issue and input_api.rietveld:
117     issue_properties = input_api.rietveld.get_issue_properties( 
118         issue=int(issue), messages=False)
119     owner_email = issue_properties['owner_email']
120
121     try:
122       authors_content = ''
123       for line in open(AUTHORS_FILE_NAME):
124         if not line.startswith('#'):
125           authors_content += line
126       email_fnmatches = re.findall('<(.*)>', authors_content)
127       for email_fnmatch in email_fnmatches:
128         if fnmatch.fnmatch(owner_email, email_fnmatch):
129           # Found a match, the user is in the AUTHORS file break out of the loop
130           break
131       else:
132         # TODO(rmistry): Remove the below CLA messaging once a CLA checker has
133         # been added to the CQ.
134         results.append(
135           output_api.PresubmitError(
136             'The email %s is not in Skia\'s AUTHORS file.\n'
137             'Issue owner, this CL must include an addition to the Skia AUTHORS '
138             'file.\n'
139             'Googler reviewers, please check that the AUTHORS entry '
140             'corresponds to an email address in http://goto/cla-signers. If it '
141             'does not then ask the issue owner to sign the CLA at '
142             'https://developers.google.com/open-source/cla/individual '
143             '(individual) or '
144             'https://developers.google.com/open-source/cla/corporate '
145             '(corporate).'
146             % owner_email)) 
147     except IOError:
148       # Do not fail if authors file cannot be found.
149       traceback.print_exc()
150       input_api.logging.error('AUTHORS file not found!')
151
152   return results
153
154
155 def _CheckLGTMsForPublicAPI(input_api, output_api):
156   """Check LGTMs for public API changes.
157
158   For public API files make sure there is an LGTM from the list of owners in
159   PUBLIC_API_OWNERS.
160   """
161   results = []
162   requires_owner_check = False
163   for affected_svn_file in input_api.AffectedFiles():
164     affected_file_path = affected_svn_file.AbsoluteLocalPath()
165     file_path, file_ext = os.path.splitext(affected_file_path)
166     # We only care about files that end in .h and are under the include dir.
167     if file_ext == '.h' and 'include' in file_path.split(os.path.sep):
168       requires_owner_check = True
169
170   if not requires_owner_check:
171     return results
172
173   lgtm_from_owner = False
174   issue = input_api.change.issue
175   if issue and input_api.rietveld:
176     issue_properties = input_api.rietveld.get_issue_properties(
177         issue=int(issue), messages=True)
178     if re.match(REVERT_CL_SUBJECT_PREFIX, issue_properties['subject'], re.I):
179       # It is a revert CL, ignore the public api owners check.
180       return results
181     if issue_properties['owner_email'] in PUBLIC_API_OWNERS:
182       # An owner created the CL that is an automatic LGTM.
183       lgtm_from_owner = True
184
185     messages = issue_properties.get('messages')
186     if messages:
187       for message in messages:
188         if (message['sender'] in PUBLIC_API_OWNERS and
189             'lgtm' in message['text'].lower()):
190           # Found an lgtm in a message from an owner.
191           lgtm_from_owner = True
192           break;
193
194   if not lgtm_from_owner:
195     results.append(
196         output_api.PresubmitError(
197             'Since the CL is editing public API, you must have an LGTM from '
198             'one of: %s' % str(PUBLIC_API_OWNERS)))
199   return results
200
201
202 def CheckChangeOnCommit(input_api, output_api):
203   """Presubmit checks for the change on commit.
204
205   The following are the presubmit checks:
206   * Check change has one and only one EOL.
207   * Ensures that the Skia tree is open in
208     http://skia-tree-status.appspot.com/. Shows a warning if it is in 'Caution'
209     state and an error if it is in 'Closed' state.
210   """
211   results = []
212   results.extend(_CommonChecks(input_api, output_api))
213   results.extend(
214       _CheckTreeStatus(input_api, output_api, json_url=(
215           SKIA_TREE_STATUS_URL + '/banner-status?format=json')))
216   results.extend(_CheckLGTMsForPublicAPI(input_api, output_api))
217   results.extend(_CheckOwnerIsInAuthorsFile(input_api, output_api))
218   return results