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