204 lines
7.2 KiB
Python
Executable file
204 lines
7.2 KiB
Python
Executable file
#!/usr/bin/env python2.7
|
|
|
|
# Copyright 2016, VIXL authors
|
|
# All rights reserved.
|
|
#
|
|
# Redistribution and use in source and binary forms, with or without
|
|
# modification, are permitted provided that the following conditions are met:
|
|
#
|
|
# * Redistributions of source code must retain the above copyright notice,
|
|
# this list of conditions and the following disclaimer.
|
|
# * Redistributions in binary form must reproduce the above copyright notice,
|
|
# this list of conditions and the following disclaimer in the documentation
|
|
# and/or other materials provided with the distribution.
|
|
# * Neither the name of ARM Limited nor the names of its contributors may be
|
|
# used to endorse or promote products derived from this software without
|
|
# specific prior written permission.
|
|
#
|
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS CONTRIBUTORS "AS IS" AND
|
|
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
|
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
import argparse
|
|
import fnmatch
|
|
import multiprocessing
|
|
import os
|
|
import signal
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
|
|
import config
|
|
import git
|
|
import printer
|
|
import util
|
|
|
|
|
|
is_output_redirected = not sys.stdout.isatty()
|
|
|
|
# Catch SIGINT to gracefully exit when ctrl+C is pressed.
|
|
def sigint_handler(signal, frame):
|
|
sys.exit(1)
|
|
signal.signal(signal.SIGINT, sigint_handler)
|
|
|
|
def BuildOptions():
|
|
parser = argparse.ArgumentParser(
|
|
description = '''This tool runs `clang-format` on C++ files.
|
|
If no files are provided on the command-line, all C++ source files in `src`,
|
|
`sample`, and `benchmarks` are processed.
|
|
When available, `colordiff` is automatically used to clour the output.''',
|
|
# Print default values.
|
|
formatter_class = argparse.ArgumentDefaultsHelpFormatter)
|
|
parser.add_argument('files', nargs = '*')
|
|
parser.add_argument('--in-place', '-i',
|
|
action = 'store_true', default = False,
|
|
help = 'Edit files in place.')
|
|
parser.add_argument('--jobs', '-j', metavar = 'N', type = int, nargs = '?',
|
|
default = multiprocessing.cpu_count(),
|
|
const = multiprocessing.cpu_count(),
|
|
help = '''Runs the tests using N jobs. If the option is set
|
|
but no value is provided, the script will use as many jobs
|
|
as it thinks useful.''')
|
|
return parser.parse_args()
|
|
|
|
|
|
# Returns 0 if the file is correctly formatted, or 1 otherwise.
|
|
def ClangFormat(filename, in_place = False, progress_prefix = ''):
|
|
rc = 0
|
|
printer.PrintOverwritableLine('Processing %s' % filename,
|
|
type = printer.LINE_TYPE_LINTER)
|
|
|
|
cmd_format = ['clang-format-3.6', filename]
|
|
temp_file, temp_file_name = tempfile.mkstemp(prefix = 'clang_format_')
|
|
cmd_format_string = '$ ' + ' '.join(cmd_format) + ' > %s' % temp_file_name
|
|
p_format = subprocess.Popen(cmd_format,
|
|
stdout = temp_file, stderr = subprocess.STDOUT)
|
|
|
|
rc += p_format.wait()
|
|
|
|
cmd_diff = ['diff', '--unified', filename, temp_file_name]
|
|
cmd_diff_string = '$ ' + ' '.join(cmd_diff)
|
|
p_diff = subprocess.Popen(cmd_diff,
|
|
stdout = subprocess.PIPE, stderr = subprocess.STDOUT)
|
|
|
|
if util.IsCommandAvailable('colordiff') and not is_output_redirected:
|
|
p_colordiff = subprocess.Popen(
|
|
['colordiff', '--unified'],
|
|
stdin = p_diff.stdout,
|
|
stdout = subprocess.PIPE, stderr = subprocess.STDOUT)
|
|
out, unused = p_colordiff.communicate()
|
|
else:
|
|
out, unused = p_diff.communicate()
|
|
|
|
rc += p_diff.wait()
|
|
|
|
if in_place:
|
|
cmd_format = ['clang-format-3.6', '-i', filename]
|
|
p_format = subprocess.Popen(cmd_format,
|
|
stdout=temp_file, stderr=subprocess.STDOUT)
|
|
|
|
if rc != 0:
|
|
printer.Print('Incorrectly formatted file: ' + filename + '\n' + \
|
|
cmd_format_string + '\n' + \
|
|
cmd_diff_string + '\n' + \
|
|
out)
|
|
|
|
os.remove(temp_file_name)
|
|
|
|
return 0 if rc == 0 else 1
|
|
|
|
|
|
# The multiprocessing map_async function does not allow passing multiple
|
|
# arguments directly, so use a wrapper.
|
|
def ClangFormatWrapper(args):
|
|
# Run under a try-catch to avoid flooding the output when the script is
|
|
# interrupted from the keyboard with ctrl+C.
|
|
try:
|
|
return ClangFormat(*args)
|
|
except:
|
|
sys.exit(1)
|
|
|
|
|
|
# Returns the total number of files incorrectly formatted.
|
|
def ClangFormatFiles(files, in_place = False, jobs = 1, progress_prefix = ''):
|
|
if not util.IsCommandAvailable('clang-format-3.6'):
|
|
print(
|
|
printer.COLOUR_RED + \
|
|
("`clang-format-3.6` not found. Please ensure it is installed "
|
|
"and in your PATH.") + \
|
|
printer.NO_COLOUR)
|
|
return -1
|
|
|
|
pool = multiprocessing.Pool(jobs)
|
|
# The '.get(9999999)' is workaround to allow killing the test script with
|
|
# ctrl+C from the shell. This bug is documented at
|
|
# http://bugs.python.org/issue8296.
|
|
tasks = [(f, in_place, progress_prefix) for f in files]
|
|
# Run under a try-catch to avoid flooding the output when the script is
|
|
# interrupted from the keyboard with ctrl+C.
|
|
try:
|
|
results = pool.map_async(ClangFormatWrapper, tasks).get(9999999)
|
|
pool.close()
|
|
pool.join()
|
|
except KeyboardInterrupt:
|
|
pool.terminate()
|
|
sys.exit(1)
|
|
rc = sum(results)
|
|
|
|
printer.PrintOverwritableLine(
|
|
progress_prefix + '%d files are incorrectly formatted.' % rc,
|
|
type = printer.LINE_TYPE_LINTER)
|
|
printer.EnsureNewLine()
|
|
return rc
|
|
|
|
|
|
def Find(path, filters = ['*'], excluded_dir = ""):
|
|
files_found = []
|
|
|
|
def NameMatchesAnyFilter(name, ff):
|
|
for f in ff:
|
|
if fnmatch.fnmatch(name, f):
|
|
return True
|
|
return False
|
|
|
|
for root, dirs, files in os.walk(path):
|
|
files_found += [
|
|
os.path.join(root, fn)
|
|
for fn in files
|
|
# Include files which names match "filters".
|
|
# Exclude files for which the base directory is "excluded_dir".
|
|
if NameMatchesAnyFilter(os.path.relpath(fn), filters) and \
|
|
not os.path.dirname(os.path.join(root, fn)).endswith(excluded_dir)
|
|
]
|
|
return files_found
|
|
|
|
|
|
def GetCppSourceFilesToFormat():
|
|
sources = []
|
|
source_dirs = [config.dir_aarch32_benchmarks,
|
|
config.dir_aarch32_examples,
|
|
config.dir_aarch64_benchmarks,
|
|
config.dir_aarch64_examples,
|
|
config.dir_tests,
|
|
config.dir_src_vixl ]
|
|
for directory in source_dirs:
|
|
sources += Find(directory, ['*.h', '*.cc'], 'traces')
|
|
return sources
|
|
|
|
|
|
if __name__ == '__main__':
|
|
# Parse the arguments.
|
|
args = BuildOptions()
|
|
files = args.files
|
|
if not files:
|
|
files = GetCppSourceFilesToFormat()
|
|
|
|
rc = ClangFormatFiles(files, in_place = args.in_place, jobs = args.jobs)
|
|
sys.exit(rc)
|