ci: Add uncrustify config and checker
Based on what we added to phoc recently
This commit is contained in:
Executable
+169
@@ -0,0 +1,169 @@
|
|||||||
|
#!/bin/env python3
|
||||||
|
#
|
||||||
|
# Based on check-style.py by
|
||||||
|
# Carlos Garnacho <carlosg@gnome.org>
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
# Path relative to this script
|
||||||
|
uncrustify_cfg = '.gitlab-ci/uncrustify.cfg'
|
||||||
|
|
||||||
|
|
||||||
|
def run_diff(sha):
|
||||||
|
proc = subprocess.run(
|
||||||
|
["git", "diff", "-U0", "--function-context", sha, "HEAD"],
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.STDOUT,
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
return proc.stdout.strip().splitlines()
|
||||||
|
|
||||||
|
|
||||||
|
def find_chunks(diff):
|
||||||
|
file_entry_re = re.compile(r'^\+\+\+ b/(.*)$')
|
||||||
|
diff_chunk_re = re.compile(r'^@@ -\d+,\d+ \+(\d+),(\d+)')
|
||||||
|
file = None
|
||||||
|
chunks = []
|
||||||
|
|
||||||
|
for line in diff:
|
||||||
|
match = file_entry_re.match(line)
|
||||||
|
if match:
|
||||||
|
file = match.group(1)
|
||||||
|
|
||||||
|
match = diff_chunk_re.match(line)
|
||||||
|
if match:
|
||||||
|
start = int(match.group(1))
|
||||||
|
len = int(match.group(2))
|
||||||
|
end = start + len
|
||||||
|
|
||||||
|
if len > 0 and (file.endswith('.c') or file.endswith('.h') or file.endswith('.vala')):
|
||||||
|
chunks.append({'file': file, 'start': start, 'end': end})
|
||||||
|
|
||||||
|
return chunks
|
||||||
|
|
||||||
|
|
||||||
|
def reformat_chunks(chunks, rewrite, dry_run):
|
||||||
|
# Creates temp file with INDENT-ON/OFF comments
|
||||||
|
def create_temp_file(file, start, end):
|
||||||
|
with open(file) as f:
|
||||||
|
tmp = tempfile.NamedTemporaryFile()
|
||||||
|
if start > 1:
|
||||||
|
tmp.write(b'/** *INDENT-OFF* **/\n')
|
||||||
|
for i, line in enumerate(f, start=1):
|
||||||
|
if i == start - 1:
|
||||||
|
tmp.write(b'/** *INDENT-ON* **/\n')
|
||||||
|
|
||||||
|
tmp.write(bytes(line, 'utf-8'))
|
||||||
|
if i == end - 1:
|
||||||
|
tmp.write(b'/** *INDENT-OFF* **/\n')
|
||||||
|
tmp.seek(0)
|
||||||
|
return tmp
|
||||||
|
|
||||||
|
# Removes uncrustify INDENT-ON/OFF helper comments
|
||||||
|
def remove_indent_comments(output):
|
||||||
|
tmp = tempfile.NamedTemporaryFile()
|
||||||
|
for line in output:
|
||||||
|
if line != b'/** *INDENT-OFF* **/\n' and line != b'/** *INDENT-ON* **/\n':
|
||||||
|
tmp.write(line)
|
||||||
|
|
||||||
|
tmp.seek(0)
|
||||||
|
return tmp
|
||||||
|
|
||||||
|
changed = None
|
||||||
|
for chunk in chunks:
|
||||||
|
# Add INDENT-ON/OFF comments
|
||||||
|
tmp = create_temp_file(chunk['file'], chunk['start'], chunk['end'])
|
||||||
|
|
||||||
|
# uncrustify chunk
|
||||||
|
proc = subprocess.run(
|
||||||
|
["uncrustify", "-c", uncrustify_cfg, "-f", tmp.name],
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
)
|
||||||
|
reindented = proc.stdout.splitlines(keepends=True)
|
||||||
|
if proc.returncode != 0:
|
||||||
|
continue
|
||||||
|
|
||||||
|
tmp.close()
|
||||||
|
|
||||||
|
# Remove INDENT-ON/OFF comments
|
||||||
|
formatted = remove_indent_comments(reindented)
|
||||||
|
|
||||||
|
if dry_run is True:
|
||||||
|
# Show changes
|
||||||
|
proc = subprocess.run(
|
||||||
|
["diff", "-up", "--color=always", chunk['file'], formatted.name],
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.STDOUT,
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
diff = proc.stdout
|
||||||
|
if diff != '':
|
||||||
|
output = re.sub('\t', '↦\t', diff)
|
||||||
|
print(output)
|
||||||
|
changed = True
|
||||||
|
else:
|
||||||
|
# Apply changes
|
||||||
|
diff = subprocess.run(
|
||||||
|
["diff", "-up", chunk['file'], formatted.name],
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.STDOUT,
|
||||||
|
)
|
||||||
|
subprocess.run(["patch", chunk['file']], input=diff.stdout)
|
||||||
|
|
||||||
|
formatted.close()
|
||||||
|
|
||||||
|
return changed
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv):
|
||||||
|
parser = argparse.ArgumentParser(description='Check code style. Needs uncrustify installed.')
|
||||||
|
parser.add_argument('--sha', metavar='SHA', type=str,
|
||||||
|
help='SHA for the commit to compare HEAD with')
|
||||||
|
parser.add_argument('--dry-run', '-d', type=bool,
|
||||||
|
action=argparse.BooleanOptionalAction,
|
||||||
|
help='Only print changes to stdout, do not change code')
|
||||||
|
parser.add_argument('--rewrite', '-r', type=bool,
|
||||||
|
action=argparse.BooleanOptionalAction,
|
||||||
|
help='Whether to amend the result to the last commit (e.g. \'git rebase --exec "%(prog)s -r"\')')
|
||||||
|
|
||||||
|
if not os.path.exists('.git'):
|
||||||
|
print("Not in toplevel of a git repository", fille=sys.stderr)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
args = parser.parse_args(argv)
|
||||||
|
sha = args.sha or 'HEAD^'
|
||||||
|
|
||||||
|
diff = run_diff(sha)
|
||||||
|
chunks = find_chunks(diff)
|
||||||
|
changed = reformat_chunks(chunks, args.rewrite, args.dry_run)
|
||||||
|
|
||||||
|
if args.dry_run is not True and args.rewrite is True:
|
||||||
|
proc = subprocess.run(["git", "add", "-p"])
|
||||||
|
if proc.returncode == 0:
|
||||||
|
# Commit the added changes as a squash commit
|
||||||
|
subprocess.run(
|
||||||
|
["git", "commit", "--squash", "HEAD", "-C", "HEAD"],
|
||||||
|
stdout=subprocess.DEVNULL)
|
||||||
|
# Delete the unapplied changes
|
||||||
|
subprocess.run(["git", "reset", "--hard"], stdout=subprocess.DEVNULL)
|
||||||
|
return 0
|
||||||
|
elif args.dry_run is True and changed is True:
|
||||||
|
print(f"""
|
||||||
|
Issue the following commands in your local tree to apply the suggested changes:
|
||||||
|
|
||||||
|
$ git rebase {sha} --exec "./.gitlab-ci/check-style.py -r"
|
||||||
|
$ git rebase --autosquash {sha}
|
||||||
|
|
||||||
|
Don't trust uncrustify unconditionally.
|
||||||
|
""")
|
||||||
|
return 1
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.exit(main(sys.argv[1:]))
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
# Indent by two spaces
|
||||||
|
indent_columns = 2
|
||||||
|
# No tabs
|
||||||
|
indent_with_tabs = 0
|
||||||
|
# Line length
|
||||||
|
code_width = 100
|
||||||
|
|
||||||
|
#
|
||||||
|
# Keywords and operators
|
||||||
|
#
|
||||||
|
# Add between 'do' and '{'.
|
||||||
|
sp_do_brace_open = add
|
||||||
|
# Add space between '}' and 'while'.
|
||||||
|
sp_brace_close_while = add
|
||||||
|
# Add 'while' and '('.
|
||||||
|
sp_while_paren_open = add
|
||||||
|
# Add or remove space around boolean operators '&&' and '||'.
|
||||||
|
sp_bool = add
|
||||||
|
# Ternary operator
|
||||||
|
sp_cond_ternary_short = remove
|
||||||
|
# Remove newline between 'struct and '{'.
|
||||||
|
nl_struct_brace = remove
|
||||||
|
# Remove newline between 'if' and '{'.
|
||||||
|
nl_if_brace = remove
|
||||||
|
# Remove newline between '}' and 'else'.
|
||||||
|
nl_brace_else = remove
|
||||||
|
# Remove newline between 'else' and '{'.
|
||||||
|
nl_else_brace = remove
|
||||||
|
# Remove newline between 'else' and 'if'.
|
||||||
|
nl_else_if = remove
|
||||||
|
# Add or remove newline between 'for' and '{'.
|
||||||
|
nl_for_brace = remove
|
||||||
|
# Treat iterators as for loops:
|
||||||
|
set FOR wl_list_for_each wl_list_for_each_reverse wl_list_for_each_safe
|
||||||
|
|
||||||
|
#
|
||||||
|
# Function declarations, defintions and calls
|
||||||
|
#
|
||||||
|
# Add space between function name and '(' on function declaration.
|
||||||
|
sp_func_proto_paren = add
|
||||||
|
# Add or remove space between function name and '()' on function declaration
|
||||||
|
# without parameters.
|
||||||
|
sp_func_proto_paren_empty = add
|
||||||
|
# Add space between function name and '(' on function definition.
|
||||||
|
sp_func_def_paren = add
|
||||||
|
# Add or remove space between function name and '(' on function calls.
|
||||||
|
sp_func_call_paren = add
|
||||||
|
# Whether to force indentation of function definitions to start in column 1.
|
||||||
|
indent_func_def_force_col1 = true
|
||||||
|
# Add newline between return type and function name in a function definition.
|
||||||
|
nl_func_type_name = add
|
||||||
|
# Add newline between function signature and '{'.
|
||||||
|
nl_fdef_brace = add
|
||||||
|
# Whether to align variable definitions in prototypes and functions.
|
||||||
|
align_func_params = true
|
||||||
|
# The span for aligning function prototypes.
|
||||||
|
align_func_proto_span = 8
|
||||||
|
# Add space between 'decltype(...)' and word.
|
||||||
|
sp_after_decltype = add
|
||||||
|
# Add or remove space after a pointer star '*', if followed by a function
|
||||||
|
# prototype or function definition.
|
||||||
|
sp_after_ptr_star_func = remove
|
||||||
|
# Add or remove newline between a function call's ')' and '{', as in
|
||||||
|
# 'list_for_each(item, &list) { }'.
|
||||||
|
nl_fcall_brace = add
|
||||||
|
|
||||||
|
#
|
||||||
|
# Typedefs
|
||||||
|
#
|
||||||
|
# Add space between '}' and the name of a typedef on the same line.
|
||||||
|
sp_brace_typedef = add
|
||||||
|
|
||||||
|
#
|
||||||
|
# Comments
|
||||||
|
#
|
||||||
|
# Add space after the opening of a C++ comment, i.e. '// A' vs. '//A'.
|
||||||
|
sp_cmt_cpp_start = add
|
||||||
|
|
||||||
|
#
|
||||||
|
# Preprocessor
|
||||||
|
#
|
||||||
|
# Add or remove space between #else or #endif and a trailing comment.
|
||||||
|
sp_endif_cmt = add
|
||||||
|
|
||||||
|
# Newlines at the start and end of the file.
|
||||||
|
nl_start_of_file = remove
|
||||||
|
nl_end_of_file = add
|
||||||
|
nl_end_of_file_min = 1
|
||||||
|
|
||||||
|
#
|
||||||
|
# Variable definitions
|
||||||
|
#
|
||||||
|
# How to align the '*' in variable definitions.
|
||||||
|
#
|
||||||
|
# 0: Part of the type 'void * foo;' (default)
|
||||||
|
# 1: Part of the variable 'void *foo;'
|
||||||
|
# 2: Dangling 'void *foo;'
|
||||||
|
# Dangling: the '*' will not be taken into account when aligning.
|
||||||
|
align_var_def_star_style = 2
|
||||||
|
# Same for typedefs
|
||||||
|
align_typedef_star_style = 2
|
||||||
|
# The gap for aligning struct/union member definitions.
|
||||||
|
align_var_struct_gap = 1
|
||||||
|
# The span for aligning struct/union member definitions.
|
||||||
|
align_var_struct_span = 8
|
||||||
|
# Remove space between pointer stars '*'.
|
||||||
|
sp_between_ptr_star = remove
|
||||||
|
# Add space before '(' of control statements ('if', 'for', 'switch', 'while', etc.)
|
||||||
|
sp_before_sparen = add
|
||||||
Reference in New Issue
Block a user