blob: 247b78eaa0f655d4426c914f7e71b42c773d51c9 [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.
kjellandere5a87a52016-04-27 02:32:12 -070050# - whitespace/operators: Same as above (doesn't seem sufficient to eliminate
51# all move-related errors).
jbauchc4e3ead2016-02-19 00:25:55 -080052BLACKLIST_LINT_FILTERS = [
53 '-build/c++11',
kjellandere5a87a52016-04-27 02:32:12 -070054 '-whitespace/operators',
jbauchc4e3ead2016-02-19 00:25:55 -080055]
56
kjellanderfd595232015-12-04 02:44:09 -080057# List of directories of "supported" native APIs. That means changes to headers
58# will be done in a compatible way following this scheme:
59# 1. Non-breaking changes are made.
60# 2. The old APIs as marked as deprecated (with comments).
61# 3. Deprecation is announced to discuss-webrtc@googlegroups.com and
62# webrtc-users@google.com (internal list).
63# 4. (later) The deprecated APIs are removed.
kjellander53047c92015-12-02 23:56:14 -080064NATIVE_API_DIRS = (
Karl Wibergef52d8b82017-10-25 13:20:03 +020065 'api', # All subdirectories of api/ are included as well.
Mirko Bonadeia4eeeff2018-01-11 13:16:52 +010066 'media/base',
67 'media/engine',
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020068 'modules/audio_device/include',
69 'pc',
kjellanderdd705472016-06-09 11:17:27 -070070)
Mirko Bonadei4dc4e252017-09-19 13:49:16 +020071
kjellanderdd705472016-06-09 11:17:27 -070072# These directories should not be used but are maintained only to avoid breaking
73# some legacy downstream code.
74LEGACY_API_DIRS = (
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020075 'common_audio/include',
76 'modules/audio_coding/include',
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020077 'modules/audio_processing/include',
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020078 'modules/congestion_controller/include',
79 'modules/include',
80 'modules/remote_bitrate_estimator/include',
81 'modules/rtp_rtcp/include',
82 'modules/rtp_rtcp/source',
83 'modules/utility/include',
84 'modules/video_coding/codecs/h264/include',
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020085 'modules/video_coding/codecs/vp8/include',
86 'modules/video_coding/codecs/vp9/include',
87 'modules/video_coding/include',
88 'rtc_base',
89 'system_wrappers/include',
kjellander53047c92015-12-02 23:56:14 -080090)
Mirko Bonadei4dc4e252017-09-19 13:49:16 +020091
Karl Wibergd4f01c12017-11-10 10:55:45 +010092# NOTE: The set of directories in API_DIRS should be the same as those
93# listed in the table in native-api.md.
kjellanderdd705472016-06-09 11:17:27 -070094API_DIRS = NATIVE_API_DIRS[:] + LEGACY_API_DIRS[:]
kjellander53047c92015-12-02 23:56:14 -080095
Mirko Bonadei4dc4e252017-09-19 13:49:16 +020096# TARGET_RE matches a GN target, and extracts the target name and the contents.
97TARGET_RE = re.compile(r'(?P<indent>\s*)\w+\("(?P<target_name>\w+)"\) {'
98 r'(?P<target_contents>.*?)'
99 r'(?P=indent)}',
100 re.MULTILINE | re.DOTALL)
101
102# SOURCES_RE matches a block of sources inside a GN target.
103SOURCES_RE = re.compile(r'sources \+?= \[(?P<sources>.*?)\]',
104 re.MULTILINE | re.DOTALL)
105
106# FILE_PATH_RE matchies a file path.
107FILE_PATH_RE = re.compile(r'"(?P<file_path>(\w|\/)+)(?P<extension>\.\w+)"')
108
kjellander53047c92015-12-02 23:56:14 -0800109
Mirko Bonadeid8665442018-09-04 12:17:27 +0200110def FindSrcDirPath(starting_dir):
111 """Returns the abs path to the src/ dir of the project."""
112 src_dir = starting_dir
113 while os.path.basename(src_dir) != 'src':
114 src_dir = os.path.normpath(os.path.join(src_dir, os.pardir))
115 return src_dir
116
117
Oleh Prypin2f33a562017-10-04 20:17:54 +0200118@contextmanager
119def _AddToPath(*paths):
120 original_sys_path = sys.path
121 sys.path.extend(paths)
122 try:
123 yield
124 finally:
125 # Restore sys.path to what it was before.
126 sys.path = original_sys_path
ehmaldonado4fb97462017-01-30 05:27:22 -0800127
128
charujain9893e252017-09-14 13:33:22 +0200129def VerifyNativeApiHeadersListIsValid(input_api, output_api):
kjellander53047c92015-12-02 23:56:14 -0800130 """Ensures the list of native API header directories is up to date."""
131 non_existing_paths = []
132 native_api_full_paths = [
133 input_api.os_path.join(input_api.PresubmitLocalPath(),
kjellanderdd705472016-06-09 11:17:27 -0700134 *path.split('/')) for path in API_DIRS]
kjellander53047c92015-12-02 23:56:14 -0800135 for path in native_api_full_paths:
136 if not os.path.isdir(path):
137 non_existing_paths.append(path)
138 if non_existing_paths:
139 return [output_api.PresubmitError(
140 'Directories to native API headers have changed which has made the '
141 'list in PRESUBMIT.py outdated.\nPlease update it to the current '
142 'location of our native APIs.',
143 non_existing_paths)]
144 return []
145
Artem Titove92675b2018-05-22 10:21:27 +0200146
kjellanderc88b5d52017-04-05 06:42:43 -0700147API_CHANGE_MSG = """
kwibergeb133022016-04-07 07:41:48 -0700148You seem to be changing native API header files. Please make sure that you:
oprypin375b9ac2017-02-13 04:13:23 -0800149 1. Make compatible changes that don't break existing clients. Usually
150 this is done by keeping the existing method signatures unchanged.
151 2. Mark the old stuff as deprecated (see RTC_DEPRECATED macro).
kwibergeb133022016-04-07 07:41:48 -0700152 3. Create a timeline and plan for when the deprecated stuff will be
153 removed. (The amount of time we give users to change their code
154 should be informed by how much work it is for them. If they just
155 need to replace one name with another or something equally
156 simple, 1-2 weeks might be good; if they need to do serious work,
157 up to 3 months may be called for.)
158 4. Update/inform existing downstream code owners to stop using the
159 deprecated stuff. (Send announcements to
160 discuss-webrtc@googlegroups.com and webrtc-users@google.com.)
161 5. Remove the deprecated stuff, once the agreed-upon amount of time
162 has passed.
163Related files:
164"""
kjellander53047c92015-12-02 23:56:14 -0800165
Artem Titove92675b2018-05-22 10:21:27 +0200166
charujain9893e252017-09-14 13:33:22 +0200167def CheckNativeApiHeaderChanges(input_api, output_api):
kjellander53047c92015-12-02 23:56:14 -0800168 """Checks to remind proper changing of native APIs."""
169 files = []
Karl Wiberg6bfac032017-10-27 15:14:20 +0200170 source_file_filter = lambda x: input_api.FilterSourceFile(
171 x, white_list=[r'.+\.(gn|gni|h)$'])
172 for f in input_api.AffectedSourceFiles(source_file_filter):
173 for path in API_DIRS:
174 dn = os.path.dirname(f.LocalPath())
175 if path == 'api':
176 # Special case: Subdirectories included.
177 if dn == 'api' or dn.startswith('api/'):
Niels Möller1d201852019-06-26 12:58:27 +0200178 files.append(f.LocalPath())
Karl Wiberg6bfac032017-10-27 15:14:20 +0200179 else:
180 # Normal case: Subdirectories not included.
181 if dn == path:
Niels Möller1d201852019-06-26 12:58:27 +0200182 files.append(f.LocalPath())
kjellander53047c92015-12-02 23:56:14 -0800183
184 if files:
kjellanderc88b5d52017-04-05 06:42:43 -0700185 return [output_api.PresubmitNotifyResult(API_CHANGE_MSG, files)]
kjellander53047c92015-12-02 23:56:14 -0800186 return []
187
kjellander@webrtc.org0fcaf992015-11-26 15:24:52 +0100188
Artem Titova04d1402018-05-11 11:23:00 +0200189def CheckNoIOStreamInHeaders(input_api, output_api,
190 source_file_filter):
kjellander@webrtc.org51198f12012-02-21 17:53:46 +0000191 """Checks to make sure no .h files include <iostream>."""
192 files = []
193 pattern = input_api.re.compile(r'^#include\s*<iostream>',
194 input_api.re.MULTILINE)
Artem Titova04d1402018-05-11 11:23:00 +0200195 file_filter = lambda x: (input_api.FilterSourceFile(x)
196 and source_file_filter(x))
197 for f in input_api.AffectedSourceFiles(file_filter):
kjellander@webrtc.org51198f12012-02-21 17:53:46 +0000198 if not f.LocalPath().endswith('.h'):
199 continue
200 contents = input_api.ReadFile(f)
201 if pattern.search(contents):
202 files.append(f)
203
204 if len(files):
Henrik Kjellander57e5fd22015-05-25 12:55:39 +0200205 return [output_api.PresubmitError(
kjellander@webrtc.org51198f12012-02-21 17:53:46 +0000206 'Do not #include <iostream> in header files, since it inserts static ' +
207 'initialization into every file including the header. Instead, ' +
208 '#include <ostream>. See http://crbug.com/94794',
Henrik Kjellander57e5fd22015-05-25 12:55:39 +0200209 files)]
kjellander@webrtc.org51198f12012-02-21 17:53:46 +0000210 return []
211
kjellander@webrtc.orge4158642014-08-06 09:11:18 +0000212
Artem Titova04d1402018-05-11 11:23:00 +0200213def CheckNoPragmaOnce(input_api, output_api,
214 source_file_filter):
kjellander6aeef742017-02-20 01:13:18 -0800215 """Make sure that banned functions are not used."""
216 files = []
217 pattern = input_api.re.compile(r'^#pragma\s+once',
218 input_api.re.MULTILINE)
Artem Titova04d1402018-05-11 11:23:00 +0200219 file_filter = lambda x: (input_api.FilterSourceFile(x)
220 and source_file_filter(x))
221 for f in input_api.AffectedSourceFiles(file_filter):
kjellander6aeef742017-02-20 01:13:18 -0800222 if not f.LocalPath().endswith('.h'):
223 continue
224 contents = input_api.ReadFile(f)
225 if pattern.search(contents):
226 files.append(f)
227
228 if files:
229 return [output_api.PresubmitError(
230 'Do not use #pragma once in header files.\n'
231 'See http://www.chromium.org/developers/coding-style#TOC-File-headers',
232 files)]
233 return []
234
235
Artem Titova04d1402018-05-11 11:23:00 +0200236def CheckNoFRIEND_TEST(input_api, output_api, # pylint: disable=invalid-name
237 source_file_filter):
kjellander@webrtc.org51198f12012-02-21 17:53:46 +0000238 """Make sure that gtest's FRIEND_TEST() macro is not used, the
239 FRIEND_TEST_ALL_PREFIXES() macro from testsupport/gtest_prod_util.h should be
240 used instead since that allows for FLAKY_, FAILS_ and DISABLED_ prefixes."""
241 problems = []
242
Artem Titova04d1402018-05-11 11:23:00 +0200243 file_filter = lambda f: (f.LocalPath().endswith(('.cc', '.h'))
244 and source_file_filter(f))
kjellander@webrtc.org51198f12012-02-21 17:53:46 +0000245 for f in input_api.AffectedFiles(file_filter=file_filter):
246 for line_num, line in f.ChangedContents():
247 if 'FRIEND_TEST(' in line:
248 problems.append(' %s:%d' % (f.LocalPath(), line_num))
249
250 if not problems:
251 return []
252 return [output_api.PresubmitPromptWarning('WebRTC\'s code should not use '
253 'gtest\'s FRIEND_TEST() macro. Include testsupport/gtest_prod_util.h and '
254 'use FRIEND_TEST_ALL_PREFIXES() instead.\n' + '\n'.join(problems))]
255
kjellander@webrtc.orge4158642014-08-06 09:11:18 +0000256
charujain9893e252017-09-14 13:33:22 +0200257def IsLintBlacklisted(blacklist_paths, file_path):
oprypin2aa463f2017-03-23 03:17:02 -0700258 """ Checks if a file is blacklisted for lint check."""
259 for path in blacklist_paths:
260 if file_path == path or os.path.dirname(file_path).startswith(path):
kjellander@webrtc.org0fcaf992015-11-26 15:24:52 +0100261 return True
262 return False
263
264
charujain9893e252017-09-14 13:33:22 +0200265def CheckApprovedFilesLintClean(input_api, output_api,
Artem Titova04d1402018-05-11 11:23:00 +0200266 source_file_filter=None):
oprypin2aa463f2017-03-23 03:17:02 -0700267 """Checks that all new or non-blacklisted .cc and .h files pass cpplint.py.
charujain9893e252017-09-14 13:33:22 +0200268 This check is based on CheckChangeLintsClean in
kjellander@webrtc.org51198f12012-02-21 17:53:46 +0000269 depot_tools/presubmit_canned_checks.py but has less filters and only checks
270 added files."""
271 result = []
272
273 # Initialize cpplint.
274 import cpplint
275 # Access to a protected member _XX of a client class
276 # pylint: disable=W0212
277 cpplint._cpplint_state.ResetErrorCounts()
278
jbauchc4e3ead2016-02-19 00:25:55 -0800279 lint_filters = cpplint._Filters()
280 lint_filters.extend(BLACKLIST_LINT_FILTERS)
281 cpplint._SetFilters(','.join(lint_filters))
282
oprypin2aa463f2017-03-23 03:17:02 -0700283 # Create a platform independent blacklist for cpplint.
284 blacklist_paths = [input_api.os_path.join(*path.split('/'))
285 for path in CPPLINT_BLACKLIST]
kjellander@webrtc.org0fcaf992015-11-26 15:24:52 +0100286
kjellander@webrtc.org51198f12012-02-21 17:53:46 +0000287 # Use the strictest verbosity level for cpplint.py (level 1) which is the
oprypin2aa463f2017-03-23 03:17:02 -0700288 # default when running cpplint.py from command line. To make it possible to
289 # work with not-yet-converted code, we're only applying it to new (or
290 # moved/renamed) files and files not listed in CPPLINT_BLACKLIST.
kjellander@webrtc.org51198f12012-02-21 17:53:46 +0000291 verbosity_level = 1
292 files = []
293 for f in input_api.AffectedSourceFiles(source_file_filter):
Henrik Kjellander57e5fd22015-05-25 12:55:39 +0200294 # Note that moved/renamed files also count as added.
charujain9893e252017-09-14 13:33:22 +0200295 if f.Action() == 'A' or not IsLintBlacklisted(blacklist_paths,
Artem Titove92675b2018-05-22 10:21:27 +0200296 f.LocalPath()):
kjellander@webrtc.org51198f12012-02-21 17:53:46 +0000297 files.append(f.AbsoluteLocalPath())
mflodman@webrtc.org2a452092012-07-01 05:55:23 +0000298
kjellander@webrtc.org51198f12012-02-21 17:53:46 +0000299 for file_name in files:
300 cpplint.ProcessFile(file_name, verbosity_level)
301
302 if cpplint._cpplint_state.error_count > 0:
303 if input_api.is_committing:
oprypin8e58d652017-03-21 07:52:41 -0700304 res_type = output_api.PresubmitError
kjellander@webrtc.org51198f12012-02-21 17:53:46 +0000305 else:
306 res_type = output_api.PresubmitPromptWarning
307 result = [res_type('Changelist failed cpplint.py check.')]
308
309 return result
310
Artem Titove92675b2018-05-22 10:21:27 +0200311
charujain9893e252017-09-14 13:33:22 +0200312def CheckNoSourcesAbove(input_api, gn_files, output_api):
ehmaldonado5b1ba082016-09-02 05:51:08 -0700313 # Disallow referencing source files with paths above the GN file location.
314 source_pattern = input_api.re.compile(r' +sources \+?= \[(.*?)\]',
315 re.MULTILINE | re.DOTALL)
316 file_pattern = input_api.re.compile(r'"((\.\./.*?)|(//.*?))"')
317 violating_gn_files = set()
318 violating_source_entries = []
319 for gn_file in gn_files:
320 contents = input_api.ReadFile(gn_file)
321 for source_block_match in source_pattern.finditer(contents):
322 # Find all source list entries starting with ../ in the source block
323 # (exclude overrides entries).
324 for file_list_match in file_pattern.finditer(source_block_match.group(1)):
325 source_file = file_list_match.group(1)
326 if 'overrides/' not in source_file:
327 violating_source_entries.append(source_file)
328 violating_gn_files.add(gn_file)
329 if violating_gn_files:
330 return [output_api.PresubmitError(
331 'Referencing source files above the directory of the GN file is not '
Henrik Kjellanderb4af3d62016-11-16 20:11:29 +0100332 'allowed. Please introduce new GN targets in the proper location '
333 'instead.\n'
ehmaldonado5b1ba082016-09-02 05:51:08 -0700334 'Invalid source entries:\n'
335 '%s\n'
336 'Violating GN files:' % '\n'.join(violating_source_entries),
337 items=violating_gn_files)]
338 return []
339
Artem Titove92675b2018-05-22 10:21:27 +0200340
Mirko Bonadei4dc4e252017-09-19 13:49:16 +0200341def CheckNoMixingSources(input_api, gn_files, output_api):
342 """Disallow mixing C, C++ and Obj-C/Obj-C++ in the same target.
343
344 See bugs.webrtc.org/7743 for more context.
345 """
Artem Titove92675b2018-05-22 10:21:27 +0200346
Mirko Bonadei4dc4e252017-09-19 13:49:16 +0200347 def _MoreThanOneSourceUsed(*sources_lists):
348 sources_used = 0
349 for source_list in sources_lists:
350 if len(source_list):
351 sources_used += 1
352 return sources_used > 1
353
354 errors = defaultdict(lambda: [])
kjellander7439f972016-12-05 22:47:46 -0800355 for gn_file in gn_files:
Mirko Bonadei4dc4e252017-09-19 13:49:16 +0200356 gn_file_content = input_api.ReadFile(gn_file)
357 for target_match in TARGET_RE.finditer(gn_file_content):
358 # list_of_sources is a list of tuples of the form
359 # (c_files, cc_files, objc_files) that keeps track of all the sources
360 # defined in a target. A GN target can have more that on definition of
361 # sources (since it supports if/else statements).
362 # E.g.:
363 # rtc_static_library("foo") {
364 # if (is_win) {
365 # sources = [ "foo.cc" ]
366 # } else {
367 # sources = [ "foo.mm" ]
368 # }
369 # }
370 # This is allowed and the presubmit check should support this case.
371 list_of_sources = []
kjellander7439f972016-12-05 22:47:46 -0800372 c_files = []
373 cc_files = []
Mirko Bonadei4dc4e252017-09-19 13:49:16 +0200374 objc_files = []
375 target_name = target_match.group('target_name')
376 target_contents = target_match.group('target_contents')
377 for sources_match in SOURCES_RE.finditer(target_contents):
378 if '+=' not in sources_match.group(0):
379 if c_files or cc_files or objc_files:
380 list_of_sources.append((c_files, cc_files, objc_files))
381 c_files = []
382 cc_files = []
383 objc_files = []
384 for file_match in FILE_PATH_RE.finditer(sources_match.group(1)):
385 file_path = file_match.group('file_path')
386 extension = file_match.group('extension')
387 if extension == '.c':
388 c_files.append(file_path + extension)
389 if extension == '.cc':
390 cc_files.append(file_path + extension)
391 if extension in ['.m', '.mm']:
392 objc_files.append(file_path + extension)
393 list_of_sources.append((c_files, cc_files, objc_files))
394 for c_files_list, cc_files_list, objc_files_list in list_of_sources:
395 if _MoreThanOneSourceUsed(c_files_list, cc_files_list, objc_files_list):
396 all_sources = sorted(c_files_list + cc_files_list + objc_files_list)
397 errors[gn_file.LocalPath()].append((target_name, all_sources))
398 if errors:
kjellander7439f972016-12-05 22:47:46 -0800399 return [output_api.PresubmitError(
Mirko Bonadei4dc4e252017-09-19 13:49:16 +0200400 'GN targets cannot mix .c, .cc and .m (or .mm) source files.\n'
401 'Please create a separate target for each collection of sources.\n'
kjellander7439f972016-12-05 22:47:46 -0800402 'Mixed sources: \n'
403 '%s\n'
Mirko Bonadei4dc4e252017-09-19 13:49:16 +0200404 'Violating GN files:\n%s\n' % (json.dumps(errors, indent=2),
405 '\n'.join(errors.keys())))]
kjellander7439f972016-12-05 22:47:46 -0800406 return []
407
Artem Titove92675b2018-05-22 10:21:27 +0200408
charujain9893e252017-09-14 13:33:22 +0200409def CheckNoPackageBoundaryViolations(input_api, gn_files, output_api):
ehmaldonado4fb97462017-01-30 05:27:22 -0800410 cwd = input_api.PresubmitLocalPath()
Oleh Prypin2f33a562017-10-04 20:17:54 +0200411 with _AddToPath(input_api.os_path.join(
412 cwd, 'tools_webrtc', 'presubmit_checks_lib')):
413 from check_package_boundaries import CheckPackageBoundaries
414 build_files = [os.path.join(cwd, gn_file.LocalPath()) for gn_file in gn_files]
415 errors = CheckPackageBoundaries(cwd, build_files)[:5]
416 if errors:
ehmaldonado4fb97462017-01-30 05:27:22 -0800417 return [output_api.PresubmitError(
Oleh Prypin2f33a562017-10-04 20:17:54 +0200418 'There are package boundary violations in the following GN files:',
419 long_text='\n\n'.join(str(err) for err in errors))]
ehmaldonado4fb97462017-01-30 05:27:22 -0800420 return []
421
Mirko Bonadeia51bbd82018-03-08 16:15:45 +0100422
Mirko Bonadeif0e0d752018-07-04 08:48:18 +0200423def _ReportFileAndLine(filename, line_num):
Mirko Bonadeia51bbd82018-03-08 16:15:45 +0100424 """Default error formatter for _FindNewViolationsOfRule."""
425 return '%s (line %s)' % (filename, line_num)
426
427
Mirko Bonadeif0e0d752018-07-04 08:48:18 +0200428def CheckNoWarningSuppressionFlagsAreAdded(gn_files, input_api, output_api,
429 error_formatter=_ReportFileAndLine):
430 """Make sure that warning suppression flags are not added wihtout a reason."""
431 msg = ('Usage of //build/config/clang:extra_warnings is discouraged '
432 'in WebRTC.\n'
433 'If you are not adding this code (e.g. you are just moving '
434 'existing code) or you want to add an exception,\n'
435 'you can add a comment on the line that causes the problem:\n\n'
436 '"-Wno-odr" # no-presubmit-check TODO(bugs.webrtc.org/BUG_ID)\n'
437 '\n'
438 'Affected files:\n')
439 errors = [] # 2-element tuples with (file, line number)
440 clang_warn_re = input_api.re.compile(r'//build/config/clang:extra_warnings')
441 no_presubmit_re = input_api.re.compile(
442 r'# no-presubmit-check TODO\(bugs\.webrtc\.org/\d+\)')
443 for f in gn_files:
444 for line_num, line in f.ChangedContents():
445 if clang_warn_re.search(line) and not no_presubmit_re.search(line):
446 errors.append(error_formatter(f.LocalPath(), line_num))
447 if errors:
448 return [output_api.PresubmitError(msg, errors)]
449 return []
450
Mirko Bonadei9ce800d2019-02-05 16:48:13 +0100451
452def CheckNoTestCaseUsageIsAdded(input_api, output_api, source_file_filter,
453 error_formatter=_ReportFileAndLine):
454 error_msg = ('Usage of legacy GoogleTest API detected!\nPlease use the '
455 'new API: https://github.com/google/googletest/blob/master/'
456 'googletest/docs/primer.md#beware-of-the-nomenclature.\n'
457 'Affected files:\n')
458 errors = [] # 2-element tuples with (file, line number)
459 test_case_re = input_api.re.compile(r'TEST_CASE')
460 file_filter = lambda f: (source_file_filter(f)
461 and f.LocalPath().endswith('.cc'))
462 for f in input_api.AffectedSourceFiles(file_filter):
463 for line_num, line in f.ChangedContents():
464 if test_case_re.search(line):
465 errors.append(error_formatter(f.LocalPath(), line_num))
466 if errors:
467 return [output_api.PresubmitError(error_msg, errors)]
468 return []
469
470
Mirko Bonadeia51bbd82018-03-08 16:15:45 +0100471def CheckNoStreamUsageIsAdded(input_api, output_api,
Artem Titov739351d2018-05-11 12:21:36 +0200472 source_file_filter,
Mirko Bonadeif0e0d752018-07-04 08:48:18 +0200473 error_formatter=_ReportFileAndLine):
Mirko Bonadeia51bbd82018-03-08 16:15:45 +0100474 """Make sure that no more dependencies on stringstream are added."""
475 error_msg = ('Usage of <sstream>, <istream> and <ostream> in WebRTC is '
476 'deprecated.\n'
477 'This includes the following types:\n'
478 'std::istringstream, std::ostringstream, std::wistringstream, '
479 'std::wostringstream,\n'
480 'std::wstringstream, std::ostream, std::wostream, std::istream,'
481 'std::wistream,\n'
482 'std::iostream, std::wiostream.\n'
483 'If you are not adding this code (e.g. you are just moving '
484 'existing code),\n'
485 'you can add a comment on the line that causes the problem:\n\n'
486 '#include <sstream> // no-presubmit-check TODO(webrtc:8982)\n'
487 'std::ostream& F() { // no-presubmit-check TODO(webrtc:8982)\n'
488 '\n'
Karl Wibergebd01e82018-03-14 15:08:39 +0100489 'If you are adding new code, consider using '
490 'rtc::SimpleStringBuilder\n'
491 '(in rtc_base/strings/string_builder.h).\n'
Mirko Bonadeia51bbd82018-03-08 16:15:45 +0100492 'Affected files:\n')
493 errors = [] # 2-element tuples with (file, line number)
494 include_re = input_api.re.compile(r'#include <(i|o|s)stream>')
495 usage_re = input_api.re.compile(r'std::(w|i|o|io|wi|wo|wio)(string)*stream')
496 no_presubmit_re = input_api.re.compile(
Jonas Olsson74395342018-04-03 12:22:07 +0200497 r'// no-presubmit-check TODO\(webrtc:8982\)')
Artem Titova04d1402018-05-11 11:23:00 +0200498 file_filter = lambda x: (input_api.FilterSourceFile(x)
499 and source_file_filter(x))
Mirko Bonadei571791a2019-05-07 14:08:05 +0200500
501 def _IsException(file_path):
502 is_test = any(file_path.endswith(x) for x in ['_test.cc', '_tests.cc',
503 '_unittest.cc',
504 '_unittests.cc'])
Patrik Höglund2ea27962020-01-13 15:10:40 +0100505 return (file_path.startswith('examples') or
506 file_path.startswith('test') or
507 is_test)
508
Mirko Bonadei571791a2019-05-07 14:08:05 +0200509
Artem Titova04d1402018-05-11 11:23:00 +0200510 for f in input_api.AffectedSourceFiles(file_filter):
Mirko Bonadei571791a2019-05-07 14:08:05 +0200511 # Usage of stringstream is allowed under examples/ and in tests.
512 if f.LocalPath() == 'PRESUBMIT.py' or _IsException(f.LocalPath()):
Mirko Bonadeid2c83322018-03-19 10:31:47 +0000513 continue
514 for line_num, line in f.ChangedContents():
515 if ((include_re.search(line) or usage_re.search(line))
516 and not no_presubmit_re.search(line)):
517 errors.append(error_formatter(f.LocalPath(), line_num))
Mirko Bonadeia51bbd82018-03-08 16:15:45 +0100518 if errors:
519 return [output_api.PresubmitError(error_msg, errors)]
520 return []
521
Artem Titove92675b2018-05-22 10:21:27 +0200522
Mirko Bonadeia05d47e2018-05-09 11:03:38 +0200523def CheckPublicDepsIsNotUsed(gn_files, input_api, output_api):
524 """Checks that public_deps is not used without a good reason."""
Mirko Bonadei5c1ad592017-12-12 11:52:27 +0100525 result = []
Mirko Bonadeia05d47e2018-05-09 11:03:38 +0200526 no_presubmit_check_re = input_api.re.compile(
Joe Chen0b3a6e32019-12-26 23:01:42 -0800527 r'# no-presubmit-check TODO\(webrtc:\d+\)')
Mirko Bonadeia05d47e2018-05-09 11:03:38 +0200528 error_msg = ('public_deps is not recommended in WebRTC BUILD.gn files '
529 'because it doesn\'t map well to downstream build systems.\n'
530 'Used in: %s (line %d).\n'
531 'If you are not adding this code (e.g. you are just moving '
Patrik Höglund81c7a602020-01-30 11:32:33 +0100532 'existing code) or you have a good reason, you can add this '
533 'comment (verbatim) on the line that causes the problem:\n\n'
Mirko Bonadeia05d47e2018-05-09 11:03:38 +0200534 'public_deps = [ # no-presubmit-check TODO(webrtc:8603)\n')
Mirko Bonadei5c1ad592017-12-12 11:52:27 +0100535 for affected_file in gn_files:
536 for (line_number, affected_line) in affected_file.ChangedContents():
Patrik Höglund81c7a602020-01-30 11:32:33 +0100537 if 'public_deps' in affected_line:
538 surpressed = no_presubmit_check_re.search(affected_line)
539 if not surpressed:
540 result.append(
541 output_api.PresubmitError(error_msg % (affected_file.LocalPath(),
542 line_number)))
Mirko Bonadei5c1ad592017-12-12 11:52:27 +0100543 return result
544
Artem Titove92675b2018-05-22 10:21:27 +0200545
Mirko Bonadei05691dd2019-10-22 07:34:24 -0700546def CheckCheckIncludesIsNotUsed(gn_files, input_api, output_api):
Patrik Höglund6f491062018-01-11 12:04:23 +0100547 result = []
548 error_msg = ('check_includes overrides are not allowed since it can cause '
549 'incorrect dependencies to form. It effectively means that your '
550 'module can include any .h file without depending on its '
551 'corresponding target. There are some exceptional cases when '
Mirko Bonadei05691dd2019-10-22 07:34:24 -0700552 'this is allowed: if so, get approval from a .gn owner in the '
Patrik Höglund6f491062018-01-11 12:04:23 +0100553 'root OWNERS file.\n'
554 'Used in: %s (line %d).')
Mirko Bonadei05691dd2019-10-22 07:34:24 -0700555 no_presubmit_re = input_api.re.compile(
556 r'# no-presubmit-check TODO\(bugs\.webrtc\.org/\d+\)')
Patrik Höglund6f491062018-01-11 12:04:23 +0100557 for affected_file in gn_files:
558 for (line_number, affected_line) in affected_file.ChangedContents():
Mirko Bonadei05691dd2019-10-22 07:34:24 -0700559 if ('check_includes' in affected_line
560 and not no_presubmit_re.search(affected_line)):
Patrik Höglund6f491062018-01-11 12:04:23 +0100561 result.append(
562 output_api.PresubmitError(error_msg % (affected_file.LocalPath(),
563 line_number)))
564 return result
565
Artem Titove92675b2018-05-22 10:21:27 +0200566
Mirko Bonadeif0e0d752018-07-04 08:48:18 +0200567def CheckGnChanges(input_api, output_api):
Artem Titova04d1402018-05-11 11:23:00 +0200568 file_filter = lambda x: (input_api.FilterSourceFile(
Oleh Prypinafe01652017-10-04 15:56:08 +0200569 x, white_list=(r'.+\.(gn|gni)$',),
Mirko Bonadeif0e0d752018-07-04 08:48:18 +0200570 black_list=(r'.*/presubmit_checks_lib/testdata/.*',)))
ehmaldonado5b1ba082016-09-02 05:51:08 -0700571
572 gn_files = []
Artem Titova04d1402018-05-11 11:23:00 +0200573 for f in input_api.AffectedSourceFiles(file_filter):
Mirko Bonadei92ea95e2017-09-15 06:47:31 +0200574 gn_files.append(f)
ehmaldonado5b1ba082016-09-02 05:51:08 -0700575
576 result = []
577 if gn_files:
charujain9893e252017-09-14 13:33:22 +0200578 result.extend(CheckNoSourcesAbove(input_api, gn_files, output_api))
Mirko Bonadei4dc4e252017-09-19 13:49:16 +0200579 result.extend(CheckNoMixingSources(input_api, gn_files, output_api))
580 result.extend(CheckNoPackageBoundaryViolations(input_api, gn_files,
581 output_api))
Mirko Bonadeia05d47e2018-05-09 11:03:38 +0200582 result.extend(CheckPublicDepsIsNotUsed(gn_files, input_api, output_api))
Mirko Bonadei05691dd2019-10-22 07:34:24 -0700583 result.extend(CheckCheckIncludesIsNotUsed(gn_files, input_api, output_api))
Mirko Bonadeif0e0d752018-07-04 08:48:18 +0200584 result.extend(CheckNoWarningSuppressionFlagsAreAdded(gn_files, input_api,
585 output_api))
ehmaldonado5b1ba082016-09-02 05:51:08 -0700586 return result
587
Artem Titove92675b2018-05-22 10:21:27 +0200588
Oleh Prypin920b6532017-10-05 11:28:51 +0200589def CheckGnGen(input_api, output_api):
590 """Runs `gn gen --check` with default args to detect mismatches between
591 #includes and dependencies in the BUILD.gn files, as well as general build
592 errors.
593 """
594 with _AddToPath(input_api.os_path.join(
595 input_api.PresubmitLocalPath(), 'tools_webrtc', 'presubmit_checks_lib')):
Yves Gerey546ee612019-02-26 17:04:16 +0100596 from build_helpers import RunGnCheck
Mirko Bonadeid8665442018-09-04 12:17:27 +0200597 errors = RunGnCheck(FindSrcDirPath(input_api.PresubmitLocalPath()))[:5]
Oleh Prypin920b6532017-10-05 11:28:51 +0200598 if errors:
599 return [output_api.PresubmitPromptWarning(
600 'Some #includes do not match the build dependency graph. Please run:\n'
601 ' gn gen --check <out_dir>',
602 long_text='\n\n'.join(errors))]
603 return []
604
Artem Titove92675b2018-05-22 10:21:27 +0200605
Artem Titova04d1402018-05-11 11:23:00 +0200606def CheckUnwantedDependencies(input_api, output_api, source_file_filter):
kjellander@webrtc.org3bd41562014-09-01 11:06:37 +0000607 """Runs checkdeps on #include statements added in this
608 change. Breaking - rules is an error, breaking ! rules is a
609 warning.
610 """
611 # Copied from Chromium's src/PRESUBMIT.py.
612
613 # We need to wait until we have an input_api object and use this
614 # roundabout construct to import checkdeps because this file is
615 # eval-ed and thus doesn't have __file__.
Mirko Bonadeid8665442018-09-04 12:17:27 +0200616 src_path = FindSrcDirPath(input_api.PresubmitLocalPath())
617 checkdeps_path = input_api.os_path.join(src_path, 'buildtools', 'checkdeps')
Oleh Prypin2f33a562017-10-04 20:17:54 +0200618 if not os.path.exists(checkdeps_path):
619 return [output_api.PresubmitError(
620 'Cannot find checkdeps at %s\nHave you run "gclient sync" to '
621 'download all the DEPS entries?' % checkdeps_path)]
622 with _AddToPath(checkdeps_path):
kjellander@webrtc.org3bd41562014-09-01 11:06:37 +0000623 import checkdeps
624 from cpp_checker import CppChecker
625 from rules import Rule
kjellander@webrtc.org3bd41562014-09-01 11:06:37 +0000626
627 added_includes = []
Artem Titova04d1402018-05-11 11:23:00 +0200628 for f in input_api.AffectedFiles(file_filter=source_file_filter):
kjellander@webrtc.org3bd41562014-09-01 11:06:37 +0000629 if not CppChecker.IsCppFile(f.LocalPath()):
630 continue
631
Henrik Kjellander57e5fd22015-05-25 12:55:39 +0200632 changed_lines = [line for _, line in f.ChangedContents()]
kjellander@webrtc.org3bd41562014-09-01 11:06:37 +0000633 added_includes.append([f.LocalPath(), changed_lines])
634
635 deps_checker = checkdeps.DepsChecker(input_api.PresubmitLocalPath())
636
637 error_descriptions = []
638 warning_descriptions = []
639 for path, rule_type, rule_description in deps_checker.CheckAddedCppIncludes(
640 added_includes):
641 description_with_path = '%s\n %s' % (path, rule_description)
642 if rule_type == Rule.DISALLOW:
643 error_descriptions.append(description_with_path)
644 else:
645 warning_descriptions.append(description_with_path)
646
647 results = []
648 if error_descriptions:
649 results.append(output_api.PresubmitError(
kjellandera7066a32017-03-23 03:47:05 -0700650 'You added one or more #includes that violate checkdeps rules.\n'
651 'Check that the DEPS files in these locations contain valid rules.\n'
652 'See https://cs.chromium.org/chromium/src/buildtools/checkdeps/ for '
653 'more details about checkdeps.',
kjellander@webrtc.org3bd41562014-09-01 11:06:37 +0000654 error_descriptions))
655 if warning_descriptions:
656 results.append(output_api.PresubmitPromptOrNotify(
657 'You added one or more #includes of files that are temporarily\n'
658 'allowed but being removed. Can you avoid introducing the\n'
kjellandera7066a32017-03-23 03:47:05 -0700659 '#include? See relevant DEPS file(s) for details and contacts.\n'
660 'See https://cs.chromium.org/chromium/src/buildtools/checkdeps/ for '
661 'more details about checkdeps.',
kjellander@webrtc.org3bd41562014-09-01 11:06:37 +0000662 warning_descriptions))
663 return results
664
Artem Titove92675b2018-05-22 10:21:27 +0200665
charujain9893e252017-09-14 13:33:22 +0200666def CheckCommitMessageBugEntry(input_api, output_api):
667 """Check that bug entries are well-formed in commit message."""
668 bogus_bug_msg = (
Mirko Bonadei61880182017-10-12 15:12:35 +0200669 'Bogus Bug entry: %s. Please specify the issue tracker prefix and the '
charujain9893e252017-09-14 13:33:22 +0200670 'issue number, separated by a colon, e.g. webrtc:123 or chromium:12345.')
671 results = []
Mirko Bonadei61880182017-10-12 15:12:35 +0200672 for bug in input_api.change.BugsFromDescription():
charujain9893e252017-09-14 13:33:22 +0200673 bug = bug.strip()
674 if bug.lower() == 'none':
675 continue
charujain81a58c72017-09-25 13:25:45 +0200676 if 'b/' not in bug and ':' not in bug:
charujain9893e252017-09-14 13:33:22 +0200677 try:
678 if int(bug) > 100000:
679 # Rough indicator for current chromium bugs.
680 prefix_guess = 'chromium'
681 else:
682 prefix_guess = 'webrtc'
Mirko Bonadei61880182017-10-12 15:12:35 +0200683 results.append('Bug entry requires issue tracker prefix, e.g. %s:%s' %
charujain9893e252017-09-14 13:33:22 +0200684 (prefix_guess, bug))
685 except ValueError:
686 results.append(bogus_bug_msg % bug)
charujain81a58c72017-09-25 13:25:45 +0200687 elif not (re.match(r'\w+:\d+', bug) or re.match(r'b/\d+', bug)):
charujain9893e252017-09-14 13:33:22 +0200688 results.append(bogus_bug_msg % bug)
689 return [output_api.PresubmitError(r) for r in results]
690
Artem Titove92675b2018-05-22 10:21:27 +0200691
charujain9893e252017-09-14 13:33:22 +0200692def CheckChangeHasBugField(input_api, output_api):
Mirko Bonadei61880182017-10-12 15:12:35 +0200693 """Requires that the changelist is associated with a bug.
kjellanderd1e26a92016-09-19 08:11:16 -0700694
695 This check is stricter than the one in depot_tools/presubmit_canned_checks.py
Mirko Bonadei61880182017-10-12 15:12:35 +0200696 since it fails the presubmit if the bug field is missing or doesn't contain
kjellanderd1e26a92016-09-19 08:11:16 -0700697 a bug reference.
Mirko Bonadei61880182017-10-12 15:12:35 +0200698
699 This supports both 'BUG=' and 'Bug:' since we are in the process of migrating
700 to Gerrit and it encourages the usage of 'Bug:'.
kjellanderd1e26a92016-09-19 08:11:16 -0700701 """
Mirko Bonadei61880182017-10-12 15:12:35 +0200702 if input_api.change.BugsFromDescription():
kjellanderd1e26a92016-09-19 08:11:16 -0700703 return []
704 else:
705 return [output_api.PresubmitError(
Mirko Bonadei61880182017-10-12 15:12:35 +0200706 'The "Bug: [bug number]" footer is mandatory. Please create a bug and '
kjellanderd1e26a92016-09-19 08:11:16 -0700707 'reference it using either of:\n'
Mirko Bonadei61880182017-10-12 15:12:35 +0200708 ' * https://bugs.webrtc.org - reference it using Bug: webrtc:XXXX\n'
709 ' * https://crbug.com - reference it using Bug: chromium:XXXXXX')]
kjellander@webrtc.orge4158642014-08-06 09:11:18 +0000710
Artem Titove92675b2018-05-22 10:21:27 +0200711
Artem Titova04d1402018-05-11 11:23:00 +0200712def CheckJSONParseErrors(input_api, output_api, source_file_filter):
kjellander569cf942016-02-11 05:02:59 -0800713 """Check that JSON files do not contain syntax errors."""
714
715 def FilterFile(affected_file):
Artem Titova04d1402018-05-11 11:23:00 +0200716 return (input_api.os_path.splitext(affected_file.LocalPath())[1] == '.json'
717 and source_file_filter(affected_file))
kjellander569cf942016-02-11 05:02:59 -0800718
719 def GetJSONParseError(input_api, filename):
720 try:
721 contents = input_api.ReadFile(filename)
722 input_api.json.loads(contents)
723 except ValueError as e:
724 return e
725 return None
726
727 results = []
728 for affected_file in input_api.AffectedFiles(
729 file_filter=FilterFile, include_deletes=False):
730 parse_error = GetJSONParseError(input_api,
731 affected_file.AbsoluteLocalPath())
732 if parse_error:
733 results.append(output_api.PresubmitError('%s could not be parsed: %s' %
Artem Titove92675b2018-05-22 10:21:27 +0200734 (affected_file.LocalPath(),
735 parse_error)))
kjellander569cf942016-02-11 05:02:59 -0800736 return results
737
738
charujain9893e252017-09-14 13:33:22 +0200739def RunPythonTests(input_api, output_api):
kjellanderc88b5d52017-04-05 06:42:43 -0700740 def Join(*args):
Henrik Kjellander8d3ad822015-05-26 19:52:05 +0200741 return input_api.os_path.join(input_api.PresubmitLocalPath(), *args)
742
743 test_directories = [
Edward Lemur6d01f6d2017-09-14 17:02:01 +0200744 input_api.PresubmitLocalPath(),
Mirko Bonadei92ea95e2017-09-15 06:47:31 +0200745 Join('rtc_tools', 'py_event_log_analyzer'),
Mirko Bonadei92ea95e2017-09-15 06:47:31 +0200746 Join('audio', 'test', 'unittests'),
ehmaldonado4fb97462017-01-30 05:27:22 -0800747 ] + [
Henrik Kjellander90fd7d82017-05-09 08:30:10 +0200748 root for root, _, files in os.walk(Join('tools_webrtc'))
ehmaldonado4fb97462017-01-30 05:27:22 -0800749 if any(f.endswith('_test.py') for f in files)
Henrik Kjellander8d3ad822015-05-26 19:52:05 +0200750 ]
751
752 tests = []
753 for directory in test_directories:
754 tests.extend(
755 input_api.canned_checks.GetUnitTestsInDirectory(
756 input_api,
757 output_api,
758 directory,
759 whitelist=[r'.+_test\.py$']))
760 return input_api.RunTests(tests, parallel=True)
761
762
Artem Titova04d1402018-05-11 11:23:00 +0200763def CheckUsageOfGoogleProtobufNamespace(input_api, output_api,
764 source_file_filter):
mbonadei38415b22017-04-07 05:38:01 -0700765 """Checks that the namespace google::protobuf has not been used."""
766 files = []
767 pattern = input_api.re.compile(r'google::protobuf')
Mirko Bonadei92ea95e2017-09-15 06:47:31 +0200768 proto_utils_path = os.path.join('rtc_base', 'protobuf_utils.h')
Artem Titova04d1402018-05-11 11:23:00 +0200769 file_filter = lambda x: (input_api.FilterSourceFile(x)
770 and source_file_filter(x))
771 for f in input_api.AffectedSourceFiles(file_filter):
mbonadei38415b22017-04-07 05:38:01 -0700772 if f.LocalPath() in [proto_utils_path, 'PRESUBMIT.py']:
773 continue
774 contents = input_api.ReadFile(f)
775 if pattern.search(contents):
776 files.append(f)
777
778 if files:
779 return [output_api.PresubmitError(
780 'Please avoid to use namespace `google::protobuf` directly.\n'
781 'Add a using directive in `%s` and include that header instead.'
782 % proto_utils_path, files)]
783 return []
784
785
Mirko Bonadei92ea95e2017-09-15 06:47:31 +0200786def _LicenseHeader(input_api):
787 """Returns the license header regexp."""
788 # Accept any year number from 2003 to the current year
789 current_year = int(input_api.time.strftime('%Y'))
790 allowed_years = (str(s) for s in reversed(xrange(2003, current_year + 1)))
791 years_re = '(' + '|'.join(allowed_years) + ')'
792 license_header = (
793 r'.*? Copyright( \(c\))? %(year)s The WebRTC [Pp]roject [Aa]uthors\. '
794 r'All [Rr]ights [Rr]eserved\.\n'
795 r'.*?\n'
796 r'.*? Use of this source code is governed by a BSD-style license\n'
797 r'.*? that can be found in the LICENSE file in the root of the source\n'
798 r'.*? tree\. An additional intellectual property rights grant can be '
799 r'found\n'
800 r'.*? in the file PATENTS\. All contributing project authors may\n'
801 r'.*? be found in the AUTHORS file in the root of the source tree\.\n'
802 ) % {
803 'year': years_re,
804 }
805 return license_header
806
807
charujain9893e252017-09-14 13:33:22 +0200808def CommonChecks(input_api, output_api):
andrew@webrtc.org53df1362012-01-26 21:24:23 +0000809 """Checks common to both upload and commit."""
niklase@google.comda159d62011-05-30 11:51:34 +0000810 results = []
tkchin42f580e2015-11-26 23:18:23 -0800811 # Filter out files that are in objc or ios dirs from being cpplint-ed since
812 # they do not follow C++ lint rules.
813 black_list = input_api.DEFAULT_BLACK_LIST + (
814 r".*\bobjc[\\\/].*",
Kári Tristan Helgason3fa35172016-09-09 08:55:05 +0000815 r".*objc\.[hcm]+$",
tkchin42f580e2015-11-26 23:18:23 -0800816 )
817 source_file_filter = lambda x: input_api.FilterSourceFile(x, None, black_list)
charujain9893e252017-09-14 13:33:22 +0200818 results.extend(CheckApprovedFilesLintClean(
tkchin42f580e2015-11-26 23:18:23 -0800819 input_api, output_api, source_file_filter))
Mirko Bonadei92ea95e2017-09-15 06:47:31 +0200820 results.extend(input_api.canned_checks.CheckLicense(
821 input_api, output_api, _LicenseHeader(input_api)))
phoglund@webrtc.org5d3713932013-03-07 09:59:43 +0000822 results.extend(input_api.canned_checks.RunPylint(input_api, output_api,
kjellander@webrtc.org177567c2016-12-22 10:40:28 +0100823 black_list=(r'^base[\\\/].*\.py$',
Henrik Kjellander14771ac2015-06-02 13:10:04 +0200824 r'^build[\\\/].*\.py$',
825 r'^buildtools[\\\/].*\.py$',
kjellander38c65c82017-04-12 22:43:38 -0700826 r'^infra[\\\/].*\.py$',
Henrik Kjellander0779e8f2016-12-22 12:01:17 +0100827 r'^ios[\\\/].*\.py$',
Henrik Kjellander14771ac2015-06-02 13:10:04 +0200828 r'^out.*[\\\/].*\.py$',
829 r'^testing[\\\/].*\.py$',
830 r'^third_party[\\\/].*\.py$',
kjellander@webrtc.org177567c2016-12-22 10:40:28 +0100831 r'^tools[\\\/].*\.py$',
kjellanderafd54942016-12-17 12:21:39 -0800832 # TODO(phoglund): should arguably be checked.
Henrik Kjellander90fd7d82017-05-09 08:30:10 +0200833 r'^tools_webrtc[\\\/]mb[\\\/].*\.py$',
Henrik Kjellander14771ac2015-06-02 13:10:04 +0200834 r'^xcodebuild.*[\\\/].*\.py$',),
Henrik Kjellander57e5fd22015-05-25 12:55:39 +0200835 pylintrc='pylintrc'))
kjellander569cf942016-02-11 05:02:59 -0800836
nisse3d21e232016-09-02 03:07:06 -0700837 # TODO(nisse): talk/ is no more, so make below checks simpler?
Henrik Kjellander57e5fd22015-05-25 12:55:39 +0200838 # WebRTC can't use the presubmit_canned_checks.PanProjectChecks function since
839 # we need to have different license checks in talk/ and webrtc/ directories.
840 # Instead, hand-picked checks are included below.
Henrik Kjellander63224672015-09-08 08:03:56 +0200841
tkchin3cd9a302016-06-08 12:40:28 -0700842 # .m and .mm files are ObjC files. For simplicity we will consider .h files in
843 # ObjC subdirectories ObjC headers.
844 objc_filter_list = (r'.+\.m$', r'.+\.mm$', r'.+objc\/.+\.h$')
Henrik Kjellanderb4af3d62016-11-16 20:11:29 +0100845 # Skip long-lines check for DEPS and GN files.
846 build_file_filter_list = (r'.+\.gn$', r'.+\.gni$', 'DEPS')
Artem Titova04d1402018-05-11 11:23:00 +0200847 # Also we will skip most checks for third_party directory.
Artem Titov42f0d782018-06-27 13:23:17 +0200848 third_party_filter_list = (r'^third_party[\\\/].+',)
tkchin3cd9a302016-06-08 12:40:28 -0700849 eighty_char_sources = lambda x: input_api.FilterSourceFile(x,
Artem Titova04d1402018-05-11 11:23:00 +0200850 black_list=build_file_filter_list + objc_filter_list +
851 third_party_filter_list)
tkchin3cd9a302016-06-08 12:40:28 -0700852 hundred_char_sources = lambda x: input_api.FilterSourceFile(x,
853 white_list=objc_filter_list)
Artem Titove92675b2018-05-22 10:21:27 +0200854 non_third_party_sources = lambda x: input_api.FilterSourceFile(x,
855 black_list=third_party_filter_list)
856
andrew@webrtc.org2442de12012-01-23 17:45:41 +0000857 results.extend(input_api.canned_checks.CheckLongLines(
tkchin3cd9a302016-06-08 12:40:28 -0700858 input_api, output_api, maxlen=80, source_file_filter=eighty_char_sources))
859 results.extend(input_api.canned_checks.CheckLongLines(
860 input_api, output_api, maxlen=100,
861 source_file_filter=hundred_char_sources))
andrew@webrtc.org2442de12012-01-23 17:45:41 +0000862 results.extend(input_api.canned_checks.CheckChangeHasNoTabs(
Artem Titova04d1402018-05-11 11:23:00 +0200863 input_api, output_api, source_file_filter=non_third_party_sources))
andrew@webrtc.org53df1362012-01-26 21:24:23 +0000864 results.extend(input_api.canned_checks.CheckChangeHasNoStrayWhitespace(
Artem Titova04d1402018-05-11 11:23:00 +0200865 input_api, output_api, source_file_filter=non_third_party_sources))
kjellandere5dc62a2016-12-14 00:16:21 -0800866 results.extend(input_api.canned_checks.CheckAuthorizedAuthor(
Oleh Prypine0735142018-10-04 11:15:54 +0200867 input_api, output_api, bot_whitelist=[
868 'chromium-webrtc-autoroll@webrtc-ci.iam.gserviceaccount.com'
869 ]))
andrew@webrtc.org53df1362012-01-26 21:24:23 +0000870 results.extend(input_api.canned_checks.CheckChangeTodoHasOwner(
Artem Titova04d1402018-05-11 11:23:00 +0200871 input_api, output_api, source_file_filter=non_third_party_sources))
Yves Gerey87a93532018-06-20 15:51:49 +0200872 results.extend(input_api.canned_checks.CheckPatchFormatted(
873 input_api, output_api))
charujain9893e252017-09-14 13:33:22 +0200874 results.extend(CheckNativeApiHeaderChanges(input_api, output_api))
Artem Titova04d1402018-05-11 11:23:00 +0200875 results.extend(CheckNoIOStreamInHeaders(
876 input_api, output_api, source_file_filter=non_third_party_sources))
877 results.extend(CheckNoPragmaOnce(
878 input_api, output_api, source_file_filter=non_third_party_sources))
879 results.extend(CheckNoFRIEND_TEST(
880 input_api, output_api, source_file_filter=non_third_party_sources))
Mirko Bonadeif0e0d752018-07-04 08:48:18 +0200881 results.extend(CheckGnChanges(input_api, output_api))
Artem Titova04d1402018-05-11 11:23:00 +0200882 results.extend(CheckUnwantedDependencies(
883 input_api, output_api, source_file_filter=non_third_party_sources))
884 results.extend(CheckJSONParseErrors(
885 input_api, output_api, source_file_filter=non_third_party_sources))
charujain9893e252017-09-14 13:33:22 +0200886 results.extend(RunPythonTests(input_api, output_api))
Artem Titova04d1402018-05-11 11:23:00 +0200887 results.extend(CheckUsageOfGoogleProtobufNamespace(
888 input_api, output_api, source_file_filter=non_third_party_sources))
889 results.extend(CheckOrphanHeaders(
890 input_api, output_api, source_file_filter=non_third_party_sources))
891 results.extend(CheckNewlineAtTheEndOfProtoFiles(
892 input_api, output_api, source_file_filter=non_third_party_sources))
893 results.extend(CheckNoStreamUsageIsAdded(
Artem Titov739351d2018-05-11 12:21:36 +0200894 input_api, output_api, non_third_party_sources))
Mirko Bonadei9ce800d2019-02-05 16:48:13 +0100895 results.extend(CheckNoTestCaseUsageIsAdded(
896 input_api, output_api, non_third_party_sources))
Mirko Bonadei7e4ee6e2018-09-28 11:45:23 +0200897 results.extend(CheckAddedDepsHaveTargetApprovals(input_api, output_api))
Mirko Bonadeia418e672018-10-24 13:57:25 +0200898 results.extend(CheckApiDepsFileIsUpToDate(input_api, output_api))
tzika06bf852018-11-15 20:37:35 +0900899 results.extend(CheckAbslMemoryInclude(
900 input_api, output_api, non_third_party_sources))
Mirko Bonadei9fa8ef12019-09-17 19:14:13 +0200901 results.extend(CheckBannedAbslMakeUnique(
902 input_api, output_api, non_third_party_sources))
Mirko Bonadeia418e672018-10-24 13:57:25 +0200903 return results
904
905
906def CheckApiDepsFileIsUpToDate(input_api, output_api):
Mirko Bonadei90490372018-10-26 13:17:47 +0200907 """Check that 'include_rules' in api/DEPS is up to date.
908
909 The file api/DEPS must be kept up to date in order to avoid to avoid to
910 include internal header from WebRTC's api/ headers.
911
912 This check is focused on ensuring that 'include_rules' contains a deny
913 rule for each root level directory. More focused allow rules can be
914 added to 'specific_include_rules'.
915 """
Mirko Bonadeia418e672018-10-24 13:57:25 +0200916 results = []
917 api_deps = os.path.join(input_api.PresubmitLocalPath(), 'api', 'DEPS')
918 with open(api_deps) as f:
919 deps_content = _ParseDeps(f.read())
920
921 include_rules = deps_content.get('include_rules', [])
Mirko Bonadei01e97ae2019-09-05 14:36:42 +0200922 dirs_to_skip = set(['api', 'docs'])
Mirko Bonadeia418e672018-10-24 13:57:25 +0200923
Mirko Bonadei90490372018-10-26 13:17:47 +0200924 # Only check top level directories affected by the current CL.
925 dirs_to_check = set()
926 for f in input_api.AffectedFiles():
927 path_tokens = [t for t in f.LocalPath().split(os.sep) if t]
928 if len(path_tokens) > 1:
Mirko Bonadei01e97ae2019-09-05 14:36:42 +0200929 if (path_tokens[0] not in dirs_to_skip and
Mirko Bonadei90490372018-10-26 13:17:47 +0200930 os.path.isdir(os.path.join(input_api.PresubmitLocalPath(),
931 path_tokens[0]))):
932 dirs_to_check.add(path_tokens[0])
Mirko Bonadeia418e672018-10-24 13:57:25 +0200933
Mirko Bonadei90490372018-10-26 13:17:47 +0200934 missing_include_rules = set()
935 for p in dirs_to_check:
Mirko Bonadeia418e672018-10-24 13:57:25 +0200936 rule = '-%s' % p
937 if rule not in include_rules:
Mirko Bonadei90490372018-10-26 13:17:47 +0200938 missing_include_rules.add(rule)
939
Mirko Bonadeia418e672018-10-24 13:57:25 +0200940 if missing_include_rules:
Mirko Bonadei90490372018-10-26 13:17:47 +0200941 error_msg = [
942 'include_rules = [\n',
943 ' ...\n',
944 ]
Mirko Bonadeia418e672018-10-24 13:57:25 +0200945
Mirko Bonadei90490372018-10-26 13:17:47 +0200946 for r in sorted(missing_include_rules):
947 error_msg.append(' "%s",\n' % str(r))
Mirko Bonadeia418e672018-10-24 13:57:25 +0200948
Mirko Bonadei90490372018-10-26 13:17:47 +0200949 error_msg.append(' ...\n')
950 error_msg.append(']\n')
951
Mirko Bonadeia418e672018-10-24 13:57:25 +0200952 results.append(output_api.PresubmitError(
Mirko Bonadei90490372018-10-26 13:17:47 +0200953 'New root level directory detected! WebRTC api/ headers should '
954 'not #include headers from \n'
955 'the new directory, so please update "include_rules" in file\n'
956 '"%s". Example:\n%s\n' % (api_deps, ''.join(error_msg))))
957
andrew@webrtc.org53df1362012-01-26 21:24:23 +0000958 return results
andrew@webrtc.org2442de12012-01-23 17:45:41 +0000959
Mirko Bonadei9fa8ef12019-09-17 19:14:13 +0200960def CheckBannedAbslMakeUnique(input_api, output_api, source_file_filter):
961 file_filter = lambda f: (f.LocalPath().endswith(('.cc', '.h'))
962 and source_file_filter(f))
963
964 files = []
965 for f in input_api.AffectedFiles(
966 include_deletes=False, file_filter=file_filter):
967 for _, line in f.ChangedContents():
968 if 'absl::make_unique' in line:
969 files.append(f)
970 break
971
972 if len(files):
973 return [output_api.PresubmitError(
974 'Please use std::make_unique instead of absl::make_unique.\n'
975 'Affected files:',
976 files)]
977 return []
978
tzika06bf852018-11-15 20:37:35 +0900979def CheckAbslMemoryInclude(input_api, output_api, source_file_filter):
980 pattern = input_api.re.compile(
981 r'^#include\s*"absl/memory/memory.h"', input_api.re.MULTILINE)
982 file_filter = lambda f: (f.LocalPath().endswith(('.cc', '.h'))
983 and source_file_filter(f))
984
985 files = []
986 for f in input_api.AffectedFiles(
987 include_deletes=False, file_filter=file_filter):
988 contents = input_api.ReadFile(f)
989 if pattern.search(contents):
990 continue
991 for _, line in f.ChangedContents():
Mirko Bonadei9fa8ef12019-09-17 19:14:13 +0200992 if 'absl::WrapUnique' in line:
tzika06bf852018-11-15 20:37:35 +0900993 files.append(f)
994 break
995
996 if len(files):
997 return [output_api.PresubmitError(
Mirko Bonadei9fa8ef12019-09-17 19:14:13 +0200998 'Please include "absl/memory/memory.h" header for absl::WrapUnique.\n'
999 'This header may or may not be included transitively depending on the '
1000 'C++ standard version.',
tzika06bf852018-11-15 20:37:35 +09001001 files)]
1002 return []
kjellander@webrtc.orge4158642014-08-06 09:11:18 +00001003
andrew@webrtc.org53df1362012-01-26 21:24:23 +00001004def CheckChangeOnUpload(input_api, output_api):
1005 results = []
charujain9893e252017-09-14 13:33:22 +02001006 results.extend(CommonChecks(input_api, output_api))
Oleh Prypin920b6532017-10-05 11:28:51 +02001007 results.extend(CheckGnGen(input_api, output_api))
Henrik Kjellander57e5fd22015-05-25 12:55:39 +02001008 results.extend(
1009 input_api.canned_checks.CheckGNFormatted(input_api, output_api))
niklase@google.comda159d62011-05-30 11:51:34 +00001010 return results
1011
kjellander@webrtc.orge4158642014-08-06 09:11:18 +00001012
andrew@webrtc.org2442de12012-01-23 17:45:41 +00001013def CheckChangeOnCommit(input_api, output_api):
niklase@google.com1198db92011-06-09 07:07:24 +00001014 results = []
charujain9893e252017-09-14 13:33:22 +02001015 results.extend(CommonChecks(input_api, output_api))
1016 results.extend(VerifyNativeApiHeadersListIsValid(input_api, output_api))
Artem Titov42f0d782018-06-27 13:23:17 +02001017 results.extend(input_api.canned_checks.CheckOwners(input_api, output_api))
andrew@webrtc.org53df1362012-01-26 21:24:23 +00001018 results.extend(input_api.canned_checks.CheckChangeWasUploaded(
1019 input_api, output_api))
1020 results.extend(input_api.canned_checks.CheckChangeHasDescription(
1021 input_api, output_api))
charujain9893e252017-09-14 13:33:22 +02001022 results.extend(CheckChangeHasBugField(input_api, output_api))
1023 results.extend(CheckCommitMessageBugEntry(input_api, output_api))
kjellander@webrtc.org12cb88c2014-02-13 11:53:43 +00001024 results.extend(input_api.canned_checks.CheckTreeIsOpen(
1025 input_api, output_api,
1026 json_url='http://webrtc-status.appspot.com/current?format=json'))
niklase@google.com1198db92011-06-09 07:07:24 +00001027 return results
mbonadei74973ed2017-05-09 07:58:05 -07001028
1029
Artem Titova04d1402018-05-11 11:23:00 +02001030def CheckOrphanHeaders(input_api, output_api, source_file_filter):
mbonadei74973ed2017-05-09 07:58:05 -07001031 # We need to wait until we have an input_api object and use this
1032 # roundabout construct to import prebubmit_checks_lib because this file is
1033 # eval-ed and thus doesn't have __file__.
Patrik Höglund2f3f7222017-12-19 11:08:56 +01001034 error_msg = """{} should be listed in {}."""
mbonadei74973ed2017-05-09 07:58:05 -07001035 results = []
Patrik Höglund7e60de22018-01-09 14:22:00 +01001036 orphan_blacklist = [
1037 os.path.join('tools_webrtc', 'ios', 'SDK'),
1038 ]
Oleh Prypin2f33a562017-10-04 20:17:54 +02001039 with _AddToPath(input_api.os_path.join(
1040 input_api.PresubmitLocalPath(), 'tools_webrtc', 'presubmit_checks_lib')):
mbonadei74973ed2017-05-09 07:58:05 -07001041 from check_orphan_headers import GetBuildGnPathFromFilePath
1042 from check_orphan_headers import IsHeaderInBuildGn
mbonadei74973ed2017-05-09 07:58:05 -07001043
Artem Titova04d1402018-05-11 11:23:00 +02001044 file_filter = lambda x: input_api.FilterSourceFile(
1045 x, black_list=orphan_blacklist) and source_file_filter(x)
1046 for f in input_api.AffectedSourceFiles(file_filter):
Patrik Höglund7e60de22018-01-09 14:22:00 +01001047 if f.LocalPath().endswith('.h'):
mbonadei74973ed2017-05-09 07:58:05 -07001048 file_path = os.path.abspath(f.LocalPath())
1049 root_dir = os.getcwd()
1050 gn_file_path = GetBuildGnPathFromFilePath(file_path, os.path.exists,
1051 root_dir)
1052 in_build_gn = IsHeaderInBuildGn(file_path, gn_file_path)
1053 if not in_build_gn:
1054 results.append(output_api.PresubmitError(error_msg.format(
Patrik Höglund2f3f7222017-12-19 11:08:56 +01001055 f.LocalPath(), os.path.relpath(gn_file_path))))
mbonadei74973ed2017-05-09 07:58:05 -07001056 return results
Mirko Bonadei960fd5b2017-06-29 14:59:36 +02001057
1058
Artem Titove92675b2018-05-22 10:21:27 +02001059def CheckNewlineAtTheEndOfProtoFiles(input_api, output_api, source_file_filter):
Mirko Bonadei960fd5b2017-06-29 14:59:36 +02001060 """Checks that all .proto files are terminated with a newline."""
1061 error_msg = 'File {} must end with exactly one newline.'
1062 results = []
Artem Titova04d1402018-05-11 11:23:00 +02001063 file_filter = lambda x: input_api.FilterSourceFile(
1064 x, white_list=(r'.+\.proto$',)) and source_file_filter(x)
1065 for f in input_api.AffectedSourceFiles(file_filter):
Mirko Bonadei960fd5b2017-06-29 14:59:36 +02001066 file_path = f.LocalPath()
1067 with open(file_path) as f:
1068 lines = f.readlines()
Mirko Bonadeia730c1c2017-09-18 11:33:13 +02001069 if len(lines) > 0 and not lines[-1].endswith('\n'):
Mirko Bonadei960fd5b2017-06-29 14:59:36 +02001070 results.append(output_api.PresubmitError(error_msg.format(file_path)))
1071 return results
Mirko Bonadei7e4ee6e2018-09-28 11:45:23 +02001072
1073
1074def _ExtractAddRulesFromParsedDeps(parsed_deps):
1075 """Extract the rules that add dependencies from a parsed DEPS file.
1076
1077 Args:
1078 parsed_deps: the locals dictionary from evaluating the DEPS file."""
1079 add_rules = set()
1080 add_rules.update([
1081 rule[1:] for rule in parsed_deps.get('include_rules', [])
1082 if rule.startswith('+') or rule.startswith('!')
1083 ])
1084 for _, rules in parsed_deps.get('specific_include_rules',
1085 {}).iteritems():
1086 add_rules.update([
1087 rule[1:] for rule in rules
1088 if rule.startswith('+') or rule.startswith('!')
1089 ])
1090 return add_rules
1091
1092
1093def _ParseDeps(contents):
1094 """Simple helper for parsing DEPS files."""
1095 # Stubs for handling special syntax in the root DEPS file.
1096 class VarImpl(object):
1097
1098 def __init__(self, local_scope):
1099 self._local_scope = local_scope
1100
1101 def Lookup(self, var_name):
1102 """Implements the Var syntax."""
1103 try:
1104 return self._local_scope['vars'][var_name]
1105 except KeyError:
1106 raise Exception('Var is not defined: %s' % var_name)
1107
1108 local_scope = {}
1109 global_scope = {
1110 'Var': VarImpl(local_scope).Lookup,
1111 }
1112 exec contents in global_scope, local_scope
1113 return local_scope
1114
1115
1116def _CalculateAddedDeps(os_path, old_contents, new_contents):
1117 """Helper method for _CheckAddedDepsHaveTargetApprovals. Returns
1118 a set of DEPS entries that we should look up.
1119
1120 For a directory (rather than a specific filename) we fake a path to
1121 a specific filename by adding /DEPS. This is chosen as a file that
1122 will seldom or never be subject to per-file include_rules.
1123 """
1124 # We ignore deps entries on auto-generated directories.
1125 auto_generated_dirs = ['grit', 'jni']
1126
1127 old_deps = _ExtractAddRulesFromParsedDeps(_ParseDeps(old_contents))
1128 new_deps = _ExtractAddRulesFromParsedDeps(_ParseDeps(new_contents))
1129
1130 added_deps = new_deps.difference(old_deps)
1131
1132 results = set()
1133 for added_dep in added_deps:
1134 if added_dep.split('/')[0] in auto_generated_dirs:
1135 continue
1136 # Assume that a rule that ends in .h is a rule for a specific file.
1137 if added_dep.endswith('.h'):
1138 results.add(added_dep)
1139 else:
1140 results.add(os_path.join(added_dep, 'DEPS'))
1141 return results
1142
1143
1144def CheckAddedDepsHaveTargetApprovals(input_api, output_api):
1145 """When a dependency prefixed with + is added to a DEPS file, we
1146 want to make sure that the change is reviewed by an OWNER of the
1147 target file or directory, to avoid layering violations from being
1148 introduced. This check verifies that this happens.
1149 """
1150 virtual_depended_on_files = set()
1151
1152 file_filter = lambda f: not input_api.re.match(
1153 r"^third_party[\\\/](WebKit|blink)[\\\/].*", f.LocalPath())
1154 for f in input_api.AffectedFiles(include_deletes=False,
1155 file_filter=file_filter):
1156 filename = input_api.os_path.basename(f.LocalPath())
1157 if filename == 'DEPS':
1158 virtual_depended_on_files.update(_CalculateAddedDeps(
1159 input_api.os_path,
1160 '\n'.join(f.OldContents()),
1161 '\n'.join(f.NewContents())))
1162
1163 if not virtual_depended_on_files:
1164 return []
1165
1166 if input_api.is_committing:
1167 if input_api.tbr:
1168 return [output_api.PresubmitNotifyResult(
1169 '--tbr was specified, skipping OWNERS check for DEPS additions')]
1170 if input_api.dry_run:
1171 return [output_api.PresubmitNotifyResult(
1172 'This is a dry run, skipping OWNERS check for DEPS additions')]
1173 if not input_api.change.issue:
1174 return [output_api.PresubmitError(
1175 "DEPS approval by OWNERS check failed: this change has "
1176 "no change number, so we can't check it for approvals.")]
1177 output = output_api.PresubmitError
1178 else:
1179 output = output_api.PresubmitNotifyResult
1180
1181 owners_db = input_api.owners_db
1182 owner_email, reviewers = (
1183 input_api.canned_checks.GetCodereviewOwnerAndReviewers(
1184 input_api,
1185 owners_db.email_regexp,
1186 approval_needed=input_api.is_committing))
1187
1188 owner_email = owner_email or input_api.change.author_email
1189
1190 reviewers_plus_owner = set(reviewers)
1191 if owner_email:
1192 reviewers_plus_owner.add(owner_email)
1193 missing_files = owners_db.files_not_covered_by(virtual_depended_on_files,
1194 reviewers_plus_owner)
1195
1196 # We strip the /DEPS part that was added by
1197 # _FilesToCheckForIncomingDeps to fake a path to a file in a
1198 # directory.
1199 def StripDeps(path):
1200 start_deps = path.rfind('/DEPS')
1201 if start_deps != -1:
1202 return path[:start_deps]
1203 else:
1204 return path
1205 unapproved_dependencies = ["'+%s'," % StripDeps(path)
1206 for path in missing_files]
1207
1208 if unapproved_dependencies:
1209 output_list = [
1210 output('You need LGTM from owners of depends-on paths in DEPS that were '
1211 'modified in this CL:\n %s' %
1212 '\n '.join(sorted(unapproved_dependencies)))]
1213 suggested_owners = owners_db.reviewers_for(missing_files, owner_email)
1214 output_list.append(output(
1215 'Suggested missing target path OWNERS:\n %s' %
1216 '\n '.join(suggested_owners or [])))
1217 return output_list
1218
1219 return []