1b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas#!/usr/bin/python
2b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas
3b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas#
4b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas# Copyright 2015, The Android Open Source Project
5b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas#
6b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas# Licensed under the Apache License, Version 2.0 (the "License");
7b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas# you may not use this file except in compliance with the License.
8b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas# You may obtain a copy of the License at
9b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas#
10b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas#     http://www.apache.org/licenses/LICENSE-2.0
11b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas#
12b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas# Unless required by applicable law or agreed to in writing, software
13b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas# distributed under the License is distributed on an "AS IS" BASIS,
14b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas# See the License for the specific language governing permissions and
16b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas# limitations under the License.
17b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas#
18b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas
19b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas"""Script that is used by developers to run style checks on Java files."""
20b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas
21b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikasimport argparse
22b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikasimport errno
23b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikasimport os
24b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikasimport shutil
25b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikasimport subprocess
26b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikasimport sys
27b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikasimport tempfile
28b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikasimport xml.dom.minidom
29b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikasimport gitlint.git as git
30b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas
31b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas
32b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikasdef _FindFoldersContaining(root, wanted):
33b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  """Recursively finds directories that have a file with the given name.
34b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas
35b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  Args:
36b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas    root: Root folder to start the search from.
37b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas    wanted: The filename that we are looking for.
38b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas
39b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  Returns:
40b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas    List of folders that has a file with the given name
41b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  """
42b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas
43b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  if not root:
44b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas    return []
45b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  if os.path.islink(root):
46b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas    return []
47b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  result = []
48b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  for file_name in os.listdir(root):
49b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas    file_path = os.path.join(root, file_name)
50b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas    if os.path.isdir(file_path):
51b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas      sub_result = _FindFoldersContaining(file_path, wanted)
52b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas      result.extend(sub_result)
53b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas    else:
54b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas      if file_name == wanted:
55b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas        result.append(root)
56b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  return result
57b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas
58b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas LiutikasMAIN_DIRECTORY = os.path.normpath(os.path.dirname(__file__))
59b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas LiutikasCHECKSTYLE_JAR = os.path.join(MAIN_DIRECTORY, 'checkstyle.jar')
60b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas LiutikasCHECKSTYLE_STYLE = os.path.join(MAIN_DIRECTORY, 'android-style.xml')
61b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas LiutikasFORCED_RULES = ['com.puppycrawl.tools.checkstyle.checks.imports.ImportOrderCheck',
62b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas                'com.puppycrawl.tools.checkstyle.checks.imports.UnusedImportsCheck']
63b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas LiutikasSKIPPED_RULES_FOR_TEST_FILES = ['com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocTypeCheck',
64b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas                                'com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocMethodCheck']
65b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas LiutikasSUBPATH_FOR_TEST_FILES = ['/tests/', '/test/', '/androidTest/']
66b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas LiutikasSUBPATH_FOR_TEST_DATA_FILES = _FindFoldersContaining(git.repository_root(),
67b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas                                                     'IGNORE_CHECKSTYLE')
68b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas LiutikasERROR_UNCOMMITTED = 'You need to commit all modified files before running Checkstyle\n'
69b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas LiutikasERROR_UNTRACKED = 'You have untracked java files that are not being checked:\n'
70b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas
71b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas
72b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikasdef RunCheckstyleOnFiles(java_files, classpath=CHECKSTYLE_JAR, config_xml=CHECKSTYLE_STYLE):
73b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  """Runs Checkstyle checks on a given set of java_files.
74b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas
75b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  Args:
76b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas    java_files: A list of files to check.
77b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas    classpath: The colon-delimited list of JARs in the classpath.
78b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas    config_xml: Path of the checkstyle XML configuration file.
79b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas
80b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  Returns:
81b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas    A tuple of errors and warnings.
82b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  """
83b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  print 'Running Checkstyle on inputted files'
84b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  java_files = map(os.path.abspath, java_files)
85b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  stdout = _ExecuteCheckstyle(java_files, classpath, config_xml)
86b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  (errors, warnings) = _ParseAndFilterOutput(stdout)
87b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  _PrintErrorsAndWarnings(errors, warnings)
88b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  return errors, warnings
89b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas
90b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas
91b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikasdef RunCheckstyleOnACommit(commit,
92b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas                           classpath=CHECKSTYLE_JAR,
93b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas                           config_xml=CHECKSTYLE_STYLE,
94b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas                           file_whitelist=None):
95b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  """Runs Checkstyle checks on a given commit.
96b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas
97b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  It will run Checkstyle on the changed Java files in a specified commit SHA-1
98b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  and if that is None it will fallback to check the latest commit of the
99b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  currently checked out branch.
100b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas
101b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  Args:
102b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas    commit: A full 40 character SHA-1 of a commit to check.
103b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas    classpath: The colon-delimited list of JARs in the classpath.
104b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas    config_xml: Path of the checkstyle XML configuration file.
105b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas    file_whitelist: A list of whitelisted file paths that should be checked.
106b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas
107b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  Returns:
108b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas    A tuple of errors and warnings.
109b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  """
110b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  if not git.repository_root():
111b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas    print 'FAILURE: not inside a git repository'
112b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas    sys.exit(1)
113b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  explicit_commit = commit is not None
114b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  if not explicit_commit:
115b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas    _WarnIfUntrackedFiles()
116b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas    commit = git.last_commit()
117b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  print 'Running Checkstyle on %s commit' % commit
118b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  commit_modified_files = _GetModifiedFiles(commit, explicit_commit)
119b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  commit_modified_files = _FilterFiles(commit_modified_files, file_whitelist)
120b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  if not commit_modified_files.keys():
121b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas    print 'No Java files to check'
122b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas    return [], []
123b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas
124b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  (tmp_dir, tmp_file_map) = _GetTempFilesForCommit(
125b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas      commit_modified_files.keys(), commit)
126b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas
127b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  java_files = tmp_file_map.keys()
128b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  stdout = _ExecuteCheckstyle(java_files, classpath, config_xml)
129b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas
130b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  # Remove all the temporary files.
131b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  shutil.rmtree(tmp_dir)
132b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas
133b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  (errors, warnings) = _ParseAndFilterOutput(stdout,
134b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas                                             commit,
135b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas                                             commit_modified_files,
136b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas                                             tmp_file_map)
137b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  _PrintErrorsAndWarnings(errors, warnings)
138b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  return errors, warnings
139b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas
140b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas
141b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikasdef _WarnIfUntrackedFiles(out=sys.stdout):
142b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  """Prints a warning and a list of untracked files if needed."""
143b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  root = git.repository_root()
144b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  untracked_files = git.modified_files(root, False)
145b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  untracked_files = {f for f in untracked_files if f.endswith('.java')}
146b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  if untracked_files:
147b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas    out.write(ERROR_UNTRACKED)
148b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas    for untracked_file in untracked_files:
149b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas      out.write(untracked_file + '\n')
150b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas    out.write('\n')
151b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas
152b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas
153b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikasdef _PrintErrorsAndWarnings(errors, warnings):
154b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  """Prints given errors and warnings."""
155b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  if errors:
156b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas    print 'ERRORS:'
157b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas    print '\n'.join(errors)
158b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  if warnings:
159b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas    print 'WARNINGS:'
160b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas    print '\n'.join(warnings)
161b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas
162b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas
163b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikasdef _ExecuteCheckstyle(java_files, classpath, config_xml):
164b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  """Runs Checkstyle to check give Java files for style errors.
165b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas
166b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  Args:
167b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas    java_files: A list of Java files that needs to be checked.
168b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas    classpath: The colon-delimited list of JARs in the classpath.
169b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas    config_xml: Path of the checkstyle XML configuration file.
170b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas
171b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  Returns:
172b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas    Checkstyle output in XML format.
173b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  """
174b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  # Run checkstyle
175b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  checkstyle_env = os.environ.copy()
176b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  checkstyle_env['JAVA_CMD'] = 'java'
177b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  try:
178b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas    check = subprocess.Popen(['java', '-cp', classpath,
179b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas                              'com.puppycrawl.tools.checkstyle.Main', '-c',
180b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas                              config_xml, '-f', 'xml'] + java_files,
181b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas                             stdout=subprocess.PIPE, env=checkstyle_env)
182b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas    stdout, _ = check.communicate()
183b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  except OSError as e:
184b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas    if e.errno == errno.ENOENT:
185b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas      print 'Error running Checkstyle!'
186b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas      sys.exit(1)
187b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas
188b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  # A work-around for Checkstyle printing error count to stdio.
189b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  if 'Checkstyle ends with' in stdout.splitlines()[-1]:
190b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas    stdout = '\n'.join(stdout.splitlines()[:-1])
191b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  return stdout
192b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas
193b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas
194b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikasdef _ParseAndFilterOutput(stdout,
195b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas                          sha=None,
196b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas                          commit_modified_files=None,
197b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas                          tmp_file_map=None):
198b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  result_errors = []
199b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  result_warnings = []
200b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  root = xml.dom.minidom.parseString(stdout)
201b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  for file_element in root.getElementsByTagName('file'):
202b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas    file_name = file_element.attributes['name'].value
203b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas    if tmp_file_map:
204b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas      file_name = tmp_file_map[file_name]
205b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas    modified_lines = None
206b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas    if commit_modified_files:
207b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas      modified_lines = git.modified_lines(file_name,
208b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas                                          commit_modified_files[file_name],
209b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas                                          sha)
210b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas    test_class = any(substring in file_name for substring
211b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas                     in SUBPATH_FOR_TEST_FILES)
212b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas    test_data_class = any(substring in file_name for substring
213b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas                          in SUBPATH_FOR_TEST_DATA_FILES)
214b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas    file_name = os.path.relpath(file_name)
215b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas    errors = file_element.getElementsByTagName('error')
216b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas    for error in errors:
217b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas      line = int(error.attributes['line'].value)
218b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas      rule = error.attributes['source'].value
219b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas      if _ShouldSkip(commit_modified_files, modified_lines, line, rule,
220b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas                     test_class, test_data_class):
221b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas        continue
222b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas
223b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas      column = ''
224b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas      if error.hasAttribute('column'):
225b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas        column = '%s:' % error.attributes['column'].value
226b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas      message = error.attributes['message'].value
227b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas      project = ''
228b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas      if os.environ.get('REPO_PROJECT'):
229b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas        project = '[' + os.environ.get('REPO_PROJECT') + '] '
230b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas
231b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas      result = '  %s%s:%s:%s %s' % (project, file_name, line, column, message)
232b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas
233b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas      severity = error.attributes['severity'].value
234b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas      if severity == 'error':
235b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas        result_errors.append(result)
236b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas      elif severity == 'warning':
237b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas        result_warnings.append(result)
238b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  return result_errors, result_warnings
239b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas
240b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas
241b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikasdef _ShouldSkip(commit_check, modified_lines, line, rule, test_class=False,
242b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas                test_data_class=False):
243b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  """Returns whether an error on a given line should be skipped.
244b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas
245b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  Args:
246b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas    commit_check: Whether Checkstyle is being run on a specific commit.
247b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas    modified_lines: A list of lines that has been modified.
248b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas    line: The line that has a rule violation.
249b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas    rule: The type of rule that a given line is violating.
250b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas    test_class: Whether the file being checked is a test class.
251b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas    test_data_class: Whether the file being check is a class used as test data.
252b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas
253b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  Returns:
254b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas    A boolean whether a given line should be skipped in the reporting.
255b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  """
256b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  # None modified_lines means checked file is new and nothing should be skipped.
257b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  if test_data_class:
258b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas    return True
259b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  if test_class and rule in SKIPPED_RULES_FOR_TEST_FILES:
260b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas    return True
261b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  if not commit_check:
262b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas    return False
263b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  if modified_lines is None:
264b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas    return False
265b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  return line not in modified_lines and rule not in FORCED_RULES
266b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas
267b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas
268b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikasdef _GetModifiedFiles(commit, explicit_commit=False, out=sys.stdout):
269b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  root = git.repository_root()
270b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  pending_files = git.modified_files(root, True)
271b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  if pending_files and not explicit_commit:
272b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas    out.write(ERROR_UNCOMMITTED)
273b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas    sys.exit(1)
274b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas
275b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  modified_files = git.modified_files(root, True, commit)
276b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  modified_files = {f: modified_files[f] for f
277b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas                    in modified_files if f.endswith('.java')}
278b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  return modified_files
279b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas
280b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas
281b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikasdef _FilterFiles(files, file_whitelist):
282b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  if not file_whitelist:
283b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas    return files
284b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  return {f: files[f] for f in files
285b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas          for whitelist in file_whitelist if whitelist in f}
286b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas
287b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas
288b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikasdef _GetTempFilesForCommit(file_names, commit):
289b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  """Creates a temporary snapshot of the files in at a commit.
290b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas
291b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  Retrieves the state of every file in file_names at a given commit and writes
292b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  them all out to a temporary directory.
293b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas
294b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  Args:
295b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas    file_names: A list of files that need to be retrieved.
296b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas    commit: A full 40 character SHA-1 of a commit.
297b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas
298b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  Returns:
299b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas    A tuple of temprorary directory name and a directionary of
300b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas    temp_file_name: filename. For example:
301b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas
302b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas    ('/tmp/random/', {'/tmp/random/blarg.java': 'real/path/to/file.java' }
303b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  """
304b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  tmp_dir_name = tempfile.mkdtemp()
305b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  tmp_file_names = {}
306b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  for file_name in file_names:
307b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas    rel_path = os.path.relpath(file_name)
308b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas    content = subprocess.check_output(
309b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas        ['git', 'show', commit + ':' + rel_path])
310b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas
311b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas    tmp_file_name = os.path.join(tmp_dir_name, rel_path)
312b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas    # create directory for the file if it doesn't exist
313b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas    if not os.path.exists(os.path.dirname(tmp_file_name)):
314b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas      os.makedirs(os.path.dirname(tmp_file_name))
315b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas
316b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas    tmp_file = open(tmp_file_name, 'w')
317b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas    tmp_file.write(content)
318b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas    tmp_file.close()
319b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas    tmp_file_names[tmp_file_name] = file_name
320b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  return tmp_dir_name, tmp_file_names
321b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas
322b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas
323b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikasdef main(args=None):
324b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  """Runs Checkstyle checks on a given set of java files or a commit.
325b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas
326b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  It will run Checkstyle on the list of java files first, if unspecified,
327b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  then the check will be run on a specified commit SHA-1 and if that
328b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  is None it will fallback to check the latest commit of the currently checked
329b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  out branch.
330b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  """
331b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  parser = argparse.ArgumentParser()
332b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  parser.add_argument('--file', '-f', nargs='+')
333b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  parser.add_argument('--sha', '-s')
334b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  parser.add_argument('--config_xml', '-c')
335b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  parser.add_argument('--file_whitelist', '-fw', nargs='+')
336b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  parser.add_argument('--add_classpath', '-p')
337b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  args = parser.parse_args()
338b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas
339b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  config_xml = args.config_xml or CHECKSTYLE_STYLE
340b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas
341b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  if not os.path.exists(config_xml):
342b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas    print 'Java checkstyle configuration file is missing'
343b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas    sys.exit(1)
344b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas
345b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  classpath = CHECKSTYLE_JAR
346b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas
347b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  if args.add_classpath:
348b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas    classpath = args.add_classpath + ':' + classpath
349b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas
350b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  if args.file:
351b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas    # Files to check were specified via command line.
352b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas    (errors, warnings) = RunCheckstyleOnFiles(args.file, classpath, config_xml)
353b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  else:
354b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas    (errors, warnings) = RunCheckstyleOnACommit(args.sha, classpath, config_xml,
355b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas                                                args.file_whitelist)
356b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas
357b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  if errors or warnings:
358b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas    sys.exit(1)
359b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas
360b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  print 'SUCCESS! NO ISSUES FOUND'
361b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  sys.exit(0)
362b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas
363b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas
364b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikasif __name__ == '__main__':
365b782f222d5ad3c1250acb12260f2ddaa0935ca4Aurimas Liutikas  main()
366