From e95f47c395dec41a59adec7694d940f2557e3667 Mon Sep 17 00:00:00 2001 From: Pavlo Solntsev Date: Mon, 11 Nov 2019 14:21:25 -0600 Subject: [PATCH] CI: Code check formating in CI --- .clang-format | 11 +++ .gitlab-ci.yml | 10 +++ .gitlab-ci/debian-stable.Dockerfile | 1 + clang-format-diff.py | 129 ++++++++++++++++++++++++++++ 4 files changed, 151 insertions(+) create mode 100644 .clang-format create mode 100755 clang-format-diff.py diff --git a/.clang-format b/.clang-format new file mode 100644 index 000000000..13fd0fbd4 --- /dev/null +++ b/.clang-format @@ -0,0 +1,11 @@ +# See https://wiki.apertis.org/Guidelines/Coding_conventions#Code_formatting +BasedOnStyle: GNU +AlwaysBreakAfterDefinitionReturnType: All +BreakBeforeBinaryOperators: None +BinPackParameters: false +SpaceAfterCStyleCast: true +# Our column limit is actually 80, but setting that results in clang-format +# making a lot of dubious hanging-indent choices; disable it and assume the +# developer will line wrap appropriately. clang-format will still check +# existing hanging indents. +ColumnLimit: 0 diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 3ec1e53e8..62ddfd5d2 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -3,6 +3,7 @@ stages: - coverage - analysis - deploy + - style-check cache: paths: @@ -13,6 +14,15 @@ variables: G_MESSAGES_DEBUG: all MESON_COMMON_OPTIONS: "--buildtype debug --fatal-meson-warnings" +style-check-diff: + image: registry.gitlab.gnome.org/gnome/glib/debian-stable:v5 + stage: style-check + allow_failure: true + only: + - merge_request + script: + - git diff -U0 --no-color ${CI_MERGE_REQUEST_TARGET_BRANCH_SHA} | ./clang-format-diff.py -binary "clang-format-7" -p1 + fedora-x86_64: image: registry.gitlab.gnome.org/gnome/glib/fedora:v4 stage: build diff --git a/.gitlab-ci/debian-stable.Dockerfile b/.gitlab-ci/debian-stable.Dockerfile index 7841bedd0..fe3d51f57 100644 --- a/.gitlab-ci/debian-stable.Dockerfile +++ b/.gitlab-ci/debian-stable.Dockerfile @@ -4,6 +4,7 @@ RUN apt-get update -qq && apt-get install --no-install-recommends -qq -y \ bindfs \ clang \ clang-tools-7 \ + clang-format-7 \ dbus \ desktop-file-utils \ elfutils \ diff --git a/clang-format-diff.py b/clang-format-diff.py new file mode 100755 index 000000000..2f9780d41 --- /dev/null +++ b/clang-format-diff.py @@ -0,0 +1,129 @@ +#!/usr/bin/env python3 +# +#===- clang-format-diff.py - ClangFormat Diff Reformatter ----*- python -*--===# +# +# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +# See https://llvm.org/LICENSE.txt for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +# +#===------------------------------------------------------------------------===# + +""" +This script reads input from a unified diff and reformats all the changed +lines. This is useful to reformat all the lines touched by a specific patch. +Example usage for git/svn users: + + git diff -U0 --no-color HEAD^ | clang-format-diff.py -p1 -i + svn diff --diff-cmd=diff -x-U0 | clang-format-diff.py -i + +""" +from __future__ import absolute_import, division, print_function + +import argparse +import difflib +import re +import subprocess +import sys + +if sys.version_info.major >= 3: + from io import StringIO +else: + from io import BytesIO as StringIO + + +def main(): + parser = argparse.ArgumentParser(description=__doc__, + formatter_class= + argparse.RawDescriptionHelpFormatter) + parser.add_argument('-i', action='store_true', default=False, + help='apply edits to files instead of displaying a diff') + parser.add_argument('-p', metavar='NUM', default=0, + help='strip the smallest prefix containing P slashes') + parser.add_argument('-regex', metavar='PATTERN', default=None, + help='custom pattern selecting file paths to reformat ' + '(case sensitive, overrides -iregex)') + parser.add_argument('-iregex', metavar='PATTERN', default= + r'.*\.(cpp|cc|c\+\+|cxx|c|cl|h|hh|hpp|m|mm|inc|js|ts|proto' + r'|protodevel|java|cs)', + help='custom pattern selecting file paths to reformat ' + '(case insensitive, overridden by -regex)') + parser.add_argument('-sort-includes', action='store_true', default=False, + help='let clang-format sort include blocks') + parser.add_argument('-v', '--verbose', action='store_true', + help='be more verbose, ineffective without -i') + parser.add_argument('-style', + help='formatting style to apply (LLVM, Google, Chromium, ' + 'Mozilla, WebKit)') + parser.add_argument('-binary', default='clang-format', + help='location of binary to use for clang-format') + args = parser.parse_args() + + # Extract changed lines for each file. + filename = None + lines_by_file = {} + for line in sys.stdin: + match = re.search(r'^\+\+\+\ (.*?/){%s}(\S*)' % args.p, line) + if match: + filename = match.group(2) + if filename == None: + continue + + if args.regex is not None: + if not re.match('^%s$' % args.regex, filename): + continue + else: + if not re.match('^%s$' % args.iregex, filename, re.IGNORECASE): + continue + + match = re.search(r'^@@.*\+(\d+)(,(\d+))?', line) + if match: + start_line = int(match.group(1)) + line_count = 1 + if match.group(3): + line_count = int(match.group(3)) + if line_count == 0: + continue + end_line = start_line + line_count - 1 + lines_by_file.setdefault(filename, []).extend( + ['-lines', str(start_line) + ':' + str(end_line)]) + + # Reformat files containing changes in place. + # We need to count amount of bytes generated in the output of clang-format-diff + # If clang-format-diff doesn't generate any bytes it means there is nothing to format. + format_line_counter=0 + for filename, lines in lines_by_file.items(): + if args.i and args.verbose: + print('Formatting {}'.format(filename)) + command = [args.binary, filename] + if args.i: + command.append('-i') + if args.sort_includes: + command.append('-sort-includes') + command.extend(lines) + if args.style: + command.extend(['-style', args.style]) + p = subprocess.Popen(command, + stdout=subprocess.PIPE, + stderr=None, + stdin=subprocess.PIPE, + universal_newlines=True) + stdout, stderr = p.communicate() + if p.returncode != 0: + sys.exit(p.returncode) + + if not args.i: + with open(filename) as f: + code = f.readlines() + formatted_code = StringIO(stdout).readlines() + diff = difflib.unified_diff(code, formatted_code, + filename, filename, + '(before formatting)', '(after formatting)') + diff_string = ''.join(diff) + if len(diff_string) > 0: + format_line_counter += sys.stdout.write(diff_string) + + if format_line_counter > 0: + sys.exit(1) + +if __name__ == '__main__': + main()