| Index: presubmit_support.py
|
| diff --git a/presubmit_support.py b/presubmit_support.py
|
| index 4df39c02b485391ba4ff0bd0b69b2e3395120e22..0929bd1c83e8a2a5fe2dd18728fdd81b8efa352a 100755
|
| --- a/presubmit_support.py
|
| +++ b/presubmit_support.py
|
| @@ -6,7 +6,7 @@
|
| """Enables directory-specific presubmit checks to run at upload and/or commit.
|
| """
|
|
|
| -__version__ = '1.5'
|
| +__version__ = '1.6'
|
|
|
| # TODO(joi) Add caching where appropriate/needed. The API is designed to allow
|
| # caching (between all different invocations of presubmit scripts for a given
|
| @@ -14,7 +14,6 @@ __version__ = '1.5'
|
|
|
| import cPickle # Exposed through the API.
|
| import cStringIO # Exposed through the API.
|
| -import exceptions
|
| import fnmatch
|
| import glob
|
| import logging
|
| @@ -56,11 +55,7 @@ import subprocess2 as subprocess # Exposed through the API.
|
| _ASKED_FOR_FEEDBACK = False
|
|
|
|
|
| -class NotImplementedException(Exception):
|
| - """We're leaving placeholders in a bunch of places to remind us of the
|
| - design of the API, but we have not implemented all of it yet. Implement as
|
| - the need arises.
|
| - """
|
| +class PresubmitFailure(Exception):
|
| pass
|
|
|
|
|
| @@ -178,7 +173,7 @@ class OutputApi(object):
|
| """A warning that should be included in the review request email."""
|
| def __init__(self, *args, **kwargs):
|
| super(OutputApi.MailTextResult, self).__init__()
|
| - raise NotImplementedException()
|
| + raise NotImplementedError()
|
|
|
|
|
| class InputApi(object):
|
| @@ -222,7 +217,8 @@ class InputApi(object):
|
|
|
| # TODO(dpranke): Update callers to pass in tbr, host_url, remove
|
| # default arguments.
|
| - def __init__(self, change, presubmit_path, is_committing, tbr, host_url=None):
|
| + def __init__(self, change, presubmit_path, is_committing, tbr, host_url,
|
| + verbose):
|
| """Builds an InputApi object.
|
|
|
| Args:
|
| @@ -276,6 +272,7 @@ class InputApi(object):
|
| # in order to be able to handle wildcard OWNERS files?
|
| self.owners_db = owners.Database(change.RepositoryRoot(),
|
| fopen=file, os_path=self.os_path)
|
| + self.verbose = verbose
|
|
|
| def PresubmitLocalPath(self):
|
| """Returns the local path of the presubmit script currently being run.
|
| @@ -595,7 +592,7 @@ class GitAffectedFile(AffectedFile):
|
|
|
| def ServerPath(self):
|
| if self._server_path is None:
|
| - raise NotImplementedException() # TODO(maruel) Implement.
|
| + raise NotImplementedError('TODO(maruel) Implement.')
|
| return self._server_path
|
|
|
| def IsDirectory(self):
|
| @@ -606,13 +603,12 @@ class GitAffectedFile(AffectedFile):
|
| # querying subversion, especially on Windows.
|
| self._is_directory = os.path.isdir(path)
|
| else:
|
| - # raise NotImplementedException() # TODO(maruel) Implement.
|
| self._is_directory = False
|
| return self._is_directory
|
|
|
| def Property(self, property_name):
|
| if not property_name in self._properties:
|
| - raise NotImplementedException() # TODO(maruel) Implement.
|
| + raise NotImplementedError('TODO(maruel) Implement.')
|
| return self._properties[property_name]
|
|
|
| def IsTextFile(self):
|
| @@ -623,7 +619,6 @@ class GitAffectedFile(AffectedFile):
|
| elif self.IsDirectory():
|
| self._is_text_file = False
|
| else:
|
| - # raise NotImplementedException() # TODO(maruel) Implement.
|
| self._is_text_file = os.path.isfile(self.AbsoluteLocalPath())
|
| return self._is_text_file
|
|
|
| @@ -861,7 +856,7 @@ def ListRelevantPresubmitFiles(files, root):
|
|
|
| class GetTrySlavesExecuter(object):
|
| @staticmethod
|
| - def ExecPresubmitScript(script_text):
|
| + def ExecPresubmitScript(script_text, presubmit_path):
|
| """Executes GetPreferredTrySlaves() from a single presubmit script.
|
|
|
| Args:
|
| @@ -871,21 +866,24 @@ class GetTrySlavesExecuter(object):
|
| A list of try slaves.
|
| """
|
| context = {}
|
| - exec script_text in context
|
| + try:
|
| + exec script_text in context
|
| + except Exception, e:
|
| + raise PresubmitFailure('"%s" had an exception.\n%s' % (presubmit_path, e))
|
|
|
| function_name = 'GetPreferredTrySlaves'
|
| if function_name in context:
|
| result = eval(function_name + '()', context)
|
| if not isinstance(result, types.ListType):
|
| - raise exceptions.RuntimeError(
|
| + raise PresubmitFailure(
|
| 'Presubmit functions must return a list, got a %s instead: %s' %
|
| (type(result), str(result)))
|
| for item in result:
|
| if not isinstance(item, basestring):
|
| - raise exceptions.RuntimeError('All try slaves names must be strings.')
|
| + raise PresubmitFailure('All try slaves names must be strings.')
|
| if item != item.strip():
|
| - raise exceptions.RuntimeError('Try slave names cannot start/end'
|
| - 'with whitespace')
|
| + raise PresubmitFailure(
|
| + 'Try slave names cannot start/end with whitespace')
|
| else:
|
| result = []
|
| return result
|
| @@ -916,14 +914,15 @@ def DoGetTrySlaves(changed_files,
|
| if default_presubmit:
|
| if verbose:
|
| output_stream.write("Running default presubmit script.\n")
|
| - results += executer.ExecPresubmitScript(default_presubmit)
|
| + fake_path = os.path.join(repository_root, 'PRESUBMIT.py')
|
| + results += executer.ExecPresubmitScript(default_presubmit, fake_path)
|
| for filename in presubmit_files:
|
| filename = os.path.abspath(filename)
|
| if verbose:
|
| output_stream.write("Running %s\n" % filename)
|
| # Accept CRLF presubmit script.
|
| presubmit_script = gclient_utils.FileRead(filename, 'rU')
|
| - results += executer.ExecPresubmitScript(presubmit_script)
|
| + results += executer.ExecPresubmitScript(presubmit_script, filename)
|
|
|
| slaves = list(set(results))
|
| if slaves and verbose:
|
| @@ -933,7 +932,7 @@ def DoGetTrySlaves(changed_files,
|
|
|
|
|
| class PresubmitExecuter(object):
|
| - def __init__(self, change, committing, tbr, host_url):
|
| + def __init__(self, change, committing, tbr, host_url, verbose):
|
| """
|
| Args:
|
| change: The Change object.
|
| @@ -946,6 +945,7 @@ class PresubmitExecuter(object):
|
| self.committing = committing
|
| self.tbr = tbr
|
| self.host_url = host_url
|
| + self.verbose = verbose
|
|
|
| def ExecPresubmitScript(self, script_text, presubmit_path):
|
| """Executes a single presubmit script.
|
| @@ -965,9 +965,12 @@ class PresubmitExecuter(object):
|
|
|
| # Load the presubmit script into context.
|
| input_api = InputApi(self.change, presubmit_path, self.committing,
|
| - self.tbr, self.host_url)
|
| + self.tbr, self.host_url, self.verbose)
|
| context = {}
|
| - exec script_text in context
|
| + try:
|
| + exec script_text in context
|
| + except Exception, e:
|
| + raise PresubmitFailure('"%s" had an exception.\n%s' % (presubmit_path, e))
|
|
|
| # These function names must change if we make substantial changes to
|
| # the presubmit API that are not backwards compatible.
|
| @@ -982,11 +985,11 @@ class PresubmitExecuter(object):
|
| logging.debug('Running %s done.' % function_name)
|
| if not (isinstance(result, types.TupleType) or
|
| isinstance(result, types.ListType)):
|
| - raise exceptions.RuntimeError(
|
| + raise PresubmitFailure(
|
| 'Presubmit functions must return a tuple or list')
|
| for item in result:
|
| if not isinstance(item, OutputApi.PresubmitResult):
|
| - raise exceptions.RuntimeError(
|
| + raise PresubmitFailure(
|
| 'All presubmit results must be of types derived from '
|
| 'output_api.PresubmitResult')
|
| else:
|
| @@ -1047,7 +1050,7 @@ def DoPresubmitChecks(change,
|
| if not presubmit_files and verbose:
|
| output.write("Warning, no presubmit.py found.\n")
|
| results = []
|
| - executer = PresubmitExecuter(change, committing, tbr, host_url)
|
| + executer = PresubmitExecuter(change, committing, tbr, host_url, verbose)
|
| if default_presubmit:
|
| if verbose:
|
| output.write("Running default presubmit script.\n")
|
| @@ -1160,8 +1163,8 @@ def Main(argv):
|
| help="Use upload instead of commit checks")
|
| parser.add_option("-r", "--recursive", action="store_true",
|
| help="Act recursively")
|
| - parser.add_option("-v", "--verbose", action="store_true", default=False,
|
| - help="Verbose output")
|
| + parser.add_option("-v", "--verbose", action="count", default=0,
|
| + help="Use 2 times for more debug info")
|
| parser.add_option("--name", default='no name')
|
| parser.add_option("--description", default='')
|
| parser.add_option("--issue", type='int', default=0)
|
| @@ -1174,26 +1177,36 @@ def Main(argv):
|
| parser.add_option("--default_presubmit")
|
| parser.add_option("--may_prompt", action='store_true', default=False)
|
| options, args = parser.parse_args(argv)
|
| - if options.verbose:
|
| + if options.verbose >= 2:
|
| logging.basicConfig(level=logging.DEBUG)
|
| + elif options.verbose:
|
| + logging.basicConfig(level=logging.INFO)
|
| + else:
|
| + logging.basicConfig(level=logging.ERROR)
|
| change_class, files = load_files(options, args)
|
| if not change_class:
|
| parser.error('For unversioned directory, <files> is not optional.')
|
| - if options.verbose:
|
| - print "Found %d file(s)." % len(files)
|
| - results = DoPresubmitChecks(change_class(options.name,
|
| - options.description,
|
| - options.root,
|
| - files,
|
| - options.issue,
|
| - options.patchset),
|
| - options.commit,
|
| - options.verbose,
|
| - sys.stdout,
|
| - sys.stdin,
|
| - options.default_presubmit,
|
| - options.may_prompt)
|
| - return not results.should_continue()
|
| + logging.info('Found %d file(s).' % len(files))
|
| + try:
|
| + results = DoPresubmitChecks(
|
| + change_class(options.name,
|
| + options.description,
|
| + options.root,
|
| + files,
|
| + options.issue,
|
| + options.patchset),
|
| + options.commit,
|
| + options.verbose,
|
| + sys.stdout,
|
| + sys.stdin,
|
| + options.default_presubmit,
|
| + options.may_prompt)
|
| + return not results.should_continue()
|
| + except PresubmitFailure, e:
|
| + print >> sys.stderr, e
|
| + print >> sys.stderr, 'Maybe your depot_tools is out of date?'
|
| + print >> sys.stderr, 'If all fails, contact maruel@'
|
| + return 2
|
|
|
|
|
| if __name__ == '__main__':
|
|
|