blob: 47a1be68f7d3c9e108122a32a5e2ba4c68e27ab4 [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
oprypin2aa463f2017-03-23 03:17:02 -070016# Files and directories that are *skipped* by cpplint in the presubmit script.
Mirko Bonadeifc17a782020-06-30 14:31:37 +020017CPPLINT_EXCEPTIONS = [
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020018 'api/video_codecs/video_decoder.h',
19 'common_types.cc',
20 'common_types.h',
21 'examples/objc',
Amit Hilbuchce7032b2019-01-22 16:19:35 -080022 'media/base/stream_params.h',
23 'media/base/video_common.h',
24 'media/sctp/sctp_transport.cc',
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020025 'modules/audio_coding',
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020026 'modules/audio_device',
27 'modules/audio_processing',
28 'modules/desktop_capture',
29 'modules/include/module_common_types.h',
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020030 'modules/utility',
31 'modules/video_capture',
Amit Hilbuchce7032b2019-01-22 16:19:35 -080032 'p2p/base/pseudo_tcp.cc',
33 'p2p/base/pseudo_tcp.h',
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020034 '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),
Mirko Bonadeifc17a782020-06-30 14:31:37 +020048# include file and feature blocklists are
jbauchc4e3ead2016-02-19 00:25:55 -080049# google3-specific.
Mirko Bonadeie92e2862020-05-29 15:23:09 +020050# - runtime/references : Mutable references are not banned by the Google
51# C++ style guide anymore (starting from May 2020).
kjellandere5a87a52016-04-27 02:32:12 -070052# - whitespace/operators: Same as above (doesn't seem sufficient to eliminate
53# all move-related errors).
Mirko Bonadeifc17a782020-06-30 14:31:37 +020054DISABLED_LINT_FILTERS = [
jbauchc4e3ead2016-02-19 00:25:55 -080055 '-build/c++11',
Mirko Bonadeie92e2862020-05-29 15:23:09 +020056 '-runtime/references',
kjellandere5a87a52016-04-27 02:32:12 -070057 '-whitespace/operators',
jbauchc4e3ead2016-02-19 00:25:55 -080058]
59
kjellanderfd595232015-12-04 02:44:09 -080060# List of directories of "supported" native APIs. That means changes to headers
61# will be done in a compatible way following this scheme:
62# 1. Non-breaking changes are made.
63# 2. The old APIs as marked as deprecated (with comments).
64# 3. Deprecation is announced to discuss-webrtc@googlegroups.com and
65# webrtc-users@google.com (internal list).
66# 4. (later) The deprecated APIs are removed.
kjellander53047c92015-12-02 23:56:14 -080067NATIVE_API_DIRS = (
Karl Wibergef52d8b82017-10-25 13:20:03 +020068 'api', # All subdirectories of api/ are included as well.
Mirko Bonadeia4eeeff2018-01-11 13:16:52 +010069 'media/base',
70 'media/engine',
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020071 'modules/audio_device/include',
72 'pc',
kjellanderdd705472016-06-09 11:17:27 -070073)
Mirko Bonadei4dc4e252017-09-19 13:49:16 +020074
kjellanderdd705472016-06-09 11:17:27 -070075# These directories should not be used but are maintained only to avoid breaking
76# some legacy downstream code.
77LEGACY_API_DIRS = (
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020078 'common_audio/include',
79 'modules/audio_coding/include',
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020080 'modules/audio_processing/include',
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020081 'modules/congestion_controller/include',
82 'modules/include',
83 'modules/remote_bitrate_estimator/include',
84 'modules/rtp_rtcp/include',
85 'modules/rtp_rtcp/source',
86 'modules/utility/include',
87 'modules/video_coding/codecs/h264/include',
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020088 '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',
kjellander53047c92015-12-02 23:56:14 -080093)
Mirko Bonadei4dc4e252017-09-19 13:49:16 +020094
Karl Wibergd4f01c12017-11-10 10:55:45 +010095# NOTE: The set of directories in API_DIRS should be the same as those
96# listed in the table in native-api.md.
kjellanderdd705472016-06-09 11:17:27 -070097API_DIRS = NATIVE_API_DIRS[:] + LEGACY_API_DIRS[:]
kjellander53047c92015-12-02 23:56:14 -080098
Mirko Bonadei4dc4e252017-09-19 13:49:16 +020099# TARGET_RE matches a GN target, and extracts the target name and the contents.
Mirko Bonadei2dcf3482020-06-05 14:30:41 +0200100TARGET_RE = re.compile(
101 r'(?P<indent>\s*)(?P<target_type>\w+)\("(?P<target_name>\w+)"\) {'
102 r'(?P<target_contents>.*?)'
103 r'(?P=indent)}',
104 re.MULTILINE | re.DOTALL)
Mirko Bonadei4dc4e252017-09-19 13:49:16 +0200105
106# SOURCES_RE matches a block of sources inside a GN target.
107SOURCES_RE = re.compile(r'sources \+?= \[(?P<sources>.*?)\]',
108 re.MULTILINE | re.DOTALL)
109
Mirko Bonadei2dcf3482020-06-05 14:30:41 +0200110# DEPS_RE matches a block of sources inside a GN target.
111DEPS_RE = re.compile(r'\bdeps \+?= \[(?P<deps>.*?)\]',
112 re.MULTILINE | re.DOTALL)
113
Mirko Bonadei4dc4e252017-09-19 13:49:16 +0200114# FILE_PATH_RE matchies a file path.
115FILE_PATH_RE = re.compile(r'"(?P<file_path>(\w|\/)+)(?P<extension>\.\w+)"')
116
kjellander53047c92015-12-02 23:56:14 -0800117
Mirko Bonadeid8665442018-09-04 12:17:27 +0200118def FindSrcDirPath(starting_dir):
119 """Returns the abs path to the src/ dir of the project."""
120 src_dir = starting_dir
121 while os.path.basename(src_dir) != 'src':
122 src_dir = os.path.normpath(os.path.join(src_dir, os.pardir))
123 return src_dir
124
125
Oleh Prypin2f33a562017-10-04 20:17:54 +0200126@contextmanager
127def _AddToPath(*paths):
128 original_sys_path = sys.path
129 sys.path.extend(paths)
130 try:
131 yield
132 finally:
133 # Restore sys.path to what it was before.
134 sys.path = original_sys_path
ehmaldonado4fb97462017-01-30 05:27:22 -0800135
136
charujain9893e252017-09-14 13:33:22 +0200137def VerifyNativeApiHeadersListIsValid(input_api, output_api):
kjellander53047c92015-12-02 23:56:14 -0800138 """Ensures the list of native API header directories is up to date."""
139 non_existing_paths = []
140 native_api_full_paths = [
141 input_api.os_path.join(input_api.PresubmitLocalPath(),
kjellanderdd705472016-06-09 11:17:27 -0700142 *path.split('/')) for path in API_DIRS]
kjellander53047c92015-12-02 23:56:14 -0800143 for path in native_api_full_paths:
144 if not os.path.isdir(path):
145 non_existing_paths.append(path)
146 if non_existing_paths:
147 return [output_api.PresubmitError(
148 'Directories to native API headers have changed which has made the '
149 'list in PRESUBMIT.py outdated.\nPlease update it to the current '
150 'location of our native APIs.',
151 non_existing_paths)]
152 return []
153
Artem Titove92675b2018-05-22 10:21:27 +0200154
kjellanderc88b5d52017-04-05 06:42:43 -0700155API_CHANGE_MSG = """
kwibergeb133022016-04-07 07:41:48 -0700156You seem to be changing native API header files. Please make sure that you:
oprypin375b9ac2017-02-13 04:13:23 -0800157 1. Make compatible changes that don't break existing clients. Usually
158 this is done by keeping the existing method signatures unchanged.
159 2. Mark the old stuff as deprecated (see RTC_DEPRECATED macro).
kwibergeb133022016-04-07 07:41:48 -0700160 3. Create a timeline and plan for when the deprecated stuff will be
161 removed. (The amount of time we give users to change their code
162 should be informed by how much work it is for them. If they just
163 need to replace one name with another or something equally
164 simple, 1-2 weeks might be good; if they need to do serious work,
165 up to 3 months may be called for.)
166 4. Update/inform existing downstream code owners to stop using the
167 deprecated stuff. (Send announcements to
168 discuss-webrtc@googlegroups.com and webrtc-users@google.com.)
169 5. Remove the deprecated stuff, once the agreed-upon amount of time
170 has passed.
171Related files:
172"""
kjellander53047c92015-12-02 23:56:14 -0800173
Artem Titove92675b2018-05-22 10:21:27 +0200174
charujain9893e252017-09-14 13:33:22 +0200175def CheckNativeApiHeaderChanges(input_api, output_api):
kjellander53047c92015-12-02 23:56:14 -0800176 """Checks to remind proper changing of native APIs."""
177 files = []
Karl Wiberg6bfac032017-10-27 15:14:20 +0200178 source_file_filter = lambda x: input_api.FilterSourceFile(
Mirko Bonadeifc17a782020-06-30 14:31:37 +0200179 x, allow_list=[r'.+\.(gn|gni|h)$'])
Karl Wiberg6bfac032017-10-27 15:14:20 +0200180 for f in input_api.AffectedSourceFiles(source_file_filter):
181 for path in API_DIRS:
182 dn = os.path.dirname(f.LocalPath())
183 if path == 'api':
184 # Special case: Subdirectories included.
185 if dn == 'api' or dn.startswith('api/'):
Niels Möller1d201852019-06-26 12:58:27 +0200186 files.append(f.LocalPath())
Karl Wiberg6bfac032017-10-27 15:14:20 +0200187 else:
188 # Normal case: Subdirectories not included.
189 if dn == path:
Niels Möller1d201852019-06-26 12:58:27 +0200190 files.append(f.LocalPath())
kjellander53047c92015-12-02 23:56:14 -0800191
192 if files:
kjellanderc88b5d52017-04-05 06:42:43 -0700193 return [output_api.PresubmitNotifyResult(API_CHANGE_MSG, files)]
kjellander53047c92015-12-02 23:56:14 -0800194 return []
195
kjellander@webrtc.org0fcaf992015-11-26 15:24:52 +0100196
Artem Titova04d1402018-05-11 11:23:00 +0200197def CheckNoIOStreamInHeaders(input_api, output_api,
198 source_file_filter):
kjellander@webrtc.org51198f12012-02-21 17:53:46 +0000199 """Checks to make sure no .h files include <iostream>."""
200 files = []
201 pattern = input_api.re.compile(r'^#include\s*<iostream>',
202 input_api.re.MULTILINE)
Artem Titova04d1402018-05-11 11:23:00 +0200203 file_filter = lambda x: (input_api.FilterSourceFile(x)
204 and source_file_filter(x))
205 for f in input_api.AffectedSourceFiles(file_filter):
kjellander@webrtc.org51198f12012-02-21 17:53:46 +0000206 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 len(files):
Henrik Kjellander57e5fd22015-05-25 12:55:39 +0200213 return [output_api.PresubmitError(
kjellander@webrtc.org51198f12012-02-21 17:53:46 +0000214 'Do not #include <iostream> in header files, since it inserts static ' +
215 'initialization into every file including the header. Instead, ' +
216 '#include <ostream>. See http://crbug.com/94794',
Henrik Kjellander57e5fd22015-05-25 12:55:39 +0200217 files)]
kjellander@webrtc.org51198f12012-02-21 17:53:46 +0000218 return []
219
kjellander@webrtc.orge4158642014-08-06 09:11:18 +0000220
Artem Titova04d1402018-05-11 11:23:00 +0200221def CheckNoPragmaOnce(input_api, output_api,
222 source_file_filter):
kjellander6aeef742017-02-20 01:13:18 -0800223 """Make sure that banned functions are not used."""
224 files = []
225 pattern = input_api.re.compile(r'^#pragma\s+once',
226 input_api.re.MULTILINE)
Artem Titova04d1402018-05-11 11:23:00 +0200227 file_filter = lambda x: (input_api.FilterSourceFile(x)
228 and source_file_filter(x))
229 for f in input_api.AffectedSourceFiles(file_filter):
kjellander6aeef742017-02-20 01:13:18 -0800230 if not f.LocalPath().endswith('.h'):
231 continue
232 contents = input_api.ReadFile(f)
233 if pattern.search(contents):
234 files.append(f)
235
236 if files:
237 return [output_api.PresubmitError(
238 'Do not use #pragma once in header files.\n'
239 'See http://www.chromium.org/developers/coding-style#TOC-File-headers',
240 files)]
241 return []
242
243
Artem Titova04d1402018-05-11 11:23:00 +0200244def CheckNoFRIEND_TEST(input_api, output_api, # pylint: disable=invalid-name
245 source_file_filter):
kjellander@webrtc.org51198f12012-02-21 17:53:46 +0000246 """Make sure that gtest's FRIEND_TEST() macro is not used, the
247 FRIEND_TEST_ALL_PREFIXES() macro from testsupport/gtest_prod_util.h should be
248 used instead since that allows for FLAKY_, FAILS_ and DISABLED_ prefixes."""
249 problems = []
250
Artem Titova04d1402018-05-11 11:23:00 +0200251 file_filter = lambda f: (f.LocalPath().endswith(('.cc', '.h'))
252 and source_file_filter(f))
kjellander@webrtc.org51198f12012-02-21 17:53:46 +0000253 for f in input_api.AffectedFiles(file_filter=file_filter):
254 for line_num, line in f.ChangedContents():
255 if 'FRIEND_TEST(' in line:
256 problems.append(' %s:%d' % (f.LocalPath(), line_num))
257
258 if not problems:
259 return []
260 return [output_api.PresubmitPromptWarning('WebRTC\'s code should not use '
261 'gtest\'s FRIEND_TEST() macro. Include testsupport/gtest_prod_util.h and '
262 'use FRIEND_TEST_ALL_PREFIXES() instead.\n' + '\n'.join(problems))]
263
kjellander@webrtc.orge4158642014-08-06 09:11:18 +0000264
Mirko Bonadeifc17a782020-06-30 14:31:37 +0200265def IsLintDisabled(disabled_paths, file_path):
266 """ Checks if a file is disabled for lint check."""
267 for path in disabled_paths:
oprypin2aa463f2017-03-23 03:17:02 -0700268 if file_path == path or os.path.dirname(file_path).startswith(path):
kjellander@webrtc.org0fcaf992015-11-26 15:24:52 +0100269 return True
270 return False
271
272
charujain9893e252017-09-14 13:33:22 +0200273def CheckApprovedFilesLintClean(input_api, output_api,
Artem Titova04d1402018-05-11 11:23:00 +0200274 source_file_filter=None):
Mirko Bonadeifc17a782020-06-30 14:31:37 +0200275 """Checks that all new or non-exempt .cc and .h files pass cpplint.py.
charujain9893e252017-09-14 13:33:22 +0200276 This check is based on CheckChangeLintsClean in
kjellander@webrtc.org51198f12012-02-21 17:53:46 +0000277 depot_tools/presubmit_canned_checks.py but has less filters and only checks
278 added files."""
279 result = []
280
281 # Initialize cpplint.
282 import cpplint
283 # Access to a protected member _XX of a client class
284 # pylint: disable=W0212
285 cpplint._cpplint_state.ResetErrorCounts()
286
jbauchc4e3ead2016-02-19 00:25:55 -0800287 lint_filters = cpplint._Filters()
Mirko Bonadeifc17a782020-06-30 14:31:37 +0200288 lint_filters.extend(DISABLED_LINT_FILTERS)
jbauchc4e3ead2016-02-19 00:25:55 -0800289 cpplint._SetFilters(','.join(lint_filters))
290
Mirko Bonadeifc17a782020-06-30 14:31:37 +0200291 # Create a platform independent exempt list for cpplint.
292 disabled_paths = [input_api.os_path.join(*path.split('/'))
293 for path in CPPLINT_EXCEPTIONS]
kjellander@webrtc.org0fcaf992015-11-26 15:24:52 +0100294
kjellander@webrtc.org51198f12012-02-21 17:53:46 +0000295 # Use the strictest verbosity level for cpplint.py (level 1) which is the
oprypin2aa463f2017-03-23 03:17:02 -0700296 # default when running cpplint.py from command line. To make it possible to
297 # work with not-yet-converted code, we're only applying it to new (or
Mirko Bonadeifc17a782020-06-30 14:31:37 +0200298 # moved/renamed) files and files not listed in CPPLINT_EXCEPTIONS.
kjellander@webrtc.org51198f12012-02-21 17:53:46 +0000299 verbosity_level = 1
300 files = []
301 for f in input_api.AffectedSourceFiles(source_file_filter):
Henrik Kjellander57e5fd22015-05-25 12:55:39 +0200302 # Note that moved/renamed files also count as added.
Mirko Bonadeifc17a782020-06-30 14:31:37 +0200303 if f.Action() == 'A' or not IsLintDisabled(disabled_paths,
Artem Titove92675b2018-05-22 10:21:27 +0200304 f.LocalPath()):
kjellander@webrtc.org51198f12012-02-21 17:53:46 +0000305 files.append(f.AbsoluteLocalPath())
mflodman@webrtc.org2a452092012-07-01 05:55:23 +0000306
kjellander@webrtc.org51198f12012-02-21 17:53:46 +0000307 for file_name in files:
308 cpplint.ProcessFile(file_name, verbosity_level)
309
310 if cpplint._cpplint_state.error_count > 0:
311 if input_api.is_committing:
oprypin8e58d652017-03-21 07:52:41 -0700312 res_type = output_api.PresubmitError
kjellander@webrtc.org51198f12012-02-21 17:53:46 +0000313 else:
314 res_type = output_api.PresubmitPromptWarning
315 result = [res_type('Changelist failed cpplint.py check.')]
316
317 return result
318
Artem Titove92675b2018-05-22 10:21:27 +0200319
charujain9893e252017-09-14 13:33:22 +0200320def CheckNoSourcesAbove(input_api, gn_files, output_api):
ehmaldonado5b1ba082016-09-02 05:51:08 -0700321 # Disallow referencing source files with paths above the GN file location.
322 source_pattern = input_api.re.compile(r' +sources \+?= \[(.*?)\]',
323 re.MULTILINE | re.DOTALL)
324 file_pattern = input_api.re.compile(r'"((\.\./.*?)|(//.*?))"')
325 violating_gn_files = set()
326 violating_source_entries = []
327 for gn_file in gn_files:
328 contents = input_api.ReadFile(gn_file)
329 for source_block_match in source_pattern.finditer(contents):
330 # Find all source list entries starting with ../ in the source block
331 # (exclude overrides entries).
332 for file_list_match in file_pattern.finditer(source_block_match.group(1)):
333 source_file = file_list_match.group(1)
334 if 'overrides/' not in source_file:
335 violating_source_entries.append(source_file)
336 violating_gn_files.add(gn_file)
337 if violating_gn_files:
338 return [output_api.PresubmitError(
339 'Referencing source files above the directory of the GN file is not '
Henrik Kjellanderb4af3d62016-11-16 20:11:29 +0100340 'allowed. Please introduce new GN targets in the proper location '
341 'instead.\n'
ehmaldonado5b1ba082016-09-02 05:51:08 -0700342 'Invalid source entries:\n'
343 '%s\n'
344 'Violating GN files:' % '\n'.join(violating_source_entries),
345 items=violating_gn_files)]
346 return []
347
Artem Titove92675b2018-05-22 10:21:27 +0200348
Mirko Bonadei2dcf3482020-06-05 14:30:41 +0200349def CheckAbseilDependencies(input_api, gn_files, output_api):
350 """Checks that Abseil dependencies are declared in `absl_deps`."""
351 absl_re = re.compile(r'third_party/abseil-cpp', re.MULTILINE | re.DOTALL)
352 target_types_to_check = [
353 'rtc_library',
354 'rtc_source_set',
Mirko Bonadei96115cf2020-06-23 23:39:56 +0200355 'rtc_static_library',
356 'webrtc_fuzzer_test',
Mirko Bonadei2dcf3482020-06-05 14:30:41 +0200357 ]
358 error_msg = ('Abseil dependencies in target "%s" (file: %s) '
359 'should be moved to the "absl_deps" parameter.')
360 errors = []
361
362 for gn_file in gn_files:
363 gn_file_content = input_api.ReadFile(gn_file)
364 for target_match in TARGET_RE.finditer(gn_file_content):
365 target_type = target_match.group('target_type')
366 target_name = target_match.group('target_name')
367 target_contents = target_match.group('target_contents')
368 if target_type in target_types_to_check:
369 for deps_match in DEPS_RE.finditer(target_contents):
370 deps = deps_match.group('deps').splitlines()
371 for dep in deps:
372 if re.search(absl_re, dep):
373 errors.append(
374 output_api.PresubmitError(error_msg % (target_name,
375 gn_file.LocalPath())))
376 break # no need to warn more than once per target
377 return errors
378
379
Mirko Bonadei4dc4e252017-09-19 13:49:16 +0200380def CheckNoMixingSources(input_api, gn_files, output_api):
381 """Disallow mixing C, C++ and Obj-C/Obj-C++ in the same target.
382
383 See bugs.webrtc.org/7743 for more context.
384 """
Artem Titove92675b2018-05-22 10:21:27 +0200385
Mirko Bonadei4dc4e252017-09-19 13:49:16 +0200386 def _MoreThanOneSourceUsed(*sources_lists):
387 sources_used = 0
388 for source_list in sources_lists:
389 if len(source_list):
390 sources_used += 1
391 return sources_used > 1
392
393 errors = defaultdict(lambda: [])
kjellander7439f972016-12-05 22:47:46 -0800394 for gn_file in gn_files:
Mirko Bonadei4dc4e252017-09-19 13:49:16 +0200395 gn_file_content = input_api.ReadFile(gn_file)
396 for target_match in TARGET_RE.finditer(gn_file_content):
397 # list_of_sources is a list of tuples of the form
398 # (c_files, cc_files, objc_files) that keeps track of all the sources
399 # defined in a target. A GN target can have more that on definition of
400 # sources (since it supports if/else statements).
401 # E.g.:
402 # rtc_static_library("foo") {
403 # if (is_win) {
404 # sources = [ "foo.cc" ]
405 # } else {
406 # sources = [ "foo.mm" ]
407 # }
408 # }
409 # This is allowed and the presubmit check should support this case.
410 list_of_sources = []
kjellander7439f972016-12-05 22:47:46 -0800411 c_files = []
412 cc_files = []
Mirko Bonadei4dc4e252017-09-19 13:49:16 +0200413 objc_files = []
414 target_name = target_match.group('target_name')
415 target_contents = target_match.group('target_contents')
416 for sources_match in SOURCES_RE.finditer(target_contents):
417 if '+=' not in sources_match.group(0):
418 if c_files or cc_files or objc_files:
419 list_of_sources.append((c_files, cc_files, objc_files))
420 c_files = []
421 cc_files = []
422 objc_files = []
423 for file_match in FILE_PATH_RE.finditer(sources_match.group(1)):
424 file_path = file_match.group('file_path')
425 extension = file_match.group('extension')
426 if extension == '.c':
427 c_files.append(file_path + extension)
428 if extension == '.cc':
429 cc_files.append(file_path + extension)
430 if extension in ['.m', '.mm']:
431 objc_files.append(file_path + extension)
432 list_of_sources.append((c_files, cc_files, objc_files))
433 for c_files_list, cc_files_list, objc_files_list in list_of_sources:
434 if _MoreThanOneSourceUsed(c_files_list, cc_files_list, objc_files_list):
435 all_sources = sorted(c_files_list + cc_files_list + objc_files_list)
436 errors[gn_file.LocalPath()].append((target_name, all_sources))
437 if errors:
kjellander7439f972016-12-05 22:47:46 -0800438 return [output_api.PresubmitError(
Mirko Bonadei4dc4e252017-09-19 13:49:16 +0200439 'GN targets cannot mix .c, .cc and .m (or .mm) source files.\n'
440 'Please create a separate target for each collection of sources.\n'
kjellander7439f972016-12-05 22:47:46 -0800441 'Mixed sources: \n'
442 '%s\n'
Mirko Bonadei4dc4e252017-09-19 13:49:16 +0200443 'Violating GN files:\n%s\n' % (json.dumps(errors, indent=2),
444 '\n'.join(errors.keys())))]
kjellander7439f972016-12-05 22:47:46 -0800445 return []
446
Artem Titove92675b2018-05-22 10:21:27 +0200447
charujain9893e252017-09-14 13:33:22 +0200448def CheckNoPackageBoundaryViolations(input_api, gn_files, output_api):
ehmaldonado4fb97462017-01-30 05:27:22 -0800449 cwd = input_api.PresubmitLocalPath()
Oleh Prypin2f33a562017-10-04 20:17:54 +0200450 with _AddToPath(input_api.os_path.join(
451 cwd, 'tools_webrtc', 'presubmit_checks_lib')):
452 from check_package_boundaries import CheckPackageBoundaries
453 build_files = [os.path.join(cwd, gn_file.LocalPath()) for gn_file in gn_files]
454 errors = CheckPackageBoundaries(cwd, build_files)[:5]
455 if errors:
ehmaldonado4fb97462017-01-30 05:27:22 -0800456 return [output_api.PresubmitError(
Oleh Prypin2f33a562017-10-04 20:17:54 +0200457 'There are package boundary violations in the following GN files:',
458 long_text='\n\n'.join(str(err) for err in errors))]
ehmaldonado4fb97462017-01-30 05:27:22 -0800459 return []
460
Mirko Bonadeia51bbd82018-03-08 16:15:45 +0100461
Mirko Bonadeif0e0d752018-07-04 08:48:18 +0200462def _ReportFileAndLine(filename, line_num):
Mirko Bonadeia51bbd82018-03-08 16:15:45 +0100463 """Default error formatter for _FindNewViolationsOfRule."""
464 return '%s (line %s)' % (filename, line_num)
465
466
Mirko Bonadeif0e0d752018-07-04 08:48:18 +0200467def CheckNoWarningSuppressionFlagsAreAdded(gn_files, input_api, output_api,
468 error_formatter=_ReportFileAndLine):
469 """Make sure that warning suppression flags are not added wihtout a reason."""
470 msg = ('Usage of //build/config/clang:extra_warnings is discouraged '
471 'in WebRTC.\n'
472 'If you are not adding this code (e.g. you are just moving '
473 'existing code) or you want to add an exception,\n'
474 'you can add a comment on the line that causes the problem:\n\n'
475 '"-Wno-odr" # no-presubmit-check TODO(bugs.webrtc.org/BUG_ID)\n'
476 '\n'
477 'Affected files:\n')
478 errors = [] # 2-element tuples with (file, line number)
479 clang_warn_re = input_api.re.compile(r'//build/config/clang:extra_warnings')
480 no_presubmit_re = input_api.re.compile(
481 r'# no-presubmit-check TODO\(bugs\.webrtc\.org/\d+\)')
482 for f in gn_files:
483 for line_num, line in f.ChangedContents():
484 if clang_warn_re.search(line) and not no_presubmit_re.search(line):
485 errors.append(error_formatter(f.LocalPath(), line_num))
486 if errors:
487 return [output_api.PresubmitError(msg, errors)]
488 return []
489
Mirko Bonadei9ce800d2019-02-05 16:48:13 +0100490
491def CheckNoTestCaseUsageIsAdded(input_api, output_api, source_file_filter,
492 error_formatter=_ReportFileAndLine):
493 error_msg = ('Usage of legacy GoogleTest API detected!\nPlease use the '
494 'new API: https://github.com/google/googletest/blob/master/'
495 'googletest/docs/primer.md#beware-of-the-nomenclature.\n'
496 'Affected files:\n')
497 errors = [] # 2-element tuples with (file, line number)
498 test_case_re = input_api.re.compile(r'TEST_CASE')
499 file_filter = lambda f: (source_file_filter(f)
500 and f.LocalPath().endswith('.cc'))
501 for f in input_api.AffectedSourceFiles(file_filter):
502 for line_num, line in f.ChangedContents():
503 if test_case_re.search(line):
504 errors.append(error_formatter(f.LocalPath(), line_num))
505 if errors:
506 return [output_api.PresubmitError(error_msg, errors)]
507 return []
508
509
Mirko Bonadeia51bbd82018-03-08 16:15:45 +0100510def CheckNoStreamUsageIsAdded(input_api, output_api,
Artem Titov739351d2018-05-11 12:21:36 +0200511 source_file_filter,
Mirko Bonadeif0e0d752018-07-04 08:48:18 +0200512 error_formatter=_ReportFileAndLine):
Mirko Bonadeia51bbd82018-03-08 16:15:45 +0100513 """Make sure that no more dependencies on stringstream are added."""
514 error_msg = ('Usage of <sstream>, <istream> and <ostream> in WebRTC is '
515 'deprecated.\n'
516 'This includes the following types:\n'
517 'std::istringstream, std::ostringstream, std::wistringstream, '
518 'std::wostringstream,\n'
519 'std::wstringstream, std::ostream, std::wostream, std::istream,'
520 'std::wistream,\n'
521 'std::iostream, std::wiostream.\n'
522 'If you are not adding this code (e.g. you are just moving '
523 'existing code),\n'
524 'you can add a comment on the line that causes the problem:\n\n'
525 '#include <sstream> // no-presubmit-check TODO(webrtc:8982)\n'
526 'std::ostream& F() { // no-presubmit-check TODO(webrtc:8982)\n'
527 '\n'
Karl Wibergebd01e82018-03-14 15:08:39 +0100528 'If you are adding new code, consider using '
529 'rtc::SimpleStringBuilder\n'
530 '(in rtc_base/strings/string_builder.h).\n'
Mirko Bonadeia51bbd82018-03-08 16:15:45 +0100531 'Affected files:\n')
532 errors = [] # 2-element tuples with (file, line number)
533 include_re = input_api.re.compile(r'#include <(i|o|s)stream>')
534 usage_re = input_api.re.compile(r'std::(w|i|o|io|wi|wo|wio)(string)*stream')
535 no_presubmit_re = input_api.re.compile(
Jonas Olsson74395342018-04-03 12:22:07 +0200536 r'// no-presubmit-check TODO\(webrtc:8982\)')
Artem Titova04d1402018-05-11 11:23:00 +0200537 file_filter = lambda x: (input_api.FilterSourceFile(x)
538 and source_file_filter(x))
Mirko Bonadei571791a2019-05-07 14:08:05 +0200539
540 def _IsException(file_path):
541 is_test = any(file_path.endswith(x) for x in ['_test.cc', '_tests.cc',
542 '_unittest.cc',
543 '_unittests.cc'])
Patrik Höglund2ea27962020-01-13 15:10:40 +0100544 return (file_path.startswith('examples') or
545 file_path.startswith('test') or
546 is_test)
547
Mirko Bonadei571791a2019-05-07 14:08:05 +0200548
Artem Titova04d1402018-05-11 11:23:00 +0200549 for f in input_api.AffectedSourceFiles(file_filter):
Mirko Bonadei571791a2019-05-07 14:08:05 +0200550 # Usage of stringstream is allowed under examples/ and in tests.
551 if f.LocalPath() == 'PRESUBMIT.py' or _IsException(f.LocalPath()):
Mirko Bonadeid2c83322018-03-19 10:31:47 +0000552 continue
553 for line_num, line in f.ChangedContents():
554 if ((include_re.search(line) or usage_re.search(line))
555 and not no_presubmit_re.search(line)):
556 errors.append(error_formatter(f.LocalPath(), line_num))
Mirko Bonadeia51bbd82018-03-08 16:15:45 +0100557 if errors:
558 return [output_api.PresubmitError(error_msg, errors)]
559 return []
560
Artem Titove92675b2018-05-22 10:21:27 +0200561
Mirko Bonadeia05d47e2018-05-09 11:03:38 +0200562def CheckPublicDepsIsNotUsed(gn_files, input_api, output_api):
563 """Checks that public_deps is not used without a good reason."""
Mirko Bonadei5c1ad592017-12-12 11:52:27 +0100564 result = []
Mirko Bonadeia05d47e2018-05-09 11:03:38 +0200565 no_presubmit_check_re = input_api.re.compile(
Joe Chen0b3a6e32019-12-26 23:01:42 -0800566 r'# no-presubmit-check TODO\(webrtc:\d+\)')
Mirko Bonadeia05d47e2018-05-09 11:03:38 +0200567 error_msg = ('public_deps is not recommended in WebRTC BUILD.gn files '
568 'because it doesn\'t map well to downstream build systems.\n'
569 'Used in: %s (line %d).\n'
570 'If you are not adding this code (e.g. you are just moving '
Patrik Höglund81c7a602020-01-30 11:32:33 +0100571 'existing code) or you have a good reason, you can add this '
572 'comment (verbatim) on the line that causes the problem:\n\n'
Mirko Bonadeia05d47e2018-05-09 11:03:38 +0200573 'public_deps = [ # no-presubmit-check TODO(webrtc:8603)\n')
Mirko Bonadei5c1ad592017-12-12 11:52:27 +0100574 for affected_file in gn_files:
575 for (line_number, affected_line) in affected_file.ChangedContents():
Patrik Höglund81c7a602020-01-30 11:32:33 +0100576 if 'public_deps' in affected_line:
577 surpressed = no_presubmit_check_re.search(affected_line)
578 if not surpressed:
579 result.append(
580 output_api.PresubmitError(error_msg % (affected_file.LocalPath(),
581 line_number)))
Mirko Bonadei5c1ad592017-12-12 11:52:27 +0100582 return result
583
Artem Titove92675b2018-05-22 10:21:27 +0200584
Mirko Bonadei05691dd2019-10-22 07:34:24 -0700585def CheckCheckIncludesIsNotUsed(gn_files, input_api, output_api):
Patrik Höglund6f491062018-01-11 12:04:23 +0100586 result = []
587 error_msg = ('check_includes overrides are not allowed since it can cause '
588 'incorrect dependencies to form. It effectively means that your '
589 'module can include any .h file without depending on its '
590 'corresponding target. There are some exceptional cases when '
Mirko Bonadei05691dd2019-10-22 07:34:24 -0700591 'this is allowed: if so, get approval from a .gn owner in the '
Patrik Höglund6f491062018-01-11 12:04:23 +0100592 'root OWNERS file.\n'
593 'Used in: %s (line %d).')
Mirko Bonadei05691dd2019-10-22 07:34:24 -0700594 no_presubmit_re = input_api.re.compile(
595 r'# no-presubmit-check TODO\(bugs\.webrtc\.org/\d+\)')
Patrik Höglund6f491062018-01-11 12:04:23 +0100596 for affected_file in gn_files:
597 for (line_number, affected_line) in affected_file.ChangedContents():
Mirko Bonadei05691dd2019-10-22 07:34:24 -0700598 if ('check_includes' in affected_line
599 and not no_presubmit_re.search(affected_line)):
Patrik Höglund6f491062018-01-11 12:04:23 +0100600 result.append(
601 output_api.PresubmitError(error_msg % (affected_file.LocalPath(),
602 line_number)))
603 return result
604
Artem Titove92675b2018-05-22 10:21:27 +0200605
Mirko Bonadeif0e0d752018-07-04 08:48:18 +0200606def CheckGnChanges(input_api, output_api):
Artem Titova04d1402018-05-11 11:23:00 +0200607 file_filter = lambda x: (input_api.FilterSourceFile(
Mirko Bonadeifc17a782020-06-30 14:31:37 +0200608 x, allow_list=(r'.+\.(gn|gni)$',),
609 block_list=(r'.*/presubmit_checks_lib/testdata/.*',)))
ehmaldonado5b1ba082016-09-02 05:51:08 -0700610
611 gn_files = []
Artem Titova04d1402018-05-11 11:23:00 +0200612 for f in input_api.AffectedSourceFiles(file_filter):
Mirko Bonadei92ea95e2017-09-15 06:47:31 +0200613 gn_files.append(f)
ehmaldonado5b1ba082016-09-02 05:51:08 -0700614
615 result = []
616 if gn_files:
charujain9893e252017-09-14 13:33:22 +0200617 result.extend(CheckNoSourcesAbove(input_api, gn_files, output_api))
Mirko Bonadei4dc4e252017-09-19 13:49:16 +0200618 result.extend(CheckNoMixingSources(input_api, gn_files, output_api))
Mirko Bonadei2dcf3482020-06-05 14:30:41 +0200619 result.extend(CheckAbseilDependencies(input_api, gn_files, output_api))
Mirko Bonadei4dc4e252017-09-19 13:49:16 +0200620 result.extend(CheckNoPackageBoundaryViolations(input_api, gn_files,
621 output_api))
Mirko Bonadeia05d47e2018-05-09 11:03:38 +0200622 result.extend(CheckPublicDepsIsNotUsed(gn_files, input_api, output_api))
Mirko Bonadei05691dd2019-10-22 07:34:24 -0700623 result.extend(CheckCheckIncludesIsNotUsed(gn_files, input_api, output_api))
Mirko Bonadeif0e0d752018-07-04 08:48:18 +0200624 result.extend(CheckNoWarningSuppressionFlagsAreAdded(gn_files, input_api,
625 output_api))
ehmaldonado5b1ba082016-09-02 05:51:08 -0700626 return result
627
Artem Titove92675b2018-05-22 10:21:27 +0200628
Oleh Prypin920b6532017-10-05 11:28:51 +0200629def CheckGnGen(input_api, output_api):
630 """Runs `gn gen --check` with default args to detect mismatches between
631 #includes and dependencies in the BUILD.gn files, as well as general build
632 errors.
633 """
634 with _AddToPath(input_api.os_path.join(
635 input_api.PresubmitLocalPath(), 'tools_webrtc', 'presubmit_checks_lib')):
Yves Gerey546ee612019-02-26 17:04:16 +0100636 from build_helpers import RunGnCheck
Mirko Bonadeid8665442018-09-04 12:17:27 +0200637 errors = RunGnCheck(FindSrcDirPath(input_api.PresubmitLocalPath()))[:5]
Oleh Prypin920b6532017-10-05 11:28:51 +0200638 if errors:
639 return [output_api.PresubmitPromptWarning(
640 'Some #includes do not match the build dependency graph. Please run:\n'
641 ' gn gen --check <out_dir>',
642 long_text='\n\n'.join(errors))]
643 return []
644
Artem Titove92675b2018-05-22 10:21:27 +0200645
Artem Titova04d1402018-05-11 11:23:00 +0200646def CheckUnwantedDependencies(input_api, output_api, source_file_filter):
kjellander@webrtc.org3bd41562014-09-01 11:06:37 +0000647 """Runs checkdeps on #include statements added in this
648 change. Breaking - rules is an error, breaking ! rules is a
649 warning.
650 """
651 # Copied from Chromium's src/PRESUBMIT.py.
652
653 # We need to wait until we have an input_api object and use this
654 # roundabout construct to import checkdeps because this file is
655 # eval-ed and thus doesn't have __file__.
Mirko Bonadeid8665442018-09-04 12:17:27 +0200656 src_path = FindSrcDirPath(input_api.PresubmitLocalPath())
657 checkdeps_path = input_api.os_path.join(src_path, 'buildtools', 'checkdeps')
Oleh Prypin2f33a562017-10-04 20:17:54 +0200658 if not os.path.exists(checkdeps_path):
659 return [output_api.PresubmitError(
660 'Cannot find checkdeps at %s\nHave you run "gclient sync" to '
661 'download all the DEPS entries?' % checkdeps_path)]
662 with _AddToPath(checkdeps_path):
kjellander@webrtc.org3bd41562014-09-01 11:06:37 +0000663 import checkdeps
664 from cpp_checker import CppChecker
665 from rules import Rule
kjellander@webrtc.org3bd41562014-09-01 11:06:37 +0000666
667 added_includes = []
Artem Titova04d1402018-05-11 11:23:00 +0200668 for f in input_api.AffectedFiles(file_filter=source_file_filter):
kjellander@webrtc.org3bd41562014-09-01 11:06:37 +0000669 if not CppChecker.IsCppFile(f.LocalPath()):
670 continue
671
Henrik Kjellander57e5fd22015-05-25 12:55:39 +0200672 changed_lines = [line for _, line in f.ChangedContents()]
kjellander@webrtc.org3bd41562014-09-01 11:06:37 +0000673 added_includes.append([f.LocalPath(), changed_lines])
674
675 deps_checker = checkdeps.DepsChecker(input_api.PresubmitLocalPath())
676
677 error_descriptions = []
678 warning_descriptions = []
679 for path, rule_type, rule_description in deps_checker.CheckAddedCppIncludes(
680 added_includes):
681 description_with_path = '%s\n %s' % (path, rule_description)
682 if rule_type == Rule.DISALLOW:
683 error_descriptions.append(description_with_path)
684 else:
685 warning_descriptions.append(description_with_path)
686
687 results = []
688 if error_descriptions:
689 results.append(output_api.PresubmitError(
kjellandera7066a32017-03-23 03:47:05 -0700690 'You added one or more #includes that violate checkdeps rules.\n'
691 'Check that the DEPS files in these locations contain valid rules.\n'
692 'See https://cs.chromium.org/chromium/src/buildtools/checkdeps/ for '
693 'more details about checkdeps.',
kjellander@webrtc.org3bd41562014-09-01 11:06:37 +0000694 error_descriptions))
695 if warning_descriptions:
696 results.append(output_api.PresubmitPromptOrNotify(
697 'You added one or more #includes of files that are temporarily\n'
698 'allowed but being removed. Can you avoid introducing the\n'
kjellandera7066a32017-03-23 03:47:05 -0700699 '#include? See relevant DEPS file(s) for details and contacts.\n'
700 'See https://cs.chromium.org/chromium/src/buildtools/checkdeps/ for '
701 'more details about checkdeps.',
kjellander@webrtc.org3bd41562014-09-01 11:06:37 +0000702 warning_descriptions))
703 return results
704
Artem Titove92675b2018-05-22 10:21:27 +0200705
charujain9893e252017-09-14 13:33:22 +0200706def CheckCommitMessageBugEntry(input_api, output_api):
707 """Check that bug entries are well-formed in commit message."""
708 bogus_bug_msg = (
Mirko Bonadei61880182017-10-12 15:12:35 +0200709 'Bogus Bug entry: %s. Please specify the issue tracker prefix and the '
charujain9893e252017-09-14 13:33:22 +0200710 'issue number, separated by a colon, e.g. webrtc:123 or chromium:12345.')
711 results = []
Mirko Bonadei61880182017-10-12 15:12:35 +0200712 for bug in input_api.change.BugsFromDescription():
charujain9893e252017-09-14 13:33:22 +0200713 bug = bug.strip()
714 if bug.lower() == 'none':
715 continue
charujain81a58c72017-09-25 13:25:45 +0200716 if 'b/' not in bug and ':' not in bug:
charujain9893e252017-09-14 13:33:22 +0200717 try:
718 if int(bug) > 100000:
719 # Rough indicator for current chromium bugs.
720 prefix_guess = 'chromium'
721 else:
722 prefix_guess = 'webrtc'
Mirko Bonadei61880182017-10-12 15:12:35 +0200723 results.append('Bug entry requires issue tracker prefix, e.g. %s:%s' %
charujain9893e252017-09-14 13:33:22 +0200724 (prefix_guess, bug))
725 except ValueError:
726 results.append(bogus_bug_msg % bug)
charujain81a58c72017-09-25 13:25:45 +0200727 elif not (re.match(r'\w+:\d+', bug) or re.match(r'b/\d+', bug)):
charujain9893e252017-09-14 13:33:22 +0200728 results.append(bogus_bug_msg % bug)
729 return [output_api.PresubmitError(r) for r in results]
730
Artem Titove92675b2018-05-22 10:21:27 +0200731
charujain9893e252017-09-14 13:33:22 +0200732def CheckChangeHasBugField(input_api, output_api):
Mirko Bonadei61880182017-10-12 15:12:35 +0200733 """Requires that the changelist is associated with a bug.
kjellanderd1e26a92016-09-19 08:11:16 -0700734
735 This check is stricter than the one in depot_tools/presubmit_canned_checks.py
Mirko Bonadei61880182017-10-12 15:12:35 +0200736 since it fails the presubmit if the bug field is missing or doesn't contain
kjellanderd1e26a92016-09-19 08:11:16 -0700737 a bug reference.
Mirko Bonadei61880182017-10-12 15:12:35 +0200738
739 This supports both 'BUG=' and 'Bug:' since we are in the process of migrating
740 to Gerrit and it encourages the usage of 'Bug:'.
kjellanderd1e26a92016-09-19 08:11:16 -0700741 """
Mirko Bonadei61880182017-10-12 15:12:35 +0200742 if input_api.change.BugsFromDescription():
kjellanderd1e26a92016-09-19 08:11:16 -0700743 return []
744 else:
745 return [output_api.PresubmitError(
Mirko Bonadei61880182017-10-12 15:12:35 +0200746 'The "Bug: [bug number]" footer is mandatory. Please create a bug and '
kjellanderd1e26a92016-09-19 08:11:16 -0700747 'reference it using either of:\n'
Mirko Bonadei61880182017-10-12 15:12:35 +0200748 ' * https://bugs.webrtc.org - reference it using Bug: webrtc:XXXX\n'
749 ' * https://crbug.com - reference it using Bug: chromium:XXXXXX')]
kjellander@webrtc.orge4158642014-08-06 09:11:18 +0000750
Artem Titove92675b2018-05-22 10:21:27 +0200751
Artem Titova04d1402018-05-11 11:23:00 +0200752def CheckJSONParseErrors(input_api, output_api, source_file_filter):
kjellander569cf942016-02-11 05:02:59 -0800753 """Check that JSON files do not contain syntax errors."""
754
755 def FilterFile(affected_file):
Artem Titova04d1402018-05-11 11:23:00 +0200756 return (input_api.os_path.splitext(affected_file.LocalPath())[1] == '.json'
757 and source_file_filter(affected_file))
kjellander569cf942016-02-11 05:02:59 -0800758
759 def GetJSONParseError(input_api, filename):
760 try:
761 contents = input_api.ReadFile(filename)
762 input_api.json.loads(contents)
763 except ValueError as e:
764 return e
765 return None
766
767 results = []
768 for affected_file in input_api.AffectedFiles(
769 file_filter=FilterFile, include_deletes=False):
770 parse_error = GetJSONParseError(input_api,
771 affected_file.AbsoluteLocalPath())
772 if parse_error:
773 results.append(output_api.PresubmitError('%s could not be parsed: %s' %
Artem Titove92675b2018-05-22 10:21:27 +0200774 (affected_file.LocalPath(),
775 parse_error)))
kjellander569cf942016-02-11 05:02:59 -0800776 return results
777
778
charujain9893e252017-09-14 13:33:22 +0200779def RunPythonTests(input_api, output_api):
kjellanderc88b5d52017-04-05 06:42:43 -0700780 def Join(*args):
Henrik Kjellander8d3ad822015-05-26 19:52:05 +0200781 return input_api.os_path.join(input_api.PresubmitLocalPath(), *args)
782
783 test_directories = [
Edward Lemur6d01f6d2017-09-14 17:02:01 +0200784 input_api.PresubmitLocalPath(),
Mirko Bonadei92ea95e2017-09-15 06:47:31 +0200785 Join('rtc_tools', 'py_event_log_analyzer'),
Mirko Bonadei92ea95e2017-09-15 06:47:31 +0200786 Join('audio', 'test', 'unittests'),
ehmaldonado4fb97462017-01-30 05:27:22 -0800787 ] + [
Henrik Kjellander90fd7d82017-05-09 08:30:10 +0200788 root for root, _, files in os.walk(Join('tools_webrtc'))
ehmaldonado4fb97462017-01-30 05:27:22 -0800789 if any(f.endswith('_test.py') for f in files)
Henrik Kjellander8d3ad822015-05-26 19:52:05 +0200790 ]
791
792 tests = []
793 for directory in test_directories:
794 tests.extend(
795 input_api.canned_checks.GetUnitTestsInDirectory(
796 input_api,
797 output_api,
798 directory,
Mirko Bonadeifc17a782020-06-30 14:31:37 +0200799 allowlist=[r'.+_test\.py$']))
Henrik Kjellander8d3ad822015-05-26 19:52:05 +0200800 return input_api.RunTests(tests, parallel=True)
801
802
Artem Titova04d1402018-05-11 11:23:00 +0200803def CheckUsageOfGoogleProtobufNamespace(input_api, output_api,
804 source_file_filter):
mbonadei38415b22017-04-07 05:38:01 -0700805 """Checks that the namespace google::protobuf has not been used."""
806 files = []
807 pattern = input_api.re.compile(r'google::protobuf')
Mirko Bonadei92ea95e2017-09-15 06:47:31 +0200808 proto_utils_path = os.path.join('rtc_base', 'protobuf_utils.h')
Artem Titova04d1402018-05-11 11:23:00 +0200809 file_filter = lambda x: (input_api.FilterSourceFile(x)
810 and source_file_filter(x))
811 for f in input_api.AffectedSourceFiles(file_filter):
mbonadei38415b22017-04-07 05:38:01 -0700812 if f.LocalPath() in [proto_utils_path, 'PRESUBMIT.py']:
813 continue
814 contents = input_api.ReadFile(f)
815 if pattern.search(contents):
816 files.append(f)
817
818 if files:
819 return [output_api.PresubmitError(
820 'Please avoid to use namespace `google::protobuf` directly.\n'
821 'Add a using directive in `%s` and include that header instead.'
822 % proto_utils_path, files)]
823 return []
824
825
Mirko Bonadei92ea95e2017-09-15 06:47:31 +0200826def _LicenseHeader(input_api):
827 """Returns the license header regexp."""
828 # Accept any year number from 2003 to the current year
829 current_year = int(input_api.time.strftime('%Y'))
830 allowed_years = (str(s) for s in reversed(xrange(2003, current_year + 1)))
831 years_re = '(' + '|'.join(allowed_years) + ')'
832 license_header = (
833 r'.*? Copyright( \(c\))? %(year)s The WebRTC [Pp]roject [Aa]uthors\. '
834 r'All [Rr]ights [Rr]eserved\.\n'
835 r'.*?\n'
836 r'.*? Use of this source code is governed by a BSD-style license\n'
837 r'.*? that can be found in the LICENSE file in the root of the source\n'
838 r'.*? tree\. An additional intellectual property rights grant can be '
839 r'found\n'
840 r'.*? in the file PATENTS\. All contributing project authors may\n'
841 r'.*? be found in the AUTHORS file in the root of the source tree\.\n'
842 ) % {
843 'year': years_re,
844 }
845 return license_header
846
847
charujain9893e252017-09-14 13:33:22 +0200848def CommonChecks(input_api, output_api):
andrew@webrtc.org53df1362012-01-26 21:24:23 +0000849 """Checks common to both upload and commit."""
niklase@google.comda159d62011-05-30 11:51:34 +0000850 results = []
tkchin42f580e2015-11-26 23:18:23 -0800851 # Filter out files that are in objc or ios dirs from being cpplint-ed since
852 # they do not follow C++ lint rules.
Mirko Bonadeifc17a782020-06-30 14:31:37 +0200853 exception_list = input_api.DEFAULT_BLACK_LIST + (
tkchin42f580e2015-11-26 23:18:23 -0800854 r".*\bobjc[\\\/].*",
Kári Tristan Helgason3fa35172016-09-09 08:55:05 +0000855 r".*objc\.[hcm]+$",
tkchin42f580e2015-11-26 23:18:23 -0800856 )
Mirko Bonadeifc17a782020-06-30 14:31:37 +0200857 source_file_filter = lambda x: input_api.FilterSourceFile(x, None,
858 exception_list)
charujain9893e252017-09-14 13:33:22 +0200859 results.extend(CheckApprovedFilesLintClean(
tkchin42f580e2015-11-26 23:18:23 -0800860 input_api, output_api, source_file_filter))
Mirko Bonadei92ea95e2017-09-15 06:47:31 +0200861 results.extend(input_api.canned_checks.CheckLicense(
862 input_api, output_api, _LicenseHeader(input_api)))
phoglund@webrtc.org5d3713932013-03-07 09:59:43 +0000863 results.extend(input_api.canned_checks.RunPylint(input_api, output_api,
Mirko Bonadeifc17a782020-06-30 14:31:37 +0200864 block_list=(r'^base[\\\/].*\.py$',
Henrik Kjellander14771ac2015-06-02 13:10:04 +0200865 r'^build[\\\/].*\.py$',
866 r'^buildtools[\\\/].*\.py$',
kjellander38c65c82017-04-12 22:43:38 -0700867 r'^infra[\\\/].*\.py$',
Henrik Kjellander0779e8f2016-12-22 12:01:17 +0100868 r'^ios[\\\/].*\.py$',
Henrik Kjellander14771ac2015-06-02 13:10:04 +0200869 r'^out.*[\\\/].*\.py$',
870 r'^testing[\\\/].*\.py$',
871 r'^third_party[\\\/].*\.py$',
kjellander@webrtc.org177567c2016-12-22 10:40:28 +0100872 r'^tools[\\\/].*\.py$',
kjellanderafd54942016-12-17 12:21:39 -0800873 # TODO(phoglund): should arguably be checked.
Henrik Kjellander90fd7d82017-05-09 08:30:10 +0200874 r'^tools_webrtc[\\\/]mb[\\\/].*\.py$',
Henrik Kjellander14771ac2015-06-02 13:10:04 +0200875 r'^xcodebuild.*[\\\/].*\.py$',),
Henrik Kjellander57e5fd22015-05-25 12:55:39 +0200876 pylintrc='pylintrc'))
kjellander569cf942016-02-11 05:02:59 -0800877
nisse3d21e232016-09-02 03:07:06 -0700878 # TODO(nisse): talk/ is no more, so make below checks simpler?
Henrik Kjellander57e5fd22015-05-25 12:55:39 +0200879 # WebRTC can't use the presubmit_canned_checks.PanProjectChecks function since
880 # we need to have different license checks in talk/ and webrtc/ directories.
881 # Instead, hand-picked checks are included below.
Henrik Kjellander63224672015-09-08 08:03:56 +0200882
tkchin3cd9a302016-06-08 12:40:28 -0700883 # .m and .mm files are ObjC files. For simplicity we will consider .h files in
884 # ObjC subdirectories ObjC headers.
885 objc_filter_list = (r'.+\.m$', r'.+\.mm$', r'.+objc\/.+\.h$')
Henrik Kjellanderb4af3d62016-11-16 20:11:29 +0100886 # Skip long-lines check for DEPS and GN files.
887 build_file_filter_list = (r'.+\.gn$', r'.+\.gni$', 'DEPS')
Artem Titova04d1402018-05-11 11:23:00 +0200888 # Also we will skip most checks for third_party directory.
Artem Titov42f0d782018-06-27 13:23:17 +0200889 third_party_filter_list = (r'^third_party[\\\/].+',)
tkchin3cd9a302016-06-08 12:40:28 -0700890 eighty_char_sources = lambda x: input_api.FilterSourceFile(x,
Mirko Bonadeifc17a782020-06-30 14:31:37 +0200891 block_list=build_file_filter_list + objc_filter_list +
Artem Titova04d1402018-05-11 11:23:00 +0200892 third_party_filter_list)
tkchin3cd9a302016-06-08 12:40:28 -0700893 hundred_char_sources = lambda x: input_api.FilterSourceFile(x,
Mirko Bonadeifc17a782020-06-30 14:31:37 +0200894 allow_list=objc_filter_list)
Artem Titove92675b2018-05-22 10:21:27 +0200895 non_third_party_sources = lambda x: input_api.FilterSourceFile(x,
Mirko Bonadeifc17a782020-06-30 14:31:37 +0200896 block_list=third_party_filter_list)
Artem Titove92675b2018-05-22 10:21:27 +0200897
andrew@webrtc.org2442de12012-01-23 17:45:41 +0000898 results.extend(input_api.canned_checks.CheckLongLines(
tkchin3cd9a302016-06-08 12:40:28 -0700899 input_api, output_api, maxlen=80, source_file_filter=eighty_char_sources))
900 results.extend(input_api.canned_checks.CheckLongLines(
901 input_api, output_api, maxlen=100,
902 source_file_filter=hundred_char_sources))
andrew@webrtc.org2442de12012-01-23 17:45:41 +0000903 results.extend(input_api.canned_checks.CheckChangeHasNoTabs(
Artem Titova04d1402018-05-11 11:23:00 +0200904 input_api, output_api, source_file_filter=non_third_party_sources))
andrew@webrtc.org53df1362012-01-26 21:24:23 +0000905 results.extend(input_api.canned_checks.CheckChangeHasNoStrayWhitespace(
Artem Titova04d1402018-05-11 11:23:00 +0200906 input_api, output_api, source_file_filter=non_third_party_sources))
kjellandere5dc62a2016-12-14 00:16:21 -0800907 results.extend(input_api.canned_checks.CheckAuthorizedAuthor(
Oleh Prypine0735142018-10-04 11:15:54 +0200908 input_api, output_api, bot_whitelist=[
909 'chromium-webrtc-autoroll@webrtc-ci.iam.gserviceaccount.com'
910 ]))
andrew@webrtc.org53df1362012-01-26 21:24:23 +0000911 results.extend(input_api.canned_checks.CheckChangeTodoHasOwner(
Artem Titova04d1402018-05-11 11:23:00 +0200912 input_api, output_api, source_file_filter=non_third_party_sources))
Yves Gerey87a93532018-06-20 15:51:49 +0200913 results.extend(input_api.canned_checks.CheckPatchFormatted(
914 input_api, output_api))
charujain9893e252017-09-14 13:33:22 +0200915 results.extend(CheckNativeApiHeaderChanges(input_api, output_api))
Artem Titova04d1402018-05-11 11:23:00 +0200916 results.extend(CheckNoIOStreamInHeaders(
917 input_api, output_api, source_file_filter=non_third_party_sources))
918 results.extend(CheckNoPragmaOnce(
919 input_api, output_api, source_file_filter=non_third_party_sources))
920 results.extend(CheckNoFRIEND_TEST(
921 input_api, output_api, source_file_filter=non_third_party_sources))
Mirko Bonadeif0e0d752018-07-04 08:48:18 +0200922 results.extend(CheckGnChanges(input_api, output_api))
Artem Titova04d1402018-05-11 11:23:00 +0200923 results.extend(CheckUnwantedDependencies(
924 input_api, output_api, source_file_filter=non_third_party_sources))
925 results.extend(CheckJSONParseErrors(
926 input_api, output_api, source_file_filter=non_third_party_sources))
charujain9893e252017-09-14 13:33:22 +0200927 results.extend(RunPythonTests(input_api, output_api))
Artem Titova04d1402018-05-11 11:23:00 +0200928 results.extend(CheckUsageOfGoogleProtobufNamespace(
929 input_api, output_api, source_file_filter=non_third_party_sources))
930 results.extend(CheckOrphanHeaders(
931 input_api, output_api, source_file_filter=non_third_party_sources))
932 results.extend(CheckNewlineAtTheEndOfProtoFiles(
933 input_api, output_api, source_file_filter=non_third_party_sources))
934 results.extend(CheckNoStreamUsageIsAdded(
Artem Titov739351d2018-05-11 12:21:36 +0200935 input_api, output_api, non_third_party_sources))
Mirko Bonadei9ce800d2019-02-05 16:48:13 +0100936 results.extend(CheckNoTestCaseUsageIsAdded(
937 input_api, output_api, non_third_party_sources))
Mirko Bonadei7e4ee6e2018-09-28 11:45:23 +0200938 results.extend(CheckAddedDepsHaveTargetApprovals(input_api, output_api))
Mirko Bonadeia418e672018-10-24 13:57:25 +0200939 results.extend(CheckApiDepsFileIsUpToDate(input_api, output_api))
tzika06bf852018-11-15 20:37:35 +0900940 results.extend(CheckAbslMemoryInclude(
941 input_api, output_api, non_third_party_sources))
Mirko Bonadei9fa8ef12019-09-17 19:14:13 +0200942 results.extend(CheckBannedAbslMakeUnique(
943 input_api, output_api, non_third_party_sources))
Mirko Bonadeia418e672018-10-24 13:57:25 +0200944 return results
945
946
947def CheckApiDepsFileIsUpToDate(input_api, output_api):
Mirko Bonadei90490372018-10-26 13:17:47 +0200948 """Check that 'include_rules' in api/DEPS is up to date.
949
950 The file api/DEPS must be kept up to date in order to avoid to avoid to
951 include internal header from WebRTC's api/ headers.
952
953 This check is focused on ensuring that 'include_rules' contains a deny
954 rule for each root level directory. More focused allow rules can be
955 added to 'specific_include_rules'.
956 """
Mirko Bonadeia418e672018-10-24 13:57:25 +0200957 results = []
958 api_deps = os.path.join(input_api.PresubmitLocalPath(), 'api', 'DEPS')
959 with open(api_deps) as f:
960 deps_content = _ParseDeps(f.read())
961
962 include_rules = deps_content.get('include_rules', [])
Mirko Bonadei01e97ae2019-09-05 14:36:42 +0200963 dirs_to_skip = set(['api', 'docs'])
Mirko Bonadeia418e672018-10-24 13:57:25 +0200964
Mirko Bonadei90490372018-10-26 13:17:47 +0200965 # Only check top level directories affected by the current CL.
966 dirs_to_check = set()
967 for f in input_api.AffectedFiles():
968 path_tokens = [t for t in f.LocalPath().split(os.sep) if t]
969 if len(path_tokens) > 1:
Mirko Bonadei01e97ae2019-09-05 14:36:42 +0200970 if (path_tokens[0] not in dirs_to_skip and
Mirko Bonadei90490372018-10-26 13:17:47 +0200971 os.path.isdir(os.path.join(input_api.PresubmitLocalPath(),
972 path_tokens[0]))):
973 dirs_to_check.add(path_tokens[0])
Mirko Bonadeia418e672018-10-24 13:57:25 +0200974
Mirko Bonadei90490372018-10-26 13:17:47 +0200975 missing_include_rules = set()
976 for p in dirs_to_check:
Mirko Bonadeia418e672018-10-24 13:57:25 +0200977 rule = '-%s' % p
978 if rule not in include_rules:
Mirko Bonadei90490372018-10-26 13:17:47 +0200979 missing_include_rules.add(rule)
980
Mirko Bonadeia418e672018-10-24 13:57:25 +0200981 if missing_include_rules:
Mirko Bonadei90490372018-10-26 13:17:47 +0200982 error_msg = [
983 'include_rules = [\n',
984 ' ...\n',
985 ]
Mirko Bonadeia418e672018-10-24 13:57:25 +0200986
Mirko Bonadei90490372018-10-26 13:17:47 +0200987 for r in sorted(missing_include_rules):
988 error_msg.append(' "%s",\n' % str(r))
Mirko Bonadeia418e672018-10-24 13:57:25 +0200989
Mirko Bonadei90490372018-10-26 13:17:47 +0200990 error_msg.append(' ...\n')
991 error_msg.append(']\n')
992
Mirko Bonadeia418e672018-10-24 13:57:25 +0200993 results.append(output_api.PresubmitError(
Mirko Bonadei90490372018-10-26 13:17:47 +0200994 'New root level directory detected! WebRTC api/ headers should '
995 'not #include headers from \n'
996 'the new directory, so please update "include_rules" in file\n'
997 '"%s". Example:\n%s\n' % (api_deps, ''.join(error_msg))))
998
andrew@webrtc.org53df1362012-01-26 21:24:23 +0000999 return results
andrew@webrtc.org2442de12012-01-23 17:45:41 +00001000
Mirko Bonadei9fa8ef12019-09-17 19:14:13 +02001001def CheckBannedAbslMakeUnique(input_api, output_api, source_file_filter):
1002 file_filter = lambda f: (f.LocalPath().endswith(('.cc', '.h'))
1003 and source_file_filter(f))
1004
1005 files = []
1006 for f in input_api.AffectedFiles(
1007 include_deletes=False, file_filter=file_filter):
1008 for _, line in f.ChangedContents():
1009 if 'absl::make_unique' in line:
1010 files.append(f)
1011 break
1012
1013 if len(files):
1014 return [output_api.PresubmitError(
1015 'Please use std::make_unique instead of absl::make_unique.\n'
1016 'Affected files:',
1017 files)]
1018 return []
1019
tzika06bf852018-11-15 20:37:35 +09001020def CheckAbslMemoryInclude(input_api, output_api, source_file_filter):
1021 pattern = input_api.re.compile(
1022 r'^#include\s*"absl/memory/memory.h"', input_api.re.MULTILINE)
1023 file_filter = lambda f: (f.LocalPath().endswith(('.cc', '.h'))
1024 and source_file_filter(f))
1025
1026 files = []
1027 for f in input_api.AffectedFiles(
1028 include_deletes=False, file_filter=file_filter):
1029 contents = input_api.ReadFile(f)
1030 if pattern.search(contents):
1031 continue
1032 for _, line in f.ChangedContents():
Mirko Bonadei9fa8ef12019-09-17 19:14:13 +02001033 if 'absl::WrapUnique' in line:
tzika06bf852018-11-15 20:37:35 +09001034 files.append(f)
1035 break
1036
1037 if len(files):
1038 return [output_api.PresubmitError(
Mirko Bonadei9fa8ef12019-09-17 19:14:13 +02001039 'Please include "absl/memory/memory.h" header for absl::WrapUnique.\n'
1040 'This header may or may not be included transitively depending on the '
1041 'C++ standard version.',
tzika06bf852018-11-15 20:37:35 +09001042 files)]
1043 return []
kjellander@webrtc.orge4158642014-08-06 09:11:18 +00001044
andrew@webrtc.org53df1362012-01-26 21:24:23 +00001045def CheckChangeOnUpload(input_api, output_api):
1046 results = []
charujain9893e252017-09-14 13:33:22 +02001047 results.extend(CommonChecks(input_api, output_api))
Oleh Prypin920b6532017-10-05 11:28:51 +02001048 results.extend(CheckGnGen(input_api, output_api))
Henrik Kjellander57e5fd22015-05-25 12:55:39 +02001049 results.extend(
1050 input_api.canned_checks.CheckGNFormatted(input_api, output_api))
niklase@google.comda159d62011-05-30 11:51:34 +00001051 return results
1052
kjellander@webrtc.orge4158642014-08-06 09:11:18 +00001053
andrew@webrtc.org2442de12012-01-23 17:45:41 +00001054def CheckChangeOnCommit(input_api, output_api):
niklase@google.com1198db92011-06-09 07:07:24 +00001055 results = []
charujain9893e252017-09-14 13:33:22 +02001056 results.extend(CommonChecks(input_api, output_api))
1057 results.extend(VerifyNativeApiHeadersListIsValid(input_api, output_api))
Artem Titov42f0d782018-06-27 13:23:17 +02001058 results.extend(input_api.canned_checks.CheckOwners(input_api, output_api))
andrew@webrtc.org53df1362012-01-26 21:24:23 +00001059 results.extend(input_api.canned_checks.CheckChangeWasUploaded(
1060 input_api, output_api))
1061 results.extend(input_api.canned_checks.CheckChangeHasDescription(
1062 input_api, output_api))
charujain9893e252017-09-14 13:33:22 +02001063 results.extend(CheckChangeHasBugField(input_api, output_api))
1064 results.extend(CheckCommitMessageBugEntry(input_api, output_api))
kjellander@webrtc.org12cb88c2014-02-13 11:53:43 +00001065 results.extend(input_api.canned_checks.CheckTreeIsOpen(
1066 input_api, output_api,
1067 json_url='http://webrtc-status.appspot.com/current?format=json'))
niklase@google.com1198db92011-06-09 07:07:24 +00001068 return results
mbonadei74973ed2017-05-09 07:58:05 -07001069
1070
Artem Titova04d1402018-05-11 11:23:00 +02001071def CheckOrphanHeaders(input_api, output_api, source_file_filter):
mbonadei74973ed2017-05-09 07:58:05 -07001072 # We need to wait until we have an input_api object and use this
1073 # roundabout construct to import prebubmit_checks_lib because this file is
1074 # eval-ed and thus doesn't have __file__.
Patrik Höglund2f3f7222017-12-19 11:08:56 +01001075 error_msg = """{} should be listed in {}."""
mbonadei74973ed2017-05-09 07:58:05 -07001076 results = []
Mirko Bonadeifc17a782020-06-30 14:31:37 +02001077 exempt_paths = [
Patrik Höglund7e60de22018-01-09 14:22:00 +01001078 os.path.join('tools_webrtc', 'ios', 'SDK'),
1079 ]
Oleh Prypin2f33a562017-10-04 20:17:54 +02001080 with _AddToPath(input_api.os_path.join(
1081 input_api.PresubmitLocalPath(), 'tools_webrtc', 'presubmit_checks_lib')):
mbonadei74973ed2017-05-09 07:58:05 -07001082 from check_orphan_headers import GetBuildGnPathFromFilePath
1083 from check_orphan_headers import IsHeaderInBuildGn
mbonadei74973ed2017-05-09 07:58:05 -07001084
Artem Titova04d1402018-05-11 11:23:00 +02001085 file_filter = lambda x: input_api.FilterSourceFile(
Mirko Bonadeifc17a782020-06-30 14:31:37 +02001086 x, block_list=exempt_paths) and source_file_filter(x)
Artem Titova04d1402018-05-11 11:23:00 +02001087 for f in input_api.AffectedSourceFiles(file_filter):
Patrik Höglund7e60de22018-01-09 14:22:00 +01001088 if f.LocalPath().endswith('.h'):
mbonadei74973ed2017-05-09 07:58:05 -07001089 file_path = os.path.abspath(f.LocalPath())
1090 root_dir = os.getcwd()
1091 gn_file_path = GetBuildGnPathFromFilePath(file_path, os.path.exists,
1092 root_dir)
1093 in_build_gn = IsHeaderInBuildGn(file_path, gn_file_path)
1094 if not in_build_gn:
1095 results.append(output_api.PresubmitError(error_msg.format(
Patrik Höglund2f3f7222017-12-19 11:08:56 +01001096 f.LocalPath(), os.path.relpath(gn_file_path))))
mbonadei74973ed2017-05-09 07:58:05 -07001097 return results
Mirko Bonadei960fd5b2017-06-29 14:59:36 +02001098
1099
Artem Titove92675b2018-05-22 10:21:27 +02001100def CheckNewlineAtTheEndOfProtoFiles(input_api, output_api, source_file_filter):
Mirko Bonadei960fd5b2017-06-29 14:59:36 +02001101 """Checks that all .proto files are terminated with a newline."""
1102 error_msg = 'File {} must end with exactly one newline.'
1103 results = []
Artem Titova04d1402018-05-11 11:23:00 +02001104 file_filter = lambda x: input_api.FilterSourceFile(
Mirko Bonadeifc17a782020-06-30 14:31:37 +02001105 x, allow_list=(r'.+\.proto$',)) and source_file_filter(x)
Artem Titova04d1402018-05-11 11:23:00 +02001106 for f in input_api.AffectedSourceFiles(file_filter):
Mirko Bonadei960fd5b2017-06-29 14:59:36 +02001107 file_path = f.LocalPath()
1108 with open(file_path) as f:
1109 lines = f.readlines()
Mirko Bonadeia730c1c2017-09-18 11:33:13 +02001110 if len(lines) > 0 and not lines[-1].endswith('\n'):
Mirko Bonadei960fd5b2017-06-29 14:59:36 +02001111 results.append(output_api.PresubmitError(error_msg.format(file_path)))
1112 return results
Mirko Bonadei7e4ee6e2018-09-28 11:45:23 +02001113
1114
1115def _ExtractAddRulesFromParsedDeps(parsed_deps):
1116 """Extract the rules that add dependencies from a parsed DEPS file.
1117
1118 Args:
1119 parsed_deps: the locals dictionary from evaluating the DEPS file."""
1120 add_rules = set()
1121 add_rules.update([
1122 rule[1:] for rule in parsed_deps.get('include_rules', [])
1123 if rule.startswith('+') or rule.startswith('!')
1124 ])
1125 for _, rules in parsed_deps.get('specific_include_rules',
1126 {}).iteritems():
1127 add_rules.update([
1128 rule[1:] for rule in rules
1129 if rule.startswith('+') or rule.startswith('!')
1130 ])
1131 return add_rules
1132
1133
1134def _ParseDeps(contents):
1135 """Simple helper for parsing DEPS files."""
1136 # Stubs for handling special syntax in the root DEPS file.
1137 class VarImpl(object):
1138
1139 def __init__(self, local_scope):
1140 self._local_scope = local_scope
1141
1142 def Lookup(self, var_name):
1143 """Implements the Var syntax."""
1144 try:
1145 return self._local_scope['vars'][var_name]
1146 except KeyError:
1147 raise Exception('Var is not defined: %s' % var_name)
1148
1149 local_scope = {}
1150 global_scope = {
1151 'Var': VarImpl(local_scope).Lookup,
1152 }
1153 exec contents in global_scope, local_scope
1154 return local_scope
1155
1156
1157def _CalculateAddedDeps(os_path, old_contents, new_contents):
1158 """Helper method for _CheckAddedDepsHaveTargetApprovals. Returns
1159 a set of DEPS entries that we should look up.
1160
1161 For a directory (rather than a specific filename) we fake a path to
1162 a specific filename by adding /DEPS. This is chosen as a file that
1163 will seldom or never be subject to per-file include_rules.
1164 """
1165 # We ignore deps entries on auto-generated directories.
1166 auto_generated_dirs = ['grit', 'jni']
1167
1168 old_deps = _ExtractAddRulesFromParsedDeps(_ParseDeps(old_contents))
1169 new_deps = _ExtractAddRulesFromParsedDeps(_ParseDeps(new_contents))
1170
1171 added_deps = new_deps.difference(old_deps)
1172
1173 results = set()
1174 for added_dep in added_deps:
1175 if added_dep.split('/')[0] in auto_generated_dirs:
1176 continue
1177 # Assume that a rule that ends in .h is a rule for a specific file.
1178 if added_dep.endswith('.h'):
1179 results.add(added_dep)
1180 else:
1181 results.add(os_path.join(added_dep, 'DEPS'))
1182 return results
1183
1184
1185def CheckAddedDepsHaveTargetApprovals(input_api, output_api):
1186 """When a dependency prefixed with + is added to a DEPS file, we
1187 want to make sure that the change is reviewed by an OWNER of the
1188 target file or directory, to avoid layering violations from being
1189 introduced. This check verifies that this happens.
1190 """
1191 virtual_depended_on_files = set()
1192
1193 file_filter = lambda f: not input_api.re.match(
1194 r"^third_party[\\\/](WebKit|blink)[\\\/].*", f.LocalPath())
1195 for f in input_api.AffectedFiles(include_deletes=False,
1196 file_filter=file_filter):
1197 filename = input_api.os_path.basename(f.LocalPath())
1198 if filename == 'DEPS':
1199 virtual_depended_on_files.update(_CalculateAddedDeps(
1200 input_api.os_path,
1201 '\n'.join(f.OldContents()),
1202 '\n'.join(f.NewContents())))
1203
1204 if not virtual_depended_on_files:
1205 return []
1206
1207 if input_api.is_committing:
1208 if input_api.tbr:
1209 return [output_api.PresubmitNotifyResult(
1210 '--tbr was specified, skipping OWNERS check for DEPS additions')]
1211 if input_api.dry_run:
1212 return [output_api.PresubmitNotifyResult(
1213 'This is a dry run, skipping OWNERS check for DEPS additions')]
1214 if not input_api.change.issue:
1215 return [output_api.PresubmitError(
1216 "DEPS approval by OWNERS check failed: this change has "
1217 "no change number, so we can't check it for approvals.")]
1218 output = output_api.PresubmitError
1219 else:
1220 output = output_api.PresubmitNotifyResult
1221
1222 owners_db = input_api.owners_db
1223 owner_email, reviewers = (
1224 input_api.canned_checks.GetCodereviewOwnerAndReviewers(
1225 input_api,
1226 owners_db.email_regexp,
1227 approval_needed=input_api.is_committing))
1228
1229 owner_email = owner_email or input_api.change.author_email
1230
1231 reviewers_plus_owner = set(reviewers)
1232 if owner_email:
1233 reviewers_plus_owner.add(owner_email)
1234 missing_files = owners_db.files_not_covered_by(virtual_depended_on_files,
1235 reviewers_plus_owner)
1236
1237 # We strip the /DEPS part that was added by
1238 # _FilesToCheckForIncomingDeps to fake a path to a file in a
1239 # directory.
1240 def StripDeps(path):
1241 start_deps = path.rfind('/DEPS')
1242 if start_deps != -1:
1243 return path[:start_deps]
1244 else:
1245 return path
1246 unapproved_dependencies = ["'+%s'," % StripDeps(path)
1247 for path in missing_files]
1248
1249 if unapproved_dependencies:
1250 output_list = [
1251 output('You need LGTM from owners of depends-on paths in DEPS that were '
1252 'modified in this CL:\n %s' %
1253 '\n '.join(sorted(unapproved_dependencies)))]
1254 suggested_owners = owners_db.reviewers_for(missing_files, owner_email)
1255 output_list.append(output(
1256 'Suggested missing target path OWNERS:\n %s' %
1257 '\n '.join(suggested_owners or [])))
1258 return output_list
1259
1260 return []