[tracker-miners/wip/carlosg/ci-updates: 3/6] ci: Add code style checks




commit b8fc8b1d92e327d34b720cecd70bb0cb682525f8
Author: Carlos Garnacho <carlosg gnome org>
Date:   Wed Aug 4 15:30:33 2021 +0200

    ci: Add code style checks

 .gitlab-ci.yml            |  14 +++++
 .gitlab-ci/uncrustify.cfg | 131 ++++++++++++++++++++++++++++++++++++++++++++
 check-style.py            | 134 ++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 279 insertions(+)
---
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index ee31ce018..d017fa1fb 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -18,6 +18,7 @@ variables:
 stages:
   - review
   - prepare
+  - code-review
   - build
   - test
   - analysis
@@ -162,6 +163,19 @@ build-ubuntu-container@x86_64:
     - check-commit-log
     - check-merge-request
 
+check-code-style:
+  extends:
+    - .fdo.distribution-image@fedora
+    - .tracker-miners.fedora:34@x86_64
+  needs:
+    - build-fedora-container@x86_64
+  stage: code-review
+  script:
+    - git fetch origin master;
+      export common_parent_sha=$(diff --old-line-format='' --new-line-format='' <(git rev-list 
--first-parent "origin/${CI_MERGE_REQUEST_TARGET_BRANCH_NAME:-${CI_DEFAULT_BRANCH}}") <(git rev-list 
--first-parent HEAD) | head -1) ;
+      python3 -u ./check-style.py --dry-run --sha $common_parent_sha ;
+  allow_failure: true
+
 .build-template: &build
   stage: build
   script:
