Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(382)

Side by Side Diff: PRESUBMIT.py

Issue 2707493002: Presubmit checks for the Pipa repository (Closed)
Patch Set: Created 3 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « DEPS ('k') | pipa/build/gitdeps.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 #!python
2 # Copyright 2012 Google Inc. All Rights Reserved.
3 #
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at
7 #
8 # http://www.apache.org/licenses/LICENSE-2.0
9 #
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15 #
16 # Presubmit script for Pipa, highly inspired by Syzygy's presubmit checks.
17
18 import itertools
19 import json
20 import os
21 import re
22 import subprocess
23 import sys
24
25
26 # Determine the root of the source tree. We use getcwd() instead of __file__
27 # because gcl loads this script as text and runs it using eval(). In this
28 # context the variable __file__ is undefined. However, gcl assures us that
29 # the current working directory will be the directory containing this file.
30 PIPA_ROOT_DIR = os.path.join(os.path.abspath(os.getcwd()), 'pipa')
31
32
33 # Bring in some presubmit tools.
34 sys.path.insert(0, os.path.join(PIPA_ROOT_DIR, 'py'))
35 import deps_utils
36 from test_utils import presubmit
37
38 _UNITTEST_MESSAGE = """\
39 Your %%s unittests must succeed before submitting! To clear this error,
40 run: %s""" % os.path.join(PIPA_ROOT_DIR, 'run_all_tests.bat')
41
42
43 # License header and copyright line.
44 _LICENSE_HEADER = """\
45 (#!python\n\
46 )?.*? Copyright 20[0-9][0-9] (Google Inc|The Chromium Authors)\. \
47 All Rights Reserved\.\n\
48 .*?\n\
49 """
50
51
52 # Regular expressions to recognize source and header files.
53 # These are lifted from presubmit_support.py in depot_tools and are
54 # formulated as a list of regex strings so that they can be passed to
55 # input_api.FilterSourceFile() as the white_list parameter.
56 _CC_SOURCES = (r'.+\.c$', r'.+\.cc$', r'.+\.cpp$', r'.+\.rc$')
57 _CC_HEADERS = (r'.+\.h$', r'.+\.inl$', r'.+\.hxx$', r'.+\.hpp$')
58 _CC_FILES = _CC_SOURCES + _CC_HEADERS
59 _CC_SOURCES_RE = re.compile('|'.join('(?:%s)' % x for x in _CC_SOURCES))
60
61 # When no files to blacklist.
62 _CC_FILES_BLACKLIST = []
63
64 # Regular expressions used to extract headers and recognize empty lines.
65 _INCLUDE_RE = re.compile(r'^\s*#\s*include\s+(?P<header>[<"][^<"]+[>"])'
66 r'\s*(?://.*(?P<nolint>NOLINT).*)?$')
67 _COMMENT_OR_BLANK_RE = re.compile(r'^\s*(?://.)?$')
68
69
70 def _IsSourceHeaderPair(source_path, header):
71 # Returns true if source and header are a matched pair.
72 # Source is the path on disk to the source file and header is the include
73 # reference to the header (i.e., "blah/foo.h" or <blah/foo.h> including the
74 # outer quotes or brackets.
75 if not _CC_SOURCES_RE.match(source_path):
76 return False
77
78 source_root = os.path.splitext(source_path)[0]
79 if source_root.endswith('_unittest'):
80 source_root = source_root[0:-9]
81 include = os.path.normpath(source_root + '.h')
82 header = os.path.normpath(header[1:-1])
83
84 return include.endswith(header)
85
86
87 def _GetHeaderCompareKey(source_path, header):
88 if _IsSourceHeaderPair(source_path, header):
89 # We put the header that corresponds to this source file first.
90 group = 0
91 elif header.startswith('<'):
92 # C++ system headers should come after C system headers.
93 group = 1 if header.endswith('.h>') else 2
94 else:
95 group = 3
96 dirname, basename = os.path.split(header[1:-1])
97 return (group, dirname.lower(), basename.lower())
98
99
100 def _GetHeaderCompareKeyFunc(source):
101 return lambda header : _GetHeaderCompareKey(source, header)
102
103
104 def _HeaderGroups(source_lines):
105 # Generates lists of headers in source, one per block of headers.
106 # Each generated value is a tuple (line, headers) denoting on which
107 # line of the source file an uninterrupted sequences of includes begins,
108 # and the list of included headers (paths including the quotes or angle
109 # brackets).
110 start_line, headers = None, []
111 for line, num in itertools.izip(source_lines, itertools.count(1)):
112 match = _INCLUDE_RE.match(line)
113 if match:
114 # The win32 api has all sorts of implicit include order dependencies.
115 # Rather than encode exceptions for these, we require that they be
116 # excluded from the ordering by a // NOLINT comment.
117 if not match.group('nolint'):
118 headers.append(match.group('header'))
119 if start_line is None:
120 # We just started a new run of headers.
121 start_line = num
122 elif headers and not _COMMENT_OR_BLANK_RE.match(line):
123 # Any non-empty or non-comment line interrupts a sequence of includes.
124 assert start_line is not None
125 yield (start_line, headers)
126 start_line = None
127 headers = []
128
129 # Just in case we have some headers we haven't yielded yet, this is our
130 # last chance to do so.
131 if headers:
132 assert start_line is not None
133 yield (start_line, headers)
134
135
136 # Stolen from depot_tool's my_activity.py
137 def _DateTimeFromRietveld(date_string):
138 try:
139 return datetime.strptime(date_string, '%Y-%m-%d %H:%M:%S.%f')
140 except ValueError:
141 # Sometimes rietveld returns a value without the milliseconds part, so we
142 # attempt to parse those cases as well.
143 return datetime.strptime(date_string, '%Y-%m-%d %H:%M:%S')
144
145
146 def _ReadFile(input_api, path):
147 file_path = input_api.change.RepositoryRoot() + '/' + path
148 return input_api.ReadFile(file_path)
149
150
151 def CheckIncludeOrder(input_api, output_api):
152 """Checks that the C/C++ include order is correct."""
153 errors = []
154 is_cc_file = lambda x: input_api.FilterSourceFile(x, white_list=_CC_FILES)
155 for f in input_api.AffectedFiles(include_deletes=False,
156 file_filter=is_cc_file):
157 for line_num, group in _HeaderGroups(f.NewContents()):
158 sorted_group = sorted(group, key=_GetHeaderCompareKeyFunc(f.LocalPath()))
159 if group != sorted_group:
160 message = '%s, line %s: Out of order includes. ' \
161 'Expected:\n\t#include %s' % (
162 f.LocalPath(),
163 line_num,
164 '\n\t#include '.join(sorted_group))
165 errors.append(output_api.PresubmitPromptWarning(message))
166 return errors
167
168
169 def CheckUnittestsRan(input_api, output_api, committing, configuration):
170 """Checks that the unittests success file is newer than any modified file"""
171 return presubmit.CheckTestSuccess(input_api, output_api, committing,
172 configuration, 'ALL',
173 message=_UNITTEST_MESSAGE % configuration)
174
175
176 def CheckAllDepsInGitignore(input_api, output_api):
177 gitignore = str(_ReadFile(input_api, '.gitignore'))
178 ignored_folders = filter(lambda line: line.startswith('/'),
179 gitignore.split('\n'))
180 # Remove trailing whitespaces and forward slashes and the beginning and at
181 # the end of .gitignore lines.
182 ignored = set([folder.strip().strip('/') for folder in ignored_folders])
183
184 messages = []
185 def AddErrorIfNotIgnored(path):
186 # Check if |path| or its parent directories are ignored.
187 path_segments = path.strip('/').split('/')
188 for length in range(1, len(path_segments) + 1):
189 if '/'.join(path_segments[:length]) in ignored:
190 return
191 messages.append(output_api.PresubmitError(
192 'External dependency path %s is not in .gitignore file' % path))
193
194 if 'DEPS' in input_api.LocalPaths():
195 deps_content = _ReadFile(input_api, 'DEPS')
196 deps = deps_utils.ParseDeps(deps_content, 'DEPS')
197 for root_dir in deps:
198 if root_dir.startswith('src/'):
199 root_dir = root_dir[3:]
200 AddErrorIfNotIgnored(root_dir)
201
202 if 'GITDEPS' in input_api.LocalPaths():
203 deps_content = _ReadFile(input_api, 'GITDEPS')
204 deps = deps_utils.ParseDeps(deps_content, 'GITDEPS')
205 for root_dir, (_, subdirs, _) in deps.iteritems():
206 if subdirs:
207 for subdir in subdirs:
208 AddErrorIfNotIgnored(root_dir + '/' + subdir)
209 else:
210 AddErrorIfNotIgnored(root_dir)
211
212 return messages
213
214
215 def CheckChange(input_api, output_api, committing):
216 if 'dcommit' in sys.argv:
217 return [output_api.PresubmitPromptWarning(
218 'You must use "git cl land" and not "git cl dcommit".')]
219
220 # The list of (canned) checks we perform on all files in all changes.
221 checks = [
222 CheckAllDepsInGitignore,
223 CheckIncludeOrder,
224 input_api.canned_checks.CheckChangeHasDescription,
225 input_api.canned_checks.CheckChangeHasNoCrAndHasOnlyOneEol,
226 input_api.canned_checks.CheckChangeHasNoTabs,
227 input_api.canned_checks.CheckChangeHasNoStrayWhitespace,
228 input_api.canned_checks.CheckDoNotSubmit,
229 input_api.canned_checks.CheckGenderNeutral,
230 input_api.canned_checks.CheckPatchFormatted,
231 ]
232 if committing:
233 checks.append(input_api.canned_checks.CheckDoNotSubmit)
234
235 results = []
236 for check in checks:
237 results += check(input_api, output_api)
238
239 results += input_api.canned_checks.CheckLongLines(input_api, output_api, 80)
240
241 # We run lint only on C/C++ files so that we avoid getting notices about
242 # files being ignored.
243 is_cc_file = lambda x: input_api.FilterSourceFile(x, white_list=_CC_FILES,
244 black_list=_CC_FILES_BLACKLIST)
245 results += input_api.canned_checks.CheckChangeLintsClean(
246 input_api, output_api, source_file_filter=is_cc_file)
247
248 # We check the license on the default recognized source file types, as well
249 # as GN files.
250 gn_file_re = r'.+\.gn?$'
251 gni_file_re = r'.+\.gni?$'
252 white_list = input_api.DEFAULT_WHITE_LIST + (gn_file_re, gni_file_re)
253 sources = lambda x: input_api.FilterSourceFile(x, white_list=white_list)
254 results += input_api.canned_checks.CheckLicense(input_api,
255 output_api,
256 _LICENSE_HEADER,
257 source_file_filter=sources)
258
259 # results += CheckUnittestsRan(input_api, output_api, committing, "Debug")
260 # results += CheckUnittestsRan(input_api, output_api, committing, "Release")
261
262 return results
263
264
265 def CheckChangeOnUpload(input_api, output_api):
266 return CheckChange(input_api, output_api, False)
267
268
269 def CheckChangeOnCommit(input_api, output_api):
270 return CheckChange(input_api, output_api, True)
OLDNEW
« no previous file with comments | « DEPS ('k') | pipa/build/gitdeps.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698