blob: 27facadb612f0662b5cf547831b3a10258fdb9a3 [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.
17CPPLINT_BLACKLIST = [
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),
48# include file and feature blacklists are
49# 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).
jbauchc4e3ead2016-02-19 00:25:55 -080054BLACKLIST_LINT_FILTERS = [
55 '-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(
179 x, white_list=[r'.+\.(gn|gni|h)$'])
180 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
charujain9893e252017-09-14 13:33:22 +0200265def IsLintBlacklisted(blacklist_paths, file_path):
oprypin2aa463f2017-03-23 03:17:02 -0700266 """ Checks if a file is blacklisted for lint check."""
267 for path in blacklist_paths:
268 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):
oprypin2aa463f2017-03-23 03:17:02 -0700275 """Checks that all new or non-blacklisted .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()
288 lint_filters.extend(BLACKLIST_LINT_FILTERS)
289 cpplint._SetFilters(','.join(lint_filters))
290
oprypin2aa463f2017-03-23 03:17:02 -0700291 # Create a platform independent blacklist for cpplint.
292 blacklist_paths = [input_api.os_path.join(*path.split('/'))
293 for path in CPPLINT_BLACKLIST]
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
298 # moved/renamed) files and files not listed in CPPLINT_BLACKLIST.
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.
charujain9893e252017-09-14 13:33:22 +0200303 if f.Action() == 'A' or not IsLintBlacklisted(blacklist_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',
355 'rtc_static_library'
356 ]
357 error_msg = ('Abseil dependencies in target "%s" (file: %s) '
358 'should be moved to the "absl_deps" parameter.')
359 errors = []
360
361 for gn_file in gn_files:
362 gn_file_content = input_api.ReadFile(gn_file)
363 for target_match in TARGET_RE.finditer(gn_file_content):
364 target_type = target_match.group('target_type')
365 target_name = target_match.group('target_name')
366 target_contents = target_match.group('target_contents')
367 if target_type in target_types_to_check:
368 for deps_match in DEPS_RE.finditer(target_contents):
369 deps = deps_match.group('deps').splitlines()
370 for dep in deps:
371 if re.search(absl_re, dep):
372 errors.append(
373 output_api.PresubmitError(error_msg % (target_name,
374 gn_file.LocalPath())))
375 break # no need to warn more than once per target
376 return errors
377
378
Mirko Bonadei4dc4e252017-09-19 13:49:16 +0200379def CheckNoMixingSources(input_api, gn_files, output_api):
380 """Disallow mixing C, C++ and Obj-C/Obj-C++ in the same target.
381
382 See bugs.webrtc.org/7743 for more context.
383 """
Artem Titove92675b2018-05-22 10:21:27 +0200384
Mirko Bonadei4dc4e252017-09-19 13:49:16 +0200385 def _MoreThanOneSourceUsed(*sources_lists):
386 sources_used = 0
387 for source_list in sources_lists:
388 if len(source_list):
389 sources_used += 1
390 return sources_used > 1
391
392 errors = defaultdict(lambda: [])
kjellander7439f972016-12-05 22:47:46 -0800393 for gn_file in gn_files:
Mirko Bonadei4dc4e252017-09-19 13:49:16 +0200394 gn_file_content = input_api.ReadFile(gn_file)
395 for target_match in TARGET_RE.finditer(gn_file_content):
396 # list_of_sources is a list of tuples of the form
397 # (c_files, cc_files, objc_files) that keeps track of all the sources
398 # defined in a target. A GN target can have more that on definition of
399 # sources (since it supports if/else statements).
400 # E.g.:
401 # rtc_static_library("foo") {
402 # if (is_win) {
403 # sources = [ "foo.cc" ]
404 # } else {
405 # sources = [ "foo.mm" ]
406 # }
407 # }
408 # This is allowed and the presubmit check should support this case.
409 list_of_sources = []
kjellander7439f972016-12-05 22:47:46 -0800410 c_files = []
411 cc_files = []
Mirko Bonadei4dc4e252017-09-19 13:49:16 +0200412 objc_files = []
413 target_name = target_match.group('target_name')
414 target_contents = target_match.group('target_contents')
415 for sources_match in SOURCES_RE.finditer(target_contents):
416 if '+=' not in sources_match.group(0):
417 if c_files or cc_files or objc_files:
418 list_of_sources.append((c_files, cc_files, objc_files))
419 c_files = []
420 cc_files = []
421 objc_files = []
422 for file_match in FILE_PATH_RE.finditer(sources_match.group(1)):
423 file_path = file_match.group('file_path')
424 extension = file_match.group('extension')
425 if extension == '.c':
426 c_files.append(file_path + extension)
427 if extension == '.cc':
428 cc_files.append(file_path + extension)
429 if extension in ['.m', '.mm']:
430 objc_files.append(file_path + extension)
431 list_of_sources.append((c_files, cc_files, objc_files))
432 for c_files_list, cc_files_list, objc_files_list in list_of_sources:
433 if _MoreThanOneSourceUsed(c_files_list, cc_files_list, objc_files_list):
434 all_sources = sorted(c_files_list + cc_files_list + objc_files_list)
435 errors[gn_file.LocalPath()].append((target_name, all_sources))
436 if errors:
kjellander7439f972016-12-05 22:47:46 -0800437 return [output_api.PresubmitError(
Mirko Bonadei4dc4e252017-09-19 13:49:16 +0200438 'GN targets cannot mix .c, .cc and .m (or .mm) source files.\n'
439 'Please create a separate target for each collection of sources.\n'
kjellander7439f972016-12-05 22:47:46 -0800440 'Mixed sources: \n'
441 '%s\n'
Mirko Bonadei4dc4e252017-09-19 13:49:16 +0200442 'Violating GN files:\n%s\n' % (json.dumps(errors, indent=2),
443 '\n'.join(errors.keys())))]
kjellander7439f972016-12-05 22:47:46 -0800444 return []
445
Artem Titove92675b2018-05-22 10:21:27 +0200446
charujain9893e252017-09-14 13:33:22 +0200447def CheckNoPackageBoundaryViolations(input_api, gn_files, output_api):
ehmaldonado4fb97462017-01-30 05:27:22 -0800448 cwd = input_api.PresubmitLocalPath()
Oleh Prypin2f33a562017-10-04 20:17:54 +0200449 with _AddToPath(input_api.os_path.join(
450 cwd, 'tools_webrtc', 'presubmit_checks_lib')):
451 from check_package_boundaries import CheckPackageBoundaries
452 build_files = [os.path.join(cwd, gn_file.LocalPath()) for gn_file in gn_files]
453 errors = CheckPackageBoundaries(cwd, build_files)[:5]
454 if errors:
ehmaldonado4fb97462017-01-30 05:27:22 -0800455 return [output_api.PresubmitError(
Oleh Prypin2f33a562017-10-04 20:17:54 +0200456 'There are package boundary violations in the following GN files:',
457 long_text='\n\n'.join(str(err) for err in errors))]
ehmaldonado4fb97462017-01-30 05:27:22 -0800458 return []
459
Mirko Bonadeia51bbd82018-03-08 16:15:45 +0100460
Mirko Bonadeif0e0d752018-07-04 08:48:18 +0200461def _ReportFileAndLine(filename, line_num):
Mirko Bonadeia51bbd82018-03-08 16:15:45 +0100462 """Default error formatter for _FindNewViolationsOfRule."""
463 return '%s (line %s)' % (filename, line_num)
464
465
Mirko Bonadeif0e0d752018-07-04 08:48:18 +0200466def CheckNoWarningSuppressionFlagsAreAdded(gn_files, input_api, output_api,
467 error_formatter=_ReportFileAndLine):
468 """Make sure that warning suppression flags are not added wihtout a reason."""
469 msg = ('Usage of //build/config/clang:extra_warnings is discouraged '
470 'in WebRTC.\n'
471 'If you are not adding this code (e.g. you are just moving '
472 'existing code) or you want to add an exception,\n'
473 'you can add a comment on the line that causes the problem:\n\n'
474 '"-Wno-odr" # no-presubmit-check TODO(bugs.webrtc.org/BUG_ID)\n'
475 '\n'
476 'Affected files:\n')
477 errors = [] # 2-element tuples with (file, line number)
478 clang_warn_re = input_api.re.compile(r'//build/config/clang:extra_warnings')
479 no_presubmit_re = input_api.re.compile(
480 r'# no-presubmit-check TODO\(bugs\.webrtc\.org/\d+\)')
481 for f in gn_files:
482 for line_num, line in f.ChangedContents():
483 if clang_warn_re.search(line) and not no_presubmit_re.search(line):
484 errors.append(error_formatter(f.LocalPath(), line_num))
485 if errors:
486 return [output_api.PresubmitError(msg, errors)]
487 return []
488
Mirko Bonadei9ce800d2019-02-05 16:48:13 +0100489
490def CheckNoTestCaseUsageIsAdded(input_api, output_api, source_file_filter,
491 error_formatter=_ReportFileAndLine):
492 error_msg = ('Usage of legacy GoogleTest API detected!\nPlease use the '
493 'new API: https://github.com/google/googletest/blob/master/'
494 'googletest/docs/primer.md#beware-of-the-nomenclature.\n'
495 'Affected files:\n')
496 errors = [] # 2-element tuples with (file, line number)
497 test_case_re = input_api.re.compile(r'TEST_CASE')
498 file_filter = lambda f: (source_file_filter(f)
499 and f.LocalPath().endswith('.cc'))
500 for f in input_api.AffectedSourceFiles(file_filter):
501 for line_num, line in f.ChangedContents():
502 if test_case_re.search(line):
503 errors.append(error_formatter(f.LocalPath(), line_num))
504 if errors:
505 return [output_api.PresubmitError(error_msg, errors)]
506 return []
507
508
Mirko Bonadeia51bbd82018-03-08 16:15:45 +0100509def CheckNoStreamUsageIsAdded(input_api, output_api,
Artem Titov739351d2018-05-11 12:21:36 +0200510 source_file_filter,
Mirko Bonadeif0e0d752018-07-04 08:48:18 +0200511 error_formatter=_ReportFileAndLine):
Mirko Bonadeia51bbd82018-03-08 16:15:45 +0100512 """Make sure that no more dependencies on stringstream are added."""
513 error_msg = ('Usage of <sstream>, <istream> and <ostream> in WebRTC is '
514 'deprecated.\n'
515 'This includes the following types:\n'
516 'std::istringstream, std::ostringstream, std::wistringstream, '
517 'std::wostringstream,\n'
518 'std::wstringstream, std::ostream, std::wostream, std::istream,'
519 'std::wistream,\n'
520 'std::iostream, std::wiostream.\n'
521 'If you are not adding this code (e.g. you are just moving '
522 'existing code),\n'
523 'you can add a comment on the line that causes the problem:\n\n'
524 '#include <sstream> // no-presubmit-check TODO(webrtc:8982)\n'
525 'std::ostream& F() { // no-presubmit-check TODO(webrtc:8982)\n'
526 '\n'
Karl Wibergebd01e82018-03-14 15:08:39 +0100527 'If you are adding new code, consider using '
528 'rtc::SimpleStringBuilder\n'
529 '(in rtc_base/strings/string_builder.h).\n'
Mirko Bonadeia51bbd82018-03-08 16:15:45 +0100530 'Affected files:\n')
531 errors = [] # 2-element tuples with (file, line number)
532 include_re = input_api.re.compile(r'#include <(i|o|s)stream>')
533 usage_re = input_api.re.compile(r'std::(w|i|o|io|wi|wo|wio)(string)*stream')
534 no_presubmit_re = input_api.re.compile(
Jonas Olsson74395342018-04-03 12:22:07 +0200535 r'// no-presubmit-check TODO\(webrtc:8982\)')
Artem Titova04d1402018-05-11 11:23:00 +0200536 file_filter = lambda x: (input_api.FilterSourceFile(x)
537 and source_file_filter(x))
Mirko Bonadei571791a2019-05-07 14:08:05 +0200538
539 def _IsException(file_path):
540 is_test = any(file_path.endswith(x) for x in ['_test.cc', '_tests.cc',
541 '_unittest.cc',
542 '_unittests.cc'])
Patrik Höglund2ea27962020-01-13 15:10:40 +0100543 return (file_path.startswith('examples') or
544 file_path.startswith('test') or
545 is_test)
546
Mirko Bonadei571791a2019-05-07 14:08:05 +0200547
Artem Titova04d1402018-05-11 11:23:00 +0200548 for f in input_api.AffectedSourceFiles(file_filter):
Mirko Bonadei571791a2019-05-07 14:08:05 +0200549 # Usage of stringstream is allowed under examples/ and in tests.
550 if f.LocalPath() == 'PRESUBMIT.py' or _IsException(f.LocalPath()):
Mirko Bonadeid2c83322018-03-19 10:31:47 +0000551 continue
552 for line_num, line in f.ChangedContents():
553 if ((include_re.search(line) or usage_re.search(line))
554 and not no_presubmit_re.search(line)):
555 errors.append(error_formatter(f.LocalPath(), line_num))
Mirko Bonadeia51bbd82018-03-08 16:15:45 +0100556 if errors:
557 return [output_api.PresubmitError(error_msg, errors)]
558 return []
559
Artem Titove92675b2018-05-22 10:21:27 +0200560
Mirko Bonadeia05d47e2018-05-09 11:03:38 +0200561def CheckPublicDepsIsNotUsed(gn_files, input_api, output_api):
562 """Checks that public_deps is not used without a good reason."""
Mirko Bonadei5c1ad592017-12-12 11:52:27 +0100563 result = []
Mirko Bonadeia05d47e2018-05-09 11:03:38 +0200564 no_presubmit_check_re = input_api.re.compile(
Joe Chen0b3a6e32019-12-26 23:01:42 -0800565 r'# no-presubmit-check TODO\(webrtc:\d+\)')
Mirko Bonadeia05d47e2018-05-09 11:03:38 +0200566 error_msg = ('public_deps is not recommended in WebRTC BUILD.gn files '
567 'because it doesn\'t map well to downstream build systems.\n'
568 'Used in: %s (line %d).\n'
569 'If you are not adding this code (e.g. you are just moving '
Patrik Höglund81c7a602020-01-30 11:32:33 +0100570 'existing code) or you have a good reason, you can add this '
571 'comment (verbatim) on the line that causes the problem:\n\n'
Mirko Bonadeia05d47e2018-05-09 11:03:38 +0200572 'public_deps = [ # no-presubmit-check TODO(webrtc:8603)\n')
Mirko Bonadei5c1ad592017-12-12 11:52:27 +0100573 for affected_file in gn_files:
574 for (line_number, affected_line) in affected_file.ChangedContents():
Patrik Höglund81c7a602020-01-30 11:32:33 +0100575 if 'public_deps' in affected_line:
576 surpressed = no_presubmit_check_re.search(affected_line)
577 if not surpressed:
578 result.append(
579 output_api.PresubmitError(error_msg % (affected_file.LocalPath(),
580 line_number)))
Mirko Bonadei5c1ad592017-12-12 11:52:27 +0100581 return result
582
Artem Titove92675b2018-05-22 10:21:27 +0200583
Mirko Bonadei05691dd2019-10-22 07:34:24 -0700584def CheckCheckIncludesIsNotUsed(gn_files, input_api, output_api):
Patrik Höglund6f491062018-01-11 12:04:23 +0100585 result = []
586 error_msg = ('check_includes overrides are not allowed since it can cause '
587 'incorrect dependencies to form. It effectively means that your '
588 'module can include any .h file without depending on its '
589 'corresponding target. There are some exceptional cases when '
Mirko Bonadei05691dd2019-10-22 07:34:24 -0700590 'this is allowed: if so, get approval from a .gn owner in the '
Patrik Höglund6f491062018-01-11 12:04:23 +0100591 'root OWNERS file.\n'
592 'Used in: %s (line %d).')
Mirko Bonadei05691dd2019-10-22 07:34:24 -0700593 no_presubmit_re = input_api.re.compile(
594 r'# no-presubmit-check TODO\(bugs\.webrtc\.org/\d+\)')
Patrik Höglund6f491062018-01-11 12:04:23 +0100595 for affected_file in gn_files:
596 for (line_number, affected_line) in affected_file.ChangedContents():
Mirko Bonadei05691dd2019-10-22 07:34:24 -0700597 if ('check_includes' in affected_line
598 and not no_presubmit_re.search(affected_line)):
Patrik Höglund6f491062018-01-11 12:04:23 +0100599 result.append(
600 output_api.PresubmitError(error_msg % (affected_file.LocalPath(),
601 line_number)))
602 return result
603
Artem Titove92675b2018-05-22 10:21:27 +0200604
Mirko Bonadeif0e0d752018-07-04 08:48:18 +0200605def CheckGnChanges(input_api, output_api):
Artem Titova04d1402018-05-11 11:23:00 +0200606 file_filter = lambda x: (input_api.FilterSourceFile(
Oleh Prypinafe01652017-10-04 15:56:08 +0200607 x, white_list=(r'.+\.(gn|gni)$',),
Mirko Bonadeif0e0d752018-07-04 08:48:18 +0200608 black_list=(r'.*/presubmit_checks_lib/testdata/.*',)))
ehmaldonado5b1ba082016-09-02 05:51:08 -0700609
610 gn_files = []
Artem Titova04d1402018-05-11 11:23:00 +0200611 for f in input_api.AffectedSourceFiles(file_filter):
Mirko Bonadei92ea95e2017-09-15 06:47:31 +0200612 gn_files.append(f)
ehmaldonado5b1ba082016-09-02 05:51:08 -0700613
614 result = []
615 if gn_files:
charujain9893e252017-09-14 13:33:22 +0200616 result.extend(CheckNoSourcesAbove(input_api, gn_files, output_api))
Mirko Bonadei4dc4e252017-09-19 13:49:16 +0200617 result.extend(CheckNoMixingSources(input_api, gn_files, output_api))
Mirko Bonadei2dcf3482020-06-05 14:30:41 +0200618 result.extend(CheckAbseilDependencies(input_api, gn_files, output_api))
Mirko Bonadei4dc4e252017-09-19 13:49:16 +0200619 result.extend(CheckNoPackageBoundaryViolations(input_api, gn_files,
620 output_api))
Mirko Bonadeia05d47e2018-05-09 11:03:38 +0200621 result.extend(CheckPublicDepsIsNotUsed(gn_files, input_api, output_api))
Mirko Bonadei05691dd2019-10-22 07:34:24 -0700622 result.extend(CheckCheckIncludesIsNotUsed(gn_files, input_api, output_api))
Mirko Bonadeif0e0d752018-07-04 08:48:18 +0200623 result.extend(CheckNoWarningSuppressionFlagsAreAdded(gn_files, input_api,
624 output_api))
ehmaldonado5b1ba082016-09-02 05:51:08 -0700625 return result
626
Artem Titove92675b2018-05-22 10:21:27 +0200627
Oleh Prypin920b6532017-10-05 11:28:51 +0200628def CheckGnGen(input_api, output_api):
629 """Runs `gn gen --check` with default args to detect mismatches between
630 #includes and dependencies in the BUILD.gn files, as well as general build
631 errors.
632 """
633 with _AddToPath(input_api.os_path.join(
634 input_api.PresubmitLocalPath(), 'tools_webrtc', 'presubmit_checks_lib')):
Yves Gerey546ee612019-02-26 17:04:16 +0100635 from build_helpers import RunGnCheck
Mirko Bonadeid8665442018-09-04 12:17:27 +0200636 errors = RunGnCheck(FindSrcDirPath(input_api.PresubmitLocalPath()))[:5]
Oleh Prypin920b6532017-10-05 11:28:51 +0200637 if errors:
638 return [output_api.PresubmitPromptWarning(
639 'Some #includes do not match the build dependency graph. Please run:\n'
640 ' gn gen --check <out_dir>',
641 long_text='\n\n'.join(errors))]
642 return []
643
Artem Titove92675b2018-05-22 10:21:27 +0200644
Artem Titova04d1402018-05-11 11:23:00 +0200645def CheckUnwantedDependencies(input_api, output_api, source_file_filter):
kjellander@webrtc.org3bd41562014-09-01 11:06:37 +0000646 """Runs checkdeps on #include statements added in this
647 change. Breaking - rules is an error, breaking ! rules is a
648 warning.
649 """
650 # Copied from Chromium's src/PRESUBMIT.py.
651
652 # We need to wait until we have an input_api object and use this
653 # roundabout construct to import checkdeps because this file is
654 # eval-ed and thus doesn't have __file__.
Mirko Bonadeid8665442018-09-04 12:17:27 +0200655 src_path = FindSrcDirPath(input_api.PresubmitLocalPath())
656 checkdeps_path = input_api.os_path.join(src_path, 'buildtools', 'checkdeps')
Oleh Prypin2f33a562017-10-04 20:17:54 +0200657 if not os.path.exists(checkdeps_path):
658 return [output_api.PresubmitError(
659 'Cannot find checkdeps at %s\nHave you run "gclient sync" to '
660 'download all the DEPS entries?' % checkdeps_path)]
661 with _AddToPath(checkdeps_path):
kjellander@webrtc.org3bd41562014-09-01 11:06:37 +0000662 import checkdeps
663 from cpp_checker import CppChecker
664 from rules import Rule
kjellander@webrtc.org3bd41562014-09-01 11:06:37 +0000665
666 added_includes = []
Artem Titova04d1402018-05-11 11:23:00 +0200667 for f in input_api.AffectedFiles(file_filter=source_file_filter):
kjellander@webrtc.org3bd41562014-09-01 11:06:37 +0000668 if not CppChecker.IsCppFile(f.LocalPath()):
669 continue
670
Henrik Kjellander57e5fd22015-05-25 12:55:39 +0200671 changed_lines = [line for _, line in f.ChangedContents()]
kjellander@webrtc.org3bd41562014-09-01 11:06:37 +0000672 added_includes.append([f.LocalPath(), changed_lines])
673
674 deps_checker = checkdeps.DepsChecker(input_api.PresubmitLocalPath())
675
676 error_descriptions = []
677 warning_descriptions = []
678 for path, rule_type, rule_description in deps_checker.CheckAddedCppIncludes(
679 added_includes):
680 description_with_path = '%s\n %s' % (path, rule_description)
681 if rule_type == Rule.DISALLOW:
682 error_descriptions.append(description_with_path)
683 else:
684 warning_descriptions.append(description_with_path)
685
686 results = []
687 if error_descriptions:
688 results.append(output_api.PresubmitError(
kjellandera7066a32017-03-23 03:47:05 -0700689 'You added one or more #includes that violate checkdeps rules.\n'
690 'Check that the DEPS files in these locations contain valid rules.\n'
691 'See https://cs.chromium.org/chromium/src/buildtools/checkdeps/ for '
692 'more details about checkdeps.',
kjellander@webrtc.org3bd41562014-09-01 11:06:37 +0000693 error_descriptions))
694 if warning_descriptions:
695 results.append(output_api.PresubmitPromptOrNotify(
696 'You added one or more #includes of files that are temporarily\n'
697 'allowed but being removed. Can you avoid introducing the\n'
kjellandera7066a32017-03-23 03:47:05 -0700698 '#include? See relevant DEPS file(s) for details and contacts.\n'
699 'See https://cs.chromium.org/chromium/src/buildtools/checkdeps/ for '
700 'more details about checkdeps.',
kjellander@webrtc.org3bd41562014-09-01 11:06:37 +0000701 warning_descriptions))
702 return results
703
Artem Titove92675b2018-05-22 10:21:27 +0200704
charujain9893e252017-09-14 13:33:22 +0200705def CheckCommitMessageBugEntry(input_api, output_api):
706 """Check that bug entries are well-formed in commit message."""
707 bogus_bug_msg = (
Mirko Bonadei61880182017-10-12 15:12:35 +0200708 'Bogus Bug entry: %s. Please specify the issue tracker prefix and the '
charujain9893e252017-09-14 13:33:22 +0200709 'issue number, separated by a colon, e.g. webrtc:123 or chromium:12345.')
710 results = []
Mirko Bonadei61880182017-10-12 15:12:35 +0200711 for bug in input_api.change.BugsFromDescription():
charujain9893e252017-09-14 13:33:22 +0200712 bug = bug.strip()
713 if bug.lower() == 'none':
714 continue
charujain81a58c72017-09-25 13:25:45 +0200715 if 'b/' not in bug and ':' not in bug:
charujain9893e252017-09-14 13:33:22 +0200716 try:
717 if int(bug) > 100000:
718 # Rough indicator for current chromium bugs.
719 prefix_guess = 'chromium'
720 else:
721 prefix_guess = 'webrtc'
Mirko Bonadei61880182017-10-12 15:12:35 +0200722 results.append('Bug entry requires issue tracker prefix, e.g. %s:%s' %
charujain9893e252017-09-14 13:33:22 +0200723 (prefix_guess, bug))
724 except ValueError:
725 results.append(bogus_bug_msg % bug)
charujain81a58c72017-09-25 13:25:45 +0200726 elif not (re.match(r'\w+:\d+', bug) or re.match(r'b/\d+', bug)):
charujain9893e252017-09-14 13:33:22 +0200727 results.append(bogus_bug_msg % bug)
728 return [output_api.PresubmitError(r) for r in results]
729
Artem Titove92675b2018-05-22 10:21:27 +0200730
charujain9893e252017-09-14 13:33:22 +0200731def CheckChangeHasBugField(input_api, output_api):
Mirko Bonadei61880182017-10-12 15:12:35 +0200732 """Requires that the changelist is associated with a bug.
kjellanderd1e26a92016-09-19 08:11:16 -0700733
734 This check is stricter than the one in depot_tools/presubmit_canned_checks.py
Mirko Bonadei61880182017-10-12 15:12:35 +0200735 since it fails the presubmit if the bug field is missing or doesn't contain
kjellanderd1e26a92016-09-19 08:11:16 -0700736 a bug reference.
Mirko Bonadei61880182017-10-12 15:12:35 +0200737
738 This supports both 'BUG=' and 'Bug:' since we are in the process of migrating
739 to Gerrit and it encourages the usage of 'Bug:'.
kjellanderd1e26a92016-09-19 08:11:16 -0700740 """
Mirko Bonadei61880182017-10-12 15:12:35 +0200741 if input_api.change.BugsFromDescription():
kjellanderd1e26a92016-09-19 08:11:16 -0700742 return []
743 else:
744 return [output_api.PresubmitError(
Mirko Bonadei61880182017-10-12 15:12:35 +0200745 'The "Bug: [bug number]" footer is mandatory. Please create a bug and '
kjellanderd1e26a92016-09-19 08:11:16 -0700746 'reference it using either of:\n'
Mirko Bonadei61880182017-10-12 15:12:35 +0200747 ' * https://bugs.webrtc.org - reference it using Bug: webrtc:XXXX\n'
748 ' * https://crbug.com - reference it using Bug: chromium:XXXXXX')]
kjellander@webrtc.orge4158642014-08-06 09:11:18 +0000749
Artem Titove92675b2018-05-22 10:21:27 +0200750
Artem Titova04d1402018-05-11 11:23:00 +0200751def CheckJSONParseErrors(input_api, output_api, source_file_filter):
kjellander569cf942016-02-11 05:02:59 -0800752 """Check that JSON files do not contain syntax errors."""
753
754 def FilterFile(affected_file):
Artem Titova04d1402018-05-11 11:23:00 +0200755 return (input_api.os_path.splitext(affected_file.LocalPath())[1] == '.json'
756 and source_file_filter(affected_file))
kjellander569cf942016-02-11 05:02:59 -0800757
758 def GetJSONParseError(input_api, filename):
759 try:
760 contents = input_api.ReadFile(filename)
761 input_api.json.loads(contents)
762 except ValueError as e:
763 return e
764 return None
765
766 results = []
767 for affected_file in input_api.AffectedFiles(
768 file_filter=FilterFile, include_deletes=False):
769 parse_error = GetJSONParseError(input_api,
770 affected_file.AbsoluteLocalPath())
771 if parse_error:
772 results.append(output_api.PresubmitError('%s could not be parsed: %s' %
Artem Titove92675b2018-05-22 10:21:27 +0200773 (affected_file.LocalPath(),
774 parse_error)))
kjellander569cf942016-02-11 05:02:59 -0800775 return results
776
777
charujain9893e252017-09-14 13:33:22 +0200778def RunPythonTests(input_api, output_api):
kjellanderc88b5d52017-04-05 06:42:43 -0700779 def Join(*args):
Henrik Kjellander8d3ad822015-05-26 19:52:05 +0200780 return input_api.os_path.join(input_api.PresubmitLocalPath(), *args)
781
782 test_directories = [
Edward Lemur6d01f6d2017-09-14 17:02:01 +0200783 input_api.PresubmitLocalPath(),
Mirko Bonadei92ea95e2017-09-15 06:47:31 +0200784 Join('rtc_tools', 'py_event_log_analyzer'),
Mirko Bonadei92ea95e2017-09-15 06:47:31 +0200785 Join('audio', 'test', 'unittests'),
ehmaldonado4fb97462017-01-30 05:27:22 -0800786 ] + [
Henrik Kjellander90fd7d82017-05-09 08:30:10 +0200787 root for root, _, files in os.walk(Join('tools_webrtc'))
ehmaldonado4fb97462017-01-30 05:27:22 -0800788 if any(f.endswith('_test.py') for f in files)
Henrik Kjellander8d3ad822015-05-26 19:52:05 +0200789 ]
790
791 tests = []
792 for directory in test_directories:
793 tests.extend(
794 input_api.canned_checks.GetUnitTestsInDirectory(
795 input_api,
796 output_api,
797 directory,
798 whitelist=[r'.+_test\.py$']))
799 return input_api.RunTests(tests, parallel=True)
800
801
Artem Titova04d1402018-05-11 11:23:00 +0200802def CheckUsageOfGoogleProtobufNamespace(input_api, output_api,
803 source_file_filter):
mbonadei38415b22017-04-07 05:38:01 -0700804 """Checks that the namespace google::protobuf has not been used."""
805 files = []
806 pattern = input_api.re.compile(r'google::protobuf')
Mirko Bonadei92ea95e2017-09-15 06:47:31 +0200807 proto_utils_path = os.path.join('rtc_base', 'protobuf_utils.h')
Artem Titova04d1402018-05-11 11:23:00 +0200808 file_filter = lambda x: (input_api.FilterSourceFile(x)
809 and source_file_filter(x))
810 for f in input_api.AffectedSourceFiles(file_filter):
mbonadei38415b22017-04-07 05:38:01 -0700811 if f.LocalPath() in [proto_utils_path, 'PRESUBMIT.py']:
812 continue
813 contents = input_api.ReadFile(f)
814 if pattern.search(contents):
815 files.append(f)
816
817 if files:
818 return [output_api.PresubmitError(
819 'Please avoid to use namespace `google::protobuf` directly.\n'
820 'Add a using directive in `%s` and include that header instead.'
821 % proto_utils_path, files)]
822 return []
823
824
Mirko Bonadei92ea95e2017-09-15 06:47:31 +0200825def _LicenseHeader(input_api):
826 """Returns the license header regexp."""
827 # Accept any year number from 2003 to the current year
828 current_year = int(input_api.time.strftime('%Y'))
829 allowed_years = (str(s) for s in reversed(xrange(2003, current_year + 1)))
830 years_re = '(' + '|'.join(allowed_years) + ')'
831 license_header = (
832 r'.*? Copyright( \(c\))? %(year)s The WebRTC [Pp]roject [Aa]uthors\. '
833 r'All [Rr]ights [Rr]eserved\.\n'
834 r'.*?\n'
835 r'.*? Use of this source code is governed by a BSD-style license\n'
836 r'.*? that can be found in the LICENSE file in the root of the source\n'
837 r'.*? tree\. An additional intellectual property rights grant can be '
838 r'found\n'
839 r'.*? in the file PATENTS\. All contributing project authors may\n'
840 r'.*? be found in the AUTHORS file in the root of the source tree\.\n'
841 ) % {
842 'year': years_re,
843 }
844 return license_header
845
846
charujain9893e252017-09-14 13:33:22 +0200847def CommonChecks(input_api, output_api):
andrew@webrtc.org53df1362012-01-26 21:24:23 +0000848 """Checks common to both upload and commit."""
niklase@google.comda159d62011-05-30 11:51:34 +0000849 results = []
tkchin42f580e2015-11-26 23:18:23 -0800850 # Filter out files that are in objc or ios dirs from being cpplint-ed since
851 # they do not follow C++ lint rules.
852 black_list = input_api.DEFAULT_BLACK_LIST + (
853 r".*\bobjc[\\\/].*",
Kári Tristan Helgason3fa35172016-09-09 08:55:05 +0000854 r".*objc\.[hcm]+$",
tkchin42f580e2015-11-26 23:18:23 -0800855 )
856 source_file_filter = lambda x: input_api.FilterSourceFile(x, None, black_list)
charujain9893e252017-09-14 13:33:22 +0200857 results.extend(CheckApprovedFilesLintClean(
tkchin42f580e2015-11-26 23:18:23 -0800858 input_api, output_api, source_file_filter))
Mirko Bonadei92ea95e2017-09-15 06:47:31 +0200859 results.extend(input_api.canned_checks.CheckLicense(
860 input_api, output_api, _LicenseHeader(input_api)))
phoglund@webrtc.org5d3713932013-03-07 09:59:43 +0000861 results.extend(input_api.canned_checks.RunPylint(input_api, output_api,
kjellander@webrtc.org177567c2016-12-22 10:40:28 +0100862 black_list=(r'^base[\\\/].*\.py$',
Henrik Kjellander14771ac2015-06-02 13:10:04 +0200863 r'^build[\\\/].*\.py$',
864 r'^buildtools[\\\/].*\.py$',
kjellander38c65c82017-04-12 22:43:38 -0700865 r'^infra[\\\/].*\.py$',
Henrik Kjellander0779e8f2016-12-22 12:01:17 +0100866 r'^ios[\\\/].*\.py$',
Henrik Kjellander14771ac2015-06-02 13:10:04 +0200867 r'^out.*[\\\/].*\.py$',
868 r'^testing[\\\/].*\.py$',
869 r'^third_party[\\\/].*\.py$',
kjellander@webrtc.org177567c2016-12-22 10:40:28 +0100870 r'^tools[\\\/].*\.py$',
kjellanderafd54942016-12-17 12:21:39 -0800871 # TODO(phoglund): should arguably be checked.
Henrik Kjellander90fd7d82017-05-09 08:30:10 +0200872 r'^tools_webrtc[\\\/]mb[\\\/].*\.py$',
Henrik Kjellander14771ac2015-06-02 13:10:04 +0200873 r'^xcodebuild.*[\\\/].*\.py$',),
Henrik Kjellander57e5fd22015-05-25 12:55:39 +0200874 pylintrc='pylintrc'))
kjellander569cf942016-02-11 05:02:59 -0800875
nisse3d21e232016-09-02 03:07:06 -0700876 # TODO(nisse): talk/ is no more, so make below checks simpler?
Henrik Kjellander57e5fd22015-05-25 12:55:39 +0200877 # WebRTC can't use the presubmit_canned_checks.PanProjectChecks function since
878 # we need to have different license checks in talk/ and webrtc/ directories.
879 # Instead, hand-picked checks are included below.
Henrik Kjellander63224672015-09-08 08:03:56 +0200880
tkchin3cd9a302016-06-08 12:40:28 -0700881 # .m and .mm files are ObjC files. For simplicity we will consider .h files in
882 # ObjC subdirectories ObjC headers.
883 objc_filter_list = (r'.+\.m$', r'.+\.mm$', r'.+objc\/.+\.h$')
Henrik Kjellanderb4af3d62016-11-16 20:11:29 +0100884 # Skip long-lines check for DEPS and GN files.
885 build_file_filter_list = (r'.+\.gn$', r'.+\.gni$', 'DEPS')
Artem Titova04d1402018-05-11 11:23:00 +0200886 # Also we will skip most checks for third_party directory.
Artem Titov42f0d782018-06-27 13:23:17 +0200887 third_party_filter_list = (r'^third_party[\\\/].+',)
tkchin3cd9a302016-06-08 12:40:28 -0700888 eighty_char_sources = lambda x: input_api.FilterSourceFile(x,
Artem Titova04d1402018-05-11 11:23:00 +0200889 black_list=build_file_filter_list + objc_filter_list +
890 third_party_filter_list)
tkchin3cd9a302016-06-08 12:40:28 -0700891 hundred_char_sources = lambda x: input_api.FilterSourceFile(x,
892 white_list=objc_filter_list)
Artem Titove92675b2018-05-22 10:21:27 +0200893 non_third_party_sources = lambda x: input_api.FilterSourceFile(x,
894 black_list=third_party_filter_list)
895
andrew@webrtc.org2442de12012-01-23 17:45:41 +0000896 results.extend(input_api.canned_checks.CheckLongLines(
tkchin3cd9a302016-06-08 12:40:28 -0700897 input_api, output_api, maxlen=80, source_file_filter=eighty_char_sources))
898 results.extend(input_api.canned_checks.CheckLongLines(
899 input_api, output_api, maxlen=100,
900 source_file_filter=hundred_char_sources))
andrew@webrtc.org2442de12012-01-23 17:45:41 +0000901 results.extend(input_api.canned_checks.CheckChangeHasNoTabs(
Artem Titova04d1402018-05-11 11:23:00 +0200902 input_api, output_api, source_file_filter=non_third_party_sources))
andrew@webrtc.org53df1362012-01-26 21:24:23 +0000903 results.extend(input_api.canned_checks.CheckChangeHasNoStrayWhitespace(
Artem Titova04d1402018-05-11 11:23:00 +0200904 input_api, output_api, source_file_filter=non_third_party_sources))
kjellandere5dc62a2016-12-14 00:16:21 -0800905 results.extend(input_api.canned_checks.CheckAuthorizedAuthor(
Oleh Prypine0735142018-10-04 11:15:54 +0200906 input_api, output_api, bot_whitelist=[
907 'chromium-webrtc-autoroll@webrtc-ci.iam.gserviceaccount.com'
908 ]))
andrew@webrtc.org53df1362012-01-26 21:24:23 +0000909 results.extend(input_api.canned_checks.CheckChangeTodoHasOwner(
Artem Titova04d1402018-05-11 11:23:00 +0200910 input_api, output_api, source_file_filter=non_third_party_sources))
Yves Gerey87a93532018-06-20 15:51:49 +0200911 results.extend(input_api.canned_checks.CheckPatchFormatted(
912 input_api, output_api))
charujain9893e252017-09-14 13:33:22 +0200913 results.extend(CheckNativeApiHeaderChanges(input_api, output_api))
Artem Titova04d1402018-05-11 11:23:00 +0200914 results.extend(CheckNoIOStreamInHeaders(
915 input_api, output_api, source_file_filter=non_third_party_sources))
916 results.extend(CheckNoPragmaOnce(
917 input_api, output_api, source_file_filter=non_third_party_sources))
918 results.extend(CheckNoFRIEND_TEST(
919 input_api, output_api, source_file_filter=non_third_party_sources))
Mirko Bonadeif0e0d752018-07-04 08:48:18 +0200920 results.extend(CheckGnChanges(input_api, output_api))
Artem Titova04d1402018-05-11 11:23:00 +0200921 results.extend(CheckUnwantedDependencies(
922 input_api, output_api, source_file_filter=non_third_party_sources))
923 results.extend(CheckJSONParseErrors(
924 input_api, output_api, source_file_filter=non_third_party_sources))
charujain9893e252017-09-14 13:33:22 +0200925 results.extend(RunPythonTests(input_api, output_api))
Artem Titova04d1402018-05-11 11:23:00 +0200926 results.extend(CheckUsageOfGoogleProtobufNamespace(
927 input_api, output_api, source_file_filter=non_third_party_sources))
928 results.extend(CheckOrphanHeaders(
929 input_api, output_api, source_file_filter=non_third_party_sources))
930 results.extend(CheckNewlineAtTheEndOfProtoFiles(
931 input_api, output_api, source_file_filter=non_third_party_sources))
932 results.extend(CheckNoStreamUsageIsAdded(
Artem Titov739351d2018-05-11 12:21:36 +0200933 input_api, output_api, non_third_party_sources))
Mirko Bonadei9ce800d2019-02-05 16:48:13 +0100934 results.extend(CheckNoTestCaseUsageIsAdded(
935 input_api, output_api, non_third_party_sources))
Mirko Bonadei7e4ee6e2018-09-28 11:45:23 +0200936 results.extend(CheckAddedDepsHaveTargetApprovals(input_api, output_api))
Mirko Bonadeia418e672018-10-24 13:57:25 +0200937 results.extend(CheckApiDepsFileIsUpToDate(input_api, output_api))
tzika06bf852018-11-15 20:37:35 +0900938 results.extend(CheckAbslMemoryInclude(
939 input_api, output_api, non_third_party_sources))
Mirko Bonadei9fa8ef12019-09-17 19:14:13 +0200940 results.extend(CheckBannedAbslMakeUnique(
941 input_api, output_api, non_third_party_sources))
Mirko Bonadeia418e672018-10-24 13:57:25 +0200942 return results
943
944
945def CheckApiDepsFileIsUpToDate(input_api, output_api):
Mirko Bonadei90490372018-10-26 13:17:47 +0200946 """Check that 'include_rules' in api/DEPS is up to date.
947
948 The file api/DEPS must be kept up to date in order to avoid to avoid to
949 include internal header from WebRTC's api/ headers.
950
951 This check is focused on ensuring that 'include_rules' contains a deny
952 rule for each root level directory. More focused allow rules can be
953 added to 'specific_include_rules'.
954 """
Mirko Bonadeia418e672018-10-24 13:57:25 +0200955 results = []
956 api_deps = os.path.join(input_api.PresubmitLocalPath(), 'api', 'DEPS')
957 with open(api_deps) as f:
958 deps_content = _ParseDeps(f.read())
959
960 include_rules = deps_content.get('include_rules', [])
Mirko Bonadei01e97ae2019-09-05 14:36:42 +0200961 dirs_to_skip = set(['api', 'docs'])
Mirko Bonadeia418e672018-10-24 13:57:25 +0200962
Mirko Bonadei90490372018-10-26 13:17:47 +0200963 # Only check top level directories affected by the current CL.
964 dirs_to_check = set()
965 for f in input_api.AffectedFiles():
966 path_tokens = [t for t in f.LocalPath().split(os.sep) if t]
967 if len(path_tokens) > 1:
Mirko Bonadei01e97ae2019-09-05 14:36:42 +0200968 if (path_tokens[0] not in dirs_to_skip and
Mirko Bonadei90490372018-10-26 13:17:47 +0200969 os.path.isdir(os.path.join(input_api.PresubmitLocalPath(),
970 path_tokens[0]))):
971 dirs_to_check.add(path_tokens[0])
Mirko Bonadeia418e672018-10-24 13:57:25 +0200972
Mirko Bonadei90490372018-10-26 13:17:47 +0200973 missing_include_rules = set()
974 for p in dirs_to_check:
Mirko Bonadeia418e672018-10-24 13:57:25 +0200975 rule = '-%s' % p
976 if rule not in include_rules:
Mirko Bonadei90490372018-10-26 13:17:47 +0200977 missing_include_rules.add(rule)
978
Mirko Bonadeia418e672018-10-24 13:57:25 +0200979 if missing_include_rules:
Mirko Bonadei90490372018-10-26 13:17:47 +0200980 error_msg = [
981 'include_rules = [\n',
982 ' ...\n',
983 ]
Mirko Bonadeia418e672018-10-24 13:57:25 +0200984
Mirko Bonadei90490372018-10-26 13:17:47 +0200985 for r in sorted(missing_include_rules):
986 error_msg.append(' "%s",\n' % str(r))
Mirko Bonadeia418e672018-10-24 13:57:25 +0200987
Mirko Bonadei90490372018-10-26 13:17:47 +0200988 error_msg.append(' ...\n')
989 error_msg.append(']\n')
990
Mirko Bonadeia418e672018-10-24 13:57:25 +0200991 results.append(output_api.PresubmitError(
Mirko Bonadei90490372018-10-26 13:17:47 +0200992 'New root level directory detected! WebRTC api/ headers should '
993 'not #include headers from \n'
994 'the new directory, so please update "include_rules" in file\n'
995 '"%s". Example:\n%s\n' % (api_deps, ''.join(error_msg))))
996
andrew@webrtc.org53df1362012-01-26 21:24:23 +0000997 return results
andrew@webrtc.org2442de12012-01-23 17:45:41 +0000998
Mirko Bonadei9fa8ef12019-09-17 19:14:13 +0200999def CheckBannedAbslMakeUnique(input_api, output_api, source_file_filter):
1000 file_filter = lambda f: (f.LocalPath().endswith(('.cc', '.h'))
1001 and source_file_filter(f))
1002
1003 files = []
1004 for f in input_api.AffectedFiles(
1005 include_deletes=False, file_filter=file_filter):
1006 for _, line in f.ChangedContents():
1007 if 'absl::make_unique' in line:
1008 files.append(f)
1009 break
1010
1011 if len(files):
1012 return [output_api.PresubmitError(
1013 'Please use std::make_unique instead of absl::make_unique.\n'
1014 'Affected files:',
1015 files)]
1016 return []
1017
tzika06bf852018-11-15 20:37:35 +09001018def CheckAbslMemoryInclude(input_api, output_api, source_file_filter):
1019 pattern = input_api.re.compile(
1020 r'^#include\s*"absl/memory/memory.h"', input_api.re.MULTILINE)
1021 file_filter = lambda f: (f.LocalPath().endswith(('.cc', '.h'))
1022 and source_file_filter(f))
1023
1024 files = []
1025 for f in input_api.AffectedFiles(
1026 include_deletes=False, file_filter=file_filter):
1027 contents = input_api.ReadFile(f)
1028 if pattern.search(contents):
1029 continue
1030 for _, line in f.ChangedContents():
Mirko Bonadei9fa8ef12019-09-17 19:14:13 +02001031 if 'absl::WrapUnique' in line:
tzika06bf852018-11-15 20:37:35 +09001032 files.append(f)
1033 break
1034
1035 if len(files):
1036 return [output_api.PresubmitError(
Mirko Bonadei9fa8ef12019-09-17 19:14:13 +02001037 'Please include "absl/memory/memory.h" header for absl::WrapUnique.\n'
1038 'This header may or may not be included transitively depending on the '
1039 'C++ standard version.',
tzika06bf852018-11-15 20:37:35 +09001040 files)]
1041 return []
kjellander@webrtc.orge4158642014-08-06 09:11:18 +00001042
andrew@webrtc.org53df1362012-01-26 21:24:23 +00001043def CheckChangeOnUpload(input_api, output_api):
1044 results = []
charujain9893e252017-09-14 13:33:22 +02001045 results.extend(CommonChecks(input_api, output_api))
Oleh Prypin920b6532017-10-05 11:28:51 +02001046 results.extend(CheckGnGen(input_api, output_api))
Henrik Kjellander57e5fd22015-05-25 12:55:39 +02001047 results.extend(
1048 input_api.canned_checks.CheckGNFormatted(input_api, output_api))
niklase@google.comda159d62011-05-30 11:51:34 +00001049 return results
1050
kjellander@webrtc.orge4158642014-08-06 09:11:18 +00001051
andrew@webrtc.org2442de12012-01-23 17:45:41 +00001052def CheckChangeOnCommit(input_api, output_api):
niklase@google.com1198db92011-06-09 07:07:24 +00001053 results = []
charujain9893e252017-09-14 13:33:22 +02001054 results.extend(CommonChecks(input_api, output_api))
1055 results.extend(VerifyNativeApiHeadersListIsValid(input_api, output_api))
Artem Titov42f0d782018-06-27 13:23:17 +02001056 results.extend(input_api.canned_checks.CheckOwners(input_api, output_api))
andrew@webrtc.org53df1362012-01-26 21:24:23 +00001057 results.extend(input_api.canned_checks.CheckChangeWasUploaded(
1058 input_api, output_api))
1059 results.extend(input_api.canned_checks.CheckChangeHasDescription(
1060 input_api, output_api))
charujain9893e252017-09-14 13:33:22 +02001061 results.extend(CheckChangeHasBugField(input_api, output_api))
1062 results.extend(CheckCommitMessageBugEntry(input_api, output_api))
kjellander@webrtc.org12cb88c2014-02-13 11:53:43 +00001063 results.extend(input_api.canned_checks.CheckTreeIsOpen(
1064 input_api, output_api,
1065 json_url='http://webrtc-status.appspot.com/current?format=json'))
niklase@google.com1198db92011-06-09 07:07:24 +00001066 return results
mbonadei74973ed2017-05-09 07:58:05 -07001067
1068
Artem Titova04d1402018-05-11 11:23:00 +02001069def CheckOrphanHeaders(input_api, output_api, source_file_filter):
mbonadei74973ed2017-05-09 07:58:05 -07001070 # We need to wait until we have an input_api object and use this
1071 # roundabout construct to import prebubmit_checks_lib because this file is
1072 # eval-ed and thus doesn't have __file__.
Patrik Höglund2f3f7222017-12-19 11:08:56 +01001073 error_msg = """{} should be listed in {}."""
mbonadei74973ed2017-05-09 07:58:05 -07001074 results = []
Patrik Höglund7e60de22018-01-09 14:22:00 +01001075 orphan_blacklist = [
1076 os.path.join('tools_webrtc', 'ios', 'SDK'),
1077 ]
Oleh Prypin2f33a562017-10-04 20:17:54 +02001078 with _AddToPath(input_api.os_path.join(
1079 input_api.PresubmitLocalPath(), 'tools_webrtc', 'presubmit_checks_lib')):
mbonadei74973ed2017-05-09 07:58:05 -07001080 from check_orphan_headers import GetBuildGnPathFromFilePath
1081 from check_orphan_headers import IsHeaderInBuildGn
mbonadei74973ed2017-05-09 07:58:05 -07001082
Artem Titova04d1402018-05-11 11:23:00 +02001083 file_filter = lambda x: input_api.FilterSourceFile(
1084 x, black_list=orphan_blacklist) and source_file_filter(x)
1085 for f in input_api.AffectedSourceFiles(file_filter):
Patrik Höglund7e60de22018-01-09 14:22:00 +01001086 if f.LocalPath().endswith('.h'):
mbonadei74973ed2017-05-09 07:58:05 -07001087 file_path = os.path.abspath(f.LocalPath())
1088 root_dir = os.getcwd()
1089 gn_file_path = GetBuildGnPathFromFilePath(file_path, os.path.exists,
1090 root_dir)
1091 in_build_gn = IsHeaderInBuildGn(file_path, gn_file_path)
1092 if not in_build_gn:
1093 results.append(output_api.PresubmitError(error_msg.format(
Patrik Höglund2f3f7222017-12-19 11:08:56 +01001094 f.LocalPath(), os.path.relpath(gn_file_path))))
mbonadei74973ed2017-05-09 07:58:05 -07001095 return results
Mirko Bonadei960fd5b2017-06-29 14:59:36 +02001096
1097
Artem Titove92675b2018-05-22 10:21:27 +02001098def CheckNewlineAtTheEndOfProtoFiles(input_api, output_api, source_file_filter):
Mirko Bonadei960fd5b2017-06-29 14:59:36 +02001099 """Checks that all .proto files are terminated with a newline."""
1100 error_msg = 'File {} must end with exactly one newline.'
1101 results = []
Artem Titova04d1402018-05-11 11:23:00 +02001102 file_filter = lambda x: input_api.FilterSourceFile(
1103 x, white_list=(r'.+\.proto$',)) and source_file_filter(x)
1104 for f in input_api.AffectedSourceFiles(file_filter):
Mirko Bonadei960fd5b2017-06-29 14:59:36 +02001105 file_path = f.LocalPath()
1106 with open(file_path) as f:
1107 lines = f.readlines()
Mirko Bonadeia730c1c2017-09-18 11:33:13 +02001108 if len(lines) > 0 and not lines[-1].endswith('\n'):
Mirko Bonadei960fd5b2017-06-29 14:59:36 +02001109 results.append(output_api.PresubmitError(error_msg.format(file_path)))
1110 return results
Mirko Bonadei7e4ee6e2018-09-28 11:45:23 +02001111
1112
1113def _ExtractAddRulesFromParsedDeps(parsed_deps):
1114 """Extract the rules that add dependencies from a parsed DEPS file.
1115
1116 Args:
1117 parsed_deps: the locals dictionary from evaluating the DEPS file."""
1118 add_rules = set()
1119 add_rules.update([
1120 rule[1:] for rule in parsed_deps.get('include_rules', [])
1121 if rule.startswith('+') or rule.startswith('!')
1122 ])
1123 for _, rules in parsed_deps.get('specific_include_rules',
1124 {}).iteritems():
1125 add_rules.update([
1126 rule[1:] for rule in rules
1127 if rule.startswith('+') or rule.startswith('!')
1128 ])
1129 return add_rules
1130
1131
1132def _ParseDeps(contents):
1133 """Simple helper for parsing DEPS files."""
1134 # Stubs for handling special syntax in the root DEPS file.
1135 class VarImpl(object):
1136
1137 def __init__(self, local_scope):
1138 self._local_scope = local_scope
1139
1140 def Lookup(self, var_name):
1141 """Implements the Var syntax."""
1142 try:
1143 return self._local_scope['vars'][var_name]
1144 except KeyError:
1145 raise Exception('Var is not defined: %s' % var_name)
1146
1147 local_scope = {}
1148 global_scope = {
1149 'Var': VarImpl(local_scope).Lookup,
1150 }
1151 exec contents in global_scope, local_scope
1152 return local_scope
1153
1154
1155def _CalculateAddedDeps(os_path, old_contents, new_contents):
1156 """Helper method for _CheckAddedDepsHaveTargetApprovals. Returns
1157 a set of DEPS entries that we should look up.
1158
1159 For a directory (rather than a specific filename) we fake a path to
1160 a specific filename by adding /DEPS. This is chosen as a file that
1161 will seldom or never be subject to per-file include_rules.
1162 """
1163 # We ignore deps entries on auto-generated directories.
1164 auto_generated_dirs = ['grit', 'jni']
1165
1166 old_deps = _ExtractAddRulesFromParsedDeps(_ParseDeps(old_contents))
1167 new_deps = _ExtractAddRulesFromParsedDeps(_ParseDeps(new_contents))
1168
1169 added_deps = new_deps.difference(old_deps)
1170
1171 results = set()
1172 for added_dep in added_deps:
1173 if added_dep.split('/')[0] in auto_generated_dirs:
1174 continue
1175 # Assume that a rule that ends in .h is a rule for a specific file.
1176 if added_dep.endswith('.h'):
1177 results.add(added_dep)
1178 else:
1179 results.add(os_path.join(added_dep, 'DEPS'))
1180 return results
1181
1182
1183def CheckAddedDepsHaveTargetApprovals(input_api, output_api):
1184 """When a dependency prefixed with + is added to a DEPS file, we
1185 want to make sure that the change is reviewed by an OWNER of the
1186 target file or directory, to avoid layering violations from being
1187 introduced. This check verifies that this happens.
1188 """
1189 virtual_depended_on_files = set()
1190
1191 file_filter = lambda f: not input_api.re.match(
1192 r"^third_party[\\\/](WebKit|blink)[\\\/].*", f.LocalPath())
1193 for f in input_api.AffectedFiles(include_deletes=False,
1194 file_filter=file_filter):
1195 filename = input_api.os_path.basename(f.LocalPath())
1196 if filename == 'DEPS':
1197 virtual_depended_on_files.update(_CalculateAddedDeps(
1198 input_api.os_path,
1199 '\n'.join(f.OldContents()),
1200 '\n'.join(f.NewContents())))
1201
1202 if not virtual_depended_on_files:
1203 return []
1204
1205 if input_api.is_committing:
1206 if input_api.tbr:
1207 return [output_api.PresubmitNotifyResult(
1208 '--tbr was specified, skipping OWNERS check for DEPS additions')]
1209 if input_api.dry_run:
1210 return [output_api.PresubmitNotifyResult(
1211 'This is a dry run, skipping OWNERS check for DEPS additions')]
1212 if not input_api.change.issue:
1213 return [output_api.PresubmitError(
1214 "DEPS approval by OWNERS check failed: this change has "
1215 "no change number, so we can't check it for approvals.")]
1216 output = output_api.PresubmitError
1217 else:
1218 output = output_api.PresubmitNotifyResult
1219
1220 owners_db = input_api.owners_db
1221 owner_email, reviewers = (
1222 input_api.canned_checks.GetCodereviewOwnerAndReviewers(
1223 input_api,
1224 owners_db.email_regexp,
1225 approval_needed=input_api.is_committing))
1226
1227 owner_email = owner_email or input_api.change.author_email
1228
1229 reviewers_plus_owner = set(reviewers)
1230 if owner_email:
1231 reviewers_plus_owner.add(owner_email)
1232 missing_files = owners_db.files_not_covered_by(virtual_depended_on_files,
1233 reviewers_plus_owner)
1234
1235 # We strip the /DEPS part that was added by
1236 # _FilesToCheckForIncomingDeps to fake a path to a file in a
1237 # directory.
1238 def StripDeps(path):
1239 start_deps = path.rfind('/DEPS')
1240 if start_deps != -1:
1241 return path[:start_deps]
1242 else:
1243 return path
1244 unapproved_dependencies = ["'+%s'," % StripDeps(path)
1245 for path in missing_files]
1246
1247 if unapproved_dependencies:
1248 output_list = [
1249 output('You need LGTM from owners of depends-on paths in DEPS that were '
1250 'modified in this CL:\n %s' %
1251 '\n '.join(sorted(unapproved_dependencies)))]
1252 suggested_owners = owners_db.reviewers_for(missing_files, owner_email)
1253 output_list.append(output(
1254 'Suggested missing target path OWNERS:\n %s' %
1255 '\n '.join(suggested_owners or [])))
1256 return output_list
1257
1258 return []