blob: f01338f2ef8830df5f59744fcb57018f2d14c3c4 [file] [log] [blame]
Christoffer Jansson4e8a7732022-02-08 09:01:12 +01001#!/usr/bin/env vpython3
2
andrew@webrtc.org2442de12012-01-23 17:45:41 +00003# Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
4#
5# Use of this source code is governed by a BSD-style license
6# that can be found in the LICENSE file in the root of the source
7# tree. An additional intellectual property rights grant can be found
8# in the file PATENTS. All contributing project authors may
9# be found in the AUTHORS file in the root of the source tree.
niklase@google.comda159d62011-05-30 11:51:34 +000010
kjellander7439f972016-12-05 22:47:46 -080011import json
kjellander@webrtc.orgaefe61a2014-12-08 13:00:30 +000012import os
kjellander@webrtc.org85759802013-10-22 16:47:40 +000013import re
kjellander@webrtc.org3bd41562014-09-01 11:06:37 +000014import sys
Mirko Bonadei4dc4e252017-09-19 13:49:16 +020015from collections import defaultdict
Oleh Prypin2f33a562017-10-04 20:17:54 +020016from contextlib import contextmanager
kjellander@webrtc.org85759802013-10-22 16:47:40 +000017
Christoffer Jansson4e8a7732022-02-08 09:01:12 +010018# Runs PRESUBMIT.py in py3 mode by git cl presubmit.
19USE_PYTHON3 = True
20
oprypin2aa463f2017-03-23 03:17:02 -070021# Files and directories that are *skipped* by cpplint in the presubmit script.
Mirko Bonadeifc17a782020-06-30 14:31:37 +020022CPPLINT_EXCEPTIONS = [
Mirko Bonadei8cc66952020-10-30 10:13:45 +010023 'api/video_codecs/video_decoder.h',
24 'common_types.cc',
25 'common_types.h',
26 'examples/objc',
27 'media/base/stream_params.h',
28 'media/base/video_common.h',
Florent Castelli22379fc2021-04-08 15:06:09 +020029 'media/sctp/usrsctp_transport.cc',
Mirko Bonadei8cc66952020-10-30 10:13:45 +010030 'modules/audio_coding',
31 'modules/audio_device',
32 'modules/audio_processing',
33 'modules/desktop_capture',
34 'modules/include/module_common_types.h',
35 'modules/utility',
36 'modules/video_capture',
37 'p2p/base/pseudo_tcp.cc',
38 'p2p/base/pseudo_tcp.h',
Christoffer Jansson4e8a7732022-02-08 09:01:12 +010039 'PRESUBMIT.py',
40 'presubmit_test_mocks.py',
41 'presubmit_test.py',
Mirko Bonadei8cc66952020-10-30 10:13:45 +010042 'rtc_base',
43 'sdk/android/src/jni',
44 'sdk/objc',
45 'system_wrappers',
46 'test',
47 'tools_webrtc',
48 'voice_engine',
kjellander@webrtc.org0fcaf992015-11-26 15:24:52 +010049]
50
jbauchc4e3ead2016-02-19 00:25:55 -080051# These filters will always be removed, even if the caller specifies a filter
52# set, as they are problematic or broken in some way.
53#
54# Justifications for each filter:
55# - build/c++11 : Rvalue ref checks are unreliable (false positives),
Mirko Bonadeifc17a782020-06-30 14:31:37 +020056# include file and feature blocklists are
jbauchc4e3ead2016-02-19 00:25:55 -080057# google3-specific.
Mirko Bonadeie92e2862020-05-29 15:23:09 +020058# - runtime/references : Mutable references are not banned by the Google
59# C++ style guide anymore (starting from May 2020).
kjellandere5a87a52016-04-27 02:32:12 -070060# - whitespace/operators: Same as above (doesn't seem sufficient to eliminate
61# all move-related errors).
Mirko Bonadeifc17a782020-06-30 14:31:37 +020062DISABLED_LINT_FILTERS = [
Mirko Bonadei8cc66952020-10-30 10:13:45 +010063 '-build/c++11',
64 '-runtime/references',
65 '-whitespace/operators',
jbauchc4e3ead2016-02-19 00:25:55 -080066]
67
kjellanderfd595232015-12-04 02:44:09 -080068# List of directories of "supported" native APIs. That means changes to headers
69# will be done in a compatible way following this scheme:
70# 1. Non-breaking changes are made.
71# 2. The old APIs as marked as deprecated (with comments).
72# 3. Deprecation is announced to discuss-webrtc@googlegroups.com and
73# webrtc-users@google.com (internal list).
74# 4. (later) The deprecated APIs are removed.
kjellander53047c92015-12-02 23:56:14 -080075NATIVE_API_DIRS = (
Mirko Bonadei8cc66952020-10-30 10:13:45 +010076 'api', # All subdirectories of api/ are included as well.
77 'media/base',
78 'media/engine',
79 'modules/audio_device/include',
80 'pc',
kjellanderdd705472016-06-09 11:17:27 -070081)
Mirko Bonadei4dc4e252017-09-19 13:49:16 +020082
kjellanderdd705472016-06-09 11:17:27 -070083# These directories should not be used but are maintained only to avoid breaking
84# some legacy downstream code.
85LEGACY_API_DIRS = (
Mirko Bonadei8cc66952020-10-30 10:13:45 +010086 'common_audio/include',
87 'modules/audio_coding/include',
88 'modules/audio_processing/include',
89 'modules/congestion_controller/include',
90 'modules/include',
91 'modules/remote_bitrate_estimator/include',
92 'modules/rtp_rtcp/include',
93 'modules/rtp_rtcp/source',
94 'modules/utility/include',
95 'modules/video_coding/codecs/h264/include',
96 'modules/video_coding/codecs/vp8/include',
97 'modules/video_coding/codecs/vp9/include',
98 'modules/video_coding/include',
99 'rtc_base',
100 'system_wrappers/include',
kjellander53047c92015-12-02 23:56:14 -0800101)
Mirko Bonadei4dc4e252017-09-19 13:49:16 +0200102
Karl Wibergd4f01c12017-11-10 10:55:45 +0100103# NOTE: The set of directories in API_DIRS should be the same as those
104# listed in the table in native-api.md.
kjellanderdd705472016-06-09 11:17:27 -0700105API_DIRS = NATIVE_API_DIRS[:] + LEGACY_API_DIRS[:]
kjellander53047c92015-12-02 23:56:14 -0800106
Mirko Bonadei4dc4e252017-09-19 13:49:16 +0200107# TARGET_RE matches a GN target, and extracts the target name and the contents.
Mirko Bonadei2dcf3482020-06-05 14:30:41 +0200108TARGET_RE = re.compile(
Mirko Bonadei8cc66952020-10-30 10:13:45 +0100109 r'(?P<indent>\s*)(?P<target_type>\w+)\("(?P<target_name>\w+)"\) {'
110 r'(?P<target_contents>.*?)'
111 r'(?P=indent)}', re.MULTILINE | re.DOTALL)
Mirko Bonadei4dc4e252017-09-19 13:49:16 +0200112
113# SOURCES_RE matches a block of sources inside a GN target.
114SOURCES_RE = re.compile(r'sources \+?= \[(?P<sources>.*?)\]',
115 re.MULTILINE | re.DOTALL)
116
Mirko Bonadei2dcf3482020-06-05 14:30:41 +0200117# DEPS_RE matches a block of sources inside a GN target.
118DEPS_RE = re.compile(r'\bdeps \+?= \[(?P<deps>.*?)\]',
119 re.MULTILINE | re.DOTALL)
120
Philipp Hancke0c2a9ca2021-08-11 12:00:27 +0200121# FILE_PATH_RE matches a file path.
Mirko Bonadei4dc4e252017-09-19 13:49:16 +0200122FILE_PATH_RE = re.compile(r'"(?P<file_path>(\w|\/)+)(?P<extension>\.\w+)"')
123
kjellander53047c92015-12-02 23:56:14 -0800124
Mirko Bonadeid8665442018-09-04 12:17:27 +0200125def FindSrcDirPath(starting_dir):
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100126 """Returns the abs path to the src/ dir of the project."""
127 src_dir = starting_dir
128 while os.path.basename(src_dir) != 'src':
129 src_dir = os.path.normpath(os.path.join(src_dir, os.pardir))
130 return src_dir
Mirko Bonadeid8665442018-09-04 12:17:27 +0200131
132
Oleh Prypin2f33a562017-10-04 20:17:54 +0200133@contextmanager
134def _AddToPath(*paths):
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100135 original_sys_path = sys.path
136 sys.path.extend(paths)
137 try:
138 yield
139 finally:
140 # Restore sys.path to what it was before.
141 sys.path = original_sys_path
ehmaldonado4fb97462017-01-30 05:27:22 -0800142
143
charujain9893e252017-09-14 13:33:22 +0200144def VerifyNativeApiHeadersListIsValid(input_api, output_api):
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100145 """Ensures the list of native API header directories is up to date."""
146 non_existing_paths = []
147 native_api_full_paths = [
148 input_api.os_path.join(input_api.PresubmitLocalPath(), *path.split('/'))
149 for path in API_DIRS
150 ]
151 for path in native_api_full_paths:
152 if not os.path.isdir(path):
153 non_existing_paths.append(path)
154 if non_existing_paths:
155 return [
156 output_api.PresubmitError(
157 'Directories to native API headers have changed which has made '
158 'the list in PRESUBMIT.py outdated.\nPlease update it to the '
159 'current location of our native APIs.', non_existing_paths)
Mirko Bonadei8cc66952020-10-30 10:13:45 +0100160 ]
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100161 return []
kjellander53047c92015-12-02 23:56:14 -0800162
Artem Titove92675b2018-05-22 10:21:27 +0200163
kjellanderc88b5d52017-04-05 06:42:43 -0700164API_CHANGE_MSG = """
kwibergeb133022016-04-07 07:41:48 -0700165You seem to be changing native API header files. Please make sure that you:
oprypin375b9ac2017-02-13 04:13:23 -0800166 1. Make compatible changes that don't break existing clients. Usually
167 this is done by keeping the existing method signatures unchanged.
Danil Chapovalov7013b3b2021-02-22 14:31:26 +0100168 2. Mark the old stuff as deprecated (use the ABSL_DEPRECATED macro).
kwibergeb133022016-04-07 07:41:48 -0700169 3. Create a timeline and plan for when the deprecated stuff will be
170 removed. (The amount of time we give users to change their code
171 should be informed by how much work it is for them. If they just
172 need to replace one name with another or something equally
173 simple, 1-2 weeks might be good; if they need to do serious work,
174 up to 3 months may be called for.)
175 4. Update/inform existing downstream code owners to stop using the
176 deprecated stuff. (Send announcements to
177 discuss-webrtc@googlegroups.com and webrtc-users@google.com.)
178 5. Remove the deprecated stuff, once the agreed-upon amount of time
179 has passed.
180Related files:
181"""
kjellander53047c92015-12-02 23:56:14 -0800182
Artem Titove92675b2018-05-22 10:21:27 +0200183
charujain9893e252017-09-14 13:33:22 +0200184def CheckNativeApiHeaderChanges(input_api, output_api):
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100185 """Checks to remind proper changing of native APIs."""
186 files = []
187 source_file_filter = lambda x: input_api.FilterSourceFile(
188 x, files_to_check=[r'.+\.(gn|gni|h)$'])
189 for f in input_api.AffectedSourceFiles(source_file_filter):
190 for path in API_DIRS:
191 dn = os.path.dirname(f.LocalPath())
192 if path == 'api':
193 # Special case: Subdirectories included.
194 if dn == 'api' or dn.startswith('api/'):
195 files.append(f.LocalPath())
196 else:
197 # Normal case: Subdirectories not included.
198 if dn == path:
199 files.append(f.LocalPath())
kjellander53047c92015-12-02 23:56:14 -0800200
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100201 if files:
202 return [output_api.PresubmitNotifyResult(API_CHANGE_MSG, files)]
203 return []
kjellander53047c92015-12-02 23:56:14 -0800204
kjellander@webrtc.org0fcaf992015-11-26 15:24:52 +0100205
Mirko Bonadei8cc66952020-10-30 10:13:45 +0100206def CheckNoIOStreamInHeaders(input_api, output_api, source_file_filter):
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100207 """Checks to make sure no .h files include <iostream>."""
208 files = []
209 pattern = input_api.re.compile(r'^#include\s*<iostream>',
210 input_api.re.MULTILINE)
211 file_filter = lambda x: (input_api.FilterSourceFile(x) and source_file_filter(
212 x))
213 for f in input_api.AffectedSourceFiles(file_filter):
214 if not f.LocalPath().endswith('.h'):
215 continue
216 contents = input_api.ReadFile(f)
217 if pattern.search(contents):
218 files.append(f)
kjellander@webrtc.org51198f12012-02-21 17:53:46 +0000219
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100220 if len(files) > 0:
221 return [
222 output_api.PresubmitError(
223 'Do not #include <iostream> in header files, since it inserts '
224 'static initialization into every file including the header. '
225 'Instead, #include <ostream>. See http://crbug.com/94794', files)
226 ]
227 return []
kjellander@webrtc.org51198f12012-02-21 17:53:46 +0000228
kjellander@webrtc.orge4158642014-08-06 09:11:18 +0000229
Mirko Bonadei8cc66952020-10-30 10:13:45 +0100230def CheckNoPragmaOnce(input_api, output_api, source_file_filter):
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100231 """Make sure that banned functions are not used."""
232 files = []
233 pattern = input_api.re.compile(r'^#pragma\s+once', input_api.re.MULTILINE)
234 file_filter = lambda x: (input_api.FilterSourceFile(x) and source_file_filter(
235 x))
236 for f in input_api.AffectedSourceFiles(file_filter):
237 if not f.LocalPath().endswith('.h'):
238 continue
239 contents = input_api.ReadFile(f)
240 if pattern.search(contents):
241 files.append(f)
kjellander6aeef742017-02-20 01:13:18 -0800242
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100243 if files:
244 return [
245 output_api.PresubmitError(
246 'Do not use #pragma once in header files.\n'
247 'See http://www.chromium.org/developers/coding-style'
248 '#TOC-File-headers', files)
249 ]
250 return []
251
kjellander6aeef742017-02-20 01:13:18 -0800252
Byoungchan Lee94f2ef22021-07-01 22:21:44 +0900253def CheckNoFRIEND_TEST(# pylint: disable=invalid-name
Mirko Bonadei8cc66952020-10-30 10:13:45 +0100254 input_api,
Byoungchan Lee94f2ef22021-07-01 22:21:44 +0900255 output_api,
Mirko Bonadei8cc66952020-10-30 10:13:45 +0100256 source_file_filter):
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100257 """Make sure that gtest's FRIEND_TEST() macro is not used, the
kjellander@webrtc.org51198f12012-02-21 17:53:46 +0000258 FRIEND_TEST_ALL_PREFIXES() macro from testsupport/gtest_prod_util.h should be
259 used instead since that allows for FLAKY_, FAILS_ and DISABLED_ prefixes."""
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100260 problems = []
kjellander@webrtc.org51198f12012-02-21 17:53:46 +0000261
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100262 file_filter = lambda f: (f.LocalPath().endswith(('.cc', '.h')) and
263 source_file_filter(f))
264 for f in input_api.AffectedFiles(file_filter=file_filter):
265 for line_num, line in f.ChangedContents():
266 if 'FRIEND_TEST(' in line:
267 problems.append(' %s:%d' % (f.LocalPath(), line_num))
kjellander@webrtc.org51198f12012-02-21 17:53:46 +0000268
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100269 if not problems:
270 return []
271 return [
272 output_api.PresubmitPromptWarning(
273 'WebRTC\'s code should not use gtest\'s FRIEND_TEST() macro. '
274 'Include testsupport/gtest_prod_util.h and use '
275 'FRIEND_TEST_ALL_PREFIXES() instead.\n' + '\n'.join(problems))
276 ]
kjellander@webrtc.org51198f12012-02-21 17:53:46 +0000277
kjellander@webrtc.orge4158642014-08-06 09:11:18 +0000278
Mirko Bonadeifc17a782020-06-30 14:31:37 +0200279def IsLintDisabled(disabled_paths, file_path):
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100280 """ Checks if a file is disabled for lint check."""
281 for path in disabled_paths:
282 if file_path == path or os.path.dirname(file_path).startswith(path):
283 return True
284 return False
kjellander@webrtc.org0fcaf992015-11-26 15:24:52 +0100285
286
charujain9893e252017-09-14 13:33:22 +0200287def CheckApprovedFilesLintClean(input_api, output_api,
Artem Titova04d1402018-05-11 11:23:00 +0200288 source_file_filter=None):
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100289 """Checks that all new or non-exempt .cc and .h files pass cpplint.py.
charujain9893e252017-09-14 13:33:22 +0200290 This check is based on CheckChangeLintsClean in
kjellander@webrtc.org51198f12012-02-21 17:53:46 +0000291 depot_tools/presubmit_canned_checks.py but has less filters and only checks
292 added files."""
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100293 result = []
kjellander@webrtc.org51198f12012-02-21 17:53:46 +0000294
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100295 # Initialize cpplint.
296 import cpplint
297 # Access to a protected member _XX of a client class
298 # pylint: disable=W0212
299 cpplint._cpplint_state.ResetErrorCounts()
kjellander@webrtc.org51198f12012-02-21 17:53:46 +0000300
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100301 lint_filters = cpplint._Filters()
302 lint_filters.extend(DISABLED_LINT_FILTERS)
303 cpplint._SetFilters(','.join(lint_filters))
jbauchc4e3ead2016-02-19 00:25:55 -0800304
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100305 # Create a platform independent exempt list for cpplint.
306 disabled_paths = [
307 input_api.os_path.join(*path.split('/')) for path in CPPLINT_EXCEPTIONS
308 ]
kjellander@webrtc.org0fcaf992015-11-26 15:24:52 +0100309
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100310 # Use the strictest verbosity level for cpplint.py (level 1) which is the
311 # default when running cpplint.py from command line. To make it possible to
312 # work with not-yet-converted code, we're only applying it to new (or
313 # moved/renamed) files and files not listed in CPPLINT_EXCEPTIONS.
314 verbosity_level = 1
315 files = []
316 for f in input_api.AffectedSourceFiles(source_file_filter):
317 # Note that moved/renamed files also count as added.
318 if f.Action() == 'A' or not IsLintDisabled(disabled_paths, f.LocalPath()):
319 files.append(f.AbsoluteLocalPath())
mflodman@webrtc.org2a452092012-07-01 05:55:23 +0000320
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100321 for file_name in files:
322 cpplint.ProcessFile(file_name, verbosity_level)
kjellander@webrtc.org51198f12012-02-21 17:53:46 +0000323
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100324 if cpplint._cpplint_state.error_count > 0:
325 if input_api.is_committing:
326 res_type = output_api.PresubmitError
327 else:
328 res_type = output_api.PresubmitPromptWarning
329 result = [res_type('Changelist failed cpplint.py check.')]
kjellander@webrtc.org51198f12012-02-21 17:53:46 +0000330
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100331 return result
kjellander@webrtc.org51198f12012-02-21 17:53:46 +0000332
Artem Titove92675b2018-05-22 10:21:27 +0200333
charujain9893e252017-09-14 13:33:22 +0200334def CheckNoSourcesAbove(input_api, gn_files, output_api):
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100335 # Disallow referencing source files with paths above the GN file location.
336 source_pattern = input_api.re.compile(r' +sources \+?= \[(.*?)\]',
337 re.MULTILINE | re.DOTALL)
338 file_pattern = input_api.re.compile(r'"((\.\./.*?)|(//.*?))"')
339 violating_gn_files = set()
340 violating_source_entries = []
341 for gn_file in gn_files:
342 contents = input_api.ReadFile(gn_file)
343 for source_block_match in source_pattern.finditer(contents):
344 # Find all source list entries starting with ../ in the source block
345 # (exclude overrides entries).
346 for file_list_match in file_pattern.finditer(source_block_match.group(1)):
347 source_file = file_list_match.group(1)
348 if 'overrides/' not in source_file:
349 violating_source_entries.append(source_file)
350 violating_gn_files.add(gn_file)
351 if violating_gn_files:
352 return [
353 output_api.PresubmitError(
354 'Referencing source files above the directory of the GN file '
355 'is not allowed. Please introduce new GN targets in the proper '
356 'location instead.\n'
357 'Invalid source entries:\n'
358 '%s\n'
359 'Violating GN files:' % '\n'.join(violating_source_entries),
360 items=violating_gn_files)
361 ]
362 return []
ehmaldonado5b1ba082016-09-02 05:51:08 -0700363
Artem Titove92675b2018-05-22 10:21:27 +0200364
Mirko Bonadei2dcf3482020-06-05 14:30:41 +0200365def CheckAbseilDependencies(input_api, gn_files, output_api):
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100366 """Checks that Abseil dependencies are declared in `absl_deps`."""
367 absl_re = re.compile(r'third_party/abseil-cpp', re.MULTILINE | re.DOTALL)
368 target_types_to_check = [
369 'rtc_library',
370 'rtc_source_set',
371 'rtc_static_library',
372 'webrtc_fuzzer_test',
373 ]
374 error_msg = ('Abseil dependencies in target "%s" (file: %s) '
375 'should be moved to the "absl_deps" parameter.')
376 errors = []
Mirko Bonadei2dcf3482020-06-05 14:30:41 +0200377
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100378 # pylint: disable=too-many-nested-blocks
379 for gn_file in gn_files:
380 gn_file_content = input_api.ReadFile(gn_file)
381 for target_match in TARGET_RE.finditer(gn_file_content):
382 target_type = target_match.group('target_type')
383 target_name = target_match.group('target_name')
384 target_contents = target_match.group('target_contents')
385 if target_type in target_types_to_check:
386 for deps_match in DEPS_RE.finditer(target_contents):
387 deps = deps_match.group('deps').splitlines()
388 for dep in deps:
389 if re.search(absl_re, dep):
390 errors.append(
391 output_api.PresubmitError(error_msg %
392 (target_name, gn_file.LocalPath())))
393 break # no need to warn more than once per target
394 return errors
Mirko Bonadei2dcf3482020-06-05 14:30:41 +0200395
396
Mirko Bonadei4dc4e252017-09-19 13:49:16 +0200397def CheckNoMixingSources(input_api, gn_files, output_api):
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100398 """Disallow mixing C, C++ and Obj-C/Obj-C++ in the same target.
Mirko Bonadei4dc4e252017-09-19 13:49:16 +0200399
400 See bugs.webrtc.org/7743 for more context.
401 """
Artem Titove92675b2018-05-22 10:21:27 +0200402
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100403 def _MoreThanOneSourceUsed(*sources_lists):
404 sources_used = 0
405 for source_list in sources_lists:
406 if len(source_list) > 0:
407 sources_used += 1
408 return sources_used > 1
Mirko Bonadei4dc4e252017-09-19 13:49:16 +0200409
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100410 errors = defaultdict(lambda: [])
411 for gn_file in gn_files:
412 gn_file_content = input_api.ReadFile(gn_file)
413 for target_match in TARGET_RE.finditer(gn_file_content):
414 # list_of_sources is a list of tuples of the form
415 # (c_files, cc_files, objc_files) that keeps track of all the
416 # sources defined in a target. A GN target can have more that
417 # on definition of sources (since it supports if/else statements).
418 # E.g.:
419 # rtc_static_library("foo") {
420 # if (is_win) {
421 # sources = [ "foo.cc" ]
422 # } else {
423 # sources = [ "foo.mm" ]
424 # }
425 # }
426 # This is allowed and the presubmit check should support this case.
427 list_of_sources = []
428 c_files = []
429 cc_files = []
430 objc_files = []
431 target_name = target_match.group('target_name')
432 target_contents = target_match.group('target_contents')
433 for sources_match in SOURCES_RE.finditer(target_contents):
434 if '+=' not in sources_match.group(0):
435 if c_files or cc_files or objc_files:
Mirko Bonadei4dc4e252017-09-19 13:49:16 +0200436 list_of_sources.append((c_files, cc_files, objc_files))
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100437 c_files = []
438 cc_files = []
439 objc_files = []
440 for file_match in FILE_PATH_RE.finditer(sources_match.group(1)):
441 file_path = file_match.group('file_path')
442 extension = file_match.group('extension')
443 if extension == '.c':
444 c_files.append(file_path + extension)
445 if extension == '.cc':
446 cc_files.append(file_path + extension)
447 if extension in ['.m', '.mm']:
448 objc_files.append(file_path + extension)
449 list_of_sources.append((c_files, cc_files, objc_files))
450 for c_files_list, cc_files_list, objc_files_list in list_of_sources:
451 if _MoreThanOneSourceUsed(c_files_list, cc_files_list, objc_files_list):
452 all_sources = sorted(c_files_list + cc_files_list + objc_files_list)
453 errors[gn_file.LocalPath()].append((target_name, all_sources))
454 if errors:
455 return [
456 output_api.PresubmitError(
457 'GN targets cannot mix .c, .cc and .m (or .mm) source files.\n'
458 'Please create a separate target for each collection of '
459 'sources.\n'
460 'Mixed sources: \n'
461 '%s\n'
462 'Violating GN files:\n%s\n' %
463 (json.dumps(errors, indent=2), '\n'.join(list(errors.keys()))))
464 ]
465 return []
kjellander7439f972016-12-05 22:47:46 -0800466
Artem Titove92675b2018-05-22 10:21:27 +0200467
charujain9893e252017-09-14 13:33:22 +0200468def CheckNoPackageBoundaryViolations(input_api, gn_files, output_api):
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100469 cwd = input_api.PresubmitLocalPath()
470 with _AddToPath(
471 input_api.os_path.join(cwd, 'tools_webrtc', 'presubmit_checks_lib')):
472 from check_package_boundaries import CheckPackageBoundaries
473 build_files = [os.path.join(cwd, gn_file.LocalPath()) for gn_file in gn_files]
474 errors = CheckPackageBoundaries(cwd, build_files)[:5]
475 if errors:
476 return [
477 output_api.PresubmitError(
478 'There are package boundary violations in the following GN '
479 'files:',
480 long_text='\n\n'.join(str(err) for err in errors))
Mirko Bonadei8cc66952020-10-30 10:13:45 +0100481 ]
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100482 return []
ehmaldonado4fb97462017-01-30 05:27:22 -0800483
Mirko Bonadeia51bbd82018-03-08 16:15:45 +0100484
Mirko Bonadeif0e0d752018-07-04 08:48:18 +0200485def _ReportFileAndLine(filename, line_num):
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100486 """Default error formatter for _FindNewViolationsOfRule."""
487 return '%s (line %s)' % (filename, line_num)
Mirko Bonadeia51bbd82018-03-08 16:15:45 +0100488
489
Mirko Bonadei8cc66952020-10-30 10:13:45 +0100490def CheckNoWarningSuppressionFlagsAreAdded(gn_files,
491 input_api,
492 output_api,
Mirko Bonadeif0e0d752018-07-04 08:48:18 +0200493 error_formatter=_ReportFileAndLine):
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100494 """Ensure warning suppression flags are not added without a reason."""
495 msg = ('Usage of //build/config/clang:extra_warnings is discouraged '
496 'in WebRTC.\n'
497 'If you are not adding this code (e.g. you are just moving '
498 'existing code) or you want to add an exception,\n'
499 'you can add a comment on the line that causes the problem:\n\n'
500 '"-Wno-odr" # no-presubmit-check TODO(bugs.webrtc.org/BUG_ID)\n'
501 '\n'
502 'Affected files:\n')
503 errors = [] # 2-element tuples with (file, line number)
504 clang_warn_re = input_api.re.compile(r'//build/config/clang:extra_warnings')
505 # pylint: disable-next=fixme
506 no_presubmit_re = input_api.re.compile(
507 r'# no-presubmit-check TODO\(bugs\.webrtc\.org/\d+\)')
508 for f in gn_files:
509 for line_num, line in f.ChangedContents():
510 if clang_warn_re.search(line) and not no_presubmit_re.search(line):
511 errors.append(error_formatter(f.LocalPath(), line_num))
512 if errors:
513 return [output_api.PresubmitError(msg, errors)]
514 return []
Mirko Bonadeif0e0d752018-07-04 08:48:18 +0200515
Mirko Bonadei9ce800d2019-02-05 16:48:13 +0100516
Mirko Bonadei8cc66952020-10-30 10:13:45 +0100517def CheckNoTestCaseUsageIsAdded(input_api,
518 output_api,
519 source_file_filter,
Mirko Bonadei9ce800d2019-02-05 16:48:13 +0100520 error_formatter=_ReportFileAndLine):
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100521 error_msg = ('Usage of legacy GoogleTest API detected!\nPlease use the '
522 'new API: https://github.com/google/googletest/blob/master/'
523 'googletest/docs/primer.md#beware-of-the-nomenclature.\n'
524 'Affected files:\n')
525 errors = [] # 2-element tuples with (file, line number)
526 test_case_re = input_api.re.compile(r'TEST_CASE')
527 file_filter = lambda f: (source_file_filter(f) and f.LocalPath().endswith(
528 '.cc'))
529 for f in input_api.AffectedSourceFiles(file_filter):
530 for line_num, line in f.ChangedContents():
531 if test_case_re.search(line):
532 errors.append(error_formatter(f.LocalPath(), line_num))
533 if errors:
534 return [output_api.PresubmitError(error_msg, errors)]
535 return []
Mirko Bonadei9ce800d2019-02-05 16:48:13 +0100536
537
Mirko Bonadei8cc66952020-10-30 10:13:45 +0100538def CheckNoStreamUsageIsAdded(input_api,
539 output_api,
Artem Titov739351d2018-05-11 12:21:36 +0200540 source_file_filter,
Mirko Bonadeif0e0d752018-07-04 08:48:18 +0200541 error_formatter=_ReportFileAndLine):
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100542 """Make sure that no more dependencies on stringstream are added."""
543 error_msg = ('Usage of <sstream>, <istream> and <ostream> in WebRTC is '
544 'deprecated.\n'
545 'This includes the following types:\n'
546 'std::istringstream, std::ostringstream, std::wistringstream, '
547 'std::wostringstream,\n'
548 'std::wstringstream, std::ostream, std::wostream, std::istream,'
549 'std::wistream,\n'
550 'std::iostream, std::wiostream.\n'
551 'If you are not adding this code (e.g. you are just moving '
552 'existing code),\n'
553 'you can add a comment on the line that causes the problem:\n\n'
554 '#include <sstream> // no-presubmit-check TODO(webrtc:8982)\n'
555 'std::ostream& F() { // no-presubmit-check TODO(webrtc:8982)\n'
556 '\n'
557 'If you are adding new code, consider using '
558 'rtc::SimpleStringBuilder\n'
559 '(in rtc_base/strings/string_builder.h).\n'
560 'Affected files:\n')
561 errors = [] # 2-element tuples with (file, line number)
562 include_re = input_api.re.compile(r'#include <(i|o|s)stream>')
563 usage_re = input_api.re.compile(r'std::(w|i|o|io|wi|wo|wio)(string)*stream')
564 no_presubmit_re = input_api.re.compile(
565 r'// no-presubmit-check TODO\(webrtc:8982\)')
566 file_filter = lambda x: (input_api.FilterSourceFile(x) and source_file_filter(
567 x))
Mirko Bonadei571791a2019-05-07 14:08:05 +0200568
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100569 def _IsException(file_path):
570 is_test = any(
571 file_path.endswith(x)
572 for x in ['_test.cc', '_tests.cc', '_unittest.cc', '_unittests.cc'])
573 return (file_path.startswith('examples') or file_path.startswith('test')
574 or is_test)
Patrik Höglund2ea27962020-01-13 15:10:40 +0100575
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100576 for f in input_api.AffectedSourceFiles(file_filter):
577 # Usage of stringstream is allowed under examples/ and in tests.
578 if f.LocalPath() == 'PRESUBMIT.py' or _IsException(f.LocalPath()):
579 continue
580 for line_num, line in f.ChangedContents():
581 if ((include_re.search(line) or usage_re.search(line))
582 and not no_presubmit_re.search(line)):
583 errors.append(error_formatter(f.LocalPath(), line_num))
584 if errors:
585 return [output_api.PresubmitError(error_msg, errors)]
586 return []
Mirko Bonadeia51bbd82018-03-08 16:15:45 +0100587
Artem Titove92675b2018-05-22 10:21:27 +0200588
Mirko Bonadeia05d47e2018-05-09 11:03:38 +0200589def CheckPublicDepsIsNotUsed(gn_files, input_api, output_api):
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100590 """Checks that public_deps is not used without a good reason."""
591 result = []
592 no_presubmit_check_re = input_api.re.compile(
593 r'# no-presubmit-check TODO\(webrtc:\d+\)')
594 error_msg = ('public_deps is not recommended in WebRTC BUILD.gn files '
595 'because it doesn\'t map well to downstream build systems.\n'
596 'Used in: %s (line %d).\n'
597 'If you are not adding this code (e.g. you are just moving '
598 'existing code) or you have a good reason, you can add this '
599 'comment (verbatim) on the line that causes the problem:\n\n'
600 'public_deps = [ # no-presubmit-check TODO(webrtc:8603)\n')
601 for affected_file in gn_files:
602 for (line_number, affected_line) in affected_file.ChangedContents():
603 if 'public_deps' in affected_line:
604 surpressed = no_presubmit_check_re.search(affected_line)
605 if not surpressed:
606 result.append(
607 output_api.PresubmitError(
608 error_msg % (affected_file.LocalPath(), line_number)))
609 return result
Mirko Bonadei5c1ad592017-12-12 11:52:27 +0100610
Artem Titove92675b2018-05-22 10:21:27 +0200611
Mirko Bonadei05691dd2019-10-22 07:34:24 -0700612def CheckCheckIncludesIsNotUsed(gn_files, input_api, output_api):
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100613 result = []
614 error_msg = ('check_includes overrides are not allowed since it can cause '
615 'incorrect dependencies to form. It effectively means that your '
616 'module can include any .h file without depending on its '
617 'corresponding target. There are some exceptional cases when '
618 'this is allowed: if so, get approval from a .gn owner in the '
619 'root OWNERS file.\n'
620 'Used in: %s (line %d).')
621 # pylint: disable-next=fixme
622 no_presubmit_re = input_api.re.compile(
623 r'# no-presubmit-check TODO\(bugs\.webrtc\.org/\d+\)')
624 for affected_file in gn_files:
625 for (line_number, affected_line) in affected_file.ChangedContents():
626 if ('check_includes' in affected_line
627 and not no_presubmit_re.search(affected_line)):
628 result.append(
629 output_api.PresubmitError(error_msg %
630 (affected_file.LocalPath(), line_number)))
631 return result
Patrik Höglund6f491062018-01-11 12:04:23 +0100632
Artem Titove92675b2018-05-22 10:21:27 +0200633
Mirko Bonadeif0e0d752018-07-04 08:48:18 +0200634def CheckGnChanges(input_api, output_api):
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100635 file_filter = lambda x: (input_api.FilterSourceFile(
636 x,
637 files_to_check=(r'.+\.(gn|gni)$', ),
638 files_to_skip=(r'.*/presubmit_checks_lib/testdata/.*', )))
ehmaldonado5b1ba082016-09-02 05:51:08 -0700639
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100640 gn_files = []
641 for f in input_api.AffectedSourceFiles(file_filter):
642 gn_files.append(f)
ehmaldonado5b1ba082016-09-02 05:51:08 -0700643
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100644 result = []
645 if gn_files:
646 result.extend(CheckNoSourcesAbove(input_api, gn_files, output_api))
647 result.extend(CheckNoMixingSources(input_api, gn_files, output_api))
648 result.extend(CheckAbseilDependencies(input_api, gn_files, output_api))
649 result.extend(
650 CheckNoPackageBoundaryViolations(input_api, gn_files, output_api))
651 result.extend(CheckPublicDepsIsNotUsed(gn_files, input_api, output_api))
652 result.extend(CheckCheckIncludesIsNotUsed(gn_files, input_api, output_api))
653 result.extend(
654 CheckNoWarningSuppressionFlagsAreAdded(gn_files, input_api, output_api))
655 return result
ehmaldonado5b1ba082016-09-02 05:51:08 -0700656
Artem Titove92675b2018-05-22 10:21:27 +0200657
Oleh Prypin920b6532017-10-05 11:28:51 +0200658def CheckGnGen(input_api, output_api):
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100659 """Runs `gn gen --check` with default args to detect mismatches between
Oleh Prypin920b6532017-10-05 11:28:51 +0200660 #includes and dependencies in the BUILD.gn files, as well as general build
661 errors.
662 """
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100663 with _AddToPath(
664 input_api.os_path.join(input_api.PresubmitLocalPath(), 'tools_webrtc',
665 'presubmit_checks_lib')):
666 from build_helpers import RunGnCheck
667 errors = RunGnCheck(FindSrcDirPath(input_api.PresubmitLocalPath()))[:5]
668 if errors:
669 return [
670 output_api.PresubmitPromptWarning(
671 'Some #includes do not match the build dependency graph. '
672 'Please run:\n'
673 ' gn gen --check <out_dir>',
674 long_text='\n\n'.join(errors))
675 ]
676 return []
Oleh Prypin920b6532017-10-05 11:28:51 +0200677
Artem Titove92675b2018-05-22 10:21:27 +0200678
Artem Titova04d1402018-05-11 11:23:00 +0200679def CheckUnwantedDependencies(input_api, output_api, source_file_filter):
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100680 """Runs checkdeps on #include statements added in this
kjellander@webrtc.org3bd41562014-09-01 11:06:37 +0000681 change. Breaking - rules is an error, breaking ! rules is a
682 warning.
683 """
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100684 # Copied from Chromium's src/PRESUBMIT.py.
kjellander@webrtc.org3bd41562014-09-01 11:06:37 +0000685
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100686 # We need to wait until we have an input_api object and use this
687 # roundabout construct to import checkdeps because this file is
688 # eval-ed and thus doesn't have __file__.
689 src_path = FindSrcDirPath(input_api.PresubmitLocalPath())
690 checkdeps_path = input_api.os_path.join(src_path, 'buildtools', 'checkdeps')
691 if not os.path.exists(checkdeps_path):
692 return [
693 output_api.PresubmitError(
694 'Cannot find checkdeps at %s\nHave you run "gclient sync" to '
695 'download all the DEPS entries?' % checkdeps_path)
696 ]
697 with _AddToPath(checkdeps_path):
698 import checkdeps
699 from cpp_checker import CppChecker
700 from rules import Rule
kjellander@webrtc.org3bd41562014-09-01 11:06:37 +0000701
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100702 added_includes = []
703 for f in input_api.AffectedFiles(file_filter=source_file_filter):
704 if not CppChecker.IsCppFile(f.LocalPath()):
705 continue
kjellander@webrtc.org3bd41562014-09-01 11:06:37 +0000706
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100707 changed_lines = [line for _, line in f.ChangedContents()]
708 added_includes.append([f.LocalPath(), changed_lines])
kjellander@webrtc.org3bd41562014-09-01 11:06:37 +0000709
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100710 deps_checker = checkdeps.DepsChecker(input_api.PresubmitLocalPath())
kjellander@webrtc.org3bd41562014-09-01 11:06:37 +0000711
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100712 error_descriptions = []
713 warning_descriptions = []
714 for path, rule_type, rule_description in deps_checker.CheckAddedCppIncludes(
715 added_includes):
716 description_with_path = '%s\n %s' % (path, rule_description)
717 if rule_type == Rule.DISALLOW:
718 error_descriptions.append(description_with_path)
719 else:
720 warning_descriptions.append(description_with_path)
kjellander@webrtc.org3bd41562014-09-01 11:06:37 +0000721
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100722 results = []
723 if error_descriptions:
724 results.append(
725 output_api.PresubmitError(
726 'You added one or more #includes that violate checkdeps rules.'
727 '\nCheck that the DEPS files in these locations contain valid '
728 'rules.\nSee '
729 'https://cs.chromium.org/chromium/src/buildtools/checkdeps/ '
730 'for more details about checkdeps.', error_descriptions))
731 if warning_descriptions:
732 results.append(
733 output_api.PresubmitPromptOrNotify(
734 'You added one or more #includes of files that are temporarily'
735 '\nallowed but being removed. Can you avoid introducing the\n'
736 '#include? See relevant DEPS file(s) for details and contacts.'
737 '\nSee '
738 'https://cs.chromium.org/chromium/src/buildtools/checkdeps/ '
739 'for more details about checkdeps.', warning_descriptions))
740 return results
kjellander@webrtc.org3bd41562014-09-01 11:06:37 +0000741
Artem Titove92675b2018-05-22 10:21:27 +0200742
charujain9893e252017-09-14 13:33:22 +0200743def CheckCommitMessageBugEntry(input_api, output_api):
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100744 """Check that bug entries are well-formed in commit message."""
745 bogus_bug_msg = (
746 'Bogus Bug entry: %s. Please specify the issue tracker prefix and the '
747 'issue number, separated by a colon, e.g. webrtc:123 or chromium:12345.')
748 results = []
749 for bug in input_api.change.BugsFromDescription():
750 bug = bug.strip()
751 if bug.lower() == 'none':
752 continue
753 if 'b/' not in bug and ':' not in bug:
754 try:
755 if int(bug) > 100000:
756 # Rough indicator for current chromium bugs.
757 prefix_guess = 'chromium'
758 else:
759 prefix_guess = 'webrtc'
760 results.append('Bug entry requires issue tracker prefix, e.g. %s:%s' %
761 (prefix_guess, bug))
762 except ValueError:
763 results.append(bogus_bug_msg % bug)
764 elif not (re.match(r'\w+:\d+', bug) or re.match(r'b/\d+', bug)):
765 results.append(bogus_bug_msg % bug)
766 return [output_api.PresubmitError(r) for r in results]
charujain9893e252017-09-14 13:33:22 +0200767
Artem Titove92675b2018-05-22 10:21:27 +0200768
charujain9893e252017-09-14 13:33:22 +0200769def CheckChangeHasBugField(input_api, output_api):
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100770 """Requires that the changelist is associated with a bug.
kjellanderd1e26a92016-09-19 08:11:16 -0700771
772 This check is stricter than the one in depot_tools/presubmit_canned_checks.py
Mirko Bonadei61880182017-10-12 15:12:35 +0200773 since it fails the presubmit if the bug field is missing or doesn't contain
kjellanderd1e26a92016-09-19 08:11:16 -0700774 a bug reference.
Mirko Bonadei61880182017-10-12 15:12:35 +0200775
776 This supports both 'BUG=' and 'Bug:' since we are in the process of migrating
777 to Gerrit and it encourages the usage of 'Bug:'.
kjellanderd1e26a92016-09-19 08:11:16 -0700778 """
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100779 if input_api.change.BugsFromDescription():
780 return []
781 return [
782 output_api.PresubmitError(
783 'The "Bug: [bug number]" footer is mandatory. Please create a '
784 'bug and reference it using either of:\n'
785 ' * https://bugs.webrtc.org - reference it using Bug: '
786 'webrtc:XXXX\n'
787 ' * https://crbug.com - reference it using Bug: chromium:XXXXXX')
788 ]
kjellander@webrtc.orge4158642014-08-06 09:11:18 +0000789
Artem Titove92675b2018-05-22 10:21:27 +0200790
Artem Titova04d1402018-05-11 11:23:00 +0200791def CheckJSONParseErrors(input_api, output_api, source_file_filter):
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100792 """Check that JSON files do not contain syntax errors."""
kjellander569cf942016-02-11 05:02:59 -0800793
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100794 def FilterFile(affected_file):
795 return (input_api.os_path.splitext(affected_file.LocalPath())[1] == '.json'
796 and source_file_filter(affected_file))
kjellander569cf942016-02-11 05:02:59 -0800797
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100798 def GetJSONParseError(input_api, filename):
799 try:
800 contents = input_api.ReadFile(filename)
801 input_api.json.loads(contents)
802 except ValueError as e:
803 return e
804 return None
kjellander569cf942016-02-11 05:02:59 -0800805
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100806 results = []
807 for affected_file in input_api.AffectedFiles(file_filter=FilterFile,
808 include_deletes=False):
809 parse_error = GetJSONParseError(input_api,
810 affected_file.AbsoluteLocalPath())
811 if parse_error:
812 results.append(
813 output_api.PresubmitError('%s could not be parsed: %s' %
814 (affected_file.LocalPath(), parse_error)))
815 return results
kjellander569cf942016-02-11 05:02:59 -0800816
817
charujain9893e252017-09-14 13:33:22 +0200818def RunPythonTests(input_api, output_api):
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100819 def Join(*args):
820 return input_api.os_path.join(input_api.PresubmitLocalPath(), *args)
Henrik Kjellander8d3ad822015-05-26 19:52:05 +0200821
Christoffer Jansson70098a82022-02-21 19:43:36 +0100822 excluded_files = [
Jeremy Leconte4fc9bd92022-03-18 10:21:07 +0100823 # These tests should be run manually after webrtc_dashboard_upload target
Christoffer Jansson70098a82022-02-21 19:43:36 +0100824 # has been built.
Jeremy Leconte4fc9bd92022-03-18 10:21:07 +0100825 'catapult_uploader_test.py',
826 'process_perf_results_test.py',
Christoffer Jansson70098a82022-02-21 19:43:36 +0100827 ]
828
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100829 test_directories = [
830 input_api.PresubmitLocalPath(),
831 Join('rtc_tools', 'py_event_log_analyzer'),
832 Join('audio', 'test', 'unittests'),
833 ] + [
834 root for root, _, files in os.walk(Join('tools_webrtc')) if any(
Christoffer Jansson70098a82022-02-21 19:43:36 +0100835 f.endswith('_test.py') and f not in excluded_files for f in files)
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100836 ]
Henrik Kjellander8d3ad822015-05-26 19:52:05 +0200837
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100838 tests = []
Christoffer Jansson1b083a92022-02-15 14:52:31 +0100839
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100840 for directory in test_directories:
841 tests.extend(
842 input_api.canned_checks.GetUnitTestsInDirectory(
843 input_api,
844 output_api,
845 directory,
846 files_to_check=[r'.+_test\.py$'],
847 run_on_python2=False))
848 return input_api.RunTests(tests, parallel=True)
Henrik Kjellander8d3ad822015-05-26 19:52:05 +0200849
850
Artem Titova04d1402018-05-11 11:23:00 +0200851def CheckUsageOfGoogleProtobufNamespace(input_api, output_api,
852 source_file_filter):
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100853 """Checks that the namespace google::protobuf has not been used."""
854 files = []
855 pattern = input_api.re.compile(r'google::protobuf')
856 proto_utils_path = os.path.join('rtc_base', 'protobuf_utils.h')
857 file_filter = lambda x: (input_api.FilterSourceFile(x) and source_file_filter(
858 x))
859 for f in input_api.AffectedSourceFiles(file_filter):
860 if f.LocalPath() in [proto_utils_path, 'PRESUBMIT.py']:
861 continue
862 contents = input_api.ReadFile(f)
863 if pattern.search(contents):
864 files.append(f)
mbonadei38415b22017-04-07 05:38:01 -0700865
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100866 if files:
867 return [
868 output_api.PresubmitError(
869 'Please avoid to use namespace `google::protobuf` directly.\n'
870 'Add a using directive in `%s` and include that header instead.' %
871 proto_utils_path, files)
872 ]
873 return []
mbonadei38415b22017-04-07 05:38:01 -0700874
875
Mirko Bonadei92ea95e2017-09-15 06:47:31 +0200876def _LicenseHeader(input_api):
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100877 """Returns the license header regexp."""
878 # Accept any year number from 2003 to the current year
879 current_year = int(input_api.time.strftime('%Y'))
880 allowed_years = (str(s) for s in reversed(range(2003, current_year + 1)))
881 years_re = '(' + '|'.join(allowed_years) + ')'
882 license_header = (
883 r'.*? Copyright( \(c\))? %(year)s The WebRTC [Pp]roject [Aa]uthors\. '
884 r'All [Rr]ights [Rr]eserved\.\n'
885 r'.*?\n'
886 r'.*? Use of this source code is governed by a BSD-style license\n'
887 r'.*? that can be found in the LICENSE file in the root of the source\n'
888 r'.*? tree\. An additional intellectual property rights grant can be '
889 r'found\n'
890 r'.*? in the file PATENTS\. All contributing project authors may\n'
891 r'.*? be found in the AUTHORS file in the root of the source tree\.\n'
892 ) % {
893 'year': years_re,
894 }
895 return license_header
Mirko Bonadei92ea95e2017-09-15 06:47:31 +0200896
897
charujain9893e252017-09-14 13:33:22 +0200898def CommonChecks(input_api, output_api):
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100899 """Checks common to both upload and commit."""
900 results = []
901 # Filter out files that are in objc or ios dirs from being cpplint-ed since
902 # they do not follow C++ lint rules.
903 exception_list = input_api.DEFAULT_FILES_TO_SKIP + (
904 r".*\bobjc[\\\/].*",
905 r".*objc\.[hcm]+$",
906 )
907 source_file_filter = lambda x: input_api.FilterSourceFile(
908 x, None, exception_list)
909 results.extend(
910 CheckApprovedFilesLintClean(input_api, output_api, source_file_filter))
911 results.extend(
912 input_api.canned_checks.CheckLicense(input_api, output_api,
913 _LicenseHeader(input_api)))
Byoungchan Lee94f2ef22021-07-01 22:21:44 +0900914
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100915 # TODO(bugs.webrtc.org/12114): Delete this filter and run pylint on
916 # all python files. This is a temporary solution.
917 python_file_filter = lambda f: (f.LocalPath().endswith('.py') and
918 source_file_filter(f))
919 python_changed_files = [
920 f.LocalPath()
921 for f in input_api.AffectedFiles(include_deletes=False,
922 file_filter=python_file_filter)
923 ]
Byoungchan Lee94f2ef22021-07-01 22:21:44 +0900924
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100925 results.extend(
926 input_api.canned_checks.RunPylint(
927 input_api,
928 output_api,
929 files_to_check=python_changed_files,
930 files_to_skip=(
931 r'^base[\\\/].*\.py$',
932 r'^build[\\\/].*\.py$',
933 r'^buildtools[\\\/].*\.py$',
934 r'^infra[\\\/].*\.py$',
935 r'^ios[\\\/].*\.py$',
936 r'^out.*[\\\/].*\.py$',
937 r'^testing[\\\/].*\.py$',
938 r'^third_party[\\\/].*\.py$',
939 r'^tools[\\\/].*\.py$',
940 # TODO(bugs.webrtc.org/13605): should arguably be checked.
941 r'^tools_webrtc[\\\/]mb[\\\/].*\.py$',
942 r'^xcodebuild.*[\\\/].*\.py$',
943 ),
944 pylintrc='pylintrc',
945 version='2.7'))
kjellander569cf942016-02-11 05:02:59 -0800946
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100947 # TODO(bugs.webrtc.org/13606): talk/ is no more, so make below checks simpler?
948 # WebRTC can't use the presubmit_canned_checks.PanProjectChecks function
949 # since we need to have different license checks
950 # in talk/ and webrtc/directories.
951 # Instead, hand-picked checks are included below.
Henrik Kjellander63224672015-09-08 08:03:56 +0200952
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100953 # .m and .mm files are ObjC files. For simplicity we will consider
954 # .h files in ObjC subdirectories ObjC headers.
955 objc_filter_list = (r'.+\.m$', r'.+\.mm$', r'.+objc\/.+\.h$')
956 # Skip long-lines check for DEPS and GN files.
957 build_file_filter_list = (r'.+\.gn$', r'.+\.gni$', 'DEPS')
958 # Also we will skip most checks for third_party directory.
959 third_party_filter_list = (r'^third_party[\\\/].+', )
960 eighty_char_sources = lambda x: input_api.FilterSourceFile(
961 x,
962 files_to_skip=build_file_filter_list + objc_filter_list +
963 third_party_filter_list)
964 hundred_char_sources = lambda x: input_api.FilterSourceFile(
965 x, files_to_check=objc_filter_list)
966 non_third_party_sources = lambda x: input_api.FilterSourceFile(
967 x, files_to_skip=third_party_filter_list)
Artem Titove92675b2018-05-22 10:21:27 +0200968
Christoffer Jansson4e8a7732022-02-08 09:01:12 +0100969 results.extend(
970 input_api.canned_checks.CheckLongLines(
971 input_api,
972 output_api,
973 maxlen=80,
974 source_file_filter=eighty_char_sources))
975 results.extend(
976 input_api.canned_checks.CheckLongLines(
977 input_api,
978 output_api,
979 maxlen=100,
980 source_file_filter=hundred_char_sources))
981 results.extend(
982 input_api.canned_checks.CheckChangeHasNoTabs(
983 input_api, output_api, source_file_filter=non_third_party_sources))
984 results.extend(
985 input_api.canned_checks.CheckChangeHasNoStrayWhitespace(
986 input_api, output_api, source_file_filter=non_third_party_sources))
987 results.extend(
988 input_api.canned_checks.CheckAuthorizedAuthor(
989 input_api,
990 output_api,
991 bot_allowlist=[
992 'chromium-webrtc-autoroll@webrtc-ci.iam.gserviceaccount.com',
993 'webrtc-version-updater@webrtc-ci.iam.gserviceaccount.com',
994 ]))
995 results.extend(
996 input_api.canned_checks.CheckChangeTodoHasOwner(
997 input_api, output_api, source_file_filter=non_third_party_sources))
998 results.extend(
999 input_api.canned_checks.CheckPatchFormatted(input_api, output_api))
1000 results.extend(CheckNativeApiHeaderChanges(input_api, output_api))
1001 results.extend(
1002 CheckNoIOStreamInHeaders(input_api,
1003 output_api,
1004 source_file_filter=non_third_party_sources))
1005 results.extend(
1006 CheckNoPragmaOnce(input_api,
1007 output_api,
1008 source_file_filter=non_third_party_sources))
1009 results.extend(
1010 CheckNoFRIEND_TEST(input_api,
1011 output_api,
1012 source_file_filter=non_third_party_sources))
1013 results.extend(CheckGnChanges(input_api, output_api))
1014 results.extend(
1015 CheckUnwantedDependencies(input_api,
1016 output_api,
1017 source_file_filter=non_third_party_sources))
1018 results.extend(
1019 CheckJSONParseErrors(input_api,
Mirko Bonadei8cc66952020-10-30 10:13:45 +01001020 output_api,
1021 source_file_filter=non_third_party_sources))
Christoffer Jansson4e8a7732022-02-08 09:01:12 +01001022 results.extend(RunPythonTests(input_api, output_api))
1023 results.extend(
1024 CheckUsageOfGoogleProtobufNamespace(
1025 input_api, output_api, source_file_filter=non_third_party_sources))
1026 results.extend(
1027 CheckOrphanHeaders(input_api,
1028 output_api,
1029 source_file_filter=non_third_party_sources))
1030 results.extend(
1031 CheckNewlineAtTheEndOfProtoFiles(
1032 input_api, output_api, source_file_filter=non_third_party_sources))
1033 results.extend(
1034 CheckNoStreamUsageIsAdded(input_api, output_api, non_third_party_sources))
1035 results.extend(
1036 CheckNoTestCaseUsageIsAdded(input_api, output_api,
Mirko Bonadei8cc66952020-10-30 10:13:45 +01001037 non_third_party_sources))
Christoffer Jansson4e8a7732022-02-08 09:01:12 +01001038 results.extend(CheckAddedDepsHaveTargetApprovals(input_api, output_api))
1039 results.extend(CheckApiDepsFileIsUpToDate(input_api, output_api))
1040 results.extend(
1041 CheckAbslMemoryInclude(input_api, output_api, non_third_party_sources))
1042 results.extend(
1043 CheckAssertUsage(input_api, output_api, non_third_party_sources))
1044 results.extend(
1045 CheckBannedAbslMakeUnique(input_api, output_api, non_third_party_sources))
1046 results.extend(
1047 CheckObjcApiSymbols(input_api, output_api, non_third_party_sources))
1048 return results
Mirko Bonadeia418e672018-10-24 13:57:25 +02001049
1050
1051def CheckApiDepsFileIsUpToDate(input_api, output_api):
Christoffer Jansson4e8a7732022-02-08 09:01:12 +01001052 """Check that 'include_rules' in api/DEPS is up to date.
Mirko Bonadei90490372018-10-26 13:17:47 +02001053
1054 The file api/DEPS must be kept up to date in order to avoid to avoid to
1055 include internal header from WebRTC's api/ headers.
1056
1057 This check is focused on ensuring that 'include_rules' contains a deny
1058 rule for each root level directory. More focused allow rules can be
1059 added to 'specific_include_rules'.
1060 """
Christoffer Jansson4e8a7732022-02-08 09:01:12 +01001061 results = []
1062 api_deps = os.path.join(input_api.PresubmitLocalPath(), 'api', 'DEPS')
1063 with open(api_deps) as f:
1064 deps_content = _ParseDeps(f.read())
Mirko Bonadeia418e672018-10-24 13:57:25 +02001065
Christoffer Jansson4e8a7732022-02-08 09:01:12 +01001066 include_rules = deps_content.get('include_rules', [])
1067 dirs_to_skip = set(['api', 'docs'])
Mirko Bonadeia418e672018-10-24 13:57:25 +02001068
Christoffer Jansson4e8a7732022-02-08 09:01:12 +01001069 # Only check top level directories affected by the current CL.
1070 dirs_to_check = set()
1071 for f in input_api.AffectedFiles():
1072 path_tokens = [t for t in f.LocalPath().split(os.sep) if t]
1073 if len(path_tokens) > 1:
1074 if (path_tokens[0] not in dirs_to_skip and os.path.isdir(
1075 os.path.join(input_api.PresubmitLocalPath(), path_tokens[0]))):
1076 dirs_to_check.add(path_tokens[0])
Mirko Bonadeia418e672018-10-24 13:57:25 +02001077
Christoffer Jansson4e8a7732022-02-08 09:01:12 +01001078 missing_include_rules = set()
1079 for p in dirs_to_check:
1080 rule = '-%s' % p
1081 if rule not in include_rules:
1082 missing_include_rules.add(rule)
Mirko Bonadei90490372018-10-26 13:17:47 +02001083
Christoffer Jansson4e8a7732022-02-08 09:01:12 +01001084 if missing_include_rules:
1085 error_msg = [
1086 'include_rules = [\n',
1087 ' ...\n',
1088 ]
Mirko Bonadeia418e672018-10-24 13:57:25 +02001089
Christoffer Jansson4e8a7732022-02-08 09:01:12 +01001090 for r in sorted(missing_include_rules):
1091 error_msg.append(' "%s",\n' % str(r))
Mirko Bonadeia418e672018-10-24 13:57:25 +02001092
Christoffer Jansson4e8a7732022-02-08 09:01:12 +01001093 error_msg.append(' ...\n')
1094 error_msg.append(']\n')
Mirko Bonadei90490372018-10-26 13:17:47 +02001095
Christoffer Jansson4e8a7732022-02-08 09:01:12 +01001096 results.append(
1097 output_api.PresubmitError(
1098 'New root level directory detected! WebRTC api/ headers should '
1099 'not #include headers from \n'
1100 'the new directory, so please update "include_rules" in file\n'
1101 '"%s". Example:\n%s\n' % (api_deps, ''.join(error_msg))))
Mirko Bonadei90490372018-10-26 13:17:47 +02001102
Christoffer Jansson4e8a7732022-02-08 09:01:12 +01001103 return results
Mirko Bonadei8cc66952020-10-30 10:13:45 +01001104
andrew@webrtc.org2442de12012-01-23 17:45:41 +00001105
Mirko Bonadei9fa8ef12019-09-17 19:14:13 +02001106def CheckBannedAbslMakeUnique(input_api, output_api, source_file_filter):
Christoffer Jansson4e8a7732022-02-08 09:01:12 +01001107 file_filter = lambda f: (f.LocalPath().endswith(('.cc', '.h')) and
1108 source_file_filter(f))
Mirko Bonadei9fa8ef12019-09-17 19:14:13 +02001109
Christoffer Jansson4e8a7732022-02-08 09:01:12 +01001110 files = []
1111 for f in input_api.AffectedFiles(include_deletes=False,
1112 file_filter=file_filter):
1113 for _, line in f.ChangedContents():
1114 if 'absl::make_unique' in line:
1115 files.append(f)
1116 break
Mirko Bonadei9fa8ef12019-09-17 19:14:13 +02001117
Christoffer Jansson4e8a7732022-02-08 09:01:12 +01001118 if files:
1119 return [
1120 output_api.PresubmitError(
1121 'Please use std::make_unique instead of absl::make_unique.\n'
1122 'Affected files:', files)
1123 ]
1124 return []
Mirko Bonadei8cc66952020-10-30 10:13:45 +01001125
Mirko Bonadei9fa8ef12019-09-17 19:14:13 +02001126
Mirko Bonadeid74c0e62020-07-16 21:57:01 +02001127def CheckObjcApiSymbols(input_api, output_api, source_file_filter):
Christoffer Jansson4e8a7732022-02-08 09:01:12 +01001128 rtc_objc_export = re.compile(r'RTC_OBJC_EXPORT(.|\n){26}',
1129 re.MULTILINE | re.DOTALL)
1130 file_filter = lambda f: (f.LocalPath().endswith(('.h')) and
1131 source_file_filter(f))
Mirko Bonadeid74c0e62020-07-16 21:57:01 +02001132
Christoffer Jansson4e8a7732022-02-08 09:01:12 +01001133 files = []
1134 file_filter = lambda x: (input_api.FilterSourceFile(x) and source_file_filter(
1135 x))
1136 for f in input_api.AffectedSourceFiles(file_filter):
1137 if not f.LocalPath().endswith('.h') or not 'sdk/objc' in f.LocalPath():
1138 continue
1139 if f.LocalPath().endswith('sdk/objc/base/RTCMacros.h'):
1140 continue
1141 contents = input_api.ReadFile(f)
1142 for match in rtc_objc_export.finditer(contents):
1143 export_block = match.group(0)
1144 if 'RTC_OBJC_TYPE' not in export_block:
1145 files.append(f.LocalPath())
Mirko Bonadeid74c0e62020-07-16 21:57:01 +02001146
Christoffer Jansson4e8a7732022-02-08 09:01:12 +01001147 if len(files) > 0:
1148 return [
1149 output_api.PresubmitError(
1150 'RTC_OBJC_EXPORT types must be wrapped into an RTC_OBJC_TYPE() ' +
1151 'macro.\n\n' + 'For example:\n' +
1152 'RTC_OBJC_EXPORT @protocol RTC_OBJC_TYPE(RtcFoo)\n\n' +
1153 'RTC_OBJC_EXPORT @interface RTC_OBJC_TYPE(RtcFoo)\n\n' +
1154 'Please fix the following files:', files)
1155 ]
1156 return []
Mirko Bonadei8cc66952020-10-30 10:13:45 +01001157
Mirko Bonadeid74c0e62020-07-16 21:57:01 +02001158
Mirko Bonadeia6395132021-07-22 17:35:59 +02001159def CheckAssertUsage(input_api, output_api, source_file_filter):
Christoffer Jansson4e8a7732022-02-08 09:01:12 +01001160 pattern = input_api.re.compile(r'\bassert\(')
1161 file_filter = lambda f: (f.LocalPath().endswith(('.cc', '.h', '.m', '.mm'))
1162 and source_file_filter(f))
Mirko Bonadeia6395132021-07-22 17:35:59 +02001163
Christoffer Jansson4e8a7732022-02-08 09:01:12 +01001164 files = []
1165 for f in input_api.AffectedFiles(include_deletes=False,
1166 file_filter=file_filter):
1167 for _, line in f.ChangedContents():
1168 if pattern.search(line):
1169 files.append(f.LocalPath())
1170 break
Mirko Bonadeia6395132021-07-22 17:35:59 +02001171
Christoffer Jansson4e8a7732022-02-08 09:01:12 +01001172 if len(files) > 0:
1173 return [
1174 output_api.PresubmitError(
1175 'Usage of assert() has been detected in the following files, '
1176 'please use RTC_DCHECK() instead.\n Files:', files)
1177 ]
1178 return []
Mirko Bonadeia6395132021-07-22 17:35:59 +02001179
1180
tzika06bf852018-11-15 20:37:35 +09001181def CheckAbslMemoryInclude(input_api, output_api, source_file_filter):
Christoffer Jansson4e8a7732022-02-08 09:01:12 +01001182 pattern = input_api.re.compile(r'^#include\s*"absl/memory/memory.h"',
1183 input_api.re.MULTILINE)
1184 file_filter = lambda f: (f.LocalPath().endswith(('.cc', '.h')) and
1185 source_file_filter(f))
tzika06bf852018-11-15 20:37:35 +09001186
Christoffer Jansson4e8a7732022-02-08 09:01:12 +01001187 files = []
1188 for f in input_api.AffectedFiles(include_deletes=False,
1189 file_filter=file_filter):
1190 contents = input_api.ReadFile(f)
1191 if pattern.search(contents):
1192 continue
1193 for _, line in f.ChangedContents():
1194 if 'absl::WrapUnique' in line:
1195 files.append(f)
1196 break
tzika06bf852018-11-15 20:37:35 +09001197
Christoffer Jansson4e8a7732022-02-08 09:01:12 +01001198 if len(files) > 0:
1199 return [
1200 output_api.PresubmitError(
1201 'Please include "absl/memory/memory.h" header for '
1202 'absl::WrapUnique.\nThis header may or may not be included '
1203 'transitively depending on the C++ standard version.', files)
1204 ]
1205 return []
Mirko Bonadei8cc66952020-10-30 10:13:45 +01001206
kjellander@webrtc.orge4158642014-08-06 09:11:18 +00001207
andrew@webrtc.org53df1362012-01-26 21:24:23 +00001208def CheckChangeOnUpload(input_api, output_api):
Christoffer Jansson4e8a7732022-02-08 09:01:12 +01001209 results = []
1210 results.extend(CommonChecks(input_api, output_api))
1211 results.extend(CheckGnGen(input_api, output_api))
1212 results.extend(input_api.canned_checks.CheckGNFormatted(
1213 input_api, output_api))
1214 return results
niklase@google.comda159d62011-05-30 11:51:34 +00001215
kjellander@webrtc.orge4158642014-08-06 09:11:18 +00001216
andrew@webrtc.org2442de12012-01-23 17:45:41 +00001217def CheckChangeOnCommit(input_api, output_api):
Christoffer Jansson4e8a7732022-02-08 09:01:12 +01001218 results = []
1219 results.extend(CommonChecks(input_api, output_api))
1220 results.extend(VerifyNativeApiHeadersListIsValid(input_api, output_api))
1221 results.extend(input_api.canned_checks.CheckOwners(input_api, output_api))
1222 results.extend(
1223 input_api.canned_checks.CheckChangeWasUploaded(input_api, output_api))
1224 results.extend(
1225 input_api.canned_checks.CheckChangeHasDescription(input_api, output_api))
1226 results.extend(CheckChangeHasBugField(input_api, output_api))
1227 results.extend(CheckCommitMessageBugEntry(input_api, output_api))
1228 results.extend(
1229 input_api.canned_checks.CheckTreeIsOpen(
1230 input_api,
1231 output_api,
1232 json_url='http://webrtc-status.appspot.com/current?format=json'))
1233 return results
mbonadei74973ed2017-05-09 07:58:05 -07001234
1235
Artem Titova04d1402018-05-11 11:23:00 +02001236def CheckOrphanHeaders(input_api, output_api, source_file_filter):
Christoffer Jansson4e8a7732022-02-08 09:01:12 +01001237 # We need to wait until we have an input_api object and use this
1238 # roundabout construct to import prebubmit_checks_lib because this file is
1239 # eval-ed and thus doesn't have __file__.
1240 error_msg = """{} should be listed in {}."""
1241 results = []
Christoffer Jansson884e8ae2022-02-11 21:29:38 +01001242 exempt_paths = [re.escape(os.path.join('tools_webrtc', 'ios', 'SDK'))]
1243
Christoffer Jansson4e8a7732022-02-08 09:01:12 +01001244 with _AddToPath(
1245 input_api.os_path.join(input_api.PresubmitLocalPath(), 'tools_webrtc',
1246 'presubmit_checks_lib')):
1247 from check_orphan_headers import GetBuildGnPathFromFilePath
1248 from check_orphan_headers import IsHeaderInBuildGn
mbonadei74973ed2017-05-09 07:58:05 -07001249
Christoffer Jansson4e8a7732022-02-08 09:01:12 +01001250 file_filter = lambda x: input_api.FilterSourceFile(
1251 x, files_to_skip=exempt_paths) and source_file_filter(x)
1252 for f in input_api.AffectedSourceFiles(file_filter):
1253 if f.LocalPath().endswith('.h'):
1254 file_path = os.path.abspath(f.LocalPath())
1255 root_dir = os.getcwd()
1256 gn_file_path = GetBuildGnPathFromFilePath(file_path, os.path.exists,
1257 root_dir)
1258 in_build_gn = IsHeaderInBuildGn(file_path, gn_file_path)
1259 if not in_build_gn:
1260 results.append(
1261 output_api.PresubmitError(
1262 error_msg.format(f.LocalPath(), os.path.relpath(gn_file_path))))
1263 return results
Mirko Bonadei960fd5b2017-06-29 14:59:36 +02001264
1265
Mirko Bonadei8cc66952020-10-30 10:13:45 +01001266def CheckNewlineAtTheEndOfProtoFiles(input_api, output_api,
1267 source_file_filter):
Christoffer Jansson4e8a7732022-02-08 09:01:12 +01001268 """Checks that all .proto files are terminated with a newline."""
1269 error_msg = 'File {} must end with exactly one newline.'
1270 results = []
1271 file_filter = lambda x: input_api.FilterSourceFile(
1272 x, files_to_check=(r'.+\.proto$', )) and source_file_filter(x)
1273 for f in input_api.AffectedSourceFiles(file_filter):
1274 file_path = f.LocalPath()
1275 with open(file_path) as f:
1276 lines = f.readlines()
1277 if len(lines) > 0 and not lines[-1].endswith('\n'):
1278 results.append(output_api.PresubmitError(error_msg.format(file_path)))
1279 return results
Mirko Bonadei7e4ee6e2018-09-28 11:45:23 +02001280
1281
1282def _ExtractAddRulesFromParsedDeps(parsed_deps):
Christoffer Jansson4e8a7732022-02-08 09:01:12 +01001283 """Extract the rules that add dependencies from a parsed DEPS file.
Mirko Bonadei7e4ee6e2018-09-28 11:45:23 +02001284
1285 Args:
1286 parsed_deps: the locals dictionary from evaluating the DEPS file."""
Christoffer Jansson4e8a7732022-02-08 09:01:12 +01001287 add_rules = set()
1288 add_rules.update([
1289 rule[1:] for rule in parsed_deps.get('include_rules', [])
1290 if rule.startswith('+') or rule.startswith('!')
1291 ])
1292 for _, rules in parsed_deps.get('specific_include_rules', {}).items():
Mirko Bonadei7e4ee6e2018-09-28 11:45:23 +02001293 add_rules.update([
Christoffer Jansson4e8a7732022-02-08 09:01:12 +01001294 rule[1:] for rule in rules
Mirko Bonadei7e4ee6e2018-09-28 11:45:23 +02001295 if rule.startswith('+') or rule.startswith('!')
1296 ])
Christoffer Jansson4e8a7732022-02-08 09:01:12 +01001297 return add_rules
Mirko Bonadei7e4ee6e2018-09-28 11:45:23 +02001298
1299
1300def _ParseDeps(contents):
Christoffer Jansson4e8a7732022-02-08 09:01:12 +01001301 """Simple helper for parsing DEPS files."""
Mirko Bonadei7e4ee6e2018-09-28 11:45:23 +02001302
Christoffer Jansson4e8a7732022-02-08 09:01:12 +01001303 # Stubs for handling special syntax in the root DEPS file.
1304 class VarImpl:
1305 def __init__(self, local_scope):
1306 self._local_scope = local_scope
Mirko Bonadei7e4ee6e2018-09-28 11:45:23 +02001307
Christoffer Jansson4e8a7732022-02-08 09:01:12 +01001308 def Lookup(self, var_name):
1309 """Implements the Var syntax."""
1310 try:
1311 return self._local_scope['vars'][var_name]
1312 except KeyError as var_not_defined:
1313 raise Exception('Var is not defined: %s' %
1314 var_name) from var_not_defined
Mirko Bonadei7e4ee6e2018-09-28 11:45:23 +02001315
Christoffer Jansson4e8a7732022-02-08 09:01:12 +01001316 local_scope = {}
1317 global_scope = {
1318 'Var': VarImpl(local_scope).Lookup,
1319 }
1320 exec(contents, global_scope, local_scope)
1321 return local_scope
Mirko Bonadei7e4ee6e2018-09-28 11:45:23 +02001322
1323
1324def _CalculateAddedDeps(os_path, old_contents, new_contents):
Christoffer Jansson4e8a7732022-02-08 09:01:12 +01001325 """Helper method for _CheckAddedDepsHaveTargetApprovals. Returns
Mirko Bonadei7e4ee6e2018-09-28 11:45:23 +02001326 a set of DEPS entries that we should look up.
1327
1328 For a directory (rather than a specific filename) we fake a path to
1329 a specific filename by adding /DEPS. This is chosen as a file that
1330 will seldom or never be subject to per-file include_rules.
1331 """
Christoffer Jansson4e8a7732022-02-08 09:01:12 +01001332 # We ignore deps entries on auto-generated directories.
1333 auto_generated_dirs = ['grit', 'jni']
Mirko Bonadei7e4ee6e2018-09-28 11:45:23 +02001334
Christoffer Jansson4e8a7732022-02-08 09:01:12 +01001335 old_deps = _ExtractAddRulesFromParsedDeps(_ParseDeps(old_contents))
1336 new_deps = _ExtractAddRulesFromParsedDeps(_ParseDeps(new_contents))
Mirko Bonadei7e4ee6e2018-09-28 11:45:23 +02001337
Christoffer Jansson4e8a7732022-02-08 09:01:12 +01001338 added_deps = new_deps.difference(old_deps)
Mirko Bonadei7e4ee6e2018-09-28 11:45:23 +02001339
Christoffer Jansson4e8a7732022-02-08 09:01:12 +01001340 results = set()
1341 for added_dep in added_deps:
1342 if added_dep.split('/')[0] in auto_generated_dirs:
1343 continue
1344 # Assume that a rule that ends in .h is a rule for a specific file.
1345 if added_dep.endswith('.h'):
1346 results.add(added_dep)
1347 else:
1348 results.add(os_path.join(added_dep, 'DEPS'))
1349 return results
Mirko Bonadei7e4ee6e2018-09-28 11:45:23 +02001350
1351
1352def CheckAddedDepsHaveTargetApprovals(input_api, output_api):
Christoffer Jansson4e8a7732022-02-08 09:01:12 +01001353 """When a dependency prefixed with + is added to a DEPS file, we
Edward Lesmesbef08502021-02-05 14:08:32 -08001354 want to make sure that the change is reviewed by an OWNER of the
1355 target file or directory, to avoid layering violations from being
1356 introduced. This check verifies that this happens.
1357 """
Christoffer Jansson4e8a7732022-02-08 09:01:12 +01001358 virtual_depended_on_files = set()
Mirko Bonadei7e4ee6e2018-09-28 11:45:23 +02001359
Christoffer Jansson4e8a7732022-02-08 09:01:12 +01001360 file_filter = lambda f: not input_api.re.match(
1361 r"^third_party[\\\/](WebKit|blink)[\\\/].*", f.LocalPath())
1362 for f in input_api.AffectedFiles(include_deletes=False,
1363 file_filter=file_filter):
1364 filename = input_api.os_path.basename(f.LocalPath())
1365 if filename == 'DEPS':
1366 virtual_depended_on_files.update(
1367 _CalculateAddedDeps(input_api.os_path, '\n'.join(f.OldContents()),
1368 '\n'.join(f.NewContents())))
Mirko Bonadei7e4ee6e2018-09-28 11:45:23 +02001369
Christoffer Jansson4e8a7732022-02-08 09:01:12 +01001370 if not virtual_depended_on_files:
Mirko Bonadei8cc66952020-10-30 10:13:45 +01001371 return []
Christoffer Jansson4e8a7732022-02-08 09:01:12 +01001372
1373 if input_api.is_committing:
1374 if input_api.tbr:
1375 return [
1376 output_api.PresubmitNotifyResult(
1377 '--tbr was specified, skipping OWNERS check for DEPS '
1378 'additions')
1379 ]
1380 if input_api.dry_run:
1381 return [
1382 output_api.PresubmitNotifyResult(
1383 'This is a dry run, skipping OWNERS check for DEPS '
1384 'additions')
1385 ]
1386 if not input_api.change.issue:
1387 return [
1388 output_api.PresubmitError(
1389 "DEPS approval by OWNERS check failed: this change has "
1390 "no change number, so we can't check it for approvals.")
1391 ]
1392 output = output_api.PresubmitError
1393 else:
1394 output = output_api.PresubmitNotifyResult
1395
1396 owner_email, reviewers = (
1397 input_api.canned_checks.GetCodereviewOwnerAndReviewers(
1398 input_api, None, approval_needed=input_api.is_committing))
1399
1400 owner_email = owner_email or input_api.change.author_email
1401
1402 approval_status = input_api.owners_client.GetFilesApprovalStatus(
1403 virtual_depended_on_files, reviewers.union([owner_email]), [])
1404 missing_files = [
1405 f for f in virtual_depended_on_files
1406 if approval_status[f] != input_api.owners_client.APPROVED
1407 ]
1408
1409 # We strip the /DEPS part that was added by
1410 # _FilesToCheckForIncomingDeps to fake a path to a file in a
1411 # directory.
1412 def StripDeps(path):
1413 start_deps = path.rfind('/DEPS')
1414 if start_deps != -1:
1415 return path[:start_deps]
1416 return path
1417
1418 unapproved_dependencies = [
1419 "'+%s'," % StripDeps(path) for path in missing_files
1420 ]
1421
1422 if unapproved_dependencies:
1423 output_list = [
1424 output('You need LGTM from owners of depends-on paths in DEPS that '
1425 ' were modified in this CL:\n %s' %
1426 '\n '.join(sorted(unapproved_dependencies)))
1427 ]
1428 suggested_owners = input_api.owners_client.SuggestOwners(
1429 missing_files, exclude=[owner_email])
1430 output_list.append(
1431 output('Suggested missing target path OWNERS:\n %s' %
1432 '\n '.join(suggested_owners or [])))
1433 return output_list
1434
1435 return []