# Copyright (c) 2010 The Chromium Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. import re import subprocess import os try: from internal import internal_presubmit except ImportError: internal_presubmit = None SOURCE_FILE_EXTENSIONS = [ '.c', '.cc', '.cpp', '.h', '.m', '.mm', '.py', '.mk', '.am', '.json', '.gyp', '.gypi' ] EXCLUDED_PATHS = [] # Finds what seem to be definitions of DllRegisterServer. DLL_REGISTER_SERVER_RE = re.compile('\s*STDAPI\s+DllRegisterServer\s*\(') # Matches a Tracker story URL story_url_re = re.compile('https?://tracker.+/[0-9]+') # Matches filenames in which we allow tabs tabs_ok_re = re.compile('.*\.(vcproj|vsprops|sln)$') # Matches filenames of source files source_files_re = re.compile('.*\.(cc|h|py|js)$') # The top-level source directory. _SRC_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) def CheckChange(input_api, output_api, committing, is_chrome_frame=False): results = [] do_not_submit_errors = input_api.canned_checks.CheckDoNotSubmit(input_api, output_api) if committing: results += do_not_submit_errors elif do_not_submit_errors: results += [output_api.PresubmitNotifyResult( 'There is a DO-NOT-SUBMIT issue')] results += CheckChangeHasNoTabs(input_api, output_api) results += CheckLongLines(input_api, output_api) results += CheckHasStoryOrBug(input_api, output_api) results += LocalChecks(input_api, output_api) results += WarnOnAtlSmartPointers(input_api, output_api) if not is_chrome_frame: results += CheckNoDllRegisterServer(input_api, output_api) results += CheckUnittestsRan(input_api, output_api, committing) if internal_presubmit: results += internal_presubmit.InternalChecks(input_api, output_api, committing) return results def CheckHasStoryOrBug(input_api, output_api): """We require either BUG= to be present and non-empty. For a completely trivial change, use BUG=none. """ if not ('BUG' in input_api.change.tags): return [output_api.PresubmitError('A BUG= tag is required. For trivial ' 'changes you can use BUG=none.')] if ('BUG' in input_api.change.tags and len(input_api.change.tags['BUG']) == 0): return [output_api.PresubmitError('A non-empty BUG= is required. For ' 'trivial changes you can use BUG=none.')] return [] def CheckChangeHasNoTabs(input_api, output_api): """Slightly modified version of the canned check with the same name. This version ignores certain file types in which we allow tabs. """ for f, line_num, line in input_api.RightHandSideLines(): if tabs_ok_re.match(f.LocalPath()): continue if '\t' in line: return [output_api.PresubmitError( "Found a tab character in %s, line %s" % (f.LocalPath(), line_num))] return [] def CheckLongLines(input_api, output_api, maxlen=80): """Checks that there aren't any lines longer than maxlen characters in any of the text files to be submitted. """ basename = input_api.basename bad = [] for f, line_num, line in input_api.RightHandSideLines(): if not source_files_re.match(f.LocalPath()): continue if line.find('http://') != -1: # Exemption for long URLs continue if line.endswith('\n'): line = line[:-1] if len(line) > maxlen: bad.append( '%s, line %s, %s chars' % (basename(f.LocalPath()), line_num, len(line))) if len(bad) == 5: # Just show the first 5 errors. break if bad: msg = "Found lines longer than %s characters (first 5 shown)." % maxlen return [output_api.PresubmitPromptWarning(msg, items=bad)] else: return [] _UNITTEST_MESSAGE = '''\ You must build and run the CEEE smoke tests before submitting. To clear this error, run the script "smoke_test.bat" in the CEEE directory. ''' def CheckUnittestsRan(input_api, output_api, committing): '''Checks that the unittests success file is newer than any modified file''' # But only if there were IE files modified, since we only have unit tests # for CEEE IE. files = [] ie_paths_re = re.compile('ceee[\\\\/](ie|common)[\\\\/]') for f in input_api.AffectedFiles(include_deletes = False): path = f.LocalPath() if (ie_paths_re.match(path)): files.append(f) if not files: return [] def MakeResult(message, modified_files=[]): if committing: return output_api.PresubmitError(message, modified_files) else: return output_api.PresubmitNotifyResult(message, modified_files) os_path = input_api.os_path success_files = [ os_path.join(input_api.PresubmitLocalPath(), '../chrome/Debug/ceee.success'), os_path.join(input_api.PresubmitLocalPath(), '../chrome/Release/ceee.success')] if (not os_path.exists(success_files[0]) or not os_path.exists(success_files[1])): return [MakeResult(_UNITTEST_MESSAGE)] success_time = min(os.stat(success_files[0]).st_mtime, os.stat(success_files[1]).st_mtime) modified_files = [] for f in modified_files: file_time = os.stat(f.AbsoluteLocalPath()).st_mtime if file_time > success_time: modified_files.append(f.LocalPath()) result = [] if modified_files: result.append(MakeResult('These files have been modified since Debug and/or' ' Release unittests were built.', modified_files)) return result def CheckNoDllRegisterServer(input_api, output_api): for f, line_num, line in input_api.RightHandSideLines(): if DLL_REGISTER_SERVER_RE.search(line): file_name = os.path.basename(f.LocalPath()) if file_name not in ['install_utils.h', 'install_utils_unittest.cc']: return [output_api.PresubmitError( '%s contains a definition of DllRegisterServer at line %s.\n' 'Please search for CEEE_DEFINE_DLL_REGISTER_SERVER.' % (f.LocalPath(), line_num))] return [] def WarnOnAtlSmartPointers(input_api, output_api): smart_pointer_re = re.compile(r'\bCCom(Ptr|BSTR|Variant)\b') bad_files = [] for f in input_api.AffectedFiles(include_deletes=False): contents = input_api.ReadFile(f, 'r') if smart_pointer_re.search(contents): bad_files.append(f.LocalPath()) if bad_files: return [output_api.PresubmitPromptWarning( 'The following files use CComPtr, CComBSTR and/or CComVariant.\n' 'Please consider switching to ScopedComPtr, ScopedBstr and/or\n' 'ScopedVariant as per team policy. (NOTE: Will soon be an error.)', items=bad_files)] else: return [] def LocalChecks(input_api, output_api, max_cols=80): """Reports an error if for any source file in SOURCE_FILE_EXTENSIONS: - uses CR (or CRLF) - contains a TAB - has a line that ends with whitespace - contains a line >|max_cols| cols unless |max_cols| is 0. - File does not end in a newline, or ends in more than one. Note that the whole file is checked, not only the changes. """ C_SOURCE_FILE_EXTENSIONS = ('.c', '.cc', '.cpp', '.h', '.inl') cr_files = [] eof_files = [] results = [] excluded_paths = [input_api.re.compile(x) for x in EXCLUDED_PATHS] files = input_api.AffectedFiles(include_deletes=False) for f in files: path = f.LocalPath() root, ext = input_api.os_path.splitext(path) # Look for unsupported extensions. if not ext in SOURCE_FILE_EXTENSIONS: continue # Look for excluded paths. found = False for item in excluded_paths: if item.match(path): found = True break if found: continue # Need to read the file ourselves since AffectedFile.NewContents() # will normalize line endings. contents = input_api.ReadFile(f, 'rb') if '\r' in contents: cr_files.append(path) # Check that the file ends in one and only one newline character. if len(contents) > 0 and (contents[-1:] != "\n" or contents[-2:-1] == "\n"): eof_files.append(path) local_errors = [] # Remove end of line character. lines = contents.splitlines() line_num = 1 for line in lines: if line.endswith(' '): local_errors.append(output_api.PresubmitError( '%s, line %s ends with whitespaces.' % (path, line_num))) # Accept lines with http://, https:// and C #define/#pragma/#include to # exceed the max_cols rule. if (max_cols and len(line) > max_cols and not 'http://' in line and not 'https://' in line and not (line[0] == '#' and ext in C_SOURCE_FILE_EXTENSIONS)): local_errors.append(output_api.PresubmitError( '%s, line %s has %s chars, please reduce to %d chars.' % (path, line_num, len(line), max_cols))) if '\t' in line: local_errors.append(output_api.PresubmitError( "%s, line %s contains a tab character." % (path, line_num))) line_num += 1 # Just show the first 5 errors. if len(local_errors) == 6: local_errors.pop() local_errors.append(output_api.PresubmitError("... and more.")) break results.extend(local_errors) if cr_files: results.append(output_api.PresubmitError( 'Found CR (or CRLF) line ending in these files, please use only LF:', items=cr_files)) if eof_files: results.append(output_api.PresubmitError( 'These files should end in one (and only one) newline character:', items=eof_files)) return results