ash: Update app list layout:
[chromium-blink-merge.git] / PRESUBMIT.py
blob87f089419eb057c1c858a64a54833db12eb5801a
1 # Copyright (c) 2012 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.
5 """Top-level presubmit script for Chromium.
7 See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts
8 for more details about the presubmit API built into gcl.
9 """
12 import re
15 _EXCLUDED_PATHS = (
16 r"^breakpad[\\\/].*",
17 r"^native_client_sdk[\\\/].*",
18 r"^net[\\\/]tools[\\\/]spdyshark[\\\/].*",
19 r"^skia[\\\/].*",
20 r"^v8[\\\/].*",
21 r".*MakeFile$",
22 r".+_autogen\.h$",
26 _TEST_ONLY_WARNING = (
27 'You might be calling functions intended only for testing from\n'
28 'production code. It is OK to ignore this warning if you know what\n'
29 'you are doing, as the heuristics used to detect the situation are\n'
30 'not perfect. The commit queue will not block on this warning.\n'
31 'Email joi@chromium.org if you have questions.')
35 def _CheckNoInterfacesInBase(input_api, output_api):
36 """Checks to make sure no files in libbase.a have |@interface|."""
37 pattern = input_api.re.compile(r'^\s*@interface', input_api.re.MULTILINE)
38 files = []
39 for f in input_api.AffectedSourceFiles(input_api.FilterSourceFile):
40 if (f.LocalPath().startswith('base/') and
41 not f.LocalPath().endswith('_unittest.mm')):
42 contents = input_api.ReadFile(f)
43 if pattern.search(contents):
44 files.append(f)
46 if len(files):
47 return [ output_api.PresubmitError(
48 'Objective-C interfaces or categories are forbidden in libbase. ' +
49 'See http://groups.google.com/a/chromium.org/group/chromium-dev/' +
50 'browse_thread/thread/efb28c10435987fd',
51 files) ]
52 return []
55 def _CheckNoProductionCodeUsingTestOnlyFunctions(input_api, output_api):
56 """Attempts to prevent use of functions intended only for testing in
57 non-testing code. For now this is just a best-effort implementation
58 that ignores header files and may have some false positives. A
59 better implementation would probably need a proper C++ parser.
60 """
61 # We only scan .cc files and the like, as the declaration of
62 # for-testing functions in header files are hard to distinguish from
63 # calls to such functions without a proper C++ parser.
64 source_extensions = r'\.(cc|cpp|cxx|mm)$'
65 file_inclusion_pattern = r'.+%s' % source_extensions
66 file_exclusion_patterns = (
67 r'.*[/\\](test_|mock_).+%s' % source_extensions,
68 r'.+_test_(base|support|util)%s' % source_extensions,
69 r'.+_(api|browser|perf|unit|ui)?test%s' % source_extensions,
70 r'.+profile_sync_service_harness%s' % source_extensions,
72 path_exclusion_patterns = (
73 r'.*[/\\](test|tool(s)?)[/\\].*',
74 # At request of folks maintaining this folder.
75 r'chrome[/\\]browser[/\\]automation[/\\].*',
78 base_function_pattern = r'ForTest(ing)?|for_test(ing)?'
79 inclusion_pattern = input_api.re.compile(r'(%s)\s*\(' % base_function_pattern)
80 exclusion_pattern = input_api.re.compile(
81 r'::[A-Za-z0-9_]+(%s)|(%s)[^;]+\{' % (
82 base_function_pattern, base_function_pattern))
84 def FilterFile(affected_file):
85 black_list = (file_exclusion_patterns + path_exclusion_patterns +
86 _EXCLUDED_PATHS + input_api.DEFAULT_BLACK_LIST)
87 return input_api.FilterSourceFile(
88 affected_file,
89 white_list=(file_inclusion_pattern, ),
90 black_list=black_list)
92 problems = []
93 for f in input_api.AffectedSourceFiles(FilterFile):
94 local_path = f.LocalPath()
95 lines = input_api.ReadFile(f).splitlines()
96 line_number = 0
97 for line in lines:
98 if (inclusion_pattern.search(line) and
99 not exclusion_pattern.search(line)):
100 problems.append(
101 '%s:%d\n %s' % (local_path, line_number, line.strip()))
102 line_number += 1
104 if problems:
105 if not input_api.is_committing:
106 return [output_api.PresubmitPromptWarning(_TEST_ONLY_WARNING, problems)]
107 else:
108 # We don't warn on commit, to avoid stopping commits going through CQ.
109 return [output_api.PresubmitNotifyResult(_TEST_ONLY_WARNING, problems)]
110 else:
111 return []
114 def _CheckNoIOStreamInHeaders(input_api, output_api):
115 """Checks to make sure no .h files include <iostream>."""
116 files = []
117 pattern = input_api.re.compile(r'^#include\s*<iostream>',
118 input_api.re.MULTILINE)
119 for f in input_api.AffectedSourceFiles(input_api.FilterSourceFile):
120 if not f.LocalPath().endswith('.h'):
121 continue
122 contents = input_api.ReadFile(f)
123 if pattern.search(contents):
124 files.append(f)
126 if len(files):
127 return [ output_api.PresubmitError(
128 'Do not #include <iostream> in header files, since it inserts static ' +
129 'initialization into every file including the header. Instead, ' +
130 '#include <ostream>. See http://crbug.com/94794',
131 files) ]
132 return []
135 def _CheckNoNewWStrings(input_api, output_api):
136 """Checks to make sure we don't introduce use of wstrings."""
137 problems = []
138 for f in input_api.AffectedFiles():
139 if (not f.LocalPath().endswith(('.cc', '.h')) or
140 f.LocalPath().endswith('test.cc')):
141 continue
143 for line_num, line in f.ChangedContents():
144 if 'wstring' in line:
145 problems.append(' %s:%d' % (f.LocalPath(), line_num))
147 if not problems:
148 return []
149 return [output_api.PresubmitPromptWarning('New code should not use wstrings.'
150 ' If you are calling an API that accepts a wstring, fix the API.\n' +
151 '\n'.join(problems))]
154 def _CheckNoDEPSGIT(input_api, output_api):
155 """Make sure .DEPS.git is never modified manually."""
156 if any(f.LocalPath().endswith('.DEPS.git') for f in
157 input_api.AffectedFiles()):
158 return [output_api.PresubmitError(
159 'Never commit changes to .DEPS.git. This file is maintained by an\n'
160 'automated system based on what\'s in DEPS and your changes will be\n'
161 'overwritten.\n'
162 'See http://code.google.com/p/chromium/wiki/UsingNewGit#Rolling_DEPS\n'
163 'for more information')]
164 return []
167 def _CheckNoFRIEND_TEST(input_api, output_api):
168 """Make sure that gtest's FRIEND_TEST() macro is not used, the
169 FRIEND_TEST_ALL_PREFIXES() macro from base/gtest_prod_util.h should be used
170 instead since that allows for FLAKY_, FAILS_ and DISABLED_ prefixes."""
171 problems = []
173 file_filter = lambda f: f.LocalPath().endswith(('.cc', '.h'))
174 for f in input_api.AffectedFiles(file_filter=file_filter):
175 for line_num, line in f.ChangedContents():
176 if 'FRIEND_TEST(' in line:
177 problems.append(' %s:%d' % (f.LocalPath(), line_num))
179 if not problems:
180 return []
181 return [output_api.PresubmitPromptWarning('Chromium code should not use '
182 'gtest\'s FRIEND_TEST() macro. Include base/gtest_prod_util.h and use '
183 'FRIEND_TEST_ALL_PREFIXES() instead.\n' + '\n'.join(problems))]
186 def _CheckNoScopedAllowIO(input_api, output_api):
187 """Make sure that ScopedAllowIO is not used."""
188 problems = []
190 file_filter = lambda f: f.LocalPath().endswith(('.cc', '.h'))
191 for f in input_api.AffectedFiles(file_filter=file_filter):
192 for line_num, line in f.ChangedContents():
193 if 'ScopedAllowIO' in line:
194 problems.append(' %s:%d' % (f.LocalPath(), line_num))
196 if not problems:
197 return []
198 return [output_api.PresubmitPromptWarning('New code should not use '
199 'ScopedAllowIO. Post a task to the blocking pool or the FILE thread '
200 'instead.\n' + '\n'.join(problems))]
203 def _CommonChecks(input_api, output_api):
204 """Checks common to both upload and commit."""
205 results = []
206 results.extend(input_api.canned_checks.PanProjectChecks(
207 input_api, output_api, excluded_paths=_EXCLUDED_PATHS))
208 results.extend(_CheckNoInterfacesInBase(input_api, output_api))
209 results.extend(_CheckAuthorizedAuthor(input_api, output_api))
210 results.extend(
211 _CheckNoProductionCodeUsingTestOnlyFunctions(input_api, output_api))
212 results.extend(_CheckNoIOStreamInHeaders(input_api, output_api))
213 results.extend(_CheckNoNewWStrings(input_api, output_api))
214 results.extend(_CheckNoDEPSGIT(input_api, output_api))
215 results.extend(_CheckNoFRIEND_TEST(input_api, output_api))
216 results.extend(_CheckNoScopedAllowIO(input_api, output_api))
217 return results
220 def _CheckSubversionConfig(input_api, output_api):
221 """Verifies the subversion config file is correctly setup.
223 Checks that autoprops are enabled, returns an error otherwise.
225 join = input_api.os_path.join
226 if input_api.platform == 'win32':
227 appdata = input_api.environ.get('APPDATA', '')
228 if not appdata:
229 return [output_api.PresubmitError('%APPDATA% is not configured.')]
230 path = join(appdata, 'Subversion', 'config')
231 else:
232 home = input_api.environ.get('HOME', '')
233 if not home:
234 return [output_api.PresubmitError('$HOME is not configured.')]
235 path = join(home, '.subversion', 'config')
237 error_msg = (
238 'Please look at http://dev.chromium.org/developers/coding-style to\n'
239 'configure your subversion configuration file. This enables automatic\n'
240 'properties to simplify the project maintenance.\n'
241 'Pro-tip: just download and install\n'
242 'http://src.chromium.org/viewvc/chrome/trunk/tools/build/slave/config\n')
244 try:
245 lines = open(path, 'r').read().splitlines()
246 # Make sure auto-props is enabled and check for 2 Chromium standard
247 # auto-prop.
248 if (not '*.cc = svn:eol-style=LF' in lines or
249 not '*.pdf = svn:mime-type=application/pdf' in lines or
250 not 'enable-auto-props = yes' in lines):
251 return [
252 output_api.PresubmitNotifyResult(
253 'It looks like you have not configured your subversion config '
254 'file or it is not up-to-date.\n' + error_msg)
256 except (OSError, IOError):
257 return [
258 output_api.PresubmitNotifyResult(
259 'Can\'t find your subversion config file.\n' + error_msg)
261 return []
264 def _CheckAuthorizedAuthor(input_api, output_api):
265 """For non-googler/chromites committers, verify the author's email address is
266 in AUTHORS.
268 # TODO(maruel): Add it to input_api?
269 import fnmatch
271 author = input_api.change.author_email
272 if not author:
273 input_api.logging.info('No author, skipping AUTHOR check')
274 return []
275 authors_path = input_api.os_path.join(
276 input_api.PresubmitLocalPath(), 'AUTHORS')
277 valid_authors = (
278 input_api.re.match(r'[^#]+\s+\<(.+?)\>\s*$', line)
279 for line in open(authors_path))
280 valid_authors = [item.group(1).lower() for item in valid_authors if item]
281 if input_api.verbose:
282 print 'Valid authors are %s' % ', '.join(valid_authors)
283 if not any(fnmatch.fnmatch(author.lower(), valid) for valid in valid_authors):
284 return [output_api.PresubmitPromptWarning(
285 ('%s is not in AUTHORS file. If you are a new contributor, please visit'
286 '\n'
287 'http://www.chromium.org/developers/contributing-code and read the '
288 '"Legal" section\n'
289 'If you are a chromite, verify the contributor signed the CLA.') %
290 author)]
291 return []
294 def CheckChangeOnUpload(input_api, output_api):
295 results = []
296 results.extend(_CommonChecks(input_api, output_api))
297 return results
300 def CheckChangeOnCommit(input_api, output_api):
301 results = []
302 results.extend(_CommonChecks(input_api, output_api))
303 # TODO(thestig) temporarily disabled, doesn't work in third_party/
304 #results.extend(input_api.canned_checks.CheckSvnModifiedDirectories(
305 # input_api, output_api, sources))
306 # Make sure the tree is 'open'.
307 results.extend(input_api.canned_checks.CheckTreeIsOpen(
308 input_api,
309 output_api,
310 json_url='http://chromium-status.appspot.com/current?format=json'))
311 results.extend(input_api.canned_checks.CheckRietveldTryJobExecution(input_api,
312 output_api, 'http://codereview.chromium.org',
313 ('win_rel', 'linux_rel', 'mac_rel, win:compile'),
314 'tryserver@chromium.org'))
316 results.extend(input_api.canned_checks.CheckChangeHasBugField(
317 input_api, output_api))
318 results.extend(input_api.canned_checks.CheckChangeHasTestField(
319 input_api, output_api))
320 results.extend(input_api.canned_checks.CheckChangeHasDescription(
321 input_api, output_api))
322 results.extend(_CheckSubversionConfig(input_api, output_api))
323 return results
326 def GetPreferredTrySlaves(project, change):
327 affected_files = change.LocalPaths()
328 only_objc_files = all(f.endswith(('.mm', '.m')) for f in affected_files)
329 if only_objc_files:
330 return ['mac_rel']
331 preferred = ['win_rel', 'linux_rel', 'mac_rel']
332 preferred = ['win_rel', 'linux_rel', 'mac_rel', 'linux_clang']
333 if any(f.endswith(('.h', '.cc', '.cpp', '.cxx')) for f in affected_files):
334 preferred.append('linux_clang')
335 aura_re = '_aura[^/]*[.][^/]*'
336 if any(re.search(aura_re, f) for f in affected_files):
337 preferred.append('linux_chromeos')
338 # Nothing in chrome/
339 android_re_list = ('^base/',
340 '^build/common.gypi$',
341 '^content/',
342 '^ipc/',
343 '^jingle/',
344 '^media/',
345 '^net/',
346 '^sql/')
347 # Nothing that looks like win-only or aura-only
348 win_re = '_win\.(cc|h)$'
349 possibly_android = True
350 for non_android_re in (aura_re, win_re):
351 if all(re.search(non_android_re, f) for f in affected_files):
352 possibly_android = False
353 break
354 if possibly_android:
355 for f in change.AffectedFiles():
356 if any(re.search(r, f.LocalPath()) for r in android_re_list):
357 preferred.append('android')
358 break
359 return preferred