blob: 69751a82dfed04a670ca3f0381839064f33c7a1f [file] [log] [blame]
andrew@webrtc.org2442de12012-01-23 17:45:41 +00001# Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
2#
3# Use of this source code is governed by a BSD-style license
4# that can be found in the LICENSE file in the root of the source
5# tree. An additional intellectual property rights grant can be found
6# in the file PATENTS. All contributing project authors may
7# be found in the AUTHORS file in the root of the source tree.
niklase@google.comda159d62011-05-30 11:51:34 +00008
kjellander7439f972016-12-05 22:47:46 -08009import json
kjellander@webrtc.orgaefe61a2014-12-08 13:00:30 +000010import os
kjellander@webrtc.org85759802013-10-22 16:47:40 +000011import re
kjellander@webrtc.org3bd41562014-09-01 11:06:37 +000012import sys
Mirko Bonadei4dc4e252017-09-19 13:49:16 +020013from collections import defaultdict
Oleh Prypin2f33a562017-10-04 20:17:54 +020014from contextlib import contextmanager
kjellander@webrtc.org85759802013-10-22 16:47:40 +000015
16
oprypin2aa463f2017-03-23 03:17:02 -070017# Files and directories that are *skipped* by cpplint in the presubmit script.
18CPPLINT_BLACKLIST = [
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020019 'api/video_codecs/video_decoder.h',
20 'common_types.cc',
21 'common_types.h',
22 'examples/objc',
Steve Antone78bcb92017-10-31 09:53:08 -070023 'media/base/streamparams.h',
24 'media/base/videocommon.h',
25 'media/engine/fakewebrtcdeviceinfo.h',
26 'media/sctp/sctptransport.cc',
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020027 'modules/audio_coding',
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020028 'modules/audio_device',
29 'modules/audio_processing',
30 'modules/desktop_capture',
31 'modules/include/module_common_types.h',
32 'modules/media_file',
33 'modules/utility',
34 'modules/video_capture',
35 'p2p',
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020036 'rtc_base',
37 'sdk/android/src/jni',
38 'sdk/objc',
39 'system_wrappers',
40 'test',
Henrik Kjellander90fd7d82017-05-09 08:30:10 +020041 'tools_webrtc',
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020042 'voice_engine',
kjellander@webrtc.org0fcaf992015-11-26 15:24:52 +010043]
44
jbauchc4e3ead2016-02-19 00:25:55 -080045# These filters will always be removed, even if the caller specifies a filter
46# set, as they are problematic or broken in some way.
47#
48# Justifications for each filter:
49# - build/c++11 : Rvalue ref checks are unreliable (false positives),
50# include file and feature blacklists are
51# google3-specific.
kjellandere5a87a52016-04-27 02:32:12 -070052# - whitespace/operators: Same as above (doesn't seem sufficient to eliminate
53# all move-related errors).
jbauchc4e3ead2016-02-19 00:25:55 -080054BLACKLIST_LINT_FILTERS = [
55 '-build/c++11',
kjellandere5a87a52016-04-27 02:32:12 -070056 '-whitespace/operators',
jbauchc4e3ead2016-02-19 00:25:55 -080057]
58
kjellanderfd595232015-12-04 02:44:09 -080059# List of directories of "supported" native APIs. That means changes to headers
60# will be done in a compatible way following this scheme:
61# 1. Non-breaking changes are made.
62# 2. The old APIs as marked as deprecated (with comments).
63# 3. Deprecation is announced to discuss-webrtc@googlegroups.com and
64# webrtc-users@google.com (internal list).
65# 4. (later) The deprecated APIs are removed.
kjellander53047c92015-12-02 23:56:14 -080066NATIVE_API_DIRS = (
Karl Wibergef52d8b82017-10-25 13:20:03 +020067 'api', # All subdirectories of api/ are included as well.
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020068 'media',
69 'modules/audio_device/include',
70 'pc',
kjellanderdd705472016-06-09 11:17:27 -070071)
Mirko Bonadei4dc4e252017-09-19 13:49:16 +020072
kjellanderdd705472016-06-09 11:17:27 -070073# These directories should not be used but are maintained only to avoid breaking
74# some legacy downstream code.
75LEGACY_API_DIRS = (
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020076 'common_audio/include',
77 'modules/audio_coding/include',
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020078 'modules/audio_processing/include',
79 'modules/bitrate_controller/include',
80 'modules/congestion_controller/include',
81 'modules/include',
82 'modules/remote_bitrate_estimator/include',
83 'modules/rtp_rtcp/include',
84 'modules/rtp_rtcp/source',
85 'modules/utility/include',
86 'modules/video_coding/codecs/h264/include',
87 'modules/video_coding/codecs/i420/include',
88 'modules/video_coding/codecs/vp8/include',
89 'modules/video_coding/codecs/vp9/include',
90 'modules/video_coding/include',
91 'rtc_base',
92 'system_wrappers/include',
93 'voice_engine/include',
kjellander53047c92015-12-02 23:56:14 -080094)
Mirko Bonadei4dc4e252017-09-19 13:49:16 +020095
kjellanderdd705472016-06-09 11:17:27 -070096API_DIRS = NATIVE_API_DIRS[:] + LEGACY_API_DIRS[:]
kjellander53047c92015-12-02 23:56:14 -080097
Mirko Bonadei4dc4e252017-09-19 13:49:16 +020098# TARGET_RE matches a GN target, and extracts the target name and the contents.
99TARGET_RE = re.compile(r'(?P<indent>\s*)\w+\("(?P<target_name>\w+)"\) {'
100 r'(?P<target_contents>.*?)'
101 r'(?P=indent)}',
102 re.MULTILINE | re.DOTALL)
103
104# SOURCES_RE matches a block of sources inside a GN target.
105SOURCES_RE = re.compile(r'sources \+?= \[(?P<sources>.*?)\]',
106 re.MULTILINE | re.DOTALL)
107
108# FILE_PATH_RE matchies a file path.
109FILE_PATH_RE = re.compile(r'"(?P<file_path>(\w|\/)+)(?P<extension>\.\w+)"')
110
kjellander53047c92015-12-02 23:56:14 -0800111
Oleh Prypin2f33a562017-10-04 20:17:54 +0200112@contextmanager
113def _AddToPath(*paths):
114 original_sys_path = sys.path
115 sys.path.extend(paths)
116 try:
117 yield
118 finally:
119 # Restore sys.path to what it was before.
120 sys.path = original_sys_path
ehmaldonado4fb97462017-01-30 05:27:22 -0800121
122
charujain9893e252017-09-14 13:33:22 +0200123def VerifyNativeApiHeadersListIsValid(input_api, output_api):
kjellander53047c92015-12-02 23:56:14 -0800124 """Ensures the list of native API header directories is up to date."""
125 non_existing_paths = []
126 native_api_full_paths = [
127 input_api.os_path.join(input_api.PresubmitLocalPath(),
kjellanderdd705472016-06-09 11:17:27 -0700128 *path.split('/')) for path in API_DIRS]
kjellander53047c92015-12-02 23:56:14 -0800129 for path in native_api_full_paths:
130 if not os.path.isdir(path):
131 non_existing_paths.append(path)
132 if non_existing_paths:
133 return [output_api.PresubmitError(
134 'Directories to native API headers have changed which has made the '
135 'list in PRESUBMIT.py outdated.\nPlease update it to the current '
136 'location of our native APIs.',
137 non_existing_paths)]
138 return []
139
kjellanderc88b5d52017-04-05 06:42:43 -0700140API_CHANGE_MSG = """
kwibergeb133022016-04-07 07:41:48 -0700141You seem to be changing native API header files. Please make sure that you:
oprypin375b9ac2017-02-13 04:13:23 -0800142 1. Make compatible changes that don't break existing clients. Usually
143 this is done by keeping the existing method signatures unchanged.
144 2. Mark the old stuff as deprecated (see RTC_DEPRECATED macro).
kwibergeb133022016-04-07 07:41:48 -0700145 3. Create a timeline and plan for when the deprecated stuff will be
146 removed. (The amount of time we give users to change their code
147 should be informed by how much work it is for them. If they just
148 need to replace one name with another or something equally
149 simple, 1-2 weeks might be good; if they need to do serious work,
150 up to 3 months may be called for.)
151 4. Update/inform existing downstream code owners to stop using the
152 deprecated stuff. (Send announcements to
153 discuss-webrtc@googlegroups.com and webrtc-users@google.com.)
154 5. Remove the deprecated stuff, once the agreed-upon amount of time
155 has passed.
156Related files:
157"""
kjellander53047c92015-12-02 23:56:14 -0800158
charujain9893e252017-09-14 13:33:22 +0200159def CheckNativeApiHeaderChanges(input_api, output_api):
kjellander53047c92015-12-02 23:56:14 -0800160 """Checks to remind proper changing of native APIs."""
161 files = []
Karl Wiberg6bfac032017-10-27 15:14:20 +0200162 source_file_filter = lambda x: input_api.FilterSourceFile(
163 x, white_list=[r'.+\.(gn|gni|h)$'])
164 for f in input_api.AffectedSourceFiles(source_file_filter):
165 for path in API_DIRS:
166 dn = os.path.dirname(f.LocalPath())
167 if path == 'api':
168 # Special case: Subdirectories included.
169 if dn == 'api' or dn.startswith('api/'):
170 files.append(f)
171 else:
172 # Normal case: Subdirectories not included.
173 if dn == path:
174 files.append(f)
kjellander53047c92015-12-02 23:56:14 -0800175
176 if files:
kjellanderc88b5d52017-04-05 06:42:43 -0700177 return [output_api.PresubmitNotifyResult(API_CHANGE_MSG, files)]
kjellander53047c92015-12-02 23:56:14 -0800178 return []
179
kjellander@webrtc.org0fcaf992015-11-26 15:24:52 +0100180
charujain9893e252017-09-14 13:33:22 +0200181def CheckNoIOStreamInHeaders(input_api, output_api):
kjellander@webrtc.org51198f12012-02-21 17:53:46 +0000182 """Checks to make sure no .h files include <iostream>."""
183 files = []
184 pattern = input_api.re.compile(r'^#include\s*<iostream>',
185 input_api.re.MULTILINE)
186 for f in input_api.AffectedSourceFiles(input_api.FilterSourceFile):
187 if not f.LocalPath().endswith('.h'):
188 continue
189 contents = input_api.ReadFile(f)
190 if pattern.search(contents):
191 files.append(f)
192
193 if len(files):
Henrik Kjellander57e5fd22015-05-25 12:55:39 +0200194 return [output_api.PresubmitError(
kjellander@webrtc.org51198f12012-02-21 17:53:46 +0000195 'Do not #include <iostream> in header files, since it inserts static ' +
196 'initialization into every file including the header. Instead, ' +
197 '#include <ostream>. See http://crbug.com/94794',
Henrik Kjellander57e5fd22015-05-25 12:55:39 +0200198 files)]
kjellander@webrtc.org51198f12012-02-21 17:53:46 +0000199 return []
200
kjellander@webrtc.orge4158642014-08-06 09:11:18 +0000201
charujain9893e252017-09-14 13:33:22 +0200202def CheckNoPragmaOnce(input_api, output_api):
kjellander6aeef742017-02-20 01:13:18 -0800203 """Make sure that banned functions are not used."""
204 files = []
205 pattern = input_api.re.compile(r'^#pragma\s+once',
206 input_api.re.MULTILINE)
207 for f in input_api.AffectedSourceFiles(input_api.FilterSourceFile):
208 if not f.LocalPath().endswith('.h'):
209 continue
210 contents = input_api.ReadFile(f)
211 if pattern.search(contents):
212 files.append(f)
213
214 if files:
215 return [output_api.PresubmitError(
216 'Do not use #pragma once in header files.\n'
217 'See http://www.chromium.org/developers/coding-style#TOC-File-headers',
218 files)]
219 return []
220
221
charujain9893e252017-09-14 13:33:22 +0200222def CheckNoFRIEND_TEST(input_api, output_api): # pylint: disable=invalid-name
kjellander@webrtc.org51198f12012-02-21 17:53:46 +0000223 """Make sure that gtest's FRIEND_TEST() macro is not used, the
224 FRIEND_TEST_ALL_PREFIXES() macro from testsupport/gtest_prod_util.h should be
225 used instead since that allows for FLAKY_, FAILS_ and DISABLED_ prefixes."""
226 problems = []
227
228 file_filter = lambda f: f.LocalPath().endswith(('.cc', '.h'))
229 for f in input_api.AffectedFiles(file_filter=file_filter):
230 for line_num, line in f.ChangedContents():
231 if 'FRIEND_TEST(' in line:
232 problems.append(' %s:%d' % (f.LocalPath(), line_num))
233
234 if not problems:
235 return []
236 return [output_api.PresubmitPromptWarning('WebRTC\'s code should not use '
237 'gtest\'s FRIEND_TEST() macro. Include testsupport/gtest_prod_util.h and '
238 'use FRIEND_TEST_ALL_PREFIXES() instead.\n' + '\n'.join(problems))]
239
kjellander@webrtc.orge4158642014-08-06 09:11:18 +0000240
charujain9893e252017-09-14 13:33:22 +0200241def IsLintBlacklisted(blacklist_paths, file_path):
oprypin2aa463f2017-03-23 03:17:02 -0700242 """ Checks if a file is blacklisted for lint check."""
243 for path in blacklist_paths:
244 if file_path == path or os.path.dirname(file_path).startswith(path):
kjellander@webrtc.org0fcaf992015-11-26 15:24:52 +0100245 return True
246 return False
247
248
charujain9893e252017-09-14 13:33:22 +0200249def CheckApprovedFilesLintClean(input_api, output_api,
mflodman@webrtc.org2a452092012-07-01 05:55:23 +0000250 source_file_filter=None):
oprypin2aa463f2017-03-23 03:17:02 -0700251 """Checks that all new or non-blacklisted .cc and .h files pass cpplint.py.
charujain9893e252017-09-14 13:33:22 +0200252 This check is based on CheckChangeLintsClean in
kjellander@webrtc.org51198f12012-02-21 17:53:46 +0000253 depot_tools/presubmit_canned_checks.py but has less filters and only checks
254 added files."""
255 result = []
256
257 # Initialize cpplint.
258 import cpplint
259 # Access to a protected member _XX of a client class
260 # pylint: disable=W0212
261 cpplint._cpplint_state.ResetErrorCounts()
262
jbauchc4e3ead2016-02-19 00:25:55 -0800263 lint_filters = cpplint._Filters()
264 lint_filters.extend(BLACKLIST_LINT_FILTERS)
265 cpplint._SetFilters(','.join(lint_filters))
266
oprypin2aa463f2017-03-23 03:17:02 -0700267 # Create a platform independent blacklist for cpplint.
268 blacklist_paths = [input_api.os_path.join(*path.split('/'))
269 for path in CPPLINT_BLACKLIST]
kjellander@webrtc.org0fcaf992015-11-26 15:24:52 +0100270
kjellander@webrtc.org51198f12012-02-21 17:53:46 +0000271 # Use the strictest verbosity level for cpplint.py (level 1) which is the
oprypin2aa463f2017-03-23 03:17:02 -0700272 # default when running cpplint.py from command line. To make it possible to
273 # work with not-yet-converted code, we're only applying it to new (or
274 # moved/renamed) files and files not listed in CPPLINT_BLACKLIST.
kjellander@webrtc.org51198f12012-02-21 17:53:46 +0000275 verbosity_level = 1
276 files = []
277 for f in input_api.AffectedSourceFiles(source_file_filter):
Henrik Kjellander57e5fd22015-05-25 12:55:39 +0200278 # Note that moved/renamed files also count as added.
charujain9893e252017-09-14 13:33:22 +0200279 if f.Action() == 'A' or not IsLintBlacklisted(blacklist_paths,
oprypin2aa463f2017-03-23 03:17:02 -0700280 f.LocalPath()):
kjellander@webrtc.org51198f12012-02-21 17:53:46 +0000281 files.append(f.AbsoluteLocalPath())
mflodman@webrtc.org2a452092012-07-01 05:55:23 +0000282
kjellander@webrtc.org51198f12012-02-21 17:53:46 +0000283 for file_name in files:
284 cpplint.ProcessFile(file_name, verbosity_level)
285
286 if cpplint._cpplint_state.error_count > 0:
287 if input_api.is_committing:
oprypin8e58d652017-03-21 07:52:41 -0700288 res_type = output_api.PresubmitError
kjellander@webrtc.org51198f12012-02-21 17:53:46 +0000289 else:
290 res_type = output_api.PresubmitPromptWarning
291 result = [res_type('Changelist failed cpplint.py check.')]
292
293 return result
294
charujain9893e252017-09-14 13:33:22 +0200295def CheckNoSourcesAbove(input_api, gn_files, output_api):
ehmaldonado5b1ba082016-09-02 05:51:08 -0700296 # Disallow referencing source files with paths above the GN file location.
297 source_pattern = input_api.re.compile(r' +sources \+?= \[(.*?)\]',
298 re.MULTILINE | re.DOTALL)
299 file_pattern = input_api.re.compile(r'"((\.\./.*?)|(//.*?))"')
300 violating_gn_files = set()
301 violating_source_entries = []
302 for gn_file in gn_files:
303 contents = input_api.ReadFile(gn_file)
304 for source_block_match in source_pattern.finditer(contents):
305 # Find all source list entries starting with ../ in the source block
306 # (exclude overrides entries).
307 for file_list_match in file_pattern.finditer(source_block_match.group(1)):
308 source_file = file_list_match.group(1)
309 if 'overrides/' not in source_file:
310 violating_source_entries.append(source_file)
311 violating_gn_files.add(gn_file)
312 if violating_gn_files:
313 return [output_api.PresubmitError(
314 'Referencing source files above the directory of the GN file is not '
Henrik Kjellanderb4af3d62016-11-16 20:11:29 +0100315 'allowed. Please introduce new GN targets in the proper location '
316 'instead.\n'
ehmaldonado5b1ba082016-09-02 05:51:08 -0700317 'Invalid source entries:\n'
318 '%s\n'
319 'Violating GN files:' % '\n'.join(violating_source_entries),
320 items=violating_gn_files)]
321 return []
322
Mirko Bonadei4dc4e252017-09-19 13:49:16 +0200323def CheckNoMixingSources(input_api, gn_files, output_api):
324 """Disallow mixing C, C++ and Obj-C/Obj-C++ in the same target.
325
326 See bugs.webrtc.org/7743 for more context.
327 """
328 def _MoreThanOneSourceUsed(*sources_lists):
329 sources_used = 0
330 for source_list in sources_lists:
331 if len(source_list):
332 sources_used += 1
333 return sources_used > 1
334
335 errors = defaultdict(lambda: [])
kjellander7439f972016-12-05 22:47:46 -0800336 for gn_file in gn_files:
Mirko Bonadei4dc4e252017-09-19 13:49:16 +0200337 gn_file_content = input_api.ReadFile(gn_file)
338 for target_match in TARGET_RE.finditer(gn_file_content):
339 # list_of_sources is a list of tuples of the form
340 # (c_files, cc_files, objc_files) that keeps track of all the sources
341 # defined in a target. A GN target can have more that on definition of
342 # sources (since it supports if/else statements).
343 # E.g.:
344 # rtc_static_library("foo") {
345 # if (is_win) {
346 # sources = [ "foo.cc" ]
347 # } else {
348 # sources = [ "foo.mm" ]
349 # }
350 # }
351 # This is allowed and the presubmit check should support this case.
352 list_of_sources = []
kjellander7439f972016-12-05 22:47:46 -0800353 c_files = []
354 cc_files = []
Mirko Bonadei4dc4e252017-09-19 13:49:16 +0200355 objc_files = []
356 target_name = target_match.group('target_name')
357 target_contents = target_match.group('target_contents')
358 for sources_match in SOURCES_RE.finditer(target_contents):
359 if '+=' not in sources_match.group(0):
360 if c_files or cc_files or objc_files:
361 list_of_sources.append((c_files, cc_files, objc_files))
362 c_files = []
363 cc_files = []
364 objc_files = []
365 for file_match in FILE_PATH_RE.finditer(sources_match.group(1)):
366 file_path = file_match.group('file_path')
367 extension = file_match.group('extension')
368 if extension == '.c':
369 c_files.append(file_path + extension)
370 if extension == '.cc':
371 cc_files.append(file_path + extension)
372 if extension in ['.m', '.mm']:
373 objc_files.append(file_path + extension)
374 list_of_sources.append((c_files, cc_files, objc_files))
375 for c_files_list, cc_files_list, objc_files_list in list_of_sources:
376 if _MoreThanOneSourceUsed(c_files_list, cc_files_list, objc_files_list):
377 all_sources = sorted(c_files_list + cc_files_list + objc_files_list)
378 errors[gn_file.LocalPath()].append((target_name, all_sources))
379 if errors:
kjellander7439f972016-12-05 22:47:46 -0800380 return [output_api.PresubmitError(
Mirko Bonadei4dc4e252017-09-19 13:49:16 +0200381 'GN targets cannot mix .c, .cc and .m (or .mm) source files.\n'
382 'Please create a separate target for each collection of sources.\n'
kjellander7439f972016-12-05 22:47:46 -0800383 'Mixed sources: \n'
384 '%s\n'
Mirko Bonadei4dc4e252017-09-19 13:49:16 +0200385 'Violating GN files:\n%s\n' % (json.dumps(errors, indent=2),
386 '\n'.join(errors.keys())))]
kjellander7439f972016-12-05 22:47:46 -0800387 return []
388
charujain9893e252017-09-14 13:33:22 +0200389def CheckNoPackageBoundaryViolations(input_api, gn_files, output_api):
ehmaldonado4fb97462017-01-30 05:27:22 -0800390 cwd = input_api.PresubmitLocalPath()
Oleh Prypin2f33a562017-10-04 20:17:54 +0200391 with _AddToPath(input_api.os_path.join(
392 cwd, 'tools_webrtc', 'presubmit_checks_lib')):
393 from check_package_boundaries import CheckPackageBoundaries
394 build_files = [os.path.join(cwd, gn_file.LocalPath()) for gn_file in gn_files]
395 errors = CheckPackageBoundaries(cwd, build_files)[:5]
396 if errors:
ehmaldonado4fb97462017-01-30 05:27:22 -0800397 return [output_api.PresubmitError(
Oleh Prypin2f33a562017-10-04 20:17:54 +0200398 'There are package boundary violations in the following GN files:',
399 long_text='\n\n'.join(str(err) for err in errors))]
ehmaldonado4fb97462017-01-30 05:27:22 -0800400 return []
401
charujain9893e252017-09-14 13:33:22 +0200402def CheckGnChanges(input_api, output_api):
ehmaldonado5b1ba082016-09-02 05:51:08 -0700403 source_file_filter = lambda x: input_api.FilterSourceFile(
Oleh Prypinafe01652017-10-04 15:56:08 +0200404 x, white_list=(r'.+\.(gn|gni)$',),
405 black_list=r'.*/presubmit_checks_lib/testdata/.*')
ehmaldonado5b1ba082016-09-02 05:51:08 -0700406
407 gn_files = []
408 for f in input_api.AffectedSourceFiles(source_file_filter):
Mirko Bonadei92ea95e2017-09-15 06:47:31 +0200409 gn_files.append(f)
ehmaldonado5b1ba082016-09-02 05:51:08 -0700410
411 result = []
412 if gn_files:
charujain9893e252017-09-14 13:33:22 +0200413 result.extend(CheckNoSourcesAbove(input_api, gn_files, output_api))
Mirko Bonadei4dc4e252017-09-19 13:49:16 +0200414 result.extend(CheckNoMixingSources(input_api, gn_files, output_api))
415 result.extend(CheckNoPackageBoundaryViolations(input_api, gn_files,
416 output_api))
ehmaldonado5b1ba082016-09-02 05:51:08 -0700417 return result
418
Oleh Prypin920b6532017-10-05 11:28:51 +0200419def CheckGnGen(input_api, output_api):
420 """Runs `gn gen --check` with default args to detect mismatches between
421 #includes and dependencies in the BUILD.gn files, as well as general build
422 errors.
423 """
424 with _AddToPath(input_api.os_path.join(
425 input_api.PresubmitLocalPath(), 'tools_webrtc', 'presubmit_checks_lib')):
426 from gn_check import RunGnCheck
427 errors = RunGnCheck(input_api.PresubmitLocalPath())[:5]
428 if errors:
429 return [output_api.PresubmitPromptWarning(
430 'Some #includes do not match the build dependency graph. Please run:\n'
431 ' gn gen --check <out_dir>',
432 long_text='\n\n'.join(errors))]
433 return []
434
charujain9893e252017-09-14 13:33:22 +0200435def CheckUnwantedDependencies(input_api, output_api):
kjellander@webrtc.org3bd41562014-09-01 11:06:37 +0000436 """Runs checkdeps on #include statements added in this
437 change. Breaking - rules is an error, breaking ! rules is a
438 warning.
439 """
440 # Copied from Chromium's src/PRESUBMIT.py.
441
442 # We need to wait until we have an input_api object and use this
443 # roundabout construct to import checkdeps because this file is
444 # eval-ed and thus doesn't have __file__.
Oleh Prypin2f33a562017-10-04 20:17:54 +0200445 checkdeps_path = input_api.os_path.join(input_api.PresubmitLocalPath(),
446 'buildtools', 'checkdeps')
447 if not os.path.exists(checkdeps_path):
448 return [output_api.PresubmitError(
449 'Cannot find checkdeps at %s\nHave you run "gclient sync" to '
450 'download all the DEPS entries?' % checkdeps_path)]
451 with _AddToPath(checkdeps_path):
kjellander@webrtc.org3bd41562014-09-01 11:06:37 +0000452 import checkdeps
453 from cpp_checker import CppChecker
454 from rules import Rule
kjellander@webrtc.org3bd41562014-09-01 11:06:37 +0000455
456 added_includes = []
457 for f in input_api.AffectedFiles():
458 if not CppChecker.IsCppFile(f.LocalPath()):
459 continue
460
Henrik Kjellander57e5fd22015-05-25 12:55:39 +0200461 changed_lines = [line for _, line in f.ChangedContents()]
kjellander@webrtc.org3bd41562014-09-01 11:06:37 +0000462 added_includes.append([f.LocalPath(), changed_lines])
463
464 deps_checker = checkdeps.DepsChecker(input_api.PresubmitLocalPath())
465
466 error_descriptions = []
467 warning_descriptions = []
468 for path, rule_type, rule_description in deps_checker.CheckAddedCppIncludes(
469 added_includes):
470 description_with_path = '%s\n %s' % (path, rule_description)
471 if rule_type == Rule.DISALLOW:
472 error_descriptions.append(description_with_path)
473 else:
474 warning_descriptions.append(description_with_path)
475
476 results = []
477 if error_descriptions:
478 results.append(output_api.PresubmitError(
kjellandera7066a32017-03-23 03:47:05 -0700479 'You added one or more #includes that violate checkdeps rules.\n'
480 'Check that the DEPS files in these locations contain valid rules.\n'
481 'See https://cs.chromium.org/chromium/src/buildtools/checkdeps/ for '
482 'more details about checkdeps.',
kjellander@webrtc.org3bd41562014-09-01 11:06:37 +0000483 error_descriptions))
484 if warning_descriptions:
485 results.append(output_api.PresubmitPromptOrNotify(
486 'You added one or more #includes of files that are temporarily\n'
487 'allowed but being removed. Can you avoid introducing the\n'
kjellandera7066a32017-03-23 03:47:05 -0700488 '#include? See relevant DEPS file(s) for details and contacts.\n'
489 'See https://cs.chromium.org/chromium/src/buildtools/checkdeps/ for '
490 'more details about checkdeps.',
kjellander@webrtc.org3bd41562014-09-01 11:06:37 +0000491 warning_descriptions))
492 return results
493
charujain9893e252017-09-14 13:33:22 +0200494def CheckCommitMessageBugEntry(input_api, output_api):
495 """Check that bug entries are well-formed in commit message."""
496 bogus_bug_msg = (
Mirko Bonadei61880182017-10-12 15:12:35 +0200497 'Bogus Bug entry: %s. Please specify the issue tracker prefix and the '
charujain9893e252017-09-14 13:33:22 +0200498 'issue number, separated by a colon, e.g. webrtc:123 or chromium:12345.')
499 results = []
Mirko Bonadei61880182017-10-12 15:12:35 +0200500 for bug in input_api.change.BugsFromDescription():
charujain9893e252017-09-14 13:33:22 +0200501 bug = bug.strip()
502 if bug.lower() == 'none':
503 continue
charujain81a58c72017-09-25 13:25:45 +0200504 if 'b/' not in bug and ':' not in bug:
charujain9893e252017-09-14 13:33:22 +0200505 try:
506 if int(bug) > 100000:
507 # Rough indicator for current chromium bugs.
508 prefix_guess = 'chromium'
509 else:
510 prefix_guess = 'webrtc'
Mirko Bonadei61880182017-10-12 15:12:35 +0200511 results.append('Bug entry requires issue tracker prefix, e.g. %s:%s' %
charujain9893e252017-09-14 13:33:22 +0200512 (prefix_guess, bug))
513 except ValueError:
514 results.append(bogus_bug_msg % bug)
charujain81a58c72017-09-25 13:25:45 +0200515 elif not (re.match(r'\w+:\d+', bug) or re.match(r'b/\d+', bug)):
charujain9893e252017-09-14 13:33:22 +0200516 results.append(bogus_bug_msg % bug)
517 return [output_api.PresubmitError(r) for r in results]
518
519def CheckChangeHasBugField(input_api, output_api):
Mirko Bonadei61880182017-10-12 15:12:35 +0200520 """Requires that the changelist is associated with a bug.
kjellanderd1e26a92016-09-19 08:11:16 -0700521
522 This check is stricter than the one in depot_tools/presubmit_canned_checks.py
Mirko Bonadei61880182017-10-12 15:12:35 +0200523 since it fails the presubmit if the bug field is missing or doesn't contain
kjellanderd1e26a92016-09-19 08:11:16 -0700524 a bug reference.
Mirko Bonadei61880182017-10-12 15:12:35 +0200525
526 This supports both 'BUG=' and 'Bug:' since we are in the process of migrating
527 to Gerrit and it encourages the usage of 'Bug:'.
kjellanderd1e26a92016-09-19 08:11:16 -0700528 """
Mirko Bonadei61880182017-10-12 15:12:35 +0200529 if input_api.change.BugsFromDescription():
kjellanderd1e26a92016-09-19 08:11:16 -0700530 return []
531 else:
532 return [output_api.PresubmitError(
Mirko Bonadei61880182017-10-12 15:12:35 +0200533 'The "Bug: [bug number]" footer is mandatory. Please create a bug and '
kjellanderd1e26a92016-09-19 08:11:16 -0700534 'reference it using either of:\n'
Mirko Bonadei61880182017-10-12 15:12:35 +0200535 ' * https://bugs.webrtc.org - reference it using Bug: webrtc:XXXX\n'
536 ' * https://crbug.com - reference it using Bug: chromium:XXXXXX')]
kjellander@webrtc.orge4158642014-08-06 09:11:18 +0000537
charujain9893e252017-09-14 13:33:22 +0200538def CheckJSONParseErrors(input_api, output_api):
kjellander569cf942016-02-11 05:02:59 -0800539 """Check that JSON files do not contain syntax errors."""
540
541 def FilterFile(affected_file):
542 return input_api.os_path.splitext(affected_file.LocalPath())[1] == '.json'
543
544 def GetJSONParseError(input_api, filename):
545 try:
546 contents = input_api.ReadFile(filename)
547 input_api.json.loads(contents)
548 except ValueError as e:
549 return e
550 return None
551
552 results = []
553 for affected_file in input_api.AffectedFiles(
554 file_filter=FilterFile, include_deletes=False):
555 parse_error = GetJSONParseError(input_api,
556 affected_file.AbsoluteLocalPath())
557 if parse_error:
558 results.append(output_api.PresubmitError('%s could not be parsed: %s' %
559 (affected_file.LocalPath(), parse_error)))
560 return results
561
562
charujain9893e252017-09-14 13:33:22 +0200563def RunPythonTests(input_api, output_api):
kjellanderc88b5d52017-04-05 06:42:43 -0700564 def Join(*args):
Henrik Kjellander8d3ad822015-05-26 19:52:05 +0200565 return input_api.os_path.join(input_api.PresubmitLocalPath(), *args)
566
567 test_directories = [
Edward Lemur6d01f6d2017-09-14 17:02:01 +0200568 input_api.PresubmitLocalPath(),
Mirko Bonadei92ea95e2017-09-15 06:47:31 +0200569 Join('rtc_tools', 'py_event_log_analyzer'),
570 Join('rtc_tools'),
571 Join('audio', 'test', 'unittests'),
ehmaldonado4fb97462017-01-30 05:27:22 -0800572 ] + [
Henrik Kjellander90fd7d82017-05-09 08:30:10 +0200573 root for root, _, files in os.walk(Join('tools_webrtc'))
ehmaldonado4fb97462017-01-30 05:27:22 -0800574 if any(f.endswith('_test.py') for f in files)
Henrik Kjellander8d3ad822015-05-26 19:52:05 +0200575 ]
576
577 tests = []
578 for directory in test_directories:
579 tests.extend(
580 input_api.canned_checks.GetUnitTestsInDirectory(
581 input_api,
582 output_api,
583 directory,
584 whitelist=[r'.+_test\.py$']))
585 return input_api.RunTests(tests, parallel=True)
586
587
charujain9893e252017-09-14 13:33:22 +0200588def CheckUsageOfGoogleProtobufNamespace(input_api, output_api):
mbonadei38415b22017-04-07 05:38:01 -0700589 """Checks that the namespace google::protobuf has not been used."""
590 files = []
591 pattern = input_api.re.compile(r'google::protobuf')
Mirko Bonadei92ea95e2017-09-15 06:47:31 +0200592 proto_utils_path = os.path.join('rtc_base', 'protobuf_utils.h')
mbonadei38415b22017-04-07 05:38:01 -0700593 for f in input_api.AffectedSourceFiles(input_api.FilterSourceFile):
594 if f.LocalPath() in [proto_utils_path, 'PRESUBMIT.py']:
595 continue
596 contents = input_api.ReadFile(f)
597 if pattern.search(contents):
598 files.append(f)
599
600 if files:
601 return [output_api.PresubmitError(
602 'Please avoid to use namespace `google::protobuf` directly.\n'
603 'Add a using directive in `%s` and include that header instead.'
604 % proto_utils_path, files)]
605 return []
606
607
Mirko Bonadei92ea95e2017-09-15 06:47:31 +0200608def _LicenseHeader(input_api):
609 """Returns the license header regexp."""
610 # Accept any year number from 2003 to the current year
611 current_year = int(input_api.time.strftime('%Y'))
612 allowed_years = (str(s) for s in reversed(xrange(2003, current_year + 1)))
613 years_re = '(' + '|'.join(allowed_years) + ')'
614 license_header = (
615 r'.*? Copyright( \(c\))? %(year)s The WebRTC [Pp]roject [Aa]uthors\. '
616 r'All [Rr]ights [Rr]eserved\.\n'
617 r'.*?\n'
618 r'.*? Use of this source code is governed by a BSD-style license\n'
619 r'.*? that can be found in the LICENSE file in the root of the source\n'
620 r'.*? tree\. An additional intellectual property rights grant can be '
621 r'found\n'
622 r'.*? in the file PATENTS\. All contributing project authors may\n'
623 r'.*? be found in the AUTHORS file in the root of the source tree\.\n'
624 ) % {
625 'year': years_re,
626 }
627 return license_header
628
629
charujain9893e252017-09-14 13:33:22 +0200630def CommonChecks(input_api, output_api):
andrew@webrtc.org53df1362012-01-26 21:24:23 +0000631 """Checks common to both upload and commit."""
niklase@google.comda159d62011-05-30 11:51:34 +0000632 results = []
tkchin42f580e2015-11-26 23:18:23 -0800633 # Filter out files that are in objc or ios dirs from being cpplint-ed since
634 # they do not follow C++ lint rules.
635 black_list = input_api.DEFAULT_BLACK_LIST + (
636 r".*\bobjc[\\\/].*",
Kári Tristan Helgason3fa35172016-09-09 08:55:05 +0000637 r".*objc\.[hcm]+$",
tkchin42f580e2015-11-26 23:18:23 -0800638 )
639 source_file_filter = lambda x: input_api.FilterSourceFile(x, None, black_list)
charujain9893e252017-09-14 13:33:22 +0200640 results.extend(CheckApprovedFilesLintClean(
tkchin42f580e2015-11-26 23:18:23 -0800641 input_api, output_api, source_file_filter))
Mirko Bonadei92ea95e2017-09-15 06:47:31 +0200642 results.extend(input_api.canned_checks.CheckLicense(
643 input_api, output_api, _LicenseHeader(input_api)))
phoglund@webrtc.org5d3713932013-03-07 09:59:43 +0000644 results.extend(input_api.canned_checks.RunPylint(input_api, output_api,
kjellander@webrtc.org177567c2016-12-22 10:40:28 +0100645 black_list=(r'^base[\\\/].*\.py$',
Henrik Kjellander14771ac2015-06-02 13:10:04 +0200646 r'^build[\\\/].*\.py$',
647 r'^buildtools[\\\/].*\.py$',
kjellander38c65c82017-04-12 22:43:38 -0700648 r'^infra[\\\/].*\.py$',
Henrik Kjellander0779e8f2016-12-22 12:01:17 +0100649 r'^ios[\\\/].*\.py$',
Henrik Kjellander14771ac2015-06-02 13:10:04 +0200650 r'^out.*[\\\/].*\.py$',
651 r'^testing[\\\/].*\.py$',
652 r'^third_party[\\\/].*\.py$',
kjellander@webrtc.org177567c2016-12-22 10:40:28 +0100653 r'^tools[\\\/].*\.py$',
kjellanderafd54942016-12-17 12:21:39 -0800654 # TODO(phoglund): should arguably be checked.
Henrik Kjellander90fd7d82017-05-09 08:30:10 +0200655 r'^tools_webrtc[\\\/]mb[\\\/].*\.py$',
656 r'^tools_webrtc[\\\/]valgrind[\\\/].*\.py$',
Henrik Kjellander14771ac2015-06-02 13:10:04 +0200657 r'^xcodebuild.*[\\\/].*\.py$',),
Henrik Kjellander57e5fd22015-05-25 12:55:39 +0200658 pylintrc='pylintrc'))
kjellander569cf942016-02-11 05:02:59 -0800659
nisse3d21e232016-09-02 03:07:06 -0700660 # TODO(nisse): talk/ is no more, so make below checks simpler?
Henrik Kjellander57e5fd22015-05-25 12:55:39 +0200661 # WebRTC can't use the presubmit_canned_checks.PanProjectChecks function since
662 # we need to have different license checks in talk/ and webrtc/ directories.
663 # Instead, hand-picked checks are included below.
Henrik Kjellander63224672015-09-08 08:03:56 +0200664
tkchin3cd9a302016-06-08 12:40:28 -0700665 # .m and .mm files are ObjC files. For simplicity we will consider .h files in
666 # ObjC subdirectories ObjC headers.
667 objc_filter_list = (r'.+\.m$', r'.+\.mm$', r'.+objc\/.+\.h$')
Henrik Kjellanderb4af3d62016-11-16 20:11:29 +0100668 # Skip long-lines check for DEPS and GN files.
669 build_file_filter_list = (r'.+\.gn$', r'.+\.gni$', 'DEPS')
tkchin3cd9a302016-06-08 12:40:28 -0700670 eighty_char_sources = lambda x: input_api.FilterSourceFile(x,
671 black_list=build_file_filter_list + objc_filter_list)
672 hundred_char_sources = lambda x: input_api.FilterSourceFile(x,
673 white_list=objc_filter_list)
andrew@webrtc.org2442de12012-01-23 17:45:41 +0000674 results.extend(input_api.canned_checks.CheckLongLines(
tkchin3cd9a302016-06-08 12:40:28 -0700675 input_api, output_api, maxlen=80, source_file_filter=eighty_char_sources))
676 results.extend(input_api.canned_checks.CheckLongLines(
677 input_api, output_api, maxlen=100,
678 source_file_filter=hundred_char_sources))
679
andrew@webrtc.org2442de12012-01-23 17:45:41 +0000680 results.extend(input_api.canned_checks.CheckChangeHasNoTabs(
681 input_api, output_api))
andrew@webrtc.org53df1362012-01-26 21:24:23 +0000682 results.extend(input_api.canned_checks.CheckChangeHasNoStrayWhitespace(
683 input_api, output_api))
kjellandere5dc62a2016-12-14 00:16:21 -0800684 results.extend(input_api.canned_checks.CheckAuthorizedAuthor(
685 input_api, output_api))
andrew@webrtc.org53df1362012-01-26 21:24:23 +0000686 results.extend(input_api.canned_checks.CheckChangeTodoHasOwner(
687 input_api, output_api))
charujain9893e252017-09-14 13:33:22 +0200688 results.extend(CheckNativeApiHeaderChanges(input_api, output_api))
689 results.extend(CheckNoIOStreamInHeaders(input_api, output_api))
690 results.extend(CheckNoPragmaOnce(input_api, output_api))
691 results.extend(CheckNoFRIEND_TEST(input_api, output_api))
692 results.extend(CheckGnChanges(input_api, output_api))
693 results.extend(CheckUnwantedDependencies(input_api, output_api))
694 results.extend(CheckJSONParseErrors(input_api, output_api))
695 results.extend(RunPythonTests(input_api, output_api))
696 results.extend(CheckUsageOfGoogleProtobufNamespace(input_api, output_api))
Mirko Bonadei866d3372017-09-15 12:35:26 +0200697 results.extend(CheckOrphanHeaders(input_api, output_api))
Mirko Bonadeia730c1c2017-09-18 11:33:13 +0200698 results.extend(CheckNewlineAtTheEndOfProtoFiles(input_api, output_api))
andrew@webrtc.org53df1362012-01-26 21:24:23 +0000699 return results
andrew@webrtc.org2442de12012-01-23 17:45:41 +0000700
kjellander@webrtc.orge4158642014-08-06 09:11:18 +0000701
andrew@webrtc.org53df1362012-01-26 21:24:23 +0000702def CheckChangeOnUpload(input_api, output_api):
703 results = []
charujain9893e252017-09-14 13:33:22 +0200704 results.extend(CommonChecks(input_api, output_api))
Oleh Prypin920b6532017-10-05 11:28:51 +0200705 results.extend(CheckGnGen(input_api, output_api))
Henrik Kjellander57e5fd22015-05-25 12:55:39 +0200706 results.extend(
707 input_api.canned_checks.CheckGNFormatted(input_api, output_api))
niklase@google.comda159d62011-05-30 11:51:34 +0000708 return results
709
kjellander@webrtc.orge4158642014-08-06 09:11:18 +0000710
andrew@webrtc.org2442de12012-01-23 17:45:41 +0000711def CheckChangeOnCommit(input_api, output_api):
niklase@google.com1198db92011-06-09 07:07:24 +0000712 results = []
charujain9893e252017-09-14 13:33:22 +0200713 results.extend(CommonChecks(input_api, output_api))
714 results.extend(VerifyNativeApiHeadersListIsValid(input_api, output_api))
niklase@google.com1198db92011-06-09 07:07:24 +0000715 results.extend(input_api.canned_checks.CheckOwners(input_api, output_api))
andrew@webrtc.org53df1362012-01-26 21:24:23 +0000716 results.extend(input_api.canned_checks.CheckChangeWasUploaded(
717 input_api, output_api))
718 results.extend(input_api.canned_checks.CheckChangeHasDescription(
719 input_api, output_api))
charujain9893e252017-09-14 13:33:22 +0200720 results.extend(CheckChangeHasBugField(input_api, output_api))
721 results.extend(CheckCommitMessageBugEntry(input_api, output_api))
kjellander@webrtc.org12cb88c2014-02-13 11:53:43 +0000722 results.extend(input_api.canned_checks.CheckTreeIsOpen(
723 input_api, output_api,
724 json_url='http://webrtc-status.appspot.com/current?format=json'))
niklase@google.com1198db92011-06-09 07:07:24 +0000725 return results
mbonadei74973ed2017-05-09 07:58:05 -0700726
727
charujain9893e252017-09-14 13:33:22 +0200728def CheckOrphanHeaders(input_api, output_api):
mbonadei74973ed2017-05-09 07:58:05 -0700729 # We need to wait until we have an input_api object and use this
730 # roundabout construct to import prebubmit_checks_lib because this file is
731 # eval-ed and thus doesn't have __file__.
732 error_msg = """Header file {} is not listed in any GN target.
733 Please create a target or add it to an existing one in {}"""
734 results = []
Oleh Prypin2f33a562017-10-04 20:17:54 +0200735 with _AddToPath(input_api.os_path.join(
736 input_api.PresubmitLocalPath(), 'tools_webrtc', 'presubmit_checks_lib')):
mbonadei74973ed2017-05-09 07:58:05 -0700737 from check_orphan_headers import GetBuildGnPathFromFilePath
738 from check_orphan_headers import IsHeaderInBuildGn
mbonadei74973ed2017-05-09 07:58:05 -0700739
740 for f in input_api.AffectedSourceFiles(input_api.FilterSourceFile):
mbonadeia644ad32017-05-10 05:21:55 -0700741 if f.LocalPath().endswith('.h') and f.Action() == 'A':
mbonadei74973ed2017-05-09 07:58:05 -0700742 file_path = os.path.abspath(f.LocalPath())
743 root_dir = os.getcwd()
744 gn_file_path = GetBuildGnPathFromFilePath(file_path, os.path.exists,
745 root_dir)
746 in_build_gn = IsHeaderInBuildGn(file_path, gn_file_path)
747 if not in_build_gn:
748 results.append(output_api.PresubmitError(error_msg.format(
749 file_path, gn_file_path)))
750 return results
Mirko Bonadei960fd5b2017-06-29 14:59:36 +0200751
752
Mirko Bonadeia730c1c2017-09-18 11:33:13 +0200753def CheckNewlineAtTheEndOfProtoFiles(input_api, output_api):
Mirko Bonadei960fd5b2017-06-29 14:59:36 +0200754 """Checks that all .proto files are terminated with a newline."""
755 error_msg = 'File {} must end with exactly one newline.'
756 results = []
757 source_file_filter = lambda x: input_api.FilterSourceFile(
758 x, white_list=(r'.+\.proto$',))
759 for f in input_api.AffectedSourceFiles(source_file_filter):
760 file_path = f.LocalPath()
761 with open(file_path) as f:
762 lines = f.readlines()
Mirko Bonadeia730c1c2017-09-18 11:33:13 +0200763 if len(lines) > 0 and not lines[-1].endswith('\n'):
Mirko Bonadei960fd5b2017-06-29 14:59:36 +0200764 results.append(output_api.PresubmitError(error_msg.format(file_path)))
765 return results