Add formatting/static analysis (#486)
* Reform source with clang-format. Rules applied with: $ find . -iname *.h -o -iname *.cc | xargs clang-format \ -style=file -i -fallback-style=google * Add clang-format and analyze to build. Based on the excellent work at: https://github.com/ttroy50/cmake-examples * Clean up CMake stuff on macOS. * Remove vim/emacs modelines. * Update copyright dates. * Build fixes. * Build fixes. * Build fixes. * Build fixes. * Build fixes. * Build fixes.
This commit is contained in:
163
cmake/scripts/clang-format-check-changed.py
Executable file
163
cmake/scripts/clang-format-check-changed.py
Executable file
@@ -0,0 +1,163 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
|
||||
|
||||
def check_file(filename, excludes, extensions):
|
||||
"""
|
||||
Check if a file should be included in our check
|
||||
"""
|
||||
name, ext = os.path.splitext(filename)
|
||||
|
||||
if len(ext) > 0 and ext in extensions:
|
||||
if len(excludes) == 0:
|
||||
return True
|
||||
|
||||
for exclude in excludes:
|
||||
if exclude in filename:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def check_directory(directory, excludes, extensions):
|
||||
output = []
|
||||
|
||||
if len(excludes) > 0:
|
||||
for exclude in excludes:
|
||||
if exclude in directory:
|
||||
directory_excluded = False
|
||||
return output
|
||||
|
||||
for root, _, files in os.walk(directory):
|
||||
for file in files:
|
||||
filename = os.path.join(root, file)
|
||||
if check_file(filename, excludes, extensions):
|
||||
print("Will check file [{}]".format(filename))
|
||||
output.append(filename)
|
||||
return output
|
||||
|
||||
def get_git_root(git_bin):
|
||||
cmd = [git_bin, "rev-parse", "--show-toplevel"]
|
||||
try:
|
||||
return subprocess.check_output(cmd).strip()
|
||||
except subprocess.CalledProcessError, e:
|
||||
print("Error calling git [{}]".format(e))
|
||||
raise
|
||||
|
||||
def clean_git_filename(line):
|
||||
"""
|
||||
Takes a line from git status --porcelain and returns the filename
|
||||
"""
|
||||
file = None
|
||||
git_status = line[:2]
|
||||
# Not an exhaustive list of git status output but should
|
||||
# be enough for this case
|
||||
# check if this is a delete
|
||||
if 'D' in git_status:
|
||||
return None
|
||||
# ignored file
|
||||
if '!' in git_status:
|
||||
return None
|
||||
# Covers renamed files
|
||||
if '->' in line:
|
||||
file = line[3:].split('->')[-1].strip()
|
||||
else:
|
||||
file = line[3:].strip()
|
||||
|
||||
return file
|
||||
|
||||
|
||||
def get_changed_files(git_bin, excludes, file_extensions):
|
||||
"""
|
||||
Run git status and return the list of changed files
|
||||
"""
|
||||
extensions = file_extensions.split(",")
|
||||
# arguments coming from cmake will be *.xx. We want to remove the *
|
||||
for i, extension in enumerate(extensions):
|
||||
if extension[0] == '*':
|
||||
extensions[i] = extension[1:]
|
||||
|
||||
git_root = get_git_root(git_bin)
|
||||
|
||||
cmd = [git_bin, "status", "--porcelain", "--ignore-submodules"]
|
||||
print("git cmd = {}".format(cmd))
|
||||
output = []
|
||||
returncode = 0
|
||||
try:
|
||||
cmd_output = subprocess.check_output(cmd)
|
||||
for line in cmd_output.split('\n'):
|
||||
if len(line) > 0:
|
||||
file = clean_git_filename(line)
|
||||
if not file:
|
||||
continue
|
||||
file = os.path.join(git_root, file)
|
||||
|
||||
if file[-1] == "/":
|
||||
directory_files = check_directory(
|
||||
file, excludes, file_extensions)
|
||||
output = output + directory_files
|
||||
else:
|
||||
if check_file(file, excludes, file_extensions):
|
||||
print("Will check file [{}]".format(file))
|
||||
output.append(file)
|
||||
|
||||
except subprocess.CalledProcessError, e:
|
||||
print("Error calling git [{}]".format(e))
|
||||
returncode = e.returncode
|
||||
|
||||
return output, returncode
|
||||
|
||||
|
||||
def run_clang_format(clang_format_bin, changed_files):
|
||||
"""
|
||||
Run clang format on a list of files
|
||||
@return 0 if formatted correctly.
|
||||
"""
|
||||
if len(changed_files) == 0:
|
||||
return 0
|
||||
cmd = [clang_format_bin, "-style=file",
|
||||
"-output-replacements-xml"] + changed_files
|
||||
print("clang-format cmd = {}".format(cmd))
|
||||
try:
|
||||
cmd_output = subprocess.check_output(cmd)
|
||||
if "replacement offset" in cmd_output:
|
||||
print("ERROR: Changed files don't match format")
|
||||
return 1
|
||||
except subprocess.CalledProcessError, e:
|
||||
print("Error calling clang-format [{}]".format(e))
|
||||
return e.returncode
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
def cli():
|
||||
# global params
|
||||
parser = argparse.ArgumentParser(prog='clang-format-check-changed',
|
||||
description='Checks if files chagned in git match the .clang-format specification')
|
||||
parser.add_argument("--file-extensions", type=str,
|
||||
default=".cpp,.h,.cxx,.hxx,.hpp,.cc,.ipp",
|
||||
help="Comma seperated list of file extensions to check")
|
||||
parser.add_argument('--exclude', action='append', default=[],
|
||||
help='Will not match the files / directories with these in the name')
|
||||
parser.add_argument('--clang-format-bin', type=str, default="clang-format",
|
||||
help="The clang format binary")
|
||||
parser.add_argument('--git-bin', type=str, default="git",
|
||||
help="The git binary")
|
||||
args = parser.parse_args()
|
||||
|
||||
# Run gcovr to get the .gcda files form .gcno
|
||||
changed_files, returncode = get_changed_files(
|
||||
args.git_bin, args.exclude, args.file_extensions)
|
||||
if returncode != 0:
|
||||
return returncode
|
||||
|
||||
return run_clang_format(args.clang_format_bin, changed_files)
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(cli())
|
Reference in New Issue
Block a user