blob: 27facadb612f0662b5cf547831b3a10258fdb9a3 [file] [log] [blame]
andrew@webrtc.org2442de12012-01-23 17:45:411# 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:348
kjellander7439f972016-12-06 06:47:469import json
kjellander@webrtc.orgaefe61a2014-12-08 13:00:3010import os
kjellander@webrtc.org85759802013-10-22 16:47:4011import re
kjellander@webrtc.org3bd41562014-09-01 11:06:3712import sys
Mirko Bonadei4dc4e252017-09-19 11:49:1613from collections import defaultdict
Oleh Prypin2f33a562017-10-04 18:17:5414from contextlib import contextmanager
kjellander@webrtc.org85759802013-10-22 16:47:4015
oprypin2aa463f2017-03-23 10:17:0216# Files and directories that are *skipped* by cpplint in the presubmit script.
17CPPLINT_BLACKLIST = [
Mirko Bonadei92ea95e2017-09-15 04:47:3118 'api/video_codecs/video_decoder.h',
19 'common_types.cc',
20 'common_types.h',
21 'examples/objc',
Amit Hilbuchce7032b2019-01-23 00:19:3522 'media/base/stream_params.h',
23 'media/base/video_common.h',
24 'media/sctp/sctp_transport.cc',
Mirko Bonadei92ea95e2017-09-15 04:47:3125 'modules/audio_coding',
Mirko Bonadei92ea95e2017-09-15 04:47:3126 'modules/audio_device',
27 'modules/audio_processing',
28 'modules/desktop_capture',
29 'modules/include/module_common_types.h',
Mirko Bonadei92ea95e2017-09-15 04:47:3130 'modules/utility',
31 'modules/video_capture',
Amit Hilbuchce7032b2019-01-23 00:19:3532 'p2p/base/pseudo_tcp.cc',
33 'p2p/base/pseudo_tcp.h',
Mirko Bonadei92ea95e2017-09-15 04:47:3134 'rtc_base',
35 'sdk/android/src/jni',
36 'sdk/objc',
37 'system_wrappers',
38 'test',
Henrik Kjellander90fd7d82017-05-09 06:30:1039 'tools_webrtc',
Mirko Bonadei92ea95e2017-09-15 04:47:3140 'voice_engine',
kjellander@webrtc.org0fcaf992015-11-26 14:24:5241]
42
jbauchc4e3ead2016-02-19 08:25:5543# 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 13:23:0950# - runtime/references : Mutable references are not banned by the Google
51# C++ style guide anymore (starting from May 2020).
kjellandere5a87a52016-04-27 09:32:1252# - whitespace/operators: Same as above (doesn't seem sufficient to eliminate
53# all move-related errors).
jbauchc4e3ead2016-02-19 08:25:5554BLACKLIST_LINT_FILTERS = [
55 '-build/c++11',
Mirko Bonadeie92e2862020-05-29 13:23:0956 '-runtime/references',
kjellandere5a87a52016-04-27 09:32:1257 '-whitespace/operators',
jbauchc4e3ead2016-02-19 08:25:5558]
59
kjellanderfd595232015-12-04 10:44:0960# 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-03 07:56:1467NATIVE_API_DIRS = (
Karl Wibergef52d8b82017-10-25 11:20:0368 'api', # All subdirectories of api/ are included as well.
Mirko Bonadeia4eeeff2018-01-11 12:16:5269 'media/base',
70 'media/engine',
Mirko Bonadei92ea95e2017-09-15 04:47:3171 'modules/audio_device/include',
72 'pc',
kjellanderdd705472016-06-09 18:17:2773)
Mirko Bonadei4dc4e252017-09-19 11:49:1674
kjellanderdd705472016-06-09 18:17:2775# 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 04:47:3178 'common_audio/include',
79 'modules/audio_coding/include',
Mirko Bonadei92ea95e2017-09-15 04:47:3180 'modules/audio_processing/include',
Mirko Bonadei92ea95e2017-09-15 04:47:3181 '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 04:47:3188 '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-03 07:56:1493)
Mirko Bonadei4dc4e252017-09-19 11:49:1694
Karl Wibergd4f01c12017-11-10 09:55:4595# 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 18:17:2797API_DIRS = NATIVE_API_DIRS[:] + LEGACY_API_DIRS[:]
kjellander53047c92015-12-03 07:56:1498
Mirko Bonadei4dc4e252017-09-19 11:49:1699# TARGET_RE matches a GN target, and extracts the target name and the contents.
Mirko Bonadei2dcf3482020-06-05 12:30:41100TARGET_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 11:49:16105
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 12:30:41110# 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 11:49:16114# FILE_PATH_RE matchies a file path.
115FILE_PATH_RE = re.compile(r'"(?P<file_path>(\w|\/)+)(?P<extension>\.\w+)"')
116
kjellander53047c92015-12-03 07:56:14117
Mirko Bonadeid8665442018-09-04 10:17:27118def 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 18:17:54126@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 13:27:22135
136
charujain9893e252017-09-14 11:33:22137def VerifyNativeApiHeadersListIsValid(input_api, output_api):
kjellander53047c92015-12-03 07:56:14138 """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 18:17:27142 *path.split('/')) for path in API_DIRS]
kjellander53047c92015-12-03 07:56:14143 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 08:21:27154
kjellanderc88b5d52017-04-05 13:42:43155API_CHANGE_MSG = """
kwibergeb133022016-04-07 14:41:48156You seem to be changing native API header files. Please make sure that you:
oprypin375b9ac2017-02-13 12:13:23157 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 14:41:48160 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-03 07:56:14173
Artem Titove92675b2018-05-22 08:21:27174
charujain9893e252017-09-14 11:33:22175def CheckNativeApiHeaderChanges(input_api, output_api):
kjellander53047c92015-12-03 07:56:14176 """Checks to remind proper changing of native APIs."""
177 files = []
Karl Wiberg6bfac032017-10-27 13:14:20178 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 10:58:27186 files.append(f.LocalPath())
Karl Wiberg6bfac032017-10-27 13:14:20187 else:
188 # Normal case: Subdirectories not included.
189 if dn == path:
Niels Möller1d201852019-06-26 10:58:27190 files.append(f.LocalPath())
kjellander53047c92015-12-03 07:56:14191
192 if files:
kjellanderc88b5d52017-04-05 13:42:43193 return [output_api.PresubmitNotifyResult(API_CHANGE_MSG, files)]
kjellander53047c92015-12-03 07:56:14194 return []
195
kjellander@webrtc.org0fcaf992015-11-26 14:24:52196
Artem Titova04d1402018-05-11 09:23:00197def CheckNoIOStreamInHeaders(input_api, output_api,
198 source_file_filter):
kjellander@webrtc.org51198f12012-02-21 17:53:46199 """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 09:23:00203 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:46206 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 10:55:39213 return [output_api.PresubmitError(
kjellander@webrtc.org51198f12012-02-21 17:53:46214 '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 10:55:39217 files)]
kjellander@webrtc.org51198f12012-02-21 17:53:46218 return []
219
kjellander@webrtc.orge4158642014-08-06 09:11:18220
Artem Titova04d1402018-05-11 09:23:00221def CheckNoPragmaOnce(input_api, output_api,
222 source_file_filter):
kjellander6aeef742017-02-20 09:13:18223 """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 09:23:00227 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 09:13:18230 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 09:23:00244def CheckNoFRIEND_TEST(input_api, output_api, # pylint: disable=invalid-name
245 source_file_filter):
kjellander@webrtc.org51198f12012-02-21 17:53:46246 """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 09:23:00251 file_filter = lambda f: (f.LocalPath().endswith(('.cc', '.h'))
252 and source_file_filter(f))
kjellander@webrtc.org51198f12012-02-21 17:53:46253 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:18264
charujain9893e252017-09-14 11:33:22265def IsLintBlacklisted(blacklist_paths, file_path):
oprypin2aa463f2017-03-23 10:17:02266 """ 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 14:24:52269 return True
270 return False
271
272
charujain9893e252017-09-14 11:33:22273def CheckApprovedFilesLintClean(input_api, output_api,
Artem Titova04d1402018-05-11 09:23:00274 source_file_filter=None):
oprypin2aa463f2017-03-23 10:17:02275 """Checks that all new or non-blacklisted .cc and .h files pass cpplint.py.
charujain9893e252017-09-14 11:33:22276 This check is based on CheckChangeLintsClean in
kjellander@webrtc.org51198f12012-02-21 17:53:46277 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 08:25:55287 lint_filters = cpplint._Filters()
288 lint_filters.extend(BLACKLIST_LINT_FILTERS)
289 cpplint._SetFilters(','.join(lint_filters))
290
oprypin2aa463f2017-03-23 10:17:02291 # 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 14:24:52294
kjellander@webrtc.org51198f12012-02-21 17:53:46295 # Use the strictest verbosity level for cpplint.py (level 1) which is the
oprypin2aa463f2017-03-23 10:17:02296 # 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:46299 verbosity_level = 1
300 files = []
301 for f in input_api.AffectedSourceFiles(source_file_filter):
Henrik Kjellander57e5fd22015-05-25 10:55:39302 # Note that moved/renamed files also count as added.
charujain9893e252017-09-14 11:33:22303 if f.Action() == 'A' or not IsLintBlacklisted(blacklist_paths,
Artem Titove92675b2018-05-22 08:21:27304 f.LocalPath()):
kjellander@webrtc.org51198f12012-02-21 17:53:46305 files.append(f.AbsoluteLocalPath())
mflodman@webrtc.org2a452092012-07-01 05:55:23306
kjellander@webrtc.org51198f12012-02-21 17:53:46307 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 14:52:41312 res_type = output_api.PresubmitError
kjellander@webrtc.org51198f12012-02-21 17:53:46313 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 08:21:27319
charujain9893e252017-09-14 11:33:22320def CheckNoSourcesAbove(input_api, gn_files, output_api):
ehmaldonado5b1ba082016-09-02 12:51:08321 # 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 19:11:29340 'allowed. Please introduce new GN targets in the proper location '
341 'instead.\n'
ehmaldonado5b1ba082016-09-02 12:51:08342 '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 08:21:27348
Mirko Bonadei2dcf3482020-06-05 12:30:41349def 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 11:49:16379def 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 08:21:27384
Mirko Bonadei4dc4e252017-09-19 11:49:16385 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-06 06:47:46393 for gn_file in gn_files:
Mirko Bonadei4dc4e252017-09-19 11:49:16394 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-06 06:47:46410 c_files = []
411 cc_files = []
Mirko Bonadei4dc4e252017-09-19 11:49:16412 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-06 06:47:46437 return [output_api.PresubmitError(
Mirko Bonadei4dc4e252017-09-19 11:49:16438 '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-06 06:47:46440 'Mixed sources: \n'
441 '%s\n'
Mirko Bonadei4dc4e252017-09-19 11:49:16442 'Violating GN files:\n%s\n' % (json.dumps(errors, indent=2),
443 '\n'.join(errors.keys())))]
kjellander7439f972016-12-06 06:47:46444 return []
445
Artem Titove92675b2018-05-22 08:21:27446
charujain9893e252017-09-14 11:33:22447def CheckNoPackageBoundaryViolations(input_api, gn_files, output_api):
ehmaldonado4fb97462017-01-30 13:27:22448 cwd = input_api.PresubmitLocalPath()
Oleh Prypin2f33a562017-10-04 18:17:54449 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 13:27:22455 return [output_api.PresubmitError(
Oleh Prypin2f33a562017-10-04 18:17:54456 '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 13:27:22458 return []
459
Mirko Bonadeia51bbd82018-03-08 15:15:45460
Mirko Bonadeif0e0d752018-07-04 06:48:18461def _ReportFileAndLine(filename, line_num):
Mirko Bonadeia51bbd82018-03-08 15:15:45462 """Default error formatter for _FindNewViolationsOfRule."""
463 return '%s (line %s)' % (filename, line_num)
464
465
Mirko Bonadeif0e0d752018-07-04 06:48:18466def 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 15:48:13489
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 15:15:45509def CheckNoStreamUsageIsAdded(input_api, output_api,
Artem Titov739351d2018-05-11 10:21:36510 source_file_filter,
Mirko Bonadeif0e0d752018-07-04 06:48:18511 error_formatter=_ReportFileAndLine):
Mirko Bonadeia51bbd82018-03-08 15:15:45512 """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 14:08:39527 '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 15:15:45530 '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 10:22:07535 r'// no-presubmit-check TODO\(webrtc:8982\)')
Artem Titova04d1402018-05-11 09:23:00536 file_filter = lambda x: (input_api.FilterSourceFile(x)
537 and source_file_filter(x))
Mirko Bonadei571791a2019-05-07 12:08:05538
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 14:10:40543 return (file_path.startswith('examples') or
544 file_path.startswith('test') or
545 is_test)
546
Mirko Bonadei571791a2019-05-07 12:08:05547
Artem Titova04d1402018-05-11 09:23:00548 for f in input_api.AffectedSourceFiles(file_filter):
Mirko Bonadei571791a2019-05-07 12:08:05549 # 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:47551 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 15:15:45556 if errors:
557 return [output_api.PresubmitError(error_msg, errors)]
558 return []
559
Artem Titove92675b2018-05-22 08:21:27560
Mirko Bonadeia05d47e2018-05-09 09:03:38561def CheckPublicDepsIsNotUsed(gn_files, input_api, output_api):
562 """Checks that public_deps is not used without a good reason."""
Mirko Bonadei5c1ad592017-12-12 10:52:27563 result = []
Mirko Bonadeia05d47e2018-05-09 09:03:38564 no_presubmit_check_re = input_api.re.compile(
Joe Chen0b3a6e32019-12-27 07:01:42565 r'# no-presubmit-check TODO\(webrtc:\d+\)')
Mirko Bonadeia05d47e2018-05-09 09:03:38566 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 10:32:33570 '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 09:03:38572 'public_deps = [ # no-presubmit-check TODO(webrtc:8603)\n')
Mirko Bonadei5c1ad592017-12-12 10:52:27573 for affected_file in gn_files:
574 for (line_number, affected_line) in affected_file.ChangedContents():
Patrik Höglund81c7a602020-01-30 10:32:33575 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 10:52:27581 return result
582
Artem Titove92675b2018-05-22 08:21:27583
Mirko Bonadei05691dd2019-10-22 14:34:24584def CheckCheckIncludesIsNotUsed(gn_files, input_api, output_api):
Patrik Höglund6f491062018-01-11 11:04:23585 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 14:34:24590 'this is allowed: if so, get approval from a .gn owner in the '
Patrik Höglund6f491062018-01-11 11:04:23591 'root OWNERS file.\n'
592 'Used in: %s (line %d).')
Mirko Bonadei05691dd2019-10-22 14:34:24593 no_presubmit_re = input_api.re.compile(
594 r'# no-presubmit-check TODO\(bugs\.webrtc\.org/\d+\)')
Patrik Höglund6f491062018-01-11 11:04:23595 for affected_file in gn_files:
596 for (line_number, affected_line) in affected_file.ChangedContents():
Mirko Bonadei05691dd2019-10-22 14:34:24597 if ('check_includes' in affected_line
598 and not no_presubmit_re.search(affected_line)):
Patrik Höglund6f491062018-01-11 11:04:23599 result.append(
600 output_api.PresubmitError(error_msg % (affected_file.LocalPath(),
601 line_number)))
602 return result
603
Artem Titove92675b2018-05-22 08:21:27604
Mirko Bonadeif0e0d752018-07-04 06:48:18605def CheckGnChanges(input_api, output_api):
Artem Titova04d1402018-05-11 09:23:00606 file_filter = lambda x: (input_api.FilterSourceFile(
Oleh Prypinafe01652017-10-04 13:56:08607 x, white_list=(r'.+\.(gn|gni)$',),
Mirko Bonadeif0e0d752018-07-04 06:48:18608 black_list=(r'.*/presubmit_checks_lib/testdata/.*',)))
ehmaldonado5b1ba082016-09-02 12:51:08609
610 gn_files = []
Artem Titova04d1402018-05-11 09:23:00611 for f in input_api.AffectedSourceFiles(file_filter):
Mirko Bonadei92ea95e2017-09-15 04:47:31612 gn_files.append(f)
ehmaldonado5b1ba082016-09-02 12:51:08613
614 result = []
615 if gn_files:
charujain9893e252017-09-14 11:33:22616 result.extend(CheckNoSourcesAbove(input_api, gn_files, output_api))
Mirko Bonadei4dc4e252017-09-19 11:49:16617 result.extend(CheckNoMixingSources(input_api, gn_files, output_api))
Mirko Bonadei2dcf3482020-06-05 12:30:41618 result.extend(CheckAbseilDependencies(input_api, gn_files, output_api))
Mirko Bonadei4dc4e252017-09-19 11:49:16619 result.extend(CheckNoPackageBoundaryViolations(input_api, gn_files,
620 output_api))
Mirko Bonadeia05d47e2018-05-09 09:03:38621 result.extend(CheckPublicDepsIsNotUsed(gn_files, input_api, output_api))
Mirko Bonadei05691dd2019-10-22 14:34:24622 result.extend(CheckCheckIncludesIsNotUsed(gn_files, input_api, output_api))
Mirko Bonadeif0e0d752018-07-04 06:48:18623 result.extend(CheckNoWarningSuppressionFlagsAreAdded(gn_files, input_api,
624 output_api))
ehmaldonado5b1ba082016-09-02 12:51:08625 return result
626
Artem Titove92675b2018-05-22 08:21:27627
Oleh Prypin920b6532017-10-05 09:28:51628def 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 16:04:16635 from build_helpers import RunGnCheck
Mirko Bonadeid8665442018-09-04 10:17:27636 errors = RunGnCheck(FindSrcDirPath(input_api.PresubmitLocalPath()))[:5]
Oleh Prypin920b6532017-10-05 09:28:51637 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 08:21:27644
Artem Titova04d1402018-05-11 09:23:00645def CheckUnwantedDependencies(input_api, output_api, source_file_filter):
kjellander@webrtc.org3bd41562014-09-01 11:06:37646 """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 10:17:27655 src_path = FindSrcDirPath(input_api.PresubmitLocalPath())
656 checkdeps_path = input_api.os_path.join(src_path, 'buildtools', 'checkdeps')
Oleh Prypin2f33a562017-10-04 18:17:54657 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:37662 import checkdeps
663 from cpp_checker import CppChecker
664 from rules import Rule
kjellander@webrtc.org3bd41562014-09-01 11:06:37665
666 added_includes = []
Artem Titova04d1402018-05-11 09:23:00667 for f in input_api.AffectedFiles(file_filter=source_file_filter):
kjellander@webrtc.org3bd41562014-09-01 11:06:37668 if not CppChecker.IsCppFile(f.LocalPath()):
669 continue
670
Henrik Kjellander57e5fd22015-05-25 10:55:39671 changed_lines = [line for _, line in f.ChangedContents()]
kjellander@webrtc.org3bd41562014-09-01 11:06:37672 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 10:47:05689 '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:37693 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 10:47:05698 '#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:37701 warning_descriptions))
702 return results
703
Artem Titove92675b2018-05-22 08:21:27704
charujain9893e252017-09-14 11:33:22705def CheckCommitMessageBugEntry(input_api, output_api):
706 """Check that bug entries are well-formed in commit message."""
707 bogus_bug_msg = (
Mirko Bonadei61880182017-10-12 13:12:35708 'Bogus Bug entry: %s. Please specify the issue tracker prefix and the '
charujain9893e252017-09-14 11:33:22709 'issue number, separated by a colon, e.g. webrtc:123 or chromium:12345.')
710 results = []
Mirko Bonadei61880182017-10-12 13:12:35711 for bug in input_api.change.BugsFromDescription():
charujain9893e252017-09-14 11:33:22712 bug = bug.strip()
713 if bug.lower() == 'none':
714 continue
charujain81a58c72017-09-25 11:25:45715 if 'b/' not in bug and ':' not in bug:
charujain9893e252017-09-14 11:33:22716 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 13:12:35722 results.append('Bug entry requires issue tracker prefix, e.g. %s:%s' %
charujain9893e252017-09-14 11:33:22723 (prefix_guess, bug))
724 except ValueError:
725 results.append(bogus_bug_msg % bug)
charujain81a58c72017-09-25 11:25:45726 elif not (re.match(r'\w+:\d+', bug) or re.match(r'b/\d+', bug)):
charujain9893e252017-09-14 11:33:22727 results.append(bogus_bug_msg % bug)
728 return [output_api.PresubmitError(r) for r in results]
729
Artem Titove92675b2018-05-22 08:21:27730
charujain9893e252017-09-14 11:33:22731def CheckChangeHasBugField(input_api, output_api):
Mirko Bonadei61880182017-10-12 13:12:35732 """Requires that the changelist is associated with a bug.
kjellanderd1e26a92016-09-19 15:11:16733
734 This check is stricter than the one in depot_tools/presubmit_canned_checks.py
Mirko Bonadei61880182017-10-12 13:12:35735 since it fails the presubmit if the bug field is missing or doesn't contain
kjellanderd1e26a92016-09-19 15:11:16736 a bug reference.
Mirko Bonadei61880182017-10-12 13:12:35737
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 15:11:16740 """
Mirko Bonadei61880182017-10-12 13:12:35741 if input_api.change.BugsFromDescription():
kjellanderd1e26a92016-09-19 15:11:16742 return []
743 else:
744 return [output_api.PresubmitError(
Mirko Bonadei61880182017-10-12 13:12:35745 'The "Bug: [bug number]" footer is mandatory. Please create a bug and '
kjellanderd1e26a92016-09-19 15:11:16746 'reference it using either of:\n'
Mirko Bonadei61880182017-10-12 13:12:35747 ' * 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:18749
Artem Titove92675b2018-05-22 08:21:27750
Artem Titova04d1402018-05-11 09:23:00751def CheckJSONParseErrors(input_api, output_api, source_file_filter):
kjellander569cf942016-02-11 13:02:59752 """Check that JSON files do not contain syntax errors."""
753
754 def FilterFile(affected_file):
Artem Titova04d1402018-05-11 09:23:00755 return (input_api.os_path.splitext(affected_file.LocalPath())[1] == '.json'
756 and source_file_filter(affected_file))
kjellander569cf942016-02-11 13:02:59757
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 08:21:27773 (affected_file.LocalPath(),
774 parse_error)))
kjellander569cf942016-02-11 13:02:59775 return results
776
777
charujain9893e252017-09-14 11:33:22778def RunPythonTests(input_api, output_api):
kjellanderc88b5d52017-04-05 13:42:43779 def Join(*args):
Henrik Kjellander8d3ad822015-05-26 17:52:05780 return input_api.os_path.join(input_api.PresubmitLocalPath(), *args)
781
782 test_directories = [
Edward Lemur6d01f6d2017-09-14 15:02:01783 input_api.PresubmitLocalPath(),
Mirko Bonadei92ea95e2017-09-15 04:47:31784 Join('rtc_tools', 'py_event_log_analyzer'),
Mirko Bonadei92ea95e2017-09-15 04:47:31785 Join('audio', 'test', 'unittests'),
ehmaldonado4fb97462017-01-30 13:27:22786 ] + [
Henrik Kjellander90fd7d82017-05-09 06:30:10787 root for root, _, files in os.walk(Join('tools_webrtc'))
ehmaldonado4fb97462017-01-30 13:27:22788 if any(f.endswith('_test.py') for f in files)
Henrik Kjellander8d3ad822015-05-26 17:52:05789 ]
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 09:23:00802def CheckUsageOfGoogleProtobufNamespace(input_api, output_api,
803 source_file_filter):
mbonadei38415b22017-04-07 12:38:01804 """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 04:47:31807 proto_utils_path = os.path.join('rtc_base', 'protobuf_utils.h')
Artem Titova04d1402018-05-11 09:23:00808 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 12:38:01811 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 04:47:31825def _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 11:33:22847def CommonChecks(input_api, output_api):
andrew@webrtc.org53df1362012-01-26 21:24:23848 """Checks common to both upload and commit."""
niklase@google.comda159d62011-05-30 11:51:34849 results = []
tkchin42f580e2015-11-27 07:18:23850 # 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:05854 r".*objc\.[hcm]+$",
tkchin42f580e2015-11-27 07:18:23855 )
856 source_file_filter = lambda x: input_api.FilterSourceFile(x, None, black_list)
charujain9893e252017-09-14 11:33:22857 results.extend(CheckApprovedFilesLintClean(
tkchin42f580e2015-11-27 07:18:23858 input_api, output_api, source_file_filter))
Mirko Bonadei92ea95e2017-09-15 04:47:31859 results.extend(input_api.canned_checks.CheckLicense(
860 input_api, output_api, _LicenseHeader(input_api)))
phoglund@webrtc.org5d3713932013-03-07 09:59:43861 results.extend(input_api.canned_checks.RunPylint(input_api, output_api,
kjellander@webrtc.org177567c2016-12-22 09:40:28862 black_list=(r'^base[\\\/].*\.py$',
Henrik Kjellander14771ac2015-06-02 11:10:04863 r'^build[\\\/].*\.py$',
864 r'^buildtools[\\\/].*\.py$',
kjellander38c65c82017-04-13 05:43:38865 r'^infra[\\\/].*\.py$',
Henrik Kjellander0779e8f2016-12-22 11:01:17866 r'^ios[\\\/].*\.py$',
Henrik Kjellander14771ac2015-06-02 11:10:04867 r'^out.*[\\\/].*\.py$',
868 r'^testing[\\\/].*\.py$',
869 r'^third_party[\\\/].*\.py$',
kjellander@webrtc.org177567c2016-12-22 09:40:28870 r'^tools[\\\/].*\.py$',
kjellanderafd54942016-12-17 20:21:39871 # TODO(phoglund): should arguably be checked.
Henrik Kjellander90fd7d82017-05-09 06:30:10872 r'^tools_webrtc[\\\/]mb[\\\/].*\.py$',
Henrik Kjellander14771ac2015-06-02 11:10:04873 r'^xcodebuild.*[\\\/].*\.py$',),
Henrik Kjellander57e5fd22015-05-25 10:55:39874 pylintrc='pylintrc'))
kjellander569cf942016-02-11 13:02:59875
nisse3d21e232016-09-02 10:07:06876 # TODO(nisse): talk/ is no more, so make below checks simpler?
Henrik Kjellander57e5fd22015-05-25 10:55:39877 # 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 06:03:56880
tkchin3cd9a302016-06-08 19:40:28881 # .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 19:11:29884 # Skip long-lines check for DEPS and GN files.
885 build_file_filter_list = (r'.+\.gn$', r'.+\.gni$', 'DEPS')
Artem Titova04d1402018-05-11 09:23:00886 # Also we will skip most checks for third_party directory.
Artem Titov42f0d782018-06-27 11:23:17887 third_party_filter_list = (r'^third_party[\\\/].+',)
tkchin3cd9a302016-06-08 19:40:28888 eighty_char_sources = lambda x: input_api.FilterSourceFile(x,
Artem Titova04d1402018-05-11 09:23:00889 black_list=build_file_filter_list + objc_filter_list +
890 third_party_filter_list)
tkchin3cd9a302016-06-08 19:40:28891 hundred_char_sources = lambda x: input_api.FilterSourceFile(x,
892 white_list=objc_filter_list)
Artem Titove92675b2018-05-22 08:21:27893 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:41896 results.extend(input_api.canned_checks.CheckLongLines(
tkchin3cd9a302016-06-08 19:40:28897 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:41901 results.extend(input_api.canned_checks.CheckChangeHasNoTabs(
Artem Titova04d1402018-05-11 09:23:00902 input_api, output_api, source_file_filter=non_third_party_sources))
andrew@webrtc.org53df1362012-01-26 21:24:23903 results.extend(input_api.canned_checks.CheckChangeHasNoStrayWhitespace(
Artem Titova04d1402018-05-11 09:23:00904 input_api, output_api, source_file_filter=non_third_party_sources))
kjellandere5dc62a2016-12-14 08:16:21905 results.extend(input_api.canned_checks.CheckAuthorizedAuthor(
Oleh Prypine0735142018-10-04 09:15:54906 input_api, output_api, bot_whitelist=[
907 'chromium-webrtc-autoroll@webrtc-ci.iam.gserviceaccount.com'
908 ]))
andrew@webrtc.org53df1362012-01-26 21:24:23909 results.extend(input_api.canned_checks.CheckChangeTodoHasOwner(
Artem Titova04d1402018-05-11 09:23:00910 input_api, output_api, source_file_filter=non_third_party_sources))
Yves Gerey87a93532018-06-20 13:51:49911 results.extend(input_api.canned_checks.CheckPatchFormatted(
912 input_api, output_api))
charujain9893e252017-09-14 11:33:22913 results.extend(CheckNativeApiHeaderChanges(input_api, output_api))
Artem Titova04d1402018-05-11 09:23:00914 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 06:48:18920 results.extend(CheckGnChanges(input_api, output_api))
Artem Titova04d1402018-05-11 09:23:00921 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 11:33:22925 results.extend(RunPythonTests(input_api, output_api))
Artem Titova04d1402018-05-11 09:23:00926 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 10:21:36933 input_api, output_api, non_third_party_sources))
Mirko Bonadei9ce800d2019-02-05 15:48:13934 results.extend(CheckNoTestCaseUsageIsAdded(
935 input_api, output_api, non_third_party_sources))
Mirko Bonadei7e4ee6e2018-09-28 09:45:23936 results.extend(CheckAddedDepsHaveTargetApprovals(input_api, output_api))
Mirko Bonadeia418e672018-10-24 11:57:25937 results.extend(CheckApiDepsFileIsUpToDate(input_api, output_api))
tzika06bf852018-11-15 11:37:35938 results.extend(CheckAbslMemoryInclude(
939 input_api, output_api, non_third_party_sources))
Mirko Bonadei9fa8ef12019-09-17 17:14:13940 results.extend(CheckBannedAbslMakeUnique(
941 input_api, output_api, non_third_party_sources))
Mirko Bonadeia418e672018-10-24 11:57:25942 return results
943
944
945def CheckApiDepsFileIsUpToDate(input_api, output_api):
Mirko Bonadei90490372018-10-26 11:17:47946 """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 11:57:25955 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 12:36:42961 dirs_to_skip = set(['api', 'docs'])
Mirko Bonadeia418e672018-10-24 11:57:25962
Mirko Bonadei90490372018-10-26 11:17:47963 # 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 12:36:42968 if (path_tokens[0] not in dirs_to_skip and
Mirko Bonadei90490372018-10-26 11:17:47969 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 11:57:25972
Mirko Bonadei90490372018-10-26 11:17:47973 missing_include_rules = set()
974 for p in dirs_to_check:
Mirko Bonadeia418e672018-10-24 11:57:25975 rule = '-%s' % p
976 if rule not in include_rules:
Mirko Bonadei90490372018-10-26 11:17:47977 missing_include_rules.add(rule)
978
Mirko Bonadeia418e672018-10-24 11:57:25979 if missing_include_rules:
Mirko Bonadei90490372018-10-26 11:17:47980 error_msg = [
981 'include_rules = [\n',
982 ' ...\n',
983 ]
Mirko Bonadeia418e672018-10-24 11:57:25984
Mirko Bonadei90490372018-10-26 11:17:47985 for r in sorted(missing_include_rules):
986 error_msg.append(' "%s",\n' % str(r))
Mirko Bonadeia418e672018-10-24 11:57:25987
Mirko Bonadei90490372018-10-26 11:17:47988 error_msg.append(' ...\n')
989 error_msg.append(']\n')
990
Mirko Bonadeia418e672018-10-24 11:57:25991 results.append(output_api.PresubmitError(
Mirko Bonadei90490372018-10-26 11:17:47992 '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:23997 return results
andrew@webrtc.org2442de12012-01-23 17:45:41998
Mirko Bonadei9fa8ef12019-09-17 17:14:13999def 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 11:37:351018def 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 17:14:131031 if 'absl::WrapUnique' in line:
tzika06bf852018-11-15 11:37:351032 files.append(f)
1033 break
1034
1035 if len(files):
1036 return [output_api.PresubmitError(
Mirko Bonadei9fa8ef12019-09-17 17:14:131037 '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 11:37:351040 files)]
1041 return []
kjellander@webrtc.orge4158642014-08-06 09:11:181042
andrew@webrtc.org53df1362012-01-26 21:24:231043def CheckChangeOnUpload(input_api, output_api):
1044 results = []
charujain9893e252017-09-14 11:33:221045 results.extend(CommonChecks(input_api, output_api))
Oleh Prypin920b6532017-10-05 09:28:511046 results.extend(CheckGnGen(input_api, output_api))
Henrik Kjellander57e5fd22015-05-25 10:55:391047 results.extend(
1048 input_api.canned_checks.CheckGNFormatted(input_api, output_api))
niklase@google.comda159d62011-05-30 11:51:341049 return results
1050
kjellander@webrtc.orge4158642014-08-06 09:11:181051
andrew@webrtc.org2442de12012-01-23 17:45:411052def CheckChangeOnCommit(input_api, output_api):
niklase@google.com1198db92011-06-09 07:07:241053 results = []
charujain9893e252017-09-14 11:33:221054 results.extend(CommonChecks(input_api, output_api))
1055 results.extend(VerifyNativeApiHeadersListIsValid(input_api, output_api))
Artem Titov42f0d782018-06-27 11:23:171056 results.extend(input_api.canned_checks.CheckOwners(input_api, output_api))
andrew@webrtc.org53df1362012-01-26 21:24:231057 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 11:33:221061 results.extend(CheckChangeHasBugField(input_api, output_api))
1062 results.extend(CheckCommitMessageBugEntry(input_api, output_api))
kjellander@webrtc.org12cb88c2014-02-13 11:53:431063 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:241066 return results
mbonadei74973ed2017-05-09 14:58:051067
1068
Artem Titova04d1402018-05-11 09:23:001069def CheckOrphanHeaders(input_api, output_api, source_file_filter):
mbonadei74973ed2017-05-09 14:58:051070 # 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 10:08:561073 error_msg = """{} should be listed in {}."""
mbonadei74973ed2017-05-09 14:58:051074 results = []
Patrik Höglund7e60de22018-01-09 13:22:001075 orphan_blacklist = [
1076 os.path.join('tools_webrtc', 'ios', 'SDK'),
1077 ]
Oleh Prypin2f33a562017-10-04 18:17:541078 with _AddToPath(input_api.os_path.join(
1079 input_api.PresubmitLocalPath(), 'tools_webrtc', 'presubmit_checks_lib')):
mbonadei74973ed2017-05-09 14:58:051080 from check_orphan_headers import GetBuildGnPathFromFilePath
1081 from check_orphan_headers import IsHeaderInBuildGn
mbonadei74973ed2017-05-09 14:58:051082
Artem Titova04d1402018-05-11 09:23:001083 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 13:22:001086 if f.LocalPath().endswith('.h'):
mbonadei74973ed2017-05-09 14:58:051087 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 10:08:561094 f.LocalPath(), os.path.relpath(gn_file_path))))
mbonadei74973ed2017-05-09 14:58:051095 return results
Mirko Bonadei960fd5b2017-06-29 12:59:361096
1097
Artem Titove92675b2018-05-22 08:21:271098def CheckNewlineAtTheEndOfProtoFiles(input_api, output_api, source_file_filter):
Mirko Bonadei960fd5b2017-06-29 12:59:361099 """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 09:23:001102 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 12:59:361105 file_path = f.LocalPath()
1106 with open(file_path) as f:
1107 lines = f.readlines()
Mirko Bonadeia730c1c2017-09-18 09:33:131108 if len(lines) > 0 and not lines[-1].endswith('\n'):
Mirko Bonadei960fd5b2017-06-29 12:59:361109 results.append(output_api.PresubmitError(error_msg.format(file_path)))
1110 return results
Mirko Bonadei7e4ee6e2018-09-28 09:45:231111
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 []