blob: c72fc198fdb66af824586a5ceda3460427fdbd4e [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 = []
160 for f in input_api.AffectedSourceFiles(input_api.FilterSourceFile):
161 if f.LocalPath().endswith('.h'):
kjellanderdd705472016-06-09 11:17:27 -0700162 for path in API_DIRS:
Karl Wibergef52d8b82017-10-25 13:20:03 +0200163 dn = os.path.dirname(f.LocalPath())
164 if path == 'api':
165 # Special case: Subdirectories included.
166 if dn == 'api' or dn.startswith('api/'):
167 files.append(f)
168 else:
169 # Normal case: Subdirectories not included.
170 if dn == path:
171 files.append(f)
kjellander53047c92015-12-02 23:56:14 -0800172
173 if files:
kjellanderc88b5d52017-04-05 06:42:43 -0700174 return [output_api.PresubmitNotifyResult(API_CHANGE_MSG, files)]
kjellander53047c92015-12-02 23:56:14 -0800175 return []
176
kjellander@webrtc.org0fcaf992015-11-26 15:24:52 +0100177
charujain9893e252017-09-14 13:33:22 +0200178def CheckNoIOStreamInHeaders(input_api, output_api):
kjellander@webrtc.org51198f12012-02-21 17:53:46 +0000179 """Checks to make sure no .h files include <iostream>."""
180 files = []
181 pattern = input_api.re.compile(r'^#include\s*<iostream>',
182 input_api.re.MULTILINE)
183 for f in input_api.AffectedSourceFiles(input_api.FilterSourceFile):
184 if not f.LocalPath().endswith('.h'):
185 continue
186 contents = input_api.ReadFile(f)
187 if pattern.search(contents):
188 files.append(f)
189
190 if len(files):
Henrik Kjellander57e5fd22015-05-25 12:55:39 +0200191 return [output_api.PresubmitError(
kjellander@webrtc.org51198f12012-02-21 17:53:46 +0000192 'Do not #include <iostream> in header files, since it inserts static ' +
193 'initialization into every file including the header. Instead, ' +
194 '#include <ostream>. See http://crbug.com/94794',
Henrik Kjellander57e5fd22015-05-25 12:55:39 +0200195 files)]
kjellander@webrtc.org51198f12012-02-21 17:53:46 +0000196 return []
197
kjellander@webrtc.orge4158642014-08-06 09:11:18 +0000198
charujain9893e252017-09-14 13:33:22 +0200199def CheckNoPragmaOnce(input_api, output_api):
kjellander6aeef742017-02-20 01:13:18 -0800200 """Make sure that banned functions are not used."""
201 files = []
202 pattern = input_api.re.compile(r'^#pragma\s+once',
203 input_api.re.MULTILINE)
204 for f in input_api.AffectedSourceFiles(input_api.FilterSourceFile):
205 if not f.LocalPath().endswith('.h'):
206 continue
207 contents = input_api.ReadFile(f)
208 if pattern.search(contents):
209 files.append(f)
210
211 if files:
212 return [output_api.PresubmitError(
213 'Do not use #pragma once in header files.\n'
214 'See http://www.chromium.org/developers/coding-style#TOC-File-headers',
215 files)]
216 return []
217
218
charujain9893e252017-09-14 13:33:22 +0200219def CheckNoFRIEND_TEST(input_api, output_api): # pylint: disable=invalid-name
kjellander@webrtc.org51198f12012-02-21 17:53:46 +0000220 """Make sure that gtest's FRIEND_TEST() macro is not used, the
221 FRIEND_TEST_ALL_PREFIXES() macro from testsupport/gtest_prod_util.h should be
222 used instead since that allows for FLAKY_, FAILS_ and DISABLED_ prefixes."""
223 problems = []
224
225 file_filter = lambda f: f.LocalPath().endswith(('.cc', '.h'))
226 for f in input_api.AffectedFiles(file_filter=file_filter):
227 for line_num, line in f.ChangedContents():
228 if 'FRIEND_TEST(' in line:
229 problems.append(' %s:%d' % (f.LocalPath(), line_num))
230
231 if not problems:
232 return []
233 return [output_api.PresubmitPromptWarning('WebRTC\'s code should not use '
234 'gtest\'s FRIEND_TEST() macro. Include testsupport/gtest_prod_util.h and '
235 'use FRIEND_TEST_ALL_PREFIXES() instead.\n' + '\n'.join(problems))]
236
kjellander@webrtc.orge4158642014-08-06 09:11:18 +0000237
charujain9893e252017-09-14 13:33:22 +0200238def IsLintBlacklisted(blacklist_paths, file_path):
oprypin2aa463f2017-03-23 03:17:02 -0700239 """ Checks if a file is blacklisted for lint check."""
240 for path in blacklist_paths:
241 if file_path == path or os.path.dirname(file_path).startswith(path):
kjellander@webrtc.org0fcaf992015-11-26 15:24:52 +0100242 return True
243 return False
244
245
charujain9893e252017-09-14 13:33:22 +0200246def CheckApprovedFilesLintClean(input_api, output_api,
mflodman@webrtc.org2a452092012-07-01 05:55:23 +0000247 source_file_filter=None):
oprypin2aa463f2017-03-23 03:17:02 -0700248 """Checks that all new or non-blacklisted .cc and .h files pass cpplint.py.
charujain9893e252017-09-14 13:33:22 +0200249 This check is based on CheckChangeLintsClean in
kjellander@webrtc.org51198f12012-02-21 17:53:46 +0000250 depot_tools/presubmit_canned_checks.py but has less filters and only checks
251 added files."""
252 result = []
253
254 # Initialize cpplint.
255 import cpplint
256 # Access to a protected member _XX of a client class
257 # pylint: disable=W0212
258 cpplint._cpplint_state.ResetErrorCounts()
259
jbauchc4e3ead2016-02-19 00:25:55 -0800260 lint_filters = cpplint._Filters()
261 lint_filters.extend(BLACKLIST_LINT_FILTERS)
262 cpplint._SetFilters(','.join(lint_filters))
263
oprypin2aa463f2017-03-23 03:17:02 -0700264 # Create a platform independent blacklist for cpplint.
265 blacklist_paths = [input_api.os_path.join(*path.split('/'))
266 for path in CPPLINT_BLACKLIST]
kjellander@webrtc.org0fcaf992015-11-26 15:24:52 +0100267
kjellander@webrtc.org51198f12012-02-21 17:53:46 +0000268 # Use the strictest verbosity level for cpplint.py (level 1) which is the
oprypin2aa463f2017-03-23 03:17:02 -0700269 # default when running cpplint.py from command line. To make it possible to
270 # work with not-yet-converted code, we're only applying it to new (or
271 # moved/renamed) files and files not listed in CPPLINT_BLACKLIST.
kjellander@webrtc.org51198f12012-02-21 17:53:46 +0000272 verbosity_level = 1
273 files = []
274 for f in input_api.AffectedSourceFiles(source_file_filter):
Henrik Kjellander57e5fd22015-05-25 12:55:39 +0200275 # Note that moved/renamed files also count as added.
charujain9893e252017-09-14 13:33:22 +0200276 if f.Action() == 'A' or not IsLintBlacklisted(blacklist_paths,
oprypin2aa463f2017-03-23 03:17:02 -0700277 f.LocalPath()):
kjellander@webrtc.org51198f12012-02-21 17:53:46 +0000278 files.append(f.AbsoluteLocalPath())
mflodman@webrtc.org2a452092012-07-01 05:55:23 +0000279
kjellander@webrtc.org51198f12012-02-21 17:53:46 +0000280 for file_name in files:
281 cpplint.ProcessFile(file_name, verbosity_level)
282
283 if cpplint._cpplint_state.error_count > 0:
284 if input_api.is_committing:
oprypin8e58d652017-03-21 07:52:41 -0700285 res_type = output_api.PresubmitError
kjellander@webrtc.org51198f12012-02-21 17:53:46 +0000286 else:
287 res_type = output_api.PresubmitPromptWarning
288 result = [res_type('Changelist failed cpplint.py check.')]
289
290 return result
291
charujain9893e252017-09-14 13:33:22 +0200292def CheckNoSourcesAbove(input_api, gn_files, output_api):
ehmaldonado5b1ba082016-09-02 05:51:08 -0700293 # Disallow referencing source files with paths above the GN file location.
294 source_pattern = input_api.re.compile(r' +sources \+?= \[(.*?)\]',
295 re.MULTILINE | re.DOTALL)
296 file_pattern = input_api.re.compile(r'"((\.\./.*?)|(//.*?))"')
297 violating_gn_files = set()
298 violating_source_entries = []
299 for gn_file in gn_files:
300 contents = input_api.ReadFile(gn_file)
301 for source_block_match in source_pattern.finditer(contents):
302 # Find all source list entries starting with ../ in the source block
303 # (exclude overrides entries).
304 for file_list_match in file_pattern.finditer(source_block_match.group(1)):
305 source_file = file_list_match.group(1)
306 if 'overrides/' not in source_file:
307 violating_source_entries.append(source_file)
308 violating_gn_files.add(gn_file)
309 if violating_gn_files:
310 return [output_api.PresubmitError(
311 'Referencing source files above the directory of the GN file is not '
Henrik Kjellanderb4af3d62016-11-16 20:11:29 +0100312 'allowed. Please introduce new GN targets in the proper location '
313 'instead.\n'
ehmaldonado5b1ba082016-09-02 05:51:08 -0700314 'Invalid source entries:\n'
315 '%s\n'
316 'Violating GN files:' % '\n'.join(violating_source_entries),
317 items=violating_gn_files)]
318 return []
319
Mirko Bonadei4dc4e252017-09-19 13:49:16 +0200320def CheckNoMixingSources(input_api, gn_files, output_api):
321 """Disallow mixing C, C++ and Obj-C/Obj-C++ in the same target.
322
323 See bugs.webrtc.org/7743 for more context.
324 """
325 def _MoreThanOneSourceUsed(*sources_lists):
326 sources_used = 0
327 for source_list in sources_lists:
328 if len(source_list):
329 sources_used += 1
330 return sources_used > 1
331
332 errors = defaultdict(lambda: [])
kjellander7439f972016-12-05 22:47:46 -0800333 for gn_file in gn_files:
Mirko Bonadei4dc4e252017-09-19 13:49:16 +0200334 gn_file_content = input_api.ReadFile(gn_file)
335 for target_match in TARGET_RE.finditer(gn_file_content):
336 # list_of_sources is a list of tuples of the form
337 # (c_files, cc_files, objc_files) that keeps track of all the sources
338 # defined in a target. A GN target can have more that on definition of
339 # sources (since it supports if/else statements).
340 # E.g.:
341 # rtc_static_library("foo") {
342 # if (is_win) {
343 # sources = [ "foo.cc" ]
344 # } else {
345 # sources = [ "foo.mm" ]
346 # }
347 # }
348 # This is allowed and the presubmit check should support this case.
349 list_of_sources = []
kjellander7439f972016-12-05 22:47:46 -0800350 c_files = []
351 cc_files = []
Mirko Bonadei4dc4e252017-09-19 13:49:16 +0200352 objc_files = []
353 target_name = target_match.group('target_name')
354 target_contents = target_match.group('target_contents')
355 for sources_match in SOURCES_RE.finditer(target_contents):
356 if '+=' not in sources_match.group(0):
357 if c_files or cc_files or objc_files:
358 list_of_sources.append((c_files, cc_files, objc_files))
359 c_files = []
360 cc_files = []
361 objc_files = []
362 for file_match in FILE_PATH_RE.finditer(sources_match.group(1)):
363 file_path = file_match.group('file_path')
364 extension = file_match.group('extension')
365 if extension == '.c':
366 c_files.append(file_path + extension)
367 if extension == '.cc':
368 cc_files.append(file_path + extension)
369 if extension in ['.m', '.mm']:
370 objc_files.append(file_path + extension)
371 list_of_sources.append((c_files, cc_files, objc_files))
372 for c_files_list, cc_files_list, objc_files_list in list_of_sources:
373 if _MoreThanOneSourceUsed(c_files_list, cc_files_list, objc_files_list):
374 all_sources = sorted(c_files_list + cc_files_list + objc_files_list)
375 errors[gn_file.LocalPath()].append((target_name, all_sources))
376 if errors:
kjellander7439f972016-12-05 22:47:46 -0800377 return [output_api.PresubmitError(
Mirko Bonadei4dc4e252017-09-19 13:49:16 +0200378 'GN targets cannot mix .c, .cc and .m (or .mm) source files.\n'
379 'Please create a separate target for each collection of sources.\n'
kjellander7439f972016-12-05 22:47:46 -0800380 'Mixed sources: \n'
381 '%s\n'
Mirko Bonadei4dc4e252017-09-19 13:49:16 +0200382 'Violating GN files:\n%s\n' % (json.dumps(errors, indent=2),
383 '\n'.join(errors.keys())))]
kjellander7439f972016-12-05 22:47:46 -0800384 return []
385
charujain9893e252017-09-14 13:33:22 +0200386def CheckNoPackageBoundaryViolations(input_api, gn_files, output_api):
ehmaldonado4fb97462017-01-30 05:27:22 -0800387 cwd = input_api.PresubmitLocalPath()
Oleh Prypin2f33a562017-10-04 20:17:54 +0200388 with _AddToPath(input_api.os_path.join(
389 cwd, 'tools_webrtc', 'presubmit_checks_lib')):
390 from check_package_boundaries import CheckPackageBoundaries
391 build_files = [os.path.join(cwd, gn_file.LocalPath()) for gn_file in gn_files]
392 errors = CheckPackageBoundaries(cwd, build_files)[:5]
393 if errors:
ehmaldonado4fb97462017-01-30 05:27:22 -0800394 return [output_api.PresubmitError(
Oleh Prypin2f33a562017-10-04 20:17:54 +0200395 'There are package boundary violations in the following GN files:',
396 long_text='\n\n'.join(str(err) for err in errors))]
ehmaldonado4fb97462017-01-30 05:27:22 -0800397 return []
398
charujain9893e252017-09-14 13:33:22 +0200399def CheckGnChanges(input_api, output_api):
ehmaldonado5b1ba082016-09-02 05:51:08 -0700400 source_file_filter = lambda x: input_api.FilterSourceFile(
Oleh Prypinafe01652017-10-04 15:56:08 +0200401 x, white_list=(r'.+\.(gn|gni)$',),
402 black_list=r'.*/presubmit_checks_lib/testdata/.*')
ehmaldonado5b1ba082016-09-02 05:51:08 -0700403
404 gn_files = []
405 for f in input_api.AffectedSourceFiles(source_file_filter):
Mirko Bonadei92ea95e2017-09-15 06:47:31 +0200406 gn_files.append(f)
ehmaldonado5b1ba082016-09-02 05:51:08 -0700407
408 result = []
409 if gn_files:
charujain9893e252017-09-14 13:33:22 +0200410 result.extend(CheckNoSourcesAbove(input_api, gn_files, output_api))
Mirko Bonadei4dc4e252017-09-19 13:49:16 +0200411 result.extend(CheckNoMixingSources(input_api, gn_files, output_api))
412 result.extend(CheckNoPackageBoundaryViolations(input_api, gn_files,
413 output_api))
ehmaldonado5b1ba082016-09-02 05:51:08 -0700414 return result
415
Oleh Prypin920b6532017-10-05 11:28:51 +0200416def CheckGnGen(input_api, output_api):
417 """Runs `gn gen --check` with default args to detect mismatches between
418 #includes and dependencies in the BUILD.gn files, as well as general build
419 errors.
420 """
421 with _AddToPath(input_api.os_path.join(
422 input_api.PresubmitLocalPath(), 'tools_webrtc', 'presubmit_checks_lib')):
423 from gn_check import RunGnCheck
424 errors = RunGnCheck(input_api.PresubmitLocalPath())[:5]
425 if errors:
426 return [output_api.PresubmitPromptWarning(
427 'Some #includes do not match the build dependency graph. Please run:\n'
428 ' gn gen --check <out_dir>',
429 long_text='\n\n'.join(errors))]
430 return []
431
charujain9893e252017-09-14 13:33:22 +0200432def CheckUnwantedDependencies(input_api, output_api):
kjellander@webrtc.org3bd41562014-09-01 11:06:37 +0000433 """Runs checkdeps on #include statements added in this
434 change. Breaking - rules is an error, breaking ! rules is a
435 warning.
436 """
437 # Copied from Chromium's src/PRESUBMIT.py.
438
439 # We need to wait until we have an input_api object and use this
440 # roundabout construct to import checkdeps because this file is
441 # eval-ed and thus doesn't have __file__.
Oleh Prypin2f33a562017-10-04 20:17:54 +0200442 checkdeps_path = input_api.os_path.join(input_api.PresubmitLocalPath(),
443 'buildtools', 'checkdeps')
444 if not os.path.exists(checkdeps_path):
445 return [output_api.PresubmitError(
446 'Cannot find checkdeps at %s\nHave you run "gclient sync" to '
447 'download all the DEPS entries?' % checkdeps_path)]
448 with _AddToPath(checkdeps_path):
kjellander@webrtc.org3bd41562014-09-01 11:06:37 +0000449 import checkdeps
450 from cpp_checker import CppChecker
451 from rules import Rule
kjellander@webrtc.org3bd41562014-09-01 11:06:37 +0000452
453 added_includes = []
454 for f in input_api.AffectedFiles():
455 if not CppChecker.IsCppFile(f.LocalPath()):
456 continue
457
Henrik Kjellander57e5fd22015-05-25 12:55:39 +0200458 changed_lines = [line for _, line in f.ChangedContents()]
kjellander@webrtc.org3bd41562014-09-01 11:06:37 +0000459 added_includes.append([f.LocalPath(), changed_lines])
460
461 deps_checker = checkdeps.DepsChecker(input_api.PresubmitLocalPath())
462
463 error_descriptions = []
464 warning_descriptions = []
465 for path, rule_type, rule_description in deps_checker.CheckAddedCppIncludes(
466 added_includes):
467 description_with_path = '%s\n %s' % (path, rule_description)
468 if rule_type == Rule.DISALLOW:
469 error_descriptions.append(description_with_path)
470 else:
471 warning_descriptions.append(description_with_path)
472
473 results = []
474 if error_descriptions:
475 results.append(output_api.PresubmitError(
kjellandera7066a32017-03-23 03:47:05 -0700476 'You added one or more #includes that violate checkdeps rules.\n'
477 'Check that the DEPS files in these locations contain valid rules.\n'
478 'See https://cs.chromium.org/chromium/src/buildtools/checkdeps/ for '
479 'more details about checkdeps.',
kjellander@webrtc.org3bd41562014-09-01 11:06:37 +0000480 error_descriptions))
481 if warning_descriptions:
482 results.append(output_api.PresubmitPromptOrNotify(
483 'You added one or more #includes of files that are temporarily\n'
484 'allowed but being removed. Can you avoid introducing the\n'
kjellandera7066a32017-03-23 03:47:05 -0700485 '#include? See relevant DEPS file(s) for details and contacts.\n'
486 'See https://cs.chromium.org/chromium/src/buildtools/checkdeps/ for '
487 'more details about checkdeps.',
kjellander@webrtc.org3bd41562014-09-01 11:06:37 +0000488 warning_descriptions))
489 return results
490
charujain9893e252017-09-14 13:33:22 +0200491def CheckCommitMessageBugEntry(input_api, output_api):
492 """Check that bug entries are well-formed in commit message."""
493 bogus_bug_msg = (
Mirko Bonadei61880182017-10-12 15:12:35 +0200494 'Bogus Bug entry: %s. Please specify the issue tracker prefix and the '
charujain9893e252017-09-14 13:33:22 +0200495 'issue number, separated by a colon, e.g. webrtc:123 or chromium:12345.')
496 results = []
Mirko Bonadei61880182017-10-12 15:12:35 +0200497 for bug in input_api.change.BugsFromDescription():
charujain9893e252017-09-14 13:33:22 +0200498 bug = bug.strip()
499 if bug.lower() == 'none':
500 continue
charujain81a58c72017-09-25 13:25:45 +0200501 if 'b/' not in bug and ':' not in bug:
charujain9893e252017-09-14 13:33:22 +0200502 try:
503 if int(bug) > 100000:
504 # Rough indicator for current chromium bugs.
505 prefix_guess = 'chromium'
506 else:
507 prefix_guess = 'webrtc'
Mirko Bonadei61880182017-10-12 15:12:35 +0200508 results.append('Bug entry requires issue tracker prefix, e.g. %s:%s' %
charujain9893e252017-09-14 13:33:22 +0200509 (prefix_guess, bug))
510 except ValueError:
511 results.append(bogus_bug_msg % bug)
charujain81a58c72017-09-25 13:25:45 +0200512 elif not (re.match(r'\w+:\d+', bug) or re.match(r'b/\d+', bug)):
charujain9893e252017-09-14 13:33:22 +0200513 results.append(bogus_bug_msg % bug)
514 return [output_api.PresubmitError(r) for r in results]
515
516def CheckChangeHasBugField(input_api, output_api):
Mirko Bonadei61880182017-10-12 15:12:35 +0200517 """Requires that the changelist is associated with a bug.
kjellanderd1e26a92016-09-19 08:11:16 -0700518
519 This check is stricter than the one in depot_tools/presubmit_canned_checks.py
Mirko Bonadei61880182017-10-12 15:12:35 +0200520 since it fails the presubmit if the bug field is missing or doesn't contain
kjellanderd1e26a92016-09-19 08:11:16 -0700521 a bug reference.
Mirko Bonadei61880182017-10-12 15:12:35 +0200522
523 This supports both 'BUG=' and 'Bug:' since we are in the process of migrating
524 to Gerrit and it encourages the usage of 'Bug:'.
kjellanderd1e26a92016-09-19 08:11:16 -0700525 """
Mirko Bonadei61880182017-10-12 15:12:35 +0200526 if input_api.change.BugsFromDescription():
kjellanderd1e26a92016-09-19 08:11:16 -0700527 return []
528 else:
529 return [output_api.PresubmitError(
Mirko Bonadei61880182017-10-12 15:12:35 +0200530 'The "Bug: [bug number]" footer is mandatory. Please create a bug and '
kjellanderd1e26a92016-09-19 08:11:16 -0700531 'reference it using either of:\n'
Mirko Bonadei61880182017-10-12 15:12:35 +0200532 ' * https://bugs.webrtc.org - reference it using Bug: webrtc:XXXX\n'
533 ' * https://crbug.com - reference it using Bug: chromium:XXXXXX')]
kjellander@webrtc.orge4158642014-08-06 09:11:18 +0000534
charujain9893e252017-09-14 13:33:22 +0200535def CheckJSONParseErrors(input_api, output_api):
kjellander569cf942016-02-11 05:02:59 -0800536 """Check that JSON files do not contain syntax errors."""
537
538 def FilterFile(affected_file):
539 return input_api.os_path.splitext(affected_file.LocalPath())[1] == '.json'
540
541 def GetJSONParseError(input_api, filename):
542 try:
543 contents = input_api.ReadFile(filename)
544 input_api.json.loads(contents)
545 except ValueError as e:
546 return e
547 return None
548
549 results = []
550 for affected_file in input_api.AffectedFiles(
551 file_filter=FilterFile, include_deletes=False):
552 parse_error = GetJSONParseError(input_api,
553 affected_file.AbsoluteLocalPath())
554 if parse_error:
555 results.append(output_api.PresubmitError('%s could not be parsed: %s' %
556 (affected_file.LocalPath(), parse_error)))
557 return results
558
559
charujain9893e252017-09-14 13:33:22 +0200560def RunPythonTests(input_api, output_api):
kjellanderc88b5d52017-04-05 06:42:43 -0700561 def Join(*args):
Henrik Kjellander8d3ad822015-05-26 19:52:05 +0200562 return input_api.os_path.join(input_api.PresubmitLocalPath(), *args)
563
564 test_directories = [
Edward Lemur6d01f6d2017-09-14 17:02:01 +0200565 input_api.PresubmitLocalPath(),
Mirko Bonadei92ea95e2017-09-15 06:47:31 +0200566 Join('rtc_tools', 'py_event_log_analyzer'),
567 Join('rtc_tools'),
568 Join('audio', 'test', 'unittests'),
ehmaldonado4fb97462017-01-30 05:27:22 -0800569 ] + [
Henrik Kjellander90fd7d82017-05-09 08:30:10 +0200570 root for root, _, files in os.walk(Join('tools_webrtc'))
ehmaldonado4fb97462017-01-30 05:27:22 -0800571 if any(f.endswith('_test.py') for f in files)
Henrik Kjellander8d3ad822015-05-26 19:52:05 +0200572 ]
573
574 tests = []
575 for directory in test_directories:
576 tests.extend(
577 input_api.canned_checks.GetUnitTestsInDirectory(
578 input_api,
579 output_api,
580 directory,
581 whitelist=[r'.+_test\.py$']))
582 return input_api.RunTests(tests, parallel=True)
583
584
charujain9893e252017-09-14 13:33:22 +0200585def CheckUsageOfGoogleProtobufNamespace(input_api, output_api):
mbonadei38415b22017-04-07 05:38:01 -0700586 """Checks that the namespace google::protobuf has not been used."""
587 files = []
588 pattern = input_api.re.compile(r'google::protobuf')
Mirko Bonadei92ea95e2017-09-15 06:47:31 +0200589 proto_utils_path = os.path.join('rtc_base', 'protobuf_utils.h')
mbonadei38415b22017-04-07 05:38:01 -0700590 for f in input_api.AffectedSourceFiles(input_api.FilterSourceFile):
591 if f.LocalPath() in [proto_utils_path, 'PRESUBMIT.py']:
592 continue
593 contents = input_api.ReadFile(f)
594 if pattern.search(contents):
595 files.append(f)
596
597 if files:
598 return [output_api.PresubmitError(
599 'Please avoid to use namespace `google::protobuf` directly.\n'
600 'Add a using directive in `%s` and include that header instead.'
601 % proto_utils_path, files)]
602 return []
603
604
Mirko Bonadei92ea95e2017-09-15 06:47:31 +0200605def _LicenseHeader(input_api):
606 """Returns the license header regexp."""
607 # Accept any year number from 2003 to the current year
608 current_year = int(input_api.time.strftime('%Y'))
609 allowed_years = (str(s) for s in reversed(xrange(2003, current_year + 1)))
610 years_re = '(' + '|'.join(allowed_years) + ')'
611 license_header = (
612 r'.*? Copyright( \(c\))? %(year)s The WebRTC [Pp]roject [Aa]uthors\. '
613 r'All [Rr]ights [Rr]eserved\.\n'
614 r'.*?\n'
615 r'.*? Use of this source code is governed by a BSD-style license\n'
616 r'.*? that can be found in the LICENSE file in the root of the source\n'
617 r'.*? tree\. An additional intellectual property rights grant can be '
618 r'found\n'
619 r'.*? in the file PATENTS\. All contributing project authors may\n'
620 r'.*? be found in the AUTHORS file in the root of the source tree\.\n'
621 ) % {
622 'year': years_re,
623 }
624 return license_header
625
626
charujain9893e252017-09-14 13:33:22 +0200627def CommonChecks(input_api, output_api):
andrew@webrtc.org53df1362012-01-26 21:24:23 +0000628 """Checks common to both upload and commit."""
niklase@google.comda159d62011-05-30 11:51:34 +0000629 results = []
tkchin42f580e2015-11-26 23:18:23 -0800630 # Filter out files that are in objc or ios dirs from being cpplint-ed since
631 # they do not follow C++ lint rules.
632 black_list = input_api.DEFAULT_BLACK_LIST + (
633 r".*\bobjc[\\\/].*",
Kári Tristan Helgason3fa35172016-09-09 08:55:05 +0000634 r".*objc\.[hcm]+$",
tkchin42f580e2015-11-26 23:18:23 -0800635 )
636 source_file_filter = lambda x: input_api.FilterSourceFile(x, None, black_list)
charujain9893e252017-09-14 13:33:22 +0200637 results.extend(CheckApprovedFilesLintClean(
tkchin42f580e2015-11-26 23:18:23 -0800638 input_api, output_api, source_file_filter))
Mirko Bonadei92ea95e2017-09-15 06:47:31 +0200639 results.extend(input_api.canned_checks.CheckLicense(
640 input_api, output_api, _LicenseHeader(input_api)))
phoglund@webrtc.org5d3713932013-03-07 09:59:43 +0000641 results.extend(input_api.canned_checks.RunPylint(input_api, output_api,
kjellander@webrtc.org177567c2016-12-22 10:40:28 +0100642 black_list=(r'^base[\\\/].*\.py$',
Henrik Kjellander14771ac2015-06-02 13:10:04 +0200643 r'^build[\\\/].*\.py$',
644 r'^buildtools[\\\/].*\.py$',
kjellander38c65c82017-04-12 22:43:38 -0700645 r'^infra[\\\/].*\.py$',
Henrik Kjellander0779e8f2016-12-22 12:01:17 +0100646 r'^ios[\\\/].*\.py$',
Henrik Kjellander14771ac2015-06-02 13:10:04 +0200647 r'^out.*[\\\/].*\.py$',
648 r'^testing[\\\/].*\.py$',
649 r'^third_party[\\\/].*\.py$',
kjellander@webrtc.org177567c2016-12-22 10:40:28 +0100650 r'^tools[\\\/].*\.py$',
kjellanderafd54942016-12-17 12:21:39 -0800651 # TODO(phoglund): should arguably be checked.
Henrik Kjellander90fd7d82017-05-09 08:30:10 +0200652 r'^tools_webrtc[\\\/]mb[\\\/].*\.py$',
653 r'^tools_webrtc[\\\/]valgrind[\\\/].*\.py$',
Henrik Kjellander14771ac2015-06-02 13:10:04 +0200654 r'^xcodebuild.*[\\\/].*\.py$',),
Henrik Kjellander57e5fd22015-05-25 12:55:39 +0200655 pylintrc='pylintrc'))
kjellander569cf942016-02-11 05:02:59 -0800656
nisse3d21e232016-09-02 03:07:06 -0700657 # TODO(nisse): talk/ is no more, so make below checks simpler?
Henrik Kjellander57e5fd22015-05-25 12:55:39 +0200658 # WebRTC can't use the presubmit_canned_checks.PanProjectChecks function since
659 # we need to have different license checks in talk/ and webrtc/ directories.
660 # Instead, hand-picked checks are included below.
Henrik Kjellander63224672015-09-08 08:03:56 +0200661
tkchin3cd9a302016-06-08 12:40:28 -0700662 # .m and .mm files are ObjC files. For simplicity we will consider .h files in
663 # ObjC subdirectories ObjC headers.
664 objc_filter_list = (r'.+\.m$', r'.+\.mm$', r'.+objc\/.+\.h$')
Henrik Kjellanderb4af3d62016-11-16 20:11:29 +0100665 # Skip long-lines check for DEPS and GN files.
666 build_file_filter_list = (r'.+\.gn$', r'.+\.gni$', 'DEPS')
tkchin3cd9a302016-06-08 12:40:28 -0700667 eighty_char_sources = lambda x: input_api.FilterSourceFile(x,
668 black_list=build_file_filter_list + objc_filter_list)
669 hundred_char_sources = lambda x: input_api.FilterSourceFile(x,
670 white_list=objc_filter_list)
andrew@webrtc.org2442de12012-01-23 17:45:41 +0000671 results.extend(input_api.canned_checks.CheckLongLines(
tkchin3cd9a302016-06-08 12:40:28 -0700672 input_api, output_api, maxlen=80, source_file_filter=eighty_char_sources))
673 results.extend(input_api.canned_checks.CheckLongLines(
674 input_api, output_api, maxlen=100,
675 source_file_filter=hundred_char_sources))
676
andrew@webrtc.org2442de12012-01-23 17:45:41 +0000677 results.extend(input_api.canned_checks.CheckChangeHasNoTabs(
678 input_api, output_api))
andrew@webrtc.org53df1362012-01-26 21:24:23 +0000679 results.extend(input_api.canned_checks.CheckChangeHasNoStrayWhitespace(
680 input_api, output_api))
kjellandere5dc62a2016-12-14 00:16:21 -0800681 results.extend(input_api.canned_checks.CheckAuthorizedAuthor(
682 input_api, output_api))
andrew@webrtc.org53df1362012-01-26 21:24:23 +0000683 results.extend(input_api.canned_checks.CheckChangeTodoHasOwner(
684 input_api, output_api))
charujain9893e252017-09-14 13:33:22 +0200685 results.extend(CheckNativeApiHeaderChanges(input_api, output_api))
686 results.extend(CheckNoIOStreamInHeaders(input_api, output_api))
687 results.extend(CheckNoPragmaOnce(input_api, output_api))
688 results.extend(CheckNoFRIEND_TEST(input_api, output_api))
689 results.extend(CheckGnChanges(input_api, output_api))
690 results.extend(CheckUnwantedDependencies(input_api, output_api))
691 results.extend(CheckJSONParseErrors(input_api, output_api))
692 results.extend(RunPythonTests(input_api, output_api))
693 results.extend(CheckUsageOfGoogleProtobufNamespace(input_api, output_api))
Mirko Bonadei866d3372017-09-15 12:35:26 +0200694 results.extend(CheckOrphanHeaders(input_api, output_api))
Mirko Bonadeia730c1c2017-09-18 11:33:13 +0200695 results.extend(CheckNewlineAtTheEndOfProtoFiles(input_api, output_api))
andrew@webrtc.org53df1362012-01-26 21:24:23 +0000696 return results
andrew@webrtc.org2442de12012-01-23 17:45:41 +0000697
kjellander@webrtc.orge4158642014-08-06 09:11:18 +0000698
andrew@webrtc.org53df1362012-01-26 21:24:23 +0000699def CheckChangeOnUpload(input_api, output_api):
700 results = []
charujain9893e252017-09-14 13:33:22 +0200701 results.extend(CommonChecks(input_api, output_api))
Oleh Prypin920b6532017-10-05 11:28:51 +0200702 results.extend(CheckGnGen(input_api, output_api))
Henrik Kjellander57e5fd22015-05-25 12:55:39 +0200703 results.extend(
704 input_api.canned_checks.CheckGNFormatted(input_api, output_api))
niklase@google.comda159d62011-05-30 11:51:34 +0000705 return results
706
kjellander@webrtc.orge4158642014-08-06 09:11:18 +0000707
andrew@webrtc.org2442de12012-01-23 17:45:41 +0000708def CheckChangeOnCommit(input_api, output_api):
niklase@google.com1198db92011-06-09 07:07:24 +0000709 results = []
charujain9893e252017-09-14 13:33:22 +0200710 results.extend(CommonChecks(input_api, output_api))
711 results.extend(VerifyNativeApiHeadersListIsValid(input_api, output_api))
niklase@google.com1198db92011-06-09 07:07:24 +0000712 results.extend(input_api.canned_checks.CheckOwners(input_api, output_api))
andrew@webrtc.org53df1362012-01-26 21:24:23 +0000713 results.extend(input_api.canned_checks.CheckChangeWasUploaded(
714 input_api, output_api))
715 results.extend(input_api.canned_checks.CheckChangeHasDescription(
716 input_api, output_api))
charujain9893e252017-09-14 13:33:22 +0200717 results.extend(CheckChangeHasBugField(input_api, output_api))
718 results.extend(CheckCommitMessageBugEntry(input_api, output_api))
kjellander@webrtc.org12cb88c2014-02-13 11:53:43 +0000719 results.extend(input_api.canned_checks.CheckTreeIsOpen(
720 input_api, output_api,
721 json_url='http://webrtc-status.appspot.com/current?format=json'))
niklase@google.com1198db92011-06-09 07:07:24 +0000722 return results
mbonadei74973ed2017-05-09 07:58:05 -0700723
724
charujain9893e252017-09-14 13:33:22 +0200725def CheckOrphanHeaders(input_api, output_api):
mbonadei74973ed2017-05-09 07:58:05 -0700726 # We need to wait until we have an input_api object and use this
727 # roundabout construct to import prebubmit_checks_lib because this file is
728 # eval-ed and thus doesn't have __file__.
729 error_msg = """Header file {} is not listed in any GN target.
730 Please create a target or add it to an existing one in {}"""
731 results = []
Oleh Prypin2f33a562017-10-04 20:17:54 +0200732 with _AddToPath(input_api.os_path.join(
733 input_api.PresubmitLocalPath(), 'tools_webrtc', 'presubmit_checks_lib')):
mbonadei74973ed2017-05-09 07:58:05 -0700734 from check_orphan_headers import GetBuildGnPathFromFilePath
735 from check_orphan_headers import IsHeaderInBuildGn
mbonadei74973ed2017-05-09 07:58:05 -0700736
737 for f in input_api.AffectedSourceFiles(input_api.FilterSourceFile):
mbonadeia644ad32017-05-10 05:21:55 -0700738 if f.LocalPath().endswith('.h') and f.Action() == 'A':
mbonadei74973ed2017-05-09 07:58:05 -0700739 file_path = os.path.abspath(f.LocalPath())
740 root_dir = os.getcwd()
741 gn_file_path = GetBuildGnPathFromFilePath(file_path, os.path.exists,
742 root_dir)
743 in_build_gn = IsHeaderInBuildGn(file_path, gn_file_path)
744 if not in_build_gn:
745 results.append(output_api.PresubmitError(error_msg.format(
746 file_path, gn_file_path)))
747 return results
Mirko Bonadei960fd5b2017-06-29 14:59:36 +0200748
749
Mirko Bonadeia730c1c2017-09-18 11:33:13 +0200750def CheckNewlineAtTheEndOfProtoFiles(input_api, output_api):
Mirko Bonadei960fd5b2017-06-29 14:59:36 +0200751 """Checks that all .proto files are terminated with a newline."""
752 error_msg = 'File {} must end with exactly one newline.'
753 results = []
754 source_file_filter = lambda x: input_api.FilterSourceFile(
755 x, white_list=(r'.+\.proto$',))
756 for f in input_api.AffectedSourceFiles(source_file_filter):
757 file_path = f.LocalPath()
758 with open(file_path) as f:
759 lines = f.readlines()
Mirko Bonadeia730c1c2017-09-18 11:33:13 +0200760 if len(lines) > 0 and not lines[-1].endswith('\n'):
Mirko Bonadei960fd5b2017-06-29 14:59:36 +0200761 results.append(output_api.PresubmitError(error_msg.format(file_path)))
762 return results