• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python2.7
2
3# Copyright 2016, VIXL authors
4# All rights reserved.
5#
6# Redistribution and use in source and binary forms, with or without
7# modification, are permitted provided that the following conditions are met:
8#
9#   * Redistributions of source code must retain the above copyright notice,
10#     this list of conditions and the following disclaimer.
11#   * Redistributions in binary form must reproduce the above copyright notice,
12#     this list of conditions and the following disclaimer in the documentation
13#     and/or other materials provided with the distribution.
14#   * Neither the name of ARM Limited nor the names of its contributors may be
15#     used to endorse or promote products derived from this software without
16#     specific prior written permission.
17#
18# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS CONTRIBUTORS "AS IS" AND
19# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
22# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
29import argparse
30import multiprocessing
31import os
32import re
33import subprocess
34import sys
35import tempfile
36
37from threaded_tests import Test, TestQueue
38import printer
39import util
40
41CLANG_FORMAT_VERSION_MAJOR = 3
42CLANG_FORMAT_VERSION_MINOR = 8
43
44DEFAULT_CLANG_FORMAT = \
45    'clang-format-{}.{}'.format(CLANG_FORMAT_VERSION_MAJOR,
46                                CLANG_FORMAT_VERSION_MINOR)
47
48is_output_redirected = not sys.stdout.isatty()
49
50def BuildOptions():
51  parser = argparse.ArgumentParser(
52    description = '''This tool runs `clang-format` on C++ files.
53    If no files are provided on the command-line, all C++ source files are
54    processed, except for the test traces.
55    When available, `colordiff` is automatically used to colour the output.''',
56    # Print default values.
57    formatter_class = argparse.ArgumentDefaultsHelpFormatter)
58  parser.add_argument('files', nargs = '*')
59  parser.add_argument('--clang-format', default=DEFAULT_CLANG_FORMAT,
60                      help='Path to clang-format.')
61  parser.add_argument('--in-place', '-i',
62                      action = 'store_true', default = False,
63                      help = 'Edit files in place.')
64  parser.add_argument('--jobs', '-j', metavar = 'N', type = int, nargs = '?',
65                      default = multiprocessing.cpu_count(),
66                      const = multiprocessing.cpu_count(),
67                      help = '''Runs the tests using N jobs. If the option is set
68                      but no value is provided, the script will use as many jobs
69                      as it thinks useful.''')
70  return parser.parse_args()
71
72
73def ClangFormatIsAvailable(clang_format):
74  if not util.IsCommandAvailable(clang_format):
75    return False
76  cmd = '%s -version' % clang_format
77  rc, version = util.getstatusoutput(cmd)
78  if rc != 0:
79      util.abort("Failed to execute %s: %s" % (cmd, version))
80  m = re.search("^clang-format version (\d)\.(\d)\.\d.*$",
81                version.decode(), re.M)
82  if not m:
83      util.abort("Failed to get clang-format's version: %s" % version)
84  major, minor = m.groups()
85  return int(major) == CLANG_FORMAT_VERSION_MAJOR and \
86      int(minor) == CLANG_FORMAT_VERSION_MINOR
87
88def RunTest(test):
89  filename = test.args['filename']
90  clang_format = test.args['clang_format']
91  in_place = test.args['in_place']
92
93  rc = 0
94
95  cmd_format = [clang_format, filename]
96  temp_file, temp_file_name = tempfile.mkstemp(prefix = 'clang_format_')
97  cmd_format_string = '$ ' + ' '.join(cmd_format) + ' > %s' % temp_file_name
98  p_format = subprocess.Popen(cmd_format,
99                              stdout = temp_file, stderr = subprocess.STDOUT)
100
101  rc += p_format.wait()
102
103  cmd_diff = ['diff', '--unified', filename, temp_file_name]
104  cmd_diff_string = '$ ' + ' '.join(cmd_diff)
105  p_diff = subprocess.Popen(cmd_diff,
106                            stdout = subprocess.PIPE, stderr = subprocess.STDOUT)
107
108  if util.IsCommandAvailable('colordiff') and not is_output_redirected:
109    p_colordiff = subprocess.Popen(
110            ['colordiff', '--unified'],
111            stdin = p_diff.stdout,
112            stdout = subprocess.PIPE, stderr = subprocess.STDOUT)
113    out, unused = p_colordiff.communicate()
114  else:
115    out, unused = p_diff.communicate()
116
117  rc += p_diff.wait()
118
119  if in_place:
120      cmd_format = [clang_format, '-i', filename]
121      p_format = subprocess.Popen(cmd_format,
122                                  stdout=temp_file, stderr=subprocess.STDOUT)
123
124  if rc != 0:
125    with Test.n_tests_failed.get_lock(): Test.n_tests_failed.value += 1
126  else:
127    with Test.n_tests_passed.get_lock(): Test.n_tests_passed.value += 1
128
129  printer.__print_lock__.acquire()
130
131  printer.UpdateProgress(test.shared.start_time,
132                         Test.n_tests_passed.value,
133                         Test.n_tests_failed.value,
134                         test.shared.n_tests,
135                         Test.n_tests_skipped.value,
136                         test.shared.n_known_failures,
137                         test.name,
138                         prevent_next_overwrite = rc != 0,
139                         has_lock = True,
140                         prefix = test.shared.progress_prefix)
141
142  if rc != 0:
143    printer.Print('Incorrectly formatted file: ' + filename + '\n' + \
144                  cmd_format_string + '\n' + \
145                  cmd_diff_string + '\n' + \
146                  out, has_lock = True)
147  printer.__print_lock__.release()
148
149  os.remove(temp_file_name)
150
151# Returns the total number of files incorrectly formatted.
152def ClangFormatFiles(files, clang_format, in_place = False, jobs = 1,
153                     progress_prefix = ''):
154  if not ClangFormatIsAvailable(clang_format):
155    error_message = "`{}` version {}.{} not found. Please ensure it " \
156                    "is installed, in your PATH and the correct version." \
157                    .format(clang_format,
158                            CLANG_FORMAT_VERSION_MAJOR,
159                            CLANG_FORMAT_VERSION_MINOR)
160    print(printer.COLOUR_RED + error_message + printer.NO_COLOUR)
161    return -1
162
163  queue = TestQueue(prefix = progress_prefix)
164  for f in files:
165    queue.AddTest(f, filename = f, clang_format = clang_format, in_place = in_place)
166
167  rc = queue.Run(jobs, True, RunTest)
168
169  printer.PrintOverwritableLine(
170      progress_prefix + '%d files are incorrectly formatted.' % rc,
171      type = printer.LINE_TYPE_LINTER)
172  printer.EnsureNewLine()
173
174  return rc
175
176if __name__ == '__main__':
177  # Parse the arguments.
178  args = BuildOptions()
179  files = args.files or util.get_source_files()
180
181  rc = ClangFormatFiles(files, clang_format = args.clang_format,
182                        in_place = args.in_place, jobs = args.jobs)
183
184  sys.exit(rc)
185