| OLD | NEW |
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # Copyright (c) 2011 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2011 The Chromium Authors. All rights reserved. |
| 3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
| 5 | 5 |
| 6 """Enables directory-specific presubmit checks to run at upload and/or commit. | 6 """Enables directory-specific presubmit checks to run at upload and/or commit. |
| 7 """ | 7 """ |
| 8 | 8 |
| 9 __version__ = '1.6' | 9 __version__ = '1.6.1' |
| 10 | 10 |
| 11 # TODO(joi) Add caching where appropriate/needed. The API is designed to allow | 11 # TODO(joi) Add caching where appropriate/needed. The API is designed to allow |
| 12 # caching (between all different invocations of presubmit scripts for a given | 12 # caching (between all different invocations of presubmit scripts for a given |
| 13 # change). We should add it as our presubmit scripts start feeling slow. | 13 # change). We should add it as our presubmit scripts start feeling slow. |
| 14 | 14 |
| 15 import cPickle # Exposed through the API. | 15 import cPickle # Exposed through the API. |
| 16 import cStringIO # Exposed through the API. | 16 import cStringIO # Exposed through the API. |
| 17 import fnmatch | 17 import fnmatch |
| 18 import glob | 18 import glob |
| 19 import logging | 19 import logging |
| (...skipping 188 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 208 r".*\bRelease[\\\/].*", | 208 r".*\bRelease[\\\/].*", |
| 209 r".*\bxcodebuild[\\\/].*", | 209 r".*\bxcodebuild[\\\/].*", |
| 210 r".*\bsconsbuild[\\\/].*", | 210 r".*\bsconsbuild[\\\/].*", |
| 211 # All caps files like README and LICENCE. | 211 # All caps files like README and LICENCE. |
| 212 r".*\b[A-Z0-9_]{2,}$", | 212 r".*\b[A-Z0-9_]{2,}$", |
| 213 # SCM (can happen in dual SCM configuration). (Slightly over aggressive) | 213 # SCM (can happen in dual SCM configuration). (Slightly over aggressive) |
| 214 r"(|.*[\\\/])\.git[\\\/].*", | 214 r"(|.*[\\\/])\.git[\\\/].*", |
| 215 r"(|.*[\\\/])\.svn[\\\/].*", | 215 r"(|.*[\\\/])\.svn[\\\/].*", |
| 216 ) | 216 ) |
| 217 | 217 |
| 218 # TODO(dpranke): Update callers to pass in tbr, host_url, remove | 218 def __init__(self, change, presubmit_path, is_committing, tbr, |
| 219 # default arguments. | 219 rietveld, verbose): |
| 220 def __init__(self, change, presubmit_path, is_committing, tbr, host_url, | |
| 221 verbose): | |
| 222 """Builds an InputApi object. | 220 """Builds an InputApi object. |
| 223 | 221 |
| 224 Args: | 222 Args: |
| 225 change: A presubmit.Change object. | 223 change: A presubmit.Change object. |
| 226 presubmit_path: The path to the presubmit script being processed. | 224 presubmit_path: The path to the presubmit script being processed. |
| 227 is_committing: True if the change is about to be committed. | 225 is_committing: True if the change is about to be committed. |
| 228 tbr: True if '--tbr' was passed to skip any reviewer/owner checks | 226 tbr: True if '--tbr' was passed to skip any reviewer/owner checks |
| 229 host_url: scheme, host, and path of rietveld instance | 227 rietveld: rietveld client object |
| 230 """ | 228 """ |
| 231 # Version number of the presubmit_support script. | 229 # Version number of the presubmit_support script. |
| 232 self.version = [int(x) for x in __version__.split('.')] | 230 self.version = [int(x) for x in __version__.split('.')] |
| 233 self.change = change | 231 self.change = change |
| 234 self.host_url = host_url | |
| 235 self.is_committing = is_committing | 232 self.is_committing = is_committing |
| 236 self.tbr = tbr | 233 self.tbr = tbr |
| 237 self.host_url = host_url or 'http://codereview.chromium.org' | 234 self.rietveld = rietveld |
| 235 # TBD |
| 236 self.host_url = 'http://codereview.chromium.org' |
| 237 if self.rietveld: |
| 238 self.host_url = rietveld.url |
| 238 | 239 |
| 239 # We expose various modules and functions as attributes of the input_api | 240 # We expose various modules and functions as attributes of the input_api |
| 240 # so that presubmit scripts don't have to import them. | 241 # so that presubmit scripts don't have to import them. |
| 241 self.basename = os.path.basename | 242 self.basename = os.path.basename |
| 242 self.cPickle = cPickle | 243 self.cPickle = cPickle |
| 243 self.cStringIO = cStringIO | 244 self.cStringIO = cStringIO |
| 244 self.json = json | 245 self.json = json |
| 245 self.os_listdir = os.listdir | 246 self.os_listdir = os.listdir |
| 246 self.os_walk = os.walk | 247 self.os_walk = os.walk |
| 247 self.os_path = os.path | 248 self.os_path = os.path |
| (...skipping 677 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 925 results += executer.ExecPresubmitScript(presubmit_script, filename) | 926 results += executer.ExecPresubmitScript(presubmit_script, filename) |
| 926 | 927 |
| 927 slaves = list(set(results)) | 928 slaves = list(set(results)) |
| 928 if slaves and verbose: | 929 if slaves and verbose: |
| 929 output_stream.write(', '.join(slaves)) | 930 output_stream.write(', '.join(slaves)) |
| 930 output_stream.write('\n') | 931 output_stream.write('\n') |
| 931 return slaves | 932 return slaves |
| 932 | 933 |
| 933 | 934 |
| 934 class PresubmitExecuter(object): | 935 class PresubmitExecuter(object): |
| 935 def __init__(self, change, committing, tbr, host_url, verbose): | 936 def __init__(self, change, committing, tbr, rietveld, verbose): |
| 936 """ | 937 """ |
| 937 Args: | 938 Args: |
| 938 change: The Change object. | 939 change: The Change object. |
| 939 committing: True if 'gcl commit' is running, False if 'gcl upload' is. | 940 committing: True if 'gcl commit' is running, False if 'gcl upload' is. |
| 940 tbr: True if '--tbr' was passed to skip any reviewer/owner checks | 941 tbr: True if '--tbr' was passed to skip any reviewer/owner checks |
| 941 host_url: scheme, host, and path of rietveld instance | 942 rietveld: rietveld client object. |
| 942 (or None for default) | |
| 943 """ | 943 """ |
| 944 self.change = change | 944 self.change = change |
| 945 self.committing = committing | 945 self.committing = committing |
| 946 self.tbr = tbr | 946 self.tbr = tbr |
| 947 self.host_url = host_url | 947 self.rietveld = rietveld |
| 948 self.verbose = verbose | 948 self.verbose = verbose |
| 949 | 949 |
| 950 def ExecPresubmitScript(self, script_text, presubmit_path): | 950 def ExecPresubmitScript(self, script_text, presubmit_path): |
| 951 """Executes a single presubmit script. | 951 """Executes a single presubmit script. |
| 952 | 952 |
| 953 Args: | 953 Args: |
| 954 script_text: The text of the presubmit script. | 954 script_text: The text of the presubmit script. |
| 955 presubmit_path: The path to the presubmit file (this will be reported via | 955 presubmit_path: The path to the presubmit file (this will be reported via |
| 956 input_api.PresubmitLocalPath()). | 956 input_api.PresubmitLocalPath()). |
| 957 | 957 |
| 958 Return: | 958 Return: |
| 959 A list of result objects, empty if no problems. | 959 A list of result objects, empty if no problems. |
| 960 """ | 960 """ |
| 961 | 961 |
| 962 # Change to the presubmit file's directory to support local imports. | 962 # Change to the presubmit file's directory to support local imports. |
| 963 main_path = os.getcwd() | 963 main_path = os.getcwd() |
| 964 os.chdir(os.path.dirname(presubmit_path)) | 964 os.chdir(os.path.dirname(presubmit_path)) |
| 965 | 965 |
| 966 # Load the presubmit script into context. | 966 # Load the presubmit script into context. |
| 967 input_api = InputApi(self.change, presubmit_path, self.committing, | 967 input_api = InputApi(self.change, presubmit_path, self.committing, |
| 968 self.tbr, self.host_url, self.verbose) | 968 self.tbr, self.rietveld, self.verbose) |
| 969 context = {} | 969 context = {} |
| 970 try: | 970 try: |
| 971 exec script_text in context | 971 exec script_text in context |
| 972 except Exception, e: | 972 except Exception, e: |
| 973 raise PresubmitFailure('"%s" had an exception.\n%s' % (presubmit_path, e)) | 973 raise PresubmitFailure('"%s" had an exception.\n%s' % (presubmit_path, e)) |
| 974 | 974 |
| 975 # These function names must change if we make substantial changes to | 975 # These function names must change if we make substantial changes to |
| 976 # the presubmit API that are not backwards compatible. | 976 # the presubmit API that are not backwards compatible. |
| 977 if self.committing: | 977 if self.committing: |
| 978 function_name = 'CheckChangeOnCommit' | 978 function_name = 'CheckChangeOnCommit' |
| (...skipping 14 matching lines...) Expand all Loading... |
| 993 'All presubmit results must be of types derived from ' | 993 'All presubmit results must be of types derived from ' |
| 994 'output_api.PresubmitResult') | 994 'output_api.PresubmitResult') |
| 995 else: | 995 else: |
| 996 result = () # no error since the script doesn't care about current event. | 996 result = () # no error since the script doesn't care about current event. |
| 997 | 997 |
| 998 # Return the process to the original working directory. | 998 # Return the process to the original working directory. |
| 999 os.chdir(main_path) | 999 os.chdir(main_path) |
| 1000 return result | 1000 return result |
| 1001 | 1001 |
| 1002 | 1002 |
| 1003 # TODO(dpranke): make all callers pass in tbr, host_url? | |
| 1004 def DoPresubmitChecks(change, | 1003 def DoPresubmitChecks(change, |
| 1005 committing, | 1004 committing, |
| 1006 verbose, | 1005 verbose, |
| 1007 output_stream, | 1006 output_stream, |
| 1008 input_stream, | 1007 input_stream, |
| 1009 default_presubmit, | 1008 default_presubmit, |
| 1010 may_prompt, | 1009 may_prompt, |
| 1011 tbr=False, | 1010 tbr, |
| 1012 host_url=None): | 1011 rietveld): |
| 1013 """Runs all presubmit checks that apply to the files in the change. | 1012 """Runs all presubmit checks that apply to the files in the change. |
| 1014 | 1013 |
| 1015 This finds all PRESUBMIT.py files in directories enclosing the files in the | 1014 This finds all PRESUBMIT.py files in directories enclosing the files in the |
| 1016 change (up to the repository root) and calls the relevant entrypoint function | 1015 change (up to the repository root) and calls the relevant entrypoint function |
| 1017 depending on whether the change is being committed or uploaded. | 1016 depending on whether the change is being committed or uploaded. |
| 1018 | 1017 |
| 1019 Prints errors, warnings and notifications. Prompts the user for warnings | 1018 Prints errors, warnings and notifications. Prompts the user for warnings |
| 1020 when needed. | 1019 when needed. |
| 1021 | 1020 |
| 1022 Args: | 1021 Args: |
| 1023 change: The Change object. | 1022 change: The Change object. |
| 1024 committing: True if 'gcl commit' is running, False if 'gcl upload' is. | 1023 committing: True if 'gcl commit' is running, False if 'gcl upload' is. |
| 1025 verbose: Prints debug info. | 1024 verbose: Prints debug info. |
| 1026 output_stream: A stream to write output from presubmit tests to. | 1025 output_stream: A stream to write output from presubmit tests to. |
| 1027 input_stream: A stream to read input from the user. | 1026 input_stream: A stream to read input from the user. |
| 1028 default_presubmit: A default presubmit script to execute in any case. | 1027 default_presubmit: A default presubmit script to execute in any case. |
| 1029 may_prompt: Enable (y/n) questions on warning or error. | 1028 may_prompt: Enable (y/n) questions on warning or error. |
| 1030 tbr: was --tbr specified to skip any reviewer/owner checks? | 1029 tbr: was --tbr specified to skip any reviewer/owner checks? |
| 1031 host_url: scheme, host, and port of host to use for rietveld-related | 1030 rietveld: rietveld object. |
| 1032 checks | |
| 1033 | 1031 |
| 1034 Warning: | 1032 Warning: |
| 1035 If may_prompt is true, output_stream SHOULD be sys.stdout and input_stream | 1033 If may_prompt is true, output_stream SHOULD be sys.stdout and input_stream |
| 1036 SHOULD be sys.stdin. | 1034 SHOULD be sys.stdin. |
| 1037 | 1035 |
| 1038 Return: | 1036 Return: |
| 1039 A PresubmitOutput object. Use output.should_continue() to figure out | 1037 A PresubmitOutput object. Use output.should_continue() to figure out |
| 1040 if there were errors or warnings and the caller should abort. | 1038 if there were errors or warnings and the caller should abort. |
| 1041 """ | 1039 """ |
| 1042 output = PresubmitOutput(input_stream, output_stream) | 1040 output = PresubmitOutput(input_stream, output_stream) |
| 1043 if committing: | 1041 if committing: |
| 1044 output.write("Running presubmit commit checks ...\n") | 1042 output.write("Running presubmit commit checks ...\n") |
| 1045 else: | 1043 else: |
| 1046 output.write("Running presubmit upload checks ...\n") | 1044 output.write("Running presubmit upload checks ...\n") |
| 1047 start_time = time.time() | 1045 start_time = time.time() |
| 1048 presubmit_files = ListRelevantPresubmitFiles(change.AbsoluteLocalPaths(True), | 1046 presubmit_files = ListRelevantPresubmitFiles(change.AbsoluteLocalPaths(True), |
| 1049 change.RepositoryRoot()) | 1047 change.RepositoryRoot()) |
| 1050 if not presubmit_files and verbose: | 1048 if not presubmit_files and verbose: |
| 1051 output.write("Warning, no presubmit.py found.\n") | 1049 output.write("Warning, no presubmit.py found.\n") |
| 1052 results = [] | 1050 results = [] |
| 1053 executer = PresubmitExecuter(change, committing, tbr, host_url, verbose) | 1051 executer = PresubmitExecuter(change, committing, tbr, rietveld, verbose) |
| 1054 if default_presubmit: | 1052 if default_presubmit: |
| 1055 if verbose: | 1053 if verbose: |
| 1056 output.write("Running default presubmit script.\n") | 1054 output.write("Running default presubmit script.\n") |
| 1057 fake_path = os.path.join(change.RepositoryRoot(), 'PRESUBMIT.py') | 1055 fake_path = os.path.join(change.RepositoryRoot(), 'PRESUBMIT.py') |
| 1058 results += executer.ExecPresubmitScript(default_presubmit, fake_path) | 1056 results += executer.ExecPresubmitScript(default_presubmit, fake_path) |
| 1059 for filename in presubmit_files: | 1057 for filename in presubmit_files: |
| 1060 filename = os.path.abspath(filename) | 1058 filename = os.path.abspath(filename) |
| 1061 if verbose: | 1059 if verbose: |
| 1062 output.write("Running %s\n" % filename) | 1060 output.write("Running %s\n" % filename) |
| 1063 # Accept CRLF presubmit script. | 1061 # Accept CRLF presubmit script. |
| (...skipping 129 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1193 options.description, | 1191 options.description, |
| 1194 options.root, | 1192 options.root, |
| 1195 files, | 1193 files, |
| 1196 options.issue, | 1194 options.issue, |
| 1197 options.patchset), | 1195 options.patchset), |
| 1198 options.commit, | 1196 options.commit, |
| 1199 options.verbose, | 1197 options.verbose, |
| 1200 sys.stdout, | 1198 sys.stdout, |
| 1201 sys.stdin, | 1199 sys.stdin, |
| 1202 options.default_presubmit, | 1200 options.default_presubmit, |
| 1203 options.may_prompt) | 1201 options.may_prompt, |
| 1202 False, |
| 1203 None) |
| 1204 return not results.should_continue() | 1204 return not results.should_continue() |
| 1205 except PresubmitFailure, e: | 1205 except PresubmitFailure, e: |
| 1206 print >> sys.stderr, e | 1206 print >> sys.stderr, e |
| 1207 print >> sys.stderr, 'Maybe your depot_tools is out of date?' | 1207 print >> sys.stderr, 'Maybe your depot_tools is out of date?' |
| 1208 print >> sys.stderr, 'If all fails, contact maruel@' | 1208 print >> sys.stderr, 'If all fails, contact maruel@' |
| 1209 return 2 | 1209 return 2 |
| 1210 | 1210 |
| 1211 | 1211 |
| 1212 if __name__ == '__main__': | 1212 if __name__ == '__main__': |
| 1213 fix_encoding.fix_encoding() | 1213 fix_encoding.fix_encoding() |
| 1214 sys.exit(Main(None)) | 1214 sys.exit(Main(None)) |
| OLD | NEW |