It is valid to have empty modified_lines list for files that only contain deletions. In such case we should skip all the errors except for forced rules. Updated the test to match the expectation. Change-Id: I6993968b882fb6fbe2ba1f63f3b6879c3308ff34
168 lines
5.6 KiB
Python
Executable File
168 lines
5.6 KiB
Python
Executable File
#!/usr/bin/python
|
|
|
|
#
|
|
# Copyright 2015, The Android Open Source Project
|
|
#
|
|
# 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.
|
|
#
|
|
|
|
"""Script that is used by developers to run style checks on Java files."""
|
|
|
|
import argparse
|
|
import errno
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
import xml.dom.minidom
|
|
import gitlint.git as git
|
|
|
|
|
|
MAIN_DIRECTORY = os.path.normpath(os.path.dirname(__file__))
|
|
CHECKSTYLE_JAR = os.path.join(MAIN_DIRECTORY, 'checkstyle.jar')
|
|
CHECKSTYLE_STYLE = os.path.join(MAIN_DIRECTORY, 'android-style.xml')
|
|
FORCED_RULES = ['com.puppycrawl.tools.checkstyle.checks.imports.ImportOrderCheck',
|
|
'com.puppycrawl.tools.checkstyle.checks.imports.UnusedImportsCheck']
|
|
ERROR_UNCOMMITTED = 'You need to commit all modified files before running Checkstyle\n'
|
|
ERROR_UNTRACKED = 'You have untracked java files that are not being checked:\n'
|
|
|
|
def RunCheckstyle(java_files):
|
|
if not os.path.exists(CHECKSTYLE_STYLE):
|
|
print 'Java checkstyle configuration file is missing'
|
|
sys.exit(1)
|
|
|
|
if java_files:
|
|
# Files to check were specified via command line.
|
|
print 'Running Checkstyle on inputted files'
|
|
sha = ''
|
|
last_commit_modified_files = {}
|
|
java_files = map(os.path.abspath, java_files)
|
|
else:
|
|
# Files where not specified exclicitly. Let's use last commit files.
|
|
sha, last_commit_modified_files = _GetModifiedFiles()
|
|
print 'Running Checkstyle on %s commit' % sha
|
|
java_files = last_commit_modified_files.keys()
|
|
|
|
if not java_files:
|
|
print 'No files to check'
|
|
return
|
|
|
|
stdout = _ExecuteCheckstyle(java_files)
|
|
(errors, warnings) = _ParseAndFilterOutput(stdout,
|
|
sha,
|
|
last_commit_modified_files)
|
|
|
|
if errors:
|
|
print 'ERRORS:'
|
|
print '\n'.join(errors)
|
|
if warnings:
|
|
print 'WARNINGS:'
|
|
print '\n'.join(warnings)
|
|
if errors or warnings:
|
|
sys.exit(1)
|
|
|
|
print 'SUCCESS! NO ISSUES FOUND'
|
|
sys.exit(0)
|
|
|
|
|
|
def _ExecuteCheckstyle(java_files):
|
|
# Run checkstyle
|
|
checkstyle_env = os.environ.copy()
|
|
checkstyle_env['JAVA_CMD'] = 'java'
|
|
try:
|
|
check = subprocess.Popen(['java', '-cp',
|
|
CHECKSTYLE_JAR,
|
|
'com.puppycrawl.tools.checkstyle.Main', '-c',
|
|
CHECKSTYLE_STYLE, '-f', 'xml'] + java_files,
|
|
stdout=subprocess.PIPE, env=checkstyle_env)
|
|
stdout, _ = check.communicate()
|
|
except OSError as e:
|
|
if e.errno == errno.ENOENT:
|
|
print 'Error running Checkstyle!'
|
|
sys.exit(1)
|
|
|
|
# Work-around for Checkstyle printing error count to stdio.
|
|
if 'Checkstyle ends with' in stdout.splitlines()[-1]:
|
|
stdout = '\n'.join(stdout.splitlines()[:-1])
|
|
return stdout
|
|
|
|
def _ParseAndFilterOutput(stdout, sha, last_commit_modified_files):
|
|
result_errors = []
|
|
result_warnings = []
|
|
root = xml.dom.minidom.parseString(stdout)
|
|
for file_element in root.getElementsByTagName('file'):
|
|
file_name = file_element.attributes['name'].value
|
|
if last_commit_modified_files:
|
|
modified_lines = git.modified_lines(file_name,
|
|
last_commit_modified_files[file_name],
|
|
sha)
|
|
file_name = os.path.relpath(file_name)
|
|
errors = file_element.getElementsByTagName('error')
|
|
for error in errors:
|
|
line = int(error.attributes['line'].value)
|
|
rule = error.attributes['source'].value
|
|
if last_commit_modified_files and _ShouldSkip(modified_lines, line, rule):
|
|
continue
|
|
|
|
column = ''
|
|
if error.hasAttribute('column'):
|
|
column = '%s:' % (error.attributes['column'].value)
|
|
message = error.attributes['message'].value
|
|
result = ' %s:%s:%s %s' % (file_name, line, column, message)
|
|
|
|
severity = error.attributes['severity'].value
|
|
if severity == 'error':
|
|
result_errors.append(result)
|
|
elif severity == 'warning':
|
|
result_warnings.append(result)
|
|
return (result_errors, result_warnings)
|
|
|
|
|
|
# Returns whether an error on a given line should be skipped
|
|
# based on the modified_lines list and the rule.
|
|
def _ShouldSkip(modified_lines, line, rule):
|
|
# None modified_lines means checked file is new and nothing should be skipped.
|
|
if modified_lines is None:
|
|
return False
|
|
return line not in modified_lines and rule not in FORCED_RULES
|
|
|
|
|
|
def _GetModifiedFiles(out = sys.stdout):
|
|
root = git.repository_root()
|
|
sha = git.last_commit()
|
|
pending_files = git.modified_files(root, True)
|
|
if pending_files:
|
|
out.write(ERROR_UNCOMMITTED)
|
|
sys.exit(1)
|
|
untracked_files = git.modified_files(root, False)
|
|
untracked_files = {f for f in untracked_files if f.endswith('.java')}
|
|
if untracked_files:
|
|
out.write(ERROR_UNTRACKED)
|
|
for untracked_file in untracked_files:
|
|
out.write(untracked_file + '\n')
|
|
out.write('\n')
|
|
|
|
modified_files = git.modified_files(root, True, sha)
|
|
modified_files = {f: modified_files[f] for f
|
|
in modified_files if f.endswith('.java')}
|
|
return (sha, modified_files)
|
|
|
|
|
|
def main(args=None):
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument('--file', '-f', nargs='+')
|
|
args = parser.parse_args()
|
|
RunCheckstyle(args.file)
|
|
|
|
if __name__ == '__main__':
|
|
main()
|