blob: 3f0a72c7f18f10a335b6e1804867baaa6edaccbf [file] [log] [blame]
andrew@webrtc.org2442de12012-01-23 17:45:41 +00001# Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
2#
3# Use of this source code is governed by a BSD-style license
4# that can be found in the LICENSE file in the root of the source
5# tree. An additional intellectual property rights grant can be found
6# in the file PATENTS. All contributing project authors may
7# be found in the AUTHORS file in the root of the source tree.
niklase@google.comda159d62011-05-30 11:51:34 +00008
kjellander7439f972016-12-05 22:47:46 -08009import json
kjellander@webrtc.orgaefe61a2014-12-08 13:00:30 +000010import os
kjellander@webrtc.org85759802013-10-22 16:47:40 +000011import re
kjellander@webrtc.org3bd41562014-09-01 11:06:37 +000012import sys
Mirko Bonadei4dc4e252017-09-19 13:49:16 +020013from collections import defaultdict
Oleh Prypin2f33a562017-10-04 20:17:54 +020014from contextlib import contextmanager
kjellander@webrtc.org85759802013-10-22 16:47:40 +000015
16
oprypin2aa463f2017-03-23 03:17:02 -070017# Files and directories that are *skipped* by cpplint in the presubmit script.
18CPPLINT_BLACKLIST = [
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020019 'api/video_codecs/video_decoder.h',
20 'common_types.cc',
21 'common_types.h',
22 'examples/objc',
Steve Antone78bcb92017-10-31 09:53:08 -070023 'media/base/streamparams.h',
24 'media/base/videocommon.h',
25 'media/engine/fakewebrtcdeviceinfo.h',
26 'media/sctp/sctptransport.cc',
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020027 'modules/audio_coding',
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020028 'modules/audio_device',
29 'modules/audio_processing',
30 'modules/desktop_capture',
31 'modules/include/module_common_types.h',
32 'modules/media_file',
33 'modules/utility',
34 'modules/video_capture',
Steve Anton6c38cc72017-11-29 10:25:58 -080035 'p2p/base/session.cc',
36 'p2p/base/session.h',
37 'p2p/base/pseudotcp.cc',
38 'p2p/base/pseudotcp.h',
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020039 'rtc_base',
40 'sdk/android/src/jni',
41 'sdk/objc',
42 'system_wrappers',
43 'test',
Henrik Kjellander90fd7d82017-05-09 08:30:10 +020044 'tools_webrtc',
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020045 'voice_engine',
kjellander@webrtc.org0fcaf992015-11-26 15:24:52 +010046]
47
jbauchc4e3ead2016-02-19 00:25:55 -080048# These filters will always be removed, even if the caller specifies a filter
49# set, as they are problematic or broken in some way.
50#
51# Justifications for each filter:
52# - build/c++11 : Rvalue ref checks are unreliable (false positives),
53# include file and feature blacklists are
54# google3-specific.
kjellandere5a87a52016-04-27 02:32:12 -070055# - whitespace/operators: Same as above (doesn't seem sufficient to eliminate
56# all move-related errors).
jbauchc4e3ead2016-02-19 00:25:55 -080057BLACKLIST_LINT_FILTERS = [
58 '-build/c++11',
kjellandere5a87a52016-04-27 02:32:12 -070059 '-whitespace/operators',
jbauchc4e3ead2016-02-19 00:25:55 -080060]
61
kjellanderfd595232015-12-04 02:44:09 -080062# List of directories of "supported" native APIs. That means changes to headers
63# will be done in a compatible way following this scheme:
64# 1. Non-breaking changes are made.
65# 2. The old APIs as marked as deprecated (with comments).
66# 3. Deprecation is announced to discuss-webrtc@googlegroups.com and
67# webrtc-users@google.com (internal list).
68# 4. (later) The deprecated APIs are removed.
kjellander53047c92015-12-02 23:56:14 -080069NATIVE_API_DIRS = (
Karl Wibergef52d8b82017-10-25 13:20:03 +020070 'api', # All subdirectories of api/ are included as well.
Mirko Bonadeia4eeeff2018-01-11 13:16:52 +010071 'media/base',
72 'media/engine',
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020073 'modules/audio_device/include',
74 'pc',
kjellanderdd705472016-06-09 11:17:27 -070075)
Mirko Bonadei4dc4e252017-09-19 13:49:16 +020076
kjellanderdd705472016-06-09 11:17:27 -070077# These directories should not be used but are maintained only to avoid breaking
78# some legacy downstream code.
79LEGACY_API_DIRS = (
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020080 'common_audio/include',
81 'modules/audio_coding/include',
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020082 'modules/audio_processing/include',
83 'modules/bitrate_controller/include',
84 'modules/congestion_controller/include',
85 'modules/include',
86 'modules/remote_bitrate_estimator/include',
87 'modules/rtp_rtcp/include',
88 'modules/rtp_rtcp/source',
89 'modules/utility/include',
90 'modules/video_coding/codecs/h264/include',
91 'modules/video_coding/codecs/i420/include',
92 'modules/video_coding/codecs/vp8/include',
93 'modules/video_coding/codecs/vp9/include',
94 'modules/video_coding/include',
95 'rtc_base',
96 'system_wrappers/include',
97 'voice_engine/include',
kjellander53047c92015-12-02 23:56:14 -080098)
Mirko Bonadei4dc4e252017-09-19 13:49:16 +020099
Karl Wibergd4f01c12017-11-10 10:55:45 +0100100# NOTE: The set of directories in API_DIRS should be the same as those
101# listed in the table in native-api.md.
kjellanderdd705472016-06-09 11:17:27 -0700102API_DIRS = NATIVE_API_DIRS[:] + LEGACY_API_DIRS[:]
kjellander53047c92015-12-02 23:56:14 -0800103
Mirko Bonadei4dc4e252017-09-19 13:49:16 +0200104# TARGET_RE matches a GN target, and extracts the target name and the contents.
105TARGET_RE = re.compile(r'(?P<indent>\s*)\w+\("(?P<target_name>\w+)"\) {'
106 r'(?P<target_contents>.*?)'
107 r'(?P=indent)}',
108 re.MULTILINE | re.DOTALL)
109
110# SOURCES_RE matches a block of sources inside a GN target.
111SOURCES_RE = re.compile(r'sources \+?= \[(?P<sources>.*?)\]',
112 re.MULTILINE | re.DOTALL)
113
114# FILE_PATH_RE matchies a file path.
115FILE_PATH_RE = re.compile(r'"(?P<file_path>(\w|\/)+)(?P<extension>\.\w+)"')
116
kjellander53047c92015-12-02 23:56:14 -0800117
Oleh Prypin2f33a562017-10-04 20:17:54 +0200118@contextmanager
119def _AddToPath(*paths):
120 original_sys_path = sys.path
121 sys.path.extend(paths)
122 try:
123 yield
124 finally:
125 # Restore sys.path to what it was before.
126 sys.path = original_sys_path
ehmaldonado4fb97462017-01-30 05:27:22 -0800127
128
charujain9893e252017-09-14 13:33:22 +0200129def VerifyNativeApiHeadersListIsValid(input_api, output_api):
kjellander53047c92015-12-02 23:56:14 -0800130 """Ensures the list of native API header directories is up to date."""
131 non_existing_paths = []
132 native_api_full_paths = [
133 input_api.os_path.join(input_api.PresubmitLocalPath(),
kjellanderdd705472016-06-09 11:17:27 -0700134 *path.split('/')) for path in API_DIRS]
kjellander53047c92015-12-02 23:56:14 -0800135 for path in native_api_full_paths:
136 if not os.path.isdir(path):
137 non_existing_paths.append(path)
138 if non_existing_paths:
139 return [output_api.PresubmitError(
140 'Directories to native API headers have changed which has made the '
141 'list in PRESUBMIT.py outdated.\nPlease update it to the current '
142 'location of our native APIs.',
143 non_existing_paths)]
144 return []
145
kjellanderc88b5d52017-04-05 06:42:43 -0700146API_CHANGE_MSG = """
kwibergeb133022016-04-07 07:41:48 -0700147You seem to be changing native API header files. Please make sure that you:
oprypin375b9ac2017-02-13 04:13:23 -0800148 1. Make compatible changes that don't break existing clients. Usually
149 this is done by keeping the existing method signatures unchanged.
150 2. Mark the old stuff as deprecated (see RTC_DEPRECATED macro).
kwibergeb133022016-04-07 07:41:48 -0700151 3. Create a timeline and plan for when the deprecated stuff will be
152 removed. (The amount of time we give users to change their code
153 should be informed by how much work it is for them. If they just
154 need to replace one name with another or something equally
155 simple, 1-2 weeks might be good; if they need to do serious work,
156 up to 3 months may be called for.)
157 4. Update/inform existing downstream code owners to stop using the
158 deprecated stuff. (Send announcements to
159 discuss-webrtc@googlegroups.com and webrtc-users@google.com.)
160 5. Remove the deprecated stuff, once the agreed-upon amount of time
161 has passed.
162Related files:
163"""
kjellander53047c92015-12-02 23:56:14 -0800164
charujain9893e252017-09-14 13:33:22 +0200165def CheckNativeApiHeaderChanges(input_api, output_api):
kjellander53047c92015-12-02 23:56:14 -0800166 """Checks to remind proper changing of native APIs."""
167 files = []
Karl Wiberg6bfac032017-10-27 15:14:20 +0200168 source_file_filter = lambda x: input_api.FilterSourceFile(
169 x, white_list=[r'.+\.(gn|gni|h)$'])
170 for f in input_api.AffectedSourceFiles(source_file_filter):
171 for path in API_DIRS:
172 dn = os.path.dirname(f.LocalPath())
173 if path == 'api':
174 # Special case: Subdirectories included.
175 if dn == 'api' or dn.startswith('api/'):
176 files.append(f)
177 else:
178 # Normal case: Subdirectories not included.
179 if dn == path:
180 files.append(f)
kjellander53047c92015-12-02 23:56:14 -0800181
182 if files:
kjellanderc88b5d52017-04-05 06:42:43 -0700183 return [output_api.PresubmitNotifyResult(API_CHANGE_MSG, files)]
kjellander53047c92015-12-02 23:56:14 -0800184 return []
185
kjellander@webrtc.org0fcaf992015-11-26 15:24:52 +0100186
charujain9893e252017-09-14 13:33:22 +0200187def CheckNoIOStreamInHeaders(input_api, output_api):
kjellander@webrtc.org51198f12012-02-21 17:53:46 +0000188 """Checks to make sure no .h files include <iostream>."""
189 files = []
190 pattern = input_api.re.compile(r'^#include\s*<iostream>',
191 input_api.re.MULTILINE)
192 for f in input_api.AffectedSourceFiles(input_api.FilterSourceFile):
193 if not f.LocalPath().endswith('.h'):
194 continue
195 contents = input_api.ReadFile(f)
196 if pattern.search(contents):
197 files.append(f)
198
199 if len(files):
Henrik Kjellander57e5fd22015-05-25 12:55:39 +0200200 return [output_api.PresubmitError(
kjellander@webrtc.org51198f12012-02-21 17:53:46 +0000201 'Do not #include <iostream> in header files, since it inserts static ' +
202 'initialization into every file including the header. Instead, ' +
203 '#include <ostream>. See http://crbug.com/94794',
Henrik Kjellander57e5fd22015-05-25 12:55:39 +0200204 files)]
kjellander@webrtc.org51198f12012-02-21 17:53:46 +0000205 return []
206
kjellander@webrtc.orge4158642014-08-06 09:11:18 +0000207
charujain9893e252017-09-14 13:33:22 +0200208def CheckNoPragmaOnce(input_api, output_api):
kjellander6aeef742017-02-20 01:13:18 -0800209 """Make sure that banned functions are not used."""
210 files = []
211 pattern = input_api.re.compile(r'^#pragma\s+once',
212 input_api.re.MULTILINE)
213 for f in input_api.AffectedSourceFiles(input_api.FilterSourceFile):
214 if not f.LocalPath().endswith('.h'):
215 continue
216 contents = input_api.ReadFile(f)
217 if pattern.search(contents):
218 files.append(f)
219
220 if files:
221 return [output_api.PresubmitError(
222 'Do not use #pragma once in header files.\n'
223 'See http://www.chromium.org/developers/coding-style#TOC-File-headers',
224 files)]
225 return []
226
227
charujain9893e252017-09-14 13:33:22 +0200228def CheckNoFRIEND_TEST(input_api, output_api): # pylint: disable=invalid-name
kjellander@webrtc.org51198f12012-02-21 17:53:46 +0000229 """Make sure that gtest's FRIEND_TEST() macro is not used, the
230 FRIEND_TEST_ALL_PREFIXES() macro from testsupport/gtest_prod_util.h should be
231 used instead since that allows for FLAKY_, FAILS_ and DISABLED_ prefixes."""
232 problems = []
233
234 file_filter = lambda f: f.LocalPath().endswith(('.cc', '.h'))
235 for f in input_api.AffectedFiles(file_filter=file_filter):
236 for line_num, line in f.ChangedContents():
237 if 'FRIEND_TEST(' in line:
238 problems.append(' %s:%d' % (f.LocalPath(), line_num))
239
240 if not problems:
241 return []
242 return [output_api.PresubmitPromptWarning('WebRTC\'s code should not use '
243 'gtest\'s FRIEND_TEST() macro. Include testsupport/gtest_prod_util.h and '
244 'use FRIEND_TEST_ALL_PREFIXES() instead.\n' + '\n'.join(problems))]
245
kjellander@webrtc.orge4158642014-08-06 09:11:18 +0000246
charujain9893e252017-09-14 13:33:22 +0200247def IsLintBlacklisted(blacklist_paths, file_path):
oprypin2aa463f2017-03-23 03:17:02 -0700248 """ Checks if a file is blacklisted for lint check."""
249 for path in blacklist_paths:
250 if file_path == path or os.path.dirname(file_path).startswith(path):
kjellander@webrtc.org0fcaf992015-11-26 15:24:52 +0100251 return True
252 return False
253
254
charujain9893e252017-09-14 13:33:22 +0200255def CheckApprovedFilesLintClean(input_api, output_api,
mflodman@webrtc.org2a452092012-07-01 05:55:23 +0000256 source_file_filter=None):
oprypin2aa463f2017-03-23 03:17:02 -0700257 """Checks that all new or non-blacklisted .cc and .h files pass cpplint.py.
charujain9893e252017-09-14 13:33:22 +0200258 This check is based on CheckChangeLintsClean in
kjellander@webrtc.org51198f12012-02-21 17:53:46 +0000259 depot_tools/presubmit_canned_checks.py but has less filters and only checks
260 added files."""
261 result = []
262
263 # Initialize cpplint.
264 import cpplint
265 # Access to a protected member _XX of a client class
266 # pylint: disable=W0212
267 cpplint._cpplint_state.ResetErrorCounts()
268
jbauchc4e3ead2016-02-19 00:25:55 -0800269 lint_filters = cpplint._Filters()
270 lint_filters.extend(BLACKLIST_LINT_FILTERS)
271 cpplint._SetFilters(','.join(lint_filters))
272
oprypin2aa463f2017-03-23 03:17:02 -0700273 # Create a platform independent blacklist for cpplint.
274 blacklist_paths = [input_api.os_path.join(*path.split('/'))
275 for path in CPPLINT_BLACKLIST]
kjellander@webrtc.org0fcaf992015-11-26 15:24:52 +0100276
kjellander@webrtc.org51198f12012-02-21 17:53:46 +0000277 # Use the strictest verbosity level for cpplint.py (level 1) which is the
oprypin2aa463f2017-03-23 03:17:02 -0700278 # default when running cpplint.py from command line. To make it possible to
279 # work with not-yet-converted code, we're only applying it to new (or
280 # moved/renamed) files and files not listed in CPPLINT_BLACKLIST.
kjellander@webrtc.org51198f12012-02-21 17:53:46 +0000281 verbosity_level = 1
282 files = []
283 for f in input_api.AffectedSourceFiles(source_file_filter):
Henrik Kjellander57e5fd22015-05-25 12:55:39 +0200284 # Note that moved/renamed files also count as added.
charujain9893e252017-09-14 13:33:22 +0200285 if f.Action() == 'A' or not IsLintBlacklisted(blacklist_paths,
oprypin2aa463f2017-03-23 03:17:02 -0700286 f.LocalPath()):
kjellander@webrtc.org51198f12012-02-21 17:53:46 +0000287 files.append(f.AbsoluteLocalPath())
mflodman@webrtc.org2a452092012-07-01 05:55:23 +0000288
kjellander@webrtc.org51198f12012-02-21 17:53:46 +0000289 for file_name in files:
290 cpplint.ProcessFile(file_name, verbosity_level)
291
292 if cpplint._cpplint_state.error_count > 0:
293 if input_api.is_committing:
oprypin8e58d652017-03-21 07:52:41 -0700294 res_type = output_api.PresubmitError
kjellander@webrtc.org51198f12012-02-21 17:53:46 +0000295 else:
296 res_type = output_api.PresubmitPromptWarning
297 result = [res_type('Changelist failed cpplint.py check.')]
298
299 return result
300
charujain9893e252017-09-14 13:33:22 +0200301def CheckNoSourcesAbove(input_api, gn_files, output_api):
ehmaldonado5b1ba082016-09-02 05:51:08 -0700302 # Disallow referencing source files with paths above the GN file location.
303 source_pattern = input_api.re.compile(r' +sources \+?= \[(.*?)\]',
304 re.MULTILINE | re.DOTALL)
305 file_pattern = input_api.re.compile(r'"((\.\./.*?)|(//.*?))"')
306 violating_gn_files = set()
307 violating_source_entries = []
308 for gn_file in gn_files:
309 contents = input_api.ReadFile(gn_file)
310 for source_block_match in source_pattern.finditer(contents):
311 # Find all source list entries starting with ../ in the source block
312 # (exclude overrides entries).
313 for file_list_match in file_pattern.finditer(source_block_match.group(1)):
314 source_file = file_list_match.group(1)
315 if 'overrides/' not in source_file:
316 violating_source_entries.append(source_file)
317 violating_gn_files.add(gn_file)
318 if violating_gn_files:
319 return [output_api.PresubmitError(
320 'Referencing source files above the directory of the GN file is not '
Henrik Kjellanderb4af3d62016-11-16 20:11:29 +0100321 'allowed. Please introduce new GN targets in the proper location '
322 'instead.\n'
ehmaldonado5b1ba082016-09-02 05:51:08 -0700323 'Invalid source entries:\n'
324 '%s\n'
325 'Violating GN files:' % '\n'.join(violating_source_entries),
326 items=violating_gn_files)]
327 return []
328
Mirko Bonadei4dc4e252017-09-19 13:49:16 +0200329def CheckNoMixingSources(input_api, gn_files, output_api):
330 """Disallow mixing C, C++ and Obj-C/Obj-C++ in the same target.
331
332 See bugs.webrtc.org/7743 for more context.
333 """
334 def _MoreThanOneSourceUsed(*sources_lists):
335 sources_used = 0
336 for source_list in sources_lists:
337 if len(source_list):
338 sources_used += 1
339 return sources_used > 1
340
341 errors = defaultdict(lambda: [])
kjellander7439f972016-12-05 22:47:46 -0800342 for gn_file in gn_files:
Mirko Bonadei4dc4e252017-09-19 13:49:16 +0200343 gn_file_content = input_api.ReadFile(gn_file)
344 for target_match in TARGET_RE.finditer(gn_file_content):
345 # list_of_sources is a list of tuples of the form
346 # (c_files, cc_files, objc_files) that keeps track of all the sources
347 # defined in a target. A GN target can have more that on definition of
348 # sources (since it supports if/else statements).
349 # E.g.:
350 # rtc_static_library("foo") {
351 # if (is_win) {
352 # sources = [ "foo.cc" ]
353 # } else {
354 # sources = [ "foo.mm" ]
355 # }
356 # }
357 # This is allowed and the presubmit check should support this case.
358 list_of_sources = []
kjellander7439f972016-12-05 22:47:46 -0800359 c_files = []
360 cc_files = []
Mirko Bonadei4dc4e252017-09-19 13:49:16 +0200361 objc_files = []
362 target_name = target_match.group('target_name')
363 target_contents = target_match.group('target_contents')
364 for sources_match in SOURCES_RE.finditer(target_contents):
365 if '+=' not in sources_match.group(0):
366 if c_files or cc_files or objc_files:
367 list_of_sources.append((c_files, cc_files, objc_files))
368 c_files = []
369 cc_files = []
370 objc_files = []
371 for file_match in FILE_PATH_RE.finditer(sources_match.group(1)):
372 file_path = file_match.group('file_path')
373 extension = file_match.group('extension')
374 if extension == '.c':
375 c_files.append(file_path + extension)
376 if extension == '.cc':
377 cc_files.append(file_path + extension)
378 if extension in ['.m', '.mm']:
379 objc_files.append(file_path + extension)
380 list_of_sources.append((c_files, cc_files, objc_files))
381 for c_files_list, cc_files_list, objc_files_list in list_of_sources:
382 if _MoreThanOneSourceUsed(c_files_list, cc_files_list, objc_files_list):
383 all_sources = sorted(c_files_list + cc_files_list + objc_files_list)
384 errors[gn_file.LocalPath()].append((target_name, all_sources))
385 if errors:
kjellander7439f972016-12-05 22:47:46 -0800386 return [output_api.PresubmitError(
Mirko Bonadei4dc4e252017-09-19 13:49:16 +0200387 'GN targets cannot mix .c, .cc and .m (or .mm) source files.\n'
388 'Please create a separate target for each collection of sources.\n'
kjellander7439f972016-12-05 22:47:46 -0800389 'Mixed sources: \n'
390 '%s\n'
Mirko Bonadei4dc4e252017-09-19 13:49:16 +0200391 'Violating GN files:\n%s\n' % (json.dumps(errors, indent=2),
392 '\n'.join(errors.keys())))]
kjellander7439f972016-12-05 22:47:46 -0800393 return []
394
charujain9893e252017-09-14 13:33:22 +0200395def CheckNoPackageBoundaryViolations(input_api, gn_files, output_api):
ehmaldonado4fb97462017-01-30 05:27:22 -0800396 cwd = input_api.PresubmitLocalPath()
Oleh Prypin2f33a562017-10-04 20:17:54 +0200397 with _AddToPath(input_api.os_path.join(
398 cwd, 'tools_webrtc', 'presubmit_checks_lib')):
399 from check_package_boundaries import CheckPackageBoundaries
400 build_files = [os.path.join(cwd, gn_file.LocalPath()) for gn_file in gn_files]
401 errors = CheckPackageBoundaries(cwd, build_files)[:5]
402 if errors:
ehmaldonado4fb97462017-01-30 05:27:22 -0800403 return [output_api.PresubmitError(
Oleh Prypin2f33a562017-10-04 20:17:54 +0200404 'There are package boundary violations in the following GN files:',
405 long_text='\n\n'.join(str(err) for err in errors))]
ehmaldonado4fb97462017-01-30 05:27:22 -0800406 return []
407
Mirko Bonadei5c1ad592017-12-12 11:52:27 +0100408def CheckPublicDepsIsNotUsed(gn_files, output_api):
409 result = []
410 error_msg = ('public_deps is not allowed in WebRTC BUILD.gn files because '
411 'it doesn\'t map well to downstream build systems.\n'
412 'Used in: %s (line %d).')
413 for affected_file in gn_files:
414 for (line_number, affected_line) in affected_file.ChangedContents():
415 if 'public_deps' in affected_line:
416 result.append(
417 output_api.PresubmitError(error_msg % (affected_file.LocalPath(),
418 line_number)))
419 return result
420
Patrik Höglund6f491062018-01-11 12:04:23 +0100421def CheckCheckIncludesIsNotUsed(gn_files, output_api):
422 result = []
423 error_msg = ('check_includes overrides are not allowed since it can cause '
424 'incorrect dependencies to form. It effectively means that your '
425 'module can include any .h file without depending on its '
426 'corresponding target. There are some exceptional cases when '
427 'this is allowed: if so, get approval from a .gn owner in the'
428 'root OWNERS file.\n'
429 'Used in: %s (line %d).')
430 for affected_file in gn_files:
431 for (line_number, affected_line) in affected_file.ChangedContents():
432 if 'check_includes' in affected_line:
433 result.append(
434 output_api.PresubmitError(error_msg % (affected_file.LocalPath(),
435 line_number)))
436 return result
437
charujain9893e252017-09-14 13:33:22 +0200438def CheckGnChanges(input_api, output_api):
ehmaldonado5b1ba082016-09-02 05:51:08 -0700439 source_file_filter = lambda x: input_api.FilterSourceFile(
Oleh Prypinafe01652017-10-04 15:56:08 +0200440 x, white_list=(r'.+\.(gn|gni)$',),
Mirko Bonadei5c1ad592017-12-12 11:52:27 +0100441 black_list=(r'.*/presubmit_checks_lib/testdata/.*',))
ehmaldonado5b1ba082016-09-02 05:51:08 -0700442
443 gn_files = []
444 for f in input_api.AffectedSourceFiles(source_file_filter):
Mirko Bonadei92ea95e2017-09-15 06:47:31 +0200445 gn_files.append(f)
ehmaldonado5b1ba082016-09-02 05:51:08 -0700446
447 result = []
448 if gn_files:
charujain9893e252017-09-14 13:33:22 +0200449 result.extend(CheckNoSourcesAbove(input_api, gn_files, output_api))
Mirko Bonadei4dc4e252017-09-19 13:49:16 +0200450 result.extend(CheckNoMixingSources(input_api, gn_files, output_api))
451 result.extend(CheckNoPackageBoundaryViolations(input_api, gn_files,
452 output_api))
Mirko Bonadei5c1ad592017-12-12 11:52:27 +0100453 result.extend(CheckPublicDepsIsNotUsed(gn_files, output_api))
Patrik Höglund6f491062018-01-11 12:04:23 +0100454 result.extend(CheckCheckIncludesIsNotUsed(gn_files, output_api))
ehmaldonado5b1ba082016-09-02 05:51:08 -0700455 return result
456
Oleh Prypin920b6532017-10-05 11:28:51 +0200457def CheckGnGen(input_api, output_api):
458 """Runs `gn gen --check` with default args to detect mismatches between
459 #includes and dependencies in the BUILD.gn files, as well as general build
460 errors.
461 """
462 with _AddToPath(input_api.os_path.join(
463 input_api.PresubmitLocalPath(), 'tools_webrtc', 'presubmit_checks_lib')):
464 from gn_check import RunGnCheck
465 errors = RunGnCheck(input_api.PresubmitLocalPath())[:5]
466 if errors:
467 return [output_api.PresubmitPromptWarning(
468 'Some #includes do not match the build dependency graph. Please run:\n'
469 ' gn gen --check <out_dir>',
470 long_text='\n\n'.join(errors))]
471 return []
472
charujain9893e252017-09-14 13:33:22 +0200473def CheckUnwantedDependencies(input_api, output_api):
kjellander@webrtc.org3bd41562014-09-01 11:06:37 +0000474 """Runs checkdeps on #include statements added in this
475 change. Breaking - rules is an error, breaking ! rules is a
476 warning.
477 """
478 # Copied from Chromium's src/PRESUBMIT.py.
479
480 # We need to wait until we have an input_api object and use this
481 # roundabout construct to import checkdeps because this file is
482 # eval-ed and thus doesn't have __file__.
Oleh Prypin2f33a562017-10-04 20:17:54 +0200483 checkdeps_path = input_api.os_path.join(input_api.PresubmitLocalPath(),
484 'buildtools', 'checkdeps')
485 if not os.path.exists(checkdeps_path):
486 return [output_api.PresubmitError(
487 'Cannot find checkdeps at %s\nHave you run "gclient sync" to '
488 'download all the DEPS entries?' % checkdeps_path)]
489 with _AddToPath(checkdeps_path):
kjellander@webrtc.org3bd41562014-09-01 11:06:37 +0000490 import checkdeps
491 from cpp_checker import CppChecker
492 from rules import Rule
kjellander@webrtc.org3bd41562014-09-01 11:06:37 +0000493
494 added_includes = []
495 for f in input_api.AffectedFiles():
496 if not CppChecker.IsCppFile(f.LocalPath()):
497 continue
498
Henrik Kjellander57e5fd22015-05-25 12:55:39 +0200499 changed_lines = [line for _, line in f.ChangedContents()]
kjellander@webrtc.org3bd41562014-09-01 11:06:37 +0000500 added_includes.append([f.LocalPath(), changed_lines])
501
502 deps_checker = checkdeps.DepsChecker(input_api.PresubmitLocalPath())
503
504 error_descriptions = []
505 warning_descriptions = []
506 for path, rule_type, rule_description in deps_checker.CheckAddedCppIncludes(
507 added_includes):
508 description_with_path = '%s\n %s' % (path, rule_description)
509 if rule_type == Rule.DISALLOW:
510 error_descriptions.append(description_with_path)
511 else:
512 warning_descriptions.append(description_with_path)
513
514 results = []
515 if error_descriptions:
516 results.append(output_api.PresubmitError(
kjellandera7066a32017-03-23 03:47:05 -0700517 'You added one or more #includes that violate checkdeps rules.\n'
518 'Check that the DEPS files in these locations contain valid rules.\n'
519 'See https://cs.chromium.org/chromium/src/buildtools/checkdeps/ for '
520 'more details about checkdeps.',
kjellander@webrtc.org3bd41562014-09-01 11:06:37 +0000521 error_descriptions))
522 if warning_descriptions:
523 results.append(output_api.PresubmitPromptOrNotify(
524 'You added one or more #includes of files that are temporarily\n'
525 'allowed but being removed. Can you avoid introducing the\n'
kjellandera7066a32017-03-23 03:47:05 -0700526 '#include? See relevant DEPS file(s) for details and contacts.\n'
527 'See https://cs.chromium.org/chromium/src/buildtools/checkdeps/ for '
528 'more details about checkdeps.',
kjellander@webrtc.org3bd41562014-09-01 11:06:37 +0000529 warning_descriptions))
530 return results
531
charujain9893e252017-09-14 13:33:22 +0200532def CheckCommitMessageBugEntry(input_api, output_api):
533 """Check that bug entries are well-formed in commit message."""
534 bogus_bug_msg = (
Mirko Bonadei61880182017-10-12 15:12:35 +0200535 'Bogus Bug entry: %s. Please specify the issue tracker prefix and the '
charujain9893e252017-09-14 13:33:22 +0200536 'issue number, separated by a colon, e.g. webrtc:123 or chromium:12345.')
537 results = []
Mirko Bonadei61880182017-10-12 15:12:35 +0200538 for bug in input_api.change.BugsFromDescription():
charujain9893e252017-09-14 13:33:22 +0200539 bug = bug.strip()
540 if bug.lower() == 'none':
541 continue
charujain81a58c72017-09-25 13:25:45 +0200542 if 'b/' not in bug and ':' not in bug:
charujain9893e252017-09-14 13:33:22 +0200543 try:
544 if int(bug) > 100000:
545 # Rough indicator for current chromium bugs.
546 prefix_guess = 'chromium'
547 else:
548 prefix_guess = 'webrtc'
Mirko Bonadei61880182017-10-12 15:12:35 +0200549 results.append('Bug entry requires issue tracker prefix, e.g. %s:%s' %
charujain9893e252017-09-14 13:33:22 +0200550 (prefix_guess, bug))
551 except ValueError:
552 results.append(bogus_bug_msg % bug)
charujain81a58c72017-09-25 13:25:45 +0200553 elif not (re.match(r'\w+:\d+', bug) or re.match(r'b/\d+', bug)):
charujain9893e252017-09-14 13:33:22 +0200554 results.append(bogus_bug_msg % bug)
555 return [output_api.PresubmitError(r) for r in results]
556
557def CheckChangeHasBugField(input_api, output_api):
Mirko Bonadei61880182017-10-12 15:12:35 +0200558 """Requires that the changelist is associated with a bug.
kjellanderd1e26a92016-09-19 08:11:16 -0700559
560 This check is stricter than the one in depot_tools/presubmit_canned_checks.py
Mirko Bonadei61880182017-10-12 15:12:35 +0200561 since it fails the presubmit if the bug field is missing or doesn't contain
kjellanderd1e26a92016-09-19 08:11:16 -0700562 a bug reference.
Mirko Bonadei61880182017-10-12 15:12:35 +0200563
564 This supports both 'BUG=' and 'Bug:' since we are in the process of migrating
565 to Gerrit and it encourages the usage of 'Bug:'.
kjellanderd1e26a92016-09-19 08:11:16 -0700566 """
Mirko Bonadei61880182017-10-12 15:12:35 +0200567 if input_api.change.BugsFromDescription():
kjellanderd1e26a92016-09-19 08:11:16 -0700568 return []
569 else:
570 return [output_api.PresubmitError(
Mirko Bonadei61880182017-10-12 15:12:35 +0200571 'The "Bug: [bug number]" footer is mandatory. Please create a bug and '
kjellanderd1e26a92016-09-19 08:11:16 -0700572 'reference it using either of:\n'
Mirko Bonadei61880182017-10-12 15:12:35 +0200573 ' * https://bugs.webrtc.org - reference it using Bug: webrtc:XXXX\n'
574 ' * https://crbug.com - reference it using Bug: chromium:XXXXXX')]
kjellander@webrtc.orge4158642014-08-06 09:11:18 +0000575
charujain9893e252017-09-14 13:33:22 +0200576def CheckJSONParseErrors(input_api, output_api):
kjellander569cf942016-02-11 05:02:59 -0800577 """Check that JSON files do not contain syntax errors."""
578
579 def FilterFile(affected_file):
580 return input_api.os_path.splitext(affected_file.LocalPath())[1] == '.json'
581
582 def GetJSONParseError(input_api, filename):
583 try:
584 contents = input_api.ReadFile(filename)
585 input_api.json.loads(contents)
586 except ValueError as e:
587 return e
588 return None
589
590 results = []
591 for affected_file in input_api.AffectedFiles(
592 file_filter=FilterFile, include_deletes=False):
593 parse_error = GetJSONParseError(input_api,
594 affected_file.AbsoluteLocalPath())
595 if parse_error:
596 results.append(output_api.PresubmitError('%s could not be parsed: %s' %
597 (affected_file.LocalPath(), parse_error)))
598 return results
599
600
charujain9893e252017-09-14 13:33:22 +0200601def RunPythonTests(input_api, output_api):
kjellanderc88b5d52017-04-05 06:42:43 -0700602 def Join(*args):
Henrik Kjellander8d3ad822015-05-26 19:52:05 +0200603 return input_api.os_path.join(input_api.PresubmitLocalPath(), *args)
604
605 test_directories = [
Edward Lemur6d01f6d2017-09-14 17:02:01 +0200606 input_api.PresubmitLocalPath(),
Mirko Bonadei92ea95e2017-09-15 06:47:31 +0200607 Join('rtc_tools', 'py_event_log_analyzer'),
608 Join('rtc_tools'),
609 Join('audio', 'test', 'unittests'),
ehmaldonado4fb97462017-01-30 05:27:22 -0800610 ] + [
Henrik Kjellander90fd7d82017-05-09 08:30:10 +0200611 root for root, _, files in os.walk(Join('tools_webrtc'))
ehmaldonado4fb97462017-01-30 05:27:22 -0800612 if any(f.endswith('_test.py') for f in files)
Henrik Kjellander8d3ad822015-05-26 19:52:05 +0200613 ]
614
615 tests = []
616 for directory in test_directories:
617 tests.extend(
618 input_api.canned_checks.GetUnitTestsInDirectory(
619 input_api,
620 output_api,
621 directory,
622 whitelist=[r'.+_test\.py$']))
623 return input_api.RunTests(tests, parallel=True)
624
625
charujain9893e252017-09-14 13:33:22 +0200626def CheckUsageOfGoogleProtobufNamespace(input_api, output_api):
mbonadei38415b22017-04-07 05:38:01 -0700627 """Checks that the namespace google::protobuf has not been used."""
628 files = []
629 pattern = input_api.re.compile(r'google::protobuf')
Mirko Bonadei92ea95e2017-09-15 06:47:31 +0200630 proto_utils_path = os.path.join('rtc_base', 'protobuf_utils.h')
mbonadei38415b22017-04-07 05:38:01 -0700631 for f in input_api.AffectedSourceFiles(input_api.FilterSourceFile):
632 if f.LocalPath() in [proto_utils_path, 'PRESUBMIT.py']:
633 continue
634 contents = input_api.ReadFile(f)
635 if pattern.search(contents):
636 files.append(f)
637
638 if files:
639 return [output_api.PresubmitError(
640 'Please avoid to use namespace `google::protobuf` directly.\n'
641 'Add a using directive in `%s` and include that header instead.'
642 % proto_utils_path, files)]
643 return []
644
645
Mirko Bonadei92ea95e2017-09-15 06:47:31 +0200646def _LicenseHeader(input_api):
647 """Returns the license header regexp."""
648 # Accept any year number from 2003 to the current year
649 current_year = int(input_api.time.strftime('%Y'))
650 allowed_years = (str(s) for s in reversed(xrange(2003, current_year + 1)))
651 years_re = '(' + '|'.join(allowed_years) + ')'
652 license_header = (
653 r'.*? Copyright( \(c\))? %(year)s The WebRTC [Pp]roject [Aa]uthors\. '
654 r'All [Rr]ights [Rr]eserved\.\n'
655 r'.*?\n'
656 r'.*? Use of this source code is governed by a BSD-style license\n'
657 r'.*? that can be found in the LICENSE file in the root of the source\n'
658 r'.*? tree\. An additional intellectual property rights grant can be '
659 r'found\n'
660 r'.*? in the file PATENTS\. All contributing project authors may\n'
661 r'.*? be found in the AUTHORS file in the root of the source tree\.\n'
662 ) % {
663 'year': years_re,
664 }
665 return license_header
666
667
charujain9893e252017-09-14 13:33:22 +0200668def CommonChecks(input_api, output_api):
andrew@webrtc.org53df1362012-01-26 21:24:23 +0000669 """Checks common to both upload and commit."""
niklase@google.comda159d62011-05-30 11:51:34 +0000670 results = []
tkchin42f580e2015-11-26 23:18:23 -0800671 # Filter out files that are in objc or ios dirs from being cpplint-ed since
672 # they do not follow C++ lint rules.
673 black_list = input_api.DEFAULT_BLACK_LIST + (
674 r".*\bobjc[\\\/].*",
Kári Tristan Helgason3fa35172016-09-09 08:55:05 +0000675 r".*objc\.[hcm]+$",
tkchin42f580e2015-11-26 23:18:23 -0800676 )
677 source_file_filter = lambda x: input_api.FilterSourceFile(x, None, black_list)
charujain9893e252017-09-14 13:33:22 +0200678 results.extend(CheckApprovedFilesLintClean(
tkchin42f580e2015-11-26 23:18:23 -0800679 input_api, output_api, source_file_filter))
Mirko Bonadei92ea95e2017-09-15 06:47:31 +0200680 results.extend(input_api.canned_checks.CheckLicense(
681 input_api, output_api, _LicenseHeader(input_api)))
phoglund@webrtc.org5d3713932013-03-07 09:59:43 +0000682 results.extend(input_api.canned_checks.RunPylint(input_api, output_api,
kjellander@webrtc.org177567c2016-12-22 10:40:28 +0100683 black_list=(r'^base[\\\/].*\.py$',
Henrik Kjellander14771ac2015-06-02 13:10:04 +0200684 r'^build[\\\/].*\.py$',
685 r'^buildtools[\\\/].*\.py$',
kjellander38c65c82017-04-12 22:43:38 -0700686 r'^infra[\\\/].*\.py$',
Henrik Kjellander0779e8f2016-12-22 12:01:17 +0100687 r'^ios[\\\/].*\.py$',
Henrik Kjellander14771ac2015-06-02 13:10:04 +0200688 r'^out.*[\\\/].*\.py$',
689 r'^testing[\\\/].*\.py$',
690 r'^third_party[\\\/].*\.py$',
kjellander@webrtc.org177567c2016-12-22 10:40:28 +0100691 r'^tools[\\\/].*\.py$',
kjellanderafd54942016-12-17 12:21:39 -0800692 # TODO(phoglund): should arguably be checked.
Henrik Kjellander90fd7d82017-05-09 08:30:10 +0200693 r'^tools_webrtc[\\\/]mb[\\\/].*\.py$',
694 r'^tools_webrtc[\\\/]valgrind[\\\/].*\.py$',
Henrik Kjellander14771ac2015-06-02 13:10:04 +0200695 r'^xcodebuild.*[\\\/].*\.py$',),
Henrik Kjellander57e5fd22015-05-25 12:55:39 +0200696 pylintrc='pylintrc'))
kjellander569cf942016-02-11 05:02:59 -0800697
nisse3d21e232016-09-02 03:07:06 -0700698 # TODO(nisse): talk/ is no more, so make below checks simpler?
Henrik Kjellander57e5fd22015-05-25 12:55:39 +0200699 # WebRTC can't use the presubmit_canned_checks.PanProjectChecks function since
700 # we need to have different license checks in talk/ and webrtc/ directories.
701 # Instead, hand-picked checks are included below.
Henrik Kjellander63224672015-09-08 08:03:56 +0200702
tkchin3cd9a302016-06-08 12:40:28 -0700703 # .m and .mm files are ObjC files. For simplicity we will consider .h files in
704 # ObjC subdirectories ObjC headers.
705 objc_filter_list = (r'.+\.m$', r'.+\.mm$', r'.+objc\/.+\.h$')
Henrik Kjellanderb4af3d62016-11-16 20:11:29 +0100706 # Skip long-lines check for DEPS and GN files.
707 build_file_filter_list = (r'.+\.gn$', r'.+\.gni$', 'DEPS')
tkchin3cd9a302016-06-08 12:40:28 -0700708 eighty_char_sources = lambda x: input_api.FilterSourceFile(x,
709 black_list=build_file_filter_list + objc_filter_list)
710 hundred_char_sources = lambda x: input_api.FilterSourceFile(x,
711 white_list=objc_filter_list)
andrew@webrtc.org2442de12012-01-23 17:45:41 +0000712 results.extend(input_api.canned_checks.CheckLongLines(
tkchin3cd9a302016-06-08 12:40:28 -0700713 input_api, output_api, maxlen=80, source_file_filter=eighty_char_sources))
714 results.extend(input_api.canned_checks.CheckLongLines(
715 input_api, output_api, maxlen=100,
716 source_file_filter=hundred_char_sources))
717
andrew@webrtc.org2442de12012-01-23 17:45:41 +0000718 results.extend(input_api.canned_checks.CheckChangeHasNoTabs(
719 input_api, output_api))
andrew@webrtc.org53df1362012-01-26 21:24:23 +0000720 results.extend(input_api.canned_checks.CheckChangeHasNoStrayWhitespace(
721 input_api, output_api))
kjellandere5dc62a2016-12-14 00:16:21 -0800722 results.extend(input_api.canned_checks.CheckAuthorizedAuthor(
723 input_api, output_api))
andrew@webrtc.org53df1362012-01-26 21:24:23 +0000724 results.extend(input_api.canned_checks.CheckChangeTodoHasOwner(
725 input_api, output_api))
charujain9893e252017-09-14 13:33:22 +0200726 results.extend(CheckNativeApiHeaderChanges(input_api, output_api))
727 results.extend(CheckNoIOStreamInHeaders(input_api, output_api))
728 results.extend(CheckNoPragmaOnce(input_api, output_api))
729 results.extend(CheckNoFRIEND_TEST(input_api, output_api))
730 results.extend(CheckGnChanges(input_api, output_api))
731 results.extend(CheckUnwantedDependencies(input_api, output_api))
732 results.extend(CheckJSONParseErrors(input_api, output_api))
733 results.extend(RunPythonTests(input_api, output_api))
734 results.extend(CheckUsageOfGoogleProtobufNamespace(input_api, output_api))
Mirko Bonadei866d3372017-09-15 12:35:26 +0200735 results.extend(CheckOrphanHeaders(input_api, output_api))
Mirko Bonadeia730c1c2017-09-18 11:33:13 +0200736 results.extend(CheckNewlineAtTheEndOfProtoFiles(input_api, output_api))
andrew@webrtc.org53df1362012-01-26 21:24:23 +0000737 return results
andrew@webrtc.org2442de12012-01-23 17:45:41 +0000738
kjellander@webrtc.orge4158642014-08-06 09:11:18 +0000739
andrew@webrtc.org53df1362012-01-26 21:24:23 +0000740def CheckChangeOnUpload(input_api, output_api):
741 results = []
charujain9893e252017-09-14 13:33:22 +0200742 results.extend(CommonChecks(input_api, output_api))
Oleh Prypin920b6532017-10-05 11:28:51 +0200743 results.extend(CheckGnGen(input_api, output_api))
Henrik Kjellander57e5fd22015-05-25 12:55:39 +0200744 results.extend(
745 input_api.canned_checks.CheckGNFormatted(input_api, output_api))
niklase@google.comda159d62011-05-30 11:51:34 +0000746 return results
747
kjellander@webrtc.orge4158642014-08-06 09:11:18 +0000748
andrew@webrtc.org2442de12012-01-23 17:45:41 +0000749def CheckChangeOnCommit(input_api, output_api):
niklase@google.com1198db92011-06-09 07:07:24 +0000750 results = []
charujain9893e252017-09-14 13:33:22 +0200751 results.extend(CommonChecks(input_api, output_api))
752 results.extend(VerifyNativeApiHeadersListIsValid(input_api, output_api))
niklase@google.com1198db92011-06-09 07:07:24 +0000753 results.extend(input_api.canned_checks.CheckOwners(input_api, output_api))
andrew@webrtc.org53df1362012-01-26 21:24:23 +0000754 results.extend(input_api.canned_checks.CheckChangeWasUploaded(
755 input_api, output_api))
756 results.extend(input_api.canned_checks.CheckChangeHasDescription(
757 input_api, output_api))
charujain9893e252017-09-14 13:33:22 +0200758 results.extend(CheckChangeHasBugField(input_api, output_api))
759 results.extend(CheckCommitMessageBugEntry(input_api, output_api))
kjellander@webrtc.org12cb88c2014-02-13 11:53:43 +0000760 results.extend(input_api.canned_checks.CheckTreeIsOpen(
761 input_api, output_api,
762 json_url='http://webrtc-status.appspot.com/current?format=json'))
niklase@google.com1198db92011-06-09 07:07:24 +0000763 return results
mbonadei74973ed2017-05-09 07:58:05 -0700764
765
charujain9893e252017-09-14 13:33:22 +0200766def CheckOrphanHeaders(input_api, output_api):
mbonadei74973ed2017-05-09 07:58:05 -0700767 # We need to wait until we have an input_api object and use this
768 # roundabout construct to import prebubmit_checks_lib because this file is
769 # eval-ed and thus doesn't have __file__.
Patrik Höglund2f3f7222017-12-19 11:08:56 +0100770 error_msg = """{} should be listed in {}."""
mbonadei74973ed2017-05-09 07:58:05 -0700771 results = []
Patrik Höglund7e60de22018-01-09 14:22:00 +0100772 orphan_blacklist = [
773 os.path.join('tools_webrtc', 'ios', 'SDK'),
774 ]
Oleh Prypin2f33a562017-10-04 20:17:54 +0200775 with _AddToPath(input_api.os_path.join(
776 input_api.PresubmitLocalPath(), 'tools_webrtc', 'presubmit_checks_lib')):
mbonadei74973ed2017-05-09 07:58:05 -0700777 from check_orphan_headers import GetBuildGnPathFromFilePath
778 from check_orphan_headers import IsHeaderInBuildGn
mbonadei74973ed2017-05-09 07:58:05 -0700779
Patrik Höglund7e60de22018-01-09 14:22:00 +0100780 source_file_filter = lambda x: input_api.FilterSourceFile(
781 x, black_list=orphan_blacklist)
782 for f in input_api.AffectedSourceFiles(source_file_filter):
783 if f.LocalPath().endswith('.h'):
mbonadei74973ed2017-05-09 07:58:05 -0700784 file_path = os.path.abspath(f.LocalPath())
785 root_dir = os.getcwd()
786 gn_file_path = GetBuildGnPathFromFilePath(file_path, os.path.exists,
787 root_dir)
788 in_build_gn = IsHeaderInBuildGn(file_path, gn_file_path)
789 if not in_build_gn:
790 results.append(output_api.PresubmitError(error_msg.format(
Patrik Höglund2f3f7222017-12-19 11:08:56 +0100791 f.LocalPath(), os.path.relpath(gn_file_path))))
mbonadei74973ed2017-05-09 07:58:05 -0700792 return results
Mirko Bonadei960fd5b2017-06-29 14:59:36 +0200793
794
Mirko Bonadeia730c1c2017-09-18 11:33:13 +0200795def CheckNewlineAtTheEndOfProtoFiles(input_api, output_api):
Mirko Bonadei960fd5b2017-06-29 14:59:36 +0200796 """Checks that all .proto files are terminated with a newline."""
797 error_msg = 'File {} must end with exactly one newline.'
798 results = []
799 source_file_filter = lambda x: input_api.FilterSourceFile(
800 x, white_list=(r'.+\.proto$',))
801 for f in input_api.AffectedSourceFiles(source_file_filter):
802 file_path = f.LocalPath()
803 with open(file_path) as f:
804 lines = f.readlines()
Mirko Bonadeia730c1c2017-09-18 11:33:13 +0200805 if len(lines) > 0 and not lines[-1].endswith('\n'):
Mirko Bonadei960fd5b2017-06-29 14:59:36 +0200806 results.append(output_api.PresubmitError(error_msg.format(file_path)))
807 return results