blob: a6d41c6515846729978097ca124fdd7e9d0a2f00 [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',
23 'media',
24 'modules/audio_coding',
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020025 'modules/audio_device',
26 'modules/audio_processing',
27 'modules/desktop_capture',
28 'modules/include/module_common_types.h',
29 'modules/media_file',
30 'modules/utility',
31 'modules/video_capture',
32 'p2p',
33 'pc',
34 'rtc_base',
35 'sdk/android/src/jni',
36 'sdk/objc',
37 'system_wrappers',
38 'test',
Henrik Kjellander90fd7d82017-05-09 08:30:10 +020039 'tools_webrtc',
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020040 'voice_engine',
kjellander@webrtc.org0fcaf992015-11-26 15:24:52 +010041]
42
jbauchc4e3ead2016-02-19 00:25:55 -080043# These filters will always be removed, even if the caller specifies a filter
44# set, as they are problematic or broken in some way.
45#
46# Justifications for each filter:
47# - build/c++11 : Rvalue ref checks are unreliable (false positives),
48# include file and feature blacklists are
49# google3-specific.
kjellandere5a87a52016-04-27 02:32:12 -070050# - whitespace/operators: Same as above (doesn't seem sufficient to eliminate
51# all move-related errors).
jbauchc4e3ead2016-02-19 00:25:55 -080052BLACKLIST_LINT_FILTERS = [
53 '-build/c++11',
kjellandere5a87a52016-04-27 02:32:12 -070054 '-whitespace/operators',
jbauchc4e3ead2016-02-19 00:25:55 -080055]
56
kjellanderfd595232015-12-04 02:44:09 -080057# List of directories of "supported" native APIs. That means changes to headers
58# will be done in a compatible way following this scheme:
59# 1. Non-breaking changes are made.
60# 2. The old APIs as marked as deprecated (with comments).
61# 3. Deprecation is announced to discuss-webrtc@googlegroups.com and
62# webrtc-users@google.com (internal list).
63# 4. (later) The deprecated APIs are removed.
kjellander53047c92015-12-02 23:56:14 -080064NATIVE_API_DIRS = (
Karl Wibergef52d8b82017-10-25 13:20:03 +020065 'api', # All subdirectories of api/ are included as well.
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020066 'media',
67 'modules/audio_device/include',
68 'pc',
kjellanderdd705472016-06-09 11:17:27 -070069)
Mirko Bonadei4dc4e252017-09-19 13:49:16 +020070
kjellanderdd705472016-06-09 11:17:27 -070071# These directories should not be used but are maintained only to avoid breaking
72# some legacy downstream code.
73LEGACY_API_DIRS = (
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020074 'common_audio/include',
75 'modules/audio_coding/include',
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020076 'modules/audio_processing/include',
77 'modules/bitrate_controller/include',
78 'modules/congestion_controller/include',
79 'modules/include',
80 'modules/remote_bitrate_estimator/include',
81 'modules/rtp_rtcp/include',
82 'modules/rtp_rtcp/source',
83 'modules/utility/include',
84 'modules/video_coding/codecs/h264/include',
85 'modules/video_coding/codecs/i420/include',
86 'modules/video_coding/codecs/vp8/include',
87 'modules/video_coding/codecs/vp9/include',
88 'modules/video_coding/include',
89 'rtc_base',
90 'system_wrappers/include',
91 'voice_engine/include',
kjellander53047c92015-12-02 23:56:14 -080092)
Mirko Bonadei4dc4e252017-09-19 13:49:16 +020093
kjellanderdd705472016-06-09 11:17:27 -070094API_DIRS = NATIVE_API_DIRS[:] + LEGACY_API_DIRS[:]
kjellander53047c92015-12-02 23:56:14 -080095
Mirko Bonadei4dc4e252017-09-19 13:49:16 +020096# TARGET_RE matches a GN target, and extracts the target name and the contents.
97TARGET_RE = re.compile(r'(?P<indent>\s*)\w+\("(?P<target_name>\w+)"\) {'
98 r'(?P<target_contents>.*?)'
99 r'(?P=indent)}',
100 re.MULTILINE | re.DOTALL)
101
102# SOURCES_RE matches a block of sources inside a GN target.
103SOURCES_RE = re.compile(r'sources \+?= \[(?P<sources>.*?)\]',
104 re.MULTILINE | re.DOTALL)
105
106# FILE_PATH_RE matchies a file path.
107FILE_PATH_RE = re.compile(r'"(?P<file_path>(\w|\/)+)(?P<extension>\.\w+)"')
108
kjellander53047c92015-12-02 23:56:14 -0800109
Oleh Prypin2f33a562017-10-04 20:17:54 +0200110@contextmanager
111def _AddToPath(*paths):
112 original_sys_path = sys.path
113 sys.path.extend(paths)
114 try:
115 yield
116 finally:
117 # Restore sys.path to what it was before.
118 sys.path = original_sys_path
ehmaldonado4fb97462017-01-30 05:27:22 -0800119
120
charujain9893e252017-09-14 13:33:22 +0200121def VerifyNativeApiHeadersListIsValid(input_api, output_api):
kjellander53047c92015-12-02 23:56:14 -0800122 """Ensures the list of native API header directories is up to date."""
123 non_existing_paths = []
124 native_api_full_paths = [
125 input_api.os_path.join(input_api.PresubmitLocalPath(),
kjellanderdd705472016-06-09 11:17:27 -0700126 *path.split('/')) for path in API_DIRS]
kjellander53047c92015-12-02 23:56:14 -0800127 for path in native_api_full_paths:
128 if not os.path.isdir(path):
129 non_existing_paths.append(path)
130 if non_existing_paths:
131 return [output_api.PresubmitError(
132 'Directories to native API headers have changed which has made the '
133 'list in PRESUBMIT.py outdated.\nPlease update it to the current '
134 'location of our native APIs.',
135 non_existing_paths)]
136 return []
137
kjellanderc88b5d52017-04-05 06:42:43 -0700138API_CHANGE_MSG = """
kwibergeb133022016-04-07 07:41:48 -0700139You seem to be changing native API header files. Please make sure that you:
oprypin375b9ac2017-02-13 04:13:23 -0800140 1. Make compatible changes that don't break existing clients. Usually
141 this is done by keeping the existing method signatures unchanged.
142 2. Mark the old stuff as deprecated (see RTC_DEPRECATED macro).
kwibergeb133022016-04-07 07:41:48 -0700143 3. Create a timeline and plan for when the deprecated stuff will be
144 removed. (The amount of time we give users to change their code
145 should be informed by how much work it is for them. If they just
146 need to replace one name with another or something equally
147 simple, 1-2 weeks might be good; if they need to do serious work,
148 up to 3 months may be called for.)
149 4. Update/inform existing downstream code owners to stop using the
150 deprecated stuff. (Send announcements to
151 discuss-webrtc@googlegroups.com and webrtc-users@google.com.)
152 5. Remove the deprecated stuff, once the agreed-upon amount of time
153 has passed.
154Related files:
155"""
kjellander53047c92015-12-02 23:56:14 -0800156
charujain9893e252017-09-14 13:33:22 +0200157def CheckNativeApiHeaderChanges(input_api, output_api):
kjellander53047c92015-12-02 23:56:14 -0800158 """Checks to remind proper changing of native APIs."""
159 files = []
Karl Wiberg6bfac032017-10-27 15:14:20 +0200160 source_file_filter = lambda x: input_api.FilterSourceFile(
161 x, white_list=[r'.+\.(gn|gni|h)$'])
162 for f in input_api.AffectedSourceFiles(source_file_filter):
163 for path in API_DIRS:
164 dn = os.path.dirname(f.LocalPath())
165 if path == 'api':
166 # Special case: Subdirectories included.
167 if dn == 'api' or dn.startswith('api/'):
168 files.append(f)
169 else:
170 # Normal case: Subdirectories not included.
171 if dn == path:
172 files.append(f)
kjellander53047c92015-12-02 23:56:14 -0800173
174 if files:
kjellanderc88b5d52017-04-05 06:42:43 -0700175 return [output_api.PresubmitNotifyResult(API_CHANGE_MSG, files)]
kjellander53047c92015-12-02 23:56:14 -0800176 return []
177
kjellander@webrtc.org0fcaf992015-11-26 15:24:52 +0100178
charujain9893e252017-09-14 13:33:22 +0200179def CheckNoIOStreamInHeaders(input_api, output_api):
kjellander@webrtc.org51198f12012-02-21 17:53:46 +0000180 """Checks to make sure no .h files include <iostream>."""
181 files = []
182 pattern = input_api.re.compile(r'^#include\s*<iostream>',
183 input_api.re.MULTILINE)
184 for f in input_api.AffectedSourceFiles(input_api.FilterSourceFile):
185 if not f.LocalPath().endswith('.h'):
186 continue
187 contents = input_api.ReadFile(f)
188 if pattern.search(contents):
189 files.append(f)
190
191 if len(files):
Henrik Kjellander57e5fd22015-05-25 12:55:39 +0200192 return [output_api.PresubmitError(
kjellander@webrtc.org51198f12012-02-21 17:53:46 +0000193 'Do not #include <iostream> in header files, since it inserts static ' +
194 'initialization into every file including the header. Instead, ' +
195 '#include <ostream>. See http://crbug.com/94794',
Henrik Kjellander57e5fd22015-05-25 12:55:39 +0200196 files)]
kjellander@webrtc.org51198f12012-02-21 17:53:46 +0000197 return []
198
kjellander@webrtc.orge4158642014-08-06 09:11:18 +0000199
charujain9893e252017-09-14 13:33:22 +0200200def CheckNoPragmaOnce(input_api, output_api):
kjellander6aeef742017-02-20 01:13:18 -0800201 """Make sure that banned functions are not used."""
202 files = []
203 pattern = input_api.re.compile(r'^#pragma\s+once',
204 input_api.re.MULTILINE)
205 for f in input_api.AffectedSourceFiles(input_api.FilterSourceFile):
206 if not f.LocalPath().endswith('.h'):
207 continue
208 contents = input_api.ReadFile(f)
209 if pattern.search(contents):
210 files.append(f)
211
212 if files:
213 return [output_api.PresubmitError(
214 'Do not use #pragma once in header files.\n'
215 'See http://www.chromium.org/developers/coding-style#TOC-File-headers',
216 files)]
217 return []
218
219
charujain9893e252017-09-14 13:33:22 +0200220def CheckNoFRIEND_TEST(input_api, output_api): # pylint: disable=invalid-name
kjellander@webrtc.org51198f12012-02-21 17:53:46 +0000221 """Make sure that gtest's FRIEND_TEST() macro is not used, the
222 FRIEND_TEST_ALL_PREFIXES() macro from testsupport/gtest_prod_util.h should be
223 used instead since that allows for FLAKY_, FAILS_ and DISABLED_ prefixes."""
224 problems = []
225
226 file_filter = lambda f: f.LocalPath().endswith(('.cc', '.h'))
227 for f in input_api.AffectedFiles(file_filter=file_filter):
228 for line_num, line in f.ChangedContents():
229 if 'FRIEND_TEST(' in line:
230 problems.append(' %s:%d' % (f.LocalPath(), line_num))
231
232 if not problems:
233 return []
234 return [output_api.PresubmitPromptWarning('WebRTC\'s code should not use '
235 'gtest\'s FRIEND_TEST() macro. Include testsupport/gtest_prod_util.h and '
236 'use FRIEND_TEST_ALL_PREFIXES() instead.\n' + '\n'.join(problems))]
237
kjellander@webrtc.orge4158642014-08-06 09:11:18 +0000238
charujain9893e252017-09-14 13:33:22 +0200239def IsLintBlacklisted(blacklist_paths, file_path):
oprypin2aa463f2017-03-23 03:17:02 -0700240 """ Checks if a file is blacklisted for lint check."""
241 for path in blacklist_paths:
242 if file_path == path or os.path.dirname(file_path).startswith(path):
kjellander@webrtc.org0fcaf992015-11-26 15:24:52 +0100243 return True
244 return False
245
246
charujain9893e252017-09-14 13:33:22 +0200247def CheckApprovedFilesLintClean(input_api, output_api,
mflodman@webrtc.org2a452092012-07-01 05:55:23 +0000248 source_file_filter=None):
oprypin2aa463f2017-03-23 03:17:02 -0700249 """Checks that all new or non-blacklisted .cc and .h files pass cpplint.py.
charujain9893e252017-09-14 13:33:22 +0200250 This check is based on CheckChangeLintsClean in
kjellander@webrtc.org51198f12012-02-21 17:53:46 +0000251 depot_tools/presubmit_canned_checks.py but has less filters and only checks
252 added files."""
253 result = []
254
255 # Initialize cpplint.
256 import cpplint
257 # Access to a protected member _XX of a client class
258 # pylint: disable=W0212
259 cpplint._cpplint_state.ResetErrorCounts()
260
jbauchc4e3ead2016-02-19 00:25:55 -0800261 lint_filters = cpplint._Filters()
262 lint_filters.extend(BLACKLIST_LINT_FILTERS)
263 cpplint._SetFilters(','.join(lint_filters))
264
oprypin2aa463f2017-03-23 03:17:02 -0700265 # Create a platform independent blacklist for cpplint.
266 blacklist_paths = [input_api.os_path.join(*path.split('/'))
267 for path in CPPLINT_BLACKLIST]
kjellander@webrtc.org0fcaf992015-11-26 15:24:52 +0100268
kjellander@webrtc.org51198f12012-02-21 17:53:46 +0000269 # Use the strictest verbosity level for cpplint.py (level 1) which is the
oprypin2aa463f2017-03-23 03:17:02 -0700270 # default when running cpplint.py from command line. To make it possible to
271 # work with not-yet-converted code, we're only applying it to new (or
272 # moved/renamed) files and files not listed in CPPLINT_BLACKLIST.
kjellander@webrtc.org51198f12012-02-21 17:53:46 +0000273 verbosity_level = 1
274 files = []
275 for f in input_api.AffectedSourceFiles(source_file_filter):
Henrik Kjellander57e5fd22015-05-25 12:55:39 +0200276 # Note that moved/renamed files also count as added.
charujain9893e252017-09-14 13:33:22 +0200277 if f.Action() == 'A' or not IsLintBlacklisted(blacklist_paths,
oprypin2aa463f2017-03-23 03:17:02 -0700278 f.LocalPath()):
kjellander@webrtc.org51198f12012-02-21 17:53:46 +0000279 files.append(f.AbsoluteLocalPath())
mflodman@webrtc.org2a452092012-07-01 05:55:23 +0000280
kjellander@webrtc.org51198f12012-02-21 17:53:46 +0000281 for file_name in files:
282 cpplint.ProcessFile(file_name, verbosity_level)
283
284 if cpplint._cpplint_state.error_count > 0:
285 if input_api.is_committing:
oprypin8e58d652017-03-21 07:52:41 -0700286 res_type = output_api.PresubmitError
kjellander@webrtc.org51198f12012-02-21 17:53:46 +0000287 else:
288 res_type = output_api.PresubmitPromptWarning
289 result = [res_type('Changelist failed cpplint.py check.')]
290
291 return result
292
charujain9893e252017-09-14 13:33:22 +0200293def CheckNoSourcesAbove(input_api, gn_files, output_api):
ehmaldonado5b1ba082016-09-02 05:51:08 -0700294 # Disallow referencing source files with paths above the GN file location.
295 source_pattern = input_api.re.compile(r' +sources \+?= \[(.*?)\]',
296 re.MULTILINE | re.DOTALL)
297 file_pattern = input_api.re.compile(r'"((\.\./.*?)|(//.*?))"')
298 violating_gn_files = set()
299 violating_source_entries = []
300 for gn_file in gn_files:
301 contents = input_api.ReadFile(gn_file)
302 for source_block_match in source_pattern.finditer(contents):
303 # Find all source list entries starting with ../ in the source block
304 # (exclude overrides entries).
305 for file_list_match in file_pattern.finditer(source_block_match.group(1)):
306 source_file = file_list_match.group(1)
307 if 'overrides/' not in source_file:
308 violating_source_entries.append(source_file)
309 violating_gn_files.add(gn_file)
310 if violating_gn_files:
311 return [output_api.PresubmitError(
312 'Referencing source files above the directory of the GN file is not '
Henrik Kjellanderb4af3d62016-11-16 20:11:29 +0100313 'allowed. Please introduce new GN targets in the proper location '
314 'instead.\n'
ehmaldonado5b1ba082016-09-02 05:51:08 -0700315 'Invalid source entries:\n'
316 '%s\n'
317 'Violating GN files:' % '\n'.join(violating_source_entries),
318 items=violating_gn_files)]
319 return []
320
Mirko Bonadei4dc4e252017-09-19 13:49:16 +0200321def CheckNoMixingSources(input_api, gn_files, output_api):
322 """Disallow mixing C, C++ and Obj-C/Obj-C++ in the same target.
323
324 See bugs.webrtc.org/7743 for more context.
325 """
326 def _MoreThanOneSourceUsed(*sources_lists):
327 sources_used = 0
328 for source_list in sources_lists:
329 if len(source_list):
330 sources_used += 1
331 return sources_used > 1
332
333 errors = defaultdict(lambda: [])
kjellander7439f972016-12-05 22:47:46 -0800334 for gn_file in gn_files:
Mirko Bonadei4dc4e252017-09-19 13:49:16 +0200335 gn_file_content = input_api.ReadFile(gn_file)
336 for target_match in TARGET_RE.finditer(gn_file_content):
337 # list_of_sources is a list of tuples of the form
338 # (c_files, cc_files, objc_files) that keeps track of all the sources
339 # defined in a target. A GN target can have more that on definition of
340 # sources (since it supports if/else statements).
341 # E.g.:
342 # rtc_static_library("foo") {
343 # if (is_win) {
344 # sources = [ "foo.cc" ]
345 # } else {
346 # sources = [ "foo.mm" ]
347 # }
348 # }
349 # This is allowed and the presubmit check should support this case.
350 list_of_sources = []
kjellander7439f972016-12-05 22:47:46 -0800351 c_files = []
352 cc_files = []
Mirko Bonadei4dc4e252017-09-19 13:49:16 +0200353 objc_files = []
354 target_name = target_match.group('target_name')
355 target_contents = target_match.group('target_contents')
356 for sources_match in SOURCES_RE.finditer(target_contents):
357 if '+=' not in sources_match.group(0):
358 if c_files or cc_files or objc_files:
359 list_of_sources.append((c_files, cc_files, objc_files))
360 c_files = []
361 cc_files = []
362 objc_files = []
363 for file_match in FILE_PATH_RE.finditer(sources_match.group(1)):
364 file_path = file_match.group('file_path')
365 extension = file_match.group('extension')
366 if extension == '.c':
367 c_files.append(file_path + extension)
368 if extension == '.cc':
369 cc_files.append(file_path + extension)
370 if extension in ['.m', '.mm']:
371 objc_files.append(file_path + extension)
372 list_of_sources.append((c_files, cc_files, objc_files))
373 for c_files_list, cc_files_list, objc_files_list in list_of_sources:
374 if _MoreThanOneSourceUsed(c_files_list, cc_files_list, objc_files_list):
375 all_sources = sorted(c_files_list + cc_files_list + objc_files_list)
376 errors[gn_file.LocalPath()].append((target_name, all_sources))
377 if errors:
kjellander7439f972016-12-05 22:47:46 -0800378 return [output_api.PresubmitError(
Mirko Bonadei4dc4e252017-09-19 13:49:16 +0200379 'GN targets cannot mix .c, .cc and .m (or .mm) source files.\n'
380 'Please create a separate target for each collection of sources.\n'
kjellander7439f972016-12-05 22:47:46 -0800381 'Mixed sources: \n'
382 '%s\n'
Mirko Bonadei4dc4e252017-09-19 13:49:16 +0200383 'Violating GN files:\n%s\n' % (json.dumps(errors, indent=2),
384 '\n'.join(errors.keys())))]
kjellander7439f972016-12-05 22:47:46 -0800385 return []
386
charujain9893e252017-09-14 13:33:22 +0200387def CheckNoPackageBoundaryViolations(input_api, gn_files, output_api):
ehmaldonado4fb97462017-01-30 05:27:22 -0800388 cwd = input_api.PresubmitLocalPath()
Oleh Prypin2f33a562017-10-04 20:17:54 +0200389 with _AddToPath(input_api.os_path.join(
390 cwd, 'tools_webrtc', 'presubmit_checks_lib')):
391 from check_package_boundaries import CheckPackageBoundaries
392 build_files = [os.path.join(cwd, gn_file.LocalPath()) for gn_file in gn_files]
393 errors = CheckPackageBoundaries(cwd, build_files)[:5]
394 if errors:
ehmaldonado4fb97462017-01-30 05:27:22 -0800395 return [output_api.PresubmitError(
Oleh Prypin2f33a562017-10-04 20:17:54 +0200396 'There are package boundary violations in the following GN files:',
397 long_text='\n\n'.join(str(err) for err in errors))]
ehmaldonado4fb97462017-01-30 05:27:22 -0800398 return []
399
charujain9893e252017-09-14 13:33:22 +0200400def CheckGnChanges(input_api, output_api):
ehmaldonado5b1ba082016-09-02 05:51:08 -0700401 source_file_filter = lambda x: input_api.FilterSourceFile(
Oleh Prypinafe01652017-10-04 15:56:08 +0200402 x, white_list=(r'.+\.(gn|gni)$',),
403 black_list=r'.*/presubmit_checks_lib/testdata/.*')
ehmaldonado5b1ba082016-09-02 05:51:08 -0700404
405 gn_files = []
406 for f in input_api.AffectedSourceFiles(source_file_filter):
Mirko Bonadei92ea95e2017-09-15 06:47:31 +0200407 gn_files.append(f)
ehmaldonado5b1ba082016-09-02 05:51:08 -0700408
409 result = []
410 if gn_files:
charujain9893e252017-09-14 13:33:22 +0200411 result.extend(CheckNoSourcesAbove(input_api, gn_files, output_api))
Mirko Bonadei4dc4e252017-09-19 13:49:16 +0200412 result.extend(CheckNoMixingSources(input_api, gn_files, output_api))
413 result.extend(CheckNoPackageBoundaryViolations(input_api, gn_files,
414 output_api))
ehmaldonado5b1ba082016-09-02 05:51:08 -0700415 return result
416
Oleh Prypin920b6532017-10-05 11:28:51 +0200417def CheckGnGen(input_api, output_api):
418 """Runs `gn gen --check` with default args to detect mismatches between
419 #includes and dependencies in the BUILD.gn files, as well as general build
420 errors.
421 """
422 with _AddToPath(input_api.os_path.join(
423 input_api.PresubmitLocalPath(), 'tools_webrtc', 'presubmit_checks_lib')):
424 from gn_check import RunGnCheck
425 errors = RunGnCheck(input_api.PresubmitLocalPath())[:5]
426 if errors:
427 return [output_api.PresubmitPromptWarning(
428 'Some #includes do not match the build dependency graph. Please run:\n'
429 ' gn gen --check <out_dir>',
430 long_text='\n\n'.join(errors))]
431 return []
432
charujain9893e252017-09-14 13:33:22 +0200433def CheckUnwantedDependencies(input_api, output_api):
kjellander@webrtc.org3bd41562014-09-01 11:06:37 +0000434 """Runs checkdeps on #include statements added in this
435 change. Breaking - rules is an error, breaking ! rules is a
436 warning.
437 """
438 # Copied from Chromium's src/PRESUBMIT.py.
439
440 # We need to wait until we have an input_api object and use this
441 # roundabout construct to import checkdeps because this file is
442 # eval-ed and thus doesn't have __file__.
Oleh Prypin2f33a562017-10-04 20:17:54 +0200443 checkdeps_path = input_api.os_path.join(input_api.PresubmitLocalPath(),
444 'buildtools', 'checkdeps')
445 if not os.path.exists(checkdeps_path):
446 return [output_api.PresubmitError(
447 'Cannot find checkdeps at %s\nHave you run "gclient sync" to '
448 'download all the DEPS entries?' % checkdeps_path)]
449 with _AddToPath(checkdeps_path):
kjellander@webrtc.org3bd41562014-09-01 11:06:37 +0000450 import checkdeps
451 from cpp_checker import CppChecker
452 from rules import Rule
kjellander@webrtc.org3bd41562014-09-01 11:06:37 +0000453
454 added_includes = []
455 for f in input_api.AffectedFiles():
456 if not CppChecker.IsCppFile(f.LocalPath()):
457 continue
458
Henrik Kjellander57e5fd22015-05-25 12:55:39 +0200459 changed_lines = [line for _, line in f.ChangedContents()]
kjellander@webrtc.org3bd41562014-09-01 11:06:37 +0000460 added_includes.append([f.LocalPath(), changed_lines])
461
462 deps_checker = checkdeps.DepsChecker(input_api.PresubmitLocalPath())
463
464 error_descriptions = []
465 warning_descriptions = []
466 for path, rule_type, rule_description in deps_checker.CheckAddedCppIncludes(
467 added_includes):
468 description_with_path = '%s\n %s' % (path, rule_description)
469 if rule_type == Rule.DISALLOW:
470 error_descriptions.append(description_with_path)
471 else:
472 warning_descriptions.append(description_with_path)
473
474 results = []
475 if error_descriptions:
476 results.append(output_api.PresubmitError(
kjellandera7066a32017-03-23 03:47:05 -0700477 'You added one or more #includes that violate checkdeps rules.\n'
478 'Check that the DEPS files in these locations contain valid rules.\n'
479 'See https://cs.chromium.org/chromium/src/buildtools/checkdeps/ for '
480 'more details about checkdeps.',
kjellander@webrtc.org3bd41562014-09-01 11:06:37 +0000481 error_descriptions))
482 if warning_descriptions:
483 results.append(output_api.PresubmitPromptOrNotify(
484 'You added one or more #includes of files that are temporarily\n'
485 'allowed but being removed. Can you avoid introducing the\n'
kjellandera7066a32017-03-23 03:47:05 -0700486 '#include? See relevant DEPS file(s) for details and contacts.\n'
487 'See https://cs.chromium.org/chromium/src/buildtools/checkdeps/ for '
488 'more details about checkdeps.',
kjellander@webrtc.org3bd41562014-09-01 11:06:37 +0000489 warning_descriptions))
490 return results
491
charujain9893e252017-09-14 13:33:22 +0200492def CheckCommitMessageBugEntry(input_api, output_api):
493 """Check that bug entries are well-formed in commit message."""
494 bogus_bug_msg = (
Mirko Bonadei61880182017-10-12 15:12:35 +0200495 'Bogus Bug entry: %s. Please specify the issue tracker prefix and the '
charujain9893e252017-09-14 13:33:22 +0200496 'issue number, separated by a colon, e.g. webrtc:123 or chromium:12345.')
497 results = []
Mirko Bonadei61880182017-10-12 15:12:35 +0200498 for bug in input_api.change.BugsFromDescription():
charujain9893e252017-09-14 13:33:22 +0200499 bug = bug.strip()
500 if bug.lower() == 'none':
501 continue
charujain81a58c72017-09-25 13:25:45 +0200502 if 'b/' not in bug and ':' not in bug:
charujain9893e252017-09-14 13:33:22 +0200503 try:
504 if int(bug) > 100000:
505 # Rough indicator for current chromium bugs.
506 prefix_guess = 'chromium'
507 else:
508 prefix_guess = 'webrtc'
Mirko Bonadei61880182017-10-12 15:12:35 +0200509 results.append('Bug entry requires issue tracker prefix, e.g. %s:%s' %
charujain9893e252017-09-14 13:33:22 +0200510 (prefix_guess, bug))
511 except ValueError:
512 results.append(bogus_bug_msg % bug)
charujain81a58c72017-09-25 13:25:45 +0200513 elif not (re.match(r'\w+:\d+', bug) or re.match(r'b/\d+', bug)):
charujain9893e252017-09-14 13:33:22 +0200514 results.append(bogus_bug_msg % bug)
515 return [output_api.PresubmitError(r) for r in results]
516
517def CheckChangeHasBugField(input_api, output_api):
Mirko Bonadei61880182017-10-12 15:12:35 +0200518 """Requires that the changelist is associated with a bug.
kjellanderd1e26a92016-09-19 08:11:16 -0700519
520 This check is stricter than the one in depot_tools/presubmit_canned_checks.py
Mirko Bonadei61880182017-10-12 15:12:35 +0200521 since it fails the presubmit if the bug field is missing or doesn't contain
kjellanderd1e26a92016-09-19 08:11:16 -0700522 a bug reference.
Mirko Bonadei61880182017-10-12 15:12:35 +0200523
524 This supports both 'BUG=' and 'Bug:' since we are in the process of migrating
525 to Gerrit and it encourages the usage of 'Bug:'.
kjellanderd1e26a92016-09-19 08:11:16 -0700526 """
Mirko Bonadei61880182017-10-12 15:12:35 +0200527 if input_api.change.BugsFromDescription():
kjellanderd1e26a92016-09-19 08:11:16 -0700528 return []
529 else:
530 return [output_api.PresubmitError(
Mirko Bonadei61880182017-10-12 15:12:35 +0200531 'The "Bug: [bug number]" footer is mandatory. Please create a bug and '
kjellanderd1e26a92016-09-19 08:11:16 -0700532 'reference it using either of:\n'
Mirko Bonadei61880182017-10-12 15:12:35 +0200533 ' * https://bugs.webrtc.org - reference it using Bug: webrtc:XXXX\n'
534 ' * https://crbug.com - reference it using Bug: chromium:XXXXXX')]
kjellander@webrtc.orge4158642014-08-06 09:11:18 +0000535
charujain9893e252017-09-14 13:33:22 +0200536def CheckJSONParseErrors(input_api, output_api):
kjellander569cf942016-02-11 05:02:59 -0800537 """Check that JSON files do not contain syntax errors."""
538
539 def FilterFile(affected_file):
540 return input_api.os_path.splitext(affected_file.LocalPath())[1] == '.json'
541
542 def GetJSONParseError(input_api, filename):
543 try:
544 contents = input_api.ReadFile(filename)
545 input_api.json.loads(contents)
546 except ValueError as e:
547 return e
548 return None
549
550 results = []
551 for affected_file in input_api.AffectedFiles(
552 file_filter=FilterFile, include_deletes=False):
553 parse_error = GetJSONParseError(input_api,
554 affected_file.AbsoluteLocalPath())
555 if parse_error:
556 results.append(output_api.PresubmitError('%s could not be parsed: %s' %
557 (affected_file.LocalPath(), parse_error)))
558 return results
559
560
charujain9893e252017-09-14 13:33:22 +0200561def RunPythonTests(input_api, output_api):
kjellanderc88b5d52017-04-05 06:42:43 -0700562 def Join(*args):
Henrik Kjellander8d3ad822015-05-26 19:52:05 +0200563 return input_api.os_path.join(input_api.PresubmitLocalPath(), *args)
564
565 test_directories = [
Edward Lemur6d01f6d2017-09-14 17:02:01 +0200566 input_api.PresubmitLocalPath(),
Mirko Bonadei92ea95e2017-09-15 06:47:31 +0200567 Join('rtc_tools', 'py_event_log_analyzer'),
568 Join('rtc_tools'),
569 Join('audio', 'test', 'unittests'),
ehmaldonado4fb97462017-01-30 05:27:22 -0800570 ] + [
Henrik Kjellander90fd7d82017-05-09 08:30:10 +0200571 root for root, _, files in os.walk(Join('tools_webrtc'))
ehmaldonado4fb97462017-01-30 05:27:22 -0800572 if any(f.endswith('_test.py') for f in files)
Henrik Kjellander8d3ad822015-05-26 19:52:05 +0200573 ]
574
575 tests = []
576 for directory in test_directories:
577 tests.extend(
578 input_api.canned_checks.GetUnitTestsInDirectory(
579 input_api,
580 output_api,
581 directory,
582 whitelist=[r'.+_test\.py$']))
583 return input_api.RunTests(tests, parallel=True)
584
585
charujain9893e252017-09-14 13:33:22 +0200586def CheckUsageOfGoogleProtobufNamespace(input_api, output_api):
mbonadei38415b22017-04-07 05:38:01 -0700587 """Checks that the namespace google::protobuf has not been used."""
588 files = []
589 pattern = input_api.re.compile(r'google::protobuf')
Mirko Bonadei92ea95e2017-09-15 06:47:31 +0200590 proto_utils_path = os.path.join('rtc_base', 'protobuf_utils.h')
mbonadei38415b22017-04-07 05:38:01 -0700591 for f in input_api.AffectedSourceFiles(input_api.FilterSourceFile):
592 if f.LocalPath() in [proto_utils_path, 'PRESUBMIT.py']:
593 continue
594 contents = input_api.ReadFile(f)
595 if pattern.search(contents):
596 files.append(f)
597
598 if files:
599 return [output_api.PresubmitError(
600 'Please avoid to use namespace `google::protobuf` directly.\n'
601 'Add a using directive in `%s` and include that header instead.'
602 % proto_utils_path, files)]
603 return []
604
605
Mirko Bonadei92ea95e2017-09-15 06:47:31 +0200606def _LicenseHeader(input_api):
607 """Returns the license header regexp."""
608 # Accept any year number from 2003 to the current year
609 current_year = int(input_api.time.strftime('%Y'))
610 allowed_years = (str(s) for s in reversed(xrange(2003, current_year + 1)))
611 years_re = '(' + '|'.join(allowed_years) + ')'
612 license_header = (
613 r'.*? Copyright( \(c\))? %(year)s The WebRTC [Pp]roject [Aa]uthors\. '
614 r'All [Rr]ights [Rr]eserved\.\n'
615 r'.*?\n'
616 r'.*? Use of this source code is governed by a BSD-style license\n'
617 r'.*? that can be found in the LICENSE file in the root of the source\n'
618 r'.*? tree\. An additional intellectual property rights grant can be '
619 r'found\n'
620 r'.*? in the file PATENTS\. All contributing project authors may\n'
621 r'.*? be found in the AUTHORS file in the root of the source tree\.\n'
622 ) % {
623 'year': years_re,
624 }
625 return license_header
626
627
charujain9893e252017-09-14 13:33:22 +0200628def CommonChecks(input_api, output_api):
andrew@webrtc.org53df1362012-01-26 21:24:23 +0000629 """Checks common to both upload and commit."""
niklase@google.comda159d62011-05-30 11:51:34 +0000630 results = []
tkchin42f580e2015-11-26 23:18:23 -0800631 # Filter out files that are in objc or ios dirs from being cpplint-ed since
632 # they do not follow C++ lint rules.
633 black_list = input_api.DEFAULT_BLACK_LIST + (
634 r".*\bobjc[\\\/].*",
Kári Tristan Helgason3fa35172016-09-09 08:55:05 +0000635 r".*objc\.[hcm]+$",
tkchin42f580e2015-11-26 23:18:23 -0800636 )
637 source_file_filter = lambda x: input_api.FilterSourceFile(x, None, black_list)
charujain9893e252017-09-14 13:33:22 +0200638 results.extend(CheckApprovedFilesLintClean(
tkchin42f580e2015-11-26 23:18:23 -0800639 input_api, output_api, source_file_filter))
Mirko Bonadei92ea95e2017-09-15 06:47:31 +0200640 results.extend(input_api.canned_checks.CheckLicense(
641 input_api, output_api, _LicenseHeader(input_api)))
phoglund@webrtc.org5d3713932013-03-07 09:59:43 +0000642 results.extend(input_api.canned_checks.RunPylint(input_api, output_api,
kjellander@webrtc.org177567c2016-12-22 10:40:28 +0100643 black_list=(r'^base[\\\/].*\.py$',
Henrik Kjellander14771ac2015-06-02 13:10:04 +0200644 r'^build[\\\/].*\.py$',
645 r'^buildtools[\\\/].*\.py$',
kjellander38c65c82017-04-12 22:43:38 -0700646 r'^infra[\\\/].*\.py$',
Henrik Kjellander0779e8f2016-12-22 12:01:17 +0100647 r'^ios[\\\/].*\.py$',
Henrik Kjellander14771ac2015-06-02 13:10:04 +0200648 r'^out.*[\\\/].*\.py$',
649 r'^testing[\\\/].*\.py$',
650 r'^third_party[\\\/].*\.py$',
kjellander@webrtc.org177567c2016-12-22 10:40:28 +0100651 r'^tools[\\\/].*\.py$',
kjellanderafd54942016-12-17 12:21:39 -0800652 # TODO(phoglund): should arguably be checked.
Henrik Kjellander90fd7d82017-05-09 08:30:10 +0200653 r'^tools_webrtc[\\\/]mb[\\\/].*\.py$',
654 r'^tools_webrtc[\\\/]valgrind[\\\/].*\.py$',
Henrik Kjellander14771ac2015-06-02 13:10:04 +0200655 r'^xcodebuild.*[\\\/].*\.py$',),
Henrik Kjellander57e5fd22015-05-25 12:55:39 +0200656 pylintrc='pylintrc'))
kjellander569cf942016-02-11 05:02:59 -0800657
nisse3d21e232016-09-02 03:07:06 -0700658 # TODO(nisse): talk/ is no more, so make below checks simpler?
Henrik Kjellander57e5fd22015-05-25 12:55:39 +0200659 # WebRTC can't use the presubmit_canned_checks.PanProjectChecks function since
660 # we need to have different license checks in talk/ and webrtc/ directories.
661 # Instead, hand-picked checks are included below.
Henrik Kjellander63224672015-09-08 08:03:56 +0200662
tkchin3cd9a302016-06-08 12:40:28 -0700663 # .m and .mm files are ObjC files. For simplicity we will consider .h files in
664 # ObjC subdirectories ObjC headers.
665 objc_filter_list = (r'.+\.m$', r'.+\.mm$', r'.+objc\/.+\.h$')
Henrik Kjellanderb4af3d62016-11-16 20:11:29 +0100666 # Skip long-lines check for DEPS and GN files.
667 build_file_filter_list = (r'.+\.gn$', r'.+\.gni$', 'DEPS')
tkchin3cd9a302016-06-08 12:40:28 -0700668 eighty_char_sources = lambda x: input_api.FilterSourceFile(x,
669 black_list=build_file_filter_list + objc_filter_list)
670 hundred_char_sources = lambda x: input_api.FilterSourceFile(x,
671 white_list=objc_filter_list)
andrew@webrtc.org2442de12012-01-23 17:45:41 +0000672 results.extend(input_api.canned_checks.CheckLongLines(
tkchin3cd9a302016-06-08 12:40:28 -0700673 input_api, output_api, maxlen=80, source_file_filter=eighty_char_sources))
674 results.extend(input_api.canned_checks.CheckLongLines(
675 input_api, output_api, maxlen=100,
676 source_file_filter=hundred_char_sources))
677
andrew@webrtc.org2442de12012-01-23 17:45:41 +0000678 results.extend(input_api.canned_checks.CheckChangeHasNoTabs(
679 input_api, output_api))
andrew@webrtc.org53df1362012-01-26 21:24:23 +0000680 results.extend(input_api.canned_checks.CheckChangeHasNoStrayWhitespace(
681 input_api, output_api))
kjellandere5dc62a2016-12-14 00:16:21 -0800682 results.extend(input_api.canned_checks.CheckAuthorizedAuthor(
683 input_api, output_api))
andrew@webrtc.org53df1362012-01-26 21:24:23 +0000684 results.extend(input_api.canned_checks.CheckChangeTodoHasOwner(
685 input_api, output_api))
charujain9893e252017-09-14 13:33:22 +0200686 results.extend(CheckNativeApiHeaderChanges(input_api, output_api))
687 results.extend(CheckNoIOStreamInHeaders(input_api, output_api))
688 results.extend(CheckNoPragmaOnce(input_api, output_api))
689 results.extend(CheckNoFRIEND_TEST(input_api, output_api))
690 results.extend(CheckGnChanges(input_api, output_api))
691 results.extend(CheckUnwantedDependencies(input_api, output_api))
692 results.extend(CheckJSONParseErrors(input_api, output_api))
693 results.extend(RunPythonTests(input_api, output_api))
694 results.extend(CheckUsageOfGoogleProtobufNamespace(input_api, output_api))
Mirko Bonadei866d3372017-09-15 12:35:26 +0200695 results.extend(CheckOrphanHeaders(input_api, output_api))
Mirko Bonadeia730c1c2017-09-18 11:33:13 +0200696 results.extend(CheckNewlineAtTheEndOfProtoFiles(input_api, output_api))
andrew@webrtc.org53df1362012-01-26 21:24:23 +0000697 return results
andrew@webrtc.org2442de12012-01-23 17:45:41 +0000698
kjellander@webrtc.orge4158642014-08-06 09:11:18 +0000699
andrew@webrtc.org53df1362012-01-26 21:24:23 +0000700def CheckChangeOnUpload(input_api, output_api):
701 results = []
charujain9893e252017-09-14 13:33:22 +0200702 results.extend(CommonChecks(input_api, output_api))
Oleh Prypin920b6532017-10-05 11:28:51 +0200703 results.extend(CheckGnGen(input_api, output_api))
Henrik Kjellander57e5fd22015-05-25 12:55:39 +0200704 results.extend(
705 input_api.canned_checks.CheckGNFormatted(input_api, output_api))
niklase@google.comda159d62011-05-30 11:51:34 +0000706 return results
707
kjellander@webrtc.orge4158642014-08-06 09:11:18 +0000708
andrew@webrtc.org2442de12012-01-23 17:45:41 +0000709def CheckChangeOnCommit(input_api, output_api):
niklase@google.com1198db92011-06-09 07:07:24 +0000710 results = []
charujain9893e252017-09-14 13:33:22 +0200711 results.extend(CommonChecks(input_api, output_api))
712 results.extend(VerifyNativeApiHeadersListIsValid(input_api, output_api))
niklase@google.com1198db92011-06-09 07:07:24 +0000713 results.extend(input_api.canned_checks.CheckOwners(input_api, output_api))
andrew@webrtc.org53df1362012-01-26 21:24:23 +0000714 results.extend(input_api.canned_checks.CheckChangeWasUploaded(
715 input_api, output_api))
716 results.extend(input_api.canned_checks.CheckChangeHasDescription(
717 input_api, output_api))
charujain9893e252017-09-14 13:33:22 +0200718 results.extend(CheckChangeHasBugField(input_api, output_api))
719 results.extend(CheckCommitMessageBugEntry(input_api, output_api))
kjellander@webrtc.org12cb88c2014-02-13 11:53:43 +0000720 results.extend(input_api.canned_checks.CheckTreeIsOpen(
721 input_api, output_api,
722 json_url='http://webrtc-status.appspot.com/current?format=json'))
niklase@google.com1198db92011-06-09 07:07:24 +0000723 return results
mbonadei74973ed2017-05-09 07:58:05 -0700724
725
charujain9893e252017-09-14 13:33:22 +0200726def CheckOrphanHeaders(input_api, output_api):
mbonadei74973ed2017-05-09 07:58:05 -0700727 # We need to wait until we have an input_api object and use this
728 # roundabout construct to import prebubmit_checks_lib because this file is
729 # eval-ed and thus doesn't have __file__.
730 error_msg = """Header file {} is not listed in any GN target.
731 Please create a target or add it to an existing one in {}"""
732 results = []
Oleh Prypin2f33a562017-10-04 20:17:54 +0200733 with _AddToPath(input_api.os_path.join(
734 input_api.PresubmitLocalPath(), 'tools_webrtc', 'presubmit_checks_lib')):
mbonadei74973ed2017-05-09 07:58:05 -0700735 from check_orphan_headers import GetBuildGnPathFromFilePath
736 from check_orphan_headers import IsHeaderInBuildGn
mbonadei74973ed2017-05-09 07:58:05 -0700737
738 for f in input_api.AffectedSourceFiles(input_api.FilterSourceFile):
mbonadeia644ad32017-05-10 05:21:55 -0700739 if f.LocalPath().endswith('.h') and f.Action() == 'A':
mbonadei74973ed2017-05-09 07:58:05 -0700740 file_path = os.path.abspath(f.LocalPath())
741 root_dir = os.getcwd()
742 gn_file_path = GetBuildGnPathFromFilePath(file_path, os.path.exists,
743 root_dir)
744 in_build_gn = IsHeaderInBuildGn(file_path, gn_file_path)
745 if not in_build_gn:
746 results.append(output_api.PresubmitError(error_msg.format(
747 file_path, gn_file_path)))
748 return results
Mirko Bonadei960fd5b2017-06-29 14:59:36 +0200749
750
Mirko Bonadeia730c1c2017-09-18 11:33:13 +0200751def CheckNewlineAtTheEndOfProtoFiles(input_api, output_api):
Mirko Bonadei960fd5b2017-06-29 14:59:36 +0200752 """Checks that all .proto files are terminated with a newline."""
753 error_msg = 'File {} must end with exactly one newline.'
754 results = []
755 source_file_filter = lambda x: input_api.FilterSourceFile(
756 x, white_list=(r'.+\.proto$',))
757 for f in input_api.AffectedSourceFiles(source_file_filter):
758 file_path = f.LocalPath()
759 with open(file_path) as f:
760 lines = f.readlines()
Mirko Bonadeia730c1c2017-09-18 11:33:13 +0200761 if len(lines) > 0 and not lines[-1].endswith('\n'):
Mirko Bonadei960fd5b2017-06-29 14:59:36 +0200762 results.append(output_api.PresubmitError(error_msg.format(file_path)))
763 return results