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

Side by Side Diff: presubmit_support.py

Issue 4360002: Largely reduce the number of pylint warnings and fix one typo. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/depot_tools
Patch Set: address comments Created 10 years, 1 month 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 | Annotate | Revision Log
« no previous file with comments | « presubmit_canned_checks.py ('k') | pylintrc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 #!/usr/bin/python 1 #!/usr/bin/python
2 # Copyright (c) 2006-2009 The Chromium Authors. All rights reserved. 2 # Copyright (c) 2010 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.3.5' 9 __version__ = '1.3.5'
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
(...skipping 12 matching lines...) Expand all
25 import random 25 import random
26 import re # Exposed through the API. 26 import re # Exposed through the API.
27 import subprocess # Exposed through the API. 27 import subprocess # Exposed through the API.
28 import sys # Parts exposed through API. 28 import sys # Parts exposed through API.
29 import tempfile # Exposed through the API. 29 import tempfile # Exposed through the API.
30 import time 30 import time
31 import traceback # Exposed through the API. 31 import traceback # Exposed through the API.
32 import types 32 import types
33 import unittest # Exposed through the API. 33 import unittest # Exposed through the API.
34 import urllib2 # Exposed through the API. 34 import urllib2 # Exposed through the API.
35 import warnings 35 from warnings import warn
36 36
37 try: 37 try:
38 import simplejson as json 38 import simplejson as json
39 except ImportError: 39 except ImportError:
40 try: 40 try:
41 import json 41 import json
42 # Some versions of python2.5 have an incomplete json module. Check to make 42 # Some versions of python2.5 have an incomplete json module. Check to make
43 # sure loads exists. 43 # sure loads exists.
44 json.loads 44 json.loads
45 except (ImportError, AttributeError): 45 except (ImportError, AttributeError):
46 # Import the one included in depot_tools. 46 # Import the one included in depot_tools.
47 sys.path.append(os.path.join(os.path.dirname(__file__), 'third_party')) 47 sys.path.append(os.path.join(os.path.dirname(__file__), 'third_party'))
48 import simplejson as json 48 import simplejson as json
49 49
50 # Local imports. 50 # Local imports.
51 import gcl
52 import gclient_utils 51 import gclient_utils
53 import presubmit_canned_checks 52 import presubmit_canned_checks
54 import scm 53 import scm
55 54
56 55
57 # Ask for feedback only once in program lifetime. 56 # Ask for feedback only once in program lifetime.
58 _ASKED_FOR_FEEDBACK = False 57 _ASKED_FOR_FEEDBACK = False
59 58
60 59
61 class NotImplementedException(Exception): 60 class NotImplementedException(Exception):
62 """We're leaving placeholders in a bunch of places to remind us of the 61 """We're leaving placeholders in a bunch of places to remind us of the
63 design of the API, but we have not implemented all of it yet. Implement as 62 design of the API, but we have not implemented all of it yet. Implement as
64 the need arises. 63 the need arises.
65 """ 64 """
66 pass 65 pass
67 66
68 67
69 def normpath(path): 68 def normpath(path):
70 '''Version of os.path.normpath that also changes backward slashes to 69 '''Version of os.path.normpath that also changes backward slashes to
71 forward slashes when not running on Windows. 70 forward slashes when not running on Windows.
72 ''' 71 '''
73 # This is safe to always do because the Windows version of os.path.normpath 72 # This is safe to always do because the Windows version of os.path.normpath
74 # will replace forward slashes with backward slashes. 73 # will replace forward slashes with backward slashes.
75 path = path.replace(os.sep, '/') 74 path = path.replace(os.sep, '/')
76 return os.path.normpath(path) 75 return os.path.normpath(path)
77 76
77
78 def PromptYesNo(input_stream, output_stream, prompt): 78 def PromptYesNo(input_stream, output_stream, prompt):
79 output_stream.write(prompt) 79 output_stream.write(prompt)
80 response = input_stream.readline().strip().lower() 80 response = input_stream.readline().strip().lower()
81 return response == 'y' or response == 'yes' 81 return response == 'y' or response == 'yes'
82 82
83
84 def _RightHandSideLinesImpl(affected_files):
85 """Implements RightHandSideLines for InputApi and GclChange."""
86 for af in affected_files:
87 lines = af.NewContents()
88 line_number = 0
89 for line in lines:
90 line_number += 1
91 yield (af, line_number, line)
92
93
83 class OutputApi(object): 94 class OutputApi(object):
84 """This class (more like a module) gets passed to presubmit scripts so that 95 """This class (more like a module) gets passed to presubmit scripts so that
85 they can specify various types of results. 96 they can specify various types of results.
86 """ 97 """
87 98
88 class PresubmitResult(object): 99 class PresubmitResult(object):
89 """Base class for result objects.""" 100 """Base class for result objects."""
90 101
91 def __init__(self, message, items=None, long_text=''): 102 def __init__(self, message, items=None, long_text=''):
92 """ 103 """
(...skipping 58 matching lines...) Expand 10 before | Expand all | Expand 10 after
151 def ShouldPrompt(self): 162 def ShouldPrompt(self):
152 return True 163 return True
153 164
154 class PresubmitNotifyResult(PresubmitResult): 165 class PresubmitNotifyResult(PresubmitResult):
155 """Just print something to the screen -- but it's not even a warning.""" 166 """Just print something to the screen -- but it's not even a warning."""
156 pass 167 pass
157 168
158 class MailTextResult(PresubmitResult): 169 class MailTextResult(PresubmitResult):
159 """A warning that should be included in the review request email.""" 170 """A warning that should be included in the review request email."""
160 def __init__(self, *args, **kwargs): 171 def __init__(self, *args, **kwargs):
161 raise NotImplementedException() # TODO(joi) Implement. 172 super(OutputApi.MailTextResult, self).__init__()
173 raise NotImplementedException()
162 174
163 175
164 class InputApi(object): 176 class InputApi(object):
165 """An instance of this object is passed to presubmit scripts so they can 177 """An instance of this object is passed to presubmit scripts so they can
166 know stuff about the change they're looking at. 178 know stuff about the change they're looking at.
167 """ 179 """
168 180
169 # File extensions that are considered source files from a style guide 181 # File extensions that are considered source files from a style guide
170 # perspective. Don't modify this list from a presubmit script! 182 # perspective. Don't modify this list from a presubmit script!
171 DEFAULT_WHITE_LIST = ( 183 DEFAULT_WHITE_LIST = (
(...skipping 130 matching lines...) Expand 10 before | Expand all | Expand 10 after
302 def ServerPaths(self, include_dirs=False): 314 def ServerPaths(self, include_dirs=False):
303 """Returns server paths of input_api.AffectedFiles().""" 315 """Returns server paths of input_api.AffectedFiles()."""
304 return [af.ServerPath() for af in self.AffectedFiles(include_dirs)] 316 return [af.ServerPath() for af in self.AffectedFiles(include_dirs)]
305 317
306 def AffectedTextFiles(self, include_deletes=None): 318 def AffectedTextFiles(self, include_deletes=None):
307 """Same as input_api.change.AffectedTextFiles() except only lists files 319 """Same as input_api.change.AffectedTextFiles() except only lists files
308 in the same directory as the current presubmit script, or subdirectories 320 in the same directory as the current presubmit script, or subdirectories
309 thereof. 321 thereof.
310 """ 322 """
311 if include_deletes is not None: 323 if include_deletes is not None:
312 warnings.warn("AffectedTextFiles(include_deletes=%s)" 324 warn("AffectedTextFiles(include_deletes=%s)"
313 " is deprecated and ignored" % str(include_deletes), 325 " is deprecated and ignored" % str(include_deletes),
314 category=DeprecationWarning, 326 category=DeprecationWarning,
315 stacklevel=2) 327 stacklevel=2)
316 return filter(lambda x: x.IsTextFile(), 328 return filter(lambda x: x.IsTextFile(),
317 self.AffectedFiles(include_dirs=False, include_deletes=False)) 329 self.AffectedFiles(include_dirs=False, include_deletes=False))
318 330
319 def FilterSourceFile(self, affected_file, white_list=None, black_list=None): 331 def FilterSourceFile(self, affected_file, white_list=None, black_list=None):
320 """Filters out files that aren't considered "source file". 332 """Filters out files that aren't considered "source file".
321 333
322 If white_list or black_list is None, InputApi.DEFAULT_WHITE_LIST 334 If white_list or black_list is None, InputApi.DEFAULT_WHITE_LIST
323 and InputApi.DEFAULT_BLACK_LIST is used respectively. 335 and InputApi.DEFAULT_BLACK_LIST is used respectively.
324 336
325 The lists will be compiled as regular expression and 337 The lists will be compiled as regular expression and
326 AffectedFile.LocalPath() needs to pass both list. 338 AffectedFile.LocalPath() needs to pass both list.
327 339
328 Note: Copy-paste this function to suit your needs or use a lambda function. 340 Note: Copy-paste this function to suit your needs or use a lambda function.
329 """ 341 """
330 def Find(affected_file, list): 342 def Find(affected_file, items):
331 for item in list: 343 for item in items:
332 local_path = affected_file.LocalPath() 344 local_path = affected_file.LocalPath()
333 if self.re.match(item, local_path): 345 if self.re.match(item, local_path):
334 logging.debug("%s matched %s" % (item, local_path)) 346 logging.debug("%s matched %s" % (item, local_path))
335 return True 347 return True
336 return False 348 return False
337 return (Find(affected_file, white_list or self.DEFAULT_WHITE_LIST) and 349 return (Find(affected_file, white_list or self.DEFAULT_WHITE_LIST) and
338 not Find(affected_file, black_list or self.DEFAULT_BLACK_LIST)) 350 not Find(affected_file, black_list or self.DEFAULT_BLACK_LIST))
339 351
340 def AffectedSourceFiles(self, source_file): 352 def AffectedSourceFiles(self, source_file):
341 """Filter the list of AffectedTextFiles by the function source_file. 353 """Filter the list of AffectedTextFiles by the function source_file.
(...skipping 15 matching lines...) Expand all
357 369
358 Yields: 370 Yields:
359 a 3 tuple: 371 a 3 tuple:
360 the AffectedFile instance of the current file; 372 the AffectedFile instance of the current file;
361 integer line number (1-based); and 373 integer line number (1-based); and
362 the contents of the line as a string. 374 the contents of the line as a string.
363 375
364 Note: The cariage return (LF or CR) is stripped off. 376 Note: The cariage return (LF or CR) is stripped off.
365 """ 377 """
366 files = self.AffectedSourceFiles(source_file_filter) 378 files = self.AffectedSourceFiles(source_file_filter)
367 return InputApi._RightHandSideLinesImpl(files) 379 return _RightHandSideLinesImpl(files)
368 380
369 def ReadFile(self, file_item, mode='r'): 381 def ReadFile(self, file_item, mode='r'):
370 """Reads an arbitrary file. 382 """Reads an arbitrary file.
371 383
372 Deny reading anything outside the repository. 384 Deny reading anything outside the repository.
373 """ 385 """
374 if isinstance(file_item, AffectedFile): 386 if isinstance(file_item, AffectedFile):
375 file_item = file_item.AbsoluteLocalPath() 387 file_item = file_item.AbsoluteLocalPath()
376 if not file_item.startswith(self.change.RepositoryRoot()): 388 if not file_item.startswith(self.change.RepositoryRoot()):
377 raise IOError('Access outside the repository root is denied.') 389 raise IOError('Access outside the repository root is denied.')
378 return gclient_utils.FileRead(file_item, mode) 390 return gclient_utils.FileRead(file_item, mode)
379 391
380 @staticmethod
381 def _RightHandSideLinesImpl(affected_files):
382 """Implements RightHandSideLines for InputApi and GclChange."""
383 for af in affected_files:
384 lines = af.NewContents()
385 line_number = 0
386 for line in lines:
387 line_number += 1
388 yield (af, line_number, line)
389
390 392
391 class AffectedFile(object): 393 class AffectedFile(object):
392 """Representation of a file in a change.""" 394 """Representation of a file in a change."""
393 395
394 def __init__(self, path, action, repository_root=''): 396 def __init__(self, path, action, repository_root=''):
395 self._path = path 397 self._path = path
396 self._action = action 398 self._action = action
397 self._local_root = repository_root 399 self._local_root = repository_root
398 self._is_directory = None 400 self._is_directory = None
399 self._properties = {} 401 self._properties = {}
(...skipping 258 matching lines...) Expand 10 before | Expand all | Expand 10 after
658 affected = filter(lambda x: not x.IsDirectory(), self._affected_files) 660 affected = filter(lambda x: not x.IsDirectory(), self._affected_files)
659 661
660 if include_deletes: 662 if include_deletes:
661 return affected 663 return affected
662 else: 664 else:
663 return filter(lambda x: x.Action() != 'D', affected) 665 return filter(lambda x: x.Action() != 'D', affected)
664 666
665 def AffectedTextFiles(self, include_deletes=None): 667 def AffectedTextFiles(self, include_deletes=None):
666 """Return a list of the existing text files in a change.""" 668 """Return a list of the existing text files in a change."""
667 if include_deletes is not None: 669 if include_deletes is not None:
668 warnings.warn("AffectedTextFiles(include_deletes=%s)" 670 warn("AffectedTextFiles(include_deletes=%s)"
669 " is deprecated and ignored" % str(include_deletes), 671 " is deprecated and ignored" % str(include_deletes),
670 category=DeprecationWarning, 672 category=DeprecationWarning,
671 stacklevel=2) 673 stacklevel=2)
672 return filter(lambda x: x.IsTextFile(), 674 return filter(lambda x: x.IsTextFile(),
673 self.AffectedFiles(include_dirs=False, include_deletes=False)) 675 self.AffectedFiles(include_dirs=False, include_deletes=False))
674 676
675 def LocalPaths(self, include_dirs=False): 677 def LocalPaths(self, include_dirs=False):
676 """Convenience function.""" 678 """Convenience function."""
677 return [af.LocalPath() for af in self.AffectedFiles(include_dirs)] 679 return [af.LocalPath() for af in self.AffectedFiles(include_dirs)]
678 680
679 def AbsoluteLocalPaths(self, include_dirs=False): 681 def AbsoluteLocalPaths(self, include_dirs=False):
680 """Convenience function.""" 682 """Convenience function."""
681 return [af.AbsoluteLocalPath() for af in self.AffectedFiles(include_dirs)] 683 return [af.AbsoluteLocalPath() for af in self.AffectedFiles(include_dirs)]
682 684
683 def ServerPaths(self, include_dirs=False): 685 def ServerPaths(self, include_dirs=False):
684 """Convenience function.""" 686 """Convenience function."""
685 return [af.ServerPath() for af in self.AffectedFiles(include_dirs)] 687 return [af.ServerPath() for af in self.AffectedFiles(include_dirs)]
686 688
687 def RightHandSideLines(self): 689 def RightHandSideLines(self):
688 """An iterator over all text lines in "new" version of changed files. 690 """An iterator over all text lines in "new" version of changed files.
689 691
690 Lists lines from new or modified text files in the change. 692 Lists lines from new or modified text files in the change.
691 693
692 This is useful for doing line-by-line regex checks, like checking for 694 This is useful for doing line-by-line regex checks, like checking for
693 trailing whitespace. 695 trailing whitespace.
694 696
695 Yields: 697 Yields:
696 a 3 tuple: 698 a 3 tuple:
697 the AffectedFile instance of the current file; 699 the AffectedFile instance of the current file;
698 integer line number (1-based); and 700 integer line number (1-based); and
699 the contents of the line as a string. 701 the contents of the line as a string.
700 """ 702 """
701 return InputApi._RightHandSideLinesImpl( 703 return _RightHandSideLinesImpl(
702 filter(lambda x: x.IsTextFile(), 704 x for x in self.AffectedFiles(include_deletes=False)
703 self.AffectedFiles(include_deletes=False))) 705 if x.IsTextFile())
704 706
705 707
706 class SvnChange(Change): 708 class SvnChange(Change):
707 _AFFECTED_FILES = SvnAffectedFile 709 _AFFECTED_FILES = SvnAffectedFile
708 710
709 def __init__(self, *args, **kwargs): 711 def __init__(self, *args, **kwargs):
710 Change.__init__(self, *args, **kwargs) 712 Change.__init__(self, *args, **kwargs)
711 self.scm = 'svn' 713 self.scm = 'svn'
712 self._changelists = None 714 self._changelists = None
713 715
714 def _GetChangeLists(self): 716 def _GetChangeLists(self):
715 """Get all change lists.""" 717 """Get all change lists."""
716 if self._changelists == None: 718 if self._changelists == None:
717 previous_cwd = os.getcwd() 719 previous_cwd = os.getcwd()
718 os.chdir(self.RepositoryRoot()) 720 os.chdir(self.RepositoryRoot())
721 # Need to import here to avoid circular dependency.
722 import gcl
719 self._changelists = gcl.GetModifiedFiles() 723 self._changelists = gcl.GetModifiedFiles()
720 os.chdir(previous_cwd) 724 os.chdir(previous_cwd)
721 return self._changelists 725 return self._changelists
722 726
723 def GetAllModifiedFiles(self): 727 def GetAllModifiedFiles(self):
724 """Get all modified files.""" 728 """Get all modified files."""
725 changelists = self._GetChangeLists() 729 changelists = self._GetChangeLists()
726 all_modified_files = [] 730 all_modified_files = []
727 for cl in changelists.values(): 731 for cl in changelists.values():
728 all_modified_files.extend( 732 all_modified_files.extend(
(...skipping 56 matching lines...) Expand 10 before | Expand all | Expand 10 after
785 results = [] 789 results = []
786 for directory in sorted(list(candidates)): 790 for directory in sorted(list(candidates)):
787 p = os.path.join(directory, 'PRESUBMIT.py') 791 p = os.path.join(directory, 'PRESUBMIT.py')
788 if os.path.isfile(p): 792 if os.path.isfile(p):
789 results.append(p) 793 results.append(p)
790 794
791 return results 795 return results
792 796
793 797
794 class GetTrySlavesExecuter(object): 798 class GetTrySlavesExecuter(object):
795 def ExecPresubmitScript(self, script_text): 799 @staticmethod
800 def ExecPresubmitScript(script_text):
796 """Executes GetPreferredTrySlaves() from a single presubmit script. 801 """Executes GetPreferredTrySlaves() from a single presubmit script.
797 802
798 Args: 803 Args:
799 script_text: The text of the presubmit script. 804 script_text: The text of the presubmit script.
800 805
801 Return: 806 Return:
802 A list of try slaves. 807 A list of try slaves.
803 """ 808 """
804 context = {} 809 context = {}
805 exec script_text in context 810 exec script_text in context
(...skipping 289 matching lines...) Expand 10 before | Expand all | Expand 10 after
1095 options.commit, 1100 options.commit,
1096 options.verbose, 1101 options.verbose,
1097 sys.stdout, 1102 sys.stdout,
1098 sys.stdin, 1103 sys.stdin,
1099 options.default_presubmit, 1104 options.default_presubmit,
1100 options.may_prompt) 1105 options.may_prompt)
1101 1106
1102 1107
1103 if __name__ == '__main__': 1108 if __name__ == '__main__':
1104 sys.exit(Main(sys.argv)) 1109 sys.exit(Main(sys.argv))
OLDNEW
« no previous file with comments | « presubmit_canned_checks.py ('k') | pylintrc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698