diff --git a/.gitlab-ci/uncrustify.cfg b/.gitlab-ci/uncrustify.cfg
new file mode 100644
index 000000000..86d9a03f5
--- /dev/null
+++ b/.gitlab-ci/uncrustify.cfg
@@ -0,0 +1,131 @@
+# Configuration for formatting Tracker source code using the
+# 'uncrustify' indent tool.
+#
+# Run `uncrustify --show-config` to see documentation for these options.
+#
+# See also: https://wiki.gnome.org/Projects/Tracker/Documentation/CodingStyle
+
+#################################################################################
+# CHANGES
+#
+# The first part of this file controls what automated changes Uncrustify makes.
+#################################################################################
+
+# Files are in UTF-8
+utf8_force = true
+
+# Unix style newlines
+newlines = lf
+
+# We use tabs to indent up to brace level, but spaces to align continuation
+# lines. For example:
+#
+#     int main() {
+#     --->printf ("This is a long string "
+#     --->        "which continues onto a second line.");
+#     };
+#
+align_with_tabs = false
+indent_with_tabs = 1
+
+# We align parameters in function definitions, like this:
+#
+#     gdouble tracker_string_to_date (const gchar  *date_string,
+#                                     gint         *offset_p,
+#                                     GError      **error)
+#
+align_func_params = true
+
+# A '*' in a variable definition is considered 'dangling', rather than
+# being part of the variable type. This produces the following style of
+# alignment:
+#
+#    tracker_string_to_date (const gchar  *date_string,
+#                            gint         *offset_p,
+#                            GError      **error)
+#
+align_var_def_star_style = 2  # dangling
+
+# Keep extra spaces which uncrustify thinks are not needed for alignment.
+#
+# This causes uncrustify to preserve a lot more of the existing alignment
+# in Tracker's source code, for example we can keep this:
+#
+#    tracker_string_to_date (const gchar  *date_string,
+#                            gint         *offset_p,
+#                            GError      **error)
+#
+# Instead of it being changed to this:
+#
+#    tracker_string_to_date (const gchar *date_string,
+#                            gint        *offset_p,
+#                            GError     **error)
+#
+# Because this setting is enabled, the uncrustify process is not
+# idempodent with regards to variable alignment because we still have some
+# extra alignment in the sourcecode which uncrustify did not insert, and
+# rerunning uncrustify with different settings might remove those extra spaces.
+align_keep_extra_space = true
+
+# Ensure arithmetic operators are properly spaced, e.g:
+# foo = 1 + (2 / 4);
+sp_arith = force
+sp_arith_additive = force
+
+# Ensure spaces between assignments, e.g.:
+# foo = 2;
+# foo += 2;
+sp_assign = force
+
+# Ensure there is space between '*' and '(', e.g.:
+# typedef GNode* (* ForeachFunc) (...);
+sp_ptr_star_paren = remove
+
+# Remove spaces between '*', e.g.:
+# gchar **foo;
+sp_between_ptr_star = remove
+
+#################################################################################
+# IGNORES
+#
+# The second part of this file controls what Uncrustify ignores.
+#################################################################################
+
+# Disable auto-alignment of macros, we manually align the \ with
+# spaces which uncrustify doesn't support.
+align_nl_cont = false
+
+# Ignore spacing multiline comments.
+cmt_indent_multi = false
+
+# Ignore spacing around = operator (and -=, etc).
+sp_after_assign = ignore
+sp_before_assign = ignore
+
+# Ignore space after casts like `(int)foo`
+sp_after_cast = ignore
+
+# Ignore spacing around pointer stars.
+sp_after_ptr_star = ignore
+
+# Ignore spaces after ; in for (; ; ;) statements.
+sp_after_semi_for = ignore
+sp_after_semi_for_empty = ignore
+
+# Ignore spacing around ++ / -- operators.
+sp_incdec = ignore
+
+# Ignore Space after ! (not) operator, for example:
+#
+#     if (!home) {
+#
+sp_not = ignore
+
+# Ignore space around preprocessor '##' operator. We might want a space before
+# and no space after, for example in this:
+#
+#     #define trace(message, ...) \
+#         g_debug (message, ##__VA_ARGS__)
+#
+sp_pp_concat = ignore
+
diff --git a/check-style.py b/check-style.py
new file mode 100755
index 000000000..52891a1a6
--- /dev/null
+++ b/check-style.py
@@ -0,0 +1,134 @@
+#!/bin/env python3
+
+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.Popen(["git", "diff", "-U0", "--function-context", sha, "HEAD"], 
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+    files = proc.stdout.read().strip().decode('utf-8')
+    return files.split('\n')
+
+def find_chunks(diff):
+    file_entry_re = re.compile('^\+\+\+ b/(.*)$')
+    diff_chunk_re = re.compile('^@@ -\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):
+    # Creates temp file with INDENT-ON/OFF comments
+    def create_temp_file(file, start, end):
+        with open(file) as f:
+            tmp = tempfile.NamedTemporaryFile()
+            tmp.write(b'/** *INDENT-OFF* **/\n')
+            for i, line in enumerate(f):
+                if i == start - 2:
+                    tmp.write(b'/** *INDENT-ON* **/\n')
+
+                tmp.write(bytes(line, 'utf-8'))
+
+                if i == end - 2:
+                    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.Popen(["uncrustify", "-c", uncrustify_cfg, "-f", tmp.name], 
stdout=subprocess.PIPE, stderr=subprocess.DEVNULL)
+        reindented = proc.stdout.readlines()
+        tmp.close()
+
+        # Remove INDENT-ON/OFF comments
+        formatted = remove_indent_comments(reindented)
+
+        if dry_run is True:
+            # Show changes
+            proc = subprocess.Popen(["diff", "-up", "--color=always", chunk['file'], formatted.name], 
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+            diff = proc.stdout.read().decode('utf-8')
+            if diff != '':
+                output = re.sub('\t', '↦\t', diff)
+                print(output)
+                changed = True
+        else:
+            # Apply changes
+            diff = subprocess.Popen(["diff", "-up", chunk['file'], formatted.name], stdout=subprocess.PIPE, 
stderr=subprocess.STDOUT)
+            patch = subprocess.Popen(["patch", chunk['file']], stdin=diff.stdout)
+            diff.stdout.close()
+            patch.communicate()
+
+        formatted.close()
+
+    return changed
+
+
+parser = argparse.ArgumentParser(description='Check code style.')
+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"\')')
+
+# Change CWD to script location, necessary for always locating the configuration file
+os.chdir(os.path.dirname(os.path.abspath(sys.argv[0])))
+
+args = parser.parse_args()
+sha = args.sha or 'HEAD^'
+rewrite = args.rewrite
+dry_run = args.dry_run
+
+diff = run_diff(sha)
+chunks = find_chunks(diff)
+changed = reformat_chunks(chunks, rewrite)
+
+if dry_run is not True and rewrite is True:
+    proc = subprocess.Popen(["git", "commit", "--all", "--amend", "-C", "HEAD"], stdout=subprocess.DEVNULL)
+    os._exit(0)
+elif dry_run is True and changed is True:
+    print ("\nIssue the following command in your local tree to apply the suggested changes (needs 
uncrustify installed):\n\n $ git rebase origin/master --exec \"./check-style.py -r\" \n")
+    os._exit(-1)
+
+os._exit(0)


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]