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

Unified 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 side-by-side diff with in-line comments
Download patch
« no previous file with comments | « DEPS ('k') | pipa/build/gitdeps.py » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: PRESUBMIT.py
diff --git a/PRESUBMIT.py b/PRESUBMIT.py
new file mode 100644
index 0000000000000000000000000000000000000000..4a3002afbd1df3e7d431b1f8ddc688d2f9e03d85
--- /dev/null
+++ b/PRESUBMIT.py
@@ -0,0 +1,270 @@
+#!python
+# Copyright 2012 Google Inc. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Presubmit script for Pipa, highly inspired by Syzygy's presubmit checks.
+
+import itertools
+import json
+import os
+import re
+import subprocess
+import sys
+
+
+# Determine the root of the source tree. We use getcwd() instead of __file__
+# because gcl loads this script as text and runs it using eval(). In this
+# context the variable __file__ is undefined. However, gcl assures us that
+# the current working directory will be the directory containing this file.
+PIPA_ROOT_DIR = os.path.join(os.path.abspath(os.getcwd()), 'pipa')
+
+
+# Bring in some presubmit tools.
+sys.path.insert(0, os.path.join(PIPA_ROOT_DIR, 'py'))
+import deps_utils
+from test_utils import presubmit
+
+_UNITTEST_MESSAGE = """\
+Your %%s unittests must succeed before submitting! To clear this error,
+ run: %s""" % os.path.join(PIPA_ROOT_DIR, 'run_all_tests.bat')
+
+
+# License header and copyright line.
+_LICENSE_HEADER = """\
+(#!python\n\
+)?.*? Copyright 20[0-9][0-9] (Google Inc|The Chromium Authors)\. \
+All Rights Reserved\.\n\
+.*?\n\
+"""
+
+
+# Regular expressions to recognize source and header files.
+# These are lifted from presubmit_support.py in depot_tools and are
+# formulated as a list of regex strings so that they can be passed to
+# input_api.FilterSourceFile() as the white_list parameter.
+_CC_SOURCES = (r'.+\.c$', r'.+\.cc$', r'.+\.cpp$', r'.+\.rc$')
+_CC_HEADERS = (r'.+\.h$', r'.+\.inl$', r'.+\.hxx$', r'.+\.hpp$')
+_CC_FILES = _CC_SOURCES + _CC_HEADERS
+_CC_SOURCES_RE = re.compile('|'.join('(?:%s)' % x for x in _CC_SOURCES))
+
+# When no files to blacklist.
+_CC_FILES_BLACKLIST = []
+
+# Regular expressions used to extract headers and recognize empty lines.
+_INCLUDE_RE = re.compile(r'^\s*#\s*include\s+(?P<header>[<"][^<"]+[>"])'
+ r'\s*(?://.*(?P<nolint>NOLINT).*)?$')
+_COMMENT_OR_BLANK_RE = re.compile(r'^\s*(?://.)?$')
+
+
+def _IsSourceHeaderPair(source_path, header):
+ # Returns true if source and header are a matched pair.
+ # Source is the path on disk to the source file and header is the include
+ # reference to the header (i.e., "blah/foo.h" or <blah/foo.h> including the
+ # outer quotes or brackets.
+ if not _CC_SOURCES_RE.match(source_path):
+ return False
+
+ source_root = os.path.splitext(source_path)[0]
+ if source_root.endswith('_unittest'):
+ source_root = source_root[0:-9]
+ include = os.path.normpath(source_root + '.h')
+ header = os.path.normpath(header[1:-1])
+
+ return include.endswith(header)
+
+
+def _GetHeaderCompareKey(source_path, header):
+ if _IsSourceHeaderPair(source_path, header):
+ # We put the header that corresponds to this source file first.
+ group = 0
+ elif header.startswith('<'):
+ # C++ system headers should come after C system headers.
+ group = 1 if header.endswith('.h>') else 2
+ else:
+ group = 3
+ dirname, basename = os.path.split(header[1:-1])
+ return (group, dirname.lower(), basename.lower())
+
+
+def _GetHeaderCompareKeyFunc(source):
+ return lambda header : _GetHeaderCompareKey(source, header)
+
+
+def _HeaderGroups(source_lines):
+ # Generates lists of headers in source, one per block of headers.
+ # Each generated value is a tuple (line, headers) denoting on which
+ # line of the source file an uninterrupted sequences of includes begins,
+ # and the list of included headers (paths including the quotes or angle
+ # brackets).
+ start_line, headers = None, []
+ for line, num in itertools.izip(source_lines, itertools.count(1)):
+ match = _INCLUDE_RE.match(line)
+ if match:
+ # The win32 api has all sorts of implicit include order dependencies.
+ # Rather than encode exceptions for these, we require that they be
+ # excluded from the ordering by a // NOLINT comment.
+ if not match.group('nolint'):
+ headers.append(match.group('header'))
+ if start_line is None:
+ # We just started a new run of headers.
+ start_line = num
+ elif headers and not _COMMENT_OR_BLANK_RE.match(line):
+ # Any non-empty or non-comment line interrupts a sequence of includes.
+ assert start_line is not None
+ yield (start_line, headers)
+ start_line = None
+ headers = []
+
+ # Just in case we have some headers we haven't yielded yet, this is our
+ # last chance to do so.
+ if headers:
+ assert start_line is not None
+ yield (start_line, headers)
+
+
+# Stolen from depot_tool's my_activity.py
+def _DateTimeFromRietveld(date_string):
+ try:
+ return datetime.strptime(date_string, '%Y-%m-%d %H:%M:%S.%f')
+ except ValueError:
+ # Sometimes rietveld returns a value without the milliseconds part, so we
+ # attempt to parse those cases as well.
+ return datetime.strptime(date_string, '%Y-%m-%d %H:%M:%S')
+
+
+def _ReadFile(input_api, path):
+ file_path = input_api.change.RepositoryRoot() + '/' + path
+ return input_api.ReadFile(file_path)
+
+
+def CheckIncludeOrder(input_api, output_api):
+ """Checks that the C/C++ include order is correct."""
+ errors = []
+ is_cc_file = lambda x: input_api.FilterSourceFile(x, white_list=_CC_FILES)
+ for f in input_api.AffectedFiles(include_deletes=False,
+ file_filter=is_cc_file):
+ for line_num, group in _HeaderGroups(f.NewContents()):
+ sorted_group = sorted(group, key=_GetHeaderCompareKeyFunc(f.LocalPath()))
+ if group != sorted_group:
+ message = '%s, line %s: Out of order includes. ' \
+ 'Expected:\n\t#include %s' % (
+ f.LocalPath(),
+ line_num,
+ '\n\t#include '.join(sorted_group))
+ errors.append(output_api.PresubmitPromptWarning(message))
+ return errors
+
+
+def CheckUnittestsRan(input_api, output_api, committing, configuration):
+ """Checks that the unittests success file is newer than any modified file"""
+ return presubmit.CheckTestSuccess(input_api, output_api, committing,
+ configuration, 'ALL',
+ message=_UNITTEST_MESSAGE % configuration)
+
+
+def CheckAllDepsInGitignore(input_api, output_api):
+ gitignore = str(_ReadFile(input_api, '.gitignore'))
+ ignored_folders = filter(lambda line: line.startswith('/'),
+ gitignore.split('\n'))
+ # Remove trailing whitespaces and forward slashes and the beginning and at
+ # the end of .gitignore lines.
+ ignored = set([folder.strip().strip('/') for folder in ignored_folders])
+
+ messages = []
+ def AddErrorIfNotIgnored(path):
+ # Check if |path| or its parent directories are ignored.
+ path_segments = path.strip('/').split('/')
+ for length in range(1, len(path_segments) + 1):
+ if '/'.join(path_segments[:length]) in ignored:
+ return
+ messages.append(output_api.PresubmitError(
+ 'External dependency path %s is not in .gitignore file' % path))
+
+ if 'DEPS' in input_api.LocalPaths():
+ deps_content = _ReadFile(input_api, 'DEPS')
+ deps = deps_utils.ParseDeps(deps_content, 'DEPS')
+ for root_dir in deps:
+ if root_dir.startswith('src/'):
+ root_dir = root_dir[3:]
+ AddErrorIfNotIgnored(root_dir)
+
+ if 'GITDEPS' in input_api.LocalPaths():
+ deps_content = _ReadFile(input_api, 'GITDEPS')
+ deps = deps_utils.ParseDeps(deps_content, 'GITDEPS')
+ for root_dir, (_, subdirs, _) in deps.iteritems():
+ if subdirs:
+ for subdir in subdirs:
+ AddErrorIfNotIgnored(root_dir + '/' + subdir)
+ else:
+ AddErrorIfNotIgnored(root_dir)
+
+ return messages
+
+
+def CheckChange(input_api, output_api, committing):
+ if 'dcommit' in sys.argv:
+ return [output_api.PresubmitPromptWarning(
+ 'You must use "git cl land" and not "git cl dcommit".')]
+
+ # The list of (canned) checks we perform on all files in all changes.
+ checks = [
+ CheckAllDepsInGitignore,
+ CheckIncludeOrder,
+ input_api.canned_checks.CheckChangeHasDescription,
+ input_api.canned_checks.CheckChangeHasNoCrAndHasOnlyOneEol,
+ input_api.canned_checks.CheckChangeHasNoTabs,
+ input_api.canned_checks.CheckChangeHasNoStrayWhitespace,
+ input_api.canned_checks.CheckDoNotSubmit,
+ input_api.canned_checks.CheckGenderNeutral,
+ input_api.canned_checks.CheckPatchFormatted,
+ ]
+ if committing:
+ checks.append(input_api.canned_checks.CheckDoNotSubmit)
+
+ results = []
+ for check in checks:
+ results += check(input_api, output_api)
+
+ results += input_api.canned_checks.CheckLongLines(input_api, output_api, 80)
+
+ # We run lint only on C/C++ files so that we avoid getting notices about
+ # files being ignored.
+ is_cc_file = lambda x: input_api.FilterSourceFile(x, white_list=_CC_FILES,
+ black_list=_CC_FILES_BLACKLIST)
+ results += input_api.canned_checks.CheckChangeLintsClean(
+ input_api, output_api, source_file_filter=is_cc_file)
+
+ # We check the license on the default recognized source file types, as well
+ # as GN files.
+ gn_file_re = r'.+\.gn?$'
+ gni_file_re = r'.+\.gni?$'
+ white_list = input_api.DEFAULT_WHITE_LIST + (gn_file_re, gni_file_re)
+ sources = lambda x: input_api.FilterSourceFile(x, white_list=white_list)
+ results += input_api.canned_checks.CheckLicense(input_api,
+ output_api,
+ _LICENSE_HEADER,
+ source_file_filter=sources)
+
+ # results += CheckUnittestsRan(input_api, output_api, committing, "Debug")
+ # results += CheckUnittestsRan(input_api, output_api, committing, "Release")
+
+ return results
+
+
+def CheckChangeOnUpload(input_api, output_api):
+ return CheckChange(input_api, output_api, False)
+
+
+def CheckChangeOnCommit(input_api, output_api):
+ return CheckChange(input_api, output_api, True)
« 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