blob: e287e7eb507ebcea15ac4c7979a879e7ca4c763c [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 Bonadei92ea95e2017-09-15 06:47:31 +020071 'media',
72 'modules/audio_device/include',
73 'pc',
kjellanderdd705472016-06-09 11:17:27 -070074)
Mirko Bonadei4dc4e252017-09-19 13:49:16 +020075
kjellanderdd705472016-06-09 11:17:27 -070076# These directories should not be used but are maintained only to avoid breaking
77# some legacy downstream code.
78LEGACY_API_DIRS = (
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020079 'common_audio/include',
80 'modules/audio_coding/include',
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020081 'modules/audio_processing/include',
82 'modules/bitrate_controller/include',
83 'modules/congestion_controller/include',
84 'modules/include',
85 'modules/remote_bitrate_estimator/include',
86 'modules/rtp_rtcp/include',
87 'modules/rtp_rtcp/source',
88 'modules/utility/include',
89 'modules/video_coding/codecs/h264/include',
90 'modules/video_coding/codecs/i420/include',
91 'modules/video_coding/codecs/vp8/include',
92 'modules/video_coding/codecs/vp9/include',
93 'modules/video_coding/include',
94 'rtc_base',
95 'system_wrappers/include',
96 'voice_engine/include',
kjellander53047c92015-12-02 23:56:14 -080097)
Mirko Bonadei4dc4e252017-09-19 13:49:16 +020098
Karl Wibergd4f01c12017-11-10 10:55:45 +010099# NOTE: The set of directories in API_DIRS should be the same as those
100# listed in the table in native-api.md.
kjellanderdd705472016-06-09 11:17:27 -0700101API_DIRS = NATIVE_API_DIRS[:] + LEGACY_API_DIRS[:]
kjellander53047c92015-12-02 23:56:14 -0800102
Mirko Bonadei4dc4e252017-09-19 13:49:16 +0200103# TARGET_RE matches a GN target, and extracts the target name and the contents.
104TARGET_RE = re.compile(r'(?P<indent>\s*)\w+\("(?P<target_name>\w+)"\) {'
105 r'(?P<target_contents>.*?)'
106 r'(?P=indent)}',
107 re.MULTILINE | re.DOTALL)
108
109# SOURCES_RE matches a block of sources inside a GN target.
110SOURCES_RE = re.compile(r'sources \+?= \[(?P<sources>.*?)\]',
111 re.MULTILINE | re.DOTALL)
112
113# FILE_PATH_RE matchies a file path.
114FILE_PATH_RE = re.compile(r'"(?P<file_path>(\w|\/)+)(?P<extension>\.\w+)"')
115
kjellander53047c92015-12-02 23:56:14 -0800116
Oleh Prypin2f33a562017-10-04 20:17:54 +0200117@contextmanager
118def _AddToPath(*paths):
119 original_sys_path = sys.path
120 sys.path.extend(paths)
121 try:
122 yield
123 finally:
124 # Restore sys.path to what it was before.
125 sys.path = original_sys_path
ehmaldonado4fb97462017-01-30 05:27:22 -0800126
127
charujain9893e252017-09-14 13:33:22 +0200128def VerifyNativeApiHeadersListIsValid(input_api, output_api):
kjellander53047c92015-12-02 23:56:14 -0800129 """Ensures the list of native API header directories is up to date."""
130 non_existing_paths = []
131 native_api_full_paths = [
132 input_api.os_path.join(input_api.PresubmitLocalPath(),
kjellanderdd705472016-06-09 11:17:27 -0700133 *path.split('/')) for path in API_DIRS]
kjellander53047c92015-12-02 23:56:14 -0800134 for path in native_api_full_paths:
135 if not os.path.isdir(path):
136 non_existing_paths.append(path)
137 if non_existing_paths:
138 return [output_api.PresubmitError(
139 'Directories to native API headers have changed which has made the '
140 'list in PRESUBMIT.py outdated.\nPlease update it to the current '
141 'location of our native APIs.',
142 non_existing_paths)]
143 return []
144
kjellanderc88b5d52017-04-05 06:42:43 -0700145API_CHANGE_MSG = """
kwibergeb133022016-04-07 07:41:48 -0700146You seem to be changing native API header files. Please make sure that you:
oprypin375b9ac2017-02-13 04:13:23 -0800147 1. Make compatible changes that don't break existing clients. Usually
148 this is done by keeping the existing method signatures unchanged.
149 2. Mark the old stuff as deprecated (see RTC_DEPRECATED macro).
kwibergeb133022016-04-07 07:41:48 -0700150 3. Create a timeline and plan for when the deprecated stuff will be
151 removed. (The amount of time we give users to change their code
152 should be informed by how much work it is for them. If they just
153 need to replace one name with another or something equally
154 simple, 1-2 weeks might be good; if they need to do serious work,
155 up to 3 months may be called for.)
156 4. Update/inform existing downstream code owners to stop using the
157 deprecated stuff. (Send announcements to
158 discuss-webrtc@googlegroups.com and webrtc-users@google.com.)
159 5. Remove the deprecated stuff, once the agreed-upon amount of time
160 has passed.
161Related files:
162"""
kjellander53047c92015-12-02 23:56:14 -0800163
charujain9893e252017-09-14 13:33:22 +0200164def CheckNativeApiHeaderChanges(input_api, output_api):
kjellander53047c92015-12-02 23:56:14 -0800165 """Checks to remind proper changing of native APIs."""
166 files = []
Karl Wiberg6bfac032017-10-27 15:14:20 +0200167 source_file_filter = lambda x: input_api.FilterSourceFile(
168 x, white_list=[r'.+\.(gn|gni|h)$'])
169 for f in input_api.AffectedSourceFiles(source_file_filter):
170 for path in API_DIRS:
171 dn = os.path.dirname(f.LocalPath())
172 if path == 'api':
173 # Special case: Subdirectories included.
174 if dn == 'api' or dn.startswith('api/'):
175 files.append(f)
176 else:
177 # Normal case: Subdirectories not included.
178 if dn == path:
179 files.append(f)
kjellander53047c92015-12-02 23:56:14 -0800180
181 if files:
kjellanderc88b5d52017-04-05 06:42:43 -0700182 return [output_api.PresubmitNotifyResult(API_CHANGE_MSG, files)]
kjellander53047c92015-12-02 23:56:14 -0800183 return []
184
kjellander@webrtc.org0fcaf992015-11-26 15:24:52 +0100185
charujain9893e252017-09-14 13:33:22 +0200186def CheckNoIOStreamInHeaders(input_api, output_api):
kjellander@webrtc.org51198f12012-02-21 17:53:46 +0000187 """Checks to make sure no .h files include <iostream>."""
188 files = []
189 pattern = input_api.re.compile(r'^#include\s*<iostream>',
190 input_api.re.MULTILINE)
191 for f in input_api.AffectedSourceFiles(input_api.FilterSourceFile):
192 if not f.LocalPath().endswith('.h'):
193 continue
194 contents = input_api.ReadFile(f)
195 if pattern.search(contents):
196 files.append(f)
197
198 if len(files):
Henrik Kjellander57e5fd22015-05-25 12:55:39 +0200199 return [output_api.PresubmitError(
kjellander@webrtc.org51198f12012-02-21 17:53:46 +0000200 'Do not #include <iostream> in header files, since it inserts static ' +
201 'initialization into every file including the header. Instead, ' +
202 '#include <ostream>. See http://crbug.com/94794',
Henrik Kjellander57e5fd22015-05-25 12:55:39 +0200203 files)]
kjellander@webrtc.org51198f12012-02-21 17:53:46 +0000204 return []
205
kjellander@webrtc.orge4158642014-08-06 09:11:18 +0000206
charujain9893e252017-09-14 13:33:22 +0200207def CheckNoPragmaOnce(input_api, output_api):
kjellander6aeef742017-02-20 01:13:18 -0800208 """Make sure that banned functions are not used."""
209 files = []
210 pattern = input_api.re.compile(r'^#pragma\s+once',
211 input_api.re.MULTILINE)
212 for f in input_api.AffectedSourceFiles(input_api.FilterSourceFile):
213 if not f.LocalPath().endswith('.h'):
214 continue
215 contents = input_api.ReadFile(f)
216 if pattern.search(contents):
217 files.append(f)
218
219 if files:
220 return [output_api.PresubmitError(
221 'Do not use #pragma once in header files.\n'
222 'See http://www.chromium.org/developers/coding-style#TOC-File-headers',
223 files)]
224 return []
225
226
charujain9893e252017-09-14 13:33:22 +0200227def CheckNoFRIEND_TEST(input_api, output_api): # pylint: disable=invalid-name
kjellander@webrtc.org51198f12012-02-21 17:53:46 +0000228 """Make sure that gtest's FRIEND_TEST() macro is not used, the
229 FRIEND_TEST_ALL_PREFIXES() macro from testsupport/gtest_prod_util.h should be
230 used instead since that allows for FLAKY_, FAILS_ and DISABLED_ prefixes."""
231 problems = []
232
233 file_filter = lambda f: f.LocalPath().endswith(('.cc', '.h'))
234 for f in input_api.AffectedFiles(file_filter=file_filter):
235 for line_num, line in f.ChangedContents():
236 if 'FRIEND_TEST(' in line:
237 problems.append(' %s:%d' % (f.LocalPath(), line_num))
238
239 if not problems:
240 return []
241 return [output_api.PresubmitPromptWarning('WebRTC\'s code should not use '
242 'gtest\'s FRIEND_TEST() macro. Include testsupport/gtest_prod_util.h and '
243 'use FRIEND_TEST_ALL_PREFIXES() instead.\n' + '\n'.join(problems))]
244
kjellander@webrtc.orge4158642014-08-06 09:11:18 +0000245
charujain9893e252017-09-14 13:33:22 +0200246def IsLintBlacklisted(blacklist_paths, file_path):
oprypin2aa463f2017-03-23 03:17:02 -0700247 """ Checks if a file is blacklisted for lint check."""
248 for path in blacklist_paths:
249 if file_path == path or os.path.dirname(file_path).startswith(path):
kjellander@webrtc.org0fcaf992015-11-26 15:24:52 +0100250 return True
251 return False
252
253
charujain9893e252017-09-14 13:33:22 +0200254def CheckApprovedFilesLintClean(input_api, output_api,
mflodman@webrtc.org2a452092012-07-01 05:55:23 +0000255 source_file_filter=None):
oprypin2aa463f2017-03-23 03:17:02 -0700256 """Checks that all new or non-blacklisted .cc and .h files pass cpplint.py.
charujain9893e252017-09-14 13:33:22 +0200257 This check is based on CheckChangeLintsClean in
kjellander@webrtc.org51198f12012-02-21 17:53:46 +0000258 depot_tools/presubmit_canned_checks.py but has less filters and only checks
259 added files."""
260 result = []
261
262 # Initialize cpplint.
263 import cpplint
264 # Access to a protected member _XX of a client class
265 # pylint: disable=W0212
266 cpplint._cpplint_state.ResetErrorCounts()
267
jbauchc4e3ead2016-02-19 00:25:55 -0800268 lint_filters = cpplint._Filters()
269 lint_filters.extend(BLACKLIST_LINT_FILTERS)
270 cpplint._SetFilters(','.join(lint_filters))
271
oprypin2aa463f2017-03-23 03:17:02 -0700272 # Create a platform independent blacklist for cpplint.
273 blacklist_paths = [input_api.os_path.join(*path.split('/'))
274 for path in CPPLINT_BLACKLIST]
kjellander@webrtc.org0fcaf992015-11-26 15:24:52 +0100275
kjellander@webrtc.org51198f12012-02-21 17:53:46 +0000276 # Use the strictest verbosity level for cpplint.py (level 1) which is the
oprypin2aa463f2017-03-23 03:17:02 -0700277 # default when running cpplint.py from command line. To make it possible to
278 # work with not-yet-converted code, we're only applying it to new (or
279 # moved/renamed) files and files not listed in CPPLINT_BLACKLIST.
kjellander@webrtc.org51198f12012-02-21 17:53:46 +0000280 verbosity_level = 1
281 files = []
282 for f in input_api.AffectedSourceFiles(source_file_filter):
Henrik Kjellander57e5fd22015-05-25 12:55:39 +0200283 # Note that moved/renamed files also count as added.
charujain9893e252017-09-14 13:33:22 +0200284 if f.Action() == 'A' or not IsLintBlacklisted(blacklist_paths,
oprypin2aa463f2017-03-23 03:17:02 -0700285 f.LocalPath()):
kjellander@webrtc.org51198f12012-02-21 17:53:46 +0000286 files.append(f.AbsoluteLocalPath())
mflodman@webrtc.org2a452092012-07-01 05:55:23 +0000287
kjellander@webrtc.org51198f12012-02-21 17:53:46 +0000288 for file_name in files:
289 cpplint.ProcessFile(file_name, verbosity_level)
290
291 if cpplint._cpplint_state.error_count > 0:
292 if input_api.is_committing:
oprypin8e58d652017-03-21 07:52:41 -0700293 res_type = output_api.PresubmitError
kjellander@webrtc.org51198f12012-02-21 17:53:46 +0000294 else:
295 res_type = output_api.PresubmitPromptWarning
296 result = [res_type('Changelist failed cpplint.py check.')]
297
298 return result
299
charujain9893e252017-09-14 13:33:22 +0200300def CheckNoSourcesAbove(input_api, gn_files, output_api):
ehmaldonado5b1ba082016-09-02 05:51:08 -0700301 # Disallow referencing source files with paths above the GN file location.
302 source_pattern = input_api.re.compile(r' +sources \+?= \[(.*?)\]',
303 re.MULTILINE | re.DOTALL)
304 file_pattern = input_api.re.compile(r'"((\.\./.*?)|(//.*?))"')
305 violating_gn_files = set()
306 violating_source_entries = []
307 for gn_file in gn_files:
308 contents = input_api.ReadFile(gn_file)
309 for source_block_match in source_pattern.finditer(contents):
310 # Find all source list entries starting with ../ in the source block
311 # (exclude overrides entries).
312 for file_list_match in file_pattern.finditer(source_block_match.group(1)):
313 source_file = file_list_match.group(1)
314 if 'overrides/' not in source_file:
315 violating_source_entries.append(source_file)
316 violating_gn_files.add(gn_file)
317 if violating_gn_files:
318 return [output_api.PresubmitError(
319 'Referencing source files above the directory of the GN file is not '
Henrik Kjellanderb4af3d62016-11-16 20:11:29 +0100320 'allowed. Please introduce new GN targets in the proper location '
321 'instead.\n'
ehmaldonado5b1ba082016-09-02 05:51:08 -0700322 'Invalid source entries:\n'
323 '%s\n'
324 'Violating GN files:' % '\n'.join(violating_source_entries),
325 items=violating_gn_files)]
326 return []
327
Mirko Bonadei4dc4e252017-09-19 13:49:16 +0200328def CheckNoMixingSources(input_api, gn_files, output_api):
329 """Disallow mixing C, C++ and Obj-C/Obj-C++ in the same target.
330
331 See bugs.webrtc.org/7743 for more context.
332 """
333 def _MoreThanOneSourceUsed(*sources_lists):
334 sources_used = 0
335 for source_list in sources_lists:
336 if len(source_list):
337 sources_used += 1
338 return sources_used > 1
339
340 errors = defaultdict(lambda: [])
kjellander7439f972016-12-05 22:47:46 -0800341 for gn_file in gn_files:
Mirko Bonadei4dc4e252017-09-19 13:49:16 +0200342 gn_file_content = input_api.ReadFile(gn_file)
343 for target_match in TARGET_RE.finditer(gn_file_content):
344 # list_of_sources is a list of tuples of the form
345 # (c_files, cc_files, objc_files) that keeps track of all the sources
346 # defined in a target. A GN target can have more that on definition of
347 # sources (since it supports if/else statements).
348 # E.g.:
349 # rtc_static_library("foo") {
350 # if (is_win) {
351 # sources = [ "foo.cc" ]
352 # } else {
353 # sources = [ "foo.mm" ]
354 # }
355 # }
356 # This is allowed and the presubmit check should support this case.
357 list_of_sources = []
kjellander7439f972016-12-05 22:47:46 -0800358 c_files = []
359 cc_files = []
Mirko Bonadei4dc4e252017-09-19 13:49:16 +0200360 objc_files = []
361 target_name = target_match.group('target_name')
362 target_contents = target_match.group('target_contents')
363 for sources_match in SOURCES_RE.finditer(target_contents):
364 if '+=' not in sources_match.group(0):
365 if c_files or cc_files or objc_files:
366 list_of_sources.append((c_files, cc_files, objc_files))
367 c_files = []
368 cc_files = []
369 objc_files = []
370 for file_match in FILE_PATH_RE.finditer(sources_match.group(1)):
371 file_path = file_match.group('file_path')
372 extension = file_match.group('extension')
373 if extension == '.c':
374 c_files.append(file_path + extension)
375 if extension == '.cc':
376 cc_files.append(file_path + extension)
377 if extension in ['.m', '.mm']:
378 objc_files.append(file_path + extension)
379 list_of_sources.append((c_files, cc_files, objc_files))
380 for c_files_list, cc_files_list, objc_files_list in list_of_sources:
381 if _MoreThanOneSourceUsed(c_files_list, cc_files_list, objc_files_list):
382 all_sources = sorted(c_files_list + cc_files_list + objc_files_list)
383 errors[gn_file.LocalPath()].append((target_name, all_sources))
384 if errors:
kjellander7439f972016-12-05 22:47:46 -0800385 return [output_api.PresubmitError(
Mirko Bonadei4dc4e252017-09-19 13:49:16 +0200386 'GN targets cannot mix .c, .cc and .m (or .mm) source files.\n'
387 'Please create a separate target for each collection of sources.\n'
kjellander7439f972016-12-05 22:47:46 -0800388 'Mixed sources: \n'
389 '%s\n'
Mirko Bonadei4dc4e252017-09-19 13:49:16 +0200390 'Violating GN files:\n%s\n' % (json.dumps(errors, indent=2),
391 '\n'.join(errors.keys())))]
kjellander7439f972016-12-05 22:47:46 -0800392 return []
393
charujain9893e252017-09-14 13:33:22 +0200394def CheckNoPackageBoundaryViolations(input_api, gn_files, output_api):
ehmaldonado4fb97462017-01-30 05:27:22 -0800395 cwd = input_api.PresubmitLocalPath()
Oleh Prypin2f33a562017-10-04 20:17:54 +0200396 with _AddToPath(input_api.os_path.join(
397 cwd, 'tools_webrtc', 'presubmit_checks_lib')):
398 from check_package_boundaries import CheckPackageBoundaries
399 build_files = [os.path.join(cwd, gn_file.LocalPath()) for gn_file in gn_files]
400 errors = CheckPackageBoundaries(cwd, build_files)[:5]
401 if errors:
ehmaldonado4fb97462017-01-30 05:27:22 -0800402 return [output_api.PresubmitError(
Oleh Prypin2f33a562017-10-04 20:17:54 +0200403 'There are package boundary violations in the following GN files:',
404 long_text='\n\n'.join(str(err) for err in errors))]
ehmaldonado4fb97462017-01-30 05:27:22 -0800405 return []
406
Mirko Bonadei5c1ad592017-12-12 11:52:27 +0100407def CheckPublicDepsIsNotUsed(gn_files, output_api):
408 result = []
409 error_msg = ('public_deps is not allowed in WebRTC BUILD.gn files because '
410 'it doesn\'t map well to downstream build systems.\n'
411 'Used in: %s (line %d).')
412 for affected_file in gn_files:
413 for (line_number, affected_line) in affected_file.ChangedContents():
414 if 'public_deps' in affected_line:
415 result.append(
416 output_api.PresubmitError(error_msg % (affected_file.LocalPath(),
417 line_number)))
418 return result
419
Patrik Höglund6f491062018-01-11 12:04:23 +0100420def CheckCheckIncludesIsNotUsed(gn_files, output_api):
421 result = []
422 error_msg = ('check_includes overrides are not allowed since it can cause '
423 'incorrect dependencies to form. It effectively means that your '
424 'module can include any .h file without depending on its '
425 'corresponding target. There are some exceptional cases when '
426 'this is allowed: if so, get approval from a .gn owner in the'
427 'root OWNERS file.\n'
428 'Used in: %s (line %d).')
429 for affected_file in gn_files:
430 for (line_number, affected_line) in affected_file.ChangedContents():
431 if 'check_includes' in affected_line:
432 result.append(
433 output_api.PresubmitError(error_msg % (affected_file.LocalPath(),
434 line_number)))
435 return result
436
charujain9893e252017-09-14 13:33:22 +0200437def CheckGnChanges(input_api, output_api):
ehmaldonado5b1ba082016-09-02 05:51:08 -0700438 source_file_filter = lambda x: input_api.FilterSourceFile(
Oleh Prypinafe01652017-10-04 15:56:08 +0200439 x, white_list=(r'.+\.(gn|gni)$',),
Mirko Bonadei5c1ad592017-12-12 11:52:27 +0100440 black_list=(r'.*/presubmit_checks_lib/testdata/.*',))
ehmaldonado5b1ba082016-09-02 05:51:08 -0700441
442 gn_files = []
443 for f in input_api.AffectedSourceFiles(source_file_filter):
Mirko Bonadei92ea95e2017-09-15 06:47:31 +0200444 gn_files.append(f)
ehmaldonado5b1ba082016-09-02 05:51:08 -0700445
446 result = []
447 if gn_files:
charujain9893e252017-09-14 13:33:22 +0200448 result.extend(CheckNoSourcesAbove(input_api, gn_files, output_api))
Mirko Bonadei4dc4e252017-09-19 13:49:16 +0200449 result.extend(CheckNoMixingSources(input_api, gn_files, output_api))
450 result.extend(CheckNoPackageBoundaryViolations(input_api, gn_files,
451 output_api))
Mirko Bonadei5c1ad592017-12-12 11:52:27 +0100452 result.extend(CheckPublicDepsIsNotUsed(gn_files, output_api))
Patrik Höglund6f491062018-01-11 12:04:23 +0100453 result.extend(CheckCheckIncludesIsNotUsed(gn_files, output_api))
ehmaldonado5b1ba082016-09-02 05:51:08 -0700454 return result
455
Oleh Prypin920b6532017-10-05 11:28:51 +0200456def CheckGnGen(input_api, output_api):
457 """Runs `gn gen --check` with default args to detect mismatches between
458 #includes and dependencies in the BUILD.gn files, as well as general build
459 errors.
460 """
461 with _AddToPath(input_api.os_path.join(
462 input_api.PresubmitLocalPath(), 'tools_webrtc', 'presubmit_checks_lib')):
463 from gn_check import RunGnCheck
464 errors = RunGnCheck(input_api.PresubmitLocalPath())[:5]
465 if errors:
466 return [output_api.PresubmitPromptWarning(
467 'Some #includes do not match the build dependency graph. Please run:\n'
468 ' gn gen --check <out_dir>',
469 long_text='\n\n'.join(errors))]
470 return []
471
charujain9893e252017-09-14 13:33:22 +0200472def CheckUnwantedDependencies(input_api, output_api):
kjellander@webrtc.org3bd41562014-09-01 11:06:37 +0000473 """Runs checkdeps on #include statements added in this
474 change. Breaking - rules is an error, breaking ! rules is a
475 warning.
476 """
477 # Copied from Chromium's src/PRESUBMIT.py.
478
479 # We need to wait until we have an input_api object and use this
480 # roundabout construct to import checkdeps because this file is
481 # eval-ed and thus doesn't have __file__.
Oleh Prypin2f33a562017-10-04 20:17:54 +0200482 checkdeps_path = input_api.os_path.join(input_api.PresubmitLocalPath(),
483 'buildtools', 'checkdeps')
484 if not os.path.exists(checkdeps_path):
485 return [output_api.PresubmitError(
486 'Cannot find checkdeps at %s\nHave you run "gclient sync" to '
487 'download all the DEPS entries?' % checkdeps_path)]
488 with _AddToPath(checkdeps_path):
kjellander@webrtc.org3bd41562014-09-01 11:06:37 +0000489 import checkdeps
490 from cpp_checker import CppChecker
491 from rules import Rule
kjellander@webrtc.org3bd41562014-09-01 11:06:37 +0000492
493 added_includes = []
494 for f in input_api.AffectedFiles():
495 if not CppChecker.IsCppFile(f.LocalPath()):
496 continue
497
Henrik Kjellander57e5fd22015-05-25 12:55:39 +0200498 changed_lines = [line for _, line in f.ChangedContents()]
kjellander@webrtc.org3bd41562014-09-01 11:06:37 +0000499 added_includes.append([f.LocalPath(), changed_lines])
500
501 deps_checker = checkdeps.DepsChecker(input_api.PresubmitLocalPath())
502
503 error_descriptions = []
504 warning_descriptions = []
505 for path, rule_type, rule_description in deps_checker.CheckAddedCppIncludes(
506 added_includes):
507 description_with_path = '%s\n %s' % (path, rule_description)
508 if rule_type == Rule.DISALLOW:
509 error_descriptions.append(description_with_path)
510 else:
511 warning_descriptions.append(description_with_path)
512
513 results = []
514 if error_descriptions:
515 results.append(output_api.PresubmitError(
kjellandera7066a32017-03-23 03:47:05 -0700516 'You added one or more #includes that violate checkdeps rules.\n'
517 'Check that the DEPS files in these locations contain valid rules.\n'
518 'See https://cs.chromium.org/chromium/src/buildtools/checkdeps/ for '
519 'more details about checkdeps.',
kjellander@webrtc.org3bd41562014-09-01 11:06:37 +0000520 error_descriptions))
521 if warning_descriptions:
522 results.append(output_api.PresubmitPromptOrNotify(
523 'You added one or more #includes of files that are temporarily\n'
524 'allowed but being removed. Can you avoid introducing the\n'
kjellandera7066a32017-03-23 03:47:05 -0700525 '#include? See relevant DEPS file(s) for details and contacts.\n'
526 'See https://cs.chromium.org/chromium/src/buildtools/checkdeps/ for '
527 'more details about checkdeps.',
kjellander@webrtc.org3bd41562014-09-01 11:06:37 +0000528 warning_descriptions))
529 return results
530
charujain9893e252017-09-14 13:33:22 +0200531def CheckCommitMessageBugEntry(input_api, output_api):
532 """Check that bug entries are well-formed in commit message."""
533 bogus_bug_msg = (
Mirko Bonadei61880182017-10-12 15:12:35 +0200534 'Bogus Bug entry: %s. Please specify the issue tracker prefix and the '
charujain9893e252017-09-14 13:33:22 +0200535 'issue number, separated by a colon, e.g. webrtc:123 or chromium:12345.')
536 results = []
Mirko Bonadei61880182017-10-12 15:12:35 +0200537 for bug in input_api.change.BugsFromDescription():
charujain9893e252017-09-14 13:33:22 +0200538 bug = bug.strip()
539 if bug.lower() == 'none':
540 continue
charujain81a58c72017-09-25 13:25:45 +0200541 if 'b/' not in bug and ':' not in bug:
charujain9893e252017-09-14 13:33:22 +0200542 try:
543 if int(bug) > 100000:
544 # Rough indicator for current chromium bugs.
545 prefix_guess = 'chromium'
546 else:
547 prefix_guess = 'webrtc'
Mirko Bonadei61880182017-10-12 15:12:35 +0200548 results.append('Bug entry requires issue tracker prefix, e.g. %s:%s' %
charujain9893e252017-09-14 13:33:22 +0200549 (prefix_guess, bug))
550 except ValueError:
551 results.append(bogus_bug_msg % bug)
charujain81a58c72017-09-25 13:25:45 +0200552 elif not (re.match(r'\w+:\d+', bug) or re.match(r'b/\d+', bug)):
charujain9893e252017-09-14 13:33:22 +0200553 results.append(bogus_bug_msg % bug)
554 return [output_api.PresubmitError(r) for r in results]
555
556def CheckChangeHasBugField(input_api, output_api):
Mirko Bonadei61880182017-10-12 15:12:35 +0200557 """Requires that the changelist is associated with a bug.
kjellanderd1e26a92016-09-19 08:11:16 -0700558
559 This check is stricter than the one in depot_tools/presubmit_canned_checks.py
Mirko Bonadei61880182017-10-12 15:12:35 +0200560 since it fails the presubmit if the bug field is missing or doesn't contain
kjellanderd1e26a92016-09-19 08:11:16 -0700561 a bug reference.
Mirko Bonadei61880182017-10-12 15:12:35 +0200562
563 This supports both 'BUG=' and 'Bug:' since we are in the process of migrating
564 to Gerrit and it encourages the usage of 'Bug:'.
kjellanderd1e26a92016-09-19 08:11:16 -0700565 """
Mirko Bonadei61880182017-10-12 15:12:35 +0200566 if input_api.change.BugsFromDescription():
kjellanderd1e26a92016-09-19 08:11:16 -0700567 return []
568 else:
569 return [output_api.PresubmitError(
Mirko Bonadei61880182017-10-12 15:12:35 +0200570 'The "Bug: [bug number]" footer is mandatory. Please create a bug and '
kjellanderd1e26a92016-09-19 08:11:16 -0700571 'reference it using either of:\n'
Mirko Bonadei61880182017-10-12 15:12:35 +0200572 ' * https://bugs.webrtc.org - reference it using Bug: webrtc:XXXX\n'
573 ' * https://crbug.com - reference it using Bug: chromium:XXXXXX')]
kjellander@webrtc.orge4158642014-08-06 09:11:18 +0000574
charujain9893e252017-09-14 13:33:22 +0200575def CheckJSONParseErrors(input_api, output_api):
kjellander569cf942016-02-11 05:02:59 -0800576 """Check that JSON files do not contain syntax errors."""
577
578 def FilterFile(affected_file):
579 return input_api.os_path.splitext(affected_file.LocalPath())[1] == '.json'
580
581 def GetJSONParseError(input_api, filename):
582 try:
583 contents = input_api.ReadFile(filename)
584 input_api.json.loads(contents)
585 except ValueError as e:
586 return e
587 return None
588
589 results = []
590 for affected_file in input_api.AffectedFiles(
591 file_filter=FilterFile, include_deletes=False):
592 parse_error = GetJSONParseError(input_api,
593 affected_file.AbsoluteLocalPath())
594 if parse_error:
595 results.append(output_api.PresubmitError('%s could not be parsed: %s' %
596 (affected_file.LocalPath(), parse_error)))
597 return results
598
599
charujain9893e252017-09-14 13:33:22 +0200600def RunPythonTests(input_api, output_api):
kjellanderc88b5d52017-04-05 06:42:43 -0700601 def Join(*args):
Henrik Kjellander8d3ad822015-05-26 19:52:05 +0200602 return input_api.os_path.join(input_api.PresubmitLocalPath(), *args)
603
604 test_directories = [
Edward Lemur6d01f6d2017-09-14 17:02:01 +0200605 input_api.PresubmitLocalPath(),
Mirko Bonadei92ea95e2017-09-15 06:47:31 +0200606 Join('rtc_tools', 'py_event_log_analyzer'),
607 Join('rtc_tools'),
608 Join('audio', 'test', 'unittests'),
ehmaldonado4fb97462017-01-30 05:27:22 -0800609 ] + [
Henrik Kjellander90fd7d82017-05-09 08:30:10 +0200610 root for root, _, files in os.walk(Join('tools_webrtc'))
ehmaldonado4fb97462017-01-30 05:27:22 -0800611 if any(f.endswith('_test.py') for f in files)
Henrik Kjellander8d3ad822015-05-26 19:52:05 +0200612 ]
613
614 tests = []
615 for directory in test_directories:
616 tests.extend(
617 input_api.canned_checks.GetUnitTestsInDirectory(
618 input_api,
619 output_api,
620 directory,
621 whitelist=[r'.+_test\.py$']))
622 return input_api.RunTests(tests, parallel=True)
623
624
charujain9893e252017-09-14 13:33:22 +0200625def CheckUsageOfGoogleProtobufNamespace(input_api, output_api):
mbonadei38415b22017-04-07 05:38:01 -0700626 """Checks that the namespace google::protobuf has not been used."""
627 files = []
628 pattern = input_api.re.compile(r'google::protobuf')
Mirko Bonadei92ea95e2017-09-15 06:47:31 +0200629 proto_utils_path = os.path.join('rtc_base', 'protobuf_utils.h')
mbonadei38415b22017-04-07 05:38:01 -0700630 for f in input_api.AffectedSourceFiles(input_api.FilterSourceFile):
631 if f.LocalPath() in [proto_utils_path, 'PRESUBMIT.py']:
632 continue
633 contents = input_api.ReadFile(f)
634 if pattern.search(contents):
635 files.append(f)
636
637 if files:
638 return [output_api.PresubmitError(
639 'Please avoid to use namespace `google::protobuf` directly.\n'
640 'Add a using directive in `%s` and include that header instead.'
641 % proto_utils_path, files)]
642 return []
643
644
Mirko Bonadei92ea95e2017-09-15 06:47:31 +0200645def _LicenseHeader(input_api):
646 """Returns the license header regexp."""
647 # Accept any year number from 2003 to the current year
648 current_year = int(input_api.time.strftime('%Y'))
649 allowed_years = (str(s) for s in reversed(xrange(2003, current_year + 1)))
650 years_re = '(' + '|'.join(allowed_years) + ')'
651 license_header = (
652 r'.*? Copyright( \(c\))? %(year)s The WebRTC [Pp]roject [Aa]uthors\. '
653 r'All [Rr]ights [Rr]eserved\.\n'
654 r'.*?\n'
655 r'.*? Use of this source code is governed by a BSD-style license\n'
656 r'.*? that can be found in the LICENSE file in the root of the source\n'
657 r'.*? tree\. An additional intellectual property rights grant can be '
658 r'found\n'
659 r'.*? in the file PATENTS\. All contributing project authors may\n'
660 r'.*? be found in the AUTHORS file in the root of the source tree\.\n'
661 ) % {
662 'year': years_re,
663 }
664 return license_header
665
666
charujain9893e252017-09-14 13:33:22 +0200667def CommonChecks(input_api, output_api):
andrew@webrtc.org53df1362012-01-26 21:24:23 +0000668 """Checks common to both upload and commit."""
niklase@google.comda159d62011-05-30 11:51:34 +0000669 results = []
tkchin42f580e2015-11-26 23:18:23 -0800670 # Filter out files that are in objc or ios dirs from being cpplint-ed since
671 # they do not follow C++ lint rules.
672 black_list = input_api.DEFAULT_BLACK_LIST + (
673 r".*\bobjc[\\\/].*",
Kári Tristan Helgason3fa35172016-09-09 08:55:05 +0000674 r".*objc\.[hcm]+$",
tkchin42f580e2015-11-26 23:18:23 -0800675 )
676 source_file_filter = lambda x: input_api.FilterSourceFile(x, None, black_list)
charujain9893e252017-09-14 13:33:22 +0200677 results.extend(CheckApprovedFilesLintClean(
tkchin42f580e2015-11-26 23:18:23 -0800678 input_api, output_api, source_file_filter))
Mirko Bonadei92ea95e2017-09-15 06:47:31 +0200679 results.extend(input_api.canned_checks.CheckLicense(
680 input_api, output_api, _LicenseHeader(input_api)))
phoglund@webrtc.org5d3713932013-03-07 09:59:43 +0000681 results.extend(input_api.canned_checks.RunPylint(input_api, output_api,
kjellander@webrtc.org177567c2016-12-22 10:40:28 +0100682 black_list=(r'^base[\\\/].*\.py$',
Henrik Kjellander14771ac2015-06-02 13:10:04 +0200683 r'^build[\\\/].*\.py$',
684 r'^buildtools[\\\/].*\.py$',
kjellander38c65c82017-04-12 22:43:38 -0700685 r'^infra[\\\/].*\.py$',
Henrik Kjellander0779e8f2016-12-22 12:01:17 +0100686 r'^ios[\\\/].*\.py$',
Henrik Kjellander14771ac2015-06-02 13:10:04 +0200687 r'^out.*[\\\/].*\.py$',
688 r'^testing[\\\/].*\.py$',
689 r'^third_party[\\\/].*\.py$',
kjellander@webrtc.org177567c2016-12-22 10:40:28 +0100690 r'^tools[\\\/].*\.py$',
kjellanderafd54942016-12-17 12:21:39 -0800691 # TODO(phoglund): should arguably be checked.
Henrik Kjellander90fd7d82017-05-09 08:30:10 +0200692 r'^tools_webrtc[\\\/]mb[\\\/].*\.py$',
693 r'^tools_webrtc[\\\/]valgrind[\\\/].*\.py$',
Henrik Kjellander14771ac2015-06-02 13:10:04 +0200694 r'^xcodebuild.*[\\\/].*\.py$',),
Henrik Kjellander57e5fd22015-05-25 12:55:39 +0200695 pylintrc='pylintrc'))
kjellander569cf942016-02-11 05:02:59 -0800696
nisse3d21e232016-09-02 03:07:06 -0700697 # TODO(nisse): talk/ is no more, so make below checks simpler?
Henrik Kjellander57e5fd22015-05-25 12:55:39 +0200698 # WebRTC can't use the presubmit_canned_checks.PanProjectChecks function since
699 # we need to have different license checks in talk/ and webrtc/ directories.
700 # Instead, hand-picked checks are included below.
Henrik Kjellander63224672015-09-08 08:03:56 +0200701
tkchin3cd9a302016-06-08 12:40:28 -0700702 # .m and .mm files are ObjC files. For simplicity we will consider .h files in
703 # ObjC subdirectories ObjC headers.
704 objc_filter_list = (r'.+\.m$', r'.+\.mm$', r'.+objc\/.+\.h$')
Henrik Kjellanderb4af3d62016-11-16 20:11:29 +0100705 # Skip long-lines check for DEPS and GN files.
706 build_file_filter_list = (r'.+\.gn$', r'.+\.gni$', 'DEPS')
tkchin3cd9a302016-06-08 12:40:28 -0700707 eighty_char_sources = lambda x: input_api.FilterSourceFile(x,
708 black_list=build_file_filter_list + objc_filter_list)
709 hundred_char_sources = lambda x: input_api.FilterSourceFile(x,
710 white_list=objc_filter_list)
andrew@webrtc.org2442de12012-01-23 17:45:41 +0000711 results.extend(input_api.canned_checks.CheckLongLines(
tkchin3cd9a302016-06-08 12:40:28 -0700712 input_api, output_api, maxlen=80, source_file_filter=eighty_char_sources))
713 results.extend(input_api.canned_checks.CheckLongLines(
714 input_api, output_api, maxlen=100,
715 source_file_filter=hundred_char_sources))
716
andrew@webrtc.org2442de12012-01-23 17:45:41 +0000717 results.extend(input_api.canned_checks.CheckChangeHasNoTabs(
718 input_api, output_api))
andrew@webrtc.org53df1362012-01-26 21:24:23 +0000719 results.extend(input_api.canned_checks.CheckChangeHasNoStrayWhitespace(
720 input_api, output_api))
kjellandere5dc62a2016-12-14 00:16:21 -0800721 results.extend(input_api.canned_checks.CheckAuthorizedAuthor(
722 input_api, output_api))
andrew@webrtc.org53df1362012-01-26 21:24:23 +0000723 results.extend(input_api.canned_checks.CheckChangeTodoHasOwner(
724 input_api, output_api))
charujain9893e252017-09-14 13:33:22 +0200725 results.extend(CheckNativeApiHeaderChanges(input_api, output_api))
726 results.extend(CheckNoIOStreamInHeaders(input_api, output_api))
727 results.extend(CheckNoPragmaOnce(input_api, output_api))
728 results.extend(CheckNoFRIEND_TEST(input_api, output_api))
729 results.extend(CheckGnChanges(input_api, output_api))
730 results.extend(CheckUnwantedDependencies(input_api, output_api))
731 results.extend(CheckJSONParseErrors(input_api, output_api))
732 results.extend(RunPythonTests(input_api, output_api))
733 results.extend(CheckUsageOfGoogleProtobufNamespace(input_api, output_api))
Mirko Bonadei866d3372017-09-15 12:35:26 +0200734 results.extend(CheckOrphanHeaders(input_api, output_api))
Mirko Bonadeia730c1c2017-09-18 11:33:13 +0200735 results.extend(CheckNewlineAtTheEndOfProtoFiles(input_api, output_api))
andrew@webrtc.org53df1362012-01-26 21:24:23 +0000736 return results
andrew@webrtc.org2442de12012-01-23 17:45:41 +0000737
kjellander@webrtc.orge4158642014-08-06 09:11:18 +0000738
andrew@webrtc.org53df1362012-01-26 21:24:23 +0000739def CheckChangeOnUpload(input_api, output_api):
740 results = []
charujain9893e252017-09-14 13:33:22 +0200741 results.extend(CommonChecks(input_api, output_api))
Oleh Prypin920b6532017-10-05 11:28:51 +0200742 results.extend(CheckGnGen(input_api, output_api))
Henrik Kjellander57e5fd22015-05-25 12:55:39 +0200743 results.extend(
744 input_api.canned_checks.CheckGNFormatted(input_api, output_api))
niklase@google.comda159d62011-05-30 11:51:34 +0000745 return results
746
kjellander@webrtc.orge4158642014-08-06 09:11:18 +0000747
andrew@webrtc.org2442de12012-01-23 17:45:41 +0000748def CheckChangeOnCommit(input_api, output_api):
niklase@google.com1198db92011-06-09 07:07:24 +0000749 results = []
charujain9893e252017-09-14 13:33:22 +0200750 results.extend(CommonChecks(input_api, output_api))
751 results.extend(VerifyNativeApiHeadersListIsValid(input_api, output_api))
niklase@google.com1198db92011-06-09 07:07:24 +0000752 results.extend(input_api.canned_checks.CheckOwners(input_api, output_api))
andrew@webrtc.org53df1362012-01-26 21:24:23 +0000753 results.extend(input_api.canned_checks.CheckChangeWasUploaded(
754 input_api, output_api))
755 results.extend(input_api.canned_checks.CheckChangeHasDescription(
756 input_api, output_api))
charujain9893e252017-09-14 13:33:22 +0200757 results.extend(CheckChangeHasBugField(input_api, output_api))
758 results.extend(CheckCommitMessageBugEntry(input_api, output_api))
kjellander@webrtc.org12cb88c2014-02-13 11:53:43 +0000759 results.extend(input_api.canned_checks.CheckTreeIsOpen(
760 input_api, output_api,
761 json_url='http://webrtc-status.appspot.com/current?format=json'))
niklase@google.com1198db92011-06-09 07:07:24 +0000762 return results
mbonadei74973ed2017-05-09 07:58:05 -0700763
764
charujain9893e252017-09-14 13:33:22 +0200765def CheckOrphanHeaders(input_api, output_api):
mbonadei74973ed2017-05-09 07:58:05 -0700766 # We need to wait until we have an input_api object and use this
767 # roundabout construct to import prebubmit_checks_lib because this file is
768 # eval-ed and thus doesn't have __file__.
Patrik Höglund2f3f7222017-12-19 11:08:56 +0100769 error_msg = """{} should be listed in {}."""
mbonadei74973ed2017-05-09 07:58:05 -0700770 results = []
Patrik Höglund7e60de22018-01-09 14:22:00 +0100771 orphan_blacklist = [
772 os.path.join('tools_webrtc', 'ios', 'SDK'),
773 ]
Oleh Prypin2f33a562017-10-04 20:17:54 +0200774 with _AddToPath(input_api.os_path.join(
775 input_api.PresubmitLocalPath(), 'tools_webrtc', 'presubmit_checks_lib')):
mbonadei74973ed2017-05-09 07:58:05 -0700776 from check_orphan_headers import GetBuildGnPathFromFilePath
777 from check_orphan_headers import IsHeaderInBuildGn
mbonadei74973ed2017-05-09 07:58:05 -0700778
Patrik Höglund7e60de22018-01-09 14:22:00 +0100779 source_file_filter = lambda x: input_api.FilterSourceFile(
780 x, black_list=orphan_blacklist)
781 for f in input_api.AffectedSourceFiles(source_file_filter):
782 if f.LocalPath().endswith('.h'):
mbonadei74973ed2017-05-09 07:58:05 -0700783 file_path = os.path.abspath(f.LocalPath())
784 root_dir = os.getcwd()
785 gn_file_path = GetBuildGnPathFromFilePath(file_path, os.path.exists,
786 root_dir)
787 in_build_gn = IsHeaderInBuildGn(file_path, gn_file_path)
788 if not in_build_gn:
789 results.append(output_api.PresubmitError(error_msg.format(
Patrik Höglund2f3f7222017-12-19 11:08:56 +0100790 f.LocalPath(), os.path.relpath(gn_file_path))))
mbonadei74973ed2017-05-09 07:58:05 -0700791 return results
Mirko Bonadei960fd5b2017-06-29 14:59:36 +0200792
793
Mirko Bonadeia730c1c2017-09-18 11:33:13 +0200794def CheckNewlineAtTheEndOfProtoFiles(input_api, output_api):
Mirko Bonadei960fd5b2017-06-29 14:59:36 +0200795 """Checks that all .proto files are terminated with a newline."""
796 error_msg = 'File {} must end with exactly one newline.'
797 results = []
798 source_file_filter = lambda x: input_api.FilterSourceFile(
799 x, white_list=(r'.+\.proto$',))
800 for f in input_api.AffectedSourceFiles(source_file_filter):
801 file_path = f.LocalPath()
802 with open(file_path) as f:
803 lines = f.readlines()
Mirko Bonadeia730c1c2017-09-18 11:33:13 +0200804 if len(lines) > 0 and not lines[-1].endswith('\n'):
Mirko Bonadei960fd5b2017-06-29 14:59:36 +0200805 results.append(output_api.PresubmitError(error_msg.format(file_path)))
806 return results